Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 148 | Zur aktuellen Revision | 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
 
231
        $pcntl = [
232
            'pcntl available' => ProcessForker::isPcntlSupported(),
233
            'posix available' => ProcessForker::isPosixSupported(),
234
        ];
235
 
236
        if ($disabledPcntl = ProcessForker::disabledPcntlFunctions()) {
237
            $pcntl['disabled pcntl functions'] = $disabledPcntl;
238
        }
239
 
240
        if ($disabledPosix = ProcessForker::disabledPosixFunctions()) {
241
            $pcntl['disabled posix functions'] = $disabledPosix;
242
        }
243
 
244
        $pcntl['use pcntl'] = $config->usePcntl();
245
 
246
        $history = [
247
            'history file'     => $prettyPath($config->getHistoryFile()),
248
            'history size'     => $config->getHistorySize(),
249
            'erase duplicates' => $config->getEraseDuplicates(),
250
        ];
251
 
252
        $docs = [
253
            'manual db file'   => $prettyPath($config->getManualDbFile()),
254
            'sqlite available' => true,
255
        ];
256
 
257
        try {
258
            if ($db = $config->getManualDb()) {
259
                if ($q = $db->query('SELECT * FROM meta;')) {
260
                    $q->setFetchMode(\PDO::FETCH_KEY_PAIR);
261
                    $meta = $q->fetchAll();
262
 
263
                    foreach ($meta as $key => $val) {
264
                        switch ($key) {
265
                            case 'built_at':
266
                                $d = new \DateTime('@'.$val);
267
                                $val = $d->format(\DateTime::RFC2822);
268
                                break;
269
                        }
270
                        $key = 'db '.\str_replace('_', ' ', $key);
271
                        $docs[$key] = $val;
272
                    }
273
                } else {
274
                    $docs['db schema'] = '0.1.0';
275
                }
276
            }
277
        } catch (Exception\RuntimeException $e) {
278
            if ($e->getMessage() === 'SQLite PDO driver not found') {
279
                $docs['sqlite available'] = false;
280
            } else {
281
                throw $e;
282
            }
283
        }
284
 
285
        $autocomplete = [
286
            'tab completion enabled' => $config->useTabCompletion(),
287
            'bracketed paste'        => $config->useBracketedPaste(),
288
        ];
289
 
290
        // Shenanigans, but totally justified.
291
        try {
292
            if ($shell = Sudo::fetchProperty($config, 'shell')) {
293
                $shellClass = \get_class($shell);
294
                if ($shellClass !== 'Psy\\Shell') {
295
                    $shellInfo = [
296
                        'PsySH version' => $shell::VERSION,
297
                        'Shell class'   => $shellClass,
298
                    ];
299
                }
300
 
301
                try {
302
                    $core['loop listeners'] = \array_map('get_class', Sudo::fetchProperty($shell, 'loopListeners'));
303
                } catch (\ReflectionException $e) {
304
                    // shrug
305
                }
306
 
307
                $core['commands'] = \array_map('get_class', $shell->all());
308
 
309
                try {
310
                    $autocomplete['custom matchers'] = \array_map('get_class', Sudo::fetchProperty($shell, 'matchers'));
311
                } catch (\ReflectionException $e) {
312
                    // shrug
313
                }
314
            }
315
        } catch (\ReflectionException $e) {
316
            // shrug
317
        }
318
 
319
        // @todo Show Presenter / custom casters.
320
 
321
        return \array_merge($shellInfo, $core, \compact('updates', 'pcntl', 'input', 'readline', 'output', 'history', 'docs', 'autocomplete'));
322
    }
323
}
324
 
325
if (!\function_exists('Psy\\bin')) {
326
    /**
327
     * `psysh` command line executable.
328
     *
329
     * @return \Closure
330
     */
331
    function bin(): \Closure
332
    {
333
        return function () {
334
            if (!isset($_SERVER['PSYSH_IGNORE_ENV']) || !$_SERVER['PSYSH_IGNORE_ENV']) {
335
                if (\defined('HHVM_VERSION_ID')) {
336
                    \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);
337
                    exit(1);
338
                }
339
 
340
                if (\PHP_VERSION_ID < 70000) {
341
                    \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);
342
                    exit(1);
343
                }
344
 
345
                if (\PHP_VERSION_ID > 89999) {
346
                    \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);
347
                    exit(1);
348
                }
349
 
350
                if (!\function_exists('json_encode')) {
351
                    \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);
352
                    exit(1);
353
                }
