Laravel version update

Laravel version update
This commit is contained in:
Manish Verma
2018-08-06 18:48:58 +05:30
parent d143048413
commit 126fbb0255
13678 changed files with 1031482 additions and 778530 deletions

View File

@@ -1,12 +0,0 @@
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

View File

@@ -1,6 +0,0 @@
build-vendor/
vendor/
composer.lock
manual/
__pycache__
.php_cs.cache

View File

@@ -1,43 +0,0 @@
<?php
use Symfony\CS\Config\Config;
use Symfony\CS\FixerInterface;
use Symfony\CS\Fixer\Contrib\HeaderCommentFixer;
$header = <<<EOF
This file is part of Psy Shell.
(c) 2012-2015 Justin Hileman
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
EOF;
HeaderCommentFixer::setHeader($header);
$config = Config::create()
// use symfony level and extra fixers:
->level(FixerInterface::SYMFONY_LEVEL)
->fixers(array(
'align_double_arrow',
'concat_with_spaces',
'header_comment',
'long_array_syntax',
'ordered_use',
'strict',
'-concat_without_spaces',
'-method_argument_space',
'-pre_increment',
'-unalign_double_arrow',
'-unalign_equals',
))
->setUsingLinter(false);
$finder = $config->getFinder()
->in(__DIR__)
->name('.php_cs')
->name('build-manual')
->name('build-phar')
->exclude('build-vendor');
return $config;

View File

@@ -1,22 +0,0 @@
preset: symfony
enabled:
- align_double_arrow
- concat_with_spaces
- long_array_syntax
- ordered_use
- strict
disabled:
- concat_without_spaces
- method_argument_space
- pre_increment
- unalign_double_arrow
- unalign_equals
finder:
name:
- "*.php"
- ".php_cs"
- "build-manual"
- "build-phar"

View File

@@ -1,24 +0,0 @@
language: php
php:
- 5.3
- 5.4
- 5.5
- 5.6
- 7.0
- hhvm
install:
- travis_retry composer install --no-interaction --prefer-source
- '[ -z "$MIN_VERSIONS" ] || composer require --no-interaction --prefer-source $MIN_VERSIONS'
script:
- vendor/bin/phpunit
matrix:
fast_finish: true
include:
- php: 5.3
env: MIN_VERSIONS="symfony/console:2.3.10 symfony/var-dumper:2.7.0 nikic/php-parser:1.2.1 jakub-onderka/php-console-highlighter:0.3.0"
allow_failures:
- env: MIN_VERSIONS="symfony/console:2.3.10 symfony/var-dumper:2.7.0 nikic/php-parser:1.2.1 jakub-onderka/php-console-highlighter:0.3.0"

View File

