My first commit of codes

This commit is contained in:
sujitprasad
2015-05-01 13:13:01 +05:30
parent 4c8e5096f1
commit a6e5a69348
8487 changed files with 1317246 additions and 0 deletions

12
vendor/psy/psysh/.editorconfig vendored Normal file
View File

@@ -0,0 +1,12 @@
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

5
vendor/psy/psysh/.gitignore vendored Normal file
View File

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

16
vendor/psy/psysh/.php_cs vendored Normal file
View File

@@ -0,0 +1,16 @@
<?php
use Symfony\CS\Config\Config;
use Symfony\CS\FixerInterface;
$config = Config::create()
// use symfony level and extra fixers:
->level(Symfony\CS\FixerInterface::SYMFONY_LEVEL)
->fixers(array('align_double_arrow', '-concat_without_spaces', 'concat_with_spaces', 'ordered_use', 'strict'))
->setUsingLinter(false);
$finder = $config->getFinder()
->in('src')
->in('test');
return $config;

10
vendor/psy/psysh/.styleci.yml vendored Normal file
View File

@@ -0,0 +1,10 @@
preset: symfony
enabled:
- align_double_arrow
- concat_with_spaces
- ordered_use
- strict
disabled:
- concat_without_spaces

21
vendor/psy/psysh/.travis.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
language: php
php:
- 5.3
- 5.4
- 5.5
- 5.6
- hhvm
- hhvm-nightly
install:
- travis_retry composer install --no-interaction --prefer-source
script:
- vendor/bin/phpunit
matrix:
allow_failures:
- php: hhvm
- php: hhvm-nightly
fast_finish: true

15
vendor/psy/psysh/CONTRIBUTING.md vendored Normal file
View File

@@ -0,0 +1,15 @@
## 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.
## Building the manual
```sh
svn co https://svn.php.net/repository/phpdoc/en/trunk/reference/ php_manual
bin/build_manual phpdoc_manual ~/.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!

21
vendor/psy/psysh/LICENSE vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2012-2014 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.

135
vendor/psy/psysh/README.md vendored Normal file
View File

@@ -0,0 +1,135 @@
# PsySH
[![Package version](http://img.shields.io/packagist/v/psy/psysh.svg?style=flat-square)](https://packagist.org/packages/psy/psysh)
[![Build status](http://img.shields.io/travis/bobthecow/psysh/master.svg?style=flat-square)](http://travis-ci.org/bobthecow/psysh)
[![Made out of awesome](http://img.shields.io/badge/made_out_of_awesome-✓-brightgreen.svg?style=flat-square)](http://psysh.org)
## About
PsySH is a runtime developer console, interactive debugger and [REPL](http://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` 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' => false,
// "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 ships with presenters for scalars, resources, arrays, and objects.
// But you're not limited to those presenters. You can enable additional
// presenters (like the included MongoCursorPresenter), or write your own!
'presenters' => array(
new \Psy\Presenter\MongoCursorPresenter,
),
// 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,
),
);
```
## 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/` (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)

285
vendor/psy/psysh/bin/build_manual vendored Normal file
View File

@@ -0,0 +1,285 @@
#!/usr/bin/env php
<?php
/*
* This file is part of PsySH
*
* (c) 2013 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));
}

29
vendor/psy/psysh/bin/compile vendored Normal file
View File

@@ -0,0 +1,29 @@
#!/usr/bin/env php
<?php
/*
* This file is part of PsySH
*
* (c) 2013 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`.');
}
use Psy\Compiler;
error_reporting(-1);
ini_set('display_errors', 1);
$compiler = new Compiler();
$compiler->compile();

123
vendor/psy/psysh/bin/psysh vendored Normal file
View File

@@ -0,0 +1,123 @@
#!/usr/bin/env php
<?php
/*
* This file is part of PsySH
*
* (c) 2013 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/* <<< */
if (is_file(__DIR__ . '/../vendor/autoload.php')) {
require(__DIR__ . '/../vendor/autoload.php');
} elseif (is_file(__DIR__ . '/../../../autoload.php')) {
require(__DIR__ . '/../../../autoload.php');
} else {
die(
'You must set up the Psy Shell dependencies, run the following commands:' . PHP_EOL .
'curl -s http://getcomposer.org/installer | php' . PHP_EOL .
'php composer.phar install' . PHP_EOL
);
}
/* >>> */
use Psy\Configuration;
use Psy\Shell;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
// 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 (Shell::isIncluded($trace)) {
unset($trace);
return;
}
// Clean up after ourselves.
unset($trace);
call_user_func(function() {
$usageException = null;
$input = new ArgvInput();
try {
$input->bind(new InputDefinition(array(
new InputOption('help', 'h', InputOption::VALUE_NONE),
new InputOption('config', 'c', InputOption::VALUE_REQUIRED),
new InputOption('version', 'v', InputOption::VALUE_NONE),
new InputArgument('include', InputArgument::IS_ARRAY),
)));
} catch (\RuntimeException $e) {
$usageException = $e;
}
$config = array();
// Handle --config
if ($configFile = $input->getOption('config')) {
$config['configFile'] = $configFile;
}
$shell = new Shell(new Configuration($config));
// Handle --help
if ($usageException !== null || $input->getOption('help')) {
if ($usageException !== null) {
echo $usageException->getMessage() . PHP_EOL . PHP_EOL;
}
$version = $shell->getVersion();
$name = basename(reset($_SERVER['argv']));
echo <<<EOL
$version
Usage:
$name [--version] [--help] [files...]
Options:
--help -h Display this help message.
--config -c Use an alternate PsySH config file location.
--version -v Display the PsySH version.
EOL;
exit($usageException === null ? 0 : 1);
}
// Handle --version
if ($input->getOption('version')) {
echo $shell->getVersion() . PHP_EOL;
exit(0);
}
// Pass additional arguments to Shell as 'includes'
$shell->setIncludes($input->getArgument('include'));
try {
// And go!
$shell->run();
} catch (Exception $e) {
echo $e->getMessage() . PHP_EOL;
// TODO: this triggers the "exited unexpectedly" logic in the
// ForkingLoop, so we can't exit(1) after starting the shell...
// fix this :)
// exit(1);
}
});

