Chris@13
|
1 <?php
|
Chris@13
|
2
|
Chris@13
|
3 /*
|
Chris@13
|
4 * This file is part of Psy Shell.
|
Chris@13
|
5 *
|
Chris@13
|
6 * (c) 2012-2018 Justin Hileman
|
Chris@13
|
7 *
|
Chris@13
|
8 * For the full copyright and license information, please view the LICENSE
|
Chris@13
|
9 * file that was distributed with this source code.
|
Chris@13
|
10 */
|
Chris@13
|
11
|
Chris@13
|
12 namespace Psy;
|
Chris@13
|
13
|
Chris@13
|
14 use Psy\Exception\DeprecatedException;
|
Chris@13
|
15 use Psy\Exception\RuntimeException;
|
Chris@13
|
16 use Psy\Output\OutputPager;
|
Chris@13
|
17 use Psy\Output\ShellOutput;
|
Chris@13
|
18 use Psy\Readline\GNUReadline;
|
Chris@13
|
19 use Psy\Readline\HoaConsole;
|
Chris@13
|
20 use Psy\Readline\Libedit;
|
Chris@13
|
21 use Psy\Readline\Readline;
|
Chris@13
|
22 use Psy\Readline\Transient;
|
Chris@13
|
23 use Psy\TabCompletion\AutoCompleter;
|
Chris@13
|
24 use Psy\VarDumper\Presenter;
|
Chris@13
|
25 use Psy\VersionUpdater\Checker;
|
Chris@13
|
26 use Psy\VersionUpdater\GitHubChecker;
|
Chris@13
|
27 use Psy\VersionUpdater\IntervalChecker;
|
Chris@13
|
28 use Psy\VersionUpdater\NoopChecker;
|
Chris@13
|
29
|
Chris@13
|
30 /**
|
Chris@13
|
31 * The Psy Shell configuration.
|
Chris@13
|
32 */
|
Chris@13
|
33 class Configuration
|
Chris@13
|
34 {
|
Chris@13
|
35 const COLOR_MODE_AUTO = 'auto';
|
Chris@13
|
36 const COLOR_MODE_FORCED = 'forced';
|
Chris@13
|
37 const COLOR_MODE_DISABLED = 'disabled';
|
Chris@13
|
38
|
Chris@13
|
39 private static $AVAILABLE_OPTIONS = [
|
Chris@13
|
40 'codeCleaner',
|
Chris@13
|
41 'colorMode',
|
Chris@13
|
42 'configDir',
|
Chris@13
|
43 'dataDir',
|
Chris@13
|
44 'defaultIncludes',
|
Chris@13
|
45 'eraseDuplicates',
|
Chris@13
|
46 'errorLoggingLevel',
|
Chris@13
|
47 'forceArrayIndexes',
|
Chris@13
|
48 'historySize',
|
Chris@13
|
49 'manualDbFile',
|
Chris@13
|
50 'pager',
|
Chris@13
|
51 'prompt',
|
Chris@13
|
52 'requireSemicolons',
|
Chris@13
|
53 'runtimeDir',
|
Chris@13
|
54 'startupMessage',
|
Chris@13
|
55 'updateCheck',
|
Chris@13
|
56 'useBracketedPaste',
|
Chris@13
|
57 'usePcntl',
|
Chris@13
|
58 'useReadline',
|
Chris@13
|
59 'useTabCompletion',
|
Chris@13
|
60 'useUnicode',
|
Chris@13
|
61 'warnOnMultipleConfigs',
|
Chris@13
|
62 ];
|
Chris@13
|
63
|
Chris@13
|
64 private $defaultIncludes;
|
Chris@13
|
65 private $configDir;
|
Chris@13
|
66 private $dataDir;
|
Chris@13
|
67 private $runtimeDir;
|
Chris@13
|
68 private $configFile;
|
Chris@13
|
69 /** @var string|false */
|
Chris@13
|
70 private $historyFile;
|
Chris@13
|
71 private $historySize;
|
Chris@13
|
72 private $eraseDuplicates;
|
Chris@13
|
73 private $manualDbFile;
|
Chris@13
|
74 private $hasReadline;
|
Chris@13
|
75 private $useReadline;
|
Chris@13
|
76 private $useBracketedPaste;
|
Chris@13
|
77 private $hasPcntl;
|
Chris@13
|
78 private $usePcntl;
|
Chris@13
|
79 private $newCommands = [];
|
Chris@13
|
80 private $requireSemicolons = false;
|
Chris@13
|
81 private $useUnicode;
|
Chris@13
|
82 private $useTabCompletion;
|
Chris@13
|
83 private $newMatchers = [];
|
Chris@13
|
84 private $errorLoggingLevel = E_ALL;
|
Chris@13
|
85 private $warnOnMultipleConfigs = false;
|
Chris@13
|
86 private $colorMode;
|
Chris@13
|
87 private $updateCheck;
|
Chris@13
|
88 private $startupMessage;
|
Chris@13
|
89 private $forceArrayIndexes = false;
|
Chris@13
|
90
|
Chris@13
|
91 // services
|
Chris@13
|
92 private $readline;
|
Chris@13
|
93 private $output;
|
Chris@13
|
94 private $shell;
|
Chris@13
|
95 private $cleaner;
|
Chris@13
|
96 private $pager;
|
Chris@13
|
97 private $manualDb;
|
Chris@13
|
98 private $presenter;
|
Chris@13
|
99 private $autoCompleter;
|
Chris@13
|
100 private $checker;
|
Chris@13
|
101 private $prompt;
|
Chris@13
|
102
|
Chris@13
|
103 /**
|
Chris@13
|
104 * Construct a Configuration instance.
|
Chris@13
|
105 *
|
Chris@13
|
106 * Optionally, supply an array of configuration values to load.
|
Chris@13
|
107 *
|
Chris@13
|
108 * @param array $config Optional array of configuration values
|
Chris@13
|
109 */
|
Chris@13
|
110 public function __construct(array $config = [])
|
Chris@13
|
111 {
|
Chris@13
|
112 $this->setColorMode(self::COLOR_MODE_AUTO);
|
Chris@13
|
113
|
Chris@13
|
114 // explicit configFile option
|
Chris@13
|
115 if (isset($config['configFile'])) {
|
Chris@13
|
116 $this->configFile = $config['configFile'];
|
Chris@17
|
117 } elseif ($configFile = \getenv('PSYSH_CONFIG')) {
|
Chris@13
|
118 $this->configFile = $configFile;
|
Chris@13
|
119 }
|
Chris@13
|
120
|
Chris@13
|
121 // legacy baseDir option
|
Chris@13
|
122 if (isset($config['baseDir'])) {
|
Chris@13
|
123 $msg = "The 'baseDir' configuration option is deprecated; " .
|
Chris@13
|
124 "please specify 'configDir' and 'dataDir' options instead";
|
Chris@13
|
125 throw new DeprecatedException($msg);
|
Chris@13
|
126 }
|
Chris@13
|
127
|
Chris@13
|
128 unset($config['configFile'], $config['baseDir']);
|
Chris@13
|
129
|
Chris@13
|
130 // go go gadget, config!
|
Chris@13
|
131 $this->loadConfig($config);
|
Chris@13
|
132 $this->init();
|
Chris@13
|
133 }
|
Chris@13
|
134
|
Chris@13
|
135 /**
|
Chris@13
|
136 * Initialize the configuration.
|
Chris@13
|
137 *
|
Chris@13
|
138 * This checks for the presence of Readline and Pcntl extensions.
|
Chris@13
|
139 *
|
Chris@13
|
140 * If a config file is available, it will be loaded and merged with the current config.
|
Chris@13
|
141 *
|
Chris@13
|
142 * If no custom config file was specified and a local project config file
|
Chris@13
|
143 * is available, it will be loaded and merged with the current config.
|
Chris@13
|
144 */
|
Chris@13
|
145 public function init()
|
Chris@13
|
146 {
|
Chris@13
|
147 // feature detection
|
Chris@17
|
148 $this->hasReadline = \function_exists('readline');
|
Chris@17
|
149 $this->hasPcntl = \function_exists('pcntl_signal') && \function_exists('posix_getpid');
|
Chris@13
|
150
|
Chris@13
|
151 if ($configFile = $this->getConfigFile()) {
|
Chris@13
|
152 $this->loadConfigFile($configFile);
|
Chris@13
|
153 }
|
Chris@13
|
154
|
Chris@13
|
155 if (!$this->configFile && $localConfig = $this->getLocalConfigFile()) {
|
Chris@13
|
156 $this->loadConfigFile($localConfig);
|
Chris@13
|
157 }
|
Chris@13
|
158 }
|
Chris@13
|
159
|
Chris@13
|
160 /**
|
Chris@13
|
161 * Get the current PsySH config file.
|
Chris@13
|
162 *
|
Chris@13
|
163 * If a `configFile` option was passed to the Configuration constructor,
|
Chris@13
|
164 * this file will be returned. If not, all possible config directories will
|
Chris@13
|
165 * be searched, and the first `config.php` or `rc.php` file which exists
|
Chris@13
|
166 * will be returned.
|
Chris@13
|
167 *
|
Chris@13
|
168 * If you're trying to decide where to put your config file, pick
|
Chris@13
|
169 *
|
Chris@13
|
170 * ~/.config/psysh/config.php
|
Chris@13
|
171 *
|
Chris@13
|
172 * @return string
|
Chris@13
|
173 */
|
Chris@13
|
174 public function getConfigFile()
|
Chris@13
|
175 {
|
Chris@13
|
176 if (isset($this->configFile)) {
|
Chris@13
|
177 return $this->configFile;
|
Chris@13
|
178 }
|
Chris@13
|
179
|
Chris@13
|
180 $files = ConfigPaths::getConfigFiles(['config.php', 'rc.php'], $this->configDir);
|
Chris@13
|
181
|
Chris@13
|
182 if (!empty($files)) {
|
Chris@17
|
183 if ($this->warnOnMultipleConfigs && \count($files) > 1) {
|
Chris@17
|
184 $msg = \sprintf('Multiple configuration files found: %s. Using %s', \implode($files, ', '), $files[0]);
|
Chris@17
|
185 \trigger_error($msg, E_USER_NOTICE);
|
Chris@13
|
186 }
|
Chris@13
|
187
|
Chris@13
|
188 return $files[0];
|
Chris@13
|
189 }
|
Chris@13
|
190 }
|
Chris@13
|
191
|
Chris@13
|
192 /**
|
Chris@13
|
193 * Get the local PsySH config file.
|
Chris@13
|
194 *
|
Chris@13
|
195 * Searches for a project specific config file `.psysh.php` in the current
|
Chris@13
|
196 * working directory.
|
Chris@13
|
197 *
|
Chris@13
|
198 * @return string
|
Chris@13
|
199 */
|
Chris@13
|
200 public function getLocalConfigFile()
|
Chris@13
|
201 {
|
Chris@17
|
202 $localConfig = \getcwd() . '/.psysh.php';
|
Chris@13
|
203
|
Chris@17
|
204 if (@\is_file($localConfig)) {
|
Chris@13
|
205 return $localConfig;
|
Chris@13
|
206 }
|
Chris@13
|
207 }
|
Chris@13
|
208
|
Chris@13
|
209 /**
|
Chris@13
|
210 * Load configuration values from an array of options.
|
Chris@13
|
211 *
|
Chris@13
|
212 * @param array $options
|
Chris@13
|
213 */
|
Chris@13
|
214 public function loadConfig(array $options)
|
Chris@13
|
215 {
|
Chris@13
|
216 foreach (self::$AVAILABLE_OPTIONS as $option) {
|
Chris@13
|
217 if (isset($options[$option])) {
|
Chris@17
|
218 $method = 'set' . \ucfirst($option);
|
Chris@13
|
219 $this->$method($options[$option]);
|
Chris@13
|
220 }
|
Chris@13
|
221 }
|
Chris@13
|
222
|
Chris@13
|
223 // legacy `tabCompletion` option
|
Chris@13
|
224 if (isset($options['tabCompletion'])) {
|
Chris@13
|
225 $msg = '`tabCompletion` is deprecated; use `useTabCompletion` instead.';
|
Chris@17
|
226 @\trigger_error($msg, E_USER_DEPRECATED);
|
Chris@13
|
227
|
Chris@13
|
228 $this->setUseTabCompletion($options['tabCompletion']);
|
Chris@13
|
229 }
|
Chris@13
|
230
|
Chris@13
|
231 foreach (['commands', 'matchers', 'casters'] as $option) {
|
Chris@13
|
232 if (isset($options[$option])) {
|
Chris@17
|
233 $method = 'add' . \ucfirst($option);
|
Chris@13
|
234 $this->$method($options[$option]);
|
Chris@13
|
235 }
|
Chris@13
|
236 }
|
Chris@13
|
237
|
Chris@13
|
238 // legacy `tabCompletionMatchers` option
|
Chris@13
|
239 if (isset($options['tabCompletionMatchers'])) {
|
Chris@13
|
240 $msg = '`tabCompletionMatchers` is deprecated; use `matchers` instead.';
|
Chris@17
|
241 @\trigger_error($msg, E_USER_DEPRECATED);
|
Chris@13
|
242
|
Chris@13
|
243 $this->addMatchers($options['tabCompletionMatchers']);
|
Chris@13
|
244 }
|
Chris@13
|
245 }
|
Chris@13
|
246
|
Chris@13
|
247 /**
|
Chris@13
|
248 * Load a configuration file (default: `$HOME/.config/psysh/config.php`).
|
Chris@13
|
249 *
|
Chris@13
|
250 * This configuration instance will be available to the config file as $config.
|
Chris@13
|
251 * The config file may directly manipulate the configuration, or may return
|
Chris@13
|
252 * an array of options which will be merged with the current configuration.
|
Chris@13
|
253 *
|
Chris@13
|
254 * @throws \InvalidArgumentException if the config file returns a non-array result
|
Chris@13
|
255 *
|
Chris@13
|
256 * @param string $file
|
Chris@13
|
257 */
|
Chris@13
|
258 public function loadConfigFile($file)
|
Chris@13
|
259 {
|
Chris@13
|
260 $__psysh_config_file__ = $file;
|
Chris@13
|
261 $load = function ($config) use ($__psysh_config_file__) {
|
Chris@13
|
262 $result = require $__psysh_config_file__;
|
Chris@13
|
263 if ($result !== 1) {
|
Chris@13
|
264 return $result;
|
Chris@13
|
265 }
|
Chris@13
|
266 };
|
Chris@13
|
267 $result = $load($this);
|
Chris@13
|
268
|
Chris@13
|
269 if (!empty($result)) {
|
Chris@17
|
270 if (\is_array($result)) {
|
Chris@13
|
271 $this->loadConfig($result);
|
Chris@13
|
272 } else {
|
Chris@13
|
273 throw new \InvalidArgumentException('Psy Shell configuration must return an array of options');
|
Chris@13
|
274 }
|
Chris@13
|
275 }
|
Chris@13
|
276 }
|
Chris@13
|
277
|
Chris@13
|
278 /**
|
Chris@13
|
279 * Set files to be included by default at the start of each shell session.
|
Chris@13
|
280 *
|
Chris@13
|
281 * @param array $includes
|
Chris@13
|
282 */
|
Chris@13
|
283 public function setDefaultIncludes(array $includes = [])
|
Chris@13
|
284 {
|
Chris@13
|
285 $this->defaultIncludes = $includes;
|
Chris@13
|
286 }
|
Chris@13
|
287
|
Chris@13
|
288 /**
|
Chris@13
|
289 * Get files to be included by default at the start of each shell session.
|
Chris@13
|
290 *
|
Chris@13
|
291 * @return array
|
Chris@13
|
292 */
|
Chris@13
|
293 public function getDefaultIncludes()
|
Chris@13
|
294 {
|
Chris@13
|
295 return $this->defaultIncludes ?: [];
|
Chris@13
|
296 }
|
Chris@13
|
297
|
Chris@13
|
298 /**
|
Chris@13
|
299 * Set the shell's config directory location.
|
Chris@13
|
300 *
|
Chris@13
|
301 * @param string $dir
|
Chris@13
|
302 */
|
Chris@13
|
303 public function setConfigDir($dir)
|
Chris@13
|
304 {
|
Chris@13
|
305 $this->configDir = (string) $dir;
|
Chris@13
|
306 }
|
Chris@13
|
307
|
Chris@13
|
308 /**
|
Chris@13
|
309 * Get the current configuration directory, if any is explicitly set.
|
Chris@13
|
310 *
|
Chris@13
|
311 * @return string
|
Chris@13
|
312 */
|
Chris@13
|
313 public function getConfigDir()
|
Chris@13
|
314 {
|
Chris@13
|
315 return $this->configDir;
|
Chris@13
|
316 }
|
Chris@13
|
317
|
Chris@13
|
318 /**
|
Chris@13
|
319 * Set the shell's data directory location.
|
Chris@13
|
320 *
|
Chris@13
|
321 * @param string $dir
|
Chris@13
|
322 */
|
Chris@13
|
323 public function setDataDir($dir)
|
Chris@13
|
324 {
|
Chris@13
|
325 $this->dataDir = (string) $dir;
|
Chris@13
|
326 }
|
Chris@13
|
327
|
Chris@13
|
328 /**
|
Chris@13
|
329 * Get the current data directory, if any is explicitly set.
|
Chris@13
|
330 *
|
Chris@13
|
331 * @return string
|
Chris@13
|
332 */
|
Chris@13
|
333 public function getDataDir()
|
Chris@13
|
334 {
|
Chris@13
|
335 return $this->dataDir;
|
Chris@13
|
336 }
|
Chris@13
|
337
|
Chris@13
|
338 /**
|
Chris@13
|
339 * Set the shell's temporary directory location.
|
Chris@13
|
340 *
|
Chris@13
|
341 * @param string $dir
|
Chris@13
|
342 */
|
Chris@13
|
343 public function setRuntimeDir($dir)
|
Chris@13
|
344 {
|
Chris@13
|
345 $this->runtimeDir = (string) $dir;
|
Chris@13
|
346 }
|
Chris@13
|
347
|
Chris@13
|
348 /**
|
Chris@13
|
349 * Get the shell's temporary directory location.
|
Chris@13
|
350 *
|
Chris@13
|
351 * Defaults to `/psysh` inside the system's temp dir unless explicitly
|
Chris@13
|
352 * overridden.
|
Chris@13
|
353 *
|
Chris@13
|
354 * @return string
|
Chris@13
|
355 */
|
Chris@13
|
356 public function getRuntimeDir()
|
Chris@13
|
357 {
|
Chris@13
|
358 if (!isset($this->runtimeDir)) {
|
Chris@13
|
359 $this->runtimeDir = ConfigPaths::getRuntimeDir();
|
Chris@13
|
360 }
|
Chris@13
|
361
|
Chris@17
|
362 if (!\is_dir($this->runtimeDir)) {
|
Chris@17
|
363 \mkdir($this->runtimeDir, 0700, true);
|
Chris@13
|
364 }
|
Chris@13
|
365
|
Chris@13
|
366 return $this->runtimeDir;
|
Chris@13
|
367 }
|
Chris@13
|
368
|
Chris@13
|
369 /**
|
Chris@13
|
370 * Set the readline history file path.
|
Chris@13
|
371 *
|
Chris@13
|
372 * @param string $file
|
Chris@13
|
373 */
|
Chris@13
|
374 public function setHistoryFile($file)
|
Chris@13
|
375 {
|
Chris@13
|
376 $this->historyFile = ConfigPaths::touchFileWithMkdir($file);
|
Chris@13
|
377 }
|
Chris@13
|
378
|
Chris@13
|
379 /**
|
Chris@13
|
380 * Get the readline history file path.
|
Chris@13
|
381 *
|
Chris@13
|
382 * Defaults to `/history` inside the shell's base config dir unless
|
Chris@13
|
383 * explicitly overridden.
|
Chris@13
|
384 *
|
Chris@13
|
385 * @return string
|
Chris@13
|
386 */
|
Chris@13
|
387 public function getHistoryFile()
|
Chris@13
|
388 {
|
Chris@13
|
389 if (isset($this->historyFile)) {
|
Chris@13
|
390 return $this->historyFile;
|
Chris@13
|
391 }
|
Chris@13
|
392
|
Chris@13
|
393 $files = ConfigPaths::getConfigFiles(['psysh_history', 'history'], $this->configDir);
|
Chris@13
|
394
|
Chris@13
|
395 if (!empty($files)) {
|
Chris@17
|
396 if ($this->warnOnMultipleConfigs && \count($files) > 1) {
|
Chris@17
|
397 $msg = \sprintf('Multiple history files found: %s. Using %s', \implode($files, ', '), $files[0]);
|
Chris@17
|
398 \trigger_error($msg, E_USER_NOTICE);
|
Chris@13
|
399 }
|
Chris@13
|
400
|
Chris@13
|
401 $this->setHistoryFile($files[0]);
|
Chris@13
|
402 } else {
|
Chris@13
|
403 // fallback: create our own history file
|
Chris@13
|
404 $dir = $this->configDir ?: ConfigPaths::getCurrentConfigDir();
|
Chris@13
|
405 $this->setHistoryFile($dir . '/psysh_history');
|
Chris@13
|
406 }
|
Chris@13
|
407
|
Chris@13
|
408 return $this->historyFile;
|
Chris@13
|
409 }
|
Chris@13
|
410
|
Chris@13
|
411 /**
|
Chris@13
|
412 * Set the readline max history size.
|
Chris@13
|
413 *
|
Chris@13
|
414 * @param int $value
|
Chris@13
|
415 */
|
Chris@13
|
416 public function setHistorySize($value)
|
Chris@13
|
417 {
|
Chris@13
|
418 $this->historySize = (int) $value;
|
Chris@13
|
419 }
|
Chris@13
|
420
|
Chris@13
|
421 /**
|
Chris@13
|
422 * Get the readline max history size.
|
Chris@13
|
423 *
|
Chris@13
|
424 * @return int
|
Chris@13
|
425 */
|
Chris@13
|
426 public function getHistorySize()
|
Chris@13
|
427 {
|
Chris@13
|
428 return $this->historySize;
|
Chris@13
|
429 }
|
Chris@13
|
430
|
Chris@13
|
431 /**
|
Chris@13
|
432 * Sets whether readline erases old duplicate history entries.
|
Chris@13
|
433 *
|
Chris@13
|
434 * @param bool $value
|
Chris@13
|
435 */
|
Chris@13
|
436 public function setEraseDuplicates($value)
|
Chris@13
|
437 {
|
Chris@13
|
438 $this->eraseDuplicates = (bool) $value;
|
Chris@13
|
439 }
|
Chris@13
|
440
|
Chris@13
|
441 /**
|
Chris@13
|
442 * Get whether readline erases old duplicate history entries.
|
Chris@13
|
443 *
|
Chris@13
|
444 * @return bool
|
Chris@13
|
445 */
|
Chris@13
|
446 public function getEraseDuplicates()
|
Chris@13
|
447 {
|
Chris@13
|
448 return $this->eraseDuplicates;
|
Chris@13
|
449 }
|
Chris@13
|
450
|
Chris@13
|
451 /**
|
Chris@13
|
452 * Get a temporary file of type $type for process $pid.
|
Chris@13
|
453 *
|
Chris@13
|
454 * The file will be created inside the current temporary directory.
|
Chris@13
|
455 *
|
Chris@13
|
456 * @see self::getRuntimeDir
|
Chris@13
|
457 *
|
Chris@13
|
458 * @param string $type
|
Chris@13
|
459 * @param int $pid
|
Chris@13
|
460 *
|
Chris@13
|
461 * @return string Temporary file name
|
Chris@13
|
462 */
|
Chris@13
|
463 public function getTempFile($type, $pid)
|
Chris@13
|
464 {
|
Chris@17
|
465 return \tempnam($this->getRuntimeDir(), $type . '_' . $pid . '_');
|
Chris@13
|
466 }
|
Chris@13
|
467
|
Chris@13
|
468 /**
|
Chris@13
|
469 * Get a filename suitable for a FIFO pipe of $type for process $pid.
|
Chris@13
|
470 *
|
Chris@13
|
471 * The pipe will be created inside the current temporary directory.
|
Chris@13
|
472 *
|
Chris@13
|
473 * @param string $type
|
Chris@13
|
474 * @param int $pid
|
Chris@13
|
475 *
|
Chris@13
|
476 * @return string Pipe name
|
Chris@13
|
477 */
|
Chris@13
|
478 public function getPipe($type, $pid)
|
Chris@13
|
479 {
|
Chris@17
|
480 return \sprintf('%s/%s_%s', $this->getRuntimeDir(), $type, $pid);
|
Chris@13
|
481 }
|
Chris@13
|
482
|
Chris@13
|
483 /**
|
Chris@13
|
484 * Check whether this PHP instance has Readline available.
|
Chris@13
|
485 *
|
Chris@13
|
486 * @return bool True if Readline is available
|
Chris@13
|
487 */
|
Chris@13
|
488 public function hasReadline()
|
Chris@13
|
489 {
|
Chris@13
|
490 return $this->hasReadline;
|
Chris@13
|
491 }
|
Chris@13
|
492
|
Chris@13
|
493 /**
|
Chris@13
|
494 * Enable or disable Readline usage.
|
Chris@13
|
495 *
|
Chris@13
|
496 * @param bool $useReadline
|
Chris@13
|
497 */
|
Chris@13
|
498 public function setUseReadline($useReadline)
|
Chris@13
|
499 {
|
Chris@13
|
500 $this->useReadline = (bool) $useReadline;
|
Chris@13
|
501 }
|
Chris@13
|
502
|
Chris@13
|
503 /**
|
Chris@13
|
504 * Check whether to use Readline.
|
Chris@13
|
505 *
|
Chris@13
|
506 * If `setUseReadline` as been set to true, but Readline is not actually
|
Chris@13
|
507 * available, this will return false.
|
Chris@13
|
508 *
|
Chris@13
|
509 * @return bool True if the current Shell should use Readline
|
Chris@13
|
510 */
|
Chris@13
|
511 public function useReadline()
|
Chris@13
|
512 {
|
Chris@13
|
513 return isset($this->useReadline) ? ($this->hasReadline && $this->useReadline) : $this->hasReadline;
|
Chris@13
|
514 }
|
Chris@13
|
515
|
Chris@13
|
516 /**
|
Chris@13
|
517 * Set the Psy Shell readline service.
|
Chris@13
|
518 *
|
Chris@13
|
519 * @param Readline $readline
|
Chris@13
|
520 */
|
Chris@13
|
521 public function setReadline(Readline $readline)
|
Chris@13
|
522 {
|
Chris@13
|
523 $this->readline = $readline;
|
Chris@13
|
524 }
|
Chris@13
|
525
|
Chris@13
|
526 /**
|
Chris@13
|
527 * Get the Psy Shell readline service.
|
Chris@13
|
528 *
|
Chris@13
|
529 * By default, this service uses (in order of preference):
|
Chris@13
|
530 *
|
Chris@13
|
531 * * GNU Readline
|
Chris@13
|
532 * * Libedit
|
Chris@13
|
533 * * A transient array-based readline emulation.
|
Chris@13
|
534 *
|
Chris@13
|
535 * @return Readline
|
Chris@13
|
536 */
|
Chris@13
|
537 public function getReadline()
|
Chris@13
|
538 {
|
Chris@13
|
539 if (!isset($this->readline)) {
|
Chris@13
|
540 $className = $this->getReadlineClass();
|
Chris@13
|
541 $this->readline = new $className(
|
Chris@13
|
542 $this->getHistoryFile(),
|
Chris@13
|
543 $this->getHistorySize(),
|
Chris@13
|
544 $this->getEraseDuplicates()
|
Chris@13
|
545 );
|
Chris@13
|
546 }
|
Chris@13
|
547
|
Chris@13
|
548 return $this->readline;
|
Chris@13
|
549 }
|
Chris@13
|
550
|
Chris@13
|
551 /**
|
Chris@13
|
552 * Get the appropriate Readline implementation class name.
|
Chris@13
|
553 *
|
Chris@13
|
554 * @see self::getReadline
|
Chris@13
|
555 *
|
Chris@13
|
556 * @return string
|
Chris@13
|
557 */
|
Chris@13
|
558 private function getReadlineClass()
|
Chris@13
|
559 {
|
Chris@13
|
560 if ($this->useReadline()) {
|
Chris@13
|
561 if (GNUReadline::isSupported()) {
|
Chris@13
|
562 return 'Psy\Readline\GNUReadline';
|
Chris@13
|
563 } elseif (Libedit::isSupported()) {
|
Chris@13
|
564 return 'Psy\Readline\Libedit';
|
Chris@13
|
565 } elseif (HoaConsole::isSupported()) {
|
Chris@13
|
566 return 'Psy\Readline\HoaConsole';
|
Chris@13
|
567 }
|
Chris@13
|
568 }
|
Chris@13
|
569
|
Chris@13
|
570 return 'Psy\Readline\Transient';
|
Chris@13
|
571 }
|
Chris@13
|
572
|
Chris@13
|
573 /**
|
Chris@13
|
574 * Enable or disable bracketed paste.
|
Chris@13
|
575 *
|
Chris@13
|
576 * Note that this only works with readline (not libedit) integration for now.
|
Chris@13
|
577 *
|
Chris@13
|
578 * @param bool $useBracketedPaste
|
Chris@13
|
579 */
|
Chris@13
|
580 public function setUseBracketedPaste($useBracketedPaste)
|
Chris@13
|
581 {
|
Chris@13
|
582 $this->useBracketedPaste = (bool) $useBracketedPaste;
|
Chris@13
|
583 }
|
Chris@13
|
584
|
Chris@13
|
585 /**
|
Chris@13
|
586 * Check whether to use bracketed paste with readline.
|
Chris@13
|
587 *
|
Chris@13
|
588 * When this works, it's magical. Tabs in pastes don't try to autcomplete.
|
Chris@13
|
589 * Newlines in paste don't execute code until you get to the end. It makes
|
Chris@13
|
590 * readline act like you'd expect when pasting.
|
Chris@13
|
591 *
|
Chris@13
|
592 * But it often (usually?) does not work. And when it doesn't, it just spews
|
Chris@13
|
593 * escape codes all over the place and generally makes things ugly :(
|
Chris@13
|
594 *
|
Chris@13
|
595 * If `useBracketedPaste` has been set to true, but the current readline
|
Chris@13
|
596 * implementation is anything besides GNU readline, this will return false.
|
Chris@13
|
597 *
|
Chris@13
|
598 * @return bool True if the shell should use bracketed paste
|
Chris@13
|
599 */
|
Chris@13
|
600 public function useBracketedPaste()
|
Chris@13
|
601 {
|
Chris@13
|
602 // For now, only the GNU readline implementation supports bracketed paste.
|
Chris@13
|
603 $supported = ($this->getReadlineClass() === 'Psy\Readline\GNUReadline');
|
Chris@13
|
604
|
Chris@13
|
605 return $supported && $this->useBracketedPaste;
|
Chris@13
|
606
|
Chris@13
|
607 // @todo mebbe turn this on by default some day?
|
Chris@13
|
608 // return isset($this->useBracketedPaste) ? ($supported && $this->useBracketedPaste) : $supported;
|
Chris@13
|
609 }
|
Chris@13
|
610
|
Chris@13
|
611 /**
|
Chris@13
|
612 * Check whether this PHP instance has Pcntl available.
|
Chris@13
|
613 *
|
Chris@13
|
614 * @return bool True if Pcntl is available
|
Chris@13
|
615 */
|
Chris@13
|
616 public function hasPcntl()
|
Chris@13
|
617 {
|
Chris@13
|
618 return $this->hasPcntl;
|
Chris@13
|
619 }
|
Chris@13
|
620
|
Chris@13
|
621 /**
|
Chris@13
|
622 * Enable or disable Pcntl usage.
|
Chris@13
|
623 *
|
Chris@13
|
624 * @param bool $usePcntl
|
Chris@13
|
625 */
|
Chris@13
|
626 public function setUsePcntl($usePcntl)
|
Chris@13
|
627 {
|
Chris@13
|
628 $this->usePcntl = (bool) $usePcntl;
|
Chris@13
|
629 }
|
Chris@13
|
630
|
Chris@13
|
631 /**
|
Chris@13
|
632 * Check whether to use Pcntl.
|
Chris@13
|
633 *
|
Chris@13
|
634 * If `setUsePcntl` has been set to true, but Pcntl is not actually
|
Chris@13
|
635 * available, this will return false.
|
Chris@13
|
636 *
|
Chris@13
|
637 * @return bool True if the current Shell should use Pcntl
|
Chris@13
|
638 */
|
Chris@13
|
639 public function usePcntl()
|
Chris@13
|
640 {
|
Chris@13
|
641 return isset($this->usePcntl) ? ($this->hasPcntl && $this->usePcntl) : $this->hasPcntl;
|
Chris@13
|
642 }
|
Chris@13
|
643
|
Chris@13
|
644 /**
|
Chris@13
|
645 * Enable or disable strict requirement of semicolons.
|
Chris@13
|
646 *
|
Chris@13
|
647 * @see self::requireSemicolons()
|
Chris@13
|
648 *
|
Chris@13
|
649 * @param bool $requireSemicolons
|
Chris@13
|
650 */
|
Chris@13
|
651 public function setRequireSemicolons($requireSemicolons)
|
Chris@13
|
652 {
|
Chris@13
|
653 $this->requireSemicolons = (bool) $requireSemicolons;
|
Chris@13
|
654 }
|
Chris@13
|
655
|
Chris@13
|
656 /**
|
Chris@13
|
657 * Check whether to require semicolons on all statements.
|
Chris@13
|
658 *
|
Chris@13
|
659 * By default, PsySH will automatically insert semicolons at the end of
|
Chris@13
|
660 * statements if they're missing. To strictly require semicolons, set
|
Chris@13
|
661 * `requireSemicolons` to true.
|
Chris@13
|
662 *
|
Chris@13
|
663 * @return bool
|
Chris@13
|
664 */
|
Chris@13
|
665 public function requireSemicolons()
|
Chris@13
|
666 {
|
Chris@13
|
667 return $this->requireSemicolons;
|
Chris@13
|
668 }
|
Chris@13
|
669
|
Chris@13
|
670 /**
|
Chris@13
|
671 * Enable or disable Unicode in PsySH specific output.
|
Chris@13
|
672 *
|
Chris@13
|
673 * Note that this does not disable Unicode output in general, it just makes
|
Chris@13
|
674 * it so PsySH won't output any itself.
|
Chris@13
|
675 *
|
Chris@13
|
676 * @param bool $useUnicode
|
Chris@13
|
677 */
|
Chris@13
|
678 public function setUseUnicode($useUnicode)
|
Chris@13
|
679 {
|
Chris@13
|
680 $this->useUnicode = (bool) $useUnicode;
|
Chris@13
|
681 }
|
Chris@13
|
682
|
Chris@13
|
683 /**
|
Chris@13
|
684 * Check whether to use Unicode in PsySH specific output.
|
Chris@13
|
685 *
|
Chris@13
|
686 * Note that this does not disable Unicode output in general, it just makes
|
Chris@13
|
687 * it so PsySH won't output any itself.
|
Chris@13
|
688 *
|
Chris@13
|
689 * @return bool
|
Chris@13
|
690 */
|
Chris@13
|
691 public function useUnicode()
|
Chris@13
|
692 {
|
Chris@13
|
693 if (isset($this->useUnicode)) {
|
Chris@13
|
694 return $this->useUnicode;
|
Chris@13
|
695 }
|
Chris@13
|
696
|
Chris@13
|
697 // @todo detect `chsh` != 65001 on Windows and return false
|
Chris@13
|
698 return true;
|
Chris@13
|
699 }
|
Chris@13
|
700
|
Chris@13
|
701 /**
|
Chris@13
|
702 * Set the error logging level.
|
Chris@13
|
703 *
|
Chris@13
|
704 * @see self::errorLoggingLevel
|
Chris@13
|
705 *
|
Chris@13
|
706 * @param bool $errorLoggingLevel
|
Chris@13
|
707 */
|
Chris@13
|
708 public function setErrorLoggingLevel($errorLoggingLevel)
|
Chris@13
|
709 {
|
Chris@13
|
710 $this->errorLoggingLevel = (E_ALL | E_STRICT) & $errorLoggingLevel;
|
Chris@13
|
711 }
|
Chris@13
|
712
|
Chris@13
|
713 /**
|
Chris@13
|
714 * Get the current error logging level.
|
Chris@13
|
715 *
|
Chris@13
|
716 * By default, PsySH will automatically log all errors, regardless of the
|
Chris@13
|
717 * current `error_reporting` level. Additionally, if the `error_reporting`
|
Chris@13
|
718 * level warrants, an ErrorException will be thrown.
|
Chris@13
|
719 *
|
Chris@13
|
720 * Set `errorLoggingLevel` to 0 to prevent logging non-thrown errors. Set it
|
Chris@13
|
721 * to any valid error_reporting value to log only errors which match that
|
Chris@13
|
722 * level.
|
Chris@13
|
723 *
|
Chris@13
|
724 * http://php.net/manual/en/function.error-reporting.php
|
Chris@13
|
725 *
|
Chris@13
|
726 * @return int
|
Chris@13
|
727 */
|
Chris@13
|
728 public function errorLoggingLevel()
|
Chris@13
|
729 {
|
Chris@13
|
730 return $this->errorLoggingLevel;
|
Chris@13
|
731 }
|
Chris@13
|
732
|
Chris@13
|
733 /**
|
Chris@13
|
734 * Set a CodeCleaner service instance.
|
Chris@13
|
735 *
|
Chris@13
|
736 * @param CodeCleaner $cleaner
|
Chris@13
|
737 */
|
Chris@13
|
738 public function setCodeCleaner(CodeCleaner $cleaner)
|
Chris@13
|
739 {
|
Chris@13
|
740 $this->cleaner = $cleaner;
|
Chris@13
|
741 }
|
Chris@13
|
742
|
Chris@13
|
743 /**
|
Chris@13
|
744 * Get a CodeCleaner service instance.
|
Chris@13
|
745 *
|
Chris@13
|
746 * If none has been explicitly defined, this will create a new instance.
|
Chris@13
|
747 *
|
Chris@13
|
748 * @return CodeCleaner
|
Chris@13
|
749 */
|
Chris@13
|
750 public function getCodeCleaner()
|
Chris@13
|
751 {
|
Chris@13
|
752 if (!isset($this->cleaner)) {
|
Chris@13
|
753 $this->cleaner = new CodeCleaner();
|
Chris@13
|
754 }
|
Chris@13
|
755
|
Chris@13
|
756 return $this->cleaner;
|
Chris@13
|
757 }
|
Chris@13
|
758
|
Chris@13
|
759 /**
|
Chris@13
|
760 * Enable or disable tab completion.
|
Chris@13
|
761 *
|
Chris@13
|
762 * @param bool $useTabCompletion
|
Chris@13
|
763 */
|
Chris@13
|
764 public function setUseTabCompletion($useTabCompletion)
|
Chris@13
|
765 {
|
Chris@13
|
766 $this->useTabCompletion = (bool) $useTabCompletion;
|
Chris@13
|
767 }
|
Chris@13
|
768
|
Chris@13
|
769 /**
|
Chris@13
|
770 * @deprecated Call `setUseTabCompletion` instead
|
Chris@13
|
771 *
|
Chris@13
|
772 * @param bool $useTabCompletion
|
Chris@13
|
773 */
|
Chris@13
|
774 public function setTabCompletion($useTabCompletion)
|
Chris@13
|
775 {
|
Chris@13
|
776 $this->setUseTabCompletion($useTabCompletion);
|
Chris@13
|
777 }
|
Chris@13
|
778
|
Chris@13
|
779 /**
|
Chris@13
|
780 * Check whether to use tab completion.
|
Chris@13
|
781 *
|
Chris@13
|
782 * If `setUseTabCompletion` has been set to true, but readline is not
|
Chris@13
|
783 * actually available, this will return false.
|
Chris@13
|
784 *
|
Chris@13
|
785 * @return bool True if the current Shell should use tab completion
|
Chris@13
|
786 */
|
Chris@13
|
787 public function useTabCompletion()
|
Chris@13
|
788 {
|
Chris@13
|
789 return isset($this->useTabCompletion) ? ($this->hasReadline && $this->useTabCompletion) : $this->hasReadline;
|
Chris@13
|
790 }
|
Chris@13
|
791
|
Chris@13
|
792 /**
|
Chris@13
|
793 * @deprecated Call `useTabCompletion` instead
|
Chris@13
|
794 *
|
Chris@13
|
795 * @return bool
|
Chris@13
|
796 */
|
Chris@13
|
797 public function getTabCompletion()
|
Chris@13
|
798 {
|
Chris@13
|
799 return $this->useTabCompletion();
|
Chris@13
|
800 }
|
Chris@13
|
801
|
Chris@13
|
802 /**
|
Chris@13
|
803 * Set the Shell Output service.
|
Chris@13
|
804 *
|
Chris@13
|
805 * @param ShellOutput $output
|
Chris@13
|
806 */
|
Chris@13
|
807 public function setOutput(ShellOutput $output)
|
Chris@13
|
808 {
|
Chris@13
|
809 $this->output = $output;
|
Chris@13
|
810 }
|
Chris@13
|
811
|
Chris@13
|
812 /**
|
Chris@13
|
813 * Get a Shell Output service instance.
|
Chris@13
|
814 *
|
Chris@13
|
815 * If none has been explicitly provided, this will create a new instance
|
Chris@13
|
816 * with VERBOSITY_NORMAL and the output page supplied by self::getPager
|
Chris@13
|
817 *
|
Chris@13
|
818 * @see self::getPager
|
Chris@13
|
819 *
|
Chris@13
|
820 * @return ShellOutput
|
Chris@13
|
821 */
|
Chris@13
|
822 public function getOutput()
|
Chris@13
|
823 {
|
Chris@13
|
824 if (!isset($this->output)) {
|
Chris@13
|
825 $this->output = new ShellOutput(
|
Chris@13
|
826 ShellOutput::VERBOSITY_NORMAL,
|
Chris@13
|
827 $this->getOutputDecorated(),
|
Chris@13
|
828 null,
|
Chris@13
|
829 $this->getPager()
|
Chris@13
|
830 );
|
Chris@13
|
831 }
|
Chris@13
|
832
|
Chris@13
|
833 return $this->output;
|
Chris@13
|
834 }
|
Chris@13
|
835
|
Chris@13
|
836 /**
|
Chris@13
|
837 * Get the decoration (i.e. color) setting for the Shell Output service.
|
Chris@13
|
838 *
|
Chris@13
|
839 * @return null|bool 3-state boolean corresponding to the current color mode
|
Chris@13
|
840 */
|
Chris@13
|
841 public function getOutputDecorated()
|
Chris@13
|
842 {
|
Chris@13
|
843 if ($this->colorMode() === self::COLOR_MODE_AUTO) {
|
Chris@13
|
844 return;
|
Chris@13
|
845 } elseif ($this->colorMode() === self::COLOR_MODE_FORCED) {
|
Chris@13
|
846 return true;
|
Chris@13
|
847 } elseif ($this->colorMode() === self::COLOR_MODE_DISABLED) {
|
Chris@13
|
848 return false;
|
Chris@13
|
849 }
|
Chris@13
|
850 }
|
Chris@13
|
851
|
Chris@13
|
852 /**
|
Chris@13
|
853 * Set the OutputPager service.
|
Chris@13
|
854 *
|
Chris@13
|
855 * If a string is supplied, a ProcOutputPager will be used which shells out
|
Chris@13
|
856 * to the specified command.
|
Chris@13
|
857 *
|
Chris@13
|
858 * @throws \InvalidArgumentException if $pager is not a string or OutputPager instance
|
Chris@13
|
859 *
|
Chris@13
|
860 * @param string|OutputPager $pager
|
Chris@13
|
861 */
|
Chris@13
|
862 public function setPager($pager)
|
Chris@13
|
863 {
|
Chris@17
|
864 if ($pager && !\is_string($pager) && !$pager instanceof OutputPager) {
|
Chris@13
|
865 throw new \InvalidArgumentException('Unexpected pager instance');
|
Chris@13
|
866 }
|
Chris@13
|
867
|
Chris@13
|
868 $this->pager = $pager;
|
Chris@13
|
869 }
|
Chris@13
|
870
|
Chris@13
|
871 /**
|
Chris@13
|
872 * Get an OutputPager instance or a command for an external Proc pager.
|
Chris@13
|
873 *
|
Chris@13
|
874 * If no Pager has been explicitly provided, and Pcntl is available, this
|
Chris@13
|
875 * will default to `cli.pager` ini value, falling back to `which less`.
|
Chris@13
|
876 *
|
Chris@13
|
877 * @return string|OutputPager
|
Chris@13
|
878 */
|
Chris@13
|
879 public function getPager()
|
Chris@13
|
880 {
|
Chris@13
|
881 if (!isset($this->pager) && $this->usePcntl()) {
|
Chris@17
|
882 if ($pager = \ini_get('cli.pager')) {
|
Chris@16
|
883 // use the default pager
|
Chris@13
|
884 $this->pager = $pager;
|
Chris@17
|
885 } elseif ($less = \exec('which less 2>/dev/null')) {
|
Chris@13
|
886 // check for the presence of less...
|
Chris@13
|
887 $this->pager = $less . ' -R -S -F -X';
|
Chris@13
|
888 }
|
Chris@13
|
889 }
|
Chris@13
|
890
|
Chris@13
|
891 return $this->pager;
|
Chris@13
|
892 }
|
Chris@13
|
893
|
Chris@13
|
894 /**
|
Chris@13
|
895 * Set the Shell AutoCompleter service.
|
Chris@13
|
896 *
|
Chris@13
|
897 * @param AutoCompleter $autoCompleter
|
Chris@13
|
898 */
|
Chris@13
|
899 public function setAutoCompleter(AutoCompleter $autoCompleter)
|
Chris@13
|
900 {
|
Chris@13
|
901 $this->autoCompleter = $autoCompleter;
|
Chris@13
|
902 }
|
Chris@13
|
903
|
Chris@13
|
904 /**
|
Chris@13
|
905 * Get an AutoCompleter service instance.
|
Chris@13
|
906 *
|
Chris@13
|
907 * @return AutoCompleter
|
Chris@13
|
908 */
|
Chris@13
|
909 public function getAutoCompleter()
|
Chris@13
|
910 {
|
Chris@13
|
911 if (!isset($this->autoCompleter)) {
|
Chris@13
|
912 $this->autoCompleter = new AutoCompleter();
|
Chris@13
|
913 }
|
Chris@13
|
914
|
Chris@13
|
915 return $this->autoCompleter;
|
Chris@13
|
916 }
|
Chris@13
|
917
|
Chris@13
|
918 /**
|
Chris@13
|
919 * @deprecated Nothing should be using this anymore
|
Chris@13
|
920 *
|
Chris@13
|
921 * @return array
|
Chris@13
|
922 */
|
Chris@13
|
923 public function getTabCompletionMatchers()
|
Chris@13
|
924 {
|
Chris@13
|
925 return [];
|
Chris@13
|
926 }
|
Chris@13
|
927
|
Chris@13
|
928 /**
|
Chris@13
|
929 * Add tab completion matchers to the AutoCompleter.
|
Chris@13
|
930 *
|
Chris@13
|
931 * This will buffer new matchers in the event that the Shell has not yet
|
Chris@13
|
932 * been instantiated. This allows the user to specify matchers in their
|
Chris@13
|
933 * config rc file, despite the fact that their file is needed in the Shell
|
Chris@13
|
934 * constructor.
|
Chris@13
|
935 *
|
Chris@13
|
936 * @param array $matchers
|
Chris@13
|
937 */
|
Chris@13
|
938 public function addMatchers(array $matchers)
|
Chris@13
|
939 {
|
Chris@17
|
940 $this->newMatchers = \array_merge($this->newMatchers, $matchers);
|
Chris@13
|
941 if (isset($this->shell)) {
|
Chris@13
|
942 $this->doAddMatchers();
|
Chris@13
|
943 }
|
Chris@13
|
944 }
|
Chris@13
|
945
|
Chris@13
|
946 /**
|
Chris@13
|
947 * Internal method for adding tab completion matchers. This will set any new
|
Chris@13
|
948 * matchers once a Shell is available.
|
Chris@13
|
949 */
|
Chris@13
|
950 private function doAddMatchers()
|
Chris@13
|
951 {
|
Chris@13
|
952 if (!empty($this->newMatchers)) {
|
Chris@13
|
953 $this->shell->addMatchers($this->newMatchers);
|
Chris@13
|
954 $this->newMatchers = [];
|
Chris@13
|
955 }
|
Chris@13
|
956 }
|
Chris@13
|
957
|
Chris@13
|
958 /**
|
Chris@13
|
959 * @deprecated Use `addMatchers` instead
|
Chris@13
|
960 *
|
Chris@13
|
961 * @param array $matchers
|
Chris@13
|
962 */
|
Chris@13
|
963 public function addTabCompletionMatchers(array $matchers)
|
Chris@13
|
964 {
|
Chris@13
|
965 $this->addMatchers($matchers);
|
Chris@13
|
966 }
|
Chris@13
|
967
|
Chris@13
|
968 /**
|
Chris@13
|
969 * Add commands to the Shell.
|
Chris@13
|
970 *
|
Chris@13
|
971 * This will buffer new commands in the event that the Shell has not yet
|
Chris@13
|
972 * been instantiated. This allows the user to specify commands in their
|
Chris@13
|
973 * config rc file, despite the fact that their file is needed in the Shell
|
Chris@13
|
974 * constructor.
|
Chris@13
|
975 *
|
Chris@13
|
976 * @param array $commands
|
Chris@13
|
977 */
|
Chris@13
|
978 public function addCommands(array $commands)
|
Chris@13
|
979 {
|
Chris@17
|
980 $this->newCommands = \array_merge($this->newCommands, $commands);
|
Chris@13
|
981 if (isset($this->shell)) {
|
Chris@13
|
982 $this->doAddCommands();
|
Chris@13
|
983 }
|
Chris@13
|
984 }
|
Chris@13
|
985
|
Chris@13
|
986 /**
|
Chris@13
|
987 * Internal method for adding commands. This will set any new commands once
|
Chris@13
|
988 * a Shell is available.
|
Chris@13
|
989 */
|
Chris@13
|
990 private function doAddCommands()
|
Chris@13
|
991 {
|
Chris@13
|
992 if (!empty($this->newCommands)) {
|
Chris@13
|
993 $this->shell->addCommands($this->newCommands);
|
Chris@13
|
994 $this->newCommands = [];
|
Chris@13
|
995 }
|
Chris@13
|
996 }
|
Chris@13
|
997
|
Chris@13
|
998 /**
|
Chris@13
|
999 * Set the Shell backreference and add any new commands to the Shell.
|
Chris@13
|
1000 *
|
Chris@13
|
1001 * @param Shell $shell
|
Chris@13
|
1002 */
|
Chris@13
|
1003 public function setShell(Shell $shell)
|
Chris@13
|
1004 {
|
Chris@13
|
1005 $this->shell = $shell;
|
Chris@13
|
1006 $this->doAddCommands();
|
Chris@13
|
1007 $this->doAddMatchers();
|
Chris@13
|
1008 }
|
Chris@13
|
1009
|
Chris@13
|
1010 /**
|
Chris@13
|
1011 * Set the PHP manual database file.
|
Chris@13
|
1012 *
|
Chris@13
|
1013 * This file should be an SQLite database generated from the phpdoc source
|
Chris@13
|
1014 * with the `bin/build_manual` script.
|
Chris@13
|
1015 *
|
Chris@13
|
1016 * @param string $filename
|
Chris@13
|
1017 */
|
Chris@13
|
1018 public function setManualDbFile($filename)
|
Chris@13
|
1019 {
|
Chris@13
|
1020 $this->manualDbFile = (string) $filename;
|
Chris@13
|
1021 }
|
Chris@13
|
1022
|
Chris@13
|
1023 /**
|
Chris@13
|
1024 * Get the current PHP manual database file.
|
Chris@13
|
1025 *
|
Chris@13
|
1026 * @return string Default: '~/.local/share/psysh/php_manual.sqlite'
|
Chris@13
|
1027 */
|
Chris@13
|
1028 public function getManualDbFile()
|
Chris@13
|
1029 {
|
Chris@13
|
1030 if (isset($this->manualDbFile)) {
|
Chris@13
|
1031 return $this->manualDbFile;
|
Chris@13
|
1032 }
|
Chris@13
|
1033
|
Chris@13
|
1034 $files = ConfigPaths::getDataFiles(['php_manual.sqlite'], $this->dataDir);
|
Chris@13
|
1035 if (!empty($files)) {
|
Chris@17
|
1036 if ($this->warnOnMultipleConfigs && \count($files) > 1) {
|
Chris@17
|
1037 $msg = \sprintf('Multiple manual database files found: %s. Using %s', \implode($files, ', '), $files[0]);
|
Chris@17
|
1038 \trigger_error($msg, E_USER_NOTICE);
|
Chris@13
|
1039 }
|
Chris@13
|
1040
|
Chris@13
|
1041 return $this->manualDbFile = $files[0];
|
Chris@13
|
1042 }
|
Chris@13
|
1043 }
|
Chris@13
|
1044
|
Chris@13
|
1045 /**
|
Chris@13
|
1046 * Get a PHP manual database connection.
|
Chris@13
|
1047 *
|
Chris@13
|
1048 * @return \PDO
|
Chris@13
|
1049 */
|
Chris@13
|
1050 public function getManualDb()
|
Chris@13
|
1051 {
|
Chris@13
|
1052 if (!isset($this->manualDb)) {
|
Chris@13
|
1053 $dbFile = $this->getManualDbFile();
|
Chris@17
|
1054 if (\is_file($dbFile)) {
|
Chris@13
|
1055 try {
|
Chris@13
|
1056 $this->manualDb = new \PDO('sqlite:' . $dbFile);
|
Chris@13
|
1057 } catch (\PDOException $e) {
|
Chris@13
|
1058 if ($e->getMessage() === 'could not find driver') {
|
Chris@13
|
1059 throw new RuntimeException('SQLite PDO driver not found', 0, $e);
|
Chris@13
|
1060 } else {
|
Chris@13
|
1061 throw $e;
|
Chris@13
|
1062 }
|
Chris@13
|
1063 }
|
Chris@13
|
1064 }
|
Chris@13
|
1065 }
|
Chris@13
|
1066
|
Chris@13
|
1067 return $this->manualDb;
|
Chris@13
|
1068 }
|
Chris@13
|
1069
|
Chris@13
|
1070 /**
|
Chris@13
|
1071 * Add an array of casters definitions.
|
Chris@13
|
1072 *
|
Chris@13
|
1073 * @param array $casters
|
Chris@13
|
1074 */
|
Chris@13
|
1075 public function addCasters(array $casters)
|
Chris@13
|
1076 {
|
Chris@13
|
1077 $this->getPresenter()->addCasters($casters);
|
Chris@13
|
1078 }
|
Chris@13
|
1079
|
Chris@13
|
1080 /**
|
Chris@13
|
1081 * Get the Presenter service.
|
Chris@13
|
1082 *
|
Chris@13
|
1083 * @return Presenter
|
Chris@13
|
1084 */
|
Chris@13
|
1085 public function getPresenter()
|
Chris@13
|
1086 {
|
Chris@13
|
1087 if (!isset($this->presenter)) {
|
Chris@13
|
1088 $this->presenter = new Presenter($this->getOutput()->getFormatter(), $this->forceArrayIndexes());
|
Chris@13
|
1089 }
|
Chris@13
|
1090
|
Chris@13
|
1091 return $this->presenter;
|
Chris@13
|
1092 }
|
Chris@13
|
1093
|
Chris@13
|
1094 /**
|
Chris@13
|
1095 * Enable or disable warnings on multiple configuration or data files.
|
Chris@13
|
1096 *
|
Chris@13
|
1097 * @see self::warnOnMultipleConfigs()
|
Chris@13
|
1098 *
|
Chris@13
|
1099 * @param bool $warnOnMultipleConfigs
|
Chris@13
|
1100 */
|
Chris@13
|
1101 public function setWarnOnMultipleConfigs($warnOnMultipleConfigs)
|
Chris@13
|
1102 {
|
Chris@13
|
1103 $this->warnOnMultipleConfigs = (bool) $warnOnMultipleConfigs;
|
Chris@13
|
1104 }
|
Chris@13
|
1105
|
Chris@13
|
1106 /**
|
Chris@13
|
1107 * Check whether to warn on multiple configuration or data files.
|
Chris@13
|
1108 *
|
Chris@13
|
1109 * By default, PsySH will use the file with highest precedence, and will
|
Chris@13
|
1110 * silently ignore all others. With this enabled, a warning will be emitted
|
Chris@13
|
1111 * (but not an exception thrown) if multiple configuration or data files
|
Chris@13
|
1112 * are found.
|
Chris@13
|
1113 *
|
Chris@13
|
1114 * This will default to true in a future release, but is false for now.
|
Chris@13
|
1115 *
|
Chris@13
|
1116 * @return bool
|
Chris@13
|
1117 */
|
Chris@13
|
1118 public function warnOnMultipleConfigs()
|
Chris@13
|
1119 {
|
Chris@13
|
1120 return $this->warnOnMultipleConfigs;
|
Chris@13
|
1121 }
|
Chris@13
|
1122
|
Chris@13
|
1123 /**
|
Chris@13
|
1124 * Set the current color mode.
|
Chris@13
|
1125 *
|
Chris@13
|
1126 * @param string $colorMode
|
Chris@13
|
1127 */
|
Chris@13
|
1128 public function setColorMode($colorMode)
|
Chris@13
|
1129 {
|
Chris@13
|
1130 $validColorModes = [
|
Chris@13
|
1131 self::COLOR_MODE_AUTO,
|
Chris@13
|
1132 self::COLOR_MODE_FORCED,
|
Chris@13
|
1133 self::COLOR_MODE_DISABLED,
|
Chris@13
|
1134 ];
|
Chris@13
|
1135
|
Chris@17
|
1136 if (\in_array($colorMode, $validColorModes)) {
|
Chris@13
|
1137 $this->colorMode = $colorMode;
|
Chris@13
|
1138 } else {
|
Chris@13
|
1139 throw new \InvalidArgumentException('invalid color mode: ' . $colorMode);
|
Chris@13
|
1140 }
|
Chris@13
|
1141 }
|
Chris@13
|
1142
|
Chris@13
|
1143 /**
|
Chris@13
|
1144 * Get the current color mode.
|
Chris@13
|
1145 *
|
Chris@13
|
1146 * @return string
|
Chris@13
|
1147 */
|
Chris@13
|
1148 public function colorMode()
|
Chris@13
|
1149 {
|
Chris@13
|
1150 return $this->colorMode;
|
Chris@13
|
1151 }
|
Chris@13
|
1152
|
Chris@13
|
1153 /**
|
Chris@13
|
1154 * Set an update checker service instance.
|
Chris@13
|
1155 *
|
Chris@13
|
1156 * @param Checker $checker
|
Chris@13
|
1157 */
|
Chris@13
|
1158 public function setChecker(Checker $checker)
|
Chris@13
|
1159 {
|
Chris@13
|
1160 $this->checker = $checker;
|
Chris@13
|
1161 }
|
Chris@13
|
1162
|
Chris@13
|
1163 /**
|
Chris@13
|
1164 * Get an update checker service instance.
|
Chris@13
|
1165 *
|
Chris@13
|
1166 * If none has been explicitly defined, this will create a new instance.
|
Chris@13
|
1167 *
|
Chris@13
|
1168 * @return Checker
|
Chris@13
|
1169 */
|
Chris@13
|
1170 public function getChecker()
|
Chris@13
|
1171 {
|
Chris@13
|
1172 if (!isset($this->checker)) {
|
Chris@13
|
1173 $interval = $this->getUpdateCheck();
|
Chris@13
|
1174 switch ($interval) {
|
Chris@13
|
1175 case Checker::ALWAYS:
|
Chris@13
|
1176 $this->checker = new GitHubChecker();
|
Chris@13
|
1177 break;
|
Chris@13
|
1178
|
Chris@13
|
1179 case Checker::DAILY:
|
Chris@13
|
1180 case Checker::WEEKLY:
|
Chris@13
|
1181 case Checker::MONTHLY:
|
Chris@13
|
1182 $checkFile = $this->getUpdateCheckCacheFile();
|
Chris@13
|
1183 if ($checkFile === false) {
|
Chris@13
|
1184 $this->checker = new NoopChecker();
|
Chris@13
|
1185 } else {
|
Chris@13
|
1186 $this->checker = new IntervalChecker($checkFile, $interval);
|
Chris@13
|
1187 }
|
Chris@13
|
1188 break;
|
Chris@13
|
1189
|
Chris@13
|
1190 case Checker::NEVER:
|
Chris@13
|
1191 $this->checker = new NoopChecker();
|
Chris@13
|
1192 break;
|
Chris@13
|
1193 }
|
Chris@13
|
1194 }
|
Chris@13
|
1195
|
Chris@13
|
1196 return $this->checker;
|
Chris@13
|
1197 }
|
Chris@13
|
1198
|
Chris@13
|
1199 /**
|
Chris@13
|
1200 * Get the current update check interval.
|
Chris@13
|
1201 *
|
Chris@13
|
1202 * One of 'always', 'daily', 'weekly', 'monthly' or 'never'. If none is
|
Chris@13
|
1203 * explicitly set, default to 'weekly'.
|
Chris@13
|
1204 *
|
Chris@13
|
1205 * @return string
|
Chris@13
|
1206 */
|
Chris@13
|
1207 public function getUpdateCheck()
|
Chris@13
|
1208 {
|
Chris@13
|
1209 return isset($this->updateCheck) ? $this->updateCheck : Checker::WEEKLY;
|
Chris@13
|
1210 }
|
Chris@13
|
1211
|
Chris@13
|
1212 /**
|
Chris@13
|
1213 * Set the update check interval.
|
Chris@13
|
1214 *
|
Chris@13
|
1215 * @throws \InvalidArgumentDescription if the update check interval is unknown
|
Chris@13
|
1216 *
|
Chris@13
|
1217 * @param string $interval
|
Chris@13
|
1218 */
|
Chris@13
|
1219 public function setUpdateCheck($interval)
|
Chris@13
|
1220 {
|
Chris@13
|
1221 $validIntervals = [
|
Chris@13
|
1222 Checker::ALWAYS,
|
Chris@13
|
1223 Checker::DAILY,
|
Chris@13
|
1224 Checker::WEEKLY,
|
Chris@13
|
1225 Checker::MONTHLY,
|
Chris@13
|
1226 Checker::NEVER,
|
Chris@13
|
1227 ];
|
Chris@13
|
1228
|
Chris@17
|
1229 if (!\in_array($interval, $validIntervals)) {
|
Chris@13
|
1230 throw new \InvalidArgumentException('invalid update check interval: ' . $interval);
|
Chris@13
|
1231 }
|
Chris@13
|
1232
|
Chris@13
|
1233 $this->updateCheck = $interval;
|
Chris@13
|
1234 }
|
Chris@13
|
1235
|
Chris@13
|
1236 /**
|
Chris@13
|
1237 * Get a cache file path for the update checker.
|
Chris@13
|
1238 *
|
Chris@13
|
1239 * @return string|false Return false if config file/directory is not writable
|
Chris@13
|
1240 */
|
Chris@13
|
1241 public function getUpdateCheckCacheFile()
|
Chris@13
|
1242 {
|
Chris@13
|
1243 $dir = $this->configDir ?: ConfigPaths::getCurrentConfigDir();
|
Chris@13
|
1244
|
Chris@13
|
1245 return ConfigPaths::touchFileWithMkdir($dir . '/update_check.json');
|
Chris@13
|
1246 }
|
Chris@13
|
1247
|
Chris@13
|
1248 /**
|
Chris@13
|
1249 * Set the startup message.
|
Chris@13
|
1250 *
|
Chris@13
|
1251 * @param string $message
|
Chris@13
|
1252 */
|
Chris@13
|
1253 public function setStartupMessage($message)
|
Chris@13
|
1254 {
|
Chris@13
|
1255 $this->startupMessage = $message;
|
Chris@13
|
1256 }
|
Chris@13
|
1257
|
Chris@13
|
1258 /**
|
Chris@13
|
1259 * Get the startup message.
|
Chris@13
|
1260 *
|
Chris@13
|
1261 * @return string|null
|
Chris@13
|
1262 */
|
Chris@13
|
1263 public function getStartupMessage()
|
Chris@13
|
1264 {
|
Chris@13
|
1265 return $this->startupMessage;
|
Chris@13
|
1266 }
|
Chris@13
|
1267
|
Chris@13
|
1268 /**
|
Chris@13
|
1269 * Set the prompt.
|
Chris@13
|
1270 *
|
Chris@13
|
1271 * @param string $prompt
|
Chris@13
|
1272 */
|
Chris@13
|
1273 public function setPrompt($prompt)
|
Chris@13
|
1274 {
|
Chris@13
|
1275 $this->prompt = $prompt;
|
Chris@13
|
1276 }
|
Chris@13
|
1277
|
Chris@13
|
1278 /**
|
Chris@13
|
1279 * Get the prompt.
|
Chris@13
|
1280 *
|
Chris@13
|
1281 * @return string
|
Chris@13
|
1282 */
|
Chris@13
|
1283 public function getPrompt()
|
Chris@13
|
1284 {
|
Chris@13
|
1285 return $this->prompt;
|
Chris@13
|
1286 }
|
Chris@13
|
1287
|
Chris@13
|
1288 /**
|
Chris@13
|
1289 * Get the force array indexes.
|
Chris@13
|
1290 *
|
Chris@13
|
1291 * @return bool
|
Chris@13
|
1292 */
|
Chris@13
|
1293 public function forceArrayIndexes()
|
Chris@13
|
1294 {
|
Chris@13
|
1295 return $this->forceArrayIndexes;
|
Chris@13
|
1296 }
|
Chris@13
|
1297
|
Chris@13
|
1298 /**
|
Chris@13
|
1299 * Set the force array indexes.
|
Chris@13
|
1300 *
|
Chris@13
|
1301 * @param bool $forceArrayIndexes
|
Chris@13
|
1302 */
|
Chris@13
|
1303 public function setForceArrayIndexes($forceArrayIndexes)
|
Chris@13
|
1304 {
|
Chris@13
|
1305 $this->forceArrayIndexes = $forceArrayIndexes;
|
Chris@13
|
1306 }
|
Chris@13
|
1307 }
|