Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 200 | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
/*
4
 * This file is part of Psy Shell.
5
 *
200 lars 6
 * (c) 2012-2023 Justin Hileman
148 lars 7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
 
12
namespace Psy;
13
 
14
use Psy\ExecutionLoop\ProcessForker;
15
use Psy\VersionUpdater\GitHubChecker;
16
use Psy\VersionUpdater\Installer;
17
use Psy\VersionUpdater\SelfUpdate;
18
use Symfony\Component\Console\Input\ArgvInput;
19
use Symfony\Component\Console\Input\InputArgument;
20
use Symfony\Component\Console\Input\InputDefinition;
21
use Symfony\Component\Console\Input\InputOption;
22
 
23
if (!\function_exists('Psy\\sh')) {
24
    /**
25
     * Command to return the eval-able code to startup PsySH.
26
     *
27
     *     eval(\Psy\sh());
28
     */
29
    function sh(): string
30
    {
31
        if (\version_compare(\PHP_VERSION, '8.0', '<')) {
32
            return '\extract(\Psy\debug(\get_defined_vars(), isset($this) ? $this : @\get_called_class()));';
33
        }
34
 
35
        return <<<'EOS'
36
if (isset($this)) {
37
    \extract(\Psy\debug(\get_defined_vars(), $this));
38
} else {
39
    try {
40
        static::class;
41
        \extract(\Psy\debug(\get_defined_vars(), static::class));
42
    } catch (\Error $e) {
43
        \extract(\Psy\debug(\get_defined_vars()));
44
    }
45
}
46
EOS;
47
    }
48
}
49
 
50
if (!\function_exists('Psy\\debug')) {
51
    /**
52
     * Invoke a Psy Shell from the current context.
53
     *
54
     * For example:
55
     *
56
     *     foreach ($items as $item) {
57
     *         \Psy\debug(get_defined_vars());
58
     *     }
59
     *
60
     * If you would like your shell interaction to affect the state of the
61
     * current context, you can extract() the values returned from this call:
62
     *
63
     *     foreach ($items as $item) {
64
     *         extract(\Psy\debug(get_defined_vars()));
65
     *         var_dump($item); // will be whatever you set $item to in Psy Shell
66
     *     }
67
     *
68
     * Optionally, supply an object as the `$bindTo` parameter. This determines
69
     * the value `$this` will have in the shell, and sets up class scope so that
70
     * private and protected members are accessible:
71
     *
72
     *     class Foo {
73
     *         function bar() {
74
     *             \Psy\debug(get_defined_vars(), $this);
75
     *         }
76
     *     }
77
     *
78
     * For the static equivalent, pass a class name as the `$bindTo` parameter.
79
     * This makes `self` work in the shell, and sets up static scope so that
80
     * private and protected static members are accessible:
81
     *
82
     *     class Foo {
83
     *         static function bar() {
84
     *             \Psy\debug(get_defined_vars(), get_called_class());
85
     *         }
86
     *     }
87
     *
88
     * @param array         $vars   Scope variables from the calling context (default: [])
89
     * @param object|string $bindTo Bound object ($this) or class (self) value for the shell
90
     *
91
     * @return array Scope variables from the debugger session
92
     */
93
    function debug(array $vars = [], $bindTo = null): array
94
    {
95
        echo \PHP_EOL;
96
 
97
        $sh = new Shell();
98
        $sh->setScopeVariables($vars);
99
 
100
        // Show a couple of lines of call context for the debug session.
101
        //
102
        // @todo come up with a better way of doing this which doesn't involve injecting input :-P
103
        if ($sh->has('whereami')) {
104
            $sh->addInput('whereami -n2', true);
105
        }
106
 
107
        if (\is_string($bindTo)) {
108
            $sh->setBoundClass($bindTo);
109
        } elseif ($bindTo !== null) {
110
            $sh->setBoundObject($bindTo);
111
        }
112
 
113
        $sh->run();
114
 
115
        return $sh->getScopeVariables(false);
116
    }
117
}
118
 