46
vendor/psy/psysh/composer.json vendored Normal file
View File

@@ -0,0 +1,46 @@
{
"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.0",
"symfony/console": "~2.3.10|~2.4.2|~2.5",
"nikic/php-parser": "~1.0",
"dnoegel/php-xdg-base-dir": "0.1",
"jakub-onderka/php-console-highlighter": "0.3.*"
},
"require-dev": {
"phpunit/phpunit": "~3.7|~4.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-0": {
"Psy\\": "src/"
}
},
"bin": ["bin/psysh"],
"extra": {
"branch-alias": {
"dev-develop": "0.4.x-dev"
}
}
}

11
vendor/psy/psysh/phpcs.xml vendored Normal file
View File

@@ -0,0 +1,11 @@
<?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>

12
vendor/psy/psysh/phpunit.xml.dist vendored Normal file
View File

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

45
vendor/psy/psysh/src/Psy/Autoloader.php vendored Normal file
View File

@@ -0,0 +1,45 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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;
}
}
}

175
vendor/psy/psysh/src/Psy/CodeCleaner.php vendored Normal file
View File

@@ -0,0 +1,175 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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\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\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\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)
{
$this->parser = $parser ?: new Parser(new Lexer());
$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 StaticConstructorPass(),
new ValidFunctionNamePass(),
new ValidClassNamePass(),
new ValidConstantPass(),
new MagicConstantsPass(),
);
}
/**
* 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->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);
}
}

View File

@@ -0,0 +1,69 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,39 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,52 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,83 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,22 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,82 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,38 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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\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) {
$nodes[count($nodes) - 1] = new ReturnStmt($last, array(
'startLine' => $last->getLine(),
'endLine' => $last->getLine(),
));
}
return $nodes;
}
}

View File

@@ -0,0 +1,45 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,36 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,64 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,42 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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 String;
/**
* 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 String('', $node->getAttributes());
}
}
}

View File

@@ -0,0 +1,71 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,79 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,87 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,111 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,314 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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 statements, and `new` expressions.
*
* @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 ClassStmt) {
$this->validateClassStatement($node);
} elseif ($node instanceof InterfaceStmt) {
$this->validateInterfaceStatement($node);
} elseif ($node instanceof TraitStmt) {
$this->validateTraitStatement($node);
} elseif ($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, give it a pass for now
if (!$stmt->class instanceof Expr) {
$this->ensureClassExists($this->getFullyQualifiedName($stmt->class), $stmt);
}
}
/**
* Validate a class constant fetch expression's class.
*
* @param ClassConstFetch $stmt
*/
protected function validateClassConstFetchExpression(ClassConstFetch $stmt)
{
// if class name is an expression, give it a pass for now
if (!$stmt->class instanceof Expr) {
$this->ensureClassExists($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 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);
// 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.
*
* @param string $name
*
* @return boolean
*/
protected function classExists($name)
{
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 boolean
*/
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 boolean
*/
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

@@ -0,0 +1,79 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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)
{
// 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)) {
$constName = sprintf('%s::%s', $className, $stmt->name);
if (!defined($constName)) {
$msg = sprintf('Class constant \'%s\' not found', $constName);
throw new FatalErrorException($msg, 0, 1, null, $stmt->getLine());
}
}
}
}
}

View File

