[DEVTOOL] Add a robust and modern REPL
This commit is contained in:
committed by
Diogo Cordeiro
parent
2850e56f30
commit
c1c2a9f1a1
288
scripts/console.php
Executable file → Normal file
288
scripts/console.php
Executable file → Normal file
@@ -1,169 +1,155 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/*
|
||||
* StatusNet - the distributed open-source microblogging tool
|
||||
* Copyright (C) 2009, StatusNet, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// Abort if called from a web server
|
||||
|
||||
define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
|
||||
|
||||
$helptext = <<<ENDOFHELP
|
||||
console.php - provide an interactive PHP interpreter for testing
|
||||
|
||||
ENDOFHELP;
|
||||
|
||||
require_once INSTALLDIR.'/scripts/commandline.inc';
|
||||
|
||||
// Assume we're on a terminal if on Windows, otherwise posix_isatty tells us.
|
||||
define('CONSOLE_INTERACTIVE', !function_exists('posix_isatty') || posix_isatty(0));
|
||||
define('CONSOLE_READLINE', CONSOLE_INTERACTIVE && function_exists('readline'));
|
||||
|
||||
if (CONSOLE_READLINE && CONSOLE_INTERACTIVE) {
|
||||
define('CONSOLE_HISTORY', getenv("HOME") . "/.statusnet_console_history");
|
||||
if (file_exists(CONSOLE_HISTORY)) {
|
||||
readline_read_history(CONSOLE_HISTORY);
|
||||
}
|
||||
}
|
||||
|
||||
function read_input_line($prompt)
|
||||
{
|
||||
if (CONSOLE_INTERACTIVE) {
|
||||
if (CONSOLE_READLINE) {
|
||||
$line = readline($prompt);
|
||||
if (trim($line) != '') {
|
||||
readline_add_history($line);
|
||||
if (defined('CONSOLE_HISTORY')) {
|
||||
// Save often; it's easy to hit fatal errors.
|
||||
readline_write_history(CONSOLE_HISTORY);
|
||||
}
|
||||
}
|
||||
return $line;
|
||||
} else {
|
||||
return readline_emulation($prompt);
|
||||
}
|
||||
} else {
|
||||
return fgets(STDIN);
|
||||
}
|
||||
}
|
||||
// This file is part of GNU social - https://www.gnu.org/software/social
|
||||
//
|
||||
// GNU social is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// GNU social is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* On Unix-like systems where PHP readline extension isn't present,
|
||||
* -cough- Mac OS X -cough- we can shell out to bash to do it for us.
|
||||
* This lets us at least handle things like arrow keys, but we don't
|
||||
* get any entry history. :(
|
||||
* Description of this file.
|
||||
*
|
||||
* Shamelessly ripped from when I wrote the same code for MediaWiki. :)
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
*
|
||||
* @param string $prompt
|
||||
* @return mixed string on success, false on fail or EOF
|
||||
* @package samples
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @copyright 2019 Free Software Foundation, Inc http://www.fsf.org
|
||||
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
|
||||
*/
|
||||
function readline_emulation($prompt)
|
||||
{
|
||||
if(CONSOLE_INTERACTIVE && file_exists(trim(shell_exec('which bash')))) {
|
||||
$encPrompt = escapeshellarg($prompt);
|
||||
$command = "read -er -p $encPrompt && echo \"\$REPLY\"";
|
||||
$encCommand = escapeshellarg($command);
|
||||
$metaCommand = "bash -c $encCommand";
|
||||
|
||||
// passthru passes our STDIN and TTY to the child...
|
||||
// We can pull the returned string via output buffering.
|
||||
ob_start();
|
||||
$retval = false;
|
||||
passthru($metaCommand, $retval);
|
||||
$line = ob_get_contents();
|
||||
ob_end_clean();
|
||||
define('INSTALLDIR', dirname(__FILE__));
|
||||
define('GNUSOCIAL', true);
|
||||
define('STATUSNET', true);
|
||||
|
||||
if ($retval == 0) {
|
||||
return $line;
|
||||
} elseif ($retval == 127) {
|
||||
// Couldn't execute bash even though we thought we saw it.
|
||||
// Shell probably spit out an error message, sorry :(
|
||||
// Fall through to fgets()...
|
||||
} else {
|
||||
// EOF/ctrl+D
|
||||
return false;
|
||||
require_once INSTALLDIR . '/lib/common.php';
|
||||
|
||||
// Try to find an autoloader for a local psysh version.
|
||||
// We'll wrap this whole mess in a Closure so it doesn't leak any globals.
|
||||
call_user_func(function () {
|
||||
$cwd = null;
|
||||
|
||||
// Find the cwd arg (if present)
|
||||
$argv = isset($_SERVER['argv']) ? $_SERVER['argv'] : array();
|
||||
foreach ($argv as $i => $arg) {
|
||||
if ($arg === '--cwd') {
|
||||
if ($i >= count($argv) - 1) {
|
||||
echo 'Missing --cwd argument.' . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
$cwd = $argv[$i + 1];
|
||||
break;
|
||||
}
|
||||
|
||||
if (preg_match('/^--cwd=/', $arg)) {
|
||||
$cwd = substr($arg, 6);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback... we'll have no editing controls, EWWW
|
||||
if (feof(STDIN)) {
|
||||
return false;
|
||||
// Or fall back to the actual cwd
|
||||
if (!isset($cwd)) {
|
||||
$cwd = getcwd();
|
||||
}
|
||||
if (CONSOLE_INTERACTIVE) {
|
||||
print $prompt;
|
||||
|
||||
$cwd = str_replace('\\', '/', $cwd);
|
||||
|
||||
$chunks = explode('/', $cwd);
|
||||
while (!empty($chunks)) {
|
||||
$path = implode('/', $chunks);
|
||||
|
||||
// Find composer.json
|
||||
if (is_file($path . '/composer.json')) {
|
||||
if ($cfg = json_decode(file_get_contents($path . '/composer.json'), true)) {
|
||||
if (isset($cfg['name']) && $cfg['name'] === 'psy/psysh') {
|
||||
// We're inside the psysh project. Let's use the local
|
||||
// Composer autoload.
|
||||
if (is_file($path . '/vendor/autoload.php')) {
|
||||
require $path . '/vendor/autoload.php';
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Or a composer.lock
|
||||
if (is_file($path . '/composer.lock')) {
|
||||
if ($cfg = json_decode(file_get_contents($path . '/composer.lock'), true)) {
|
||||
foreach (array_merge($cfg['packages'], $cfg['packages-dev']) as $pkg) {
|
||||
if (isset($pkg['name']) && $pkg['name'] === 'psy/psysh') {
|
||||
// We're inside a project which requires psysh. We'll
|
||||
// use the local Composer autoload.
|
||||
if (is_file($path . '/vendor/autoload.php')) {
|
||||
require $path . '/vendor/autoload.php';
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
array_pop($chunks);
|
||||
}
|
||||
return fgets(STDIN);
|
||||
});
|
||||
|
||||
// We didn't find an autoloader for a local version, so use the autoloader that
|
||||
// came with this script.
|
||||
if (!class_exists('Psy\Shell')) {
|
||||
/* <<< */
|
||||
if (is_file(__DIR__ . '/../vendor/autoload.php')) {
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
} elseif (is_file(__DIR__ . '/../../../autoload.php')) {
|
||||
require __DIR__ . '/../../../autoload.php';
|
||||
} else {
|
||||
echo 'PsySH dependencies not found, be sure to run `composer install`.' . PHP_EOL;
|
||||
echo 'See https://getcomposer.org to get Composer.' . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
/* >>> */
|
||||
}
|
||||
|
||||
function console_help()
|
||||
{
|
||||
print "Welcome to GNU social's interactive PHP console!\n";
|
||||
print "Type some PHP code and it'll execute...\n";
|
||||
print "\n";
|
||||
print "Hint: return a value of any type to output it via var_export():\n";
|
||||
print " \$profile = new Profile();\n";
|
||||
print " \$profile->find();\n";
|
||||
print " \$profile->fetch();\n";
|
||||
print " return \$profile;\n";
|
||||
print "\n";
|
||||
print "Note that PHP is cranky and you can easily kill your session by mistyping.\n";
|
||||
print "\n";
|
||||
print "Type ctrl+D or enter 'exit' to exit.\n";
|
||||
}
|
||||
|
||||
if (CONSOLE_INTERACTIVE) {
|
||||
print "GNU social interactive PHP console... type ctrl+D or enter 'exit' to exit.\n";
|
||||
$prompt = common_slugify(common_config('site', 'name')) . '> ';
|
||||
// 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 {
|
||||
$prompt = '';
|
||||
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
|
||||
}
|
||||
while (!feof(STDIN)) {
|
||||
$line = read_input_line($prompt);
|
||||
if ($line === false) {
|
||||
if (CONSOLE_INTERACTIVE) {
|
||||
print "\n";
|
||||
}
|
||||
break;
|
||||
} elseif ($line !== '') {
|
||||
try {
|
||||
if (trim($line) == 'exit') {
|
||||
break;
|
||||
} elseif (trim($line) == 'help') {
|
||||
console_help();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Let's do this!
|
||||
$result = eval($line);
|
||||
if ($result === false) {
|
||||
// parse error
|
||||
} elseif ($result === null) {
|
||||
// no return
|
||||
} else {
|
||||
// return value from eval'd code
|
||||
var_export($result);
|
||||
}
|
||||
} catch(Exception $e) {
|
||||
print get_class($e) . ": " . $e->getMessage() . "\n";
|
||||
}
|
||||
}
|
||||
if (CONSOLE_INTERACTIVE) {
|
||||
print "\n";
|
||||
}
|
||||
|
||||
if (Psy\Shell::isIncluded($trace)) {
|
||||
unset($trace);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean up after ourselves.
|
||||
unset($trace);
|
||||
|
||||
// If the local version is too old, we can't do this
|
||||
if (!function_exists('Psy\bin')) {
|
||||
$argv = $_SERVER['argv'];
|
||||
$first = array_shift($argv);
|
||||
if (preg_match('/php(\.exe)?$/', $first)) {
|
||||
array_shift($argv);
|
||||
}
|
||||
array_unshift($argv, 'vendor/bin/psysh');
|
||||
|
||||
echo 'A local PsySH dependency was found, but it cannot be loaded. Please update to' . PHP_EOL;
|
||||
echo 'the latest version, or run the local copy directly, e.g.:' . PHP_EOL;
|
||||
echo PHP_EOL;
|
||||
echo ' ' . implode(' ', $argv) . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// And go!
|
||||
call_user_func(Psy\bin());
|
||||
|
Reference in New Issue
Block a user