119
if (!\function_exists('Psy\\info')) {
120
    /**
121
     * Get a bunch of debugging info about the current PsySH environment and
122
     * configuration.
123
     *
124
     * If a Configuration param is passed, that configuration is stored and
125
     * used for the current shell session, and no debugging info is returned.
126
     *
127
     * @param Configuration|null $config
128
     *
129
     * @return array|null
130
     */
131
    function info(Configuration $config = null)
132
    {
133
        static $lastConfig;
134
        if ($config !== null) {
135
            $lastConfig = $config;
136
 
137
            return;
138
        }
139
 
140
        $prettyPath = function ($path) {
141
            return $path;
142
        };
143
 
144
        $homeDir = (new ConfigPaths())->homeDir();
145
        if ($homeDir && $homeDir = \rtrim($homeDir, '/')) {
146
            $homePattern = '#^'.\preg_quote($homeDir, '#').'/#';
147
            $prettyPath = function ($path) use ($homePattern) {
148
                if (\is_string($path)) {
149
                    return \preg_replace($homePattern, '~/', $path);
150
                } else {
151
                    return $path;
152
                }
153
            };
154
        }
155
 
156
        $config = $lastConfig ?: new Configuration();
157
        $configEnv = (isset($_SERVER['PSYSH_CONFIG']) && $_SERVER['PSYSH_CONFIG']) ? $_SERVER['PSYSH_CONFIG'] : false;
158
 
159
        $shellInfo = [
160
            'PsySH version' => Shell::VERSION,
161
        ];
162
 
163
        $core = [
164
            'PHP version'         => \PHP_VERSION,
165
            'OS'                  => \PHP_OS,
166
            'default includes'    => $config->getDefaultIncludes(),
167
            'require semicolons'  => $config->requireSemicolons(),
168
            'error logging level' => $config->errorLoggingLevel(),
169
            'config file'         => [
170
                'default config file' => $prettyPath($config->getConfigFile()),
171
                'local config file'   => $prettyPath($config->getLocalConfigFile()),
172
                'PSYSH_CONFIG env'    => $prettyPath($configEnv),
173
            ],
174
            // 'config dir'  => $config->getConfigDir(),
175
            // 'data dir'    => $config->getDataDir(),
176
            // 'runtime dir' => $config->getRuntimeDir(),
177
        ];
178
 
179
        // Use an explicit, fresh update check here, rather than relying on whatever is in $config.
180
        $checker = new GitHubChecker();
181
        $updateAvailable = null;
182
        $latest = null;
183
        try {
184
            $updateAvailable = !$checker->isLatest();
185
            $latest = $checker->getLatest();
186
        } catch (\Throwable $e) {
187
        }
188
 
189
        $updates = [
190
            'update available'       => $updateAvailable,
191
            'latest release version' => $latest,
192
            'update check interval'  => $config->getUpdateCheck(),
193
            'update cache file'      => $prettyPath($config->getUpdateCheckCacheFile()),
194
        ];
195
 
196
        $input = [
197
            'interactive mode'  => $config->interactiveMode(),
198
            'input interactive' => $config->getInputInteractive(),
199
            'yolo'              => $config->yolo(),
200
        ];
201
 
202
        if ($config->hasReadline()) {
203
            $info = \readline_info();
204
 
205
            $readline = [
206
                'readline available' => true,
207
                'readline enabled'   => $config->useReadline(),
208
                'readline service'   => \get_class($config->getReadline()),
209
            ];
210
 
211
            if (isset($info['library_version'])) {
212
                $readline['readline library'] = $info['library_version'];
213
            }
214
 
215
            if (isset($info['readline_name']) && $info['readline_name'] !== '') {
216
                $readline['readline name'] = $info['readline_name'];
217
            }
218
        } else {
219
            $readline = [
220
                'readline available' => false,
221
            ];
222
        }
223
 
224
        $output = [
225
            'color mode'       => $config->colorMode(),
226
            'output decorated' => $config->getOutputDecorated(),
227
            'output verbosity' => $config->verbosity(),
228
            'output pager'     => $config->getPager(),
229
        ];
230
 
991 lars 231
        $theme = $config->theme();
232
        // TODO: show styles (but only if they're different than default?)
233
        $output['theme'] = [
234
            'compact'      => $theme->compact(),
235
            'prompt'       => $theme->prompt(),
236
            'bufferPrompt' => $theme->bufferPrompt(),
237
            'replayPrompt' => $theme->replayPrompt(),
238
            'returnValue'  => $theme->returnValue(),
239
        ];
240
 
148 lars 241
        $pcntl = [
242
            'pcntl available' => ProcessForker::isPcntlSupported(),
243
            'posix available' => ProcessForker::isPosixSupported(),
244
        ];
245
 
246
        if ($disabledPcntl = ProcessForker::disabledPcntlFunctions()) {
247
            $pcntl['disabled pcntl functions'] = $disabledPcntl;
248
        }
249
 
250
        if ($disabledPosix = ProcessForker::disabledPosixFunctions()) {
251
            $pcntl['disabled posix functions'] = $disabledPosix;
252
        }
253
 
254
        $pcntl['use pcntl'] = $config->usePcntl();
255
 
256
        $history = [
257
            'history file'     => $prettyPath($config->getHistoryFile()),
258
            'history size'     => $config->getHistorySize(),
259
            'erase duplicates' => $config->getEraseDuplicates(),
260
        ];
261
 
262
        $docs = [
263
            'manual db file'   => $prettyPath($config->getManualDbFile()),
264
            'sqlite available' => true,
265
        ];
266
 
267
        try {
268
            if ($db = $config->getManualDb()) {
269
                if ($q = $db->query('SELECT * FROM meta;')) {
270
                    $q->setFetchMode(\PDO::FETCH_KEY_PAIR);
271
                    $meta = $q->fetchAll();
272
 
273
                    foreach ($meta as $key => $val) {
274
                        switch ($key) {
275
                            case 'built_at':
276
                                $d = new \DateTime('@'.$val);
277
                                $val = $d->format(\DateTime::RFC2822);
278
                                break;
279
                        }
280
                        $key = 'db '.\str_replace('_', ' ', $key);
281
                        $docs[$key] = $val;
282
                    }
283
                } else {
284
                    $docs['db schema'] = '0.1.0';
285
                }
286
            }
287
        } catch (Exception\RuntimeException $e) {
288
            if ($e->getMessage() === 'SQLite PDO driver not found') {
289
                $docs['sqlite available'] = false;
290
            } else {
291
                throw $e;
292
            }
293
        }
294
 
295
        $autocomplete = [
296
            'tab completion enabled' => $config->useTabCompletion(),
297
            'bracketed paste'        => $config->useBracketedPaste(),
298
        ];
299
 
300
        // Shenanigans, but totally justified.
301
        try {
302
            if ($shell = Sudo::fetchProperty($config, 'shell')) {
303
                $shellClass = \get_class($shell);
304
                if ($shellClass !== 'Psy\\Shell') {
305
                    $shellInfo = [
306
                        'PsySH version' => $shell::VERSION,
307
                        'Shell class'   => $shellClass,
308
                    ];
309
                }
310
 
311
                try {
312
                    $core['loop listeners'] = \array_map('get_class', Sudo::fetchProperty($shell, 'loopListeners'));
313
                } catch (\ReflectionException $e) {
314
                    // shrug
315
                }
316
 
317
                $core['commands'] = \array_map('get_class', $shell->all());
318
 
319
                try {
320
                    $autocomplete['custom matchers'] = \array_map('get_class', Sudo::fetchProperty($shell, 'matchers'));
321
                } catch (\ReflectionException $e) {
322
                    // shrug
323
                }
324
            }
325
        } catch (\ReflectionException $e) {
326
            // shrug
327
        }
328
 
329
        // @todo Show Presenter / custom casters.
330
 
331
        return \array_merge($shellInfo, $core, \compact('updates', 'pcntl', 'input', 'readline', 'output', 'history', 'docs', 'autocomplete'));
332
    }
333
}
334
 