@@ -0,0 +1,60 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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\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
{
/**
* 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 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;
} elseif ($node instanceof FuncCall) {
// if function name is an expression or a variable, give it a pass for now.
$name = $node->name;
if (!$name instanceof Expression && !$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

@@ -0,0 +1,77 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,49 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,282 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,98 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,96 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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\Presenter\Presenter;
use Psy\Presenter\PresenterManager;
use Psy\Presenter\PresenterManagerAware;
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 PresenterManagerAware
{
private $presenterManager;
/**
* PresenterManagerAware interface.
*
* @param PresenterManager $manager
*/
public function setPresenterManager(PresenterManager $manager)
{
$this->presenterManager = $manager;
}
/**
* {@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->presenterManager->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

@@ -0,0 +1,52 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,98 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,260 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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 boolean
*/
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

@@ -0,0 +1,278 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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\Presenter\PresenterManager;
use Psy\Presenter\PresenterManagerAware;
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 PresenterManagerAware
{
protected $presenterManager;
protected $enumerators;
/**
* PresenterManagerAware interface.
*
* @param PresenterManager $manager
*/
public function setPresenterManager(PresenterManager $manager)
{
$this->presenterManager = $manager;
}
/**
* {@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->presenterManager;
$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

@@ -0,0 +1,118 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,80 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,103 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,146 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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\Presenter\PresenterManager;
use Psy\Util\Mirror;
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 $presenterManager;
private $filter = false;
private $invertFilter = false;
private $pattern;
/**
* Enumerator constructor.
*
* @param PresenterManager $presenterManager
*/
public function __construct(PresenterManager $presenterManager)
{
$this->presenterManager = $presenterManager;
}
/**
* 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->presenterManager->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 boolean
*/
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

@@ -0,0 +1,112 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,92 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,80 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,138 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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 boolean $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

@@ -0,0 +1,161 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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 boolean $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

@@ -0,0 +1,85 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,129 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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\Presenter\PresenterManager;
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 PresenterManager $presenterManager
* @param Context $context
*/
public function __construct(PresenterManager $presenterManager, Context $context)
{
$this->context = $context;
parent::__construct($presenterManager);
}
/**
* {@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 boolean $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

@@ -0,0 +1,125 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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\Lexer;
use PhpParser\Parser;
use Psy\Presenter\PHPParserPresenter;
use Psy\Presenter\PresenterManager;
use Psy\Presenter\PresenterManagerAware;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Parse PHP code and show the abstract syntax tree.
*/
class ParseCommand extends Command implements PresenterManagerAware
{
private $presenterManager;
private $parser;
/**
* PresenterManagerAware interface.
*
* @param PresenterManager $manager
*/
public function setPresenterManager(PresenterManager $manager)
{
$this->presenterManager = new PresenterManager();
foreach ($manager as $presenter) {
$this->presenterManager->addPresenter($presenter);
}
$this->presenterManager->addPresenter(new PHPParserPresenter());
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('parse')
->setDefinition(array(
new InputArgument('code', InputArgument::REQUIRED, 'PHP code to parse.'),
new InputOption('depth', '', InputOption::VALUE_REQUIRED, 'Depth to parse', 10),
))
->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;
}
$depth = $input->getOption('depth');
$nodes = $this->parse($code);
$output->page($this->presenterManager->present($nodes, $depth));
}
/**
* Lex and parse a string of code into statements.
*
* @param string $code
*
* @return array Statements
*/
private function parse($code)
{
$parser = $this->getParser();
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.
*
* @return Parser
*/
private function getParser()
{
if (!isset($this->parser)) {
$this->parser = new Parser(new Lexer());
}
return $this->parser;
}
}

View File

@@ -0,0 +1,41 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,172 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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 boolean $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 boolean $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

@@ -0,0 +1,63 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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\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
{
/**
* {@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), ShellOutput::OUTPUT_RAW);
} catch (RuntimeException $e) {
$output->writeln(SignatureFormatter::format($reflector));
throw $e;
}
}
}

View File

@@ -0,0 +1,87 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,137 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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 | ShellOutput::OUTPUT_RAW);
}
/**
* 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(
' %s%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

@@ -0,0 +1,115 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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\PhpConsoleColor\ConsoleColor;
use JakubOnderka\PhpConsoleHighlighter\Highlighter;
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
{
public function __construct()
{
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');
$colors = new ConsoleColor();
$colors->addTheme('line_number', array('blue'));
$highlighter = new Highlighter($colors);
$contents = file_get_contents($info['file']);
$output->page($highlighter->getCodeSnippet($contents, $info['line'], $num, $num), ShellOutput::OUTPUT_RAW);
}
}

View File

@@ -0,0 +1,111 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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);
});
}
}

162
vendor/psy/psysh/src/Psy/Compiler.php vendored Normal file
View File

@@ -0,0 +1,162 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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__ . '/../../vendor/dnoegel/php-xdg-base-dir/src')
->in(__DIR__ . '/../../vendor/jakub-onderka/php-console-color')
->in(__DIR__ . '/../../vendor/jakub-onderka/php-console-highlighter')
->in(__DIR__ . '/../../vendor/nikic/php-parser/lib')
->in(__DIR__ . '/../../vendor/symfony/console')
->in(__DIR__ . '/../../vendor/symfony/yaml');
foreach ($finder as $file) {
$this->addFile($phar, $file);
}
$this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../vendor/autoload.php'));
$this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../vendor/composer/include_paths.php'));
$this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../vendor/composer/autoload_files.php'));
$this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../vendor/composer/autoload_psr4.php'));
$this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../vendor/composer/autoload_real.php'));
$this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../vendor/composer/autoload_namespaces.php'));
$this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../vendor/composer/autoload_classmap.php'));
$this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../vendor/composer/ClassLoader.php'));
// Stubs
$phar->setStub($this->getStub());
$phar->stopBuffering();
// $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../LICENSE'), false);
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;
}
/**
* Get a Phar stub for psysh.
*
* This is basically the psysh bin, with the autoload require statements swapped out.
*
* @return string
*/
private function getStub()
{
$autoload = <<<'EOS'
Phar::mapPhar('psysh.phar');
require 'phar://psysh.phar/vendor/autoload.php';
EOS;
$content = file_get_contents(__DIR__ . '/../../bin/psysh');
$content = preg_replace('{/\* <<<.*?>>> \*/}sm', $autoload, $content);
$content .= "__HALT_COMPILER();";
return $content;
}
}

1008
vendor/psy/psysh/src/Psy/Configuration.php vendored Normal file

File diff suppressed because it is too large Load Diff

136
vendor/psy/psysh/src/Psy/Context.php vendored Normal file
View File

@@ -0,0 +1,136 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,28 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,39 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,88 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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 = null;
}
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 ErrorException($errstr, 0, $errno, $errfile, $errline);
}
}