354
 
355
                if (!\function_exists('token_get_all')) {
356
                    \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);
357
                    exit(1);
358
                }
359
            }
360
 
361
            $usageException = null;
362
            $shellIsPhar = Shell::isPhar();
363
 
364
            $input = new ArgvInput();
365
            try {
366
                $input->bind(new InputDefinition(\array_merge(Configuration::getInputOptions(), [
367
                    new InputOption('help', 'h', InputOption::VALUE_NONE),
368
                    new InputOption('version', 'V', InputOption::VALUE_NONE),
369
                    new InputOption('self-update', 'u', InputOption::VALUE_NONE),
370
 
371
                    new InputArgument('include', InputArgument::IS_ARRAY),
372
                ])));
373
            } catch (\RuntimeException $e) {
374
                $usageException = $e;
375
            }
376
 
377
            try {
378
                $config = Configuration::fromInput($input);
379
            } catch (\InvalidArgumentException $e) {
380
                $usageException = $e;
381
            }
382
 
383
            // Handle --help
384
            if ($usageException !== null || $input->getOption('help')) {
385
                if ($usageException !== null) {
386
                    echo $usageException->getMessage().\PHP_EOL.\PHP_EOL;
387
                }
388
 
389
                $version = Shell::getVersionHeader(false);
390
                $argv = isset($_SERVER['argv']) ? $_SERVER['argv'] : [];
391
                $name = $argv ? \basename(\reset($argv)) : 'psysh';
392
 
393
                echo <<<EOL
394
$version
395
 
396
Usage:
397
  $name [--version] [--help] [files...]
398
 
399
Options:
400
  -h, --help            Display this help message.
401
  -c, --config FILE     Use an alternate PsySH config file location.
402
      --cwd PATH        Use an alternate working directory.
403
  -V, --version         Display the PsySH version.
404
 
405
EOL;
406
                if ($shellIsPhar) {
407
                    echo <<<EOL
408
  -u, --self-update     Install a newer version if available.
409
 
410
EOL;
411
                }
412
                echo <<<EOL
413
      --color           Force colors in output.
414
      --no-color        Disable colors in output.
415
  -i, --interactive     Force PsySH to run in interactive mode.
416
  -n, --no-interactive  Run PsySH without interactive input. Requires input from stdin.
417
  -r, --raw-output      Print var_export-style return values (for non-interactive input)
418
      --compact         Run PsySH with compact output.
419
  -q, --quiet           Shhhhhh.
420
  -v|vv|vvv, --verbose  Increase the verbosity of messages.
421
      --yolo            Run PsySH without input validation. You don't want this.
422
 
423
EOL;
424
 
425
                exit($usageException === null ? 0 : 1);
426
            }
427
 
428
            // Handle --version
429
            if ($input->getOption('version')) {
430
                echo Shell::getVersionHeader($config->useUnicode()).\PHP_EOL;
431
                exit(0);
432
            }
433
 
434
            // Handle --self-update
435
            if ($input->getOption('self-update')) {
436
                if (!$shellIsPhar) {
437
                    \fwrite(\STDERR, 'The --self-update option can only be used with with a phar based install.'.\PHP_EOL);
438
                    exit(1);
439
                }
440
                $selfUpdate = new SelfUpdate(new GitHubChecker(), new Installer());
441
                $result = $selfUpdate->run($input, $config->getOutput());
442
                exit($result);
443
            }
444
 
445
            $shell = new Shell($config);
446
 
447
            // Pass additional arguments to Shell as 'includes'
448
            $shell->setIncludes($input->getArgument('include'));
449
 
450
            try {
451
                // And go!
452
                $shell->run();
453
            } catch (\Throwable $e) {
454
                \fwrite(\STDERR, $e->getMessage().\PHP_EOL);
455
 
456
                // @todo this triggers the "exited unexpectedly" logic in the
457
                // ForkingLoop, so we can't exit(1) after starting the shell...
458
                // fix this :)
459
 
460
                // exit(1);
461
            }
462
        };
463
    }
464
}