335
if (!\function_exists('Psy\\bin')) {
336
    /**
337
     * `psysh` command line executable.
338
     *
339
     * @return \Closure
340
     */
341
    function bin(): \Closure
342
    {
343
        return function () {
344
            if (!isset($_SERVER['PSYSH_IGNORE_ENV']) || !$_SERVER['PSYSH_IGNORE_ENV']) {
345
                if (\defined('HHVM_VERSION_ID')) {
346
                    \fwrite(\STDERR, 'PsySH v0.11 and higher does not support HHVM. Install an older version, or set the environment variable PSYSH_IGNORE_ENV=1 to override this restriction and proceed anyway.'.\PHP_EOL);
347
                    exit(1);
348
                }
349
 
350
                if (\PHP_VERSION_ID < 70000) {
351
                    \fwrite(\STDERR, 'PHP 7.0.0 or higher is required. You can set the environment variable PSYSH_IGNORE_ENV=1 to override this restriction and proceed anyway.'.\PHP_EOL);
352
                    exit(1);
353
                }
354
 
355
                if (\PHP_VERSION_ID > 89999) {
356
                    \fwrite(\STDERR, 'PHP 9 or higher is not supported. You can set the environment variable PSYSH_IGNORE_ENV=1 to override this restriction and proceed anyway.'.\PHP_EOL);
357
                    exit(1);
358
                }
359
 
360
                if (!\function_exists('json_encode')) {
361
                    \fwrite(\STDERR, 'The JSON extension is required. Please install it. You can set the environment variable PSYSH_IGNORE_ENV=1 to override this restriction and proceed anyway.'.\PHP_EOL);
362
                    exit(1);
363
                }
364
 
365
                if (!\function_exists('token_get_all')) {
366
                    \fwrite(\STDERR, 'The Tokenizer extension is required. Please install it. You can set the environment variable PSYSH_IGNORE_ENV=1 to override this restriction and proceed anyway.'.\PHP_EOL);
367
                    exit(1);
368
                }
369
            }
370
 
371
            $usageException = null;
372
            $shellIsPhar = Shell::isPhar();
373
 
374
            $input = new ArgvInput();
375
            try {
376
                $input->bind(new InputDefinition(\array_merge(Configuration::getInputOptions(), [
377
                    new InputOption('help', 'h', InputOption::VALUE_NONE),
378
                    new InputOption('version', 'V', InputOption::VALUE_NONE),
379
                    new InputOption('self-update', 'u', InputOption::VALUE_NONE),
380
 
381
                    new InputArgument('include', InputArgument::IS_ARRAY),
382
                ])));
383
            } catch (\RuntimeException $e) {
384
                $usageException = $e;
385
            }
386
 
387
            try {
388
                $config = Configuration::fromInput($input);
389
            } catch (\InvalidArgumentException $e) {
390
                $usageException = $e;
391
            }
392
 
393
            // Handle --help
394
            if ($usageException !== null || $input->getOption('help')) {
395
                if ($usageException !== null) {
396
                    echo $usageException->getMessage().\PHP_EOL.\PHP_EOL;
397
                }
398
 
399
                $version = Shell::getVersionHeader(false);
400
                $argv = isset($_SERVER['argv']) ? $_SERVER['argv'] : [];
401
                $name = $argv ? \basename(\reset($argv)) : 'psysh';
402
 
403
                echo <<<EOL
404
$version
405
 
406
Usage:
407
  $name [--version] [--help] [files...]
408
 
409
Options:
410
  -h, --help            Display this help message.
411
  -c, --config FILE     Use an alternate PsySH config file location.
412
      --cwd PATH        Use an alternate working directory.
413
  -V, --version         Display the PsySH version.
414
 
415
EOL;
416
                if ($shellIsPhar) {
417
                    echo <<<EOL
418
  -u, --self-update     Install a newer version if available.
419
 
420
EOL;
421
                }
422
                echo <<<EOL
423
      --color           Force colors in output.
424
      --no-color        Disable colors in output.
425
  -i, --interactive     Force PsySH to run in interactive mode.
426
  -n, --no-interactive  Run PsySH without interactive input. Requires input from stdin.
427
  -r, --raw-output      Print var_export-style return values (for non-interactive input)
428
      --compact         Run PsySH with compact output.
429
  -q, --quiet           Shhhhhh.
430
  -v|vv|vvv, --verbose  Increase the verbosity of messages.
431
      --yolo            Run PsySH without input validation. You don't want this.
432
 
433
EOL;
434
 
435
                exit($usageException === null ? 0 : 1);
436
            }
437
 
438
            // Handle --version
439
            if ($input->getOption('version')) {
440
                echo Shell::getVersionHeader($config->useUnicode()).\PHP_EOL;
441
                exit(0);
442
            }
443
 
444
            // Handle --self-update
445
            if ($input->getOption('self-update')) {
446
                if (!$shellIsPhar) {
447
                    \fwrite(\STDERR, 'The --self-update option can only be used with with a phar based install.'.\PHP_EOL);
448
                    exit(1);
449
                }
450
                $selfUpdate = new SelfUpdate(new GitHubChecker(), new Installer());
451
                $result = $selfUpdate->run($input, $config->getOutput());
452
                exit($result);
453
            }
454
 
455
            $shell = new Shell($config);
456
 
457
            // Pass additional arguments to Shell as 'includes'
458
            $shell->setIncludes($input->getArgument('include'));
459
 
460
            try {
461
                // And go!
462
                $shell->run();
463
            } catch (\Throwable $e) {
464
                \fwrite(\STDERR, $e->getMessage().\PHP_EOL);
465
 
466
                // @todo this triggers the "exited unexpectedly" logic in the
467
                // ForkingLoop, so we can't exit(1) after starting the shell...
468
                // fix this :)
469
 
470
                // exit(1);
471
            }
472
        };
473
    }
474
}