View File

@@ -0,0 +1,27 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,47 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,42 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,43 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,37 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,163 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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) {
try {
serialize($value);
$serializable[$key] = $value;
} catch (\Exception $e) {
// we'll just ignore this one...
}
}
return serialize($serializable);
}
}

View File

@@ -0,0 +1,168 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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\Shell;
/**
* The Psy Shell execution loop.
*/
class Loop
{
/**
* 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());
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 (\Exception $_e) {
restore_error_handler();
if (ob_get_level() > 0) {
ob_end_clean();
}
$__psysh__->writeException($_e);
}
// a bit of housekeeping
unset($__psysh_out__);
$__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 boolean
*/
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

@@ -0,0 +1,54 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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\PhpConsoleColor\ConsoleColor;
use JakubOnderka\PhpConsoleHighlighter\Highlighter;
use Psy\Exception\RuntimeException;
/**
* A pretty-printer for code.
*/
class CodeFormatter implements Formatter
{
/**
* Format the code represented by $reflector.
*
* @param \Reflector $reflector
*
* @return string formatted code
*/
public static function format(\Reflector $reflector)
{
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;
$colors = new ConsoleColor();
$colors->addTheme('line_number', array('blue'));
$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

@@ -0,0 +1,168 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,25 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,269 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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)) {
$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

@@ -0,0 +1,26 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,39 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,103 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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 Boolean $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

@@ -0,0 +1,201 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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\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 boolean $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 Boolean $newline Whether to add a newline or not
* @param integer $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 Boolean $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));
// 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

@@ -0,0 +1,174 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Presenter;
use Psy\Util\Json;
/**
* An array Presenter.
*/
class ArrayPresenter extends RecursivePresenter
{
const ARRAY_OBJECT_FMT = '<object>\\<<class>%s</class> <strong>#%s</strong>></object>';
/**
* ArrayPresenter can present arrays.
*
* @param mixed $value
*
* @return boolean
*/
public function canPresent($value)
{
return is_array($value) || $this->isArrayObject($value);
}
/**
* Determine whether something is an ArrayObject.
*
* This is a useful extension point for Presenter subclasses for Array-like
* objects which aren't necessarily subclasses of ArrayObject.
*
* @return boolean
*/
protected function isArrayObject($value)
{
return $value instanceof \ArrayObject;
}
/**
* Present a reference to the array.
*
* @param array $value
*
* @return string
*/
public function presentRef($value)
{
if ($this->isArrayObject($value)) {
return $this->presentArrayObjectRef($value);
} elseif (empty($value)) {
return '[]';
} else {
return sprintf('Array(<number>%d</number>)', count($value));
}
}
/**
* Present a reference to an ArrayObject.
*
* @param ArrayObject $value
*
* @return string
*/
protected function presentArrayObjectRef($value)
{
return sprintf(self::ARRAY_OBJECT_FMT, get_class($value), spl_object_hash($value));
}
/**
* Get an array of values from an ArrayObject.
*
* This is a useful extension point for Presenter subclasses for Array-like
* objects which aren't necessarily subclasses of ArrayObject.
*
* @return array
*/
protected function getArrayObjectValue($value)
{
return iterator_to_array($value->getIterator());
}
/**
* Present the array.
*
* @param object $value
* @param int $depth (default: null)
* @param int $options One of Presenter constants
*
* @return string
*/
protected function presentValue($value, $depth = null, $options = 0)
{
$prefix = '';
if ($this->isArrayObject($value)) {
$prefix = $this->presentArrayObjectRef($value) . ' ';
$value = $this->getArrayObjectValue($value);
}
if (empty($value) || $depth === 0) {
return $prefix . $this->presentRef($value);
}
$formatted = array();
foreach ($value as $key => $val) {
$formatted[$key] = $this->presentSubValue($val);
}
if ($this->shouldShowKeys($value)) {
$pad = max(array_map('strlen', array_map(array('Psy\Util\Json', 'encode'), array_keys($value))));
foreach ($formatted as $key => $val) {
$formatted[$key] = $this->formatKeyAndValue($key, $val, $pad);
}
} else {
$formatted = array_map(array($this, 'indentValue'), $formatted);
}
$template = sprintf('%s[%s%s%%s%s]', $prefix, PHP_EOL, self::INDENT, PHP_EOL);
$glue = sprintf(',%s%s', PHP_EOL, self::INDENT);
return sprintf($template, implode($glue, $formatted));
}
/**
* Helper method for determining whether to render array keys.
*
* Keys are only rendered for associative arrays or non-consecutive integer-
* based arrays.
*
* @param array $array
*
* @return boolean
*/
protected function shouldShowKeys(array $array)
{
$i = 0;
foreach (array_keys($array) as $k) {
if ($k !== $i++) {
return true;
}
}
return false;
}
/**
* Format a key => value pair.
*
* @param mixed $key
* @param string $value
* @param integer $pad Maximum key width, to align the hashrockets.
*
* @return string
*/
protected function formatKeyAndValue($key, $value, $pad = 0)
{
$type = is_string($value) ? 'string' : 'number';
$tpl = "<$type>%-${pad}s</$type> => %s";
return sprintf(
$tpl,
Json::encode($key),
$this->indentValue($value)
);
}
}

View File

