Chris@0: setColorMode(self::COLOR_MODE_AUTO); Chris@0: Chris@0: // explicit configFile option Chris@0: if (isset($config['configFile'])) { Chris@0: $this->configFile = $config['configFile']; Chris@0: } elseif ($configFile = getenv('PSYSH_CONFIG')) { Chris@0: $this->configFile = $configFile; Chris@0: } Chris@0: Chris@0: // legacy baseDir option Chris@0: if (isset($config['baseDir'])) { Chris@0: $msg = "The 'baseDir' configuration option is deprecated; " . Chris@0: "please specify 'configDir' and 'dataDir' options instead"; Chris@0: throw new DeprecatedException($msg); Chris@0: } Chris@0: Chris@0: unset($config['configFile'], $config['baseDir']); Chris@0: Chris@0: // go go gadget, config! Chris@0: $this->loadConfig($config); Chris@0: $this->init(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Initialize the configuration. Chris@0: * Chris@0: * This checks for the presence of Readline and Pcntl extensions. Chris@0: * Chris@0: * If a config file is available, it will be loaded and merged with the current config. Chris@0: * Chris@0: * If no custom config file was specified and a local project config file Chris@0: * is available, it will be loaded and merged with the current config. Chris@0: */ Chris@0: public function init() Chris@0: { Chris@0: // feature detection Chris@0: $this->hasReadline = function_exists('readline'); Chris@0: $this->hasPcntl = function_exists('pcntl_signal') && function_exists('posix_getpid'); Chris@0: Chris@0: if ($configFile = $this->getConfigFile()) { Chris@0: $this->loadConfigFile($configFile); Chris@0: } Chris@0: Chris@0: if (!$this->configFile && $localConfig = $this->getLocalConfigFile()) { Chris@0: $this->loadConfigFile($localConfig); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get the current PsySH config file. Chris@0: * Chris@0: * If a `configFile` option was passed to the Configuration constructor, Chris@0: * this file will be returned. If not, all possible config directories will Chris@0: * be searched, and the first `config.php` or `rc.php` file which exists Chris@0: * will be returned. Chris@0: * Chris@0: * If you're trying to decide where to put your config file, pick Chris@0: * Chris@0: * ~/.config/psysh/config.php Chris@0: * Chris@0: * @return string Chris@0: */ Chris@0: public function getConfigFile() Chris@0: { Chris@0: if (isset($this->configFile)) { Chris@0: return $this->configFile; Chris@0: } Chris@0: Chris@0: $files = ConfigPaths::getConfigFiles(['config.php', 'rc.php'], $this->configDir); Chris@0: Chris@0: if (!empty($files)) { Chris@0: if ($this->warnOnMultipleConfigs && count($files) > 1) { Chris@0: $msg = sprintf('Multiple configuration files found: %s. Using %s', implode($files, ', '), $files[0]); Chris@0: trigger_error($msg, E_USER_NOTICE); Chris@0: } Chris@0: Chris@0: return $files[0]; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get the local PsySH config file. Chris@0: * Chris@0: * Searches for a project specific config file `.psysh.php` in the current Chris@0: * working directory. Chris@0: * Chris@0: * @return string Chris@0: */ Chris@0: public function getLocalConfigFile() Chris@0: { Chris@0: $localConfig = getcwd() . '/.psysh.php'; Chris@0: Chris@0: if (@is_file($localConfig)) { Chris@0: return $localConfig; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Load configuration values from an array of options. Chris@0: * Chris@0: * @param array $options Chris@0: */ Chris@0: public function loadConfig(array $options) Chris@0: { Chris@0: foreach (self::$AVAILABLE_OPTIONS as $option) { Chris@0: if (isset($options[$option])) { Chris@0: $method = 'set' . ucfirst($option); Chris@0: $this->$method($options[$option]); Chris@0: } Chris@0: } Chris@0: Chris@0: // legacy `tabCompletion` option Chris@0: if (isset($options['tabCompletion'])) { Chris@0: $msg = '`tabCompletion` is deprecated; use `useTabCompletion` instead.'; Chris@0: @trigger_error($msg, E_USER_DEPRECATED); Chris@0: Chris@0: $this->setUseTabCompletion($options['tabCompletion']); Chris@0: } Chris@0: Chris@0: foreach (['commands', 'matchers', 'casters'] as $option) { Chris@0: if (isset($options[$option])) { Chris@0: $method = 'add' . ucfirst($option); Chris@0: $this->$method($options[$option]); Chris@0: } Chris@0: } Chris@0: Chris@0: // legacy `tabCompletionMatchers` option Chris@0: if (isset($options['tabCompletionMatchers'])) { Chris@0: $msg = '`tabCompletionMatchers` is deprecated; use `matchers` instead.'; Chris@0: @trigger_error($msg, E_USER_DEPRECATED); Chris@0: Chris@0: $this->addMatchers($options['tabCompletionMatchers']); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Load a configuration file (default: `$HOME/.config/psysh/config.php`). Chris@0: * Chris@0: * This configuration instance will be available to the config file as $config. Chris@0: * The config file may directly manipulate the configuration, or may return Chris@0: * an array of options which will be merged with the current configuration. Chris@0: * Chris@0: * @throws \InvalidArgumentException if the config file returns a non-array result Chris@0: * Chris@0: * @param string $file Chris@0: */ Chris@0: public function loadConfigFile($file) Chris@0: { Chris@0: $__psysh_config_file__ = $file; Chris@0: $load = function ($config) use ($__psysh_config_file__) { Chris@0: $result = require $__psysh_config_file__; Chris@0: if ($result !== 1) { Chris@0: return $result; Chris@0: } Chris@0: }; Chris@0: $result = $load($this); Chris@0: Chris@0: if (!empty($result)) { Chris@0: if (is_array($result)) { Chris@0: $this->loadConfig($result); Chris@0: } else { Chris@0: throw new \InvalidArgumentException('Psy Shell configuration must return an array of options'); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Set files to be included by default at the start of each shell session. Chris@0: * Chris@0: * @param array $includes Chris@0: */ Chris@0: public function setDefaultIncludes(array $includes = []) Chris@0: { Chris@0: $this->defaultIncludes = $includes; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get files to be included by default at the start of each shell session. Chris@0: * Chris@0: * @return array Chris@0: */ Chris@0: public function getDefaultIncludes() Chris@0: { Chris@0: return $this->defaultIncludes ?: []; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Set the shell's config directory location. Chris@0: * Chris@0: * @param string $dir Chris@0: */ Chris@0: public function setConfigDir($dir) Chris@0: { Chris@0: $this->configDir = (string) $dir; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get the current configuration directory, if any is explicitly set. Chris@0: * Chris@0: * @return string Chris@0: */ Chris@0: public function getConfigDir() Chris@0: { Chris@0: return $this->configDir; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Set the shell's data directory location. Chris@0: * Chris@0: * @param string $dir Chris@0: */ Chris@0: public function setDataDir($dir) Chris@0: { Chris@0: $this->dataDir = (string) $dir; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get the current data directory, if any is explicitly set. Chris@0: * Chris@0: * @return string Chris@0: */ Chris@0: public function getDataDir() Chris@0: { Chris@0: return $this->dataDir; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Set the shell's temporary directory location. Chris@0: * Chris@0: * @param string $dir Chris@0: */ Chris@0: public function setRuntimeDir($dir) Chris@0: { Chris@0: $this->runtimeDir = (string) $dir; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get the shell's temporary directory location. Chris@0: * Chris@0: * Defaults to `/psysh` inside the system's temp dir unless explicitly Chris@0: * overridden. Chris@0: * Chris@0: * @return string Chris@0: */ Chris@0: public function getRuntimeDir() Chris@0: { Chris@0: if (!isset($this->runtimeDir)) { Chris@0: $this->runtimeDir = ConfigPaths::getRuntimeDir(); Chris@0: } Chris@0: Chris@0: if (!is_dir($this->runtimeDir)) { Chris@0: mkdir($this->runtimeDir, 0700, true); Chris@0: } Chris@0: Chris@0: return $this->runtimeDir; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Set the readline history file path. Chris@0: * Chris@0: * @param string $file Chris@0: */ Chris@0: public function setHistoryFile($file) Chris@0: { Chris@0: $this->historyFile = ConfigPaths::touchFileWithMkdir($file); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get the readline history file path. Chris@0: * Chris@0: * Defaults to `/history` inside the shell's base config dir unless Chris@0: * explicitly overridden. Chris@0: * Chris@0: * @return string Chris@0: */ Chris@0: public function getHistoryFile() Chris@0: { Chris@0: if (isset($this->historyFile)) { Chris@0: return $this->historyFile; Chris@0: } Chris@0: Chris@0: $files = ConfigPaths::getConfigFiles(['psysh_history', 'history'], $this->configDir); Chris@0: Chris@0: if (!empty($files)) { Chris@0: if ($this->warnOnMultipleConfigs && count($files) > 1) { Chris@0: $msg = sprintf('Multiple history files found: %s. Using %s', implode($files, ', '), $files[0]); Chris@0: trigger_error($msg, E_USER_NOTICE); Chris@0: } Chris@0: Chris@0: $this->setHistoryFile($files[0]); Chris@0: } else { Chris@0: // fallback: create our own history file Chris@0: $dir = $this->configDir ?: ConfigPaths::getCurrentConfigDir(); Chris@0: $this->setHistoryFile($dir . '/psysh_history'); Chris@0: } Chris@0: Chris@0: return $this->historyFile; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Set the readline max history size. Chris@0: * Chris@0: * @param int $value Chris@0: */ Chris@0: public function setHistorySize($value) Chris@0: { Chris@0: $this->historySize = (int) $value; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get the readline max history size. Chris@0: * Chris@0: * @return int Chris@0: */ Chris@0: public function getHistorySize() Chris@0: { Chris@0: return $this->historySize; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets whether readline erases old duplicate history entries. Chris@0: * Chris@0: * @param bool $value Chris@0: */ Chris@0: public function setEraseDuplicates($value) Chris@0: { Chris@0: $this->eraseDuplicates = (bool) $value; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get whether readline erases old duplicate history entries. Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: public function getEraseDuplicates() Chris@0: { Chris@0: return $this->eraseDuplicates; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get a temporary file of type $type for process $pid. Chris@0: * Chris@0: * The file will be created inside the current temporary directory. Chris@0: * Chris@0: * @see self::getRuntimeDir Chris@0: * Chris@0: * @param string $type Chris@0: * @param int $pid Chris@0: * Chris@0: * @return string Temporary file name Chris@0: */ Chris@0: public function getTempFile($type, $pid) Chris@0: { Chris@0: return tempnam($this->getRuntimeDir(), $type . '_' . $pid . '_'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get a filename suitable for a FIFO pipe of $type for process $pid. Chris@0: * Chris@0: * The pipe will be created inside the current temporary directory. Chris@0: * Chris@0: * @param string $type Chris@0: * @param int $pid Chris@0: * Chris@0: * @return string Pipe name Chris@0: */ Chris@0: public function getPipe($type, $pid) Chris@0: { Chris@0: return sprintf('%s/%s_%s', $this->getRuntimeDir(), $type, $pid); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Check whether this PHP instance has Readline available. Chris@0: * Chris@0: * @return bool True if Readline is available Chris@0: */ Chris@0: public function hasReadline() Chris@0: { Chris@0: return $this->hasReadline; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Enable or disable Readline usage. Chris@0: * Chris@0: * @param bool $useReadline Chris@0: */ Chris@0: public function setUseReadline($useReadline) Chris@0: { Chris@0: $this->useReadline = (bool) $useReadline; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Check whether to use Readline. Chris@0: * Chris@0: * If `setUseReadline` as been set to true, but Readline is not actually Chris@0: * available, this will return false. Chris@0: * Chris@0: * @return bool True if the current Shell should use Readline Chris@0: */ Chris@0: public function useReadline() Chris@0: { Chris@0: return isset($this->useReadline) ? ($this->hasReadline && $this->useReadline) : $this->hasReadline; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Set the Psy Shell readline service. Chris@0: * Chris@0: * @param Readline $readline Chris@0: */ Chris@0: public function setReadline(Readline $readline) Chris@0: { Chris@0: $this->readline = $readline; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get the Psy Shell readline service. Chris@0: * Chris@0: * By default, this service uses (in order of preference): Chris@0: * Chris@0: * * GNU Readline Chris@0: * * Libedit Chris@0: * * A transient array-based readline emulation. Chris@0: * Chris@0: * @return Readline Chris@0: */ Chris@0: public function getReadline() Chris@0: { Chris@0: if (!isset($this->readline)) { Chris@0: $className = $this->getReadlineClass(); Chris@0: $this->readline = new $className( Chris@0: $this->getHistoryFile(), Chris@0: $this->getHistorySize(), Chris@0: $this->getEraseDuplicates() Chris@0: ); Chris@0: } Chris@0: Chris@0: return $this->readline; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get the appropriate Readline implementation class name. Chris@0: * Chris@0: * @see self::getReadline Chris@0: * Chris@0: * @return string Chris@0: */ Chris@0: private function getReadlineClass() Chris@0: { Chris@0: if ($this->useReadline()) { Chris@0: if (GNUReadline::isSupported()) { Chris@0: return 'Psy\Readline\GNUReadline'; Chris@0: } elseif (Libedit::isSupported()) { Chris@0: return 'Psy\Readline\Libedit'; Chris@0: } elseif (HoaConsole::isSupported()) { Chris@0: return 'Psy\Readline\HoaConsole'; Chris@0: } Chris@0: } Chris@0: Chris@0: return 'Psy\Readline\Transient'; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Enable or disable bracketed paste. Chris@0: * Chris@0: * Note that this only works with readline (not libedit) integration for now. Chris@0: * Chris@0: * @param bool $useBracketedPaste Chris@0: */ Chris@0: public function setUseBracketedPaste($useBracketedPaste) Chris@0: { Chris@0: $this->useBracketedPaste = (bool) $useBracketedPaste; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Check whether to use bracketed paste with readline. Chris@0: * Chris@0: * When this works, it's magical. Tabs in pastes don't try to autcomplete. Chris@0: * Newlines in paste don't execute code until you get to the end. It makes Chris@0: * readline act like you'd expect when pasting. Chris@0: * Chris@0: * But it often (usually?) does not work. And when it doesn't, it just spews Chris@0: * escape codes all over the place and generally makes things ugly :( Chris@0: * Chris@0: * If `useBracketedPaste` has been set to true, but the current readline Chris@0: * implementation is anything besides GNU readline, this will return false. Chris@0: * Chris@0: * @return bool True if the shell should use bracketed paste Chris@0: */ Chris@0: public function useBracketedPaste() Chris@0: { Chris@0: // For now, only the GNU readline implementation supports bracketed paste. Chris@0: $supported = ($this->getReadlineClass() === 'Psy\Readline\GNUReadline'); Chris@0: Chris@0: return $supported && $this->useBracketedPaste; Chris@0: Chris@0: // @todo mebbe turn this on by default some day? Chris@0: // return isset($this->useBracketedPaste) ? ($supported && $this->useBracketedPaste) : $supported; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Check whether this PHP instance has Pcntl available. Chris@0: * Chris@0: * @return bool True if Pcntl is available Chris@0: */ Chris@0: public function hasPcntl() Chris@0: { Chris@0: return $this->hasPcntl; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Enable or disable Pcntl usage. Chris@0: * Chris@0: * @param bool $usePcntl Chris@0: */ Chris@0: public function setUsePcntl($usePcntl) Chris@0: { Chris@0: $this->usePcntl = (bool) $usePcntl; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Check whether to use Pcntl. Chris@0: * Chris@0: * If `setUsePcntl` has been set to true, but Pcntl is not actually Chris@0: * available, this will return false. Chris@0: * Chris@0: * @return bool True if the current Shell should use Pcntl Chris@0: */ Chris@0: public function usePcntl() Chris@0: { Chris@0: return isset($this->usePcntl) ? ($this->hasPcntl && $this->usePcntl) : $this->hasPcntl; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Enable or disable strict requirement of semicolons. Chris@0: * Chris@0: * @see self::requireSemicolons() Chris@0: * Chris@0: * @param bool $requireSemicolons Chris@0: */ Chris@0: public function setRequireSemicolons($requireSemicolons) Chris@0: { Chris@0: $this->requireSemicolons = (bool) $requireSemicolons; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Check whether to require semicolons on all statements. Chris@0: * Chris@0: * By default, PsySH will automatically insert semicolons at the end of Chris@0: * statements if they're missing. To strictly require semicolons, set Chris@0: * `requireSemicolons` to true. Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: public function requireSemicolons() Chris@0: { Chris@0: return $this->requireSemicolons; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Enable or disable Unicode in PsySH specific output. Chris@0: * Chris@0: * Note that this does not disable Unicode output in general, it just makes Chris@0: * it so PsySH won't output any itself. Chris@0: * Chris@0: * @param bool $useUnicode Chris@0: */ Chris@0: public function setUseUnicode($useUnicode) Chris@0: { Chris@0: $this->useUnicode = (bool) $useUnicode; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Check whether to use Unicode in PsySH specific output. Chris@0: * Chris@0: * Note that this does not disable Unicode output in general, it just makes Chris@0: * it so PsySH won't output any itself. Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: public function useUnicode() Chris@0: { Chris@0: if (isset($this->useUnicode)) { Chris@0: return $this->useUnicode; Chris@0: } Chris@0: Chris@0: // @todo detect `chsh` != 65001 on Windows and return false Chris@0: return true; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Set the error logging level. Chris@0: * Chris@0: * @see self::errorLoggingLevel Chris@0: * Chris@0: * @param bool $errorLoggingLevel Chris@0: */ Chris@0: public function setErrorLoggingLevel($errorLoggingLevel) Chris@0: { Chris@0: $this->errorLoggingLevel = (E_ALL | E_STRICT) & $errorLoggingLevel; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get the current error logging level. Chris@0: * Chris@0: * By default, PsySH will automatically log all errors, regardless of the Chris@0: * current `error_reporting` level. Additionally, if the `error_reporting` Chris@0: * level warrants, an ErrorException will be thrown. Chris@0: * Chris@0: * Set `errorLoggingLevel` to 0 to prevent logging non-thrown errors. Set it Chris@0: * to any valid error_reporting value to log only errors which match that Chris@0: * level. Chris@0: * Chris@0: * http://php.net/manual/en/function.error-reporting.php Chris@0: * Chris@0: * @return int Chris@0: */ Chris@0: public function errorLoggingLevel() Chris@0: { Chris@0: return $this->errorLoggingLevel; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Set a CodeCleaner service instance. Chris@0: * Chris@0: * @param CodeCleaner $cleaner Chris@0: */ Chris@0: public function setCodeCleaner(CodeCleaner $cleaner) Chris@0: { Chris@0: $this->cleaner = $cleaner; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get a CodeCleaner service instance. Chris@0: * Chris@0: * If none has been explicitly defined, this will create a new instance. Chris@0: * Chris@0: * @return CodeCleaner Chris@0: */ Chris@0: public function getCodeCleaner() Chris@0: { Chris@0: if (!isset($this->cleaner)) { Chris@0: $this->cleaner = new CodeCleaner(); Chris@0: } Chris@0: Chris@0: return $this->cleaner; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Enable or disable tab completion. Chris@0: * Chris@0: * @param bool $useTabCompletion Chris@0: */ Chris@0: public function setUseTabCompletion($useTabCompletion) Chris@0: { Chris@0: $this->useTabCompletion = (bool) $useTabCompletion; Chris@0: } Chris@0: Chris@0: /** Chris@0: * @deprecated Call `setUseTabCompletion` instead Chris@0: * Chris@0: * @param bool $useTabCompletion Chris@0: */ Chris@0: public function setTabCompletion($useTabCompletion) Chris@0: { Chris@0: $this->setUseTabCompletion($useTabCompletion); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Check whether to use tab completion. Chris@0: * Chris@0: * If `setUseTabCompletion` has been set to true, but readline is not Chris@0: * actually available, this will return false. Chris@0: * Chris@0: * @return bool True if the current Shell should use tab completion Chris@0: */ Chris@0: public function useTabCompletion() Chris@0: { Chris@0: return isset($this->useTabCompletion) ? ($this->hasReadline && $this->useTabCompletion) : $this->hasReadline; Chris@0: } Chris@0: Chris@0: /** Chris@0: * @deprecated Call `useTabCompletion` instead Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: public function getTabCompletion() Chris@0: { Chris@0: return $this->useTabCompletion(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Set the Shell Output service. Chris@0: * Chris@0: * @param ShellOutput $output Chris@0: */ Chris@0: public function setOutput(ShellOutput $output) Chris@0: { Chris@0: $this->output = $output; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get a Shell Output service instance. Chris@0: * Chris@0: * If none has been explicitly provided, this will create a new instance Chris@0: * with VERBOSITY_NORMAL and the output page supplied by self::getPager Chris@0: * Chris@0: * @see self::getPager Chris@0: * Chris@0: * @return ShellOutput Chris@0: */ Chris@0: public function getOutput() Chris@0: { Chris@0: if (!isset($this->output)) { Chris@0: $this->output = new ShellOutput( Chris@0: ShellOutput::VERBOSITY_NORMAL, Chris@0: $this->getOutputDecorated(), Chris@0: null, Chris@0: $this->getPager() Chris@0: ); Chris@0: } Chris@0: Chris@0: return $this->output; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get the decoration (i.e. color) setting for the Shell Output service. Chris@0: * Chris@0: * @return null|bool 3-state boolean corresponding to the current color mode Chris@0: */ Chris@0: public function getOutputDecorated() Chris@0: { Chris@0: if ($this->colorMode() === self::COLOR_MODE_AUTO) { Chris@0: return; Chris@0: } elseif ($this->colorMode() === self::COLOR_MODE_FORCED) { Chris@0: return true; Chris@0: } elseif ($this->colorMode() === self::COLOR_MODE_DISABLED) { Chris@0: return false; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Set the OutputPager service. Chris@0: * Chris@0: * If a string is supplied, a ProcOutputPager will be used which shells out Chris@0: * to the specified command. Chris@0: * Chris@0: * @throws \InvalidArgumentException if $pager is not a string or OutputPager instance Chris@0: * Chris@0: * @param string|OutputPager $pager Chris@0: */ Chris@0: public function setPager($pager) Chris@0: { Chris@0: if ($pager && !is_string($pager) && !$pager instanceof OutputPager) { Chris@0: throw new \InvalidArgumentException('Unexpected pager instance'); Chris@0: } Chris@0: Chris@0: $this->pager = $pager; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get an OutputPager instance or a command for an external Proc pager. Chris@0: * Chris@0: * If no Pager has been explicitly provided, and Pcntl is available, this Chris@0: * will default to `cli.pager` ini value, falling back to `which less`. Chris@0: * Chris@0: * @return string|OutputPager Chris@0: */ Chris@0: public function getPager() Chris@0: { Chris@0: if (!isset($this->pager) && $this->usePcntl()) { Chris@0: if ($pager = ini_get('cli.pager')) { Chris@0: // use the default pager Chris@0: $this->pager = $pager; Chris@0: } elseif ($less = exec('which less 2>/dev/null')) { Chris@0: // check for the presence of less... Chris@0: $this->pager = $less . ' -R -S -F -X'; Chris@0: } Chris@0: } Chris@0: Chris@0: return $this->pager; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Set the Shell AutoCompleter service. Chris@0: * Chris@0: * @param AutoCompleter $autoCompleter Chris@0: */ Chris@0: public function setAutoCompleter(AutoCompleter $autoCompleter) Chris@0: { Chris@0: $this->autoCompleter = $autoCompleter; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get an AutoCompleter service instance. Chris@0: * Chris@0: * @return AutoCompleter Chris@0: */ Chris@0: public function getAutoCompleter() Chris@0: { Chris@0: if (!isset($this->autoCompleter)) { Chris@0: $this->autoCompleter = new AutoCompleter(); Chris@0: } Chris@0: Chris@0: return $this->autoCompleter; Chris@0: } Chris@0: Chris@0: /** Chris@0: * @deprecated Nothing should be using this anymore Chris@0: * Chris@0: * @return array Chris@0: */ Chris@0: public function getTabCompletionMatchers() Chris@0: { Chris@0: return []; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Add tab completion matchers to the AutoCompleter. Chris@0: * Chris@0: * This will buffer new matchers in the event that the Shell has not yet Chris@0: * been instantiated. This allows the user to specify matchers in their Chris@0: * config rc file, despite the fact that their file is needed in the Shell Chris@0: * constructor. Chris@0: * Chris@0: * @param array $matchers Chris@0: */ Chris@0: public function addMatchers(array $matchers) Chris@0: { Chris@0: $this->newMatchers = array_merge($this->newMatchers, $matchers); Chris@0: if (isset($this->shell)) { Chris@0: $this->doAddMatchers(); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Internal method for adding tab completion matchers. This will set any new Chris@0: * matchers once a Shell is available. Chris@0: */ Chris@0: private function doAddMatchers() Chris@0: { Chris@0: if (!empty($this->newMatchers)) { Chris@0: $this->shell->addMatchers($this->newMatchers); Chris@0: $this->newMatchers = []; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * @deprecated Use `addMatchers` instead Chris@0: * Chris@0: * @param array $matchers Chris@0: */ Chris@0: public function addTabCompletionMatchers(array $matchers) Chris@0: { Chris@0: $this->addMatchers($matchers); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Add commands to the Shell. Chris@0: * Chris@0: * This will buffer new commands in the event that the Shell has not yet Chris@0: * been instantiated. This allows the user to specify commands in their Chris@0: * config rc file, despite the fact that their file is needed in the Shell Chris@0: * constructor. Chris@0: * Chris@0: * @param array $commands Chris@0: */ Chris@0: public function addCommands(array $commands) Chris@0: { Chris@0: $this->newCommands = array_merge($this->newCommands, $commands); Chris@0: if (isset($this->shell)) { Chris@0: $this->doAddCommands(); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Internal method for adding commands. This will set any new commands once Chris@0: * a Shell is available. Chris@0: */ Chris@0: private function doAddCommands() Chris@0: { Chris@0: if (!empty($this->newCommands)) { Chris@0: $this->shell->addCommands($this->newCommands); Chris@0: $this->newCommands = []; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Set the Shell backreference and add any new commands to the Shell. Chris@0: * Chris@0: * @param Shell $shell Chris@0: */ Chris@0: public function setShell(Shell $shell) Chris@0: { Chris@0: $this->shell = $shell; Chris@0: $this->doAddCommands(); Chris@0: $this->doAddMatchers(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Set the PHP manual database file. Chris@0: * Chris@0: * This file should be an SQLite database generated from the phpdoc source Chris@0: * with the `bin/build_manual` script. Chris@0: * Chris@0: * @param string $filename Chris@0: */ Chris@0: public function setManualDbFile($filename) Chris@0: { Chris@0: $this->manualDbFile = (string) $filename; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get the current PHP manual database file. Chris@0: * Chris@0: * @return string Default: '~/.local/share/psysh/php_manual.sqlite' Chris@0: */ Chris@0: public function getManualDbFile() Chris@0: { Chris@0: if (isset($this->manualDbFile)) { Chris@0: return $this->manualDbFile; Chris@0: } Chris@0: Chris@0: $files = ConfigPaths::getDataFiles(['php_manual.sqlite'], $this->dataDir); Chris@0: if (!empty($files)) { Chris@0: if ($this->warnOnMultipleConfigs && count($files) > 1) { Chris@0: $msg = sprintf('Multiple manual database files found: %s. Using %s', implode($files, ', '), $files[0]); Chris@0: trigger_error($msg, E_USER_NOTICE); Chris@0: } Chris@0: Chris@0: return $this->manualDbFile = $files[0]; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get a PHP manual database connection. Chris@0: * Chris@0: * @return \PDO Chris@0: */ Chris@0: public function getManualDb() Chris@0: { Chris@0: if (!isset($this->manualDb)) { Chris@0: $dbFile = $this->getManualDbFile(); Chris@0: if (is_file($dbFile)) { Chris@0: try { Chris@0: $this->manualDb = new \PDO('sqlite:' . $dbFile); Chris@0: } catch (\PDOException $e) { Chris@0: if ($e->getMessage() === 'could not find driver') { Chris@0: throw new RuntimeException('SQLite PDO driver not found', 0, $e); Chris@0: } else { Chris@0: throw $e; Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: return $this->manualDb; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Add an array of casters definitions. Chris@0: * Chris@0: * @param array $casters Chris@0: */ Chris@0: public function addCasters(array $casters) Chris@0: { Chris@0: $this->getPresenter()->addCasters($casters); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get the Presenter service. Chris@0: * Chris@0: * @return Presenter Chris@0: */ Chris@0: public function getPresenter() Chris@0: { Chris@0: if (!isset($this->presenter)) { Chris@0: $this->presenter = new Presenter($this->getOutput()->getFormatter(), $this->forceArrayIndexes()); Chris@0: } Chris@0: Chris@0: return $this->presenter; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Enable or disable warnings on multiple configuration or data files. Chris@0: * Chris@0: * @see self::warnOnMultipleConfigs() Chris@0: * Chris@0: * @param bool $warnOnMultipleConfigs Chris@0: */ Chris@0: public function setWarnOnMultipleConfigs($warnOnMultipleConfigs) Chris@0: { Chris@0: $this->warnOnMultipleConfigs = (bool) $warnOnMultipleConfigs; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Check whether to warn on multiple configuration or data files. Chris@0: * Chris@0: * By default, PsySH will use the file with highest precedence, and will Chris@0: * silently ignore all others. With this enabled, a warning will be emitted Chris@0: * (but not an exception thrown) if multiple configuration or data files Chris@0: * are found. Chris@0: * Chris@0: * This will default to true in a future release, but is false for now. Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: public function warnOnMultipleConfigs() Chris@0: { Chris@0: return $this->warnOnMultipleConfigs; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Set the current color mode. Chris@0: * Chris@0: * @param string $colorMode Chris@0: */ Chris@0: public function setColorMode($colorMode) Chris@0: { Chris@0: $validColorModes = [ Chris@0: self::COLOR_MODE_AUTO, Chris@0: self::COLOR_MODE_FORCED, Chris@0: self::COLOR_MODE_DISABLED, Chris@0: ]; Chris@0: Chris@0: if (in_array($colorMode, $validColorModes)) { Chris@0: $this->colorMode = $colorMode; Chris@0: } else { Chris@0: throw new \InvalidArgumentException('invalid color mode: ' . $colorMode); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get the current color mode. Chris@0: * Chris@0: * @return string Chris@0: */ Chris@0: public function colorMode() Chris@0: { Chris@0: return $this->colorMode; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Set an update checker service instance. Chris@0: * Chris@0: * @param Checker $checker Chris@0: */ Chris@0: public function setChecker(Checker $checker) Chris@0: { Chris@0: $this->checker = $checker; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get an update checker service instance. Chris@0: * Chris@0: * If none has been explicitly defined, this will create a new instance. Chris@0: * Chris@0: * @return Checker Chris@0: */ Chris@0: public function getChecker() Chris@0: { Chris@0: if (!isset($this->checker)) { Chris@0: $interval = $this->getUpdateCheck(); Chris@0: switch ($interval) { Chris@0: case Checker::ALWAYS: Chris@0: $this->checker = new GitHubChecker(); Chris@0: break; Chris@0: Chris@0: case Checker::DAILY: Chris@0: case Checker::WEEKLY: Chris@0: case Checker::MONTHLY: Chris@0: $checkFile = $this->getUpdateCheckCacheFile(); Chris@0: if ($checkFile === false) { Chris@0: $this->checker = new NoopChecker(); Chris@0: } else { Chris@0: $this->checker = new IntervalChecker($checkFile, $interval); Chris@0: } Chris@0: break; Chris@0: Chris@0: case Checker::NEVER: Chris@0: $this->checker = new NoopChecker(); Chris@0: break; Chris@0: } Chris@0: } Chris@0: Chris@0: return $this->checker; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get the current update check interval. Chris@0: * Chris@0: * One of 'always', 'daily', 'weekly', 'monthly' or 'never'. If none is Chris@0: * explicitly set, default to 'weekly'. Chris@0: * Chris@0: * @return string Chris@0: */ Chris@0: public function getUpdateCheck() Chris@0: { Chris@0: return isset($this->updateCheck) ? $this->updateCheck : Checker::WEEKLY; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Set the update check interval. Chris@0: * Chris@0: * @throws \InvalidArgumentDescription if the update check interval is unknown Chris@0: * Chris@0: * @param string $interval Chris@0: */ Chris@0: public function setUpdateCheck($interval) Chris@0: { Chris@0: $validIntervals = [ Chris@0: Checker::ALWAYS, Chris@0: Checker::DAILY, Chris@0: Checker::WEEKLY, Chris@0: Checker::MONTHLY, Chris@0: Checker::NEVER, Chris@0: ]; Chris@0: Chris@0: if (!in_array($interval, $validIntervals)) { Chris@0: throw new \InvalidArgumentException('invalid update check interval: ' . $interval); Chris@0: } Chris@0: Chris@0: $this->updateCheck = $interval; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get a cache file path for the update checker. Chris@0: * Chris@0: * @return string|false Return false if config file/directory is not writable Chris@0: */ Chris@0: public function getUpdateCheckCacheFile() Chris@0: { Chris@0: $dir = $this->configDir ?: ConfigPaths::getCurrentConfigDir(); Chris@0: Chris@0: return ConfigPaths::touchFileWithMkdir($dir . '/update_check.json'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Set the startup message. Chris@0: * Chris@0: * @param string $message Chris@0: */ Chris@0: public function setStartupMessage($message) Chris@0: { Chris@0: $this->startupMessage = $message; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get the startup message. Chris@0: * Chris@0: * @return string|null Chris@0: */ Chris@0: public function getStartupMessage() Chris@0: { Chris@0: return $this->startupMessage; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Set the prompt. Chris@0: * Chris@0: * @param string $prompt Chris@0: */ Chris@0: public function setPrompt($prompt) Chris@0: { Chris@0: $this->prompt = $prompt; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get the prompt. Chris@0: * Chris@0: * @return string Chris@0: */ Chris@0: public function getPrompt() Chris@0: { Chris@0: return $this->prompt; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get the force array indexes. Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: public function forceArrayIndexes() Chris@0: { Chris@0: return $this->forceArrayIndexes; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Set the force array indexes. Chris@0: * Chris@0: * @param bool $forceArrayIndexes Chris@0: */ Chris@0: public function setForceArrayIndexes($forceArrayIndexes) Chris@0: { Chris@0: $this->forceArrayIndexes = $forceArrayIndexes; Chris@0: } Chris@0: }