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