@@ -0,0 +1,173 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Presenter;
/**
* A Closure Presenter.
*/
class ClosurePresenter implements Presenter, PresenterManagerAware
{
const FMT = '<keyword>function</keyword> (%s)%s { <comment>...</comment> }';
const USE_FMT = ' use (%s)';
protected $manager;
/**
* PresenterManagerAware interface.
*
* @param PresenterManager $manager
*/
public function setPresenterManager(PresenterManager $manager)
{
$this->manager = $manager;
}
/**
* ClosurePresenter can present closures.
*
* @param mixed $value
*
* @return boolean
*/
public function canPresent($value)
{
return $value instanceof \Closure;
}
/**
* Present a reference to the value.
*
* @param mixed $value
*
* @return string
*/
public function presentRef($value)
{
return sprintf(
self::FMT,
$this->formatParams($value),
$this->formatStaticVariables($value)
);
}
/**
* Present the Closure.
*
* @param \Closure $value
* @param int $depth (default:null)
* @param int $options One of Presenter constants
*
* @return string
*/
public function present($value, $depth = null, $options = 0)
{
return $this->presentRef($value);
}
/**
* Format a list of Closure parameters.
*
* @param \Closure $value
*
* @return string
*/
protected function formatParams(\Closure $value)
{
$r = new \ReflectionFunction($value);
$params = array_map(array($this, 'formatParam'), $r->getParameters());
return implode(', ', $params);
}
/**
* Format an individual Closure parameter.
*
* @param \ReflectionParameter $param
*
* @return string
*/
protected function formatParam(\ReflectionParameter $param)
{
$ret = $this->formatParamName($param->name);
if ($param->isOptional()) {
$ret .= ' = ';
if (self::isParamDefaultValueConstant($param)) {
$name = $param->getDefaultValueConstantName();
$ret .= '<const>' . $name . '</const>';
} elseif ($param->isDefaultValueAvailable()) {
$ret .= $this->manager->presentRef($param->getDefaultValue());
} else {
$ret .= '<urgent>?</urgent>';
}
}
return $ret;
}
/**
* Format static (used) variable names.
*
* @param \Closure $value
*
* @return string
*/
protected function formatStaticVariables(\Closure $value)
{
$r = new \ReflectionFunction($value);
$used = $r->getStaticVariables();
if (empty($used)) {
return '';
}
$names = array_map(array($this, 'formatParamName'), array_keys($used));
return sprintf(
self::USE_FMT,
implode(', ', $names)
);
}
/**
* Format a Closure parameter name.
*
* @param string $name
*
* @return string
*/
protected function formatParamName($name)
{
return sprintf('$<strong>%s</strong>', $name);
}
/**
* Check whether a parameter's default value is a constant.
*
* This is only supported in PHP >= 5.4.3, and currently unimplemented in
* HHVM.
*
* @param \ReflectionParameter $param
*
* @return boolean
*/
protected static function isParamDefaultValueConstant(\ReflectionParameter $param)
{
// HHVM doesn't currently support `isDefaultValueConstant`, skip for now
// see https://github.com/facebook/hhvm/issues/3812
if (defined('HHVM_VERSION')) {
return false;
}
return version_compare(PHP_VERSION, '5.4.3', '>=') && $param->isDefaultValueConstant();
}
}

View File

@@ -0,0 +1,52 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Presenter;
/**
* An Exception Presenter.
*/
class ExceptionPresenter extends ObjectPresenter
{
/**
* ExceptionPresenter can present Exceptions.
*
* @param mixed $value
*
* @return boolean
*/
public function canPresent($value)
{
return $value instanceof \Exception;
}
/**
* Get an array of exception object properties.
*
* @param object $value
* @param \ReflectionClass $class
* @param int $options One of Presenter constants
*
* @return array
*/
protected function getProperties($value, \ReflectionClass $class, $options = 0)
{
$props = array(
'<protected>message</protected>' => $value->getMessage(),
'<protected>code</protected>' => $value->getCode(),
'<protected>file</protected>' => $value->getFile(),
'<protected>line</protected>' => $value->getLine(),
'<private>previous</private>' => $value->getPrevious(),
);
return array_merge(array_filter($props), parent::getProperties($value, $class, $options));
}
}

View File