@@ -1,18 +0,0 @@
## Code style
Please make your code look like the other code in the project. PsySH follows [PSR-1](http://php-fig.org/psr/psr-1/) and [PSR-2](http://php-fig.org/psr/psr-2/). The easiest way to do make sure you're following the coding standard is to run `vendor/bin/php-cs-fixer fix` before committing.
## Branching model
Please branch off and send pull requests to the `develop` branch.
## Building the manual
```sh
svn co https://svn.php.net/repository/phpdoc/en/trunk/reference/ php_manual
bin/build_manual phpdoc_manual ~/.local/share/psysh/php_manual.sqlite
```
To build the manual for another language, switch out `en` above for `de`, `es`, or any of the other languages listed in the README.
[Partial or outdated documentation is available for other languages](http://www.php.net/manual/help-translate.php) but these translations are outdated, so their content may be completely wrong or insecure!

View File

@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2012-2015 Justin Hileman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,179 +0,0 @@
# PsySH
[![Package version](https://img.shields.io/packagist/v/psy/psysh.svg?style=flat-square)](https://packagist.org/packages/psy/psysh)
[![Monthly downloads](http://img.shields.io/packagist/dm/psy/psysh.svg?style=flat-square)](https://packagist.org/packages/psy/psysh)
[![Made out of awesome](https://img.shields.io/badge/made_out_of_awesome-✓-brightgreen.svg?style=flat-square)](http://psysh.org)
[![Build status](https://img.shields.io/travis/bobthecow/psysh/master.svg?style=flat-square)](http://travis-ci.org/bobthecow/psysh)
[![StyleCI](https://styleci.io/repos/4549925/shield)](https://styleci.io/repos/4549925)
## About
PsySH is a runtime developer console, interactive debugger and [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop) for PHP. Learn more at [psysh.org](http://psysh.org/). Check out the [Interactive Debugging in PHP talk from OSCON](https://presentate.com/bobthecow/talks/php-for-pirates) on Presentate.
## Installation
Download the `psysh` phar to install:
```
wget psysh.org/psysh
chmod +x psysh
./psysh
```
It's even awesomer if you put it somewhere in your system path (like `/usr/local/bin` or `~/bin`)!
PsySH [is available via Composer](https://packagist.org/packages/psy/psysh), so you can use it in your project as well:
```
composer require psy/psysh:@stable
./vendor/bin/psysh
```
Or you can use by checking out the the repository directly:
```
git clone https://github.com/bobthecow/psysh.git
cd psysh
./bin/psysh
```
## PsySH configuration
While PsySH strives to detect the right settings automatically, you might want to configure it yourself. Just add a file to `~/.config/psysh/config.php` (or `C:\Users\{USER}\AppData\Roaming\PsySH\config.php` on Windows):
```php
<?php
return array(
// In PHP 5.4+, PsySH will default to your `cli.pager` ini setting. If this
// is not set, it falls back to `less`. It is recommended that you set up
// `cli.pager` in your `php.ini` with your preferred output pager.
//
// If you are running PHP 5.3, or if you want to use a different pager only
// for Psy shell sessions, you can override it here.
'pager' => 'more',
// Sets the maximum number of entries the history can contain.
// If set to zero, the history size is unlimited.
'historySize' => 0,
// If set to true, the history will not keep duplicate entries.
// Newest entries override oldest.
// This is the equivalent of the HISTCONTROL=erasedups setting in bash.
'eraseDuplicates' => false,
// By default, PsySH will use a 'forking' execution loop if pcntl is
// installed. This is by far the best way to use it, but you can override
// the default by explicitly enabling or disabling this functionality here.
'usePcntl' => false,
// PsySH uses readline if you have it installed, because interactive input
// is pretty awful without it. But you can explicitly disable it if you hate
// yourself or something.
'useReadline' => false,
// PsySH automatically inserts semicolons at the end of input if a statement
// is missing one. To disable this, set `requireSemicolons` to true.
'requireSemicolons' => true,
// PsySH uses a couple of UTF-8 characters in its own output. These can be
// disabled, mostly to work around code page issues. Because Windows.
//
// Note that this does not disable Unicode output in general, it just makes
// it so PsySH won't output any itself.
'useUnicode' => false,
// While PsySH respects the current `error_reporting` level, and doesn't throw
// exceptions for all errors, it does log all errors regardless of level. Set
// `errorLoggingLevel` to 0 to prevent logging non-thrown errors. Set it to any
// valid `error_reporting` value to log only errors which match that level.
'errorLoggingLevel' => E_ALL & ~E_NOTICE,
// "Default includes" will be included once at the beginning of every PsySH
// session. This is a good place to add autoloaders for your favorite
// libraries.
'defaultIncludes' => array(
__DIR__ . '/include/bootstrap.php',
),
// While PsySH ships with a bunch of great commands, it's possible to add
// your own for even more awesome. Any Psy command added here will be
// available in your Psy shell sessions.
'commands' => array(
// The `parse` command is a command used in the development of PsySH.
// Given a string of PHP code, it pretty-prints the
// [PHP Parser](https://github.com/nikic/PHP-Parser) parse tree. It
// prolly won't be super useful for most of you, but it's there if you
// want to play :)
new \Psy\Command\ParseCommand,
),
// PsySH uses symfony/var-dumper's casters for presenting scalars, resources,
// arrays and objects. You can enable additional casters, or write your own!
// See http://symfony.com/doc/current/components/var_dumper/advanced.html#casters
'casters' => array(
'MyFooClass' => 'MyFooClassCaster::castMyFooObject',
),
// You can disable tab completion if you want to. Not sure why you'd want to.
'tabCompletion' => false,
// You can write your own tab completion matchers, too! Here are some that enable
// tab completion for MongoDB database and collection names:
'tabCompletionMatchers' => array(
new \Psy\TabCompletion\Matcher\MongoClientMatcher,
new \Psy\TabCompletion\Matcher\MongoDatabaseMatcher,
),
// If multiple versions of the same configuration or data file exist, PsySH will
// use the file with highest precedence, and will silently ignore all others. With
// this enabled, a warning will be emitted (but not an exception thrown) if multiple
// configuration or data files are found.
//
// This will default to true in a future release, but is false for now.
'warnOnMultipleConfigs' => true,
// By default, output contains colors if support for them is detected. To override:
'colorMode' => \Psy\Configuration::COLOR_MODE_FORCED, // force colors in output
'colorMode' => \Psy\Configuration::COLOR_MODE_DISABLED, // disable colors in output
);
```
## Downloading the manual
The PsySH `doc` command is great for documenting source code, but you'll need a little something extra for PHP core documentation. Download one of the following PHP Manual files and drop it in `~/.local/share/psysh/`, `/usr/local/share/psysh/` or `C:\Users\{USER}\AppData\Roaming\PsySH\` on Windows:
* **[English](http://psysh.org/manual/en/php_manual.sqlite)**
* [Brazilian Portuguese](http://psysh.org/manual/pt_BR/php_manual.sqlite)
* [Chinese (Simplified)](http://psysh.org/manual/zh/php_manual.sqlite)
* [French](http://psysh.org/manual/fr/php_manual.sqlite)
* [German](http://psysh.org/manual/de/php_manual.sqlite)
* [Italian](http://psysh.org/manual/it/php_manual.sqlite)
* [Japanese](http://psysh.org/manual/ja/php_manual.sqlite)
* [Polish](http://psysh.org/manual/pl/php_manual.sqlite)
* [Romanian](http://psysh.org/manual/ro/php_manual.sqlite)
* [Russian](http://psysh.org/manual/ru/php_manual.sqlite)
* [Persian](http://psysh.org/manual/fa/php_manual.sqlite)
* [Spanish](http://psysh.org/manual/es/php_manual.sqlite)
* [Turkish](http://psysh.org/manual/tr/php_manual.sqlite)
## As Seen On…
* Cake: [`cake console`](http://book.cakephp.org/3.0/en/console-and-shells/repl.html)
* Drupal: [`drush php`](http://drushcommands.com/drush-8x/core/core-cli/), [drush-psysh](https://github.com/grota/drush-psysh)
* eZ Publish: [`ezsh`](https://github.com/lolautruche/ezsh)
* Laravel: [`artisan tinker`](https://github.com/laravel/framework/blob/5.0/src/Illuminate/Foundation/Console/TinkerCommand.php)
* Lumen: [`artisan tinker`](https://github.com/vluzrmos/lumen-tinker)
* Magento: [`magerun console`](https://github.com/netz98/n98-magerun/blob/develop/src/N98/Magento/Command/Developer/ConsoleCommand.php)
* Pantheon CLI: [`terminus cli console`](https://github.com/pantheon-systems/terminus)
* Symfony: [sf1-psysh-bootstrap](https://github.com/varas/sf1-psysh-bootstrap)
* Symfony2: [`psymf`](https://github.com/navitronic/psymf), [sf2-psysh-bootstrap](https://github.com/varas/sf2-psysh-bootstrap), [symfony-repl](https://github.com/luxifer/symfony-repl), [PsyshBundle](https://github.com/theofidry/PsyshBundle)
* WordPress: [`wp-cli shell`](https://github.com/wp-cli/wp-cli/blob/master/php/commands/shell.php)
* Zend Framework 2: [PsyshModule](https://zfmodules.com/gianarb/zf2-psysh-module)

View File

@@ -1,6 +0,0 @@
#!/usr/bin/env bash
cd "${BASH_SOURCE%/*}/.."
./bin/build-vendor || exit 1
./bin/build-phar

View File

@@ -1,285 +0,0 @@
#!/usr/bin/env php
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
define('WRAP_WIDTH', 100);
$count = 0;
if (count($argv) !== 3 || !is_dir($argv[1])) {
echo "usage: build_manual path/to/manual output_filename.db\n";
exit(1);
}
function htmlwrap($text, $width = null)
{
if ($width === null) {
$width = WRAP_WIDTH;
}
$len = strlen($text);
$return = array();
$lastSpace = null;
$inTag = false;
$i = $tagWidth = 0;
do {
switch (substr($text, $i, 1)) {
case "\n":
$return[] = trim(substr($text, 0, $i));
$text = substr($text, $i);
$len = strlen($text);
$i = $lastSpace = 0;
continue;
case ' ':
if (!$inTag) {
$lastSpace = $i;
}
break;
case '<':
$inTag = true;
break;
case '>':
$inTag = false;
default:
}
if ($inTag) {
$tagWidth++;
}
$i++;
if (!$inTag && ($i - $tagWidth > $width)) {
$lastSpace = $lastSpace ?: $width;
$return[] = trim(substr($text, 0, $lastSpace));
$text = substr($text, $lastSpace);
$len = strlen($text);
$i = $tagWidth = 0;
}
} while ($i < $len);
$return[] = trim($text);
return implode("\n", $return);
}
function extract_paragraphs($element)
{
$paragraphs = array();
foreach ($element->getElementsByTagName('para') as $p) {
$text = '';
foreach ($p->childNodes as $child) {
// @todo: figure out if there's something we can do with tables.
if ($child instanceof DOMElement && $child->tagName === 'table') {
continue;
}
// skip references, because ugh.
if (preg_match('{^\s*&[a-z][a-z\.]+;\s*$}', $child->textContent)) {
continue;
}
$text .= $child->ownerDocument->saveXML($child);
}
if ($text = trim(preg_replace('{\n[ \t]+}', ' ', $text))) {
$paragraphs[] = $text;
}
}
return implode("\n\n", $paragraphs);
}
function format_doc($doc)
{
$chunks = array();
if (!empty($doc['description'])) {
$chunks[] = '<comment>Description:</comment>';
$chunks[] = indent_text(htmlwrap(thunk_tags($doc['description']), WRAP_WIDTH - 2));
$chunks[] = '';
}
if (!empty($doc['params'])) {
$chunks[] = '<comment>Param:</comment>';
$typeMax = max(array_map(function ($param) {
return strlen($param['type']);
}, $doc['params']));
$max = max(array_map(function ($param) {
return strlen($param['name']);
}, $doc['params']));
$template = ' <info>%-' . $typeMax . 's</info> <strong>%-' . $max . 's</strong> %s';
$indent = str_repeat(' ', $typeMax + $max + 6);
$wrapWidth = WRAP_WIDTH - strlen($indent);
foreach ($doc['params'] as $param) {
$desc = indent_text(htmlwrap(thunk_tags($param['description']), $wrapWidth), $indent, false);
$chunks[] = sprintf($template, $param['type'], $param['name'], $desc);
}
$chunks[] = '';
}
if (isset($doc['return']) || isset($doc['return_type'])) {
$chunks[] = '<comment>Return:</comment>';
$type = isset($doc['return_type']) ? $doc['return_type'] : 'unknown';
$desc = isset($doc['return']) ? $doc['return'] : '';
$indent = str_repeat(' ', strlen($type) + 4);
$wrapWidth = WRAP_WIDTH - strlen($indent);
if (!empty($desc)) {
$desc = indent_text(htmlwrap(thunk_tags($doc['return']), $wrapWidth), $indent, false);
}
$chunks[] = sprintf(' <info>%s</info> %s', $type, $desc);
$chunks[] = '';
}
array_pop($chunks); // get rid of the trailing newline
return implode("\n", $chunks);
}
function thunk_tags($text)
{
$tagMap = array(
'parameter>' => 'strong>',
'function>' => 'strong>',
'literal>' => 'return>',
'type>' => 'info>',
'constant>' => 'info>',
);
$andBack = array(
'&amp;' => '&',
'&amp;true;' => '<return>true</return>',
'&amp;false;' => '<return>false</return>',
'&amp;null;' => '<return>null</return>',
);
return strtr(strip_tags(strtr($text, $tagMap), '<strong><return><info>'), $andBack);
}
function indent_text($text, $indent = ' ', $leading = true)
{
return ($leading ? $indent : '') . str_replace("\n", "\n" . $indent, $text);
}
function find_type($xml, $paramName)
{
foreach ($xml->getElementsByTagName('methodparam') as $param) {
if ($type = $param->getElementsByTagName('type')->item(0)) {
if ($parameter = $param->getElementsByTagName('parameter')->item(0)) {
if ($paramName === $parameter->textContent) {
return $type->textContent;
}
}
}
}
}
$docs = array();
foreach (glob($argv[1] . '/*/*/*.xml') as $function) {
$funcname = basename($function);
if ($funcname === 'main.xml' || strpos($funcname, 'entities.') === 0) {
continue;
}
$xmlstr = str_replace('&', '&amp;', file_get_contents($function));
$xml = new DOMDocument();
$xml->preserveWhiteSpace = false;
if (!@$xml->loadXml($xmlstr)) {
echo "XML Parse Error: $function\n";
continue;
}
$doc = array();
$refsect1s = $xml->getElementsByTagName('refsect1');
foreach ($refsect1s as $refsect1) {
$role = $refsect1->getAttribute('role');
switch ($role) {
case 'description':
$doc['description'] = extract_paragraphs($refsect1);
if ($synopsis = $refsect1->getElementsByTagName('methodsynopsis')->item(0)) {
foreach ($synopsis->childNodes as $node) {
if ($node instanceof DOMElement && $node->tagName === 'type') {
$doc['return_type'] = $node->textContent;
break;
}
}
}
break;
case 'returnvalues':
// do nothing.
$doc['return'] = extract_paragraphs($refsect1);
break;
case 'parameters':
$params = array();
$vars = $refsect1->getElementsByTagName('varlistentry');
foreach ($vars as $var) {
if ($name = $var->getElementsByTagName('parameter')->item(0)) {
$params[] = array(
'name' => '$' . $name->textContent,
'type' => find_type($xml, $name->textContent),
'description' => extract_paragraphs($var),
);
}
}
$doc['params'] = $params;
break;
}
}
// and the purpose
if ($purpose = $xml->getElementsByTagName('refpurpose')->item(0)) {
$desc = htmlwrap($purpose->textContent);
if (isset($doc['description'])) {
$desc .= "\n\n" . $doc['description'];
}
$doc['description'] = trim($desc);
}
$formatted = format_doc($doc);
foreach ($xml->getElementsByTagName('refname') as $ref) {
$docs[$ref->textContent] = $formatted;
}
}
if (is_file($argv[2])) {
unlink($argv[2]);
}
$db = new PDO('sqlite:' . $argv[2]);
$db->query('CREATE TABLE php_manual (id char(256) PRIMARY KEY, doc TEXT)');
$cmd = $db->prepare('INSERT INTO php_manual (id, doc) VALUES (?, ?)');
foreach ($docs as $id => $doc) {
$cmd->execute(array($id, $doc));
}

View File

@@ -1,33 +0,0 @@
#!/usr/bin/env php
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
if (!is_file(dirname(__DIR__) . '/vendor/autoload.php')) {
throw new RuntimeException('Missing PsySH dev dependencies in ' . dirname(__DIR__) . '/vendor/' . ', install with `composer.phar install --dev`.');
}
require dirname(__DIR__) . '/vendor/autoload.php';
if (!class_exists('Symfony\Component\Finder\Finder')) {
throw new RuntimeException('Missing PsySH dev dependencies, install with `composer.phar install --dev`.');
}
if (!is_file(dirname(__DIR__) . '/build-vendor/autoload.php')) {
throw new RuntimeException('Missing phar vendor dependencies, install with bin/build-vendor');
}
use Psy\Compiler;
error_reporting(-1);
ini_set('display_errors', 1);
$compiler = new Compiler();
$compiler->compile();

View File

@@ -1,7 +0,0 @@
#!/usr/bin/env bash
cd "${BASH_SOURCE%/*}/.."
rm -rf build-vendor
COMPOSER_VENDOR_DIR=build-vendor composer install --ignore-platform-reqs --no-dev --no-progress --classmap-authoritative

View File

@@ -1,135 +0,0 @@
#!/usr/bin/env php
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
// Try to find an autoloader for a local psysh version.
// We'll wrap this whole mess in a Closure so it doesn't leak any globals.
call_user_func(function () {
$cwd = null;
// Find the cwd arg (if present)
$argv = isset($_SERVER['argv']) ? $_SERVER['argv'] : array();
foreach ($argv as $i => $arg) {
if ($arg === '--cwd') {
if ($i >= count($argv) - 1) {
echo 'Missing --cwd argument.' . PHP_EOL;
exit(1);
}
$cwd = $argv[$i + 1];
break;
}
if (preg_match('/^--cwd=/', $arg)) {
$cwd = substr($arg, 6);
break;
}
}
// Or fall back to the actual cwd
if (!isset($cwd)) {
$cwd = getcwd();
}
$cwd = str_replace('\\', '/', $cwd);
$chunks = explode('/', $cwd);
while (!empty($chunks)) {
$path = implode('/', $chunks);
// Find composer.json
if (is_file($path . '/composer.json')) {
if ($cfg = json_decode(file_get_contents($path . '/composer.json'), true)) {
if (isset($cfg['name']) && $cfg['name'] === 'psy/psysh') {
// We're inside the psysh project. Let's use the local
// Composer autoload.
if (is_file($path . '/vendor/autoload.php')) {
require $path . '/vendor/autoload.php';
}
return;
}
}
}
// Or a composer.lock
if (is_file($path . '/composer.lock')) {
if ($cfg = json_decode(file_get_contents($path . '/composer.lock'), true)) {
foreach (array_merge($cfg['packages'], $cfg['packages-dev']) as $pkg) {
if (isset($pkg['name']) && $pkg['name'] === 'psy/psysh') {
// We're inside a project which requires psysh. We'll
// use the local Composer autoload.
if (is_file($path . '/vendor/autoload.php')) {
require $path . '/vendor/autoload.php';
}
return;
}
}
}
}
array_pop($chunks);
}
});
// We didn't find an autoloader for a local version, so use the autoloader that
// came with this script.
if (!class_exists('Psy\Shell')) {
/* <<< */
if (is_file(__DIR__ . '/../vendor/autoload.php')) {
require __DIR__ . '/../vendor/autoload.php';
} elseif (is_file(__DIR__ . '/../../../autoload.php')) {
require __DIR__ . '/../../../autoload.php';
} else {
echo 'PsySH dependencies not found, be sure to run `composer install`.' . PHP_EOL;
echo 'See https://getcomposer.org to get Composer.' . PHP_EOL;
exit(1);
}
/* >>> */
}
// If the psysh binary was included directly, assume they just wanted an
// autoloader and bail early.
if (version_compare(PHP_VERSION, '5.3.6', '<')) {
$trace = debug_backtrace();
} elseif (version_compare(PHP_VERSION, '5.4.0', '<')) {
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
} else {
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
}
if (Psy\Shell::isIncluded($trace)) {
unset($trace);
return;
}
// Clean up after ourselves.
unset($trace);
// If the local version is too old, we can't do this
if (!function_exists('Psy\bin')) {
$argv = $_SERVER['argv'];
$first = array_shift($argv);
if (preg_match('/php(\.exe)?$/', $first)) {
array_shift($argv);
}
array_unshift($argv, 'vendor/bin/psysh');
echo 'A local PsySH dependency was found, but it cannot be loaded. Please update to' . PHP_EOL;
echo 'the latest version, or run the local copy directly, e.g.:' . PHP_EOL;
echo PHP_EOL;
echo ' ' . implode(' ', $argv) . PHP_EOL;
exit(1);
}
// And go!
call_user_func(Psy\bin());

View File

@@ -1,52 +0,0 @@
{
"name": "psy/psysh",
"description": "An interactive shell for modern PHP.",
"type": "library",
"keywords": ["console", "interactive", "shell", "repl"],
"homepage": "http://psysh.org",
"license": "MIT",
"authors": [
{
"name": "Justin Hileman",
"email": "justin@justinhileman.info",
"homepage": "http://justinhileman.com"
}
],
"require": {
"php": ">=5.3.9",
"symfony/console": "~2.3.10|^2.4.2|~3.0",
"symfony/var-dumper": "~2.7|~3.0",
"nikic/php-parser": "^1.2.1|~2.0",
"dnoegel/php-xdg-base-dir": "0.1",
"jakub-onderka/php-console-highlighter": "0.3.*"
},
"require-dev": {
"phpunit/phpunit": "~3.7|~4.0|~5.0",
"symfony/finder": "~2.1|~3.0",
"squizlabs/php_codesniffer": "~2.0",
"fabpot/php-cs-fixer": "~1.5"
},
"suggest": {
"ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)",
"ext-posix": "If you have PCNTL, you'll want the POSIX extension as well.",
"ext-readline": "Enables support for arrow-key history navigation, and showing and manipulating command history.",
"ext-pdo-sqlite": "The doc command requires SQLite to work."
},
"autoload": {
"files": ["src/Psy/functions.php"],
"psr-4": {
"Psy\\": "src/Psy/"
}
},
"autoload-dev": {
"psr-4": {
"Psy\\Test\\": "test/Psy/Test/"
}
},
"bin": ["bin/psysh"],
"extra": {
"branch-alias": {
"dev-develop": "0.8.x-dev"
}
}
}

View File

@@ -1,11 +0,0 @@
<?xml version="1.0"?>
<ruleset name="PsySH Coding Standard">
<description>The coding standard for PsySH</description>
<exclude-pattern>*/bin/*</exclude-pattern>
<exclude-pattern>*/vendor/*</exclude-pattern>
<rule ref="PSR2">
<exclude name="Generic.Functions.FunctionCallArgumentSpacing.TooMuchSpaceAfterComma"/>
</rule>
</ruleset>

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false" colors="true" bootstrap="vendor/autoload.php">
<testsuite name="PsySH">
<directory suffix="Test.php">./test</directory>
</testsuite>
<filter>
<whitelist>
<directory suffix=".php">./src/Psy</directory>
</whitelist>
</filter>
</phpunit>

View File

@@ -1,45 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy;
/**
* Psy class autoloader.
*/
class Autoloader
{
/**
* Register autoload() as an SPL autoloader.
*
* @see self::autoload
*/
public static function register()
{
spl_autoload_register(array(__CLASS__, 'autoload'));
}
/**
* Autoload Psy classes.
*
* @param string $class
*/
public static function autoload($class)
{
if (0 !== strpos($class, 'Psy')) {
return;
}
$file = dirname(__DIR__) . '/' . strtr($class, '\\', '/') . '.php';
if (is_file($file)) {
require $file;
}
}
}

View File

@@ -1,214 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy;
use PhpParser\NodeTraverser;
use PhpParser\Parser;
use PhpParser\PrettyPrinter\Standard as Printer;
use Psy\CodeCleaner\AbstractClassPass;
use Psy\CodeCleaner\AssignThisVariablePass;
use Psy\CodeCleaner\CalledClassPass;
use Psy\CodeCleaner\CallTimePassByReferencePass;
use Psy\CodeCleaner\ExitPass;
use Psy\CodeCleaner\FunctionReturnInWriteContextPass;
use Psy\CodeCleaner\ImplicitReturnPass;
use Psy\CodeCleaner\InstanceOfPass;
use Psy\CodeCleaner\LeavePsyshAlonePass;
use Psy\CodeCleaner\LegacyEmptyPass;
use Psy\CodeCleaner\MagicConstantsPass;
use Psy\CodeCleaner\NamespacePass;
use Psy\CodeCleaner\StaticConstructorPass;
use Psy\CodeCleaner\StrictTypesPass;
use Psy\CodeCleaner\UseStatementPass;
use Psy\CodeCleaner\ValidClassNamePass;
use Psy\CodeCleaner\ValidConstantPass;
use Psy\CodeCleaner\ValidFunctionNamePass;
use Psy\Exception\ParseErrorException;
/**
* A service to clean up user input, detect parse errors before they happen,
* and generally work around issues with the PHP code evaluation experience.
*/
class CodeCleaner
{
private $parser;
private $printer;
private $traverser;
private $namespace;
/**
* CodeCleaner constructor.
*
* @param Parser $parser A PhpParser Parser instance. One will be created if not explicitly supplied.
* @param Printer $printer A PhpParser Printer instance. One will be created if not explicitly supplied.
* @param NodeTraverser $traverser A PhpParser NodeTraverser instance. One will be created if not explicitly supplied.
*/
public function __construct(Parser $parser = null, Printer $printer = null, NodeTraverser $traverser = null)
{
if ($parser === null) {
$parserFactory = new ParserFactory();
$parser = $parserFactory->createParser();
}
$this->parser = $parser;
$this->printer = $printer ?: new Printer();
$this->traverser = $traverser ?: new NodeTraverser();
foreach ($this->getDefaultPasses() as $pass) {
$this->traverser->addVisitor($pass);
}
}
/**
* Get default CodeCleaner passes.
*
* @return array
*/
private function getDefaultPasses()
{
return array(
new AbstractClassPass(),
new AssignThisVariablePass(),
new FunctionReturnInWriteContextPass(),
new CallTimePassByReferencePass(),
new CalledClassPass(),
new InstanceOfPass(),
new LeavePsyshAlonePass(),
new LegacyEmptyPass(),
new ImplicitReturnPass(),
new UseStatementPass(), // must run before namespace and validation passes
new NamespacePass($this), // must run after the implicit return pass
new StrictTypesPass(),
new StaticConstructorPass(),
new ValidFunctionNamePass(),
new ValidClassNamePass(),
new ValidConstantPass(),
new MagicConstantsPass(),
new ExitPass(),
);
}
/**
* Clean the given array of code.
*
* @throws ParseErrorException if the code is invalid PHP, and cannot be coerced into valid PHP.
*
* @param array $codeLines
* @param bool $requireSemicolons
*
* @return string|false Cleaned PHP code, False if the input is incomplete.
*/
public function clean(array $codeLines, $requireSemicolons = false)
{
$stmts = $this->parse('<?php ' . implode(PHP_EOL, $codeLines) . PHP_EOL, $requireSemicolons);
if ($stmts === false) {
return false;
}
// Catch fatal errors before they happen
$stmts = $this->traverser->traverse($stmts);
return $this->printer->prettyPrint($stmts);
}
/**
* Set the current local namespace.
*
* @param null|array $namespace (default: null)
*
* @return null|array
*/
public function setNamespace(array $namespace = null)
{
$this->namespace = $namespace;
}
/**
* Get the current local namespace.
*
* @return null|array
*/
public function getNamespace()
{
return $this->namespace;
}
/**
* Lex and parse a block of code.
*
* @see Parser::parse
*
* @param string $code
* @param bool $requireSemicolons
*
* @return array A set of statements
*/
protected function parse($code, $requireSemicolons = false)
{
try {
return $this->parser->parse($code);
} catch (\PhpParser\Error $e) {
if ($this->parseErrorIsUnclosedString($e, $code)) {
return false;
}
if (!$this->parseErrorIsEOF($e)) {
throw ParseErrorException::fromParseError($e);
}
if ($requireSemicolons) {
return false;
}
try {
// Unexpected EOF, try again with an implicit semicolon
return $this->parser->parse($code . ';');
} catch (\PhpParser\Error $e) {
return false;
}
}
}
private function parseErrorIsEOF(\PhpParser\Error $e)
{
$msg = $e->getRawMessage();
return ($msg === 'Unexpected token EOF') || (strpos($msg, 'Syntax error, unexpected EOF') !== false);
}
/**
* A special test for unclosed single-quoted strings.
*
* Unlike (all?) other unclosed statements, single quoted strings have
* their own special beautiful snowflake syntax error just for
* themselves.
*
* @param \PhpParser\Error $e
* @param string $code
*
* @return bool
*/
private function parseErrorIsUnclosedString(\PhpParser\Error $e, $code)
{
if ($e->getRawMessage() !== 'Syntax error, unexpected T_ENCAPSED_AND_WHITESPACE') {
return false;
}
try {
$this->parser->parse($code . "';");
} catch (\Exception $e) {
return false;
}
return true;
}
}

View File

@@ -1,69 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_ as ClassStmt;
use PhpParser\Node\Stmt\ClassMethod;
use Psy\Exception\FatalErrorException;
/**
* The abstract class pass handles abstract classes and methods, complaining if there are too few or too many of either.
*/
class AbstractClassPass extends CodeCleanerPass
{
private $class;
private $abstractMethods;
/**
* @throws RuntimeException if the node is an abstract function with a body.
*
* @param Node $node
*/
public function enterNode(Node $node)
{
if ($node instanceof ClassStmt) {
$this->class = $node;
$this->abstractMethods = array();
} elseif ($node instanceof ClassMethod) {
if ($node->isAbstract()) {
$name = sprintf('%s::%s', $this->class->name, $node->name);
$this->abstractMethods[] = $name;
if ($node->stmts !== null) {
throw new FatalErrorException(sprintf('Abstract function %s cannot contain body', $name));
}
}
}
}
/**
* @throws RuntimeException if the node is a non-abstract class with abstract methods.
*
* @param Node $node
*/
public function leaveNode(Node $node)
{
if ($node instanceof ClassStmt) {
$count = count($this->abstractMethods);
if ($count > 0 && !$node->isAbstract()) {
throw new FatalErrorException(sprintf(
'Class %s contains %d abstract method%s must therefore be declared abstract or implement the remaining methods (%s)',
$node->name,
$count,
($count === 0) ? '' : 's',
implode(', ', $this->abstractMethods)
));
}
}
}
}

View File

@@ -1,39 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\CodeCleaner;
use PhpParser\Node as Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\Variable;
use Psy\Exception\FatalErrorException;
/**
* Validate that the user input does not assign the `$this` variable.
*
* @author Martin Hasoň <martin.hason@gmail.com>
*/
class AssignThisVariablePass extends CodeCleanerPass
{
/**
* Validate that the user input does not assign the `$this` variable.
*
* @throws RuntimeException if the user assign the `$this` variable.
*
* @param Node $node
*/
public function enterNode(Node $node)
{
if ($node instanceof Assign && $node->var instanceof Variable && $node->var->name === 'this') {
throw new FatalErrorException('Cannot re-assign $this');
}
}
}

View File

@@ -1,52 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Expr\FuncCall as FunctionCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use Psy\Exception\FatalErrorException;
/**
* Validate that the user did not use the call-time pass-by-reference that causes a fatal error.
*
* As of PHP 5.4.0, call-time pass-by-reference was removed, so using it will raise a fatal error.
*
* @author Martin Hasoň <martin.hason@gmail.com>
*/
class CallTimePassByReferencePass extends CodeCleanerPass
{
/**
* Validate of use call-time pass-by-reference.
*
* @throws RuntimeException if the user used call-time pass-by-reference in PHP >= 5.4.0
*
* @param Node $node
*/
public function enterNode(Node $node)
{
if (version_compare(PHP_VERSION, '5.4', '<')) {
return;
}
if (!$node instanceof FunctionCall && !$node instanceof MethodCall && !$node instanceof StaticCall) {
return;
}
foreach ($node->args as $arg) {
if ($arg->byRef) {
throw new FatalErrorException('Call-time pass-by-reference has been removed');
}
}
}
}

View File

@@ -1,83 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Class_ as ClassStmt;
use PhpParser\Node\Stmt\Trait_ as TraitStmt;
use Psy\Exception\ErrorException;
/**
* The called class pass throws warnings for get_class() and get_called_class()
* outside a class context.
*/
class CalledClassPass extends CodeCleanerPass
{
private $inClass;
/**
* @param array $nodes
*/
public function beforeTraverse(array $nodes)
{
$this->inClass = false;
}
/**
* @throws ErrorException if get_class or get_called_class is called without an object from outside a class
*
* @param Node $node
*/
public function enterNode(Node $node)
{
if ($node instanceof ClassStmt || $node instanceof TraitStmt) {
$this->inClass = true;
} elseif ($node instanceof FuncCall && !$this->inClass) {
// We'll give any args at all (besides null) a pass.
// Technically we should be checking whether the args are objects, but this will do for now.
//
// TODO: switch this to actually validate args when we get context-aware code cleaner passes.
if (!empty($node->args) && !$this->isNull($node->args[0])) {
return;
}
// We'll ignore name expressions as well (things like `$foo()`)
if (!($node->name instanceof Name)) {
return;
}
$name = strtolower($node->name);
if (in_array($name, array('get_class', 'get_called_class'))) {
$msg = sprintf('%s() called without object from outside a class', $name);
throw new ErrorException($msg, 0, E_USER_WARNING, null, $node->getLine());
}
}
}
/**
* @param Node $node
*/
public function leaveNode(Node $node)
{
if ($node instanceof ClassStmt) {
$this->inClass = false;
}
}
private function isNull(Node $node)
{
return $node->value instanceof ConstFetch && strtolower($node->value->name) === 'null';
}
}

View File

@@ -1,22 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\CodeCleaner;
use PhpParser\NodeVisitorAbstract;
/**
* A CodeCleaner pass is a PhpParser Node Visitor.
*/
abstract class CodeCleanerPass extends NodeVisitorAbstract
{
// Wheee!
}

View File

@@ -1,37 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\Exit_;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Throw_;
class ExitPass extends CodeCleanerPass
{
/**
* Converts exit calls to BreakExceptions.
*
* @param \PhpParser\Node $node
*/
public function leaveNode(Node $node)
{
if ($node instanceof Exit_) {
$args = array(new Arg(new String_('Goodbye.')));
return new Throw_(new New_(new Name('Psy\Exception\BreakException'), $args));
}
}
}

View File

@@ -1,82 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Expr\Array_ as ArrayNode;
use PhpParser\Node\Expr\Assign as AssignNode;
use PhpParser\Node\Expr\Empty_ as EmptyNode;
use PhpParser\Node\Expr\FuncCall as FunctionCall;
use PhpParser\Node\Expr\Isset_ as IssetNode;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use Psy\Exception\FatalErrorException;
/**
* Validate that the functions are used correctly.
*
* @author Martin Hasoň <martin.hason@gmail.com>
*/
class FunctionReturnInWriteContextPass extends CodeCleanerPass
{
const EXCEPTION_MESSAGE = "Can't use function return value in write context";
private $isPhp55;
public function __construct()
{
$this->isPhp55 = version_compare(PHP_VERSION, '5.5', '>=');
}
/**
* Validate that the functions are used correctly.
*
* @throws FatalErrorException if a function is passed as an argument reference
* @throws FatalErrorException if a function is used as an argument in the isset
* @throws FatalErrorException if a function is used as an argument in the empty, only for PHP < 5.5
* @throws FatalErrorException if a value is assigned to a function
*
* @param Node $node
*/
public function enterNode(Node $node)
{
if ($node instanceof ArrayNode || $this->isCallNode($node)) {
$items = $node instanceof ArrayNode ? $node->items : $node->args;
foreach ($items as $item) {
if ($item->byRef && $this->isCallNode($item->value)) {
throw new FatalErrorException(self::EXCEPTION_MESSAGE);
}
}
} elseif ($node instanceof IssetNode) {
foreach ($node->vars as $var) {
if (!$this->isCallNode($var)) {
continue;
}
if ($this->isPhp55) {
throw new FatalErrorException('Cannot use isset() on the result of a function call (you can use "null !== func()" instead)');
} else {
throw new FatalErrorException(self::EXCEPTION_MESSAGE);
}
}
} elseif ($node instanceof EmptyNode && !$this->isPhp55 && $this->isCallNode($node->expr)) {
throw new FatalErrorException(self::EXCEPTION_MESSAGE);
} elseif ($node instanceof AssignNode && $this->isCallNode($node->var)) {
throw new FatalErrorException(self::EXCEPTION_MESSAGE);
}
}
private function isCallNode(Node $node)
{
return $node instanceof FunctionCall || $node instanceof MethodCall || $node instanceof StaticCall;
}
}

View File

@@ -1,39 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\CodeCleaner;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Exit_;
use PhpParser\Node\Stmt\Return_ as ReturnStmt;
/**
* Add an implicit "return" to the last statement, provided it can be returned.
*/
class ImplicitReturnPass extends CodeCleanerPass
{
/**
* @param array $nodes
*/
public function beforeTraverse(array $nodes)
{
$last = end($nodes);
if ($last instanceof Expr && !($last instanceof Exit_)) {
$nodes[count($nodes) - 1] = new ReturnStmt($last, array(
'startLine' => $last->getLine(),
'endLine' => $last->getLine(),
));
}
return $nodes;
}
}

View File

@@ -1,45 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Expr\Instanceof_ as InstanceofStmt;
use PhpParser\Node\Scalar;
use PhpParser\Node\Scalar\Encapsed;
use Psy\Exception\FatalErrorException;
/**
* Validate that the instanceof statement does not receive a scalar value or a non-class constant.
*
* @author Martin Hasoň <martin.hason@gmail.com>
*/
class InstanceOfPass extends CodeCleanerPass
{
/**
* Validate that the instanceof statement does not receive a scalar value or a non-class constant.
*
* @throws FatalErrorException if a scalar or a non-class constant is given
*
* @param Node $node
*/
public function enterNode(Node $node)
{
if (!$node instanceof InstanceofStmt) {
return;
}
if (($node->expr instanceof Scalar && !$node->expr instanceof Encapsed) || $node->expr instanceof ConstFetch) {
throw new FatalErrorException('instanceof expects an object instance, constant given');
}
}
}

View File

@@ -1,36 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Expr\Variable;
use Psy\Exception\RuntimeException;
/**
* Validate that the user input does not reference the `$__psysh__` variable.
*/
class LeavePsyshAlonePass extends CodeCleanerPass
{
/**
* Validate that the user input does not reference the `$__psysh__` variable.
*
* @throws RuntimeException if the user is messing with $__psysh__.
*
* @param Node $node
*/
public function enterNode(Node $node)
{
if ($node instanceof Variable && $node->name === '__psysh__') {
throw new RuntimeException('Don\'t mess with $__psysh__. Bad things will happen.');
}
}
}

View File

@@ -1,64 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Expr\Empty_ as ExprEmpty;
use PhpParser\Node\Expr\Variable;
use Psy\Exception\ParseErrorException;
/**
* Validate that the user did not call the language construct `empty()` on a
* statement in PHP < 5.5.
*/
class LegacyEmptyPass extends CodeCleanerPass
{
/**
* Validate use of empty in PHP < 5.5.
*
* @throws ParseErrorException if the user used empty with anything but a variable.
*
* @param Node $node
*/
public function enterNode(Node $node)
{
if (version_compare(PHP_VERSION, '5.5', '>=')) {
return;
}
if (!$node instanceof ExprEmpty) {
return;
}
if (!$node->expr instanceof Variable) {
$msg = sprintf('syntax error, unexpected %s', $this->getUnexpectedThing($node->expr));
throw new ParseErrorException($msg, $node->expr->getLine());
}
}
private function getUnexpectedThing(Node $node)
{
switch ($node->getType()) {
case 'Scalar_String':
case 'Scalar_LNumber':
case 'Scalar_DNumber':
return json_encode($node->value);
case 'Expr_ConstFetch':
return (string) $node->name;
default:
return $node->getType();
}
}
}

View File

@@ -1,42 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar\MagicConst\Dir;
use PhpParser\Node\Scalar\MagicConst\File;
use PhpParser\Node\Scalar\String_ as StringNode;
/**
* Swap out __DIR__ and __FILE__ magic constants with our best guess?
*/
class MagicConstantsPass extends CodeCleanerPass
{
/**
* Swap out __DIR__ and __FILE__ constants, because the default ones when
* calling eval() don't make sense.
*
* @param Node $node
*
* @return null|FuncCall|StringNode
*/
public function enterNode(Node $node)
{
if ($node instanceof Dir) {
return new FuncCall(new Name('getcwd'), array(), $node->getAttributes());
} elseif ($node instanceof File) {
return new StringNode('', $node->getAttributes());
}
}
}

View File

@@ -1,71 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified as FullyQualifiedName;
use PhpParser\Node\Stmt\Namespace_ as NamespaceStmt;
/**
* Abstract namespace-aware code cleaner pass.
*/
abstract class NamespaceAwarePass extends CodeCleanerPass
{
protected $namespace;
protected $currentScope;
/**
* TODO: should this be final? Extending classes should be sure to either
* use afterTraverse or call parent::beforeTraverse() when overloading.
*
* Reset the namespace and the current scope before beginning analysis.
*/
public function beforeTraverse(array $nodes)
{
$this->namespace = array();
$this->currentScope = array();
}
/**
* TODO: should this be final? Extending classes should be sure to either use
* leaveNode or call parent::enterNode() when overloading.
*
* @param Node $node
*/
public function enterNode(Node $node)
{
if ($node instanceof NamespaceStmt) {
$this->namespace = isset($node->name) ? $node->name->parts : array();
}
}
/**
* Get a fully-qualified name (class, function, interface, etc).
*
* @param mixed $name
*
* @return string
*/
protected function getFullyQualifiedName($name)
{
if ($name instanceof FullyQualifiedName) {
return implode('\\', $name->parts);
} elseif ($name instanceof Name) {
$name = $name->parts;
} elseif (!is_array($name)) {
$name = array($name);
}
return implode('\\', array_merge($this->namespace, $name));
}
}

View File

@@ -1,79 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\CodeCleaner;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Namespace_ as NamespaceStmt;
use Psy\CodeCleaner;
/**
* Provide implicit namespaces for subsequent execution.
*
* The namespace pass remembers the last standalone namespace line encountered:
*
* namespace Foo\Bar;
*
* ... which it then applies implicitly to all future evaluated code, until the
* namespace is replaced by another namespace. To reset to the top level
* namespace, enter `namespace {}`. This is a bit ugly, but it does the trick :)
*/
class NamespacePass extends CodeCleanerPass
{
private $namespace = null;
private $cleaner;
/**
* @param CodeCleaner $cleaner
*/
public function __construct(CodeCleaner $cleaner)
{
$this->cleaner = $cleaner;
}
/**
* If this is a standalone namespace line, remember it for later.
*
* Otherwise, apply remembered namespaces to the code until a new namespace
* is encountered.
*
* @param array $nodes
*/
public function beforeTraverse(array $nodes)
{
$first = reset($nodes);
if (count($nodes) === 1 && $first instanceof NamespaceStmt && empty($first->stmts)) {
$this->setNamespace($first->name);
} else {
foreach ($nodes as $key => $node) {
if ($node instanceof NamespaceStmt) {
$this->setNamespace(null);
} elseif ($this->namespace !== null) {
$nodes[$key] = new NamespaceStmt($this->namespace, array($node));
}
}
}
return $nodes;
}
/**
* Remember the namespace and (re)set the namespace on the CodeCleaner as
* well.
*
* @param null|Name $namespace
*/
private function setNamespace($namespace)
{
$this->namespace = $namespace;
$this->cleaner->setNamespace($namespace === null ? null : $namespace->parts);
}
}

View File

@@ -1,87 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_ as ClassStmt;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Namespace_ as NamespaceStmt;
use Psy\Exception\FatalErrorException;
/**
* Validate that the old-style constructor function is not static.
*
* As of PHP 5.3.3, methods with the same name as the last element of a namespaced class name
* will no longer be treated as constructor. This change doesn't affect non-namespaced classes.
*
* Validation of the __construct method ensures the PHP Parser.
*
* @author Martin Hasoň <martin.hason@gmail.com>
*/
class StaticConstructorPass extends CodeCleanerPass
{
private $isPHP533;
private $namespace;
public function __construct()
{
$this->isPHP533 = version_compare(PHP_VERSION, '5.3.3', '>=');
}
public function beforeTraverse(array $nodes)
{
$this->namespace = array();
}
/**
* Validate that the old-style constructor function is not static.
*
* @throws FatalErrorException if the old-style constructor function is static.
*
* @param Node $node
*/
public function enterNode(Node $node)
{
if ($node instanceof NamespaceStmt) {
$this->namespace = isset($node->name) ? $node->name->parts : array();
} elseif ($node instanceof ClassStmt) {
// Bail early if this is PHP 5.3.3 and we have a namespaced class
if (!empty($this->namespace) && $this->isPHP533) {
return;
}
$constructor = null;
foreach ($node->stmts as $stmt) {
if ($stmt instanceof ClassMethod) {
// Bail early if we find a new-style constructor
if ('__construct' === strtolower($stmt->name)) {
return;
}
// We found a possible old-style constructor
// (unless there is also a __construct method)
if (strtolower($node->name) === strtolower($stmt->name)) {
$constructor = $stmt;
}
}
}
if ($constructor && $constructor->isStatic()) {
throw new FatalErrorException(sprintf(
'Constructor %s::%s() cannot be static',
implode('\\', array_merge($this->namespace, (array) $node->name)),
$constructor->name
));
}
}
}
}

View File

@@ -1,76 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\CodeCleaner;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Stmt\Declare_ as DeclareStmt;
use PhpParser\Node\Stmt\DeclareDeclare;
use Psy\Exception\FatalErrorException;
/**
* Provide implicit strict types declarations for for subsequent execution.
*
* The strict types pass remembers the last strict types declaration:
*
* declare(strict_types=1);
*
* ... which it then applies implicitly to all future evaluated code, until it
* is replaced by a new declaration.
*/
class StrictTypesPass extends CodeCleanerPass
{
private $strictTypes = false;
/**
* If this is a standalone strict types declaration, remember it for later.
*
* Otherwise, apply remembered strict types declaration to to the code until
* a new declaration is encountered.
*
* @throws FatalErrorException if an invalid `strict_types` declaration is found.
*
* @param array $nodes
*/
public function beforeTraverse(array $nodes)
{
if (version_compare(PHP_VERSION, '7.0', '<')) {
return;
}
$prependStrictTypes = $this->strictTypes;
foreach ($nodes as $key => $node) {
if ($node instanceof DeclareStmt) {
foreach ($node->declares as $declare) {
if ($declare->key === 'strict_types') {
$value = $declare->value;
if (!$value instanceof LNumber || ($value->value !== 0 && $value->value !== 1)) {
throw new FatalErrorException('strict_types declaration must have 0 or 1 as its value');
}
$this->strictTypes = $value->value === 1;
}
}
}
}
if ($prependStrictTypes) {
$first = reset($nodes);
if (!$first instanceof DeclareStmt) {
$declare = new DeclareStmt(array(new DeclareDeclare('strict_types', new LNumber(1))));
array_unshift($nodes, $declare);
}
}
return $nodes;
}
}

View File

@@ -1,111 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified as FullyQualifiedName;
use PhpParser\Node\Stmt\Namespace_ as NamespaceStmt;
use PhpParser\Node\Stmt\Use_ as UseStmt;
/**
* Provide implicit use statements for subsequent execution.
*
* The use statement pass remembers the last use statement line encountered:
*
* use Foo\Bar as Baz;
*
* ... which it then applies implicitly to all future evaluated code, until the
* current namespace is replaced by another namespace.
*/
class UseStatementPass extends NamespaceAwarePass
{
private $aliases = array();
private $lastAliases = array();
private $lastNamespace = null;
/**
* Re-load the last set of use statements on re-entering a namespace.
*
* This isn't how namespaces normally work, but because PsySH has to spin
* up a new namespace for every line of code, we do this to make things
* work like you'd expect.
*
* @param Node $node
*/
public function enterNode(Node $node)
{
if ($node instanceof NamespaceStmt) {
// If this is the same namespace as last namespace, let's do ourselves
// a favor and reload all the aliases...
if (strtolower($node->name) === strtolower($this->lastNamespace)) {
$this->aliases = $this->lastAliases;
}
}
}
/**
* If this statement is a namespace, forget all the aliases we had.
*
* If it's a use statement, remember the alias for later. Otherwise, apply
* remembered aliases to the code.
*
* @param Node $node
*/
public function leaveNode(Node $node)
{
if ($node instanceof UseStmt) {
// Store a reference to every "use" statement, because we'll need
// them in a bit.
foreach ($node->uses as $use) {
$this->aliases[strtolower($use->alias)] = $use->name;
}
return false;
} elseif ($node instanceof NamespaceStmt) {
// Start fresh, since we're done with this namespace.
$this->lastNamespace = $node->name;
$this->lastAliases = $this->aliases;
$this->aliases = array();
} else {
foreach ($node as $name => $subNode) {
if ($subNode instanceof Name) {
// Implicitly thunk all aliases.
if ($replacement = $this->findAlias($subNode)) {
$node->$name = $replacement;
}
}
}
return $node;
}
}
/**
* Find class/namespace aliases.
*
* @param Name $name
*
* @return FullyQualifiedName|null
*/
private function findAlias(Name $name)
{
$that = strtolower($name);
foreach ($this->aliases as $alias => $prefix) {
if ($that === $alias) {
return new FullyQualifiedName($prefix->toString());
} elseif (substr($that, 0, strlen($alias) + 1) === $alias . '\\') {
return new FullyQualifiedName($prefix->toString() . substr($name, strlen($alias)));
}
}
}
}

View File

@@ -1,365 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\New_ as NewExpr;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Class_ as ClassStmt;
use PhpParser\Node\Stmt\Interface_ as InterfaceStmt;
use PhpParser\Node\Stmt\Trait_ as TraitStmt;
use Psy\Exception\FatalErrorException;
/**
* Validate that classes exist.
*
* This pass throws a FatalErrorException rather than letting PHP run
* headfirst into a real fatal error and die.
*/
class ValidClassNamePass extends NamespaceAwarePass
{
const CLASS_TYPE = 'class';
const INTERFACE_TYPE = 'interface';
const TRAIT_TYPE = 'trait';
protected $checkTraits;
public function __construct()
{
$this->checkTraits = function_exists('trait_exists');
}
/**
* Validate class, interface and trait definitions.
*
* Validate them upon entering the node, so that we know about their
* presence and can validate constant fetches and static calls in class or
* trait methods.
*
* @param Node
*/
public function enterNode(Node $node)
{
parent::enterNode($node);
if ($node instanceof ClassStmt) {
$this->validateClassStatement($node);
} elseif ($node instanceof InterfaceStmt) {
$this->validateInterfaceStatement($node);
} elseif ($node instanceof TraitStmt) {
$this->validateTraitStatement($node);
}
}
/**
* Validate `new` expressions, class constant fetches, and static calls.
*
* @throws FatalErrorException if a class, interface or trait is referenced which does not exist.
* @throws FatalErrorException if a class extends something that is not a class.
* @throws FatalErrorException if a class implements something that is not an interface.
* @throws FatalErrorException if an interface extends something that is not an interface.
* @throws FatalErrorException if a class, interface or trait redefines an existing class, interface or trait name.
*
* @param Node $node
*/
public function leaveNode(Node $node)
{
if ($node instanceof NewExpr) {
$this->validateNewExpression($node);
} elseif ($node instanceof ClassConstFetch) {
$this->validateClassConstFetchExpression($node);
} elseif ($node instanceof StaticCall) {
$this->validateStaticCallExpression($node);
}
}
/**
* Validate a class definition statement.
*
* @param ClassStmt $stmt
*/
protected function validateClassStatement(ClassStmt $stmt)
{
$this->ensureCanDefine($stmt);
if (isset($stmt->extends)) {
$this->ensureClassExists($this->getFullyQualifiedName($stmt->extends), $stmt);
}
$this->ensureInterfacesExist($stmt->implements, $stmt);
}
/**
* Validate an interface definition statement.
*
* @param InterfaceStmt $stmt
*/
protected function validateInterfaceStatement(InterfaceStmt $stmt)
{
$this->ensureCanDefine($stmt);
$this->ensureInterfacesExist($stmt->extends, $stmt);
}
/**
* Validate a trait definition statement.
*
* @param TraitStmt $stmt
*/
protected function validateTraitStatement(TraitStmt $stmt)
{
$this->ensureCanDefine($stmt);
}
/**
* Validate a `new` expression.
*
* @param NewExpr $stmt
*/
protected function validateNewExpression(NewExpr $stmt)
{
// if class name is an expression or an anonymous class, give it a pass for now
if (!$stmt->class instanceof Expr && !$stmt->class instanceof ClassStmt) {
$this->ensureClassExists($this->getFullyQualifiedName($stmt->class), $stmt);
}
}
/**
* Validate a class constant fetch expression's class.
*
* @param ClassConstFetch $stmt
*/
protected function validateClassConstFetchExpression(ClassConstFetch $stmt)
{
// there is no need to check exists for ::class const for php 5.5 or newer
if (strtolower($stmt->name) === 'class'
&& version_compare(PHP_VERSION, '5.5', '>=')) {
return;
}
// if class name is an expression, give it a pass for now
if (!$stmt->class instanceof Expr) {
$this->ensureClassOrInterfaceExists($this->getFullyQualifiedName($stmt->class), $stmt);
}
}
/**
* Validate a class constant fetch expression's class.
*
* @param StaticCall $stmt
*/
protected function validateStaticCallExpression(StaticCall $stmt)
{
// if class name is an expression, give it a pass for now
if (!$stmt->class instanceof Expr) {
$this->ensureMethodExists($this->getFullyQualifiedName($stmt->class), $stmt->name, $stmt);
}
}
/**
* Ensure that no class, interface or trait name collides with a new definition.
*
* @throws FatalErrorException
*
* @param Stmt $stmt
*/
protected function ensureCanDefine(Stmt $stmt)
{
$name = $this->getFullyQualifiedName($stmt->name);
// check for name collisions
$errorType = null;
if ($this->classExists($name)) {
$errorType = self::CLASS_TYPE;
} elseif ($this->interfaceExists($name)) {
$errorType = self::INTERFACE_TYPE;
} elseif ($this->traitExists($name)) {
$errorType = self::TRAIT_TYPE;
}
if ($errorType !== null) {
throw $this->createError(sprintf('%s named %s already exists', ucfirst($errorType), $name), $stmt);
}
// Store creation for the rest of this code snippet so we can find local
// issue too
$this->currentScope[strtolower($name)] = $this->getScopeType($stmt);
}
/**
* Ensure that a referenced class exists.
*
* @throws FatalErrorException
*
* @param string $name
* @param Stmt $stmt
*/
protected function ensureClassExists($name, $stmt)
{
if (!$this->classExists($name)) {
throw $this->createError(sprintf('Class \'%s\' not found', $name), $stmt);
}
}
/**
* Ensure that a referenced class _or interface_ exists.
*
* @throws FatalErrorException
*
* @param string $name
* @param Stmt $stmt
*/
protected function ensureClassOrInterfaceExists($name, $stmt)
{
if (!$this->classExists($name) && !$this->interfaceExists($name)) {
throw $this->createError(sprintf('Class \'%s\' not found', $name), $stmt);
}
}
/**
* Ensure that a statically called method exists.
*
* @throws FatalErrorException
*
* @param string $class
* @param string $name
* @param Stmt $stmt
*/
protected function ensureMethodExists($class, $name, $stmt)
{
$this->ensureClassExists($class, $stmt);
// let's pretend all calls to self, parent and static are valid
if (in_array(strtolower($class), array('self', 'parent', 'static'))) {
return;
}
// if method name is an expression, give it a pass for now
if ($name instanceof Expr) {
return;
}
if (!method_exists($class, $name) && !method_exists($class, '__callStatic')) {
throw $this->createError(sprintf('Call to undefined method %s::%s()', $class, $name), $stmt);
}
}
/**
* Ensure that a referenced interface exists.
*
* @throws FatalErrorException
*
* @param $interfaces
* @param Stmt $stmt
*/
protected function ensureInterfacesExist($interfaces, $stmt)
{
foreach ($interfaces as $interface) {
/** @var string $name */
$name = $this->getFullyQualifiedName($interface);
if (!$this->interfaceExists($name)) {
throw $this->createError(sprintf('Interface \'%s\' not found', $name), $stmt);
}
}
}
/**
* Get a symbol type key for storing in the scope name cache.
*
* @param Stmt $stmt
*
* @return string
*/
protected function getScopeType(Stmt $stmt)
{
if ($stmt instanceof ClassStmt) {
return self::CLASS_TYPE;
} elseif ($stmt instanceof InterfaceStmt) {
return self::INTERFACE_TYPE;
} elseif ($stmt instanceof TraitStmt) {
return self::TRAIT_TYPE;
}
}
/**
* Check whether a class exists, or has been defined in the current code snippet.
*
* Gives `self`, `static` and `parent` a free pass.
*
* @param string $name
*
* @return bool
*/
protected function classExists($name)
{
// Give `self`, `static` and `parent` a pass. This will actually let
// some errors through, since we're not checking whether the keyword is
// being used in a class scope.
if (in_array(strtolower($name), array('self', 'static', 'parent'))) {
return true;
}
return class_exists($name) || $this->findInScope($name) === self::CLASS_TYPE;
}
/**
* Check whether an interface exists, or has been defined in the current code snippet.
*
* @param string $name
*
* @return bool
*/
protected function interfaceExists($name)
{
return interface_exists($name) || $this->findInScope($name) === self::INTERFACE_TYPE;
}
/**
* Check whether a trait exists, or has been defined in the current code snippet.
*
* @param string $name
*
* @return bool
*/
protected function traitExists($name)
{
return $this->checkTraits && (trait_exists($name) || $this->findInScope($name) === self::TRAIT_TYPE);
}
/**
* Find a symbol in the current code snippet scope.
*
* @param string $name
*
* @return string|null
*/
protected function findInScope($name)
{
$name = strtolower($name);
if (isset($this->currentScope[$name])) {
return $this->currentScope[$name];
}
}
/**
* Error creation factory.
*
* @param string $msg
* @param Stmt $stmt
*
* @return FatalErrorException
*/
protected function createError($msg, $stmt)
{
return new FatalErrorException($msg, 0, 1, null, $stmt->getLine());
}
}

View File

@@ -1,85 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\ConstFetch;
use Psy\Exception\FatalErrorException;
/**
* Validate that namespaced constant references will succeed.
*
* This pass throws a FatalErrorException rather than letting PHP run
* headfirst into a real fatal error and die.
*
* @todo Detect constants defined in the current code snippet?
* ... Might not be worth it, since it would need to both be defining and
* referencing a namespaced constant, which doesn't seem like that big of
* a target for failure.
*/
class ValidConstantPass extends NamespaceAwarePass
{
/**
* Validate that namespaced constant references will succeed.
*
* Note that this does not (yet) detect constants defined in the current code
* snippet. It won't happen very often, so we'll punt for now.
*
* @throws FatalErrorException if a constant reference is not defined.
*
* @param Node $node
*/
public function leaveNode(Node $node)
{
if ($node instanceof ConstFetch && count($node->name->parts) > 1) {
$name = $this->getFullyQualifiedName($node->name);
if (!defined($name)) {
throw new FatalErrorException(sprintf('Undefined constant %s', $name), 0, 1, null, $node->getLine());
}
} elseif ($node instanceof ClassConstFetch) {
$this->validateClassConstFetchExpression($node);
}
}
/**
* Validate a class constant fetch expression.
*
* @throws FatalErrorException if a class constant is not defined.
*
* @param ClassConstFetch $stmt
*/
protected function validateClassConstFetchExpression(ClassConstFetch $stmt)
{
// give the `class` pseudo-constant a pass
if ($stmt->name === 'class') {
return;
}
// if class name is an expression, give it a pass for now
if (!$stmt->class instanceof Expr) {
$className = $this->getFullyQualifiedName($stmt->class);
// if the class doesn't exist, don't throw an exception… it might be
// defined in the same line it's used or something stupid like that.
if (class_exists($className) || interface_exists($className)) {
$constName = sprintf('%s::%s', $className, $stmt->name);
if (!defined($constName)) {
$constType = class_exists($className) ? 'Class' : 'Interface';
$msg = sprintf('%s constant \'%s\' not found', $constType, $constName);
throw new FatalErrorException($msg, 0, 1, null, $stmt->getLine());
}
}
}
}
}

View File

@@ -1,73 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\CodeCleaner;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\Function_ as FunctionStmt;
use Psy\Exception\FatalErrorException;
/**
* Validate that function calls will succeed.
*
* This pass throws a FatalErrorException rather than letting PHP run
* headfirst into a real fatal error and die.
*/
class ValidFunctionNamePass extends NamespaceAwarePass
{
/**
* Store newly defined function names on the way in, to allow recursion.
*
* @param Node $node
*/
public function enterNode(Node $node)
{
parent::enterNode($node);
if ($node instanceof FunctionStmt) {
$name = $this->getFullyQualifiedName($node->name);
if (function_exists($name) || isset($this->currentScope[strtolower($name)])) {
throw new FatalErrorException(sprintf('Cannot redeclare %s()', $name), 0, 1, null, $node->getLine());
}
$this->currentScope[strtolower($name)] = true;
}
}
/**
* Validate that function calls will succeed.
*
* @throws FatalErrorException if a function is redefined.
* @throws FatalErrorException if the function name is a string (not an expression) and is not defined.
*
* @param Node $node
*/
public function leaveNode(Node $node)
{
if ($node instanceof FuncCall) {
// if function name is an expression or a variable, give it a pass for now.
$name = $node->name;
if (!$name instanceof Expr && !$name instanceof Variable) {
$shortName = implode('\\', $name->parts);
$fullName = $this->getFullyQualifiedName($name);
$inScope = isset($this->currentScope[strtolower($fullName)]);
if (!$inScope && !function_exists($shortName) && !function_exists($fullName)) {
$message = sprintf('Call to undefined function %s()', $name);
throw new FatalErrorException($message, 0, 1, null, $node->getLine());
}
}
}
}
}

View File

@@ -1,77 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Command;
use Psy\Output\ShellOutput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Interact with the current code buffer.
*
* Shows and clears the buffer for the current multi-line expression.
*/
class BufferCommand extends Command
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('buffer')
->setAliases(array('buf'))
->setDefinition(array(
new InputOption('clear', '', InputOption::VALUE_NONE, 'Clear the current buffer.'),
))
->setDescription('Show (or clear) the contents of the code input buffer.')
->setHelp(
<<<'HELP'
Show the contents of the code buffer for the current multi-line expression.
Optionally, clear the buffer by passing the <info>--clear</info> option.
HELP
);
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$buf = $this->getApplication()->getCodeBuffer();
if ($input->getOption('clear')) {
$this->getApplication()->resetCodeBuffer();
$output->writeln($this->formatLines($buf, 'urgent'), ShellOutput::NUMBER_LINES);
} else {
$output->writeln($this->formatLines($buf), ShellOutput::NUMBER_LINES);
}
}
/**
* A helper method for wrapping buffer lines in `<urgent>` and `<return>` formatter strings.
*
* @param array $lines
* @param string $type (default: 'return')
*
* @return array Formatted strings
*/
protected function formatLines(array $lines, $type = 'return')
{
$template = sprintf('<%s>%%s</%s>', $type, $type);
return array_map(function ($line) use ($template) {
return sprintf($template, $line);
}, $lines);
}
}

View File

@@ -1,49 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Clear the Psy Shell.
*
* Just what it says on the tin.
*/
class ClearCommand extends Command
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('clear')
->setDefinition(array())
->setDescription('Clear the Psy Shell screen.')
->setHelp(
<<<'HELP'
Clear the Psy Shell screen.
Pro Tip: If your PHP has readline support, you should be able to use ctrl+l too!
HELP
);
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$output->write(sprintf('%c[2J%c[0;0f', 27, 27));
}
}

View File

@@ -1,282 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Command;
use Psy\Shell;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command as BaseCommand;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableHelper;
use Symfony\Component\Console\Helper\TableStyle;
use Symfony\Component\Console\Output\OutputInterface;
/**
* The Psy Shell base command.
*/
abstract class Command extends BaseCommand
{
/**
* Sets the application instance for this command.
*
* @param Application $application An Application instance
*
* @api
*/
public function setApplication(Application $application = null)
{
if ($application !== null && !$application instanceof Shell) {
throw new \InvalidArgumentException('PsySH Commands require an instance of Psy\Shell.');
}
return parent::setApplication($application);
}
/**
* {@inheritdoc}
*/
public function asText()
{
$messages = array(
'<comment>Usage:</comment>',
' ' . $this->getSynopsis(),
'',
);
if ($this->getAliases()) {
$messages[] = $this->aliasesAsText();
}
if ($this->getArguments()) {
$messages[] = $this->argumentsAsText();
}
if ($this->getOptions()) {
$messages[] = $this->optionsAsText();
}
if ($help = $this->getProcessedHelp()) {
$messages[] = '<comment>Help:</comment>';
$messages[] = ' ' . str_replace("\n", "\n ", $help) . "\n";
}
return implode("\n", $messages);
}
/**
* {@inheritdoc}
*/
private function getArguments()
{
$hidden = $this->getHiddenArguments();
return array_filter($this->getNativeDefinition()->getArguments(), function ($argument) use ($hidden) {
return !in_array($argument->getName(), $hidden);
});
}
/**
* These arguments will be excluded from help output.
*
* @return array
*/
protected function getHiddenArguments()
{
return array('command');
}
/**
* {@inheritdoc}
*/
private function getOptions()
{
$hidden = $this->getHiddenOptions();
return array_filter($this->getNativeDefinition()->getOptions(), function ($option) use ($hidden) {
return !in_array($option->getName(), $hidden);
});
}
/**
* These options will be excluded from help output.
*
* @return array
*/
protected function getHiddenOptions()
{
return array('verbose');
}
/**
* Format command aliases as text..
*
* @return string
*/
private function aliasesAsText()
{
return '<comment>Aliases:</comment> <info>' . implode(', ', $this->getAliases()) . '</info>' . PHP_EOL;
}
/**
* Format command arguments as text.
*
* @return string
*/
private function argumentsAsText()
{
$max = $this->getMaxWidth();
$messages = array();
$arguments = $this->getArguments();
if (!empty($arguments)) {
$messages[] = '<comment>Arguments:</comment>';
foreach ($arguments as $argument) {
if (null !== $argument->getDefault() && (!is_array($argument->getDefault()) || count($argument->getDefault()))) {
$default = sprintf('<comment> (default: %s)</comment>', $this->formatDefaultValue($argument->getDefault()));
} else {
$default = '';
}
$description = str_replace("\n", "\n" . str_pad('', $max + 2, ' '), $argument->getDescription());
$messages[] = sprintf(" <info>%-${max}s</info> %s%s", $argument->getName(), $description, $default);
}
$messages[] = '';
}
return implode(PHP_EOL, $messages);
}
/**
* Format options as text.
*
* @return string
*/
private function optionsAsText()
{
$max = $this->getMaxWidth();
$messages = array();
$options = $this->getOptions();
if ($options) {
$messages[] = '<comment>Options:</comment>';
foreach ($options as $option) {
if ($option->acceptValue() && null !== $option->getDefault() && (!is_array($option->getDefault()) || count($option->getDefault()))) {
$default = sprintf('<comment> (default: %s)</comment>', $this->formatDefaultValue($option->getDefault()));
} else {
$default = '';
}
$multiple = $option->isArray() ? '<comment> (multiple values allowed)</comment>' : '';
$description = str_replace("\n", "\n" . str_pad('', $max + 2, ' '), $option->getDescription());
$optionMax = $max - strlen($option->getName()) - 2;
$messages[] = sprintf(
" <info>%s</info> %-${optionMax}s%s%s%s",
'--' . $option->getName(),
$option->getShortcut() ? sprintf('(-%s) ', $option->getShortcut()) : '',
$description,
$default,
$multiple
);
}
$messages[] = '';
}
return implode(PHP_EOL, $messages);
}
/**
* Calculate the maximum padding width for a set of lines.
*
* @return int
*/
private function getMaxWidth()
{
$max = 0;
foreach ($this->getOptions() as $option) {
$nameLength = strlen($option->getName()) + 2;
if ($option->getShortcut()) {
$nameLength += strlen($option->getShortcut()) + 3;
}
$max = max($max, $nameLength);
}
foreach ($this->getArguments() as $argument) {
$max = max($max, strlen($argument->getName()));
}
return ++$max;
}
/**
* Format an option default as text.
*
* @param mixed $default
*
* @return string
*/
private function formatDefaultValue($default)
{
if (is_array($default) && $default === array_values($default)) {
return sprintf("array('%s')", implode("', '", $default));
}
return str_replace("\n", '', var_export($default, true));
}
/**
* Get a Table instance.
*
* Falls back to legacy TableHelper.
*
* @return Table|TableHelper
*/
protected function getTable(OutputInterface $output)
{
if (!class_exists('Symfony\Component\Console\Helper\Table')) {
return $this->getTableHelper();
}
$style = new TableStyle();
$style
->setVerticalBorderChar(' ')
->setHorizontalBorderChar('')
->setCrossingChar('');
$table = new Table($output);
return $table
->setRows(array())
->setStyle($style);
}
/**
* Legacy fallback for getTable.
*
* @return TableHelper
*/
protected function getTableHelper()
{
$table = $this->getApplication()->getHelperSet()->get('table');
return $table
->setRows(array())
->setLayout(TableHelper::LAYOUT_BORDERLESS)
->setHorizontalBorderChar('')
->setCrossingChar('');
}
}

View File

@@ -1,98 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Command;
use Psy\Formatter\DocblockFormatter;
use Psy\Formatter\SignatureFormatter;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Read the documentation for an object, class, constant, method or property.
*/
class DocCommand extends ReflectingCommand
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('doc')
->setAliases(array('rtfm', 'man'))
->setDefinition(array(
new InputArgument('value', InputArgument::REQUIRED, 'Function, class, instance, constant, method or property to document.'),
))
->setDescription('Read the documentation for an object, class, constant, method or property.')
->setHelp(
<<<HELP
Read the documentation for an object, class, constant, method or property.
It's awesome for well-documented code, not quite as awesome for poorly documented code.
e.g.
<return>>>> doc preg_replace</return>
<return>>>> doc Psy\Shell</return>
<return>>>> doc Psy\Shell::debug</return>
<return>>>> \$s = new Psy\Shell</return>
<return>>>> doc \$s->run</return>
HELP
);
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
list($value, $reflector) = $this->getTargetAndReflector($input->getArgument('value'));
$doc = $this->getManualDoc($reflector) ?: DocblockFormatter::format($reflector);
$db = $this->getApplication()->getManualDb();
$output->page(function ($output) use ($reflector, $doc, $db) {
$output->writeln(SignatureFormatter::format($reflector));
if (empty($doc) && !$db) {
$output->writeln('');
$output->writeln('<warning>PHP manual not found</warning>');
$output->writeln(' To document core PHP functionality, download the PHP reference manual:');
$output->writeln(' https://github.com/bobthecow/psysh#downloading-the-manual');
} else {
$output->writeln('');
$output->writeln($doc);
}
});
}
private function getManualDoc($reflector)
{
switch (get_class($reflector)) {
case 'ReflectionFunction':
$id = $reflector->name;
break;
case 'ReflectionMethod':
$id = $reflector->class . '::' . $reflector->name;
break;
default:
return false;
}
if ($db = $this->getApplication()->getManualDb()) {
return $db
->query(sprintf('SELECT doc FROM php_manual WHERE id = %s', $db->quote($id)))
->fetchColumn(0);
}
}
}

View File

@@ -1,95 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Command;
use Psy\Exception\RuntimeException;
use Psy\VarDumper\Presenter;
use Psy\VarDumper\PresenterAware;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Dump an object or primitive.
*
* This is like var_dump but *way* awesomer.
*/
class DumpCommand extends ReflectingCommand implements PresenterAware
{
private $presenter;
/**
* PresenterAware interface.
*
* @param Presenter $presenter
*/
public function setPresenter(Presenter $presenter)
{
$this->presenter = $presenter;
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('dump')
->setDefinition(array(
new InputArgument('target', InputArgument::REQUIRED, 'A target object or primitive to dump.', null),
new InputOption('depth', '', InputOption::VALUE_REQUIRED, 'Depth to parse', 10),
new InputOption('all', 'a', InputOption::VALUE_NONE, 'Include private and protected methods and properties.'),
))
->setDescription('Dump an object or primitive.')
->setHelp(
<<<'HELP'
Dump an object or primitive.
This is like var_dump but <strong>way</strong> awesomer.
e.g.
<return>>>> dump $_</return>
<return>>>> dump $someVar</return>
HELP
);
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$depth = $input->getOption('depth');
$target = $this->resolveTarget($input->getArgument('target'));
$output->page($this->presenter->present($target, $depth, $input->getOption('all') ? Presenter::VERBOSE : 0));
}
/**
* Resolve dump target name.
*
* @throws RuntimeException if target name does not exist in the current scope.
*
* @param string $target
*
* @return mixed
*/
protected function resolveTarget($target)
{
$matches = array();
if (preg_match(self::INSTANCE, $target, $matches)) {
return $this->getScopeVariable($matches[1]);
} else {
throw new RuntimeException('Unknown target: ' . $target);
}
}
}

View File

@@ -1,52 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Command;
use Psy\Exception\BreakException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Exit the Psy Shell.
*
* Just what it says on the tin.
*/
class ExitCommand extends Command
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('exit')
->setAliases(array('quit', 'q'))
->setDefinition(array())
->setDescription('End the current session and return to caller.')
->setHelp(
<<<'HELP'
End the current session and return to caller.
e.g.
<return>>>> exit</return>
HELP
);
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
throw new BreakException('Goodbye.');
}
}

View File

@@ -1,98 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
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;
/**
* Help command.
*
* Lists available commands, and gives command-specific help when asked nicely.
*/
class HelpCommand extends Command
{
private $command;
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('help')
->setAliases(array('?'))
->setDefinition(array(
new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', null),
))
->setDescription('Show a list of commands. Type `help [foo]` for information about [foo].')
->setHelp('My. How meta.');
}
/**
* Helper for setting a subcommand to retrieve help for.
*
* @param Command $command
*/
public function setCommand($command)
{
$this->command = $command;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
if ($this->command !== null) {
// help for an individual command
$output->page($this->command->asText());
$this->command = null;
} elseif ($name = $input->getArgument('command_name')) {
// help for an individual command
$output->page($this->getApplication()->get($name)->asText());
} else {
// list available commands
$commands = $this->getApplication()->all();
$table = $this->getTable($output);
foreach ($commands as $name => $command) {
if ($name !== $command->getName()) {
continue;
}
if ($command->getAliases()) {
$aliases = sprintf('<comment>Aliases:</comment> %s', implode(', ', $command->getAliases()));
} else {
$aliases = '';
}
$table->addRow(array(
sprintf('<info>%s</info>', $name),
$command->getDescription(),
$aliases,
));
}
$output->startPaging();
if ($table instanceof TableHelper) {
$table->render($output);
} else {
$table->render();
}
$output->stopPaging();
}
}
}

View File

@@ -1,260 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Command;
use Psy\Output\ShellOutput;
use Psy\Readline\Readline;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Psy Shell history command.
*
* Shows, searches and replays readline history. Not too shabby.
*/
class HistoryCommand extends Command
{
/**
* Set the Shell's Readline service.
*
* @param Readline $readline
*/
public function setReadline(Readline $readline)
{
$this->readline = $readline;
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('history')
->setAliases(array('hist'))
->setDefinition(array(
new InputOption('show', 's', InputOption::VALUE_REQUIRED, 'Show the given range of lines'),
new InputOption('head', 'H', InputOption::VALUE_REQUIRED, 'Display the first N items.'),
new InputOption('tail', 'T', InputOption::VALUE_REQUIRED, 'Display the last N items.'),
new InputOption('grep', 'G', InputOption::VALUE_REQUIRED, 'Show lines matching the given pattern (string or regex).'),
new InputOption('insensitive', 'i', InputOption::VALUE_NONE, 'Case insensitive search (requires --grep).'),
new InputOption('invert', 'v', InputOption::VALUE_NONE, 'Inverted search (requires --grep).'),
new InputOption('no-numbers', 'N', InputOption::VALUE_NONE, 'Omit line numbers.'),
new InputOption('save', '', InputOption::VALUE_REQUIRED, 'Save history to a file.'),
new InputOption('replay', '', InputOption::VALUE_NONE, 'Replay'),
new InputOption('clear', '', InputOption::VALUE_NONE, 'Clear the history.'),
))
->setDescription('Show the Psy Shell history.')
->setHelp(
<<<'HELP'
Show, search, save or replay the Psy Shell history.
e.g.
<return>>>> history --grep /[bB]acon/</return>
<return>>>> history --show 0..10 --replay</return>
<return>>>> history --clear</return>
<return>>>> history --tail 1000 --save somefile.txt</return>
HELP
);
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->validateOnlyOne($input, array('show', 'head', 'tail'));
$this->validateOnlyOne($input, array('save', 'replay', 'clear'));
$history = $this->getHistorySlice(
$input->getOption('show'),
$input->getOption('head'),
$input->getOption('tail')
);
$highlighted = false;
$invert = $input->getOption('invert');
$insensitive = $input->getOption('insensitive');
if ($pattern = $input->getOption('grep')) {
if (substr($pattern, 0, 1) !== '/' || substr($pattern, -1) !== '/' || strlen($pattern) < 3) {
$pattern = '/' . preg_quote($pattern, '/') . '/';
}
if ($insensitive) {
$pattern .= 'i';
}
$this->validateRegex($pattern);
$matches = array();
$highlighted = array();
foreach ($history as $i => $line) {
if (preg_match($pattern, $line, $matches) xor $invert) {
if (!$invert) {
$chunks = explode($matches[0], $history[$i]);
$chunks = array_map(array(__CLASS__, 'escape'), $chunks);
$glue = sprintf('<urgent>%s</urgent>', self::escape($matches[0]));
$highlighted[$i] = implode($glue, $chunks);
}
} else {
unset($history[$i]);
}
}
} elseif ($invert) {
throw new \InvalidArgumentException('Cannot use -v without --grep.');
} elseif ($insensitive) {
throw new \InvalidArgumentException('Cannot use -i without --grep.');
}
if ($save = $input->getOption('save')) {
$output->writeln(sprintf('Saving history in %s...', $save));
file_put_contents($save, implode(PHP_EOL, $history) . PHP_EOL);
$output->writeln('<info>History saved.</info>');
} elseif ($input->getOption('replay')) {
if (!($input->getOption('show') || $input->getOption('head') || $input->getOption('tail'))) {
throw new \InvalidArgumentException('You must limit history via --head, --tail or --show before replaying.');
}
$count = count($history);
$output->writeln(sprintf('Replaying %d line%s of history', $count, ($count !== 1) ? 's' : ''));
$this->getApplication()->addInput($history);
} elseif ($input->getOption('clear')) {
$this->clearHistory();
$output->writeln('<info>History cleared.</info>');
} else {
$type = $input->getOption('no-numbers') ? 0 : ShellOutput::NUMBER_LINES;
if (!$highlighted) {
$type = $type | ShellOutput::OUTPUT_RAW;
}
$output->page($highlighted ?: $history, $type);
}
}
/**
* Extract a range from a string.
*
* @param string $range
*
* @return array [ start, end ]
*/
private function extractRange($range)
{
if (preg_match('/^\d+$/', $range)) {
return array($range, $range + 1);
}
$matches = array();
if ($range !== '..' && preg_match('/^(\d*)\.\.(\d*)$/', $range, $matches)) {
$start = $matches[1] ? intval($matches[1]) : 0;
$end = $matches[2] ? intval($matches[2]) + 1 : PHP_INT_MAX;
return array($start, $end);
}
throw new \InvalidArgumentException('Unexpected range: ' . $range);
}
/**
* Retrieve a slice of the readline history.
*
* @param string $show
* @param string $head
* @param string $tail
*
* @return array A slilce of history.
*/
private function getHistorySlice($show, $head, $tail)
{
$history = $this->readline->listHistory();
if ($show) {
list($start, $end) = $this->extractRange($show);
$length = $end - $start;
} elseif ($head) {
if (!preg_match('/^\d+$/', $head)) {
throw new \InvalidArgumentException('Please specify an integer argument for --head.');
}
$start = 0;
$length = intval($head);
} elseif ($tail) {
if (!preg_match('/^\d+$/', $tail)) {
throw new \InvalidArgumentException('Please specify an integer argument for --tail.');
}
$start = count($history) - $tail;
$length = intval($tail) + 1;
} else {
return $history;
}
return array_slice($history, $start, $length, true);
}
/**
* Validate that $pattern is a valid regular expression.
*
* @param string $pattern
*
* @return bool
*/
private function validateRegex($pattern)
{
set_error_handler(array('Psy\Exception\ErrorException', 'throwException'));
try {
preg_match($pattern, '');
} catch (ErrorException $e) {
throw new RuntimeException(str_replace('preg_match(): ', 'Invalid regular expression: ', $e->getRawMessage()));
}
restore_error_handler();
}
/**
* Validate that only one of the given $options is set.
*
* @param InputInterface $input
* @param array $options
*/
private function validateOnlyOne(InputInterface $input, array $options)
{
$count = 0;
foreach ($options as $opt) {
if ($input->getOption($opt)) {
$count++;
}
}
if ($count > 1) {
throw new \InvalidArgumentException('Please specify only one of --' . implode(', --', $options));
}
}
/**
* Clear the readline history.
*/
private function clearHistory()
{
$this->readline->clearHistory();
}
public static function escape($string)
{
return OutputFormatter::escape($string);
}
}

View File

@@ -1,278 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Command;
use Psy\Command\ListCommand\ClassConstantEnumerator;
use Psy\Command\ListCommand\ClassEnumerator;
use Psy\Command\ListCommand\ConstantEnumerator;
use Psy\Command\ListCommand\FunctionEnumerator;
use Psy\Command\ListCommand\GlobalVariableEnumerator;
use Psy\Command\ListCommand\InterfaceEnumerator;
use Psy\Command\ListCommand\MethodEnumerator;
use Psy\Command\ListCommand\PropertyEnumerator;
use Psy\Command\ListCommand\TraitEnumerator;
use Psy\Command\ListCommand\VariableEnumerator;
use Psy\Exception\RuntimeException;
use Psy\VarDumper\Presenter;
use Psy\VarDumper\PresenterAware;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\TableHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* List available local variables, object properties, etc.
*/
class ListCommand extends ReflectingCommand implements PresenterAware
{
protected $presenter;
protected $enumerators;
/**
* PresenterAware interface.
*
* @param Presenter $manager
*/
public function setPresenter(Presenter $presenter)
{
$this->presenter = $presenter;
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('ls')
->setAliases(array('list', 'dir'))
->setDefinition(array(
new InputArgument('target', InputArgument::OPTIONAL, 'A target class or object to list.', null),
new InputOption('vars', '', InputOption::VALUE_NONE, 'Display variables.'),
new InputOption('constants', 'c', InputOption::VALUE_NONE, 'Display defined constants.'),
new InputOption('functions', 'f', InputOption::VALUE_NONE, 'Display defined functions.'),
new InputOption('classes', 'k', InputOption::VALUE_NONE, 'Display declared classes.'),
new InputOption('interfaces', 'I', InputOption::VALUE_NONE, 'Display declared interfaces.'),
new InputOption('traits', 't', InputOption::VALUE_NONE, 'Display declared traits.'),
new InputOption('properties', 'p', InputOption::VALUE_NONE, 'Display class or object properties (public properties by default).'),
new InputOption('methods', 'm', InputOption::VALUE_NONE, 'Display class or object methods (public methods by default).'),
new InputOption('grep', 'G', InputOption::VALUE_REQUIRED, 'Limit to items matching the given pattern (string or regex).'),
new InputOption('insensitive', 'i', InputOption::VALUE_NONE, 'Case-insensitive search (requires --grep).'),
new InputOption('invert', 'v', InputOption::VALUE_NONE, 'Inverted search (requires --grep).'),
new InputOption('globals', 'g', InputOption::VALUE_NONE, 'Include global variables.'),
new InputOption('internal', 'n', InputOption::VALUE_NONE, 'Limit to internal functions and classes.'),
new InputOption('user', 'u', InputOption::VALUE_NONE, 'Limit to user-defined constants, functions and classes.'),
new InputOption('category', 'C', InputOption::VALUE_REQUIRED, 'Limit to constants in a specific category (e.g. "date").'),
new InputOption('all', 'a', InputOption::VALUE_NONE, 'Include private and protected methods and properties.'),
new InputOption('long', 'l', InputOption::VALUE_NONE, 'List in long format: includes class names and method signatures.'),
))
->setDescription('List local, instance or class variables, methods and constants.')
->setHelp(
<<<'HELP'
List variables, constants, classes, interfaces, traits, functions, methods,
and properties.
Called without options, this will return a list of variables currently in scope.
If a target object is provided, list properties, constants and methods of that
target. If a class, interface or trait name is passed instead, list constants
and methods on that class.
e.g.
<return>>>> ls</return>
<return>>>> ls $foo</return>
<return>>>> ls -k --grep mongo -i</return>
<return>>>> ls -al ReflectionClass</return>
<return>>>> ls --constants --category date</return>
<return>>>> ls -l --functions --grep /^array_.*/</return>
HELP
);
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->validateInput($input);
$this->initEnumerators();
$method = $input->getOption('long') ? 'writeLong' : 'write';
if ($target = $input->getArgument('target')) {
list($target, $reflector) = $this->getTargetAndReflector($target, true);
} else {
$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();
}
}
/**
* Initialize Enumerators.
*/
protected function initEnumerators()
{
if (!isset($this->enumerators)) {
$mgr = $this->presenter;
$this->enumerators = array(
new ClassConstantEnumerator($mgr),
new ClassEnumerator($mgr),
new ConstantEnumerator($mgr),
new FunctionEnumerator($mgr),
new GlobalVariableEnumerator($mgr),
new InterfaceEnumerator($mgr),
new PropertyEnumerator($mgr),
new MethodEnumerator($mgr),
new TraitEnumerator($mgr),
new VariableEnumerator($mgr, $this->context),
);
}
}
/**
* Write the list items to $output.
*
* @param OutputInterface $output
* @param null|array $result List of enumerated items.
*/
protected function write(OutputInterface $output, array $result = null)
{
if ($result === null) {
return;
}
foreach ($result as $label => $items) {
$names = array_map(array($this, 'formatItemName'), $items);
$output->writeln(sprintf('<strong>%s</strong>: %s', $label, implode(', ', $names)));
}
}
/**
* Write the list items to $output.
*
* Items are listed one per line, and include the item signature.
*
* @param OutputInterface $output
* @param null|array $result List of enumerated items.
*/
protected function writeLong(OutputInterface $output, array $result = null)
{
if ($result === null) {
return;
}
$table = $this->getTable($output);
foreach ($result as $label => $items) {
$output->writeln('');
$output->writeln(sprintf('<strong>%s:</strong>', $label));
$table->setRows(array());
foreach ($items as $item) {
$table->addRow(array($this->formatItemName($item), $item['value']));
}
if ($table instanceof TableHelper) {
$table->render($output);
} else {
$table->render();
}
}
}
/**
* Format an item name given its visibility.
*
* @param array $item
*
* @return string
*/
private function formatItemName($item)
{
return sprintf('<%s>%s</%s>', $item['style'], OutputFormatter::escape($item['name']), $item['style']);
}
/**
* Validate that input options make sense, provide defaults when called without options.
*
* @throws RuntimeException if options are inconsistent.
*
* @param InputInterface $input
*/
private function validateInput(InputInterface $input)
{
// grep, invert and insensitive
if (!$input->getOption('grep')) {
foreach (array('invert', 'insensitive') as $option) {
if ($input->getOption($option)) {
throw new RuntimeException('--' . $option . ' does not make sense without --grep');
}
}
}
if (!$input->getArgument('target')) {
// if no target is passed, there can be no properties or methods
foreach (array('properties', 'methods') as $option) {
if ($input->getOption($option)) {
throw new RuntimeException('--' . $option . ' does not make sense without a specified target.');
}
}
foreach (array('globals', 'vars', 'constants', 'functions', 'classes', 'interfaces', 'traits') as $option) {
if ($input->getOption($option)) {
return;
}
}
// default to --vars if no other options are passed
$input->setOption('vars', true);
} else {
// if a target is passed, classes, functions, etc don't make sense
foreach (array('vars', 'globals', 'functions', 'classes', 'interfaces', 'traits') as $option) {
if ($input->getOption($option)) {
throw new RuntimeException('--' . $option . ' does not make sense with a specified target.');
}
}
foreach (array('constants', 'properties', 'methods') as $option) {
if ($input->getOption($option)) {
return;
}
}
// default to --constants --properties --methods if no other options are passed
$input->setOption('constants', true);
$input->setOption('properties', true);
$input->setOption('methods', true);
}
}
}

View File

@@ -1,118 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Command\ListCommand;
use Psy\Reflection\ReflectionConstant;
use Symfony\Component\Console\Input\InputInterface;
/**
* Class Constant Enumerator class.
*/
class ClassConstantEnumerator extends Enumerator
{
/**
* {@inheritdoc}
*/
protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null)
{
// only list constants when a Reflector is present.
if ($reflector === null) {
return;
}
// We can only list constants on actual class (or object) reflectors.
if (!$reflector instanceof \ReflectionClass) {
// TODO: handle ReflectionExtension as well
return;
}
// only list constants if we are specifically asked
if (!$input->getOption('constants')) {
return;
}
$constants = $this->prepareConstants($this->getConstants($reflector));
if (empty($constants)) {
return;
}
$ret = array();
$ret[$this->getKindLabel($reflector)] = $constants;
return $ret;
}
/**
* Get defined constants for the given class or object Reflector.
*
* @param \Reflector $reflector
*
* @return array
*/
protected function getConstants(\Reflector $reflector)
{
$constants = array();
foreach ($reflector->getConstants() as $name => $constant) {
$constants[$name] = new ReflectionConstant($reflector, $name);
}
// TODO: this should be natcasesort
ksort($constants);
return $constants;
}
/**
* Prepare formatted constant array.
*
* @param array $constants
*
* @return array
*/
protected function prepareConstants(array $constants)
{
// My kingdom for a generator.
$ret = array();
foreach ($constants as $name => $constant) {
if ($this->showItem($name)) {
$ret[$name] = array(
'name' => $name,
'style' => self::IS_CONSTANT,
'value' => $this->presentRef($constant->getValue()),
);
}
}
return $ret;
}
/**
* Get a label for the particular kind of "class" represented.
*
* @param \ReflectionClass $reflector
*
* @return string
*/
protected function getKindLabel(\ReflectionClass $reflector)
{
if ($reflector->isInterface()) {
return 'Interface Constants';
} elseif (method_exists($reflector, 'isTrait') && $reflector->isTrait()) {
return 'Trait Constants';
} else {
return 'Class Constants';
}
}
}

View File

@@ -1,80 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Command\ListCommand;
use Symfony\Component\Console\Input\InputInterface;
/**
* Class Enumerator class.
*/
class ClassEnumerator extends Enumerator
{
/**
* {@inheritdoc}
*/
protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null)
{
// only list classes when no Reflector is present.
//
// TODO: make a NamespaceReflector and pass that in for commands like:
//
// ls --classes Foo
//
// ... for listing classes in the Foo namespace
if ($reflector !== null || $target !== null) {
return;
}
// only list classes if we are specifically asked
if (!$input->getOption('classes')) {
return;
}
$classes = $this->prepareClasses(get_declared_classes());
if (empty($classes)) {
return;
}
return array(
'Classes' => $classes,
);
}
/**
* Prepare formatted class array.
*
* @param array $class
*
* @return array
*/
protected function prepareClasses(array $classes)
{
natcasesort($classes);
// My kingdom for a generator.
$ret = array();
foreach ($classes as $name) {
if ($this->showItem($name)) {
$ret[$name] = array(
'name' => $name,
'style' => self::IS_CLASS,
'value' => $this->presentSignature($name),
);
}
}
return $ret;
}
}

View File

@@ -1,103 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Command\ListCommand;
use Symfony\Component\Console\Input\InputInterface;
/**
* Constant Enumerator class.
*/
class ConstantEnumerator extends Enumerator
{
/**
* {@inheritdoc}
*/
protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null)
{
// only list constants when no Reflector is present.
//
// TODO: make a NamespaceReflector and pass that in for commands like:
//
// ls --constants Foo
//
// ... for listing constants in the Foo namespace
if ($reflector !== null || $target !== null) {
return;
}
// only list constants if we are specifically asked
if (!$input->getOption('constants')) {
return;
}
$category = $input->getOption('user') ? 'user' : $input->getOption('category');
$label = $category ? ucfirst($category) . ' Constants' : 'Constants';
$constants = $this->prepareConstants($this->getConstants($category));
if (empty($constants)) {
return;
}
$ret = array();
$ret[$label] = $constants;
return $ret;
}
/**
* Get defined constants.
*
* Optionally restrict constants to a given category, e.g. "date".
*
* @param string $category
*
* @return array
*/
protected function getConstants($category = null)
{
if (!$category) {
return get_defined_constants();
}
$consts = get_defined_constants(true);
return isset($consts[$category]) ? $consts[$category] : array();
}
/**
* Prepare formatted constant array.
*
* @param array $constants
*
* @return array
*/
protected function prepareConstants(array $constants)
{
// My kingdom for a generator.
$ret = array();
$names = array_keys($constants);
natcasesort($names);
foreach ($names as $name) {
if ($this->showItem($name)) {
$ret[$name] = array(
'name' => $name,
'style' => self::IS_CONSTANT,
'value' => $this->presentRef($constants[$name]),
);
}
}
return $ret;
}
}

View File

@@ -1,146 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Command\ListCommand;
use Psy\Formatter\SignatureFormatter;
use Psy\Util\Mirror;
use Psy\VarDumper\Presenter;
use Symfony\Component\Console\Input\InputInterface;
/**
* Abstract Enumerator class.
*/
abstract class Enumerator
{
// Output styles
const IS_PUBLIC = 'public';
const IS_PROTECTED = 'protected';
const IS_PRIVATE = 'private';
const IS_GLOBAL = 'global';
const IS_CONSTANT = 'const';
const IS_CLASS = 'class';
const IS_FUNCTION = 'function';
private $presenter;
private $filter = false;
private $invertFilter = false;
private $pattern;
/**
* Enumerator constructor.
*
* @param Presenter $presenter
*/
public function __construct(Presenter $presenter)
{
$this->presenter = $presenter;
}
/**
* Return a list of categorized things with the given input options and target.
*
* @param InputInterface $input
* @param Reflector $reflector
* @param mixed $target
*
* @return array
*/
public function enumerate(InputInterface $input, \Reflector $reflector = null, $target = null)
{
$this->setFilter($input);
return $this->listItems($input, $reflector, $target);
}
/**
* Enumerate specific items with the given input options and target.
*
* Implementing classes should return an array of arrays:
*
* [
* 'Constants' => [
* 'FOO' => [
* 'name' => 'FOO',
* 'style' => 'public',
* 'value' => '123',
* ],
* ],
* ]
*
* @param InputInterface $input
* @param Reflector $reflector
* @param mixed $target
*
* @return array
*/
abstract protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null);
protected function presentRef($value)
{
return $this->presenter->presentRef($value);
}
protected function showItem($name)
{
return $this->filter === false || (preg_match($this->pattern, $name) xor $this->invertFilter);
}
private function setFilter(InputInterface $input)
{
if ($pattern = $input->getOption('grep')) {
if (substr($pattern, 0, 1) !== '/' || substr($pattern, -1) !== '/' || strlen($pattern) < 3) {
$pattern = '/' . preg_quote($pattern, '/') . '/';
}
if ($input->getOption('insensitive')) {
$pattern .= 'i';
}
$this->validateRegex($pattern);
$this->filter = true;
$this->pattern = $pattern;
$this->invertFilter = $input->getOption('invert');
} else {
$this->filter = false;
}
}
/**
* Validate that $pattern is a valid regular expression.
*
* @param string $pattern
*
* @return bool
*/
private function validateRegex($pattern)
{
set_error_handler(array('Psy\Exception\ErrorException', 'throwException'));
try {
preg_match($pattern, '');
} catch (ErrorException $e) {
throw new RuntimeException(str_replace('preg_match(): ', 'Invalid regular expression: ', $e->getRawMessage()));
}
restore_error_handler();
}
protected function presentSignature($target)
{
// This might get weird if the signature is actually for a reflector. Hrm.
if (!$target instanceof \Reflector) {
$target = Mirror::get($target);
}
return SignatureFormatter::format($target);
}
}

View File

@@ -1,112 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Command\ListCommand;
use Symfony\Component\Console\Input\InputInterface;
/**
* Function Enumerator class.
*/
class FunctionEnumerator extends Enumerator
{
/**
* {@inheritdoc}
*/
protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null)
{
// only list functions when no Reflector is present.
//
// TODO: make a NamespaceReflector and pass that in for commands like:
//
// ls --functions Foo
//
// ... for listing functions in the Foo namespace
if ($reflector !== null || $target !== null) {
return;
}
// only list functions if we are specifically asked
if (!$input->getOption('functions')) {
return;
}
if ($input->getOption('user')) {
$label = 'User Functions';
$functions = $this->getFunctions('user');
} elseif ($input->getOption('internal')) {
$label = 'Internal Functions';
$functions = $this->getFunctions('internal');
} else {
$label = 'Functions';
$functions = $this->getFunctions();
}
$functions = $this->prepareFunctions($functions);
if (empty($functions)) {
return;
}
$ret = array();
$ret[$label] = $functions;
return $ret;
}
/**
* Get defined functions.
*
* Optionally limit functions to "user" or "internal" functions.
*
* @param null|string $type "user" or "internal" (default: both)
*
* @return array
*/
protected function getFunctions($type = null)
{
$funcs = get_defined_functions();
if ($type) {
return $funcs[$type];
} else {
return array_merge($funcs['internal'], $funcs['user']);
}
}
/**
* Prepare formatted function array.
*
* @param array $functions
*
* @return array
*/
protected function prepareFunctions(array $functions)
{
natcasesort($functions);
// My kingdom for a generator.
$ret = array();
foreach ($functions as $name) {
if ($this->showItem($name)) {
$ret[$name] = array(
'name' => $name,
'style' => self::IS_FUNCTION,
'value' => $this->presentSignature($name),
);
}
}
return $ret;
}
}

View File

@@ -1,92 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Command\ListCommand;
use Symfony\Component\Console\Input\InputInterface;
/**
* Global Variable Enumerator class.
*/
class GlobalVariableEnumerator extends Enumerator
{
/**
* {@inheritdoc}
*/
protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null)
{
// only list globals when no Reflector is present.
if ($reflector !== null || $target !== null) {
return;
}
// only list globals if we are specifically asked
if (!$input->getOption('globals')) {
return;
}
$globals = $this->prepareGlobals($this->getGlobals());
if (empty($globals)) {
return;
}
return array(
'Global Variables' => $globals,
);
}
/**
* Get defined global variables.
*
* @return array
*/
protected function getGlobals()
{
global $GLOBALS;
$names = array_keys($GLOBALS);
natcasesort($names);
$ret = array();
foreach ($names as $name) {
$ret[$name] = $GLOBALS[$name];
}
return $ret;
}
/**
* Prepare formatted global variable array.
*
* @param array $globals
*
* @return array
*/
protected function prepareGlobals($globals)
{
// My kingdom for a generator.
$ret = array();
foreach ($globals as $name => $value) {
if ($this->showItem($name)) {
$fname = '$' . $name;
$ret[$fname] = array(
'name' => $fname,
'style' => self::IS_GLOBAL,
'value' => $this->presentRef($value),
);
}
}
return $ret;
}
}

View File

@@ -1,80 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Command\ListCommand;
use Symfony\Component\Console\Input\InputInterface;
/**
* Interface Enumerator class.
*/
class InterfaceEnumerator extends Enumerator
{
/**
* {@inheritdoc}
*/
protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null)
{
// only list interfaces when no Reflector is present.
//
// TODO: make a NamespaceReflector and pass that in for commands like:
//
// ls --interfaces Foo
//
// ... for listing interfaces in the Foo namespace
if ($reflector !== null || $target !== null) {
return;
}
// only list interfaces if we are specifically asked
if (!$input->getOption('interfaces')) {
return;
}
$interfaces = $this->prepareInterfaces(get_declared_interfaces());
if (empty($interfaces)) {
return;
}
return array(
'Interfaces' => $interfaces,
);
}
/**
* Prepare formatted interface array.
*
* @param array $interfaces
*
* @return array
*/
protected function prepareInterfaces(array $interfaces)
{
natcasesort($interfaces);
// My kingdom for a generator.
$ret = array();
foreach ($interfaces as $name) {
if ($this->showItem($name)) {
$ret[$name] = array(
'name' => $name,
'style' => self::IS_CLASS,
'value' => $this->presentSignature($name),
);
}
}
return $ret;
}
}

View File

@@ -1,138 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Command\ListCommand;
use Symfony\Component\Console\Input\InputInterface;
/**
* Method Enumerator class.
*/
class MethodEnumerator extends Enumerator
{
/**
* {@inheritdoc}
*/
protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null)
{
// only list methods when a Reflector is present.
if ($reflector === null) {
return;
}
// We can only list methods on actual class (or object) reflectors.
if (!$reflector instanceof \ReflectionClass) {
return;
}
// only list methods if we are specifically asked
if (!$input->getOption('methods')) {
return;
}
$showAll = $input->getOption('all');
$methods = $this->prepareMethods($this->getMethods($showAll, $reflector));
if (empty($methods)) {
return;
}
$ret = array();
$ret[$this->getKindLabel($reflector)] = $methods;
return $ret;
}
/**
* Get defined methods for the given class or object Reflector.
*
* @param bool $showAll Include private and protected methods.
* @param \Reflector $reflector
*
* @return array
*/
protected function getMethods($showAll, \Reflector $reflector)
{
$methods = array();
foreach ($reflector->getMethods() as $name => $method) {
if ($showAll || $method->isPublic()) {
$methods[$method->getName()] = $method;
}
}
// TODO: this should be natcasesort
ksort($methods);
return $methods;
}
/**
* Prepare formatted method array.
*
* @param array $methods
*
* @return array
*/
protected function prepareMethods(array $methods)
{
// My kingdom for a generator.
$ret = array();
foreach ($methods as $name => $method) {
if ($this->showItem($name)) {
$ret[$name] = array(
'name' => $name,
'style' => $this->getVisibilityStyle($method),
'value' => $this->presentSignature($method),
);
}
}
return $ret;
}
/**
* Get a label for the particular kind of "class" represented.
*
* @param \ReflectionClass $reflector
*
* @return string
*/
protected function getKindLabel(\ReflectionClass $reflector)
{
if ($reflector->isInterface()) {
return 'Interface Methods';
} elseif (method_exists($reflector, 'isTrait') && $reflector->isTrait()) {
return 'Trait Methods';
} else {
return 'Class Methods';
}
}
/**
* Get output style for the given method's visibility.
*
* @param \ReflectionMethod $method
*
* @return string
*/
private function getVisibilityStyle(\ReflectionMethod $method)
{
if ($method->isPublic()) {
return self::IS_PUBLIC;
} elseif ($method->isProtected()) {
return self::IS_PROTECTED;
} else {
return self::IS_PRIVATE;
}
}
}

View File

@@ -1,161 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Command\ListCommand;
use Symfony\Component\Console\Input\InputInterface;
/**
* Property Enumerator class.
*/
class PropertyEnumerator extends Enumerator
{
/**
* {@inheritdoc}
*/
protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null)
{
// only list properties when a Reflector is present.
if ($reflector === null) {
return;
}
// We can only list properties on actual class (or object) reflectors.
if (!$reflector instanceof \ReflectionClass) {
return;
}
// only list properties if we are specifically asked
if (!$input->getOption('properties')) {
return;
}
$showAll = $input->getOption('all');
$properties = $this->prepareProperties($this->getProperties($showAll, $reflector), $target);
if (empty($properties)) {
return;
}
$ret = array();
$ret[$this->getKindLabel($reflector)] = $properties;
return $ret;
}
/**
* Get defined properties for the given class or object Reflector.
*
* @param bool $showAll Include private and protected properties.
* @param \Reflector $reflector
*
* @return array
*/
protected function getProperties($showAll, \Reflector $reflector)
{
$properties = array();
foreach ($reflector->getProperties() as $property) {
if ($showAll || $property->isPublic()) {
$properties[$property->getName()] = $property;
}
}
// TODO: this should be natcasesort
ksort($properties);
return $properties;
}
/**
* Prepare formatted property array.
*
* @param array $properties
*
* @return array
*/
protected function prepareProperties(array $properties, $target = null)
{
// My kingdom for a generator.
$ret = array();
foreach ($properties as $name => $property) {
if ($this->showItem($name)) {
$fname = '$' . $name;
$ret[$fname] = array(
'name' => $fname,
'style' => $this->getVisibilityStyle($property),
'value' => $this->presentValue($property, $target),
);
}
}
return $ret;
}
/**
* Get a label for the particular kind of "class" represented.
*
* @param \ReflectionClass $reflector
*
* @return string
*/
protected function getKindLabel(\ReflectionClass $reflector)
{
if ($reflector->isInterface()) {
return 'Interface Properties';
} elseif (method_exists($reflector, 'isTrait') && $reflector->isTrait()) {
return 'Trait Properties';
} else {
return 'Class Properties';
}
}
/**
* Get output style for the given property's visibility.
*
* @param \ReflectionProperty $property
*
* @return string
*/
private function getVisibilityStyle(\ReflectionProperty $property)
{
if ($property->isPublic()) {
return self::IS_PUBLIC;
} elseif ($property->isProtected()) {
return self::IS_PROTECTED;
} else {
return self::IS_PRIVATE;
}
}
/**
* Present the $target's current value for a reflection property.
*
* @param \ReflectionProperty $property
* @param mixed $target
*
* @return string
*/
protected function presentValue(\ReflectionProperty $property, $target)
{
if (!is_object($target)) {
// TODO: figure out if there's a way to return defaults when target
// is a class/interface/trait rather than an object.
return '';
}
$property->setAccessible(true);
$value = $property->getValue($target);
return $this->presentRef($value);
}
}

View File

@@ -1,85 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Command\ListCommand;
use Symfony\Component\Console\Input\InputInterface;
/**
* Trait Enumerator class.
*/
class TraitEnumerator extends Enumerator
{
/**
* {@inheritdoc}
*/
protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null)
{
// bail early if current PHP doesn't know about traits.
if (!function_exists('trait_exists')) {
return;
}
// only list traits when no Reflector is present.
//
// TODO: make a NamespaceReflector and pass that in for commands like:
//
// ls --traits Foo
//
// ... for listing traits in the Foo namespace
if ($reflector !== null || $target !== null) {
return;
}
// only list traits if we are specifically asked
if (!$input->getOption('traits')) {
return;
}
$traits = $this->prepareTraits(get_declared_traits());
if (empty($traits)) {
return;
}
return array(
'Traits' => $traits,
);
}
/**
* Prepare formatted trait array.
*
* @param array $traits
*
* @return array
*/
protected function prepareTraits(array $traits)
{
natcasesort($traits);
// My kingdom for a generator.
$ret = array();
foreach ($traits as $name) {
if ($this->showItem($name)) {
$ret[$name] = array(
'name' => $name,
'style' => self::IS_CLASS,
'value' => $this->presentSignature($name),
);
}
}
return $ret;
}
}

View File

@@ -1,129 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Command\ListCommand;
use Psy\Context;
use Psy\VarDumper\Presenter;
use Symfony\Component\Console\Input\InputInterface;
/**
* Variable Enumerator class.
*/
class VariableEnumerator extends Enumerator
{
private static $specialVars = array('_', '_e');
private $context;
/**
* Variable Enumerator constructor.
*
* Unlike most other enumerators, the Variable Enumerator needs access to
* the current scope variables, so we need to pass it a Context instance.
*
* @param Presenter $presenter
* @param Context $context
*/
public function __construct(Presenter $presenter, Context $context)
{
$this->context = $context;
parent::__construct($presenter);
}
/**
* {@inheritdoc}
*/
protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null)
{
// only list variables when no Reflector is present.
if ($reflector !== null || $target !== null) {
return;
}
// only list variables if we are specifically asked
if (!$input->getOption('vars')) {
return;
}
$showAll = $input->getOption('all');
$variables = $this->prepareVariables($this->getVariables($showAll));
if (empty($variables)) {
return;
}
return array(
'Variables' => $variables,
);
}
/**
* Get scope variables.
*
* @param bool $showAll Include special variables (e.g. $_).
*
* @return array
*/
protected function getVariables($showAll)
{
$scopeVars = $this->context->getAll();
uksort($scopeVars, function ($a, $b) {
if ($a === '_e') {
return 1;
} elseif ($b === '_e') {
return -1;
} elseif ($a === '_') {
return 1;
} elseif ($b === '_') {
return -1;
} else {
// TODO: this should be natcasesort
return strcasecmp($a, $b);
}
});
$ret = array();
foreach ($scopeVars as $name => $val) {
if (!$showAll && in_array($name, self::$specialVars)) {
continue;
}
$ret[$name] = $val;
}
return $ret;
}
/**
* Prepare formatted variable array.
*
* @param array $variables
*
* @return array
*/
protected function prepareVariables(array $variables)
{
// My kingdom for a generator.
$ret = array();
foreach ($variables as $name => $val) {
if ($this->showItem($name)) {
$fname = '$' . $name;
$ret[$fname] = array(
'name' => $fname,
'style' => in_array($name, self::$specialVars) ? self::IS_PRIVATE : self::IS_PUBLIC,
'value' => $this->presentRef($val), // TODO: add types to variable signatures
);
}
}
return $ret;
}
}

View File

@@ -1,159 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Command;
use PhpParser\Node;
use PhpParser\Parser;
use Psy\ParserFactory;
use Psy\VarDumper\Presenter;
use Psy\VarDumper\PresenterAware;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\VarDumper\Caster\Caster;
/**
* Parse PHP code and show the abstract syntax tree.
*/
class ParseCommand extends Command implements PresenterAware
{
private $presenter;
private $parserFactory;
private $parsers;
/**
* {@inheritdoc}
*/
public function __construct($name = null)
{
$this->parserFactory = new ParserFactory();
$this->parsers = array();
parent::__construct($name);
}
/**
* PresenterAware interface.
*
* @param Presenter $presenter
*/
public function setPresenter(Presenter $presenter)
{
$this->presenter = clone $presenter;
$this->presenter->addCasters(array(
'PhpParser\Node' => function (Node $node, array $a) {
$a = array(
Caster::PREFIX_VIRTUAL . 'type' => $node->getType(),
Caster::PREFIX_VIRTUAL . 'attributes' => $node->getAttributes(),
);
foreach ($node->getSubNodeNames() as $name) {
$a[Caster::PREFIX_VIRTUAL . $name] = $node->$name;
}
return $a;
},
));
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$definition = array(
new InputArgument('code', InputArgument::REQUIRED, 'PHP code to parse.'),
new InputOption('depth', '', InputOption::VALUE_REQUIRED, 'Depth to parse', 10),
);
if ($this->parserFactory->hasKindsSupport()) {
$msg = 'One of PhpParser\\ParserFactory constants: '
. implode(', ', ParserFactory::getPossibleKinds())
. " (default is based on current interpreter's version)";
$defaultKind = $this->parserFactory->getDefaultKind();
$definition[] = new InputOption('kind', '', InputOption::VALUE_REQUIRED, $msg, $defaultKind);
}
$this
->setName('parse')
->setDefinition($definition)
->setDescription('Parse PHP code and show the abstract syntax tree.')
->setHelp(
<<<'HELP'
Parse PHP code and show the abstract syntax tree.
This command is used in the development of PsySH. Given a string of PHP code,
it pretty-prints the PHP Parser parse tree.
See https://github.com/nikic/PHP-Parser
It prolly won't be super useful for most of you, but it's here if you want to play.
HELP
);
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$code = $input->getArgument('code');
if (strpos('<?', $code) === false) {
$code = '<?php ' . $code;
}
$parserKind = $input->getOption('kind');
$depth = $input->getOption('depth');
$nodes = $this->parse($this->getParser($parserKind), $code);
$output->page($this->presenter->present($nodes, $depth));
}
/**
* Lex and parse a string of code into statements.
*
* @param Parser $parser
* @param string $code
*
* @return array Statements
*/
private function parse(Parser $parser, $code)
{
try {
return $parser->parse($code);
} catch (\PhpParser\Error $e) {
if (strpos($e->getMessage(), 'unexpected EOF') === false) {
throw $e;
}
// If we got an unexpected EOF, let's try it again with a semicolon.
return $parser->parse($code . ';');
}
}
/**
* Get (or create) the Parser instance.
*
* @param string|null $kind One of Psy\ParserFactory constants (only for PHP parser 2.0 and above).
*
* @return Parser
*/
private function getParser($kind = null)
{
if (!array_key_exists($kind, $this->parsers)) {
$this->parsers[$kind] = $this->parserFactory->createParser($kind);
}
return $this->parsers[$kind];
}
}

View File

@@ -1,41 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* A dumb little command for printing out the current Psy Shell version.
*/
class PsyVersionCommand extends Command
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('version')
->setDefinition(array())
->setDescription('Show Psy Shell version.')
->setHelp('Show Psy Shell version.');
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$output->writeln($this->getApplication()->getVersion());
}
}

View File

@@ -1,172 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Command;
use Psy\Context;
use Psy\ContextAware;
use Psy\Exception\RuntimeException;
use Psy\Util\Mirror;
/**
* An abstract command with helpers for inspecting the current context.
*/
abstract class ReflectingCommand extends Command implements ContextAware
{
const CLASS_OR_FUNC = '/^[\\\\\w]+$/';
const INSTANCE = '/^\$(\w+)$/';
const CLASS_MEMBER = '/^([\\\\\w]+)::(\w+)$/';
const CLASS_STATIC = '/^([\\\\\w]+)::\$(\w+)$/';
const INSTANCE_MEMBER = '/^\$(\w+)(::|->)(\w+)$/';
const INSTANCE_STATIC = '/^\$(\w+)::\$(\w+)$/';
/**
* Context instance (for ContextAware interface).
*
* @var Context
*/
protected $context;
/**
* ContextAware interface.
*
* @param Context $context
*/
public function setContext(Context $context)
{
$this->context = $context;
}
/**
* Get the target for a value.
*
* @throws \InvalidArgumentException when the value specified can't be resolved.
*
* @param string $valueName Function, class, variable, constant, method or property name.
* @param bool $classOnly True if the name should only refer to a class, function or instance
*
* @return array (class or instance name, member name, kind)
*/
protected function getTarget($valueName, $classOnly = false)
{
$valueName = trim($valueName);
$matches = array();
switch (true) {
case preg_match(self::CLASS_OR_FUNC, $valueName, $matches):
return array($this->resolveName($matches[0], true), null, 0);
case preg_match(self::INSTANCE, $valueName, $matches):
return array($this->resolveInstance($matches[1]), null, 0);
case !$classOnly && preg_match(self::CLASS_MEMBER, $valueName, $matches):
return array($this->resolveName($matches[1]), $matches[2], Mirror::CONSTANT | Mirror::METHOD);
case !$classOnly && preg_match(self::CLASS_STATIC, $valueName, $matches):
return array($this->resolveName($matches[1]), $matches[2], Mirror::STATIC_PROPERTY | Mirror::PROPERTY);
case !$classOnly && preg_match(self::INSTANCE_MEMBER, $valueName, $matches):
if ($matches[2] === '->') {
$kind = Mirror::METHOD | Mirror::PROPERTY;
} else {
$kind = Mirror::CONSTANT | Mirror::METHOD;
}
return array($this->resolveInstance($matches[1]), $matches[3], $kind);
case !$classOnly && preg_match(self::INSTANCE_STATIC, $valueName, $matches):
return array($this->resolveInstance($matches[1]), $matches[2], Mirror::STATIC_PROPERTY);
default:
throw new RuntimeException('Unknown target: ' . $valueName);
}
}
/**
* Resolve a class or function name (with the current shell namespace).
*
* @param string $name
* @param bool $includeFunctions (default: false)
*
* @return string
*/
protected function resolveName($name, $includeFunctions = false)
{
if (substr($name, 0, 1) === '\\') {
return $name;
}
if ($namespace = $this->getApplication()->getNamespace()) {
$fullName = $namespace . '\\' . $name;
if (class_exists($fullName) || interface_exists($fullName) || ($includeFunctions && function_exists($fullName))) {
return $fullName;
}
}
return $name;
}
/**
* Get a Reflector and documentation for a function, class or instance, constant, method or property.
*
* @param string $valueName Function, class, variable, constant, method or property name.
* @param bool $classOnly True if the name should only refer to a class, function or instance
*
* @return array (value, Reflector)
*/
protected function getTargetAndReflector($valueName, $classOnly = false)
{
list($value, $member, $kind) = $this->getTarget($valueName, $classOnly);
return array($value, Mirror::get($value, $member, $kind));
}
/**
* Return a variable instance from the current scope.
*
* @throws \InvalidArgumentException when the requested variable does not exist in the current scope.
*
* @param string $name
*
* @return mixed Variable instance.
*/
protected function resolveInstance($name)
{
$value = $this->getScopeVariable($name);
if (!is_object($value)) {
throw new RuntimeException('Unable to inspect a non-object');
}
return $value;
}
/**
* Get a variable from the current shell scope.
*
* @param string $name
*
* @return mixed
*/
protected function getScopeVariable($name)
{
return $this->context->get($name);
}
/**
* Get all scope variables from the current shell scope.
*
* @return array
*/
protected function getScopeVariables()
{
return $this->context->getAll();
}
}

View File

@@ -1,76 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Command;
use Psy\Configuration;
use Psy\Exception\RuntimeException;
use Psy\Formatter\CodeFormatter;
use Psy\Formatter\SignatureFormatter;
use Psy\Output\ShellOutput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Show the code for an object, class, constant, method or property.
*/
class ShowCommand extends ReflectingCommand
{
private $colorMode;
/**
* @param null|string $colorMode (default: null)
*/
public function __construct($colorMode = null)
{
$this->colorMode = $colorMode ?: Configuration::COLOR_MODE_AUTO;
return parent::__construct();
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('show')
->setDefinition(array(
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.')
->setHelp(
<<<HELP
Show the code for an object, class, constant, method or property.
e.g.
<return>>>> show \$myObject</return>
<return>>>> show Psy\Shell::debug</return>
HELP
);
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
list($value, $reflector) = $this->getTargetAndReflector($input->getArgument('value'));
try {
$output->page(CodeFormatter::format($reflector, $this->colorMode), ShellOutput::OUTPUT_RAW);
} catch (RuntimeException $e) {
$output->writeln(SignatureFormatter::format($reflector));
throw $e;
}
}
}

View File

@@ -1,87 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Command;
use Psy\Context;
use Psy\ContextAware;
use Psy\Exception\ThrowUpException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Throw an exception out of the Psy Shell.
*/
class ThrowUpCommand extends Command implements ContextAware
{
/**
* Context instance (for ContextAware interface).
*
* @var Context
*/
protected $context;
/**
* ContextAware interface.
*
* @param Context $context
*/
public function setContext(Context $context)
{
$this->context = $context;
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('throw-up')
->setDefinition(array(
new InputArgument('exception', InputArgument::OPTIONAL, 'Exception to throw'),
))
->setDescription('Throw an exception out of the Psy Shell.')
->setHelp(
<<<'HELP'
Throws an exception out of the current the Psy Shell instance.
By default it throws the most recent exception.
e.g.
<return>>>> throw-up</return>
<return>>>> throw-up $e</return>
HELP
);
}
/**
* {@inheritdoc}
*
* @throws InvalidArgumentException if there is no exception to throw.
* @throws ThrowUpException because what else do you expect it to do?
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
if ($name = $input->getArgument('exception')) {
$orig = $this->context->get(preg_replace('/^\$/', '', $name));
} else {
$orig = $this->context->getLastException();
}
if (!$orig instanceof \Exception) {
throw new \InvalidArgumentException('throw-up can only throw Exceptions');
}
throw new ThrowUpException($orig);
}
}

View File

@@ -1,137 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Command;
use Psy\Output\ShellOutput;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Show the current stack trace.
*/
class TraceCommand extends Command
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('trace')
->setDefinition(array(
new InputOption('include-psy', 'p', InputOption::VALUE_NONE, 'Include Psy in the call stack.'),
new InputOption('num', 'n', InputOption::VALUE_REQUIRED, 'Only include NUM lines.'),
))
->setDescription('Show the current call stack.')
->setHelp(
<<<'HELP'
Show the current call stack.
Optionally, include PsySH in the call stack by passing the <info>--include-psy</info> option.
e.g.
<return>> trace -n10</return>
<return>> trace --include-psy</return>
HELP
);
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$trace = $this->getBacktrace(new \Exception(), $input->getOption('num'), $input->getOption('include-psy'));
$output->page($trace, ShellOutput::NUMBER_LINES);
}
/**
* Get a backtrace for an exception.
*
* Optionally limit the number of rows to include with $count, and exclude
* Psy from the trace.
*
* @param \Exception $e The exception with a backtrace.
* @param int $count (default: PHP_INT_MAX)
* @param bool $includePsy (default: true)
*
* @return array Formatted stacktrace lines.
*/
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;
}
$lines = array();
$trace = $e->getTrace();
array_unshift($trace, array(
'function' => '',
'file' => $e->getFile() !== null ? $e->getFile() : 'n/a',
'line' => $e->getLine() !== null ? $e->getLine() : 'n/a',
'args' => array(),
));
if (!$includePsy) {
for ($i = count($trace) - 1; $i >= 0; $i--) {
$thing = isset($trace[$i]['class']) ? $trace[$i]['class'] : $trace[$i]['function'];
if (preg_match('/\\\\?Psy\\\\/', $thing)) {
$trace = array_slice($trace, $i + 1);
break;
}
}
}
for ($i = 0, $count = min($count, count($trace)); $i < $count; $i++) {
$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']) ? $this->replaceCwd($cwd, $trace[$i]['file']) : 'n/a';
$line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a';
$lines[] = sprintf(
' <class>%s</class>%s%s() at <info>%s:%s</info>',
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);
}
}
}

View File

@@ -1,123 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Command;
use JakubOnderka\PhpConsoleHighlighter\Highlighter;
use Psy\Configuration;
use Psy\ConsoleColorFactory;
use Psy\Output\ShellOutput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Show the context of where you opened the debugger.
*/
class WhereamiCommand extends Command
{
private $colorMode;
/**
* @param null|string $colorMode (default: null)
*/
public function __construct($colorMode = null)
{
$this->colorMode = $colorMode ?: Configuration::COLOR_MODE_AUTO;
if (version_compare(PHP_VERSION, '5.3.6', '>=')) {
$this->backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
} else {
$this->backtrace = debug_backtrace();
}
return parent::__construct();
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('whereami')
->setDefinition(array(
new InputOption('num', 'n', InputOption::VALUE_OPTIONAL, 'Number of lines before and after.', '5'),
))
->setDescription('Show where you are in the code.')
->setHelp(
<<<'HELP'
Show where you are in the code.
Optionally, include how many lines before and after you want to display.
e.g.
<return>> whereami </return>
<return>> whereami -n10</return>
HELP
);
}
/**
* Obtains the correct trace in the full backtrace.
*
* @return array
*/
protected function trace()
{
foreach ($this->backtrace as $i => $backtrace) {
if (!isset($backtrace['class'], $backtrace['function'])) {
continue;
}
$correctClass = $backtrace['class'] === 'Psy\Shell';
$correctFunction = $backtrace['function'] === 'debug';
if ($correctClass && $correctFunction) {
return $backtrace;
}
}
return end($this->backtrace);
}
/**
* Determine the file and line based on the specific backtrace.
*
* @return array
*/
protected function fileInfo()
{
$backtrace = $this->trace();
if (preg_match('/eval\(/', $backtrace['file'])) {
preg_match_all('/([^\(]+)\((\d+)/', $backtrace['file'], $matches);
$file = $matches[1][0];
$line = (int) $matches[2][0];
} else {
$file = $backtrace['file'];
$line = $backtrace['line'];
}
return compact('file', 'line');
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$info = $this->fileInfo();
$num = $input->getOption('num');
$factory = new ConsoleColorFactory($this->colorMode);
$colors = $factory->getConsoleColor();
$highlighter = new Highlighter($colors);
$contents = file_get_contents($info['file']);
$output->page($highlighter->getCodeSnippet($contents, $info['line'], $num, $num), ShellOutput::OUTPUT_RAW);
}
}

View File

@@ -1,111 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Command;
use Psy\Context;
use Psy\ContextAware;
use Psy\Output\ShellOutput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Show the last uncaught exception.
*/
class WtfCommand extends TraceCommand implements ContextAware
{
/**
* Context instance (for ContextAware interface).
*
* @var Context
*/
protected $context;
/**
* ContextAware interface.
*
* @param Context $context
*/
public function setContext(Context $context)
{
$this->context = $context;
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('wtf')
->setAliases(array('last-exception', 'wtf?'))
->setDefinition(array(
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.')
->setHelp(
<<<'HELP'
Shows a few lines of the backtrace of the most recent exception.
If you want to see more lines, add more question marks or exclamation marks:
e.g.
<return>>>> wtf ?</return>
<return>>>> wtf ?!???!?!?</return>
To see the entire backtrace, pass the -v/--verbose flag:
e.g.
<return>>>> wtf -v</return>
HELP
);
}
/**
* {@inheritdoc}
*
* --verbose is not hidden for this option :)
*
* @return array
*/
protected function getHiddenOptions()
{
$options = parent::getHiddenOptions();
unset($options[array_search('verbose', $options)]);
return $options;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$incredulity = implode('', $input->getArgument('incredulity'));
if (strlen(preg_replace('/[\\?!]/', '', $incredulity))) {
throw new \InvalidArgumentException('Incredulity must include only "?" and "!".');
}
$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);
});
}
}

View File

@@ -1,157 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy;
use Symfony\Component\Finder\Finder;
/**
* A Psy Shell Phar compiler.
*/
class Compiler
{
/**
* Compiles psysh into a single phar file.
*
* @param string $pharFile The full path to the file to create
*/
public function compile($pharFile = 'psysh.phar')
{
if (file_exists($pharFile)) {
unlink($pharFile);
}
$this->version = Shell::VERSION;
$phar = new \Phar($pharFile, 0, 'psysh.phar');
$phar->setSignatureAlgorithm(\Phar::SHA1);
$phar->startBuffering();
$finder = Finder::create()
->files()
->ignoreVCS(true)
->name('*.php')
->notName('Compiler.php')
->notName('Autoloader.php')
->in(__DIR__ . '/..');
foreach ($finder as $file) {
$this->addFile($phar, $file);
}
$finder = Finder::create()
->files()
->ignoreVCS(true)
->name('*.php')
->exclude('Tests')
->in(__DIR__ . '/../../build-vendor');
foreach ($finder as $file) {
$this->addFile($phar, $file);
}
// Stubs
$phar->setStub($this->getStub());
$phar->stopBuffering();
unset($phar);
}
/**
* Add a file to the psysh Phar.
*
* @param Phar $phar
* @param SplFileInfo $file
* @param bool $strip (default: true)
*/
private function addFile($phar, $file, $strip = true)
{
$path = str_replace(dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR, '', $file->getRealPath());
$content = file_get_contents($file);
if ($strip) {
$content = $this->stripWhitespace($content);
} elseif ('LICENSE' === basename($file)) {
$content = "\n" . $content . "\n";
}
$phar->addFromString($path, $content);
}
/**
* Removes whitespace from a PHP source string while preserving line numbers.
*
* @param string $source A PHP string
*
* @return string The PHP string with the whitespace removed
*/
private function stripWhitespace($source)
{
if (!function_exists('token_get_all')) {
return $source;
}
$output = '';
foreach (token_get_all($source) as $token) {
if (is_string($token)) {
$output .= $token;
} elseif (in_array($token[0], array(T_COMMENT, T_DOC_COMMENT))) {
$output .= str_repeat("\n", substr_count($token[1], "\n"));
} elseif (T_WHITESPACE === $token[0]) {
// reduce wide spaces
$whitespace = preg_replace('{[ \t]+}', ' ', $token[1]);
// normalize newlines to \n
$whitespace = preg_replace('{(?:\r\n|\r|\n)}', "\n", $whitespace);
// trim leading spaces
$whitespace = preg_replace('{\n +}', "\n", $whitespace);
$output .= $whitespace;
} else {
$output .= $token[1];
}
}
return $output;
}
private static function getStubLicense()
{
$license = file_get_contents(__DIR__ . '/../../LICENSE');
$license = str_replace('The MIT License (MIT)', '', $license);
$license = str_replace("\n", "\n * ", trim($license));
return $license;
}
const STUB_AUTOLOAD = <<<'EOS'
Phar::mapPhar('psysh.phar');
require 'phar://psysh.phar/build-vendor/autoload.php';
EOS;
/**
* Get a Phar stub for psysh.
*
* This is basically the psysh bin, with the autoload require statements swapped out.
*
* @return string
*/
private function getStub()
{
$content = file_get_contents(__DIR__ . '/../../bin/psysh');
$content = preg_replace('{/\* <<<.*?>>> \*/}sm', self::STUB_AUTOLOAD, $content);
$content = preg_replace('/\\(c\\) .*?with this source code./sm', self::getStubLicense(), $content);
$content .= '__HALT_COMPILER();';
return $content;
}
}

View File

@@ -1,183 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy;
use XdgBaseDir\Xdg;
/**
* A Psy Shell configuration path helper.
*/
class ConfigPaths
{
/**
* Get potential config directory paths.
*
* Returns `~/.psysh`, `%APPDATA%/PsySH` (when on Windows), and all
* XDG Base Directory config directories:
*
* http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
*
* @return string[]
*/
public static function getConfigDirs()
{
$xdg = new Xdg();
return self::getDirNames($xdg->getConfigDirs());
}
/**
* Get potential home config directory paths.
*
* Returns `~/.psysh`, `%APPDATA%/PsySH` (when on Windows), and the
* XDG Base Directory home config directory:
*
* http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
*
* @return string[]
*/
public static function getHomeConfigDirs()
{
$xdg = new Xdg();
return self::getDirNames(array($xdg->getHomeConfigDir()));
}
/**
* Get the current home config directory.
*
* Returns the highest precedence home config directory which actually
* exists. If none of them exists, returns the highest precedence home
* config directory (`%APPDATA%/PsySH` on Windows, `~/.config/psysh`
* everywhere else).
*
* @see self::getHomeConfigDirs
*
* @return string
*/
public static function getCurrentConfigDir()
{
$configDirs = self::getHomeConfigDirs();
foreach ($configDirs as $configDir) {
if (@is_dir($configDir)) {
return $configDir;
}
}
return $configDirs[0];
}
/**
* Find real config files in config directories.
*
* @param string[] $names Config file names
* @param string $configDir Optionally use a specific config directory
*
* @return string[]
*/
public static function getConfigFiles(array $names, $configDir = null)
{
$dirs = ($configDir === null) ? self::getConfigDirs() : array($configDir);
return self::getRealFiles($dirs, $names);
}
/**
* Get potential data directory paths.
*
* If a `dataDir` option was explicitly set, returns an array containing
* just that directory.
*
* Otherwise, it returns `~/.psysh` and all XDG Base Directory data directories:
*
* http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
*
* @return string[]
*/
public static function getDataDirs()
{
$xdg = new Xdg();
return self::getDirNames($xdg->getDataDirs());
}
/**
* Find real data files in config directories.
*
* @param string[] $names Config file names
* @param string $dataDir Optionally use a specific config directory
*
* @return string[]
*/
public static function getDataFiles(array $names, $dataDir = null)
{
$dirs = ($dataDir === null) ? self::getDataDirs() : array($dataDir);
return self::getRealFiles($dirs, $names);
}
/**
* Get a runtime directory.
*
* Defaults to `/psysh` inside the system's temp dir.
*
* @return string
*/
public static function getRuntimeDir()
{
$xdg = new Xdg();
return $xdg->getRuntimeDir(false) . '/psysh';
}
private static function getDirNames(array $baseDirs)
{
$dirs = array_map(function ($dir) {
return strtr($dir, '\\', '/') . '/psysh';
}, $baseDirs);
// Add ~/.psysh
if ($home = getenv('HOME')) {
$dirs[] = strtr($home, '\\', '/') . '/.psysh';
}
// Add some Windows specific ones :)
if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
if ($appData = getenv('APPDATA')) {
// AppData gets preference
array_unshift($dirs, strtr($appData, '\\', '/') . '/PsySH');
}
$dir = strtr(getenv('HOMEDRIVE') . '/' . getenv('HOMEPATH'), '\\', '/') . '/.psysh';
if (!in_array($dir, $dirs)) {
$dirs[] = $dir;
}
}
return $dirs;
}
private static function getRealFiles(array $dirNames, array $fileNames)
{
$files = array();
foreach ($dirNames as $dir) {
foreach ($fileNames as $name) {
$file = $dir . '/' . $name;
if (@is_file($file)) {
$files[] = $file;
}
}
}
return $files;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,79 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy;
use JakubOnderka\PhpConsoleColor\ConsoleColor;
use JakubOnderka\PhpConsoleHighlighter\Highlighter;
/**
* Builds `ConsoleColor` instances configured according to the given color mode.
*/
class ConsoleColorFactory
{
private $colorMode;
/**
* @param string $colorMode
*/
public function __construct($colorMode)
{
$this->colorMode = $colorMode;
}
/**
* Get a `ConsoleColor` instance configured according to the given color
* mode.
*
* @return ConsoleColor
*/
public function getConsoleColor()
{
if ($this->colorMode === Configuration::COLOR_MODE_AUTO) {
return $this->getDefaultConsoleColor();
} elseif ($this->colorMode === Configuration::COLOR_MODE_FORCED) {
return $this->getForcedConsoleColor();
} elseif ($this->colorMode === Configuration::COLOR_MODE_DISABLED) {
return $this->getDisabledConsoleColor();
}
}
private function getDefaultConsoleColor()
{
$color = new ConsoleColor();
$color->addTheme(Highlighter::LINE_NUMBER, array('blue'));
return $color;
}
private function getForcedConsoleColor()
{
$color = $this->getDefaultConsoleColor();
$color->setForceStyle(true);
return $color;
}
private function getDisabledConsoleColor()
{
$color = new ConsoleColor();
$color->addTheme(Highlighter::TOKEN_STRING, array('none'));
$color->addTheme(Highlighter::TOKEN_COMMENT, array('none'));
$color->addTheme(Highlighter::TOKEN_KEYWORD, array('none'));
$color->addTheme(Highlighter::TOKEN_DEFAULT, array('none'));
$color->addTheme(Highlighter::TOKEN_HTML, array('none'));
$color->addTheme(Highlighter::ACTUAL_LINE_MARK, array('none'));
$color->addTheme(Highlighter::LINE_NUMBER, array('none'));
return $color;
}
}

View File

@@ -1,136 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy;
/**
* The Shell execution context.
*
* This class encapsulates the current variables, most recent return value and
* exception, and the current namespace.
*/
class Context
{
private static $specialVars = array('_', '_e', '__psysh__');
private $scopeVariables = array();
private $lastException;
private $returnValue;
/**
* Get a context variable.
*
* @throws InvalidArgumentException If the variable is not found in the current context.
*
* @param string $name
*
* @return mixed
*/
public function get($name)
{
switch ($name) {
case '_':
return $this->returnValue;
case '_e':
if (!isset($this->lastException)) {
throw new \InvalidArgumentException('Unknown variable: $' . $name);
}
return $this->lastException;
default:
if (!array_key_exists($name, $this->scopeVariables)) {
throw new \InvalidArgumentException('Unknown variable: $' . $name);
}
return $this->scopeVariables[$name];
}
}
/**
* Get all defined variables.
*
* @return array
*/
public function getAll()
{
$vars = $this->scopeVariables;
$vars['_'] = $this->returnValue;
if (isset($this->lastException)) {
$vars['_e'] = $this->lastException;
}
return $vars;
}
/**
* Set all scope variables.
*
* This method does *not* set the magic $_ and $_e variables.
*
* @param array $vars
*/
public function setAll(array $vars)
{
foreach (self::$specialVars as $key) {
unset($vars[$key]);
}
$this->scopeVariables = $vars;
}
/**
* Set the most recent return value.
*
* @param mixed $value
*/
public function setReturnValue($value)
{
$this->returnValue = $value;
}
/**
* Get the most recent return value.
*
* @return mixed
*/
public function getReturnValue()
{
return $this->returnValue;
}
/**
* Set the most recent Exception.
*
* @param \Exception $e
*/
public function setLastException(\Exception $e)
{
$this->lastException = $e;
}
/**
* Get the most recent Exception.
*
* @throws InvalidArgumentException If no Exception has been caught.
*
* @return null|Exception
*/
public function getLastException()
{
if (!isset($this->lastException)) {
throw new \InvalidArgumentException('No most-recent exception');
}
return $this->lastException;
}
}

View File

@@ -1,28 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy;
/**
* ContextAware interface.
*
* This interface is used to pass the Shell's context into commands and such
* which require access to the current scope variables.
*/
interface ContextAware
{
/**
* Set the Context reference.
*
* @param Context $context
*/
public function setContext(Context $context);
}

View File

@@ -1,39 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Exception;
/**
* A break exception, used for halting the Psy Shell.
*/
class BreakException extends \Exception implements Exception
{
private $rawMessage;
/**
* {@inheritdoc}
*/
public function __construct($message = '', $code = 0, \Exception $previous = null)
{
$this->rawMessage = $message;
parent::__construct(sprintf('Exit: %s', $message), $code, $previous);
}
/**
* Return a raw (unformatted) version of the error message.
*
* @return string
*/
public function getRawMessage()
{
return $this->rawMessage;
}
}

View File

@@ -1,20 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Exception;
/**
* A DeprecatedException for Psy.
*/
class DeprecatedException extends RuntimeException
{
// This space intentionally left blank.
}

View File

@@ -1,88 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Exception;
/**
* A custom error Exception for Psy with a formatted $message.
*/
class ErrorException extends \ErrorException implements Exception
{
private $rawMessage;
/**
* Construct a Psy ErrorException.
*
* @param string $message (default: "")
* @param int $code (default: 0)
* @param int $severity (default: 1)
* @param string $filename (default: null)
* @param int $lineno (default: null)
* @param Exception $previous (default: null)
*/
public function __construct($message = '', $code = 0, $severity = 1, $filename = null, $lineno = null, $previous = null)
{
$this->rawMessage = $message;
if (!empty($filename) && preg_match('{Psy[/\\\\]ExecutionLoop}', $filename)) {
$filename = '';
}
switch ($severity) {
case E_WARNING:
case E_CORE_WARNING:
case E_COMPILE_WARNING:
case E_USER_WARNING:
$type = 'warning';
break;
case E_STRICT:
$type = 'Strict error';
break;
default:
$type = 'error';
break;
}
$message = sprintf('PHP %s: %s%s on line %d', $type, $message, $filename ? ' in ' . $filename : '', $lineno);
parent::__construct($message, $code, $severity, $filename, $lineno, $previous);
}
/**
* Get the raw (unformatted) message for this error.
*
* @return string
*/
public function getRawMessage()
{
return $this->rawMessage;
}
/**
* Helper for throwing an ErrorException.
*
* This allows us to:
*
* set_error_handler(array('Psy\Exception\ErrorException', 'throwException'));
*
* @throws ErrorException
*
* @param int $errno Error type
* @param string $errstr Message
* @param string $errfile Filename
* @param int $errline Line number
*/
public static function throwException($errno, $errstr, $errfile, $errline)
{
throw new self($errstr, 0, $errno, $errfile, $errline);
}
}

View File

@@ -1,27 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Exception;
/**
* An interface for Psy Exceptions.
*/
interface Exception
{
/**
* This is the only thing, really...
*
* Return a raw (unformatted) version of the message.
*
* @return string
*/
public function getRawMessage();
}

View File

@@ -1,47 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Exception;
/**
* A "fatal error" Exception for Psy.
*/
class FatalErrorException extends \ErrorException implements Exception
{
private $rawMessage;
/**
* Create a fatal error.
*
* @param string $message (default: "")
* @param int $code (default: 0)
* @param int $severity (default: 9000)
* @param string $filename (default: null)
* @param int $lineno (default: null)
* @param \Exception $previous (default: null)
*/
public function __construct($message = '', $code = 0, $severity = 9000, $filename = null, $lineno = null, $previous = null)
{
$this->rawMessage = $message;
$message = sprintf('PHP Fatal error: %s in %s on line %d', $message, $filename ?: "eval()'d code", $lineno);
parent::__construct($message, $code, $severity, $filename, $lineno, $previous);
}
/**
* Return a raw (unformatted) version of the error message.
*
* @return string
*/
public function getRawMessage()
{
return $this->rawMessage;
}
}

View File

@@ -1,42 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Exception;
/**
* A "parse error" Exception for Psy.
*/
class ParseErrorException extends \PhpParser\Error implements Exception
{
/**
* Constructor!
*
* @param string $message (default: "")
* @param int $line (default: -1)
*/
public function __construct($message = '', $line = -1)
{
$message = sprintf('PHP Parse error: %s', $message);
parent::__construct($message, $line);
}
/**
* Create a ParseErrorException from a PhpParser Error.
*
* @param \PhpParser\Error $e
*
* @return ParseErrorException
*/
public static function fromParseError(\PhpParser\Error $e)
{
return new self($e->getRawMessage(), $e->getRawLine());
}
}

View File

@@ -1,43 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Exception;
/**
* A RuntimeException for Psy.
*/
class RuntimeException extends \RuntimeException implements Exception
{
private $rawMessage;
/**
* Make this bad boy.
*
* @param string $message (default: "")
* @param int $code (default: 0)
* @param \Exception $previous (default: null)
*/
public function __construct($message = '', $code = 0, \Exception $previous = null)
{
$this->rawMessage = $message;
parent::__construct($message, $code, $previous);
}
/**
* Return a raw (unformatted) version of the error message.
*
* @return string
*/
public function getRawMessage()
{
return $this->rawMessage;
}
}

View File

@@ -1,37 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Exception;
/**
* A throw-up exception, used for throwing an exception out of the Psy Shell.
*/
class ThrowUpException extends \Exception implements Exception
{
/**
* {@inheritdoc}
*/
public function __construct(\Exception $exception)
{
$message = sprintf("Throwing %s with message '%s'", get_class($exception), $exception->getMessage());
parent::__construct($message, $exception->getCode(), $exception);
}
/**
* Return a raw (unformatted) version of the error message.
*
* @return string
*/
public function getRawMessage()
{
return $this->getPrevious()->getMessage();
}
}

View File

@@ -1,55 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Exception;
/**
* A "type error" Exception for Psy.
*/
class TypeErrorException extends \Exception implements Exception
{
private $rawMessage;
/**
* Constructor!
*
* @param string $message (default: "")
* @param int $code (default: 0)
*/
public function __construct($message = '', $code = 0)
{
$this->rawMessage = $message;
$message = preg_replace('/, called in .*?: eval\\(\\)\'d code/', '', $message);
parent::__construct(sprintf('TypeError: %s', $message), $code);
}
/**
* Get the raw (unformatted) message for this error.
*
* @return string
*/
public function getRawMessage()
{
return $this->rawMessage;
}
/**
* Create a TypeErrorException from a TypeError.
*
* @param \TypeError $e
*
* @return TypeErrorException
*/
public static function fromTypeError(\TypeError $e)
{
return new self($e->getMessage(), $e->getLine());
}
}

View File

@@ -1,174 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\ExecutionLoop;
use Psy\Shell;
/**
* A forking version of the Psy Shell execution loop.
*
* This version is preferred, as it won't die prematurely if user input includes
* a fatal error, such as redeclaring a class or function.
*/
class ForkingLoop extends Loop
{
private $savegame;
/**
* Run the execution loop.
*
* Forks into a master and a loop process. The loop process will handle the
* evaluation of all instructions, then return its state via a socket upon
* completion.
*
* @param Shell $shell
*/
public function run(Shell $shell)
{
list($up, $down) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
if (!$up) {
throw new \RuntimeException('Unable to create socket pair.');
}
$pid = pcntl_fork();
if ($pid < 0) {
throw new \RuntimeException('Unable to start execution loop.');
} elseif ($pid > 0) {
// This is the main thread. We'll just wait for a while.
// We won't be needing this one.
fclose($up);
// Wait for a return value from the loop process.
$read = array($down);
$write = null;
$except = null;
if (stream_select($read, $write, $except, null) === false) {
throw new \RuntimeException('Error waiting for execution loop.');
}
$content = stream_get_contents($down);
fclose($down);
if ($content) {
$shell->setScopeVariables(@unserialize($content));
}
return;
}
// This is the child process. It's going to do all the work.
if (function_exists('setproctitle')) {
setproctitle('psysh (loop)');
}
// We won't be needing this one.
fclose($down);
// Let's do some processing.
parent::run($shell);
// Send the scope variables back up to the main thread
fwrite($up, $this->serializeReturn($shell->getScopeVariables()));
fclose($up);
exit;
}
/**
* Create a savegame at the start of each loop iteration.
*/
public function beforeLoop()
{
$this->createSavegame();
}
/**
* Clean up old savegames at the end of each loop iteration.
*/
public function afterLoop()
{
// if there's an old savegame hanging around, let's kill it.
if (isset($this->savegame)) {
posix_kill($this->savegame, SIGKILL);
pcntl_signal_dispatch();
}
}
/**
* Create a savegame fork.
*
* The savegame contains the current execution state, and can be resumed in
* the event that the worker dies unexpectedly (for example, by encountering
* a PHP fatal error).
*/
private function createSavegame()
{
// the current process will become the savegame
$this->savegame = posix_getpid();
$pid = pcntl_fork();
if ($pid < 0) {
throw new \RuntimeException('Unable to create savegame fork.');
} elseif ($pid > 0) {
// we're the savegame now... let's wait and see what happens
pcntl_waitpid($pid, $status);
// worker exited cleanly, let's bail
if (!pcntl_wexitstatus($status)) {
posix_kill(posix_getpid(), SIGKILL);
}
// worker didn't exit cleanly, we'll need to have another go
$this->createSavegame();
}
}
/**
* Serialize all serializable return values.
*
* A naïve serialization will run into issues if there is a Closure or
* SimpleXMLElement (among other things) in scope when exiting the execution
* loop. We'll just ignore these unserializable classes, and serialize what
* we can.
*
* @param array $return
*
* @return string
*/
private function serializeReturn(array $return)
{
$serializable = array();
foreach ($return as $key => $value) {
// No need to return magic variables
if ($key === '_' || $key === '_e') {
continue;
}
// Resources don't error, but they don't serialize well either.
if (is_resource($value) || $value instanceof \Closure) {
continue;
}
try {
@serialize($value);
$serializable[$key] = $value;
} catch (\Exception $e) {
// we'll just ignore this one...
}
}
return @serialize($serializable);
}
}

View File

@@ -1,175 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\ExecutionLoop;
use Psy\Configuration;
use Psy\Exception\BreakException;
use Psy\Exception\ThrowUpException;
use Psy\Exception\TypeErrorException;
use Psy\Shell;
/**
* The Psy Shell execution loop.
*/
class Loop
{
const NOOP_INPUT = 'return null;';
/**
* Loop constructor.
*
* The non-forking loop doesn't have much use for Configuration, so we'll
* just ignore it.
*
* @param Configuration $config
*/
public function __construct(Configuration $config)
{
// don't need this
}
/**
* Run the execution loop.
*
* @throws ThrowUpException if thrown by the `throw-up` command.
*
* @param Shell $shell
*/
public function run(Shell $shell)
{
$loop = function ($__psysh__) {
// Load user-defined includes
set_error_handler(array($__psysh__, 'handleError'));
try {
foreach ($__psysh__->getIncludes() as $__psysh_include__) {
include $__psysh_include__;
}
} catch (\Exception $_e) {
$__psysh__->writeException($_e);
}
restore_error_handler();
unset($__psysh_include__);
extract($__psysh__->getScopeVariables());
do {
$__psysh__->beforeLoop();
$__psysh__->setScopeVariables(get_defined_vars());
try {
// read a line, see if we should eval
$__psysh__->getInput();
// evaluate the current code buffer
ob_start(
array($__psysh__, 'writeStdout'),
version_compare(PHP_VERSION, '5.4', '>=') ? 1 : 2
);
set_error_handler(array($__psysh__, 'handleError'));
$_ = eval($__psysh__->flushCode() ?: Loop::NOOP_INPUT);
restore_error_handler();
ob_end_flush();
$__psysh__->writeReturnValue($_);
} catch (BreakException $_e) {
restore_error_handler();
if (ob_get_level() > 0) {
ob_end_clean();
}
$__psysh__->writeException($_e);
return;
} catch (ThrowUpException $_e) {
restore_error_handler();
if (ob_get_level() > 0) {
ob_end_clean();
}
$__psysh__->writeException($_e);
throw $_e;
} catch (\TypeError $_e) {
restore_error_handler();
if (ob_get_level() > 0) {
ob_end_clean();
}
$__psysh__->writeException(TypeErrorException::fromTypeError($_e));
} catch (\Exception $_e) {
restore_error_handler();
if (ob_get_level() > 0) {
ob_end_clean();
}
$__psysh__->writeException($_e);
}
$__psysh__->afterLoop();
} while (true);
};
// bind the closure to $this from the shell scope variables...
if (self::bindLoop()) {
$that = null;
try {
$that = $shell->getScopeVariable('this');
} catch (\InvalidArgumentException $e) {
// well, it was worth a shot
}
if (is_object($that)) {
$loop = $loop->bindTo($that, get_class($that));
} else {
$loop = $loop->bindTo(null, null);
}
}
$loop($shell);
}
/**
* A beforeLoop callback.
*
* This is executed at the start of each loop iteration. In the default
* (non-forking) loop implementation, this is a no-op.
*/
public function beforeLoop()
{
// no-op
}
/**
* A afterLoop callback.
*
* This is executed at the end of each loop iteration. In the default
* (non-forking) loop implementation, this is a no-op.
*/
public function afterLoop()
{
// no-op
}
/**
* Decide whether to bind the execution loop.
*
* @return bool
*/
protected static function bindLoop()
{
// skip binding on HHVM <= 3.5.0
// see https://github.com/facebook/hhvm/issues/1203
if (defined('HHVM_VERSION')) {
return version_compare(HHVM_VERSION, '3.5.0', '>=');
}
return version_compare(PHP_VERSION, '5.4', '>=');
}
}

View File

@@ -1,58 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Formatter;
use JakubOnderka\PhpConsoleHighlighter\Highlighter;
use Psy\Configuration;
use Psy\ConsoleColorFactory;
use Psy\Exception\RuntimeException;
/**
* A pretty-printer for code.
*/
class CodeFormatter implements Formatter
{
/**
* Format the code represented by $reflector.
*
* @param \Reflector $reflector
* @param null|string $colorMode (default: null)
*
* @return string formatted code
*/
public static function format(\Reflector $reflector, $colorMode = null)
{
$colorMode = $colorMode ?: Configuration::COLOR_MODE_AUTO;
if ($fileName = $reflector->getFileName()) {
if (!is_file($fileName)) {
throw new RuntimeException('Source code unavailable.');
}
$file = file_get_contents($fileName);
$start = $reflector->getStartLine();
$end = $reflector->getEndLine() - $start;
$factory = new ConsoleColorFactory($colorMode);
$colors = $factory->getConsoleColor();
$highlighter = new Highlighter($colors);
return $highlighter->getCodeSnippet($file, $start, 0, $end);
// no need to escape this bad boy, since (for now) it's being output raw.
// return OutputFormatter::escape(implode(PHP_EOL, $code));
return implode(PHP_EOL, $code);
} else {
throw new RuntimeException('Source code unavailable.');
}
}
}

View File

@@ -1,168 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Formatter;
use Psy\Util\Docblock;
use Symfony\Component\Console\Formatter\OutputFormatter;
/**
* A pretty-printer for docblocks.
*/
class DocblockFormatter implements Formatter
{
private static $vectorParamTemplates = array(
'type' => 'info',
'var' => 'strong',
);
/**
* Format a docblock.
*
* @param \Reflector $reflector
*
* @return string Formatted docblock
*/
public static function format(\Reflector $reflector)
{
$docblock = new Docblock($reflector);
$chunks = array();
if (!empty($docblock->desc)) {
$chunks[] = '<comment>Description:</comment>';
$chunks[] = self::indent(OutputFormatter::escape($docblock->desc), ' ');
$chunks[] = '';
}
if (!empty($docblock->tags)) {
foreach ($docblock::$vectors as $name => $vector) {
if (isset($docblock->tags[$name])) {
$chunks[] = sprintf('<comment>%s:</comment>', self::inflect($name));
$chunks[] = self::formatVector($vector, $docblock->tags[$name]);
$chunks[] = '';
}
}
$tags = self::formatTags(array_keys($docblock::$vectors), $docblock->tags);
if (!empty($tags)) {
$chunks[] = $tags;
$chunks[] = '';
}
}
return rtrim(implode("\n", $chunks));
}
/**
* Format a docblock vector, for example, `@throws`, `@param`, or `@return`.
*
* @see DocBlock::$vectors
*
* @param array $vector
* @param array $lines
*
* @return string
*/
private static function formatVector(array $vector, array $lines)
{
$template = array(' ');
foreach ($vector as $type) {
$max = 0;
foreach ($lines as $line) {
$chunk = $line[$type];
$cur = empty($chunk) ? 0 : strlen($chunk) + 1;
if ($cur > $max) {
$max = $cur;
}
}
$template[] = self::getVectorParamTemplate($type, $max);
}
$template = implode(' ', $template);
return implode("\n", array_map(function ($line) use ($template) {
$escaped = array_map(array('Symfony\Component\Console\Formatter\OutputFormatter', 'escape'), $line);
return rtrim(vsprintf($template, $escaped));
}, $lines));
}
/**
* Format docblock tags.
*
* @param array $skip Tags to exclude
* @param array $tags Tags to format
*
* @return string formatted tags
*/
private static function formatTags(array $skip, array $tags)
{
$chunks = array();
foreach ($tags as $name => $values) {
if (in_array($name, $skip)) {
continue;
}
foreach ($values as $value) {
$chunks[] = sprintf('<comment>%s%s</comment> %s', self::inflect($name), empty($value) ? '' : ':', OutputFormatter::escape($value));
}
$chunks[] = '';
}
return implode("\n", $chunks);
}
/**
* Get a docblock vector template.
*
* @param string $type Vector type
* @param int $max Pad width
*
* @return string
*/
private static function getVectorParamTemplate($type, $max)
{
if (!isset(self::$vectorParamTemplates[$type])) {
return sprintf('%%-%ds', $max);
}
return sprintf('<%s>%%-%ds</%s>', self::$vectorParamTemplates[$type], $max, self::$vectorParamTemplates[$type]);
}
/**
* Indent a string.
*
* @param string $text String to indent
* @param string $indent (default: ' ')
*
* @return string
*/
private static function indent($text, $indent = ' ')
{
return $indent . str_replace("\n", "\n" . $indent, $text);
}
/**
* Convert underscored or whitespace separated words into sentence case.
*
* @param string $text
*
* @return string
*/
private static function inflect($text)
{
$words = trim(preg_replace('/[\s_-]+/', ' ', preg_replace('/([a-z])([A-Z])/', '$1 $2', $text)));
return implode(' ', array_map('ucfirst', explode(' ', $words)));
}
}

View File

@@ -1,25 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Formatter;
/**
* Formatter interface.
*/
interface Formatter
{
/**
* @param \Reflector $reflector
*
* @return string
*/
public static function format(\Reflector $reflector);
}

View File

@@ -1,271 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Formatter;
use Psy\Reflection\ReflectionConstant;
use Psy\Util\Json;
use Symfony\Component\Console\Formatter\OutputFormatter;
/**
* An abstract representation of a function, class or property signature.
*/
class SignatureFormatter implements Formatter
{
/**
* Format a signature for the given reflector.
*
* Defers to subclasses to do the actual formatting.
*
* @param \Reflector $reflector
*
* @return string Formatted signature.
*/
public static function format(\Reflector $reflector)
{
switch (true) {
case $reflector instanceof \ReflectionFunction:
return self::formatFunction($reflector);
// this case also covers \ReflectionObject:
case $reflector instanceof \ReflectionClass:
return self::formatClass($reflector);
case $reflector instanceof ReflectionConstant:
return self::formatConstant($reflector);
case $reflector instanceof \ReflectionMethod:
return self::formatMethod($reflector);
case $reflector instanceof \ReflectionProperty:
return self::formatProperty($reflector);
default:
throw new \InvalidArgumentException('Unexpected Reflector class: ' . get_class($reflector));
}
}
/**
* Print the signature name.
*
* @param \Reflector $reflector
*
* @return string Formatted name.
*/
public static function formatName(\Reflector $reflector)
{
return $reflector->getName();
}
/**
* Print the method, property or class modifiers.
*
* Technically this should be a trait. Can't wait for 5.4 :)
*
* @param \Reflector $reflector
*
* @return string Formatted modifiers.
*/
private static function formatModifiers(\Reflector $reflector)
{
return implode(' ', array_map(function ($modifier) {
return sprintf('<keyword>%s</keyword>', $modifier);
}, \Reflection::getModifierNames($reflector->getModifiers())));
}
/**
* Format a class signature.
*
* @param \ReflectionClass $reflector
*
* @return string Formatted signature.
*/
private static function formatClass(\ReflectionClass $reflector)
{
$chunks = array();
if ($modifiers = self::formatModifiers($reflector)) {
$chunks[] = $modifiers;
}
if (version_compare(PHP_VERSION, '5.4', '>=') && $reflector->isTrait()) {
$chunks[] = 'trait';
} else {
$chunks[] = $reflector->isInterface() ? 'interface' : 'class';
}
$chunks[] = sprintf('<class>%s</class>', self::formatName($reflector));
if ($parent = $reflector->getParentClass()) {
$chunks[] = 'extends';
$chunks[] = sprintf('<class>%s</class>', $parent->getName());
}
$interfaces = $reflector->getInterfaceNames();
if (!empty($interfaces)) {
sort($interfaces);
$chunks[] = 'implements';
$chunks[] = implode(', ', array_map(function ($name) {
return sprintf('<class>%s</class>', $name);
}, $interfaces));
}
return implode(' ', $chunks);
}
/**
* Format a constant signature.
*
* @param ReflectionConstant $reflector
*
* @return string Formatted signature.
*/
private static function formatConstant(ReflectionConstant $reflector)
{
$value = $reflector->getValue();
$style = self::getTypeStyle($value);
return sprintf(
'<keyword>const</keyword> <const>%s</const> = <%s>%s</%s>',
self::formatName($reflector),
$style,
OutputFormatter::escape(Json::encode($value)),
$style
);
}
/**
* Helper for getting output style for a given value's type.
*
* @param mixed $value
*
* @return string
*/
private static function getTypeStyle($value)
{
if (is_int($value) || is_float($value)) {
return 'number';
} elseif (is_string($value)) {
return 'string';
} elseif (is_bool($value) || is_null($value)) {
return 'bool';
} else {
return 'strong';
}
}
/**
* Format a property signature.
*
* @param \ReflectionProperty $reflector
*
* @return string Formatted signature.
*/
private static function formatProperty(\ReflectionProperty $reflector)
{
return sprintf(
'%s <strong>$%s</strong>',
self::formatModifiers($reflector),
$reflector->getName()
);
}
/**
* Format a function signature.
*
* @param \ReflectionFunction $reflector
*
* @return string Formatted signature.
*/
private static function formatFunction(\ReflectionFunctionAbstract $reflector)
{
return sprintf(
'<keyword>function</keyword> %s<function>%s</function>(%s)',
$reflector->returnsReference() ? '&' : '',
self::formatName($reflector),
implode(', ', self::formatFunctionParams($reflector))
);
}
/**
* Format a method signature.
*
* @param \ReflectionMethod $reflector
*
* @return string Formatted signature.
*/
private static function formatMethod(\ReflectionMethod $reflector)
{
return sprintf(
'%s %s',
self::formatModifiers($reflector),
self::formatFunction($reflector)
);
}
/**
* Print the function params.
*
* @param \ReflectionFunctionAbstract $reflector
*
* @return string
*/
private static function formatFunctionParams(\ReflectionFunctionAbstract $reflector)
{
$params = array();
foreach ($reflector->getParameters() as $param) {
$hint = '';
try {
if ($param->isArray()) {
$hint = '<keyword>array</keyword> ';
} elseif ($class = $param->getClass()) {
$hint = sprintf('<class>%s</class> ', $class->getName());
}
} catch (\Exception $e) {
// sometimes we just don't know...
// bad class names, or autoloaded classes that haven't been loaded yet, or whathaveyou.
// come to think of it, the only time I've seen this is with the intl extension.
// Hax: we'll try to extract it :P
$chunks = explode('$' . $param->getName(), (string) $param);
$chunks = explode(' ', trim($chunks[0]));
$guess = end($chunks);
$hint = sprintf('<urgent>%s</urgent> ', $guess);
}
if ($param->isOptional()) {
if (!$param->isDefaultValueAvailable()) {
$value = 'unknown';
$typeStyle = 'urgent';
} else {
$value = $param->getDefaultValue();
$typeStyle = self::getTypeStyle($value);
$value = is_array($value) ? 'array()' : is_null($value) ? 'null' : var_export($value, true);
}
$default = sprintf(' = <%s>%s</%s>', $typeStyle, OutputFormatter::escape($value), $typeStyle);
} else {
$default = '';
}
$params[] = sprintf(
'%s%s<strong>$%s</strong>%s',
$param->isPassedByReference() ? '&' : '',
$hint,
$param->getName(),
$default
);
}
return $params;
}
}

View File

@@ -1,26 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Output;
use Symfony\Component\Console\Output\OutputInterface;
/**
* An output pager is much the same as a regular OutputInterface, but allows
* the stream to be flushed to a pager periodically.
*/
interface OutputPager extends OutputInterface
{
/**
* Close the current pager process.
*/
public function close();
}

View File

@@ -1,39 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Output;
use Symfony\Component\Console\Output\StreamOutput;
/**
* A passthrough pager is a no-op. It simply wraps a StreamOutput's stream and
* does nothing when the pager is closed.
*/
class PassthruPager extends StreamOutput implements OutputPager
{
/**
* Constructor.
*
* @param StreamOutput $output
*/
public function __construct(StreamOutput $output)
{
parent::__construct($output->getStream());
}
/**
* Close the current pager process.
*/
public function close()
{
// nothing to do here
}
}

View File

@@ -1,103 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Output;
use Symfony\Component\Console\Output\StreamOutput;
/**
* ProcOutputPager class.
*
* A ProcOutputPager instance wraps a regular StreamOutput's stream. Rather
* than writing directly to the stream, it shells out to a pager process and
* gives that process the stream as stdout. This means regular *nix commands
* like `less` and `more` can be used to page large amounts of output.
*/
class ProcOutputPager extends StreamOutput implements OutputPager
{
private $proc;
private $pipe;
private $stream;
private $cmd;
/**
* Constructor.
*
* @param StreamOutput $output
* @param string $cmd Pager process command (default: 'less -R -S -F -X')
*/
public function __construct(StreamOutput $output, $cmd = 'less -R -S -F -X')
{
$this->stream = $output->getStream();
$this->cmd = $cmd;
}
/**
* Writes a message to the output.
*
* @param string $message A message to write to the output
* @param bool $newline Whether to add a newline or not
*
* @throws \RuntimeException When unable to write output (should never happen)
*/
public function doWrite($message, $newline)
{
$pipe = $this->getPipe();
if (false === @fwrite($pipe, $message . ($newline ? PHP_EOL : ''))) {
// @codeCoverageIgnoreStart
// should never happen
throw new \RuntimeException('Unable to write output.');
// @codeCoverageIgnoreEnd
}
fflush($pipe);
}
/**
* Close the current pager process.
*/
public function close()
{
if (isset($this->pipe)) {
fclose($this->pipe);
}
if (isset($this->proc)) {
$exit = proc_close($this->proc);
if ($exit !== 0) {
throw new \RuntimeException('Error closing output stream');
}
}
unset($this->pipe, $this->proc);
}
/**
* Get a pipe for paging output.
*
* If no active pager process exists, fork one and return its input pipe.
*/
private function getPipe()
{
if (!isset($this->pipe) || !isset($this->proc)) {
$desc = array(array('pipe', 'r'), $this->stream, fopen('php://stderr', 'w'));
$this->proc = proc_open($this->cmd, $desc, $pipes);
if (!is_resource($this->proc)) {
throw new \RuntimeException('Error opening output stream');
}
$this->pipe = $pipes[0];
}
return $this->pipe;
}
}

View File

@@ -1,203 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Output;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Output\ConsoleOutput;
/**
* A ConsoleOutput subclass specifically for Psy Shell output.
*/
class ShellOutput extends ConsoleOutput
{
const NUMBER_LINES = 128;
private $paging = 0;
private $pager;
/**
* Construct a ShellOutput instance.
*
* @param mixed $verbosity (default: self::VERBOSITY_NORMAL)
* @param bool $decorated (default: null)
* @param OutputFormatterInterface $formatter (default: null)
* @param null|string|OutputPager $pager (default: null)
*/
public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = null, OutputFormatterInterface $formatter = null, $pager = null)
{
parent::__construct($verbosity, $decorated, $formatter);
$this->initFormatters();
if ($pager === null) {
$this->pager = new PassthruPager($this);
} elseif (is_string($pager)) {
$this->pager = new ProcOutputPager($this, $pager);
} elseif ($pager instanceof OutputPager) {
$this->pager = $pager;
} else {
throw new \InvalidArgumentException('Unexpected pager parameter: ' . $pager);
}
}
/**
* Page multiple lines of output.
*
* The output pager is started
*
* If $messages is callable, it will be called, passing this output instance
* for rendering. Otherwise, all passed $messages are paged to output.
*
* Upon completion, the output pager is flushed.
*
* @param string|array|Closure $messages A string, array of strings or a callback.
* @param int $type (default: 0)
*/
public function page($messages, $type = 0)
{
if (is_string($messages)) {
$messages = (array) $messages;
}
if (!is_array($messages) && !is_callable($messages)) {
throw new \InvalidArgumentException('Paged output requires a string, array or callback.');
}
$this->startPaging();
if (is_callable($messages)) {
$messages($this);
} else {
$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();
}
/**
* Writes a message to the output.
*
* Optionally, pass `$type | self::NUMBER_LINES` as the $type parameter to
* number the lines of output.
*
* @throws \InvalidArgumentException When unknown output type is given
*
* @param string|array $messages The message as an array of lines or a single string
* @param bool $newline Whether to add a newline or not
* @param int $type The type of output
*/
public function write($messages, $newline = false, $type = 0)
{
if ($this->getVerbosity() === self::VERBOSITY_QUIET) {
return;
}
$messages = (array) $messages;
if ($type & self::NUMBER_LINES) {
$pad = strlen((string) count($messages));
$template = $this->isDecorated() ? "<aside>%{$pad}s</aside>: %s" : "%{$pad}s: %s";
if ($type & self::OUTPUT_RAW) {
$messages = array_map(array('Symfony\Component\Console\Formatter\OutputFormatter', 'escape'), $messages);
}
foreach ($messages as $i => $line) {
$messages[$i] = sprintf($template, $i, $line);
}
// clean this up for super.
$type = $type & ~self::NUMBER_LINES & ~self::OUTPUT_RAW;
}
parent::write($messages, $newline, $type);
}
/**
* Writes a message to the output.
*
* Handles paged output, or writes directly to the output stream.
*
* @param string $message A message to write to the output
* @param bool $newline Whether to add a newline or not
*/
public function doWrite($message, $newline)
{
if ($this->paging > 0) {
$this->pager->doWrite($message, $newline);
} else {
parent::doWrite($message, $newline);
}
}
/**
* Flush and close the output pager.
*/
private function closePager()
{
if ($this->paging <= 0) {
$this->pager->close();
}
}
/**
* Initialize output formatter styles.
*/
private function initFormatters()
{
$formatter = $this->getFormatter();
$formatter->setStyle('warning', new OutputFormatterStyle('black', 'yellow'));
$formatter->setStyle('aside', new OutputFormatterStyle('blue'));
$formatter->setStyle('strong', new OutputFormatterStyle(null, null, array('bold')));
$formatter->setStyle('return', new OutputFormatterStyle('cyan'));
$formatter->setStyle('urgent', new OutputFormatterStyle('red'));
$formatter->setStyle('hidden', new OutputFormatterStyle('black'));
// Visibility
$formatter->setStyle('public', new OutputFormatterStyle(null, null, array('bold')));
$formatter->setStyle('protected', new OutputFormatterStyle('yellow'));
$formatter->setStyle('private', new OutputFormatterStyle('red'));
$formatter->setStyle('global', new OutputFormatterStyle('cyan', null, array('bold')));
$formatter->setStyle('const', new OutputFormatterStyle('cyan'));
$formatter->setStyle('class', new OutputFormatterStyle('blue', null, array('underscore')));
$formatter->setStyle('function', new OutputFormatterStyle(null));
$formatter->setStyle('default', new OutputFormatterStyle(null));
// Types
$formatter->setStyle('number', new OutputFormatterStyle('magenta'));
$formatter->setStyle('string', new OutputFormatterStyle('green'));
$formatter->setStyle('bool', new OutputFormatterStyle('cyan'));
$formatter->setStyle('keyword', new OutputFormatterStyle('yellow'));
$formatter->setStyle('comment', new OutputFormatterStyle('blue'));
$formatter->setStyle('object', new OutputFormatterStyle('blue'));
$formatter->setStyle('resource', new OutputFormatterStyle('yellow'));
}
}

View File

@@ -1,91 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy;
use PhpParser\Lexer;
use PhpParser\Parser;
use PhpParser\ParserFactory as OriginalParserFactory;
/**
* Parser factory to abstract over PHP parser library versions.
*/
class ParserFactory
{
const ONLY_PHP5 = 'ONLY_PHP5';
const ONLY_PHP7 = 'ONLY_PHP7';
const PREFER_PHP5 = 'PREFER_PHP5';
const PREFER_PHP7 = 'PREFER_PHP7';
/**
* Possible kinds of parsers for the factory, from PHP parser library.
*
* @return array
*/
public static function getPossibleKinds()
{
return array('ONLY_PHP5', 'ONLY_PHP7', 'PREFER_PHP5', 'PREFER_PHP7');
}
/**
* Is this parser factory supports kinds?
*
* PHP parser < 2.0 doesn't support kinds, >= 2.0 — does.
*
* @return bool
*/
public function hasKindsSupport()
{
return class_exists('PhpParser\ParserFactory');
}
/**
* Default kind (if supported, based on current interpreter's version).
*
* @return string|null
*/
public function getDefaultKind()
{
if ($this->hasKindsSupport()) {
return version_compare(PHP_VERSION, '7.0', '>=') ? static::ONLY_PHP7 : static::ONLY_PHP5;
}
}
/**
* New parser instance with given kind.
*
* @param string|null $kind One of class constants (only for PHP parser 2.0 and above).
*
* @return Parser
*/
public function createParser($kind = null)
{
if ($this->hasKindsSupport()) {
$originalFactory = new OriginalParserFactory();
$kind = $kind ?: $this->getDefaultKind();
if (!in_array($kind, static::getPossibleKinds())) {
throw new \InvalidArgumentException('Unknown parser kind');
}
$parser = $originalFactory->create(constant('PhpParser\ParserFactory::' . $kind));
} else {
if ($kind !== null) {
throw new \InvalidArgumentException('Install PHP Parser v2.x to specify parser kind');
}
$parser = new Parser(new Lexer());
}
return $parser;
}
}

View File

@@ -1,155 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Readline;
/**
* A Readline interface implementation for GNU Readline.
*
* This is by far the coolest way to do it, but it doesn't work with new PHP.
*
* Oh well.
*/
class GNUReadline implements Readline
{
protected $historyFile;
protected $historySize;
protected $eraseDups;
/**
* GNU Readline is supported iff `readline_list_history` is defined. PHP
* decided it would be awesome to swap out GNU Readline for Libedit, but
* they ended up shipping an incomplete implementation. So we've got this.
*
* @return bool
*/
public static function isSupported()
{
return function_exists('readline_list_history');
}
/**
* GNU Readline constructor.
*/
public function __construct($historyFile = null, $historySize = 0, $eraseDups = false)
{
$this->historyFile = $historyFile;
$this->historySize = $historySize;
$this->eraseDups = $eraseDups;
}
/**
* {@inheritdoc}
*/
public function addHistory($line)
{
if ($res = readline_add_history($line)) {
$this->writeHistory();
}
return $res;
}
/**
* {@inheritdoc}
*/
public function clearHistory()
{
if ($res = readline_clear_history()) {
$this->writeHistory();
}
return $res;
}
/**
* {@inheritdoc}
*/
public function listHistory()
{
return readline_list_history();
}
/**
* {@inheritdoc}
*/
public function readHistory()
{
// Workaround PHP bug #69054
//
// If open_basedir is set, readline_read_history() segfaults. This will be fixed in 5.6.7:
//
// https://github.com/php/php-src/blob/423a057023ef3c00d2ffc16a6b43ba01d0f71796/NEWS#L19-L21
//
// TODO: add a PHP version check after next point release
if (!ini_get('open_basedir')) {
readline_read_history();
}
readline_clear_history();
return readline_read_history($this->historyFile);
}
/**
* {@inheritdoc}
*/
public function readline($prompt = null)
{
return readline($prompt);
}
/**
* {@inheritdoc}
*/
public function redisplay()
{
readline_redisplay();
}
/**
* {@inheritdoc}
*/
public function writeHistory()
{
// We have to write history first, since it is used
// by Libedit to list history
$res = readline_write_history($this->historyFile);
if (!$res || !$this->eraseDups && !$this->historySize > 0) {
return $res;
}
$hist = $this->listHistory();
if (!$hist) {
return true;
}
if ($this->eraseDups) {
// flip-flip technique: removes duplicates, latest entries win.
$hist = array_flip(array_flip($hist));
// sort on keys to get the order back
ksort($hist);
}
if ($this->historySize > 0) {
$histsize = count($hist);
if ($histsize > $this->historySize) {
$hist = array_slice($hist, $histsize - $this->historySize);
}
}
readline_clear_history();
foreach ($hist as $line) {
readline_add_history($line);
}
return readline_write_history($this->historyFile);
}
}

View File

@@ -1,83 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Readline;
use Psy\Util\Str;
/**
* A Libedit-based Readline implementation.
*
* This is largely the same as the Readline implementation, but it emulates
* support for `readline_list_history` since PHP decided it was a good idea to
* ship a fake Readline implementation that is missing history support.
*/
class Libedit extends GNUReadline
{
/**
* Let's emulate GNU Readline by manually reading and parsing the history file!
*
* @return bool
*/
public static function isSupported()
{
return function_exists('readline') && !function_exists('readline_list_history');
}
/**
* {@inheritdoc}
*/
public function listHistory()
{
$history = file_get_contents($this->historyFile);
if (!$history) {
return array();
}
// libedit doesn't seem to support non-unix line separators.
$history = explode("\n", $history);
// shift the history signature, ensure it's valid
if (array_shift($history) !== '_HiStOrY_V2_') {
return array();
}
// decode the line
$history = array_map(array($this, 'parseHistoryLine'), $history);
// filter empty lines & comments
return array_values(array_filter($history));
}
/**
* From GNUReadline (readline/histfile.c & readline/histexpand.c):
* lines starting with "\0" are comments or timestamps;
* if "\0" is found in an entry,
* everything from it until the next line is a comment.
*
* @param string $line The history line to parse.
*
* @return string | null
*/
protected function parseHistoryLine($line)
{
// empty line, comment or timestamp
if (!$line || $line[0] === "\0") {
return;
}
// if "\0" is found in an entry, then
// everything from it until the end of line is a comment.
if (($pos = strpos($line, "\0")) !== false) {
$line = substr($line, 0, $pos);
}
return ($line !== '') ? Str::unvis($line) : null;
}
}

View File

@@ -1,76 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Readline;
/**
* An interface abstracting the various readline_* functions.
*/
interface Readline
{
/**
* Check whether this Readline class is supported by the current system.
*
* @return bool
*/
public static function isSupported();
/**
* Add a line to the command history.
*
* @param string $line
*
* @return bool Success
*/
public function addHistory($line);
/**
* Clear the command history.
*
* @return bool Success
*/
public function clearHistory();
/**
* List the command history.
*
* @return array
*/
public function listHistory();
/**
* Read the command history.
*
* @return bool Success
*/
public function readHistory();
/**
* Read a single line of input from the user.
*
* @param null|string $prompt
*
* @return false|string
*/
public function readline($prompt = null);
/**
* Redraw readline to redraw the display.
*/
public function redisplay();
/**
* Write the command history to a file.
*
* @return bool Success
*/
public function writeHistory();
}

View File

@@ -1,146 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Readline;
use Psy\Exception\BreakException;
/**
* An array-based Readline emulation implementation.
*/
class Transient implements Readline
{
private $history;
private $historySize;
private $eraseDups;
/**
* Transient Readline is always supported.
*
* {@inheritdoc}
*/
public static function isSupported()
{
return true;
}
/**
* Transient Readline constructor.
*/
public function __construct($historyFile = null, $historySize = 0, $eraseDups = false)
{
// don't do anything with the history file...
$this->history = array();
$this->historySize = $historySize;
$this->eraseDups = $eraseDups;
}
/**
* {@inheritdoc}
*/
public function addHistory($line)
{
if ($this->eraseDups) {
if (($key = array_search($line, $this->history)) !== false) {
unset($this->history[$key]);
}
}
$this->history[] = $line;
if ($this->historySize > 0) {
$histsize = count($this->history);
if ($histsize > $this->historySize) {
$this->history = array_slice($this->history, $histsize - $this->historySize);
}
}
$this->history = array_values($this->history);
return true;
}
/**
* {@inheritdoc}
*/
public function clearHistory()
{
$this->history = array();
return true;
}
/**
* {@inheritdoc}
*/
public function listHistory()
{
return $this->history;
}
/**
* {@inheritdoc}
*/
public function readHistory()
{
return true;
}
/**
* {@inheritdoc}
*
* @throws BreakException if user hits Ctrl+D
*
* @return string
*/
public function readline($prompt = null)
{
echo $prompt;
return rtrim(fgets($this->getStdin(), 1024));
}
/**
* {@inheritdoc}
*/
public function redisplay()
{
// noop
}
/**
* {@inheritdoc}
*/
public function writeHistory()
{
return true;
}
/**
* Get a STDIN file handle.
*
* @throws BreakException if user hits Ctrl+D
*
* @return resource
*/
private function getStdin()
{
if (!isset($this->stdin)) {
$this->stdin = fopen('php://stdin', 'r');
}
if (feof($this->stdin)) {
throw new BreakException('Ctrl+D');
}
return $this->stdin;
}
}

View File

@@ -1,139 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Reflection;
/**
* Somehow the standard reflection library doesn't include constants.
*
* ReflectionConstant corrects that omission.
*/
class ReflectionConstant implements \Reflector
{
private $class;
private $name;
private $value;
/**
* Construct a ReflectionConstant object.
*
* @param mixed $class
* @param string $name
*/
public function __construct($class, $name)
{
if (!$class instanceof \ReflectionClass) {
$class = new \ReflectionClass($class);
}
$this->class = $class;
$this->name = $name;
$constants = $class->getConstants();
if (!array_key_exists($name, $constants)) {
throw new \InvalidArgumentException('Unknown constant: ' . $name);
}
$this->value = $constants[$name];
}
/**
* Gets the declaring class.
*
* @return string
*/
public function getDeclaringClass()
{
return $this->class;
}
/**
* Gets the constant name.
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Gets the value of the constant.
*
* @return mixed
*/
public function getValue()
{
return $this->value;
}
/**
* Gets the constant's file name.
*
* Currently returns null, because if it returns a file name the signature
* formatter will barf.
*/
public function getFileName()
{
return;
// return $this->class->getFileName();
}
/**
* Get the code start line.
*
* @throws \RuntimeException
*/
public function getStartLine()
{
throw new \RuntimeException('Not yet implemented because it\'s unclear what I should do here :)');
}
/**
* Get the code end line.
*
* @throws \RuntimeException
*/
public function getEndLine()
{
return $this->getStartLine();
}
/**
* Get the constant's docblock.
*
* @return false
*/
public function getDocComment()
{
return false;
}
/**
* Export the constant? I don't think this is possible.
*
* @throws \RuntimeException
*/
public static function export()
{
throw new \RuntimeException('Not yet implemented because it\'s unclear what I should do here :)');
}
/**
* To string.
*
* @return string
*/
public function __toString()
{
return $this->getName();
}
}

View File

@@ -1,887 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy;
use Psy\Exception\BreakException;
use Psy\Exception\ErrorException;
use Psy\Exception\Exception as PsyException;
use Psy\Exception\ThrowUpException;
use Psy\Output\ShellOutput;
use Psy\TabCompletion\Matcher;
use Psy\VarDumper\PresenterAware;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command as BaseCommand;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\OutputInterface;
/**
* The Psy Shell application.
*
* Usage:
*
* $shell = new Shell;
* $shell->run();
*
* @author Justin Hileman <justin@justinhileman.info>
*/
class Shell extends Application
{
const VERSION = 'v0.7.2';
const PROMPT = '>>> ';
const BUFF_PROMPT = '... ';
const REPLAY = '--> ';
const RETVAL = '=> ';
private $config;
private $cleaner;
private $output;
private $readline;
private $inputBuffer;
private $code;
private $codeBuffer;
private $codeBufferOpen;
private $context;
private $includes;
private $loop;
private $outputWantsNewline = false;
private $completion;
private $tabCompletionMatchers = array();
/**
* Create a new Psy Shell.
*
* @param Configuration $config (default: null)
*/
public function __construct(Configuration $config = null)
{
$this->config = $config ?: new Configuration();
$this->cleaner = $this->config->getCodeCleaner();
$this->loop = $this->config->getLoop();
$this->context = new Context();
$this->includes = array();
$this->readline = $this->config->getReadline();
parent::__construct('Psy Shell', self::VERSION);
$this->config->setShell($this);
}
/**
* Check whether the first thing in a backtrace is an include call.
*
* This is used by the psysh bin to decide whether to start a shell on boot,
* or to simply autoload the library.
*/
public static function isIncluded(array $trace)
{
return isset($trace[0]['function']) &&
in_array($trace[0]['function'], array('require', 'include', 'require_once', 'include_once'));
}
/**
* Invoke a Psy Shell from the current context.
*
* For example:
*
* foreach ($items as $item) {
* \Psy\Shell::debug(get_defined_vars());
* }
*
* If you would like your shell interaction to affect the state of the
* current context, you can extract() the values returned from this call:
*
* foreach ($items as $item) {
* extract(\Psy\Shell::debug(get_defined_vars()));
* var_dump($item); // will be whatever you set $item to in Psy Shell
* }
*
* Optionally, supply an object as the `$bind` parameter. This determines
* the value `$this` will have in the shell, and sets up class scope so that
* private and protected members are accessible:
*
* class Foo {
* function bar() {
* \Psy\Shell::debug(get_defined_vars(), $this);
* }
* }
*
* This only really works in PHP 5.4+ and HHVM 3.5+, so upgrade already.
*
* @param array $vars Scope variables from the calling context (default: array())
* @param object $bind Bound object ($this) value for the shell
*
* @return array Scope variables from the debugger session.
*/
public static function debug(array $vars = array(), $bind = null)
{
echo PHP_EOL;
if ($bind !== null) {
$vars['this'] = $bind;
}
$sh = new \Psy\Shell();
$sh->setScopeVariables($vars);
$sh->run();
return $sh->getScopeVariables();
}
/**
* Adds a command object.
*
* {@inheritdoc}
*
* @param BaseCommand $command A Symfony Console Command object
*
* @return BaseCommand The registered command
*/
public function add(BaseCommand $command)
{
if ($ret = parent::add($command)) {
if ($ret instanceof ContextAware) {
$ret->setContext($this->context);
}
if ($ret instanceof PresenterAware) {
$ret->setPresenter($this->config->getPresenter());
}
}
return $ret;
}
/**
* Gets the default input definition.
*
* @return InputDefinition An InputDefinition instance
*/
protected function getDefaultInputDefinition()
{
return new InputDefinition(array(
new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message.'),
));
}
/**
* Gets the default commands that should always be available.
*
* @return array An array of default Command instances
*/
protected function getDefaultCommands()
{
$hist = new Command\HistoryCommand();
$hist->setReadline($this->readline);
return array(
new Command\HelpCommand(),
new Command\ListCommand(),
new Command\DumpCommand(),
new Command\DocCommand(),
new Command\ShowCommand($this->config->colorMode()),
new Command\WtfCommand(),
new Command\WhereamiCommand($this->config->colorMode()),
new Command\ThrowUpCommand(),
new Command\TraceCommand(),
new Command\BufferCommand(),
new Command\ClearCommand(),
// new Command\PsyVersionCommand(),
$hist,
new Command\ExitCommand(),
);
}
/**
* @return array
*/
protected function getTabCompletionMatchers()
{
if (empty($this->tabCompletionMatchers)) {
$this->tabCompletionMatchers = array(
new Matcher\CommandsMatcher($this->all()),
new Matcher\KeywordsMatcher(),
new Matcher\VariablesMatcher(),
new Matcher\ConstantsMatcher(),
new Matcher\FunctionsMatcher(),
new Matcher\ClassNamesMatcher(),
new Matcher\ClassMethodsMatcher(),
new Matcher\ClassAttributesMatcher(),
new Matcher\ObjectMethodsMatcher(),
new Matcher\ObjectAttributesMatcher(),
);
}
return $this->tabCompletionMatchers;
}
/**
* @param array $matchers
*/
public function addTabCompletionMatchers(array $matchers)
{
$this->tabCompletionMatchers = array_merge($matchers, $this->getTabCompletionMatchers());
}
/**
* Set the Shell output.
*
* @param OutputInterface $output
*/
public function setOutput(OutputInterface $output)
{
$this->output = $output;
}
/**
* Runs the current application.
*
* @param InputInterface $input An Input instance
* @param OutputInterface $output An Output instance
*
* @return int 0 if everything went fine, or an error code
*/
public function run(InputInterface $input = null, OutputInterface $output = null)
{
$this->initializeTabCompletion();
if ($input === null && !isset($_SERVER['argv'])) {
$input = new ArgvInput(array());
}
if ($output === null) {
$output = $this->config->getOutput();
}
try {
return parent::run($input, $output);
} catch (\Exception $e) {
$this->writeException($e);
}
}
/**
* Runs the current application.
*
* @throws Exception if thrown via the `throw-up` command.
*
* @param InputInterface $input An Input instance
* @param OutputInterface $output An Output instance
*
* @return int 0 if everything went fine, or an error code
*/
public function doRun(InputInterface $input, OutputInterface $output)
{
$this->setOutput($output);
$this->resetCodeBuffer();
$this->setAutoExit(false);
$this->setCatchExceptions(false);
$this->readline->readHistory();
// if ($this->config->useReadline()) {
// readline_completion_function(array($this, 'autocomplete'));
// }
$this->output->writeln($this->getHeader());
try {
$this->loop->run($this);
} catch (ThrowUpException $e) {
throw $e->getPrevious();
}
}
/**
* Read user input.
*
* This will continue fetching user input until the code buffer contains
* valid code.
*
* @throws BreakException if user hits Ctrl+D
*/
public function getInput()
{
$this->codeBufferOpen = false;
do {
// reset output verbosity (in case it was altered by a subcommand)
$this->output->setVerbosity(ShellOutput::VERBOSITY_VERBOSE);
$input = $this->readline();
/*
* Handle Ctrl+D. It behaves differently in different cases:
*
* 1) In an expression, like a function or "if" block, clear the input buffer
* 2) At top-level session, behave like the exit command
*/
if ($input === false) {
$this->output->writeln('');
if ($this->hasCode()) {
$this->resetCodeBuffer();
} else {
throw new BreakException('Ctrl+D');
}
}
// handle empty input
if (trim($input) === '') {
continue;
}
if ($this->hasCommand($input)) {
$this->readline->addHistory($input);
$this->runCommand($input);
continue;
}
$this->addCode($input);
} while (!$this->hasValidCode());
}
/**
* Pass the beforeLoop callback through to the Loop instance.
*
* @see Loop::beforeLoop
*/
public function beforeLoop()
{
$this->loop->beforeLoop();
}
/**
* Pass the afterLoop callback through to the Loop instance.
*
* @see Loop::afterLoop
*/
public function afterLoop()
{
$this->loop->afterLoop();
}
/**
* Set the variables currently in scope.
*
* @param array $vars
*/
public function setScopeVariables(array $vars)
{
$this->context->setAll($vars);
}
/**
* Return the set of variables currently in scope.
*
* @return array Associative array of scope variables.
*/
public function getScopeVariables()
{
return $this->context->getAll();
}
/**
* Get the set of variable names currently in scope.
*
* @return array Array of variable names.
*/
public function getScopeVariableNames()
{
return array_keys($this->context->getAll());
}
/**
* Get a scope variable value by name.
*
* @param string $name
*
* @return mixed
*/
public function getScopeVariable($name)
{
return $this->context->get($name);
}
/**
* Add includes, to be parsed and executed before running the interactive shell.
*
* @param array $includes
*/
public function setIncludes(array $includes = array())
{
$this->includes = $includes;
}
/**
* Get PHP files to be parsed and executed before running the interactive shell.
*
* @return array
*/
public function getIncludes()
{
return array_merge($this->config->getDefaultIncludes(), $this->includes);
}
/**
* Check whether this shell's code buffer contains code.
*
* @return bool True if the code buffer contains code.
*/
public function hasCode()
{
return !empty($this->codeBuffer);
}
/**
* Check whether the code in this shell's code buffer is valid.
*
* If the code is valid, the code buffer should be flushed and evaluated.
*
* @return bool True if the code buffer content is valid.
*/
protected function hasValidCode()
{
return !$this->codeBufferOpen && $this->code !== false;
}
/**
* Add code to the code buffer.
*
* @param string $code
*/
public function addCode($code)
{
try {
// Code lines ending in \ keep the buffer open
if (substr(rtrim($code), -1) === '\\') {
$this->codeBufferOpen = true;
$code = substr(rtrim($code), 0, -1);
} else {
$this->codeBufferOpen = false;
}
$this->codeBuffer[] = $code;
$this->code = $this->cleaner->clean($this->codeBuffer, $this->config->requireSemicolons());
} catch (\Exception $e) {
// Add failed code blocks to the readline history.
$this->readline->addHistory(implode("\n", $this->codeBuffer));
throw $e;
}
}
/**
* Get the current code buffer.
*
* This is useful for commands which manipulate the buffer.
*
* @return array
*/
public function getCodeBuffer()
{
return $this->codeBuffer;
}
/**
* Run a Psy Shell command given the user input.
*
* @throws InvalidArgumentException if the input is not a valid command.
*
* @param string $input User input string
*
* @return mixed Who knows?
*/
protected function runCommand($input)
{
$command = $this->getCommand($input);
if (empty($command)) {
throw new \InvalidArgumentException('Command not found: ' . $input);
}
$input = new StringInput(str_replace('\\', '\\\\', rtrim($input, " \t\n\r\0\x0B;")));
if ($input->hasParameterOption(array('--help', '-h'))) {
$helpCommand = $this->get('help');
$helpCommand->setCommand($command);
return $helpCommand->run($input, $this->output);
}
return $command->run($input, $this->output);
}
/**
* Reset the current code buffer.
*
* This should be run after evaluating user input, catching exceptions, or
* on demand by commands such as BufferCommand.
*/
public function resetCodeBuffer()
{
$this->codeBuffer = array();
$this->code = false;
}
/**
* Inject input into the input buffer.
*
* This is useful for commands which want to replay history.
*
* @param string|array $input
*/
public function addInput($input)
{
foreach ((array) $input as $line) {
$this->inputBuffer[] = $line;
}
}
/**
* Flush the current (valid) code buffer.
*
* If the code buffer is valid, resets the code buffer and returns the
* current code.
*
* @return string PHP code buffer contents.
*/
public function flushCode()
{
if ($this->hasValidCode()) {
$this->readline->addHistory(implode("\n", $this->codeBuffer));
$code = $this->code;
$this->resetCodeBuffer();
return $code;
}
}
/**
* Get the current evaluation scope namespace.
*
* @see CodeCleaner::getNamespace
*
* @return string Current code namespace.
*/
public function getNamespace()
{
if ($namespace = $this->cleaner->getNamespace()) {
return implode('\\', $namespace);
}
}
/**
* Write a string to stdout.
*
* This is used by the shell loop for rendering output from evaluated code.
*
* @param string $out
* @param int $phase Output buffering phase
*/
public function writeStdout($out, $phase = PHP_OUTPUT_HANDLER_END)
{
$isCleaning = false;
if (version_compare(PHP_VERSION, '5.4', '>=')) {
$isCleaning = $phase & PHP_OUTPUT_HANDLER_CLEAN;
}
// Incremental flush
if ($out !== '' && !$isCleaning) {
$this->output->write($out, false, ShellOutput::OUTPUT_RAW);
$this->outputWantsNewline = (substr($out, -1) !== "\n");
}
// Output buffering is done!
if ($this->outputWantsNewline && $phase & PHP_OUTPUT_HANDLER_END) {
$this->output->writeln(sprintf('<aside>%s</aside>', $this->config->useUnicode() ? '⏎' : '\\n'));
$this->outputWantsNewline = false;
}
}
/**
* Write a return value to stdout.
*
* The return value is formatted or pretty-printed, and rendered in a
* visibly distinct manner (in this case, as cyan).
*
* @see self::presentValue
*
* @param mixed $ret
*/
public function writeReturnValue($ret)
{
$this->context->setReturnValue($ret);
$ret = $this->presentValue($ret);
$indent = str_repeat(' ', strlen(self::RETVAL));
$this->output->writeln(self::RETVAL . str_replace(PHP_EOL, PHP_EOL . $indent, $ret));
}
/**
* Renders a caught Exception.
*
* Exceptions are formatted according to severity. ErrorExceptions which were
* warnings or Strict errors aren't rendered as harshly as real errors.
*
* Stores $e as the last Exception in the Shell Context.
*
* @param \Exception $e An exception instance
* @param OutputInterface $output An OutputInterface instance
*/
public function writeException(\Exception $e)
{
$this->context->setLastException($e);
$message = $e->getMessage();
if (!$e instanceof PsyException) {
$message = sprintf('%s with message \'%s\'', get_class($e), $message);
}
$severity = ($e instanceof \ErrorException) ? $this->getSeverity($e) : 'error';
$this->output->writeln(sprintf('<%s>%s</%s>', $severity, OutputFormatter::escape($message), $severity));
$this->resetCodeBuffer();
}
/**
* Helper for getting an output style for the given ErrorException's level.
*
* @param \ErrorException $e
*
* @return string
*/
protected function getSeverity(\ErrorException $e)
{
$severity = $e->getSeverity();
if ($severity & error_reporting()) {
switch ($severity) {
case E_WARNING:
case E_NOTICE:
case E_CORE_WARNING:
case E_COMPILE_WARNING:
case E_USER_WARNING:
case E_USER_NOTICE:
case E_STRICT:
return 'warning';
default:
return 'error';
}
} else {
// Since this is below the user's reporting threshold, it's always going to be a warning.
return 'warning';
}
}
/**
* Helper for throwing an ErrorException.
*
* This allows us to:
*
* set_error_handler(array($psysh, 'handleError'));
*
* Unlike ErrorException::throwException, this error handler respects the
* current error_reporting level; i.e. it logs warnings and notices, but
* doesn't throw an exception unless it's above the current error_reporting
* threshold. This should probably only be used in the inner execution loop
* of the shell, as most of the time a thrown exception is much more useful.
*
* If the error type matches the `errorLoggingLevel` config, it will be
* logged as well, regardless of the `error_reporting` level.
*
* @see \Psy\Exception\ErrorException::throwException
* @see \Psy\Shell::writeException
*
* @throws \Psy\Exception\ErrorException depending on the current error_reporting level.
*
* @param int $errno Error type
* @param string $errstr Message
* @param string $errfile Filename
* @param int $errline Line number
*/
public function handleError($errno, $errstr, $errfile, $errline)
{
if ($errno & error_reporting()) {
ErrorException::throwException($errno, $errstr, $errfile, $errline);
} elseif ($errno & $this->config->errorLoggingLevel()) {
// log it and continue...
$this->writeException(new ErrorException($errstr, 0, $errno, $errfile, $errline));
}
}
/**
* Format a value for display.
*
* @see Presenter::present
*
* @param mixed $val
*
* @return string Formatted value
*/
protected function presentValue($val)
{
return $this->config->getPresenter()->present($val);
}
/**
* Get a command (if one exists) for the current input string.
*
* @param string $input
*
* @return null|Command
*/
protected function getCommand($input)
{
$input = new StringInput($input);
if ($name = $input->getFirstArgument()) {
return $this->get($name);
}
}
/**
* Check whether a command is set for the current input string.
*
* @param string $input
*
* @return bool True if the shell has a command for the given input.
*/
protected function hasCommand($input)
{
$input = new StringInput($input);
if ($name = $input->getFirstArgument()) {
return $this->has($name);
}
return false;
}
/**
* Get the current input prompt.
*
* @return string
*/
protected function getPrompt()
{
return $this->hasCode() ? self::BUFF_PROMPT : self::PROMPT;
}
/**
* Read a line of user input.
*
* This will return a line from the input buffer (if any exist). Otherwise,
* it will ask the user for input.
*
* If readline is enabled, this delegates to readline. Otherwise, it's an
* ugly `fgets` call.
*
* @return string One line of user input.
*/
protected function readline()
{
if (!empty($this->inputBuffer)) {
$line = array_shift($this->inputBuffer);
$this->output->writeln(sprintf('<aside>%s %s</aside>', self::REPLAY, OutputFormatter::escape($line)));
return $line;
}
return $this->readline->readline($this->getPrompt());
}
/**
* Get the shell output header.
*
* @return string
*/
protected function getHeader()
{
return sprintf('<aside>%s by Justin Hileman</aside>', $this->getVersion());
}
/**
* Get the current version of Psy Shell.
*
* @return string
*/
public function getVersion()
{
$separator = $this->config->useUnicode() ? '—' : '-';
return sprintf('Psy Shell %s (PHP %s %s %s)', self::VERSION, phpversion(), $separator, php_sapi_name());
}
/**
* Get a PHP manual database instance.
*
* @return PDO|null
*/
public function getManualDb()
{
return $this->config->getManualDb();
}
/**
* Autocomplete variable names.
*
* This is used by `readline` for tab completion.
*
* @param string $text
*
* @return mixed Array possible completions for the given input, if any.
*/
protected function autocomplete($text)
{
$info = readline_info();
// $line = substr($info['line_buffer'], 0, $info['end']);
// Check whether there's a command for this
// $words = explode(' ', $line);
// $firstWord = reset($words);
// check whether this is a variable...
$firstChar = substr($info['line_buffer'], max(0, $info['end'] - strlen($text) - 1), 1);
if ($firstChar === '$') {
return $this->getScopeVariableNames();
}
}
/**
* Initialize tab completion matchers.
*
* If tab completion is enabled this adds tab completion matchers to the
* auto completer and sets context if needed.
*/
protected function initializeTabCompletion()
{
// auto completer needs shell to be linked to configuration because of the context aware matchers
if ($this->config->getTabCompletion()) {
$this->completion = $this->config->getAutoCompleter();
$this->addTabCompletionMatchers($this->config->getTabCompletionMatchers());
foreach ($this->getTabCompletionMatchers() as $matcher) {
if ($matcher instanceof ContextAware) {
$matcher->setContext($this->context);
}
$this->completion->addMatcher($matcher);
}
$this->completion->activate();
}
}
}

View File

@@ -1,102 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\TabCompletion;
use Psy\TabCompletion\Matcher\AbstractMatcher;
/**
* A readline tab completion service.
*
* @author Marc Garcia <markcial@gmail.com>
*/
class AutoCompleter
{
/** @var Matcher\AbstractMatcher[] */
protected $matchers;
/**
* Register a tab completion Matcher.
*
* @param AbstractMatcher $matcher
*/
public function addMatcher(AbstractMatcher $matcher)
{
$this->matchers[] = $matcher;
}
/**
* Activate readline tab completion.
*/
public function activate()
{
readline_completion_function(array(&$this, 'callback'));
}
/**
* Handle readline completion.
*
* @param string $input Readline current word
* @param int $index Current word index
* @param array $info readline_info() data
*
* @return array
*/
public function processCallback($input, $index, $info = array())
{
$line = substr($info['line_buffer'], 0, $info['end']);
$tokens = token_get_all('<?php ' . $line);
// remove whitespaces
$tokens = array_filter($tokens, function ($token) {
return !AbstractMatcher::tokenIs($token, AbstractMatcher::T_WHITESPACE);
});
$matches = array();
foreach ($this->matchers as $matcher) {
if ($matcher->hasMatched($tokens)) {
$matches = array_merge($matcher->getMatches($tokens), $matches);
}
}
$matches = array_unique($matches);
return !empty($matches) ? $matches : array('');
}
/**
* The readline_completion_function callback handler.
*
* @see processCallback
*
* @param $input
* @param $index
*
* @return array
*/
public function callback($input, $index)
{
return $this->processCallback($input, $index, readline_info());
}
/**
* Remove readline callback handler on destruct.
*/
public function __destruct()
{
// PHP didn't implement the whole readline API when they first switched
// to libedit. And they still haven't.
//
// So this is a thing to make PsySH work on 5.3.x:
if (function_exists('readline_callback_handler_remove')) {
readline_callback_handler_remove();
}
}
}

View File

@@ -1,65 +0,0 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2015 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\TabCompletion\Matcher;
use Psy\Context;
use Psy\ContextAware;
/**
* An abstract tab completion Matcher which implements ContextAware.
*
* The AutoCompleter service will inject a Context instance into all
* ContextAware Matchers.
*
* @author Marc Garcia <markcial@gmail.com>
*/
abstract class AbstractContextAwareMatcher extends AbstractMatcher implements ContextAware
{
/**
* Context instance (for ContextAware interface).
*
* @var Context
*/
protected $context;
/**
* ContextAware interface.
*
* @param Context $context
*/
public function setContext(Context $context)
{
$this->context = $context;
}
/**
* Get a Context variable by name.
*
* @param $var Variable name
*
* @return mixed
*/
protected function getVariable($var)
{
return $this->context->get($var);
}
/**
* Get all variables in the current Context.
*
* @return array
*/
protected function getVariables()
{
return $this->context->getAll();
}
}

Some files were not shown because too many files have changed in this diff Show More