@@ -0,0 +1,119 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Presenter;
/**
* A Mongo Cursor Presenter.
*/
class MongoCursorPresenter extends ObjectPresenter
{
private static $boringFields = array('limit', 'batchSize', 'skip', 'flags');
private static $ignoreFields = array('server', 'host', 'port', 'connection_type_desc');
/**
* MongoCursorPresenter can present Mongo Cursors.
*
* @param mixed $value
*
* @return boolean
*/
public function canPresent($value)
{
return $value instanceof \MongoCursor;
}
/**
* Get an array of object properties.
*
* @param object $value
* @param \ReflectionClass $class
* @param int $propertyFilter One of \ReflectionProperty constants
*
* @return array
*/
protected function getProperties($value, \ReflectionClass $class, $propertyFilter)
{
$info = $value->info();
$this->normalizeQueryArray($info);
$this->normalizeFieldsArray($info);
$this->unsetBoringFields($info);
$this->unsetIgnoredFields($info);
if ($value->dead()) {
$info['dead'] = true;
}
return array_merge(
$info,
parent::getProperties($value, $class, $propertyFilter)
);
}
/**
* Normalize (empty) cursor query to always be an actual array.
*
* @param array $info Cursor info
*/
private function normalizeQueryArray(array &$info)
{
if (isset($info['query'])) {
if ($info['query'] === new \StdClass()) {
$info['query'] = array();
} elseif (is_array($info['query']) && isset($info['query']['$query'])) {
if ($info['query']['$query'] === new \StdClass()) {
$info['query']['$query'] = array();
}
}
}
}
/**
* Normalize (empty) cursor fields to always be an actual array.
*
* @param array $info Cursor info
*/
private function normalizeFieldsArray(array &$info)
{
if (isset($info['fields']) && $info['fields'] === new \StdClass()) {
$info['fields'] = array();
}
}
/**
* Unset boring fields from the Cursor info array.
*
* @param array $info Cursor info
*/
private function unsetBoringFields(array &$info)
{
foreach (self::$boringFields as $boring) {
if ($info[$boring] === 0) {
unset($info[$boring]);
}
}
}
/**
* Unset ignored fields from the Cursor info array.
*
* @param array $info Cursor info
*/
private function unsetIgnoredFields(array &$info)
{
foreach (self::$ignoreFields as $ignore) {
if (isset($info[$ignore])) {
unset($info[$ignore]);
}
}
}
}

View File

@@ -0,0 +1,143 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Presenter;
/**
* An object Presenter.
*/
class ObjectPresenter extends RecursivePresenter
{
const FMT = '<object>\\<<class>%s</class> <strong>#%s</strong>></object>';
/**
* ObjectPresenter can present objects.
*
* @param mixed $value
*
* @return boolean
*/
public function canPresent($value)
{
return is_object($value);
}
/**
* Present a reference to the object.
*
* @param object $value
*
* @return string
*/
public function presentRef($value)
{
return sprintf(self::FMT, get_class($value), spl_object_hash($value));
}
/**
* Present the object.
*
* @param object $value
* @param int $depth (default: null)
* @param int $options One of Presenter constants
*
* @return string
*/
protected function presentValue($value, $depth = null, $options = 0)
{
if ($depth === 0) {
return $this->presentRef($value);
}
$class = new \ReflectionObject($value);
$propertyFilter = \ReflectionProperty::IS_PUBLIC;
if ($options & Presenter::VERBOSE) {
$propertyFilter |= \ReflectionProperty::IS_PRIVATE | \ReflectionProperty::IS_PROTECTED;
}
$props = $this->getProperties($value, $class, $propertyFilter);
return sprintf('%s %s', $this->presentRef($value), $this->formatProperties($props));
}
/**
* Format object properties.
*
* @param array $props
*
* @return string
*/
protected function formatProperties($props)
{
if (empty($props)) {
return '{}';
}
$formatted = array();
foreach ($props as $name => $value) {
$formatted[] = sprintf('%s: %s', $name, $this->indentValue($this->presentSubValue($value)));
}
$template = sprintf('{%s%s%%s%s}', PHP_EOL, self::INDENT, PHP_EOL);
$glue = sprintf(',%s%s', PHP_EOL, self::INDENT);
return sprintf($template, implode($glue, $formatted));
}
/**
* Get an array of object properties.
*
* @param object $value
* @param \ReflectionClass $class
* @param int $propertyFilter One of \ReflectionProperty constants
*
* @return array
*/
protected function getProperties($value, \ReflectionClass $class, $propertyFilter)
{
$deprecated = false;
set_error_handler(function ($errno, $errstr) use (&$deprecated) {
if (in_array($errno, array(E_DEPRECATED, E_USER_DEPRECATED))) {
$deprecated = true;
} else {
// not a deprecation error, let someone else handle this
return false;
}
});
$props = array();
foreach ($class->getProperties($propertyFilter) as $prop) {
$deprecated = false;
$prop->setAccessible(true);
$val = $prop->getValue($value);
if (!$deprecated) {
$props[$this->propertyKey($prop)] = $val;
}
}
restore_error_handler();
return $props;
}
protected function propertyKey(\ReflectionProperty $prop)
{
$key = $prop->getName();
if ($prop->isProtected()) {
return sprintf('<protected>%s</protected>', $key);
} elseif ($prop->isPrivate()) {
return sprintf('<private>%s</private>', $key);
}
return $key;
}
}

View File

@@ -0,0 +1,69 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Presenter;
use PhpParser\Node;
/**
* A PhpParser Presenter.
*/
class PHPParserPresenter extends ObjectPresenter
{
const FMT = '<object>\\<<class>%s</class>></object>';
/**
* PHPParserPresenter can present parse trees.
*
* @param mixed $value
*
* @return boolean
*/
public function canPresent($value)
{
return $value instanceof Node;
}
/**
* Present a reference to the object.
*
* @param object $value
*
* @return string
*/
public function presentRef($value)
{
return sprintf(self::FMT, get_class($value));
}
/**
* Get an array of object properties.
*
* @param object $value
* @param \ReflectionClass $class
* @param int $propertyFilter One of \ReflectionProperty constants
*
* @return array
*/
protected function getProperties($value, \ReflectionClass $class, $propertyFilter)
{
$props = array();
$props['type'] = $value->getType();
$props['attributes'] = $value->getAttributes();
foreach ($value->getSubNodeNames() as $name) {
$props[$name] = $value->$name;
}
return $props;
}
}

View File

@@ -0,0 +1,52 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Presenter;
/**
* Presenter classes are able to pretty-print values for display. Think
* `var_dump`, but with sane and beautiful output.
*/
interface Presenter
{
const VERBOSE = 1;
/**
* Check whether this Presenter can present $value.
*
* @param mixed $value
*
* @return boolean
*/
public function canPresent($value);
/**
* Present a reference to the value.
*
* @param mixed $value
*
* @return string
*/
public function presentRef($value);
/**
* Present a full representation of the value.
*
* Optionally pass a $depth argument to limit the depth of recursive values.
*
* @param mixed $value
* @param int $depth (default: null)
* @param int $options One of Presenter constants
*
* @return string
*/
public function present($value, $depth = null, $options = 0);
}

View File

@@ -0,0 +1,177 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Presenter;
/**
* A Presenter manager service.
*
* Presenters are registered with the PresenterManager, which then delegates
* value presentation to the most recently registered Presenter capable of
* presenting that value.
*/
class PresenterManager implements Presenter, \IteratorAggregate
{
protected $presenters = array();
/**
* PresenterManager constructor.
*
* Initializes default Presenters.
*/
public function __construct()
{
$this->addPresenters(array(
new ObjectPresenter(), // lowest precedence
new ArrayPresenter(),
new ClosurePresenter(),
new ExceptionPresenter(),
new ResourcePresenter(),
new ScalarPresenter(),
));
}
/**
* Register Presenters.
*
* Presenters should be passed in an array from lowest to highest precedence.
*
* @see self::addPresenter
*
* @param Presenter[] $presenters
*/
public function addPresenters(array $presenters)
{
foreach ($presenters as $presenter) {
$this->addPresenter($presenter);
}
}
/**
* Register a Presenter.
*
* If multiple Presenters are able to present a value, the most recently
* registered Presenter takes precedence.
*
* If $presenter is already registered, it will be re-registered as the
* highest precedence Presenter.
*
* @param Presenter $presenter
*/
public function addPresenter(Presenter $presenter)
{
$this->removePresenter($presenter);
if ($presenter instanceof PresenterManagerAware) {
$presenter->setPresenterManager($this);
}
array_unshift($this->presenters, $presenter);
}
/**
* Unregister a Presenter.
*
* @param Presenter $presenter
*/
public function removePresenter(Presenter $presenter)
{
foreach ($this->presenters as $i => $p) {
if ($p === $presenter) {
unset($this->presenters[$i]);
}
}
}
/**
* Check whether a Presenter is registered for $value.
*
* @param mixed $value
*
* @return boolean
*/
public function canPresent($value)
{
return $this->getPresenter($value) !== null;
}
/**
* Present a reference to the value.
*
* @param mixed $value
*
* @throws \InvalidArgumentException If no Presenter is registered for $value
*
* @return string
*/
public function presentRef($value)
{
if ($presenter = $this->getPresenter($value)) {
return $presenter->presentRef($value);
}
throw new \InvalidArgumentException(sprintf('Unable to present %s', $value));
}
/**
* Present a full representation of the value.
*
* If $depth is 0, the value will be presented as a ref instead.
*
* @param mixed $value
* @param int $depth (default: null)
* @param int $options One of Presenter constants
*
* @throws \InvalidArgumentException If no Presenter is registered for $value
*
* @return string
*/
public function present($value, $depth = null, $options = 0)
{
if ($presenter = $this->getPresenter($value)) {
if ($depth === 0) {
return $presenter->presentRef($value);
}
return $presenter->present($value, $depth, $options);
}
throw new \InvalidArgumentException(sprintf('Unable to present %s', $value));
}
/**
* IteratorAggregate interface.
*
* @return \ArrayIterator
*/
public function getIterator()
{
return new \ArrayIterator(array_reverse($this->presenters));
}
/**
* Find the highest precedence Presenter available for $value.
*
* Returns null if none is present.
*
* @param mixed $value
*
* @return null|Presenter
*/
protected function getPresenter($value)
{
foreach ($this->presenters as $presenter) {
if ($presenter->canPresent($value)) {
return $presenter;
}
}
}
}

View File

@@ -0,0 +1,26 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Presenter;
/**
* PresenterManager injects itself as a dependency to all Presenters which
* implement PresenterManagerAware.
*/
interface PresenterManagerAware
{
/**
* Set a reference to the PresenterManager.
*
* @param PresenterManager $manager
*/
public function setPresenterManager(PresenterManager $manager);
}

View File

@@ -0,0 +1,113 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Presenter;
/**
* An abstract Presenter capable of recursively presenting sub-values.
*/
abstract class RecursivePresenter implements Presenter, PresenterManagerAware
{
const MAX_DEPTH = 5;
const INDENT = ' ';
protected $manager;
protected $depth;
/**
* PresenterManagerAware interface.
*
* @param PresenterManager $manager
*/
public function setPresenterManager(PresenterManager $manager)
{
$this->manager = $manager;
}
/**
* Present the recursive value.
*
* Subclasses should implement `presentValue` rather than overriding this
* method.
*
* @see self::presentValue()
*
* @param mixed $value
* @param int $depth (default: null)
* @param int $options One of Presenter constants
*
* @return string
*/
public function present($value, $depth = null, $options = 0)
{
$this->setDepth($depth);
return $this->presentValue($value, $depth, $options);
}
/**
* RecursivePresenter subclasses implement a `presentValue` method for
* actually doing the presentation.
*
* @param mixed $value
*
* @return string
*/
abstract protected function presentValue($value);
/**
* Keep track of the remaining recursion depth.
*
* If $depth is null, set it to `self::MAX_DEPTH`.
*
* @param int $depth (default: null)
*/
protected function setDepth($depth = null)
{
$this->depth = $depth === null ? self::MAX_DEPTH : $depth;
}
/**
* Present a sub-value.
*
* If the current recursion depth is greater than self::MAX_DEPTH, it will
* present a reference, otherwise it will present the full representation
* of the sub-value.
*
* @see PresenterManager::present()
* @see PresenterManager::presentRef()
*
* @param mixed $value
* @param int $options One of Presenter constants
*
* @return string
*/
protected function presentSubValue($value, $options = 0)
{
$depth = $this->depth;
$formatted = $this->manager->present($value, $depth - 1, $options);
$this->setDepth($depth);
return $formatted;
}
/**
* Indent every line of a value.
*
* @param string $value
*
* @return string
*/
protected function indentValue($value)
{
return str_replace(PHP_EOL, PHP_EOL . self::INDENT, $value);
}
}

View File

@@ -0,0 +1,115 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Presenter;
/**
* A resource Presenter.
*/
class ResourcePresenter extends RecursivePresenter
{
const FMT = '<resource>\\<%s <strong>resource #%s</strong>></resource>';
/**
* Resource presenter can present resources.
*
* @param mixed $value
*
* @return boolean
*/
public function canPresent($value)
{
return is_resource($value);
}
/**
* Present a reference to the value.
*
* @param mixed $value
*
* @return string
*/
public function presentRef($value)
{
$type = get_resource_type($value);
if ($type === 'stream') {
$meta = stream_get_meta_data($value);
$type = sprintf('%s stream', $meta['stream_type']);
}
$id = str_replace('Resource id #', '', (string) $value);
return sprintf(self::FMT, $type, $id);
}
/**
* Present the resource.
*
* @param resource $value
* @param int $depth (default: null)
* @param int $options One of Presenter constants
*
* @return string
*/
public function presentValue($value, $depth = null, $options = 0)
{
if ($depth === 0 || !($options & Presenter::VERBOSE)) {
return $this->presentRef($value);
}
return sprintf('%s %s', $this->presentRef($value), $this->formatMetadata($value));
}
/**
* Format resource metadata.
*
* @param resource $value
*
* @return string
*/
protected function formatMetadata($value)
{
$props = array();
switch (get_resource_type($value)) {
case 'stream':
$props = stream_get_meta_data($value);
break;
case 'curl':
$props = curl_getinfo($value);
break;
case 'xml':
$props = array(
'current_byte_index' => xml_get_current_byte_index($value),
'current_column_number' => xml_get_current_column_number($value),
'current_line_number' => xml_get_current_line_number($value),
'error_code' => xml_get_error_code($value),
);
break;
}
if (empty($props)) {
return '{}';
}
$formatted = array();
foreach ($props as $name => $value) {
$formatted[] = sprintf('%s: %s', $name, $this->indentValue($this->presentSubValue($value)));
}
$template = sprintf('{%s%s%%s%s}', PHP_EOL, self::INDENT, PHP_EOL);
$glue = sprintf(',%s%s', PHP_EOL, self::INDENT);
return sprintf($template, implode($glue, $formatted));
}
}

View File

@@ -0,0 +1,109 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Presenter;
use Psy\Util\Json;
use Symfony\Component\Console\Formatter\OutputFormatter;
/**
* A scalar (and null) Presenter.
*/
class ScalarPresenter implements Presenter
{
/**
* Scalar presenter can present scalars and null.
*
* Technically this would make it a ScalarOrNullPresenter, but that's a much
* lamer name :)
*
* @param mixed $value
*
* @return boolean
*/
public function canPresent($value)
{
return is_scalar($value) || is_null($value);
}
/**
* Present a reference to the value.
*
* @param mixed $value
*
* @return string
*/
public function presentRef($value)
{
return $this->present($value);
}
/**
* Present the scalar value.
*
* @param mixed $value
* @param int $depth (default: null)
* @param int $options One of Presenter constants
*
* @return string
*/
public function present($value, $depth = null, $options = 0)
{
$formatted = $this->format($value);
if ($typeStyle = $this->getTypeStyle($value)) {
return sprintf('<%s>%s</%s>', $typeStyle, $formatted, $typeStyle);
} else {
return $formatted;
}
}
private function format($value)
{
// Handle floats.
if (is_float($value)) {
// Some are unencodable...
if (is_nan($value)) {
return 'NAN';
} elseif (is_infinite($value)) {
return $value === INF ? 'INF' : '-INF';
}
// ... others just encode as ints when there's no decimal
$float = Json::encode($value);
if (strpos($float, '.') === false) {
$float .= '.0';
}
return $float;
}
return OutputFormatter::escape(Json::encode($value));
}
/**
* Get the output style for a value of a given type.
*
* @param mixed $value
*
* @return string
*/
private 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';
}
}
}

View File

@@ -0,0 +1,155 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,83 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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\String;
/**
* 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 boolean
*/
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 !== '') ? String::unvis($line) : null;
}
}

View File

@@ -0,0 +1,76 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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 boolean
*/
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

@@ -0,0 +1,146 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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

@@ -0,0 +1,139 @@
<?php
/*
* This file is part of Psy Shell
*
* (c) 2012-2014 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();
}
}

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