diff --git a/vendor/autoload.php b/vendor/autoload.php new file mode 100644 index 0000000000..d1cac6a692 --- /dev/null +++ b/vendor/autoload.php @@ -0,0 +1,7 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + /** @var ?string */ + private $vendorDir; + + // PSR-4 + /** + * @var array[] + * @psalm-var array> + */ + private $prefixLengthsPsr4 = array(); + /** + * @var array[] + * @psalm-var array> + */ + private $prefixDirsPsr4 = array(); + /** + * @var array[] + * @psalm-var array + */ + private $fallbackDirsPsr4 = array(); + + // PSR-0 + /** + * @var array[] + * @psalm-var array> + */ + private $prefixesPsr0 = array(); + /** + * @var array[] + * @psalm-var array + */ + private $fallbackDirsPsr0 = array(); + + /** @var bool */ + private $useIncludePath = false; + + /** + * @var string[] + * @psalm-var array + */ + private $classMap = array(); + + /** @var bool */ + private $classMapAuthoritative = false; + + /** + * @var bool[] + * @psalm-var array + */ + private $missingClasses = array(); + + /** @var ?string */ + private $apcuPrefix; + + /** + * @var self[] + */ + private static $registeredLoaders = array(); + + /** + * @param ?string $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + } + + /** + * @return string[] + */ + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + /** + * @return array[] + * @psalm-return array> + */ + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + /** + * @return array[] + * @psalm-return array + */ + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + /** + * @return array[] + * @psalm-return array + */ + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + /** + * @return string[] Array of classname => path + * @psalm-var array + */ + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param string[] $classMap Class to filename map + * @psalm-param array $classMap + * + * @return void + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param string[]|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param string[]|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param string[]|string $paths The PSR-0 base directories + * + * @return void + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param string[]|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + * + * @return void + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + * + * @return void + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + * + * @return void + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } + } + + /** + * Unregisters this instance as an autoloader. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return true|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + + return null; + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + /** + * Returns the currently registered loaders indexed by their corresponding vendor directories. + * + * @return self[] + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + * @private + */ +function includeFile($file) +{ + include $file; +} diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php new file mode 100644 index 0000000000..d50e0c9fcc --- /dev/null +++ b/vendor/composer/InstalledVersions.php @@ -0,0 +1,350 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + */ +class InstalledVersions +{ + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array}|array{}|null + */ + private static $installed; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']); + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints($constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + } + + /** + * @return array[] + * @psalm-return list}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + + if (self::$canGetVendors) { + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; + if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { + self::$installed = $installed[count($installed) - 1]; + } + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = require __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + $installed[] = self::$installed; + + return $installed; + } +} diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE new file mode 100644 index 0000000000..f27399a042 --- /dev/null +++ b/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000000..9ab1025aa5 --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,18 @@ + $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', + 'Console_Table' => $vendorDir . '/pear/console_table/Table.php', + 'DrupalFinder\\DrupalFinder' => $vendorDir . '/webflo/drupal-finder/src/DrupalFinder.php', + 'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', + 'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', +); diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php new file mode 100644 index 0000000000..2516b56600 --- /dev/null +++ b/vendor/composer/autoload_files.php @@ -0,0 +1,16 @@ + $vendorDir . '/symfony/polyfill-php80/bootstrap.php', + '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', + '0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php', + '667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', + '801c31d8ed748cfa537fa45402288c95' => $vendorDir . '/psy/psysh/src/functions.php', +); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000000..abfd5c9b82 --- /dev/null +++ b/vendor/composer/autoload_namespaces.php @@ -0,0 +1,12 @@ + array($vendorDir . '/drush/drush/lib'), + 'Dflydev\\DotAccessData' => array($vendorDir . '/dflydev/dot-access-data/src'), + 'Consolidation\\' => array($vendorDir . '/drush/drush/lib'), +); diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php new file mode 100644 index 0000000000..9c90113a7c --- /dev/null +++ b/vendor/composer/autoload_psr4.php @@ -0,0 +1,30 @@ + array($vendorDir . '/webmozart/path-util/src'), + 'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'), + 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), + 'Symfony\\Polyfill\\Php73\\' => array($vendorDir . '/symfony/polyfill-php73'), + 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), + 'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'), + 'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'), + 'Symfony\\Contracts\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher-contracts'), + 'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'), + 'Symfony\\Component\\VarDumper\\' => array($vendorDir . '/symfony/var-dumper'), + 'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'), + 'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'), + 'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'), + 'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'), + 'Psy\\' => array($vendorDir . '/psy/psysh/src'), + 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), + 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), + 'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'), + 'Drush\\' => array($vendorDir . '/drush/drush/src'), + 'Consolidation\\OutputFormatters\\' => array($vendorDir . '/consolidation/output-formatters/src'), + 'Consolidation\\AnnotatedCommand\\' => array($vendorDir . '/consolidation/annotated-command/src'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 0000000000..a19d5dc418 --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,75 @@ += 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInit049d4a4ec78aaeffc54ad90927542a79::getInitializer($loader)); + } else { + $map = require __DIR__ . '/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + } + + $loader->register(true); + + if ($useStaticLoader) { + $includeFiles = Composer\Autoload\ComposerStaticInit049d4a4ec78aaeffc54ad90927542a79::$files; + } else { + $includeFiles = require __DIR__ . '/autoload_files.php'; + } + foreach ($includeFiles as $fileIdentifier => $file) { + composerRequire049d4a4ec78aaeffc54ad90927542a79($fileIdentifier, $file); + } + + return $loader; + } +} + +function composerRequire049d4a4ec78aaeffc54ad90927542a79($fileIdentifier, $file) +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + require $file; + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } +} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php new file mode 100644 index 0000000000..4872891b80 --- /dev/null +++ b/vendor/composer/autoload_static.php @@ -0,0 +1,188 @@ + __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', + '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', + '0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php', + '667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', + '801c31d8ed748cfa537fa45402288c95' => __DIR__ . '/..' . '/psy/psysh/src/functions.php', + ); + + public static $prefixLengthsPsr4 = array ( + 'W' => + array ( + 'Webmozart\\PathUtil\\' => 19, + 'Webmozart\\Assert\\' => 17, + ), + 'S' => + array ( + 'Symfony\\Polyfill\\Php80\\' => 23, + 'Symfony\\Polyfill\\Php73\\' => 23, + 'Symfony\\Polyfill\\Mbstring\\' => 26, + 'Symfony\\Polyfill\\Ctype\\' => 23, + 'Symfony\\Contracts\\Service\\' => 26, + 'Symfony\\Contracts\\EventDispatcher\\' => 34, + 'Symfony\\Component\\Yaml\\' => 23, + 'Symfony\\Component\\VarDumper\\' => 28, + 'Symfony\\Component\\Process\\' => 26, + 'Symfony\\Component\\Finder\\' => 25, + 'Symfony\\Component\\EventDispatcher\\' => 34, + 'Symfony\\Component\\Console\\' => 26, + ), + 'P' => + array ( + 'Psy\\' => 4, + 'Psr\\Log\\' => 8, + 'Psr\\Container\\' => 14, + 'PhpParser\\' => 10, + ), + 'D' => + array ( + 'Drush\\' => 6, + ), + 'C' => + array ( + 'Consolidation\\OutputFormatters\\' => 31, + 'Consolidation\\AnnotatedCommand\\' => 31, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Webmozart\\PathUtil\\' => + array ( + 0 => __DIR__ . '/..' . '/webmozart/path-util/src', + ), + 'Webmozart\\Assert\\' => + array ( + 0 => __DIR__ . '/..' . '/webmozart/assert/src', + ), + 'Symfony\\Polyfill\\Php80\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php80', + ), + 'Symfony\\Polyfill\\Php73\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php73', + ), + 'Symfony\\Polyfill\\Mbstring\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', + ), + 'Symfony\\Polyfill\\Ctype\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', + ), + 'Symfony\\Contracts\\Service\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/service-contracts', + ), + 'Symfony\\Contracts\\EventDispatcher\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/event-dispatcher-contracts', + ), + 'Symfony\\Component\\Yaml\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/yaml', + ), + 'Symfony\\Component\\VarDumper\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/var-dumper', + ), + 'Symfony\\Component\\Process\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/process', + ), + 'Symfony\\Component\\Finder\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/finder', + ), + 'Symfony\\Component\\EventDispatcher\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/event-dispatcher', + ), + 'Symfony\\Component\\Console\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/console', + ), + 'Psy\\' => + array ( + 0 => __DIR__ . '/..' . '/psy/psysh/src', + ), + 'Psr\\Log\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/log/Psr/Log', + ), + 'Psr\\Container\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/container/src', + ), + 'PhpParser\\' => + array ( + 0 => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser', + ), + 'Drush\\' => + array ( + 0 => __DIR__ . '/..' . '/drush/drush/src', + ), + 'Consolidation\\OutputFormatters\\' => + array ( + 0 => __DIR__ . '/..' . '/consolidation/output-formatters/src', + ), + 'Consolidation\\AnnotatedCommand\\' => + array ( + 0 => __DIR__ . '/..' . '/consolidation/annotated-command/src', + ), + ); + + public static $prefixesPsr0 = array ( + 'D' => + array ( + 'Drush\\' => + array ( + 0 => __DIR__ . '/..' . '/drush/drush/lib', + ), + 'Dflydev\\DotAccessData' => + array ( + 0 => __DIR__ . '/..' . '/dflydev/dot-access-data/src', + ), + ), + 'C' => + array ( + 'Consolidation\\' => + array ( + 0 => __DIR__ . '/..' . '/drush/drush/lib', + ), + ), + ); + + public static $classMap = array ( + 'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + 'Console_Table' => __DIR__ . '/..' . '/pear/console_table/Table.php', + 'DrupalFinder\\DrupalFinder' => __DIR__ . '/..' . '/webflo/drupal-finder/src/DrupalFinder.php', + 'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', + 'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit049d4a4ec78aaeffc54ad90927542a79::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit049d4a4ec78aaeffc54ad90927542a79::$prefixDirsPsr4; + $loader->prefixesPsr0 = ComposerStaticInit049d4a4ec78aaeffc54ad90927542a79::$prefixesPsr0; + $loader->classMap = ComposerStaticInit049d4a4ec78aaeffc54ad90927542a79::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 index 0000000000..38d2122ca9 --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1,1953 @@ +{ + "packages": [ + { + "name": "consolidation/annotated-command", + "version": "2.12.2", + "version_normalized": "2.12.2.0", + "source": { + "type": "git", + "url": "https://github.com/consolidation/annotated-command.git", + "reference": "2472a23610cba1d86dcb783a81a21259473b059e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/2472a23610cba1d86dcb783a81a21259473b059e", + "reference": "2472a23610cba1d86dcb783a81a21259473b059e", + "shasum": "" + }, + "require": { + "consolidation/output-formatters": "^3.5.1", + "php": ">=5.4.5", + "psr/log": "^1", + "symfony/console": "^2.8|^3|^4", + "symfony/event-dispatcher": "^2.5|^3|^4", + "symfony/finder": "^2.5|^3|^4|^5" + }, + "require-dev": { + "g1a/composer-test-scenarios": "^3", + "php-coveralls/php-coveralls": "^1", + "phpunit/phpunit": "^6", + "squizlabs/php_codesniffer": "^2.7" + }, + "time": "2022-01-03T00:23:44+00:00", + "type": "library", + "extra": { + "scenarios": { + "finder5": { + "require": { + "symfony/finder": "^5" + }, + "config": { + "platform": { + "php": "7.2.5" + } + } + }, + "symfony4": { + "require": { + "symfony/console": "^4.0" + }, + "config": { + "platform": { + "php": "7.1.3" + } + } + }, + "symfony2": { + "require": { + "symfony/console": "^2.8" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36" + }, + "remove": [ + "php-coveralls/php-coveralls" + ], + "config": { + "platform": { + "php": "5.4.8" + } + }, + "scenario-options": { + "create-lockfile": "false" + } + }, + "phpunit4": { + "require-dev": { + "phpunit/phpunit": "^4.8.36" + }, + "remove": [ + "php-coveralls/php-coveralls" + ], + "config": { + "platform": { + "php": "5.4.8" + } + } + } + }, + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Consolidation\\AnnotatedCommand\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Initialize Symfony Console commands from annotated command class methods.", + "support": { + "issues": "https://github.com/consolidation/annotated-command/issues", + "source": "https://github.com/consolidation/annotated-command/tree/2.12.2" + }, + "install-path": "../consolidation/annotated-command" + }, + { + "name": "consolidation/output-formatters", + "version": "3.5.1", + "version_normalized": "3.5.1.0", + "source": { + "type": "git", + "url": "https://github.com/consolidation/output-formatters.git", + "reference": "0d38f13051ef05c223a2bb8e962d668e24785196" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/0d38f13051ef05c223a2bb8e962d668e24785196", + "reference": "0d38f13051ef05c223a2bb8e962d668e24785196", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^1.1.0", + "php": ">=5.4.0", + "symfony/console": "^2.8|^3|^4", + "symfony/finder": "^2.5|^3|^4|^5" + }, + "require-dev": { + "g1a/composer-test-scenarios": "^3", + "php-coveralls/php-coveralls": "^1", + "phpunit/phpunit": "^5.7.27", + "squizlabs/php_codesniffer": "^2.7", + "symfony/var-dumper": "^2.8|^3|^4", + "victorjonsson/markdowndocs": "^1.3" + }, + "suggest": { + "symfony/var-dumper": "For using the var_dump formatter" + }, + "time": "2020-10-11T04:15:32+00:00", + "type": "library", + "extra": { + "scenarios": { + "finder5": { + "require": { + "symfony/finder": "^5" + }, + "config": { + "platform": { + "php": "7.2.5" + } + } + }, + "symfony4": { + "require": { + "symfony/console": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^6" + }, + "config": { + "platform": { + "php": "7.1.3" + } + } + }, + "symfony3": { + "require": { + "symfony/console": "^3.4", + "symfony/finder": "^3.4", + "symfony/var-dumper": "^3.4" + }, + "config": { + "platform": { + "php": "5.6.32" + } + } + }, + "symfony2": { + "require": { + "symfony/console": "^2.8" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36" + }, + "remove": [ + "php-coveralls/php-coveralls" + ], + "config": { + "platform": { + "php": "5.4.8" + } + }, + "scenario-options": { + "create-lockfile": "false" + } + } + }, + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Consolidation\\OutputFormatters\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Format text by applying transformations provided by plug-in formatters.", + "support": { + "issues": "https://github.com/consolidation/output-formatters/issues", + "source": "https://github.com/consolidation/output-formatters/tree/3.5.1" + }, + "install-path": "../consolidation/output-formatters" + }, + { + "name": "dflydev/dot-access-data", + "version": "v1.1.0", + "version_normalized": "1.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/3fbd874921ab2c041e899d044585a2ab9795df8a", + "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "time": "2017-01-20T21:14:22+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Dflydev\\DotAccessData": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/master" + }, + "install-path": "../dflydev/dot-access-data" + }, + { + "name": "drush/drush", + "version": "8.4.11", + "version_normalized": "8.4.11.0", + "source": { + "type": "git", + "url": "https://github.com/drush-ops/drush.git", + "reference": "f4ac6117b0d5de614d5759de35356e7777488a58" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/drush-ops/drush/zipball/f4ac6117b0d5de614d5759de35356e7777488a58", + "reference": "f4ac6117b0d5de614d5759de35356e7777488a58", + "shasum": "" + }, + "require": { + "consolidation/annotated-command": "^2.12.0", + "consolidation/output-formatters": "~3", + "pear/console_table": "~1.3.1", + "php": ">=5.4.5", + "psr/log": "~1.0", + "psy/psysh": "~0.6", + "symfony/console": "~2.7|^3|^4.4", + "symfony/event-dispatcher": "~2.7|^3|^4.4", + "symfony/finder": "~2.7|^3|^4.4", + "symfony/process": "~2.7|^3|^4.4", + "symfony/var-dumper": "~2.7|^3|^4.4|^5", + "symfony/yaml": "~2.3|^3|^4.4", + "webflo/drupal-finder": "^1.1.0", + "webmozart/path-util": "~2" + }, + "require-dev": { + "phpunit/phpunit": "^4 || ^7.5.20 || ^9", + "squizlabs/php_codesniffer": "^3", + "symfony/console": "~2.7", + "symfony/event-dispatcher": "~2.7", + "symfony/finder": "~2.7", + "symfony/process": "2.7.*", + "symfony/var-dumper": "~2.7", + "symfony/yaml": "~2.3", + "yoast/phpunit-polyfills": "^1" + }, + "suggest": { + "drush/config-extra": "Provides configuration workflow commands, such as config-merge.", + "ext-pcntl": "*" + }, + "time": "2022-06-02T15:23:14+00:00", + "bin": [ + "drush", + "drush.launcher", + "drush.php", + "drush.complete.sh" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.3.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Drush\\": "lib/", + "Consolidation\\": "lib/" + }, + "psr-4": { + "Drush\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Moshe Weitzman", + "email": "weitzman@tejasa.com" + }, + { + "name": "Owen Barton", + "email": "drupal@owenbarton.com" + }, + { + "name": "Mark Sonnabaum", + "email": "marksonnabaum@gmail.com" + }, + { + "name": "Antoine Beaupré", + "email": "anarcat@koumbit.org" + }, + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + }, + { + "name": "Jonathan Araña Cruz", + "email": "jonhattan@faita.net" + }, + { + "name": "Jonathan Hedstrom", + "email": "jhedstrom@gmail.com" + }, + { + "name": "Christopher Gervais", + "email": "chris@ergonlogic.com" + }, + { + "name": "Dave Reid", + "email": "dave@davereid.net" + }, + { + "name": "Damian Lee", + "email": "damiankloip@googlemail.com" + } + ], + "description": "Drush is a command line shell and scripting interface for Drupal, a veritable Swiss Army knife designed to make life easier for those of us who spend some of our working hours hacking away at the command prompt.", + "homepage": "http://www.drush.org", + "support": { + "forum": "http://drupal.stackexchange.com/questions/tagged/drush", + "irc": "irc://irc.freenode.org/drush", + "issues": "https://github.com/drush-ops/drush/issues", + "source": "https://github.com/drush-ops/drush/tree/8.4.11" + }, + "funding": [ + { + "url": "https://github.com/weitzman", + "type": "github" + } + ], + "install-path": "../drush/drush" + }, + { + "name": "nikic/php-parser", + "version": "v4.14.0", + "version_normalized": "4.14.0.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/34bea19b6e03d8153165d8f30bba4c3be86184c1", + "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "time": "2022-05-31T20:59:12+00:00", + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.14.0" + }, + "install-path": "../nikic/php-parser" + }, + { + "name": "pear/console_table", + "version": "v1.3.1", + "version_normalized": "1.3.1.0", + "source": { + "type": "git", + "url": "https://github.com/pear/Console_Table.git", + "reference": "1930c11897ca61fd24b95f2f785e99e0f36dcdea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pear/Console_Table/zipball/1930c11897ca61fd24b95f2f785e99e0f36dcdea", + "reference": "1930c11897ca61fd24b95f2f785e99e0f36dcdea", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "suggest": { + "pear/Console_Color2": ">=0.1.2" + }, + "time": "2018-01-25T20:47:17+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "classmap": [ + "Table.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Jan Schneider", + "homepage": "http://pear.php.net/user/yunosh" + }, + { + "name": "Tal Peer", + "homepage": "http://pear.php.net/user/tal" + }, + { + "name": "Xavier Noguer", + "homepage": "http://pear.php.net/user/xnoguer" + }, + { + "name": "Richard Heyes", + "homepage": "http://pear.php.net/user/richard" + } + ], + "description": "Library that makes it easy to build console style tables.", + "homepage": "http://pear.php.net/package/Console_Table/", + "keywords": [ + "console" + ], + "support": { + "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Console_Table", + "source": "https://github.com/pear/Console_Table" + }, + "install-path": "../pear/console_table" + }, + { + "name": "psr/container", + "version": "1.1.2", + "version_normalized": "1.1.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "time": "2021-11-05T16:50:12+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "install-path": "../psr/container" + }, + { + "name": "psr/log", + "version": "1.1.4", + "version_normalized": "1.1.4.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2021-05-03T11:20:27+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "install-path": "../psr/log" + }, + { + "name": "psy/psysh", + "version": "v0.11.8", + "version_normalized": "0.11.8.0", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/psysh.git", + "reference": "f455acf3645262ae389b10e9beba0c358aa6994e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/f455acf3645262ae389b10e9beba0c358aa6994e", + "reference": "f455acf3645262ae389b10e9beba0c358aa6994e", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "nikic/php-parser": "^4.0 || ^3.1", + "php": "^8.0 || ^7.0.8", + "symfony/console": "^6.0 || ^5.0 || ^4.0 || ^3.4", + "symfony/var-dumper": "^6.0 || ^5.0 || ^4.0 || ^3.4" + }, + "conflict": { + "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.2" + }, + "suggest": { + "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", + "ext-pdo-sqlite": "The doc command requires SQLite to work.", + "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well.", + "ext-readline": "Enables support for arrow-key history navigation, and showing and manipulating command history." + }, + "time": "2022-07-28T14:25:11+00:00", + "bin": [ + "bin/psysh" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.11.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Psy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "An interactive shell for modern PHP.", + "homepage": "http://psysh.org", + "keywords": [ + "REPL", + "console", + "interactive", + "shell" + ], + "support": { + "issues": "https://github.com/bobthecow/psysh/issues", + "source": "https://github.com/bobthecow/psysh/tree/v0.11.8" + }, + "install-path": "../psy/psysh" + }, + { + "name": "symfony/console", + "version": "v4.4.44", + "version_normalized": "4.4.44.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "c35fafd7f12ebd6f9e29c95a370df7f1fb171a40" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/c35fafd7f12ebd6f9e29c95a370df7f1fb171a40", + "reference": "c35fafd7f12ebd6f9e29c95a370df7f1fb171a40", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2" + }, + "conflict": { + "psr/log": ">=3", + "symfony/dependency-injection": "<3.4", + "symfony/event-dispatcher": "<4.3|>=5", + "symfony/lock": "<4.4", + "symfony/process": "<3.3" + }, + "provide": { + "psr/log-implementation": "1.0|2.0" + }, + "require-dev": { + "psr/log": "^1|^2", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/event-dispatcher": "^4.3", + "symfony/lock": "^4.4|^5.0", + "symfony/process": "^3.4|^4.0|^5.0", + "symfony/var-dumper": "^4.3|^5.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "time": "2022-07-20T09:59:04+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/console/tree/v4.4.44" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/console" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.2", + "version_normalized": "2.5.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "time": "2022-01-02T09:53:40+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/deprecation-contracts" + }, + { + "name": "symfony/event-dispatcher", + "version": "v4.4.44", + "version_normalized": "4.4.44.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "1e866e9e5c1b22168e0ce5f0b467f19bba61266a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/1e866e9e5c1b22168e0ce5f0b467f19bba61266a", + "reference": "1e866e9e5c1b22168e0ce5f0b467f19bba61266a", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/event-dispatcher-contracts": "^1.1", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "symfony/dependency-injection": "<3.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "1.1" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/error-handler": "~3.4|~4.4", + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/http-foundation": "^3.4|^4.0|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/stopwatch": "^3.4|^4.0|^5.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "time": "2022-07-20T09:59:04+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v4.4.44" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/event-dispatcher" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v1.1.13", + "version_normalized": "1.1.13.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "1d5cd762abaa6b2a4169d3e77610193a7157129e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/1d5cd762abaa6b2a4169d3e77610193a7157129e", + "reference": "1d5cd762abaa6b2a4169d3e77610193a7157129e", + "shasum": "" + }, + "require": { + "php": ">=7.1.3" + }, + "suggest": { + "psr/event-dispatcher": "", + "symfony/event-dispatcher-implementation": "" + }, + "time": "2022-01-02T09:41:36+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v1.1.13" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/event-dispatcher-contracts" + }, + { + "name": "symfony/finder", + "version": "v4.4.44", + "version_normalized": "4.4.44.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "66bd787edb5e42ff59d3523f623895af05043e4f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/66bd787edb5e42ff59d3523f623895af05043e4f", + "reference": "66bd787edb5e42ff59d3523f623895af05043e4f", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-php80": "^1.16" + }, + "time": "2022-07-29T07:35:46+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v4.4.44" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/finder" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.26.0", + "version_normalized": "1.26.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "time": "2022-05-24T11:49:31+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-ctype" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.26.0", + "version_normalized": "1.26.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "time": "2022-05-24T11:49:31+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-mbstring" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.26.0", + "version_normalized": "1.26.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/e440d35fa0286f77fb45b79a03fedbeda9307e85", + "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "time": "2022-05-24T11:49:31+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php73" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.26.0", + "version_normalized": "1.26.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "time": "2022-05-10T07:21:04+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php80" + }, + { + "name": "symfony/process", + "version": "v4.4.44", + "version_normalized": "4.4.44.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "5cee9cdc4f7805e2699d9fd66991a0e6df8252a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/5cee9cdc4f7805e2699d9fd66991a0e6df8252a2", + "reference": "5cee9cdc4f7805e2699d9fd66991a0e6df8252a2", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-php80": "^1.16" + }, + "time": "2022-06-27T13:16:42+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v4.4.44" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/process" + }, + { + "name": "symfony/service-contracts", + "version": "v2.5.2", + "version_normalized": "2.5.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "time": "2022-05-30T19:17:29+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/service-contracts" + }, + { + "name": "symfony/var-dumper", + "version": "v5.4.11", + "version_normalized": "5.4.11.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "b8f306d7b8ef34fb3db3305be97ba8e088fb4861" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/b8f306d7b8ef34fb3db3305be97ba8e088fb4861", + "reference": "b8f306d7b8ef34fb3db3305be97ba8e088fb4861", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<4.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/uid": "^5.1|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "time": "2022-07-20T13:00:38+00:00", + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v5.4.11" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/var-dumper" + }, + { + "name": "symfony/yaml", + "version": "v4.4.44", + "version_normalized": "4.4.44.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "c2b28c10fb3b7ac67bafa7b8f952cd83f35acde2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/c2b28c10fb3b7ac67bafa7b8f952cd83f35acde2", + "reference": "c2b28c10fb3b7ac67bafa7b8f952cd83f35acde2", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "require-dev": { + "symfony/console": "^3.4|^4.0|^5.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "time": "2022-06-27T13:16:42+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v4.4.44" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/yaml" + }, + { + "name": "webflo/drupal-finder", + "version": "1.2.2", + "version_normalized": "1.2.2.0", + "source": { + "type": "git", + "url": "https://github.com/webflo/drupal-finder.git", + "reference": "c8e5dbe65caef285fec8057a4c718a0d4138d1ee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webflo/drupal-finder/zipball/c8e5dbe65caef285fec8057a4c718a0d4138d1ee", + "reference": "c8e5dbe65caef285fec8057a4c718a0d4138d1ee", + "shasum": "" + }, + "require": { + "ext-json": "*" + }, + "require-dev": { + "mikey179/vfsstream": "^1.6", + "phpunit/phpunit": "^4.8" + }, + "time": "2020-10-27T09:42:17+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/DrupalFinder.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Florian Weber", + "email": "florian@webflo.org" + } + ], + "description": "Helper class to locate a Drupal installation from a given path.", + "support": { + "issues": "https://github.com/webflo/drupal-finder/issues", + "source": "https://github.com/webflo/drupal-finder/tree/1.2.2" + }, + "install-path": "../webflo/drupal-finder" + }, + { + "name": "webmozart/assert", + "version": "1.11.0", + "version_normalized": "1.11.0.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": "^7.2 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "time": "2022-06-03T18:03:27+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.11.0" + }, + "install-path": "../webmozart/assert" + }, + { + "name": "webmozart/path-util", + "version": "2.3.0", + "version_normalized": "2.3.0.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/path-util.git", + "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/path-util/zipball/d939f7edc24c9a1bb9c0dee5cb05d8e859490725", + "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "webmozart/assert": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "time": "2015-12-17T08:42:14+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Webmozart\\PathUtil\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "A robust cross-platform utility for normalizing, comparing and modifying file paths.", + "support": { + "issues": "https://github.com/webmozart/path-util/issues", + "source": "https://github.com/webmozart/path-util/tree/2.3.0" + }, + "abandoned": "symfony/filesystem", + "install-path": "../webmozart/path-util" + } + ], + "dev": true, + "dev-package-names": [] +} diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php new file mode 100644 index 0000000000..63670ae4e6 --- /dev/null +++ b/vendor/composer/installed.php @@ -0,0 +1,266 @@ + array( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'reference' => '16417dcc03551fe79bc68a093f24b00fc6e0f68f', + 'name' => '__root__', + 'dev' => true, + ), + 'versions' => array( + '__root__' => array( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'reference' => '16417dcc03551fe79bc68a093f24b00fc6e0f68f', + 'dev_requirement' => false, + ), + 'consolidation/annotated-command' => array( + 'pretty_version' => '2.12.2', + 'version' => '2.12.2.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../consolidation/annotated-command', + 'aliases' => array(), + 'reference' => '2472a23610cba1d86dcb783a81a21259473b059e', + 'dev_requirement' => false, + ), + 'consolidation/output-formatters' => array( + 'pretty_version' => '3.5.1', + 'version' => '3.5.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../consolidation/output-formatters', + 'aliases' => array(), + 'reference' => '0d38f13051ef05c223a2bb8e962d668e24785196', + 'dev_requirement' => false, + ), + 'dflydev/dot-access-data' => array( + 'pretty_version' => 'v1.1.0', + 'version' => '1.1.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../dflydev/dot-access-data', + 'aliases' => array(), + 'reference' => '3fbd874921ab2c041e899d044585a2ab9795df8a', + 'dev_requirement' => false, + ), + 'drush/drush' => array( + 'pretty_version' => '8.4.11', + 'version' => '8.4.11.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../drush/drush', + 'aliases' => array(), + 'reference' => 'f4ac6117b0d5de614d5759de35356e7777488a58', + 'dev_requirement' => false, + ), + 'nikic/php-parser' => array( + 'pretty_version' => 'v4.14.0', + 'version' => '4.14.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../nikic/php-parser', + 'aliases' => array(), + 'reference' => '34bea19b6e03d8153165d8f30bba4c3be86184c1', + 'dev_requirement' => false, + ), + 'pear/console_table' => array( + 'pretty_version' => 'v1.3.1', + 'version' => '1.3.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../pear/console_table', + 'aliases' => array(), + 'reference' => '1930c11897ca61fd24b95f2f785e99e0f36dcdea', + 'dev_requirement' => false, + ), + 'psr/container' => array( + 'pretty_version' => '1.1.2', + 'version' => '1.1.2.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/container', + 'aliases' => array(), + 'reference' => '513e0666f7216c7459170d56df27dfcefe1689ea', + 'dev_requirement' => false, + ), + 'psr/event-dispatcher-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + ), + ), + 'psr/log' => array( + 'pretty_version' => '1.1.4', + 'version' => '1.1.4.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/log', + 'aliases' => array(), + 'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11', + 'dev_requirement' => false, + ), + 'psr/log-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0|2.0', + ), + ), + 'psy/psysh' => array( + 'pretty_version' => 'v0.11.8', + 'version' => '0.11.8.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psy/psysh', + 'aliases' => array(), + 'reference' => 'f455acf3645262ae389b10e9beba0c358aa6994e', + 'dev_requirement' => false, + ), + 'symfony/console' => array( + 'pretty_version' => 'v4.4.44', + 'version' => '4.4.44.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/console', + 'aliases' => array(), + 'reference' => 'c35fafd7f12ebd6f9e29c95a370df7f1fb171a40', + 'dev_requirement' => false, + ), + 'symfony/deprecation-contracts' => array( + 'pretty_version' => 'v2.5.2', + 'version' => '2.5.2.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/deprecation-contracts', + 'aliases' => array(), + 'reference' => 'e8b495ea28c1d97b5e0c121748d6f9b53d075c66', + 'dev_requirement' => false, + ), + 'symfony/event-dispatcher' => array( + 'pretty_version' => 'v4.4.44', + 'version' => '4.4.44.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/event-dispatcher', + 'aliases' => array(), + 'reference' => '1e866e9e5c1b22168e0ce5f0b467f19bba61266a', + 'dev_requirement' => false, + ), + 'symfony/event-dispatcher-contracts' => array( + 'pretty_version' => 'v1.1.13', + 'version' => '1.1.13.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/event-dispatcher-contracts', + 'aliases' => array(), + 'reference' => '1d5cd762abaa6b2a4169d3e77610193a7157129e', + 'dev_requirement' => false, + ), + 'symfony/event-dispatcher-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.1', + ), + ), + 'symfony/finder' => array( + 'pretty_version' => 'v4.4.44', + 'version' => '4.4.44.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/finder', + 'aliases' => array(), + 'reference' => '66bd787edb5e42ff59d3523f623895af05043e4f', + 'dev_requirement' => false, + ), + 'symfony/polyfill-ctype' => array( + 'pretty_version' => 'v1.26.0', + 'version' => '1.26.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-ctype', + 'aliases' => array(), + 'reference' => '6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4', + 'dev_requirement' => false, + ), + 'symfony/polyfill-mbstring' => array( + 'pretty_version' => 'v1.26.0', + 'version' => '1.26.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-mbstring', + 'aliases' => array(), + 'reference' => '9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e', + 'dev_requirement' => false, + ), + 'symfony/polyfill-php73' => array( + 'pretty_version' => 'v1.26.0', + 'version' => '1.26.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php73', + 'aliases' => array(), + 'reference' => 'e440d35fa0286f77fb45b79a03fedbeda9307e85', + 'dev_requirement' => false, + ), + 'symfony/polyfill-php80' => array( + 'pretty_version' => 'v1.26.0', + 'version' => '1.26.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php80', + 'aliases' => array(), + 'reference' => 'cfa0ae98841b9e461207c13ab093d76b0fa7bace', + 'dev_requirement' => false, + ), + 'symfony/process' => array( + 'pretty_version' => 'v4.4.44', + 'version' => '4.4.44.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/process', + 'aliases' => array(), + 'reference' => '5cee9cdc4f7805e2699d9fd66991a0e6df8252a2', + 'dev_requirement' => false, + ), + 'symfony/service-contracts' => array( + 'pretty_version' => 'v2.5.2', + 'version' => '2.5.2.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/service-contracts', + 'aliases' => array(), + 'reference' => '4b426aac47d6427cc1a1d0f7e2ac724627f5966c', + 'dev_requirement' => false, + ), + 'symfony/var-dumper' => array( + 'pretty_version' => 'v5.4.11', + 'version' => '5.4.11.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/var-dumper', + 'aliases' => array(), + 'reference' => 'b8f306d7b8ef34fb3db3305be97ba8e088fb4861', + 'dev_requirement' => false, + ), + 'symfony/yaml' => array( + 'pretty_version' => 'v4.4.44', + 'version' => '4.4.44.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/yaml', + 'aliases' => array(), + 'reference' => 'c2b28c10fb3b7ac67bafa7b8f952cd83f35acde2', + 'dev_requirement' => false, + ), + 'webflo/drupal-finder' => array( + 'pretty_version' => '1.2.2', + 'version' => '1.2.2.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../webflo/drupal-finder', + 'aliases' => array(), + 'reference' => 'c8e5dbe65caef285fec8057a4c718a0d4138d1ee', + 'dev_requirement' => false, + ), + 'webmozart/assert' => array( + 'pretty_version' => '1.11.0', + 'version' => '1.11.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../webmozart/assert', + 'aliases' => array(), + 'reference' => '11cb2199493b2f8a3b53e7f19068fc6aac760991', + 'dev_requirement' => false, + ), + 'webmozart/path-util' => array( + 'pretty_version' => '2.3.0', + 'version' => '2.3.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../webmozart/path-util', + 'aliases' => array(), + 'reference' => 'd939f7edc24c9a1bb9c0dee5cb05d8e859490725', + 'dev_requirement' => false, + ), + ), +); diff --git a/vendor/composer/platform_check.php b/vendor/composer/platform_check.php new file mode 100644 index 0000000000..580fa96095 --- /dev/null +++ b/vendor/composer/platform_check.php @@ -0,0 +1,26 @@ += 70400)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 7.4.0". You are running ' . PHP_VERSION . '.'; +} + +if ($issues) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); + } elseif (!headers_sent()) { + echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; + } + } + trigger_error( + 'Composer detected issues in your platform: ' . implode(' ', $issues), + E_USER_ERROR + ); +} diff --git a/vendor/consolidation/annotated-command/.editorconfig b/vendor/consolidation/annotated-command/.editorconfig new file mode 100644 index 0000000000..095771e673 --- /dev/null +++ b/vendor/consolidation/annotated-command/.editorconfig @@ -0,0 +1,15 @@ +# This file is for unifying the coding style for different editors and IDEs +# editorconfig.org + +root = true + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[**.php] +indent_style = space +indent_size = 4 + diff --git a/vendor/consolidation/annotated-command/.scenarios.lock/finder5/.gitignore b/vendor/consolidation/annotated-command/.scenarios.lock/finder5/.gitignore new file mode 100644 index 0000000000..5657f6ea7d --- /dev/null +++ b/vendor/consolidation/annotated-command/.scenarios.lock/finder5/.gitignore @@ -0,0 +1 @@ +vendor \ No newline at end of file diff --git a/vendor/consolidation/annotated-command/.scenarios.lock/finder5/composer.json b/vendor/consolidation/annotated-command/.scenarios.lock/finder5/composer.json new file mode 100644 index 0000000000..547e43836e --- /dev/null +++ b/vendor/consolidation/annotated-command/.scenarios.lock/finder5/composer.json @@ -0,0 +1,62 @@ +{ + "name": "consolidation/annotated-command", + "description": "Initialize Symfony Console commands from annotated command class methods.", + "license": "MIT", + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "autoload": { + "psr-4": { + "Consolidation\\AnnotatedCommand\\": "../../src" + } + }, + "autoload-dev": { + "psr-4": { + "Consolidation\\TestUtils\\": "../../tests/src" + } + }, + "require": { + "symfony/finder": "^5", + "php": ">=5.4.5", + "consolidation/output-formatters": "^3.5.1", + "psr/log": "^1", + "symfony/console": "^2.8|^3|^4", + "symfony/event-dispatcher": "^2.5|^3|^4" + }, + "require-dev": { + "phpunit/phpunit": "^6", + "php-coveralls/php-coveralls": "^1", + "g1a/composer-test-scenarios": "^3", + "squizlabs/php_codesniffer": "^2.7" + }, + "config": { + "platform": { + "php": "7.2.5" + }, + "optimize-autoloader": true, + "sort-packages": true, + "vendor-dir": "../../vendor" + }, + "scripts": { + "cs": "phpcs --standard=PSR2 -n src", + "cbf": "phpcbf --standard=PSR2 -n src", + "unit": "SHELL_INTERACTIVE=true phpunit --colors=always", + "lint": [ + "find src -name '*.php' -print0 | xargs -0 -n1 php -l", + "find tests/src -name '*.php' -print0 | xargs -0 -n1 php -l" + ], + "test": [ + "@lint", + "@unit", + "@cs" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + } +} diff --git a/vendor/consolidation/annotated-command/.scenarios.lock/finder5/composer.lock b/vendor/consolidation/annotated-command/.scenarios.lock/finder5/composer.lock new file mode 100644 index 0000000000..ede601c4ea --- /dev/null +++ b/vendor/consolidation/annotated-command/.scenarios.lock/finder5/composer.lock @@ -0,0 +1,3037 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "33b85345d55a3898a6a929558d951f57", + "packages": [ + { + "name": "consolidation/output-formatters", + "version": "3.5.1", + "source": { + "type": "git", + "url": "https://github.com/consolidation/output-formatters.git", + "reference": "0d38f13051ef05c223a2bb8e962d668e24785196" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/0d38f13051ef05c223a2bb8e962d668e24785196", + "reference": "0d38f13051ef05c223a2bb8e962d668e24785196", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^1.1.0", + "php": ">=5.4.0", + "symfony/console": "^2.8|^3|^4", + "symfony/finder": "^2.5|^3|^4|^5" + }, + "require-dev": { + "g1a/composer-test-scenarios": "^3", + "php-coveralls/php-coveralls": "^1", + "phpunit/phpunit": "^5.7.27", + "squizlabs/php_codesniffer": "^2.7", + "symfony/var-dumper": "^2.8|^3|^4", + "victorjonsson/markdowndocs": "^1.3" + }, + "suggest": { + "symfony/var-dumper": "For using the var_dump formatter" + }, + "type": "library", + "extra": { + "scenarios": { + "finder5": { + "require": { + "symfony/finder": "^5" + }, + "config": { + "platform": { + "php": "7.2.5" + } + } + }, + "symfony4": { + "require": { + "symfony/console": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^6" + }, + "config": { + "platform": { + "php": "7.1.3" + } + } + }, + "symfony3": { + "require": { + "symfony/console": "^3.4", + "symfony/finder": "^3.4", + "symfony/var-dumper": "^3.4" + }, + "config": { + "platform": { + "php": "5.6.32" + } + } + }, + "symfony2": { + "require": { + "symfony/console": "^2.8" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36" + }, + "remove": [ + "php-coveralls/php-coveralls" + ], + "config": { + "platform": { + "php": "5.4.8" + } + }, + "scenario-options": { + "create-lockfile": "false" + } + } + }, + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Consolidation\\OutputFormatters\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Format text by applying transformations provided by plug-in formatters.", + "time": "2020-10-11T04:15:32+00:00" + }, + { + "name": "dflydev/dot-access-data", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/3fbd874921ab2c041e899d044585a2ab9795df8a", + "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "Dflydev\\DotAccessData": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "time": "2017-01-20T21:14:22+00:00" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14T16:28:37+00:00" + }, + { + "name": "psr/log", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2020-03-23T09:12:05+00:00" + }, + { + "name": "symfony/console", + "version": "v4.4.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "90933b39c7b312fc3ceaa1ddeac7eb48cb953124" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/90933b39c7b312fc3ceaa1ddeac7eb48cb953124", + "reference": "90933b39c7b312fc3ceaa1ddeac7eb48cb953124", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/polyfill-php80": "^1.15", + "symfony/service-contracts": "^1.1|^2" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/event-dispatcher": "<4.3|>=5", + "symfony/lock": "<4.4", + "symfony/process": "<3.3" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/event-dispatcher": "^4.3", + "symfony/lock": "^4.4|^5.0", + "symfony/process": "^3.4|^4.0|^5.0", + "symfony/var-dumper": "^4.3|^5.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-15T07:58:55+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v4.4.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "e17bb5e0663dc725f7cdcafc932132735b4725cd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/e17bb5e0663dc725f7cdcafc932132735b4725cd", + "reference": "e17bb5e0663dc725f7cdcafc932132735b4725cd", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/event-dispatcher-contracts": "^1.1" + }, + "conflict": { + "symfony/dependency-injection": "<3.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "1.1" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/error-handler": "~3.4|~4.4", + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/http-foundation": "^3.4|^4.0|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/stopwatch": "^3.4|^4.0|^5.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-18T14:07:46+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v1.1.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "84e23fdcd2517bf37aecbd16967e83f0caee25a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/84e23fdcd2517bf37aecbd16967e83f0caee25a7", + "reference": "84e23fdcd2517bf37aecbd16967e83f0caee25a7", + "shasum": "" + }, + "require": { + "php": ">=7.1.3" + }, + "suggest": { + "psr/event-dispatcher": "", + "symfony/event-dispatcher-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-06T13:19:58+00:00" + }, + { + "name": "symfony/finder", + "version": "v5.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "2c3ba7ad6884e6c4451ce2340e2dc23f6fa3e0d8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/2c3ba7ad6884e6c4451ce2340e2dc23f6fa3e0d8", + "reference": "2c3ba7ad6884e6c4451ce2340e2dc23f6fa3e0d8", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-02T16:23:27+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.18.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/a6977d63bf9a0ad4c65cd352709e230876f9904a", + "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-14T12:35:20+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.18.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "fffa1a52a023e782cdcc221d781fe1ec8f87fcca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fffa1a52a023e782cdcc221d781fe1ec8f87fcca", + "reference": "fffa1a52a023e782cdcc221d781fe1ec8f87fcca", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-14T12:35:20+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.18.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "d87d5766cbf48d72388a9f6b85f280c8ad51f981" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/d87d5766cbf48d72388a9f6b85f280c8ad51f981", + "reference": "d87d5766cbf48d72388a9f6b85f280c8ad51f981", + "shasum": "" + }, + "require": { + "php": ">=7.0.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-14T12:35:20+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d15da7ba4957ffb8f1747218be9e1a121fd298a1", + "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.0" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-07T11:33:47+00:00" + } + ], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "f350df0268e904597e3bd9c4685c53e0e333feea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/f350df0268e904597e3bd9c4685c53e0e333feea", + "reference": "f350df0268e904597e3bd9c4685c53e0e333feea", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2020-05-29T17:27:14+00:00" + }, + { + "name": "g1a/composer-test-scenarios", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/g1a/composer-test-scenarios.git", + "reference": "e7394206d845fd593d325440507fb940bef8cb62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/g1a/composer-test-scenarios/zipball/e7394206d845fd593d325440507fb940bef8cb62", + "reference": "e7394206d845fd593d325440507fb940bef8cb62", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0.0 || ^2.0.0", + "php": ">=5.4" + }, + "require-dev": { + "composer/composer": "^1.10.6 || ^2.0@rc", + "php-coveralls/php-coveralls": "^1.0", + "phpunit/phpunit": "^4.8.36|^6", + "squizlabs/php_codesniffer": "^3.5" + }, + "bin": [ + "scripts/dependency-licenses" + ], + "type": "composer-plugin", + "extra": { + "class": "ComposerTestScenarios\\Plugin", + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "ComposerTestScenarios\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Useful scripts for testing multiple sets of Composer dependencies.", + "time": "2020-09-28T20:54:35+00:00" + }, + { + "name": "guzzle/guzzle", + "version": "v3.8.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/4de0618a01b34aa1c8c33a3f13f396dcd3882eba", + "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3.3", + "symfony/event-dispatcher": ">=2.1" + }, + "replace": { + "guzzle/batch": "self.version", + "guzzle/cache": "self.version", + "guzzle/common": "self.version", + "guzzle/http": "self.version", + "guzzle/inflection": "self.version", + "guzzle/iterator": "self.version", + "guzzle/log": "self.version", + "guzzle/parser": "self.version", + "guzzle/plugin": "self.version", + "guzzle/plugin-async": "self.version", + "guzzle/plugin-backoff": "self.version", + "guzzle/plugin-cache": "self.version", + "guzzle/plugin-cookie": "self.version", + "guzzle/plugin-curlauth": "self.version", + "guzzle/plugin-error-response": "self.version", + "guzzle/plugin-history": "self.version", + "guzzle/plugin-log": "self.version", + "guzzle/plugin-md5": "self.version", + "guzzle/plugin-mock": "self.version", + "guzzle/plugin-oauth": "self.version", + "guzzle/service": "self.version", + "guzzle/stream": "self.version" + }, + "require-dev": { + "doctrine/cache": "*", + "monolog/monolog": "1.*", + "phpunit/phpunit": "3.7.*", + "psr/log": "1.0.*", + "symfony/class-loader": "*", + "zendframework/zend-cache": "<2.3", + "zendframework/zend-log": "<2.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.8-dev" + } + }, + "autoload": { + "psr-0": { + "Guzzle": "src/", + "Guzzle\\Tests": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Guzzle Community", + "homepage": "https://github.com/guzzle/guzzle/contributors" + } + ], + "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "abandoned": "guzzlehttp/guzzle", + "time": "2014-01-28T22:29:15+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.10.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/969b211f9a51aa1f6c01d1d2aef56d3bd91598e5", + "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2020-06-29T13:22:24+00:00" + }, + { + "name": "phar-io/manifest", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "phar-io/version": "^1.0.1", + "php": "^5.6 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "time": "2017-03-05T18:14:27+00:00" + }, + { + "name": "phar-io/version", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "time": "2017-03-05T17:38:23+00:00" + }, + { + "name": "php-coveralls/php-coveralls", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-coveralls/php-coveralls.git", + "reference": "37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-coveralls/php-coveralls/zipball/37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad", + "reference": "37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-simplexml": "*", + "guzzle/guzzle": "^2.8 || ^3.0", + "php": "^5.3.3 || ^7.0", + "psr/log": "^1.0", + "symfony/config": "^2.1 || ^3.0 || ^4.0", + "symfony/console": "^2.1 || ^3.0 || ^4.0", + "symfony/stopwatch": "^2.0 || ^3.0 || ^4.0", + "symfony/yaml": "^2.0 || ^3.0 || ^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.4.3 || ^6.0" + }, + "suggest": { + "symfony/http-kernel": "Allows Symfony integration" + }, + "bin": [ + "bin/coveralls" + ], + "type": "library", + "autoload": { + "psr-4": { + "Satooshi\\": "src/Satooshi/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kitamura Satoshi", + "email": "with.no.parachute@gmail.com", + "homepage": "https://www.facebook.com/satooshi.jp" + } + ], + "description": "PHP client library for Coveralls API", + "homepage": "https://github.com/php-coveralls/php-coveralls", + "keywords": [ + "ci", + "coverage", + "github", + "test" + ], + "time": "2017-12-06T23:17:56+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.2.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2020-09-03T19:13:55+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "time": "2020-09-17T18:55:26+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "v1.10.3", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "451c3cd1418cf640de218914901e51b064abb093" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", + "reference": "451c3cd1418cf640de218914901e51b064abb093", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", + "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5 || ^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2020-03-05T15:02:03+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "5.3.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "c89677919c5dd6d3b3852f230a663118762218ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c89677919c5dd6d3b3852f230a663118762218ac", + "reference": "c89677919c5dd6d3b3852f230a663118762218ac", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^7.0", + "phpunit/php-file-iterator": "^1.4.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-token-stream": "^2.0.1", + "sebastian/code-unit-reverse-lookup": "^1.0.1", + "sebastian/environment": "^3.0", + "sebastian/version": "^2.0.1", + "theseer/tokenizer": "^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-xdebug": "^2.5.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2018-04-06T15:36:58+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2017-11-27T13:52:08+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "791198a2c6254db10131eecfe8c06670700904db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", + "reference": "791198a2c6254db10131eecfe8c06670700904db", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "abandoned": true, + "time": "2017-11-27T05:48:46+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "6.5.14", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "bac23fe7ff13dbdb461481f706f0e9fe746334b7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/bac23fe7ff13dbdb461481f706f0e9fe746334b7", + "reference": "bac23fe7ff13dbdb461481f706f0e9fe746334b7", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "^1.6.1", + "phar-io/manifest": "^1.0.1", + "phar-io/version": "^1.0", + "php": "^7.0", + "phpspec/prophecy": "^1.7", + "phpunit/php-code-coverage": "^5.3", + "phpunit/php-file-iterator": "^1.4.3", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-timer": "^1.0.9", + "phpunit/phpunit-mock-objects": "^5.0.9", + "sebastian/comparator": "^2.1", + "sebastian/diff": "^2.0", + "sebastian/environment": "^3.1", + "sebastian/exporter": "^3.1", + "sebastian/global-state": "^2.0", + "sebastian/object-enumerator": "^3.0.3", + "sebastian/resource-operations": "^1.0", + "sebastian/version": "^2.0.1" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2", + "phpunit/dbunit": "<3.0" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-xdebug": "*", + "phpunit/php-invoker": "^1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.5.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2019-02-01T05:22:47+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "5.0.10", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/cd1cf05c553ecfec36b170070573e540b67d3f1f", + "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.5", + "php": "^7.0", + "phpunit/php-text-template": "^1.2.1", + "sebastian/exporter": "^3.1" + }, + "conflict": { + "phpunit/phpunit": "<6.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.5.11" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "abandoned": true, + "time": "2018-08-09T05:50:03+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2017-03-04T06:30:41+00:00" + }, + { + "name": "sebastian/comparator", + "version": "2.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9", + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/diff": "^2.0 || ^3.0", + "sebastian/exporter": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2018-02-01T13:46:46+00:00" + }, + { + "name": "sebastian/diff", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2017-08-03T08:09:46+00:00" + }, + { + "name": "sebastian/environment", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2017-07-01T08:51:00+00:00" + }, + { + "name": "sebastian/exporter", + "version": "3.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e", + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2019-09-14T09:02:43+00:00" + }, + { + "name": "sebastian/global-state", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2017-04-27T15:39:26+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2017-08-03T12:35:26+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "773f97c67f28de00d397be301821b06708fca0be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", + "reference": "773f97c67f28de00d397be301821b06708fca0be", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "time": "2017-03-29T09:07:27+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2017-03-03T06:23:57+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28T20:34:47+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "2.9.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "2acf168de78487db620ab4bc524135a13cfe6745" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/2acf168de78487db620ab4bc524135a13cfe6745", + "reference": "2acf168de78487db620ab4bc524135a13cfe6745", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "bin": [ + "scripts/phpcs", + "scripts/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "classmap": [ + "CodeSniffer.php", + "CodeSniffer/CLI.php", + "CodeSniffer/Exception.php", + "CodeSniffer/File.php", + "CodeSniffer/Fixer.php", + "CodeSniffer/Report.php", + "CodeSniffer/Reporting.php", + "CodeSniffer/Sniff.php", + "CodeSniffer/Tokens.php", + "CodeSniffer/Reports/", + "CodeSniffer/Tokenizers/", + "CodeSniffer/DocGenerators/", + "CodeSniffer/Standards/AbstractPatternSniff.php", + "CodeSniffer/Standards/AbstractScopeSniff.php", + "CodeSniffer/Standards/AbstractVariableSniff.php", + "CodeSniffer/Standards/IncorrectPatternException.php", + "CodeSniffer/Standards/Generic/Sniffs/", + "CodeSniffer/Standards/MySource/Sniffs/", + "CodeSniffer/Standards/PEAR/Sniffs/", + "CodeSniffer/Standards/PSR1/Sniffs/", + "CodeSniffer/Standards/PSR2/Sniffs/", + "CodeSniffer/Standards/Squiz/Sniffs/", + "CodeSniffer/Standards/Zend/Sniffs/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2018-11-07T22:31:41+00:00" + }, + { + "name": "symfony/config", + "version": "v4.4.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "7c5a1002178a612787c291a4f515f87b19176b61" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/7c5a1002178a612787c291a4f515f87b19176b61", + "reference": "7c5a1002178a612787c291a4f515f87b19176b61", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/filesystem": "^3.4|^4.0|^5.0", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/finder": "<3.4" + }, + "require-dev": { + "symfony/event-dispatcher": "^3.4|^4.0|^5.0", + "symfony/finder": "^3.4|^4.0|^5.0", + "symfony/messenger": "^4.1|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/yaml": "^3.4|^4.0|^5.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Config Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-10-02T07:34:48+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v5.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "1a8697545a8d87b9f2f6b1d32414199cc5e20aae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/1a8697545a8d87b9f2f6b1d32414199cc5e20aae", + "reference": "1a8697545a8d87b9f2f6b1d32414199cc5e20aae", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-27T14:02:37+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.18.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "1c302646f6efc070cd46856e600e5e0684d6b454" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/1c302646f6efc070cd46856e600e5e0684d6b454", + "reference": "1c302646f6efc070cd46856e600e5e0684d6b454", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-14T12:35:20+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v4.4.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "6f89e19772cf61b3c65bab329fe0e318259fbd91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/6f89e19772cf61b3c65bab329fe0e318259fbd91", + "reference": "6f89e19772cf61b3c65bab329fe0e318259fbd91", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/service-contracts": "^1.0|^2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Stopwatch Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-02T16:08:58+00:00" + }, + { + "name": "symfony/yaml", + "version": "v4.4.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "c7885964b1eceb70b0981556d0a9b01d2d97c8d1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/c7885964b1eceb70b0981556d0a9b01d2d97c8d1", + "reference": "c7885964b1eceb70b0981556d0a9b01d2d97c8d1", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "require-dev": { + "symfony/console": "^3.4|^4.0|^5.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-27T03:36:23+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "75a63c33a8577608444246075ea0af0d052e452a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a", + "reference": "75a63c33a8577608444246075ea0af0d052e452a", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2020-07-12T23:59:07+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.9.1", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<3.9.1" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^7.5.13" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2020-07-08T17:02:28+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.4.5" + }, + "platform-dev": [], + "platform-overrides": { + "php": "7.2.5" + }, + "plugin-api-version": "1.1.0" +} diff --git a/vendor/consolidation/annotated-command/.scenarios.lock/install b/vendor/consolidation/annotated-command/.scenarios.lock/install new file mode 100755 index 0000000000..4d8a777705 --- /dev/null +++ b/vendor/consolidation/annotated-command/.scenarios.lock/install @@ -0,0 +1,61 @@ +#!/bin/bash + +SCENARIO=$1 +DEPENDENCIES=${2-install} + +# Convert the aliases 'highest', 'lowest' and 'lock' to +# the corresponding composer update command to run. +case $DEPENDENCIES in + highest) + UPDATE_COMMAND=update + ;; + lowest) + UPDATE_COMMAND='update --prefer-lowest' + ;; + lock|default|"") + UPDATE_COMMAND='' + ;; +esac + +original_name=scenarios +recommended_name=".scenarios.lock" + +base="$original_name" +if [ -d "$recommended_name" ] ; then + base="$recommended_name" +fi + +# If scenario is not specified, install the lockfile at +# the root of the project. +dir="$base/${SCENARIO}" +if [ -z "$SCENARIO" ] || [ "$SCENARIO" == "default" ] ; then + SCENARIO=default + dir=. +fi + +# Test to make sure that the selected scenario exists. +if [ ! -d "$dir" ] ; then + echo "Requested scenario '${SCENARIO}' does not exist." + exit 1 +fi + +echo +echo "::" +echo ":: Switch to ${SCENARIO} scenario" +echo "::" +echo + +set -ex + +composer -n validate --working-dir=$dir --no-check-all --ansi + +if [ ! -z "$UPDATE_COMMAND" ] ; then + composer -n --working-dir=$dir ${UPDATE_COMMAND} --prefer-dist --no-scripts +fi +composer -n --working-dir=$dir install --prefer-dist + +# If called from a CI context, print out some extra information about +# what we just installed. +if [[ -n "$CI" ]] ; then + composer -n --working-dir=$dir info +fi diff --git a/vendor/consolidation/annotated-command/.scenarios.lock/phpunit4/.gitignore b/vendor/consolidation/annotated-command/.scenarios.lock/phpunit4/.gitignore new file mode 100644 index 0000000000..5657f6ea7d --- /dev/null +++ b/vendor/consolidation/annotated-command/.scenarios.lock/phpunit4/.gitignore @@ -0,0 +1 @@ +vendor \ No newline at end of file diff --git a/vendor/consolidation/annotated-command/.scenarios.lock/phpunit4/composer.json b/vendor/consolidation/annotated-command/.scenarios.lock/phpunit4/composer.json new file mode 100644 index 0000000000..5b5e3597c5 --- /dev/null +++ b/vendor/consolidation/annotated-command/.scenarios.lock/phpunit4/composer.json @@ -0,0 +1,61 @@ +{ + "name": "consolidation/annotated-command", + "description": "Initialize Symfony Console commands from annotated command class methods.", + "license": "MIT", + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "autoload": { + "psr-4": { + "Consolidation\\AnnotatedCommand\\": "../../src" + } + }, + "autoload-dev": { + "psr-4": { + "Consolidation\\TestUtils\\": "../../tests/src" + } + }, + "require": { + "php": ">=5.4.5", + "consolidation/output-formatters": "^3.5.1", + "psr/log": "^1", + "symfony/console": "^2.8|^3|^4", + "symfony/event-dispatcher": "^2.5|^3|^4", + "symfony/finder": "^2.5|^3|^4|^5" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36", + "g1a/composer-test-scenarios": "^3", + "squizlabs/php_codesniffer": "^2.7" + }, + "config": { + "platform": { + "php": "5.4.8" + }, + "optimize-autoloader": true, + "sort-packages": true, + "vendor-dir": "../../vendor" + }, + "scripts": { + "cs": "phpcs --standard=PSR2 -n src", + "cbf": "phpcbf --standard=PSR2 -n src", + "unit": "SHELL_INTERACTIVE=true phpunit --colors=always", + "lint": [ + "find src -name '*.php' -print0 | xargs -0 -n1 php -l", + "find tests/src -name '*.php' -print0 | xargs -0 -n1 php -l" + ], + "test": [ + "@lint", + "@unit", + "@cs" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + } +} diff --git a/vendor/consolidation/annotated-command/.scenarios.lock/phpunit4/composer.lock b/vendor/consolidation/annotated-command/.scenarios.lock/phpunit4/composer.lock new file mode 100644 index 0000000000..0e162a0782 --- /dev/null +++ b/vendor/consolidation/annotated-command/.scenarios.lock/phpunit4/composer.lock @@ -0,0 +1,1718 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "d69dc077ee58063a1087d070cb72acc1", + "packages": [ + { + "name": "consolidation/output-formatters", + "version": "3.5.1", + "source": { + "type": "git", + "url": "https://github.com/consolidation/output-formatters.git", + "reference": "0d38f13051ef05c223a2bb8e962d668e24785196" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/0d38f13051ef05c223a2bb8e962d668e24785196", + "reference": "0d38f13051ef05c223a2bb8e962d668e24785196", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^1.1.0", + "php": ">=5.4.0", + "symfony/console": "^2.8|^3|^4", + "symfony/finder": "^2.5|^3|^4|^5" + }, + "require-dev": { + "g1a/composer-test-scenarios": "^3", + "php-coveralls/php-coveralls": "^1", + "phpunit/phpunit": "^5.7.27", + "squizlabs/php_codesniffer": "^2.7", + "symfony/var-dumper": "^2.8|^3|^4", + "victorjonsson/markdowndocs": "^1.3" + }, + "suggest": { + "symfony/var-dumper": "For using the var_dump formatter" + }, + "type": "library", + "extra": { + "scenarios": { + "finder5": { + "require": { + "symfony/finder": "^5" + }, + "config": { + "platform": { + "php": "7.2.5" + } + } + }, + "symfony4": { + "require": { + "symfony/console": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^6" + }, + "config": { + "platform": { + "php": "7.1.3" + } + } + }, + "symfony3": { + "require": { + "symfony/console": "^3.4", + "symfony/finder": "^3.4", + "symfony/var-dumper": "^3.4" + }, + "config": { + "platform": { + "php": "5.6.32" + } + } + }, + "symfony2": { + "require": { + "symfony/console": "^2.8" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36" + }, + "remove": [ + "php-coveralls/php-coveralls" + ], + "config": { + "platform": { + "php": "5.4.8" + } + }, + "scenario-options": { + "create-lockfile": "false" + } + } + }, + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Consolidation\\OutputFormatters\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Format text by applying transformations provided by plug-in formatters.", + "time": "2020-10-11T04:15:32+00:00" + }, + { + "name": "dflydev/dot-access-data", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/3fbd874921ab2c041e899d044585a2ab9795df8a", + "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "Dflydev\\DotAccessData": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "time": "2017-01-20T21:14:22+00:00" + }, + { + "name": "psr/log", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2020-03-23T09:12:05+00:00" + }, + { + "name": "symfony/console", + "version": "v2.8.52", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "cbcf4b5e233af15cd2bbd50dee1ccc9b7927dc12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/cbcf4b5e233af15cd2bbd50dee1ccc9b7927dc12", + "reference": "cbcf4b5e233af15cd2bbd50dee1ccc9b7927dc12", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/debug": "^2.7.2|~3.0.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/event-dispatcher": "~2.1|~3.0.0", + "symfony/process": "~2.1|~3.0.0" + }, + "suggest": { + "psr/log-implementation": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2018-11-20T15:55:20+00:00" + }, + { + "name": "symfony/debug", + "version": "v2.8.52", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "74251c8d50dd3be7c4ce0c7b862497cdc641a5d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/74251c8d50dd3be7c4ce0c7b862497cdc641a5d0", + "reference": "74251c8d50dd3be7c4ce0c7b862497cdc641a5d0", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/class-loader": "~2.2|~3.0.0", + "symfony/http-kernel": "~2.3.24|~2.5.9|^2.6.2|~3.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Debug Component", + "homepage": "https://symfony.com", + "time": "2018-11-11T11:18:13+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v2.8.52", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a77e974a5fecb4398833b0709210e3d5e334ffb0", + "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^2.0.5|~3.0.0", + "symfony/dependency-injection": "~2.6|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/stopwatch": "~2.3|~3.0.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2018-11-21T14:20:20+00:00" + }, + { + "name": "symfony/finder", + "version": "v2.8.52", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "1444eac52273e345d9b95129bf914639305a9ba4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/1444eac52273e345d9b95129bf914639305a9ba4", + "reference": "1444eac52273e345d9b95129bf914639305a9ba4", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "https://symfony.com", + "time": "2018-11-11T11:18:13+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.18.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/a6977d63bf9a0ad4c65cd352709e230876f9904a", + "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-14T12:35:20+00:00" + } + ], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2015-06-14T21:17:01+00:00" + }, + { + "name": "g1a/composer-test-scenarios", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/g1a/composer-test-scenarios.git", + "reference": "e7394206d845fd593d325440507fb940bef8cb62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/g1a/composer-test-scenarios/zipball/e7394206d845fd593d325440507fb940bef8cb62", + "reference": "e7394206d845fd593d325440507fb940bef8cb62", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0.0 || ^2.0.0", + "php": ">=5.4" + }, + "require-dev": { + "composer/composer": "^1.10.6 || ^2.0@rc", + "php-coveralls/php-coveralls": "^1.0", + "phpunit/phpunit": "^4.8.36|^6", + "squizlabs/php_codesniffer": "^3.5" + }, + "bin": [ + "scripts/dependency-licenses" + ], + "type": "composer-plugin", + "extra": { + "class": "ComposerTestScenarios\\Plugin", + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "ComposerTestScenarios\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Useful scripts for testing multiple sets of Composer dependencies.", + "time": "2020-09-28T20:54:35+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "2.0.5", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "e6a969a640b00d8daa3c66518b0405fb41ae0c4b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/e6a969a640b00d8daa3c66518b0405fb41ae0c4b", + "reference": "e6a969a640b00d8daa3c66518b0405fb41ae0c4b", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "dflydev/markdown": "~1.0", + "erusev/parsedown": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "phpDocumentor": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "mike.vanriel@naenius.com" + } + ], + "time": "2016-01-25T08:17:30+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "v1.10.3", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "451c3cd1418cf640de218914901e51b064abb093" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", + "reference": "451c3cd1418cf640de218914901e51b064abb093", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", + "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5 || ^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2020-03-05T15:02:03+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "2.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-file-iterator": "~1.3", + "phpunit/php-text-template": "~1.2", + "phpunit/php-token-stream": "~1.3", + "sebastian/environment": "^1.3.2", + "sebastian/version": "~1.0" + }, + "require-dev": { + "ext-xdebug": ">=2.1.4", + "phpunit/phpunit": "~4" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.2.1", + "ext-xmlwriter": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2015-10-06T15:47:00+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2017-11-27T13:52:08+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.4.12", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1ce90ba27c42e4e44e6d8458241466380b51fa16", + "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "abandoned": true, + "time": "2017-12-04T08:55:13+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "4.8.36", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "46023de9a91eec7dfb06cc56cb4e260017298517" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/46023de9a91eec7dfb06cc56cb4e260017298517", + "reference": "46023de9a91eec7dfb06cc56cb4e260017298517", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=5.3.3", + "phpspec/prophecy": "^1.3.1", + "phpunit/php-code-coverage": "~2.1", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "~2.3", + "sebastian/comparator": "~1.2.2", + "sebastian/diff": "~1.2", + "sebastian/environment": "~1.3", + "sebastian/exporter": "~1.2", + "sebastian/global-state": "~1.0", + "sebastian/version": "~1.0", + "symfony/yaml": "~2.1|~3.0" + }, + "suggest": { + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.8.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2017-06-21T08:07:12+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "2.3.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": ">=5.3.3", + "phpunit/php-text-template": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "abandoned": true, + "time": "2015-10-02T06:51:40+00:00" + }, + { + "name": "sebastian/comparator", + "version": "1.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2 || ~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2017-01-29T09:50:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2017-05-22T07:24:03+00:00" + }, + { + "name": "sebastian/environment", + "version": "1.3.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea", + "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8 || ^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2016-08-18T05:49:44+00:00" + }, + { + "name": "sebastian/exporter", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~1.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2016-06-17T09:04:28+00:00" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2015-10-12T03:26:01+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b19cc3298482a335a95f3016d2f8a6950f0fbcd7", + "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2016-10-03T07:41:43+00:00" + }, + { + "name": "sebastian/version", + "version": "1.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "shasum": "" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2015-06-21T13:59:46+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "2.9.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "2acf168de78487db620ab4bc524135a13cfe6745" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/2acf168de78487db620ab4bc524135a13cfe6745", + "reference": "2acf168de78487db620ab4bc524135a13cfe6745", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "bin": [ + "scripts/phpcs", + "scripts/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "classmap": [ + "CodeSniffer.php", + "CodeSniffer/CLI.php", + "CodeSniffer/Exception.php", + "CodeSniffer/File.php", + "CodeSniffer/Fixer.php", + "CodeSniffer/Report.php", + "CodeSniffer/Reporting.php", + "CodeSniffer/Sniff.php", + "CodeSniffer/Tokens.php", + "CodeSniffer/Reports/", + "CodeSniffer/Tokenizers/", + "CodeSniffer/DocGenerators/", + "CodeSniffer/Standards/AbstractPatternSniff.php", + "CodeSniffer/Standards/AbstractScopeSniff.php", + "CodeSniffer/Standards/AbstractVariableSniff.php", + "CodeSniffer/Standards/IncorrectPatternException.php", + "CodeSniffer/Standards/Generic/Sniffs/", + "CodeSniffer/Standards/MySource/Sniffs/", + "CodeSniffer/Standards/PEAR/Sniffs/", + "CodeSniffer/Standards/PSR1/Sniffs/", + "CodeSniffer/Standards/PSR2/Sniffs/", + "CodeSniffer/Standards/Squiz/Sniffs/", + "CodeSniffer/Standards/Zend/Sniffs/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2018-11-07T22:31:41+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.18.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "1c302646f6efc070cd46856e600e5e0684d6b454" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/1c302646f6efc070cd46856e600e5e0684d6b454", + "reference": "1c302646f6efc070cd46856e600e5e0684d6b454", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-14T12:35:20+00:00" + }, + { + "name": "symfony/yaml", + "version": "v2.8.52", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "02c1859112aa779d9ab394ae4f3381911d84052b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/02c1859112aa779d9ab394ae4f3381911d84052b", + "reference": "02c1859112aa779d9ab394ae4f3381911d84052b", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/polyfill-ctype": "~1.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2018-11-11T11:18:13+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.4.5" + }, + "platform-dev": [], + "platform-overrides": { + "php": "5.4.8" + }, + "plugin-api-version": "1.1.0" +} diff --git a/vendor/consolidation/annotated-command/.scenarios.lock/symfony2/.gitignore b/vendor/consolidation/annotated-command/.scenarios.lock/symfony2/.gitignore new file mode 100644 index 0000000000..88e99d50df --- /dev/null +++ b/vendor/consolidation/annotated-command/.scenarios.lock/symfony2/.gitignore @@ -0,0 +1,2 @@ +vendor +composer.lock \ No newline at end of file diff --git a/vendor/consolidation/annotated-command/.scenarios.lock/symfony2/composer.json b/vendor/consolidation/annotated-command/.scenarios.lock/symfony2/composer.json new file mode 100644 index 0000000000..88075ce814 --- /dev/null +++ b/vendor/consolidation/annotated-command/.scenarios.lock/symfony2/composer.json @@ -0,0 +1,61 @@ +{ + "name": "consolidation/annotated-command", + "description": "Initialize Symfony Console commands from annotated command class methods.", + "license": "MIT", + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "autoload": { + "psr-4": { + "Consolidation\\AnnotatedCommand\\": "../../src" + } + }, + "autoload-dev": { + "psr-4": { + "Consolidation\\TestUtils\\": "../../tests/src" + } + }, + "require": { + "symfony/console": "^2.8", + "php": ">=5.4.5", + "consolidation/output-formatters": "^3.5.1", + "psr/log": "^1", + "symfony/event-dispatcher": "^2.5|^3|^4", + "symfony/finder": "^2.5|^3|^4|^5" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36", + "g1a/composer-test-scenarios": "^3", + "squizlabs/php_codesniffer": "^2.7" + }, + "config": { + "platform": { + "php": "5.4.8" + }, + "optimize-autoloader": true, + "sort-packages": true, + "vendor-dir": "../../vendor" + }, + "scripts": { + "cs": "phpcs --standard=PSR2 -n src", + "cbf": "phpcbf --standard=PSR2 -n src", + "unit": "SHELL_INTERACTIVE=true phpunit --colors=always", + "lint": [ + "find src -name '*.php' -print0 | xargs -0 -n1 php -l", + "find tests/src -name '*.php' -print0 | xargs -0 -n1 php -l" + ], + "test": [ + "@lint", + "@unit", + "@cs" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + } +} diff --git a/vendor/consolidation/annotated-command/.scenarios.lock/symfony4/.gitignore b/vendor/consolidation/annotated-command/.scenarios.lock/symfony4/.gitignore new file mode 100644 index 0000000000..5657f6ea7d --- /dev/null +++ b/vendor/consolidation/annotated-command/.scenarios.lock/symfony4/.gitignore @@ -0,0 +1 @@ +vendor \ No newline at end of file diff --git a/vendor/consolidation/annotated-command/.scenarios.lock/symfony4/composer.json b/vendor/consolidation/annotated-command/.scenarios.lock/symfony4/composer.json new file mode 100644 index 0000000000..16c40493dd --- /dev/null +++ b/vendor/consolidation/annotated-command/.scenarios.lock/symfony4/composer.json @@ -0,0 +1,62 @@ +{ + "name": "consolidation/annotated-command", + "description": "Initialize Symfony Console commands from annotated command class methods.", + "license": "MIT", + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "autoload": { + "psr-4": { + "Consolidation\\AnnotatedCommand\\": "../../src" + } + }, + "autoload-dev": { + "psr-4": { + "Consolidation\\TestUtils\\": "../../tests/src" + } + }, + "require": { + "symfony/console": "^4.0", + "php": ">=5.4.5", + "consolidation/output-formatters": "^3.5.1", + "psr/log": "^1", + "symfony/event-dispatcher": "^2.5|^3|^4", + "symfony/finder": "^2.5|^3|^4|^5" + }, + "require-dev": { + "phpunit/phpunit": "^6", + "php-coveralls/php-coveralls": "^1", + "g1a/composer-test-scenarios": "^3", + "squizlabs/php_codesniffer": "^2.7" + }, + "config": { + "platform": { + "php": "7.1.3" + }, + "optimize-autoloader": true, + "sort-packages": true, + "vendor-dir": "../../vendor" + }, + "scripts": { + "cs": "phpcs --standard=PSR2 -n src", + "cbf": "phpcbf --standard=PSR2 -n src", + "unit": "SHELL_INTERACTIVE=true phpunit --colors=always", + "lint": [ + "find src -name '*.php' -print0 | xargs -0 -n1 php -l", + "find tests/src -name '*.php' -print0 | xargs -0 -n1 php -l" + ], + "test": [ + "@lint", + "@unit", + "@cs" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + } +} diff --git a/vendor/consolidation/annotated-command/.scenarios.lock/symfony4/composer.lock b/vendor/consolidation/annotated-command/.scenarios.lock/symfony4/composer.lock new file mode 100644 index 0000000000..f60852876d --- /dev/null +++ b/vendor/consolidation/annotated-command/.scenarios.lock/symfony4/composer.lock @@ -0,0 +1,3033 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "7e462e9eec4461a4eb39e90d105ea42c", + "packages": [ + { + "name": "consolidation/output-formatters", + "version": "3.5.1", + "source": { + "type": "git", + "url": "https://github.com/consolidation/output-formatters.git", + "reference": "0d38f13051ef05c223a2bb8e962d668e24785196" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/0d38f13051ef05c223a2bb8e962d668e24785196", + "reference": "0d38f13051ef05c223a2bb8e962d668e24785196", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^1.1.0", + "php": ">=5.4.0", + "symfony/console": "^2.8|^3|^4", + "symfony/finder": "^2.5|^3|^4|^5" + }, + "require-dev": { + "g1a/composer-test-scenarios": "^3", + "php-coveralls/php-coveralls": "^1", + "phpunit/phpunit": "^5.7.27", + "squizlabs/php_codesniffer": "^2.7", + "symfony/var-dumper": "^2.8|^3|^4", + "victorjonsson/markdowndocs": "^1.3" + }, + "suggest": { + "symfony/var-dumper": "For using the var_dump formatter" + }, + "type": "library", + "extra": { + "scenarios": { + "finder5": { + "require": { + "symfony/finder": "^5" + }, + "config": { + "platform": { + "php": "7.2.5" + } + } + }, + "symfony4": { + "require": { + "symfony/console": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^6" + }, + "config": { + "platform": { + "php": "7.1.3" + } + } + }, + "symfony3": { + "require": { + "symfony/console": "^3.4", + "symfony/finder": "^3.4", + "symfony/var-dumper": "^3.4" + }, + "config": { + "platform": { + "php": "5.6.32" + } + } + }, + "symfony2": { + "require": { + "symfony/console": "^2.8" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36" + }, + "remove": [ + "php-coveralls/php-coveralls" + ], + "config": { + "platform": { + "php": "5.4.8" + } + }, + "scenario-options": { + "create-lockfile": "false" + } + } + }, + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Consolidation\\OutputFormatters\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Format text by applying transformations provided by plug-in formatters.", + "time": "2020-10-11T04:15:32+00:00" + }, + { + "name": "dflydev/dot-access-data", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/3fbd874921ab2c041e899d044585a2ab9795df8a", + "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "Dflydev\\DotAccessData": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "time": "2017-01-20T21:14:22+00:00" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14T16:28:37+00:00" + }, + { + "name": "psr/log", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2020-03-23T09:12:05+00:00" + }, + { + "name": "symfony/console", + "version": "v4.4.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "90933b39c7b312fc3ceaa1ddeac7eb48cb953124" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/90933b39c7b312fc3ceaa1ddeac7eb48cb953124", + "reference": "90933b39c7b312fc3ceaa1ddeac7eb48cb953124", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/polyfill-php80": "^1.15", + "symfony/service-contracts": "^1.1|^2" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/event-dispatcher": "<4.3|>=5", + "symfony/lock": "<4.4", + "symfony/process": "<3.3" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/event-dispatcher": "^4.3", + "symfony/lock": "^4.4|^5.0", + "symfony/process": "^3.4|^4.0|^5.0", + "symfony/var-dumper": "^4.3|^5.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-15T07:58:55+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v4.4.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "e17bb5e0663dc725f7cdcafc932132735b4725cd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/e17bb5e0663dc725f7cdcafc932132735b4725cd", + "reference": "e17bb5e0663dc725f7cdcafc932132735b4725cd", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/event-dispatcher-contracts": "^1.1" + }, + "conflict": { + "symfony/dependency-injection": "<3.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "1.1" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/error-handler": "~3.4|~4.4", + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/http-foundation": "^3.4|^4.0|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/stopwatch": "^3.4|^4.0|^5.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-18T14:07:46+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v1.1.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "84e23fdcd2517bf37aecbd16967e83f0caee25a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/84e23fdcd2517bf37aecbd16967e83f0caee25a7", + "reference": "84e23fdcd2517bf37aecbd16967e83f0caee25a7", + "shasum": "" + }, + "require": { + "php": ">=7.1.3" + }, + "suggest": { + "psr/event-dispatcher": "", + "symfony/event-dispatcher-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-06T13:19:58+00:00" + }, + { + "name": "symfony/finder", + "version": "v4.4.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "60d08560f9aa72997c44077c40d47aa28a963230" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/60d08560f9aa72997c44077c40d47aa28a963230", + "reference": "60d08560f9aa72997c44077c40d47aa28a963230", + "shasum": "" + }, + "require": { + "php": ">=7.1.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-10-02T07:34:48+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.18.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/a6977d63bf9a0ad4c65cd352709e230876f9904a", + "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-14T12:35:20+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.18.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "fffa1a52a023e782cdcc221d781fe1ec8f87fcca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fffa1a52a023e782cdcc221d781fe1ec8f87fcca", + "reference": "fffa1a52a023e782cdcc221d781fe1ec8f87fcca", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-14T12:35:20+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.18.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "d87d5766cbf48d72388a9f6b85f280c8ad51f981" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/d87d5766cbf48d72388a9f6b85f280c8ad51f981", + "reference": "d87d5766cbf48d72388a9f6b85f280c8ad51f981", + "shasum": "" + }, + "require": { + "php": ">=7.0.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-14T12:35:20+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v1.1.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "b776d18b303a39f56c63747bcb977ad4b27aca26" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/b776d18b303a39f56c63747bcb977ad4b27aca26", + "reference": "b776d18b303a39f56c63747bcb977ad4b27aca26", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "psr/container": "^1.0" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-06T13:19:58+00:00" + } + ], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "f350df0268e904597e3bd9c4685c53e0e333feea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/f350df0268e904597e3bd9c4685c53e0e333feea", + "reference": "f350df0268e904597e3bd9c4685c53e0e333feea", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2020-05-29T17:27:14+00:00" + }, + { + "name": "g1a/composer-test-scenarios", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/g1a/composer-test-scenarios.git", + "reference": "e7394206d845fd593d325440507fb940bef8cb62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/g1a/composer-test-scenarios/zipball/e7394206d845fd593d325440507fb940bef8cb62", + "reference": "e7394206d845fd593d325440507fb940bef8cb62", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0.0 || ^2.0.0", + "php": ">=5.4" + }, + "require-dev": { + "composer/composer": "^1.10.6 || ^2.0@rc", + "php-coveralls/php-coveralls": "^1.0", + "phpunit/phpunit": "^4.8.36|^6", + "squizlabs/php_codesniffer": "^3.5" + }, + "bin": [ + "scripts/dependency-licenses" + ], + "type": "composer-plugin", + "extra": { + "class": "ComposerTestScenarios\\Plugin", + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "ComposerTestScenarios\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Useful scripts for testing multiple sets of Composer dependencies.", + "time": "2020-09-28T20:54:35+00:00" + }, + { + "name": "guzzle/guzzle", + "version": "v3.8.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/4de0618a01b34aa1c8c33a3f13f396dcd3882eba", + "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3.3", + "symfony/event-dispatcher": ">=2.1" + }, + "replace": { + "guzzle/batch": "self.version", + "guzzle/cache": "self.version", + "guzzle/common": "self.version", + "guzzle/http": "self.version", + "guzzle/inflection": "self.version", + "guzzle/iterator": "self.version", + "guzzle/log": "self.version", + "guzzle/parser": "self.version", + "guzzle/plugin": "self.version", + "guzzle/plugin-async": "self.version", + "guzzle/plugin-backoff": "self.version", + "guzzle/plugin-cache": "self.version", + "guzzle/plugin-cookie": "self.version", + "guzzle/plugin-curlauth": "self.version", + "guzzle/plugin-error-response": "self.version", + "guzzle/plugin-history": "self.version", + "guzzle/plugin-log": "self.version", + "guzzle/plugin-md5": "self.version", + "guzzle/plugin-mock": "self.version", + "guzzle/plugin-oauth": "self.version", + "guzzle/service": "self.version", + "guzzle/stream": "self.version" + }, + "require-dev": { + "doctrine/cache": "*", + "monolog/monolog": "1.*", + "phpunit/phpunit": "3.7.*", + "psr/log": "1.0.*", + "symfony/class-loader": "*", + "zendframework/zend-cache": "<2.3", + "zendframework/zend-log": "<2.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.8-dev" + } + }, + "autoload": { + "psr-0": { + "Guzzle": "src/", + "Guzzle\\Tests": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Guzzle Community", + "homepage": "https://github.com/guzzle/guzzle/contributors" + } + ], + "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "abandoned": "guzzlehttp/guzzle", + "time": "2014-01-28T22:29:15+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.10.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/969b211f9a51aa1f6c01d1d2aef56d3bd91598e5", + "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2020-06-29T13:22:24+00:00" + }, + { + "name": "phar-io/manifest", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "phar-io/version": "^1.0.1", + "php": "^5.6 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "time": "2017-03-05T18:14:27+00:00" + }, + { + "name": "phar-io/version", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "time": "2017-03-05T17:38:23+00:00" + }, + { + "name": "php-coveralls/php-coveralls", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-coveralls/php-coveralls.git", + "reference": "37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-coveralls/php-coveralls/zipball/37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad", + "reference": "37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-simplexml": "*", + "guzzle/guzzle": "^2.8 || ^3.0", + "php": "^5.3.3 || ^7.0", + "psr/log": "^1.0", + "symfony/config": "^2.1 || ^3.0 || ^4.0", + "symfony/console": "^2.1 || ^3.0 || ^4.0", + "symfony/stopwatch": "^2.0 || ^3.0 || ^4.0", + "symfony/yaml": "^2.0 || ^3.0 || ^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.4.3 || ^6.0" + }, + "suggest": { + "symfony/http-kernel": "Allows Symfony integration" + }, + "bin": [ + "bin/coveralls" + ], + "type": "library", + "autoload": { + "psr-4": { + "Satooshi\\": "src/Satooshi/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kitamura Satoshi", + "email": "with.no.parachute@gmail.com", + "homepage": "https://www.facebook.com/satooshi.jp" + } + ], + "description": "PHP client library for Coveralls API", + "homepage": "https://github.com/php-coveralls/php-coveralls", + "keywords": [ + "ci", + "coverage", + "github", + "test" + ], + "time": "2017-12-06T23:17:56+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/6568f4687e5b41b054365f9ae03fcb1ed5f2069b", + "reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2020-04-27T09:25:28+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "4.3.4", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/da3fd972d6bafd628114f7e7e036f45944b62e9c", + "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c", + "shasum": "" + }, + "require": { + "php": "^7.0", + "phpdocumentor/reflection-common": "^1.0.0 || ^2.0.0", + "phpdocumentor/type-resolver": "~0.4 || ^1.0.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "doctrine/instantiator": "^1.0.5", + "mockery/mockery": "^1.0", + "phpdocumentor/type-resolver": "0.4.*", + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2019-12-28T18:55:12+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", + "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", + "shasum": "" + }, + "require": { + "php": "^7.1", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "^7.1", + "mockery/mockery": "~1", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "time": "2019-08-22T18:11:29+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "v1.10.3", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "451c3cd1418cf640de218914901e51b064abb093" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", + "reference": "451c3cd1418cf640de218914901e51b064abb093", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", + "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5 || ^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2020-03-05T15:02:03+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "5.3.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "c89677919c5dd6d3b3852f230a663118762218ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c89677919c5dd6d3b3852f230a663118762218ac", + "reference": "c89677919c5dd6d3b3852f230a663118762218ac", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^7.0", + "phpunit/php-file-iterator": "^1.4.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-token-stream": "^2.0.1", + "sebastian/code-unit-reverse-lookup": "^1.0.1", + "sebastian/environment": "^3.0", + "sebastian/version": "^2.0.1", + "theseer/tokenizer": "^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-xdebug": "^2.5.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2018-04-06T15:36:58+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2017-11-27T13:52:08+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "791198a2c6254db10131eecfe8c06670700904db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", + "reference": "791198a2c6254db10131eecfe8c06670700904db", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "abandoned": true, + "time": "2017-11-27T05:48:46+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "6.5.14", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "bac23fe7ff13dbdb461481f706f0e9fe746334b7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/bac23fe7ff13dbdb461481f706f0e9fe746334b7", + "reference": "bac23fe7ff13dbdb461481f706f0e9fe746334b7", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "^1.6.1", + "phar-io/manifest": "^1.0.1", + "phar-io/version": "^1.0", + "php": "^7.0", + "phpspec/prophecy": "^1.7", + "phpunit/php-code-coverage": "^5.3", + "phpunit/php-file-iterator": "^1.4.3", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-timer": "^1.0.9", + "phpunit/phpunit-mock-objects": "^5.0.9", + "sebastian/comparator": "^2.1", + "sebastian/diff": "^2.0", + "sebastian/environment": "^3.1", + "sebastian/exporter": "^3.1", + "sebastian/global-state": "^2.0", + "sebastian/object-enumerator": "^3.0.3", + "sebastian/resource-operations": "^1.0", + "sebastian/version": "^2.0.1" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2", + "phpunit/dbunit": "<3.0" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-xdebug": "*", + "phpunit/php-invoker": "^1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.5.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2019-02-01T05:22:47+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "5.0.10", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/cd1cf05c553ecfec36b170070573e540b67d3f1f", + "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.5", + "php": "^7.0", + "phpunit/php-text-template": "^1.2.1", + "sebastian/exporter": "^3.1" + }, + "conflict": { + "phpunit/phpunit": "<6.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.5.11" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "abandoned": true, + "time": "2018-08-09T05:50:03+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2017-03-04T06:30:41+00:00" + }, + { + "name": "sebastian/comparator", + "version": "2.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9", + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/diff": "^2.0 || ^3.0", + "sebastian/exporter": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2018-02-01T13:46:46+00:00" + }, + { + "name": "sebastian/diff", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2017-08-03T08:09:46+00:00" + }, + { + "name": "sebastian/environment", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2017-07-01T08:51:00+00:00" + }, + { + "name": "sebastian/exporter", + "version": "3.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e", + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2019-09-14T09:02:43+00:00" + }, + { + "name": "sebastian/global-state", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2017-04-27T15:39:26+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2017-08-03T12:35:26+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "773f97c67f28de00d397be301821b06708fca0be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", + "reference": "773f97c67f28de00d397be301821b06708fca0be", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "time": "2017-03-29T09:07:27+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2017-03-03T06:23:57+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28T20:34:47+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "2.9.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "2acf168de78487db620ab4bc524135a13cfe6745" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/2acf168de78487db620ab4bc524135a13cfe6745", + "reference": "2acf168de78487db620ab4bc524135a13cfe6745", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "bin": [ + "scripts/phpcs", + "scripts/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "classmap": [ + "CodeSniffer.php", + "CodeSniffer/CLI.php", + "CodeSniffer/Exception.php", + "CodeSniffer/File.php", + "CodeSniffer/Fixer.php", + "CodeSniffer/Report.php", + "CodeSniffer/Reporting.php", + "CodeSniffer/Sniff.php", + "CodeSniffer/Tokens.php", + "CodeSniffer/Reports/", + "CodeSniffer/Tokenizers/", + "CodeSniffer/DocGenerators/", + "CodeSniffer/Standards/AbstractPatternSniff.php", + "CodeSniffer/Standards/AbstractScopeSniff.php", + "CodeSniffer/Standards/AbstractVariableSniff.php", + "CodeSniffer/Standards/IncorrectPatternException.php", + "CodeSniffer/Standards/Generic/Sniffs/", + "CodeSniffer/Standards/MySource/Sniffs/", + "CodeSniffer/Standards/PEAR/Sniffs/", + "CodeSniffer/Standards/PSR1/Sniffs/", + "CodeSniffer/Standards/PSR2/Sniffs/", + "CodeSniffer/Standards/Squiz/Sniffs/", + "CodeSniffer/Standards/Zend/Sniffs/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2018-11-07T22:31:41+00:00" + }, + { + "name": "symfony/config", + "version": "v4.4.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "7c5a1002178a612787c291a4f515f87b19176b61" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/7c5a1002178a612787c291a4f515f87b19176b61", + "reference": "7c5a1002178a612787c291a4f515f87b19176b61", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/filesystem": "^3.4|^4.0|^5.0", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/finder": "<3.4" + }, + "require-dev": { + "symfony/event-dispatcher": "^3.4|^4.0|^5.0", + "symfony/finder": "^3.4|^4.0|^5.0", + "symfony/messenger": "^4.1|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/yaml": "^3.4|^4.0|^5.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Config Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-10-02T07:34:48+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v4.4.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "ebc51494739d3b081ea543ed7c462fa73a4f74db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/ebc51494739d3b081ea543ed7c462fa73a4f74db", + "reference": "ebc51494739d3b081ea543ed7c462fa73a4f74db", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-ctype": "~1.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-27T13:54:16+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.18.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "1c302646f6efc070cd46856e600e5e0684d6b454" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/1c302646f6efc070cd46856e600e5e0684d6b454", + "reference": "1c302646f6efc070cd46856e600e5e0684d6b454", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-14T12:35:20+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v4.4.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "6f89e19772cf61b3c65bab329fe0e318259fbd91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/6f89e19772cf61b3c65bab329fe0e318259fbd91", + "reference": "6f89e19772cf61b3c65bab329fe0e318259fbd91", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/service-contracts": "^1.0|^2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Stopwatch Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-02T16:08:58+00:00" + }, + { + "name": "symfony/yaml", + "version": "v4.4.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "c7885964b1eceb70b0981556d0a9b01d2d97c8d1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/c7885964b1eceb70b0981556d0a9b01d2d97c8d1", + "reference": "c7885964b1eceb70b0981556d0a9b01d2d97c8d1", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "require-dev": { + "symfony/console": "^3.4|^4.0|^5.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-27T03:36:23+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "time": "2019-06-13T22:48:21+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.9.1", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<3.9.1" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^7.5.13" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2020-07-08T17:02:28+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.4.5" + }, + "platform-dev": [], + "platform-overrides": { + "php": "7.1.3" + }, + "plugin-api-version": "1.1.0" +} diff --git a/vendor/consolidation/annotated-command/CHANGELOG.md b/vendor/consolidation/annotated-command/CHANGELOG.md new file mode 100644 index 0000000000..c62d338618 --- /dev/null +++ b/vendor/consolidation/annotated-command/CHANGELOG.md @@ -0,0 +1,204 @@ +# Change Log + +### 2.12.2 - 2 Jan 2022 + +- Update call_user_func_array for PHP 8 (#257) + +### 2.12.1 - 10 Oct 2020 + +- Allow symfony/finder 5 (#213) + +### 2.12.0 - 8 Mar 2019 + +- Allow annotated args and options to specify their default values in their descriptions. (#186) + +### 2.11.2 - 1 Feb 2019 + +- Fix handling of old caches from 2.11.1 that introduced upgrade errors. + +### 2.11.1 - 31 Jan 2019 + +- Cache injected classes (#182) + +### 2.11.0 - 27 Jan 2019 + +- Make injection of InputInterface / OutputInterface general-purpose (#179) + +### 2.10.2 - 20 Dec 2018 + +- Fix commands that have a @param annotation for their InputInterface/OutputInterface params (#176) + +### 2.10.1 - 13 Dec 2018 + +- Add stdin handler convenience class +- Add setter to AnnotationData to suppliment existing array acces +- Update to Composer Test Scenarios 3 + +### 2.10.0 - 14 Nov 2018 + +- Add a new data type, CommandResult (#167) + +### 2.9.0 & 2.9.1 - 19 Sept 2018 + +- Improve commandfile discovery for extensions installed via Composer. (#156) + +### 2.8.5 - 18 Aug 2018 + +- Add dependencies.yml for dependencies.io +- Fix warning in AnnotatedCommandFactory when getCommandInfoListFromCache called with null. + +### 2.8.4 - 25 May 2018 + +- Use g1a/composer-test-scenarios for better PHP version matrix testing. + +### 2.8.3 - 23 Feb 2018 + +- BUGFIX: Do not shift off the command name unless it is there. (#139) +- Use test scenarios to test multiple versions of Symfony. (#136, #137) + +### 2.8.2 - 29 Nov 2017 + +- Allow Symfony 4 components. + +### 2.8.1 - 16 Oct 2017 + +- Add hook methods to allow Symfony command events to be added directly to the hook manager, givig better control of hook order. (#131) + +### 2.8.0 - 13 Oct 2017 + +- Remove phpdocumentor/reflection-docblock in favor of using a bespoke parser (#130) + +### 2.7.0 - 18 Sept 2017 + +- Add support for options with a default value of 'true' (#119) +- BUGFIX: Improve handling of options with optional values, which previously was not working correctly. (#118) + +### 2.6.1 - 18 Sep 2017 + +- Reverts to contents of the 2.4.13 release. + +### 2.5.0 & 2.5.1 - 17 Sep 2017 + +- BACKED OUT. These releases accidentally introduced breaking changes. + +### 2.4.13 - 28 Aug 2017 + +- Add a followLinks() method (#108) + +### 2.4.12 - 24 Aug 2017 + +- BUGFIX: Allow annotated commands to directly use InputInterface and OutputInterface (#106) + +### 2.4.11 - 27 July 2017 + +- Back out #102: do not change behavior of word wrap based on STDOUT redirection. + +### 2.4.10 - 21 July 2017 + +- Add a method CommandProcessor::setPassExceptions() to allow applicationsto prevent the command processor from catching exceptions thrown by command methods and hooks. (#103) + +### 2.4.9 - 20 Jul 2017 + +- Automatically disable wordwrap when the terminal is not connected to STDOUT (#102) + +### 2.4.8 - 3 Apr 2017 + +- Allow multiple annotations with the same key. These are returned as a csv, or, alternately, can be accessed as an array via the new accessor. +- Unprotect two methods for benefit of Drush help. (#99) +- BUGFIX: Remove symfony/console pin (#100) + +### 2.4.7 & 2.4.6 - 17 Mar 2017 + +- Avoid wrapping help text (#93) +- Pin symfony/console to version < 3.2.5 (#94) +- Add getExampleUsages() to AnnotatedCommand. (#92) + +### 2.4.5 - 28 Feb 2017 + +- Ensure that placeholder entries are written into the commandfile cache. (#86) + +### 2.4.4 - 27 Feb 2017 + +- BUGFIX: Avoid rewriting the command cache unless something has changed. +- BUGFIX: Ensure that the default value of options are correctly cached. + +### 2.4.2 - 24 Feb 2017 + +- Add SimpleCacheInterface as a documentation interface (not enforced). + +### 2.4.1 - 20 Feb 2017 + +- Support array options: multiple options on the commandline may be passed in to options array as an array of values. +- Add php 7.1 to the test matrix. + +### 2.4.0 - 3 Feb 2017 + +- Automatically rebuild cached commandfile data when commandfile changes. +- Provide path to command file in AnnotationData objects. +- Bugfix: Add dynamic options when user runs '--help my:command' (previously, only 'help my:command' worked). +- Bugfix: Include description of last parameter in help (was omitted if no options present) +- Add Windows testing with Appveyor + + +### 2.3.0 - 19 Jan 2017 + +- Add a command info cache to improve performance of applications with many commands +- Bugfix: Allow trailing backslashes in namespaces in CommandFileDiscovery +- Bugfix: Rename @topic to @topics + + +### 2.2.0 - 23 November 2016 + +- Support custom events +- Add xml and json output for replacement help command. Text / html format for replacement help command not available yet. + + +### 2.1.0 - 14 November 2016 + +- Add support for output formatter wordwrapping +- Fix version requirement for output-formatters in composer.json +- Use output-formatters ~3 +- Move php_codesniffer back to require-dev (moved to require by mistake) + + +### 2.0.0 - 30 September 2016 + +- **Breaking** Hooks with no command name now apply to all commands defined in the same class. This is a change of behavior from the 1.x branch, where hooks with no command name applied to a command with the same method name in a *different* class. +- **Breaking** The interfaces ValidatorInterface, ProcessResultInterface and AlterResultInterface have been updated to be passed a CommandData object, which contains an Input and Output object, plus the AnnotationData. +- **Breaking** The Symfony Command Event hook has been renamed to COMMAND_EVENT. There is a new COMMAND hook that behaves like the existing Drush command hook (i.e. the post-command event is called after the primary command method runs). +- Add an accessor function AnnotatedCommandFactory::setIncludeAllPublicMethods() to control whether all public methods of a command class, or only those with a @command annotation will be treated as commands. Default remains to treat all public methods as commands. The parameters to AnnotatedCommandFactory::createCommandsFromClass() and AnnotatedCommandFactory::createCommandsFromClassInfo() still behave the same way, but are deprecated. If omitted, the value set by the accessor will be used. +- @option and @usage annotations provided with @hook methods will be added to the help text of the command they hook. This should be done if a hook needs to add a new option, e.g. to control the behavior of the hook. +- @option annotations can now be either `@option type $name description`, or just `@option name description`. +- `@hook option` can be used to programatically add options to a command. +- A CommandInfoAltererInterface can be added via AnnotatedCommandFactory::addCommandInfoAlterer(); it will be given the opportunity to adjust every CommandInfo object parsed from a command file prior to the creation of commands. +- AnnotatedCommandFactory::setIncludeAllPublicMethods(false) may be used to require methods to be annotated with @commnad in order to be considered commands. This is in preference to the existing parameters of various command-creation methods of AnnotatedCommandFactory, which are now all deprecated in favor of this setter function. +- If a --field option is given, it will also force the output format to 'string'. +- Setter methods more consistently return $this. +- Removed PassThroughArgsInput. This class was unnecessary. + + +### 1.4.0 - 13 September 2016 + +- Add basic annotation hook capability, to allow hook functions to be attached to commands with arbitrary annotations. + + +### 1.3.0 - 8 September 2016 + +- Add ComandFileDiscovery::setSearchDepth(). The search depth applies to each search location, unless there are no search locations, in which case it applies to the base directory. + + +### 1.2.0 - 2 August 2016 + +- Support both the 2.x and 3.x versions of phpdocumentor/reflection-docblock. +- Support php 5.4. +- **Bug** Do not allow an @param docblock comment for the options to override the meaning of the options. + + +### 1.1.0 - 6 July 2016 + +- Introduce AnnotatedCommandFactory::createSelectedCommandsFromClassInfo() method. + + +### 1.0.0 - 20 May 2016 + +- First stable release. diff --git a/vendor/consolidation/annotated-command/CONTRIBUTING.md b/vendor/consolidation/annotated-command/CONTRIBUTING.md new file mode 100644 index 0000000000..7a526eb504 --- /dev/null +++ b/vendor/consolidation/annotated-command/CONTRIBUTING.md @@ -0,0 +1,31 @@ +# Contributing to Consolidation + +Thank you for your interest in contributing to the Consolidation effort! Consolidation aims to provide reusable, loosely-coupled components useful for building command-line tools. Consolidation is built on top of Symfony Console, but aims to separate the tool from the implementation details of Symfony. + +Here are some of the guidelines you should follow to make the most of your efforts: + +## Code Style Guidelines + +Consolidation adheres to the [PSR-2 Coding Style Guide](http://www.php-fig.org/psr/psr-2/) for PHP code. + +## Pull Request Guidelines + +Every pull request is run through: + + - phpcs -n --standard=PSR2 src + - phpunit + - [Scrutinizer](https://scrutinizer-ci.com/g/consolidation/annotated-command/) + +It is easy to run the unit tests and code sniffer locally; just run: + + - composer cs + +To run the code beautifier, which will fix many of the problems reported by phpcs: + + - composer cbf + +These two commands (`composer cs` and `composer cbf`) are defined in the `scripts` section of [composer.json](composer.json). + +After submitting a pull request, please examine the Scrutinizer report. It is not required to fix all Scrutinizer issues; you may ignore recommendations that you disagree with. The spacing patches produced by Scrutinizer do not conform to PSR2 standards, and therefore should never be applied. DocBlock patches may be applied at your discression. Things that Scrutinizer identifies as a bug nearly always need to be addressed. + +Pull requests must pass phpcs and phpunit in order to be merged; ideally, new functionality will also include new unit tests. diff --git a/vendor/consolidation/annotated-command/LICENSE b/vendor/consolidation/annotated-command/LICENSE new file mode 100644 index 0000000000..8bea4b7726 --- /dev/null +++ b/vendor/consolidation/annotated-command/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2016-2020 Consolidation Org Developers + + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +DEPENDENCY LICENSES: + +Name Version License +consolidation/output-formatters 3.5.1 MIT +dflydev/dot-access-data v1.1.0 MIT +psr/log 1.1.3 MIT +symfony/console v2.8.52 MIT +symfony/debug v2.8.52 MIT +symfony/event-dispatcher v2.8.52 MIT +symfony/finder v2.8.52 MIT +symfony/polyfill-mbstring v1.18.1 MIT \ No newline at end of file diff --git a/vendor/consolidation/annotated-command/README.md b/vendor/consolidation/annotated-command/README.md new file mode 100644 index 0000000000..dc1e378f68 --- /dev/null +++ b/vendor/consolidation/annotated-command/README.md @@ -0,0 +1,595 @@ +# Consolidation\AnnotatedCommand + +Initialize Symfony Console commands from annotated command class methods. + +[![Travis CI](https://travis-ci.org/consolidation/annotated-command.svg?branch=master)](https://travis-ci.org/consolidation/annotated-command) +[![Windows CI](https://ci.appveyor.com/api/projects/status/c2c4lcf43ux4c30p?svg=true)](https://ci.appveyor.com/project/greg-1-anderson/annotated-command) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/consolidation/annotated-command/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/consolidation/annotated-command/?branch=master) +[![Coverage Status](https://coveralls.io/repos/github/consolidation/annotated-command/badge.svg?branch=master)](https://coveralls.io/github/consolidation/annotated-command?branch=master) +[![License](https://poser.pugx.org/consolidation/annotated-command/license)](https://packagist.org/packages/consolidation/annotated-command) + +## Component Status + +Currently in use in [Robo](https://github.com/consolidation/Robo) (1.x+), [Drush](https://github.com/drush-ops/drush) (9.x+) and [Terminus](https://github.com/pantheon-systems/terminus) (1.x+). + +## Motivation + +Symfony Console provides a set of classes that are widely used to implement command line tools. Increasingly, it is becoming popular to use annotations to describe the characteristics of the command (e.g. its arguments, options and so on) implemented by the annotated method. + +Extant commandline tools that utilize this technique include: + +- [Robo](https://github.com/consolidation/Robo) +- [wp-cli](https://github.com/wp-cli/wp-cli) +- [Pantheon Terminus](https://github.com/pantheon-systems/terminus) + +This library provides routines to produce the Symfony\Component\Console\Command\Command from all public methods defined in the provided class. + +**Note** If you are looking for a very fast way to write a Symfony Console-base command-line tool, you should consider using [Robo](https://github.com/consolidation/Robo), which is built on top of this library, and adds additional conveniences to get you going quickly. Use [g1a/starter](https://github.com/g1a/starter) to quickly scaffold a new commandline tool. See [Using Robo as a Framework](http://robo.li/framework/). It is possible to use this project without Robo if desired, of course. + +## Library Usage + +This is a library intended to be used in some other project. Require from your composer.json file: +``` + "require": { + "consolidation/annotated-command": "^2" + }, +``` + +## Example Annotated Command Class +The public methods of the command class define its commands, and the parameters of each method define its arguments and options. The command options, if any, are declared as the last parameter of the methods. The options will be passed in as an associative array; the default options of the last parameter should list the options recognized by the command. + +The rest of the parameters are arguments. Parameters with a default value are optional; those without a default value are required. +```php +class MyCommandClass +{ + /** + * This is the my:echo command + * + * This command will concatenate two parameters. If the --flip flag + * is provided, then the result is the concatenation of two and one. + * + * @command my:echo + * @param integer $one The first parameter. + * @param integer $two The other parameter. + * @option arr An option that takes multiple values. + * @option flip Whether or not the second parameter should come first in the result. + * @aliases c + * @usage bet alpha --flip + * Concatenate "alpha" and "bet". + */ + public function myEcho($one, $two, $options = ['flip' => false]) + { + if ($options['flip']) { + return "{$two}{$one}"; + } + return "{$one}{$two}"; + } +} +``` +## Option Default Values + +The `$options` array must be an associative array whose key is the name of the option, and whose value is one of: + +- The boolean value `false`, which indicates that the option takes no value. +- A **string** containing the default value for options that may be provided a value, but are not required to. +- The special value InputOption::VALUE_REQUIRED, which indicates that the user must provide a value for the option whenever it is used. +- The special value InputOption::VALUE_OPTIONAL, which produces the following behavior: + - If the option is given a value (e.g. `--foo=bar`), then the value will be a string. + - If the option exists on the commandline, but has no value (e.g. `--foo`), then the value will be `true`. + - If the option does not exist on the commandline at all, then the value will be `null`. + - If the user explicitly sets `--foo=0`, then the value will be converted to `false`. + - LIMITATION: If any Input object other than ArgvInput (or a subclass thereof) is used, then the value will be `null` for both the no-value case (`--foo`) and the no-option case. When using a StringInput, use `--foo=1` instead of `--foo` to avoid this problem. +- The special value `true` produces the following behavior: + - If the option is given a value (e.g. `--foo=bar`), then the value will be a string. + - If the option exists on the commandline, but has no value (e.g. `--foo`), then the value will be `true`. + - If the option does not exist on the commandline at all, then the value will also be `true`. + - If the user explicitly sets `--foo=0`, then the value will be converted to `false`. + - If the user adds `--no-foo` on the commandline, then the value of `foo` will be `false`. +- An empty array, which indicates that the option may appear multiple times on the command line. + +No other values should be used for the default value. For example, `$options = ['a' => 1]` is **incorrect**; instead, use `$options = ['a' => '1']`. + +Default values for options may also be provided via the `@default` annotation. See hook alter, below. + +## Hooks + +Commandfiles may provide hooks in addition to commands. A commandfile method that contains a @hook annotation is registered as a hook instead of a command. The format of the hook annotation is: +``` +@hook type target +``` +The hook **type** determines when during the command lifecycle this hook will be called. The available hook types are described in detail below. + +The hook **target** specifies which command or commands the hook will be attached to. There are several different ways to specify the hook target. + +- The command's primary name (e.g. `my:command`) or the command's method name (e.g. myCommand) will attach the hook to only that command. +- An annotation (e.g. `@foo`) will attach the hook to any command that is annotated with the given label. +- If the target is specified as `*`, then the hook will be attached to all commands. +- If the target is omitted, then the hook will be attached to every command defined in the same class as the hook implementation. + +There are ten types of hooks in the command processing request flow: + +- [Command Event](#command-event-hook) (Symfony) + - @pre-command-event + - @command-event + - @post-command-event +- [Option](#option-event-hook) + - @pre-option + - @option + - @post-option +- [Initialize](#initialize-hook) (Symfony) + - @pre-init + - @init + - @post-init +- [Interact](#interact-hook) (Symfony) + - @pre-interact + - @interact + - @post-interact +- [Validate](#validate-hook) + - @pre-validate + - @validate + - @post-validate +- [Command](#command-hook) + - @pre-command + - @command + - @post-command +- [Process](#process-hook) + - @pre-process + - @process + - @post-process +- [Alter](#alter-hook) + - @pre-alter + - @alter + - @post-alter +- [Status](#status-hook) + - @status +- [Extract](#extract-hook) + - @extract + +In addition to these, there are two more hooks available: + +- [On-event](#on-event-hook) + - @on-event +- [Replace Command](#replace-command-hook) + - @replace-command + +The "pre" and "post" varieties of these hooks, where avalable, give more flexibility vis-a-vis hook ordering (and for consistency). Within one type of hook, the running order is undefined and not guaranteed. Note that many validate, process and alter hooks may run, but the first status or extract hook that successfully returns a result will halt processing of further hooks of the same type. + +Each hook has an interface that defines its calling conventions; however, any callable may be used when registering a hook, which is convenient if versions of PHP prior to 7.0 (with no anonymous classes) need to be supported. + +### Command Event Hook + +The command-event hook is called via the Symfony Console command event notification callback mechanism. This happens prior to event dispatching and command / option validation. Note that Symfony does not allow the $input object to be altered in this hook; any change made here will be reset, as Symfony re-parses the object. Changes to arguments and options should be done in the initialize hook (non-interactive alterations) or the interact hook (which is naturally for interactive alterations). + +### Option Event Hook + +The option event hook ([OptionHookInterface](src/Hooks/OptionHookInterface.php)) is called for a specific command, whenever it is executed, or its help command is called. Any additional options for the command may be added here by calling the `addOption` method of the provided `$command` object. Note that the option hook is only necessary for calculating dynamic options. Static options may be added via the @option annotation on any hook that uses them. See the [Alter Hook](https://github.com/consolidation/annotated-command#alter-hook) documentation below for an example. +``` +use Consolidation\AnnotatedCommand\AnnotationData; +use Symfony\Component\Console\Command\Command; + +/** + * @hook option some:command + */ +public function additionalOption(Command $command, AnnotationData $annotationData) +{ + $command->addOption( + 'dynamic', + '', + InputOption::VALUE_NONE, + 'Option added by @hook option some:command' + ); +} +``` + +### Initialize Hook + +The initialize hook ([InitializeHookInterface](src/Hooks/InitializeHookInterface.php)) runs prior to the interact hook. It may supply command arguments and options from a configuration file or other sources. It should never do any user interaction. + +The [consolidation/config](https://github.com/consolidation/config) project (which is used in [Robo PHP](https://github.com/consolidation/robo)) uses `@hook init` to automatically inject values from `config.yml` configuration files for options that were not provided on the command line. +``` +use Consolidation\AnnotatedCommand\AnnotationData; +use Symfony\Component\Console\Input\InputInterface; + +/** + * @hook init some:command + */ +public function initSomeCommand(InputInterface $input, AnnotationData $annotationData) +{ + $value = $input->getOption('some-option'); + if (!$value) { + $input->setOption('some-option', $this->generateRandomOptionValue()); + } +} +``` + +You may alter the AnnotationData here by using simple array syntax. Below, we +add an additional display field label for a Property List. + +``` +use Consolidation\AnnotatedCommand\AnnotationData; +use Symfony\Component\Console\Input\InputInterface; + +/** + * @hook init some:command + */ +public function initSomeCommand(InputInterface $input, AnnotationData $annotationData) +{ + $annotationData['field-labels'] .= "\n" . "new_field: My new field"; +} +``` + +Alternately, you may use the `set()` or `append()` methods on the AnnotationData +class. + +``` +use Consolidation\AnnotatedCommand\AnnotationData; +use Symfony\Component\Console\Input\InputInterface; + +/** + * @hook init some:command + */ +public function initSomeCommand(InputInterface $input, AnnotationData $annotationData) +{ + // Add a line to the field labels. + $annotationData->append('field-labels', "\n" . "new_field: My new field"); + // Replace all field labels. + $annotationData->set('field-labels', "one_field: My only field"); + +} +``` + +### Interact Hook + +The interact hook ([InteractorInterface](src/Hooks/InteractorInterface.php)) runs prior to argument and option validation. Required arguments and options not supplied on the command line may be provided during this phase by prompting the user. Note that the interact hook is not called if the --no-interaction flag is supplied, whereas the command-event hook and the init hook are. +``` +use Consolidation\AnnotatedCommand\AnnotationData; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * @hook interact some:command + */ +public function interact(InputInterface $input, OutputInterface $output, AnnotationData $annotationData) +{ + $io = new SymfonyStyle($input, $output); + + // If the user did not specify a password, then prompt for one. + $password = $input->getOption('password'); + if (empty($password)) { + $password = $io->askHidden("Enter a password:", function ($value) { return $value; }); + $input->setOption('password', $password); + } +} +``` + +### Validate Hook + +The purpose of the validate hook ([ValidatorInterface](src/Hooks/ValidatorInterface.php)) is to ensure the state of the targets of the current command are usabe in the context required by that command. Symfony has already validated the arguments and options prior to this hook. It is possible to alter the values of the arguments and options if necessary, although this is better done in the configure hook. A validation hook may take one of several actions: + +- Do nothing. This indicates that validation succeeded. +- Return a CommandError. Validation fails, and execution stops. The CommandError contains a status result code and a message, which is printed. +- Throw an exception. The exception is converted into a CommandError. +- Return false. Message is empty, and status is 1. Deprecated. + +The validate hook may change the arguments and options of the command by modifying the Input object in the provided CommandData parameter. Any number of validation hooks may run, but if any fails, then execution of the command stops. +``` +use Consolidation\AnnotatedCommand\CommandData; + +/** + * @hook validate some:command + */ +public function validatePassword(CommandData $commandData) +{ + $input = $commandData->input(); + $password = $input->getOption('password'); + + if (strpbrk($password, '!;$`') === false) { + throw new \Exception("Your password MUST contain at least one of the characters ! ; ` or $, for no rational reason whatsoever."); + } +} +``` + +### Command Hook + +The command hook is provided for semantic purposes. The pre-command and command hooks are equivalent to the post-validate hook, and should confirm to the interface ([ValidatorInterface](src/Hooks/ValidatorInterface.php)). All of the post-validate hooks will be called before the first pre-command hook is called. Similarly, the post-command hook is equivalent to the pre-process hook, and should implement the interface ([ProcessResultInterface](src/Hooks/ProcessResultInterface.php)). + +The command callback itself (the method annotated @command) is called after the last command hook, and prior to the first post-command hook. +``` +use Consolidation\AnnotatedCommand\CommandData; + +/** + * @hook pre-command some:command + */ +public function preCommand(CommandData $commandData) +{ + // Do something before some:command +} + +/** + * @hook post-command some:command + */ +public function postCommand($result, CommandData $commandData) +{ + // Do something after some:command +} +``` + +### Process Hook + +The process hook ([ProcessResultInterface](src/Hooks/ProcessResultInterface.php)) is specifically designed to convert a series of processing instructions into a final result. An example of this is implemented in Robo in the [CollectionProcessHook](https://github.com/consolidation/Robo/blob/master/src/Collection/CollectionProcessHook.php) class; if a Robo command returns a TaskInterface, then a Robo process hook will execute the task and return the result. This allows a pre-process hook to alter the task, e.g. by adding more operations to a task collection. + +The process hook should not be used for other purposes. +``` +use Consolidation\AnnotatedCommand\CommandData; + +/** + * @hook process some:command + */ +public function process($result, CommandData $commandData) +{ + if ($result instanceof MyInterimType) { + $result = $this->convertInterimResult($result); + } +} +``` + +### Alter Hook + +An alter hook ([AlterResultInterface](src/Hooks/AlterResultInterface.php)) changes the result object. Alter hooks should only operate on result objects of a type they explicitly recognize. They may return an object of the same type, or they may convert the object to some other type. + +If something goes wrong, and the alter hooks wishes to force the command to fail, then it may either return a CommandError object, or throw an exception. +``` +use Consolidation\AnnotatedCommand\CommandData; + +/** + * Demonstrate an alter hook with an option + * + * @hook alter some:command + * @option $alteration Alter the result of the command in some way. + * @usage some:command --alteration + */ +public function alterSomeCommand($result, CommandData $commandData) +{ + if ($commandData->input()->getOption('alteration')) { + $result[] = $this->getOneMoreRow(); + } + + return $result; +} +``` + +If an option needs to be provided with a default value, that may be done via the `@default` annotation. + +``` +use Consolidation\AnnotatedCommand\CommandData; + +/** + * Demonstrate an alter hook with an option that has a default value + * + * @hook alter some:command + * @option $name Give the result a name. + * @default $name George + * @usage some:command --name=George + */ +public function nameSomeCommand($result, CommandData $commandData) +{ + $result['name'] = $commandData->input()->getOption('name') + + return $result; +} +``` + +### Status Hook + +**DEPRECATED** + +Instead of using a Status Determiner hook, commands should simply return their exit code and result data separately using a CommandResult object. + +The status hook ([StatusDeterminerInterface](src/Hooks/StatusDeterminerInterface.php)) is responsible for determing whether a command succeeded (status code 0) or failed (status code > 0). The result object returned by a command may be a compound object that contains multiple bits of information about the command result. If the result object implements [ExitCodeInterface](ExitCodeInterface.php), then the `getExitCode()` method of the result object is called to determine what the status result code for the command should be. If ExitCodeInterface is not implemented, then all of the status hooks attached to this command are executed; the first one that successfully returns a result will stop further execution of status hooks, and the result it returned will be used as the status result code for this operation. + +If no status hook returns any result, then success is presumed. + +### Extract Hook + +**DEPRECATED** + +See [RowsOfFieldsWithMetadata in output-formatters](https://github.com/consolidation/output-formatters/blob/master/src/StructuredData/RowsOfFieldsWithMetadata.php) for an alternative that is more flexible for most use cases. + +The extract hook ([ExtractOutputInterface](src/Hooks/ExtractOutputInterface.php)) is responsible for determining what the actual rendered output for the command should be. The result object returned by a command may be a compound object that contains multiple bits of information about the command result. If the result object implements [OutputDataInterface](OutputDataInterface.php), then the `getOutputData()` method of the result object is called to determine what information should be displayed to the user as a result of the command's execution. If OutputDataInterface is not implemented, then all of the extract hooks attached to this command are executed; the first one that successfully returns output data will stop further execution of extract hooks. + +If no extract hook returns any data, then the result object itself is printed if it is a string; otherwise, no output is emitted (other than any produced by the command itself). + +### On-Event hook + +Commands can define their own custom events; to do so, they need only implement the CustomEventAwareInterface, and use the CustomEventAwareTrait. Event handlers for each custom event can then be defined using the on-event hook. + +A handler using an on-event hook looks something like the following: +``` +/** + * @hook on-event custom-event + */ +public function handlerForCustomEvent(/* arbitrary parameters, as defined by custom-event */) +{ + // do the needful, return what custom-event expects +} +``` +Then, to utilize this in a command: +``` +class MyCommands implements CustomEventAwareInterface +{ + use CustomEventAwareTrait; + + /** + * @command my-command + */ + public myCommand($options = []) + { + $handlers = $this->getCustomEventHandlers('custom-event'); + // iterate and call $handlers + } +} +``` +It is up to the command that defines the custom event to declare what the expected parameters for the callback function should be, and what the return value is and how it should be used. + +### Replace Command Hook + +The replace-command ([ReplaceCommandHookInterface](src/Hooks/ReplaceCommandHookInterface.php)) hook permits you to replace a command's method with another method of your own. + +For instance, if you'd like to replace the `foo:bar` command, you could utilize the following code: + +```php +input(), $commandData->output()); + } +} +``` +Then, an instance of 'MySymfonyStyle' will be provided to any command handler method that takes a SymfonyStyle parameter if the SymfonyStyleInjector is registered in your application's initialization code like so: +``` +$commandProcessor->parameterInjection()->register('Symfony\Component\Console\Style\SymfonyStyle', new SymfonyStyleInjector); +``` + +## Handling Standard Input + +Any Symfony command may use the provides StdinHandler to imlement commands that read from standard input. + +```php + /** + * @command example + * @option string $file + * @default $file - + */ + public function example(InputInterface $input) + { + $data = StdinHandler::selectStream($input, 'file')->contents(); + } +``` +This example will read all of the data available from the stdin stream into $data, or, alternately, will read the entire contents of the file specified via the `--file=/path` option. + +For more details, including examples of using the StdinHandle with a DI container, see the comments in [StdinHandler.php](src/Input/StdinHandler.php). + +## API Usage + +If you would like to use Annotated Commands to build a commandline tool, it is recommended that you use [Robo as a framework](http://robo.li/framework), as it will set up all of the various command classes for you. If you would like to integrate Annotated Commands into some other framework, see the sections below. + +### Set up Command Factory and Instantiate Commands + +To use annotated commands in an application, pass an instance of your command class in to AnnotatedCommandFactory::createCommandsFromClass(). The result will be a list of Commands that may be added to your application. +```php +$myCommandClassInstance = new MyCommandClass(); +$commandFactory = new AnnotatedCommandFactory(); +$commandFactory->setIncludeAllPublicMethods(true); +$commandFactory->commandProcessor()->setFormatterManager(new FormatterManager()); +$commandList = $commandFactory->createCommandsFromClass($myCommandClassInstance); +foreach ($commandList as $command) { + $application->add($command); +} +``` +You may have more than one command class, if you wish. If so, simply call AnnotatedCommandFactory::createCommandsFromClass() multiple times. + +If you do not wish every public method in your classes to be added as commands, use `AnnotatedCommandFactory::setIncludeAllPublicMethods(false)`, and only methods annotated with @command will become commands. + +Note that the `setFormatterManager()` operation is optional; omit this if not using [Consolidation/OutputFormatters](https://github.com/consolidation/output-formatters). + +A CommandInfoAltererInterface can be added via AnnotatedCommandFactory::addCommandInfoAlterer(); it will be given the opportunity to adjust every CommandInfo object parsed from a command file prior to the creation of commands. + +### Command File Discovery + +A discovery class, CommandFileDiscovery, is also provided to help find command files on the filesystem. Usage is as follows: +```php +$discovery = new CommandFileDiscovery(); +$myCommandFiles = $discovery->discover($path, '\Drupal'); +foreach ($myCommandFiles as $myCommandClass) { + $myCommandClassInstance = new $myCommandClass(); + // ... as above +} +``` +For a discussion on command file naming conventions and search locations, see https://github.com/consolidation/annotated-command/issues/12. + +If different namespaces are used at different command file paths, change the call to discover as follows: +```php +$myCommandFiles = $discovery->discover(['\Ns1' => $path1, '\Ns2' => $path2]); +``` +As a shortcut for the above, the method `discoverNamespaced()` will take the last directory name of each path, and append it to the base namespace provided. This matches the conventions used by Drupal modules, for example. + +### Configuring Output Formatts (e.g. to enable wordwrap) + +The Output Formatters project supports automatic formatting of tabular output. In order for wordwrapping to work correctly, the terminal width must be passed in to the Output Formatters handlers via `FormatterOptions::setWidth()`. + +In the Annotated Commands project, this is done via dependency injection. If a `PrepareFormatter` object is passed to `CommandProcessor::addPrepareFormatter()`, then it will be given an opportunity to set properties on the `FormatterOptions` when it is created. + +A `PrepareTerminalWidthOption` class is provided to use the Symfony Application class to fetch the terminal width, and provide it to the FormatterOptions. It is injected as follows: +```php +$terminalWidthOption = new PrepareTerminalWidthOption(); +$terminalWidthOption->setApplication($application); +$commandFactory->commandProcessor()->addPrepareFormatter($terminalWidthOption); +``` +To provide greater control over the width used, create your own `PrepareTerminalWidthOption` subclass, and adjust the width as needed. + +## Other Callbacks + +In addition to the hooks provided by the hook manager, there are additional callbacks available to alter the way the annotated command library operates. + +### Factory Listeners + +Factory listeners are notified every time a command file instance is used to create annotated commands. +``` +public function AnnotatedCommandFactory::addListener(CommandCreationListenerInterface $listener); +``` +Listeners can be used to construct command file instances as they are provided to the command factory. + +### Option Providers + +An option provider is given an opportunity to add options to a command as it is being constructed. +``` +public function AnnotatedCommandFactory::addAutomaticOptionProvider(AutomaticOptionsProviderInterface $listener); +``` +The complete CommandInfo record with all of the annotation data is available, so you can, for example, add an option `--foo` to every command whose method is annotated `@fooable`. + +### CommandInfo Alterers + +CommandInfo alterers can adjust information about a command immediately before it is created. Typically, these will be used to supply default values for annotations custom to the command, or take other actions based on the interfaces implemented by the commandfile instance. +``` +public function alterCommandInfo(CommandInfo $commandInfo, $commandFileInstance); +``` diff --git a/vendor/consolidation/annotated-command/composer.json b/vendor/consolidation/annotated-command/composer.json new file mode 100644 index 0000000000..3181527ed3 --- /dev/null +++ b/vendor/consolidation/annotated-command/composer.json @@ -0,0 +1,115 @@ +{ + "name": "consolidation/annotated-command", + "description": "Initialize Symfony Console commands from annotated command class methods.", + "license": "MIT", + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "autoload":{ + "psr-4":{ + "Consolidation\\AnnotatedCommand\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Consolidation\\TestUtils\\": "tests/src" + } + }, + "require": { + "php": ">=5.4.5", + "consolidation/output-formatters": "^3.5.1", + "psr/log": "^1", + "symfony/console": "^2.8|^3|^4", + "symfony/event-dispatcher": "^2.5|^3|^4", + "symfony/finder": "^2.5|^3|^4|^5" + }, + "require-dev": { + "phpunit/phpunit": "^6", + "php-coveralls/php-coveralls": "^1", + "g1a/composer-test-scenarios": "^3", + "squizlabs/php_codesniffer": "^2.7" + }, + "config": { + "optimize-autoloader": true, + "sort-packages": true, + "platform": { + "php": "7.0.8" + } + }, + "scripts": { + "cs": "phpcs --standard=PSR2 -n src", + "cbf": "phpcbf --standard=PSR2 -n src", + "unit": "SHELL_INTERACTIVE=true phpunit --colors=always", + "lint": [ + "find src -name '*.php' -print0 | xargs -0 -n1 php -l", + "find tests/src -name '*.php' -print0 | xargs -0 -n1 php -l" + ], + "test": [ + "@lint", + "@unit", + "@cs" + ] + }, + "extra": { + "scenarios": { + "finder5": { + "require": { + "symfony/finder": "^5" + }, + "config": { + "platform": { + "php": "7.2.5" + } + } + }, + "symfony4": { + "require": { + "symfony/console": "^4.0" + }, + "config": { + "platform": { + "php": "7.1.3" + } + } + }, + "symfony2": { + "require": { + "symfony/console": "^2.8" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36" + }, + "remove": [ + "php-coveralls/php-coveralls" + ], + "config": { + "platform": { + "php": "5.4.8" + } + }, + "scenario-options": { + "create-lockfile": "false" + } + }, + "phpunit4": { + "require-dev": { + "phpunit/phpunit": "^4.8.36" + }, + "remove": [ + "php-coveralls/php-coveralls" + ], + "config": { + "platform": { + "php": "5.4.8" + } + } + } + }, + "branch-alias": { + "dev-master": "2.x-dev" + } + } +} diff --git a/vendor/consolidation/annotated-command/composer.lock b/vendor/consolidation/annotated-command/composer.lock new file mode 100644 index 0000000000..2f4632ea7d --- /dev/null +++ b/vendor/consolidation/annotated-command/composer.lock @@ -0,0 +1,2711 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "254144594976df901ad0eaa5e3b04d3a", + "packages": [ + { + "name": "consolidation/output-formatters", + "version": "3.5.1", + "source": { + "type": "git", + "url": "https://github.com/consolidation/output-formatters.git", + "reference": "0d38f13051ef05c223a2bb8e962d668e24785196" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/0d38f13051ef05c223a2bb8e962d668e24785196", + "reference": "0d38f13051ef05c223a2bb8e962d668e24785196", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^1.1.0", + "php": ">=5.4.0", + "symfony/console": "^2.8|^3|^4", + "symfony/finder": "^2.5|^3|^4|^5" + }, + "require-dev": { + "g1a/composer-test-scenarios": "^3", + "php-coveralls/php-coveralls": "^1", + "phpunit/phpunit": "^5.7.27", + "squizlabs/php_codesniffer": "^2.7", + "symfony/var-dumper": "^2.8|^3|^4", + "victorjonsson/markdowndocs": "^1.3" + }, + "suggest": { + "symfony/var-dumper": "For using the var_dump formatter" + }, + "type": "library", + "extra": { + "scenarios": { + "finder5": { + "require": { + "symfony/finder": "^5" + }, + "config": { + "platform": { + "php": "7.2.5" + } + } + }, + "symfony4": { + "require": { + "symfony/console": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^6" + }, + "config": { + "platform": { + "php": "7.1.3" + } + } + }, + "symfony3": { + "require": { + "symfony/console": "^3.4", + "symfony/finder": "^3.4", + "symfony/var-dumper": "^3.4" + }, + "config": { + "platform": { + "php": "5.6.32" + } + } + }, + "symfony2": { + "require": { + "symfony/console": "^2.8" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36" + }, + "remove": [ + "php-coveralls/php-coveralls" + ], + "config": { + "platform": { + "php": "5.4.8" + } + }, + "scenario-options": { + "create-lockfile": "false" + } + } + }, + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Consolidation\\OutputFormatters\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Format text by applying transformations provided by plug-in formatters.", + "time": "2020-10-11T04:15:32+00:00" + }, + { + "name": "dflydev/dot-access-data", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/3fbd874921ab2c041e899d044585a2ab9795df8a", + "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "Dflydev\\DotAccessData": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "time": "2017-01-20T21:14:22+00:00" + }, + { + "name": "psr/log", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2020-03-23T09:12:05+00:00" + }, + { + "name": "symfony/console", + "version": "v3.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "b28996bc0a3b08914b2a8609163ec35b36b30685" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/b28996bc0a3b08914b2a8609163ec35b36b30685", + "reference": "b28996bc0a3b08914b2a8609163ec35b36b30685", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/debug": "~2.8|~3.0|~4.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/process": "<3.3" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.3|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "~2.8|~3.0|~4.0", + "symfony/lock": "~3.4|~4.0", + "symfony/process": "~3.3|~4.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-09T05:09:37+00:00" + }, + { + "name": "symfony/debug", + "version": "v3.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "9109e4414e684d0b75276ae203883467476d25d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/9109e4414e684d0b75276ae203883467476d25d0", + "reference": "9109e4414e684d0b75276ae203883467476d25d0", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/http-kernel": "~2.8|~3.0|~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Debug Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-08T22:19:14+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v3.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "0bb9ea263b39fce3a12ac9f78ef576bdd80dacb8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/0bb9ea263b39fce3a12ac9f78ef576bdd80dacb8", + "reference": "0bb9ea263b39fce3a12ac9f78ef576bdd80dacb8", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "conflict": { + "symfony/dependency-injection": "<3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0|~4.0", + "symfony/debug": "~3.4|~4.4", + "symfony/dependency-injection": "~3.3|~4.0", + "symfony/expression-language": "~2.8|~3.0|~4.0", + "symfony/stopwatch": "~2.8|~3.0|~4.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-18T12:06:50+00:00" + }, + { + "name": "symfony/finder", + "version": "v3.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "52140652ed31cee3dabd0c481b5577201fa769b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/52140652ed31cee3dabd0c481b5577201fa769b4", + "reference": "52140652ed31cee3dabd0c481b5577201fa769b4", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-02T16:06:40+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.18.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/a6977d63bf9a0ad4c65cd352709e230876f9904a", + "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-14T12:35:20+00:00" + } + ], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2015-06-14T21:17:01+00:00" + }, + { + "name": "g1a/composer-test-scenarios", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/g1a/composer-test-scenarios.git", + "reference": "e7394206d845fd593d325440507fb940bef8cb62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/g1a/composer-test-scenarios/zipball/e7394206d845fd593d325440507fb940bef8cb62", + "reference": "e7394206d845fd593d325440507fb940bef8cb62", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0.0 || ^2.0.0", + "php": ">=5.4" + }, + "require-dev": { + "composer/composer": "^1.10.6 || ^2.0@rc", + "php-coveralls/php-coveralls": "^1.0", + "phpunit/phpunit": "^4.8.36|^6", + "squizlabs/php_codesniffer": "^3.5" + }, + "bin": [ + "scripts/dependency-licenses" + ], + "type": "composer-plugin", + "extra": { + "class": "ComposerTestScenarios\\Plugin", + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "ComposerTestScenarios\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Useful scripts for testing multiple sets of Composer dependencies.", + "time": "2020-09-28T20:54:35+00:00" + }, + { + "name": "guzzle/guzzle", + "version": "v3.8.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/4de0618a01b34aa1c8c33a3f13f396dcd3882eba", + "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3.3", + "symfony/event-dispatcher": ">=2.1" + }, + "replace": { + "guzzle/batch": "self.version", + "guzzle/cache": "self.version", + "guzzle/common": "self.version", + "guzzle/http": "self.version", + "guzzle/inflection": "self.version", + "guzzle/iterator": "self.version", + "guzzle/log": "self.version", + "guzzle/parser": "self.version", + "guzzle/plugin": "self.version", + "guzzle/plugin-async": "self.version", + "guzzle/plugin-backoff": "self.version", + "guzzle/plugin-cache": "self.version", + "guzzle/plugin-cookie": "self.version", + "guzzle/plugin-curlauth": "self.version", + "guzzle/plugin-error-response": "self.version", + "guzzle/plugin-history": "self.version", + "guzzle/plugin-log": "self.version", + "guzzle/plugin-md5": "self.version", + "guzzle/plugin-mock": "self.version", + "guzzle/plugin-oauth": "self.version", + "guzzle/service": "self.version", + "guzzle/stream": "self.version" + }, + "require-dev": { + "doctrine/cache": "*", + "monolog/monolog": "1.*", + "phpunit/phpunit": "3.7.*", + "psr/log": "1.0.*", + "symfony/class-loader": "*", + "zendframework/zend-cache": "<2.3", + "zendframework/zend-log": "<2.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.8-dev" + } + }, + "autoload": { + "psr-0": { + "Guzzle": "src/", + "Guzzle\\Tests": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Guzzle Community", + "homepage": "https://github.com/guzzle/guzzle/contributors" + } + ], + "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "abandoned": "guzzlehttp/guzzle", + "time": "2014-01-28T22:29:15+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2017-10-19T19:58:43+00:00" + }, + { + "name": "phar-io/manifest", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "phar-io/version": "^1.0.1", + "php": "^5.6 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "time": "2017-03-05T18:14:27+00:00" + }, + { + "name": "phar-io/version", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "time": "2017-03-05T17:38:23+00:00" + }, + { + "name": "php-coveralls/php-coveralls", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-coveralls/php-coveralls.git", + "reference": "37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-coveralls/php-coveralls/zipball/37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad", + "reference": "37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-simplexml": "*", + "guzzle/guzzle": "^2.8 || ^3.0", + "php": "^5.3.3 || ^7.0", + "psr/log": "^1.0", + "symfony/config": "^2.1 || ^3.0 || ^4.0", + "symfony/console": "^2.1 || ^3.0 || ^4.0", + "symfony/stopwatch": "^2.0 || ^3.0 || ^4.0", + "symfony/yaml": "^2.0 || ^3.0 || ^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.4.3 || ^6.0" + }, + "suggest": { + "symfony/http-kernel": "Allows Symfony integration" + }, + "bin": [ + "bin/coveralls" + ], + "type": "library", + "autoload": { + "psr-4": { + "Satooshi\\": "src/Satooshi/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kitamura Satoshi", + "email": "with.no.parachute@gmail.com", + "homepage": "https://www.facebook.com/satooshi.jp" + } + ], + "description": "PHP client library for Coveralls API", + "homepage": "https://github.com/php-coveralls/php-coveralls", + "keywords": [ + "ci", + "coverage", + "github", + "test" + ], + "time": "2017-12-06T23:17:56+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2017-09-11T18:02:19+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "4.3.4", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/da3fd972d6bafd628114f7e7e036f45944b62e9c", + "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c", + "shasum": "" + }, + "require": { + "php": "^7.0", + "phpdocumentor/reflection-common": "^1.0.0 || ^2.0.0", + "phpdocumentor/type-resolver": "~0.4 || ^1.0.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "doctrine/instantiator": "^1.0.5", + "mockery/mockery": "^1.0", + "phpdocumentor/type-resolver": "0.4.*", + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2019-12-28T18:55:12+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.5.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "cf842904952e64e703800d094cdf34e715a8a3ae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/cf842904952e64e703800d094cdf34e715a8a3ae", + "reference": "cf842904952e64e703800d094cdf34e715a8a3ae", + "shasum": "" + }, + "require": { + "php": "^7.0", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "time": "2017-12-30T13:23:38+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "v1.10.3", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "451c3cd1418cf640de218914901e51b064abb093" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", + "reference": "451c3cd1418cf640de218914901e51b064abb093", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", + "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5 || ^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2020-03-05T15:02:03+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "5.3.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "c89677919c5dd6d3b3852f230a663118762218ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c89677919c5dd6d3b3852f230a663118762218ac", + "reference": "c89677919c5dd6d3b3852f230a663118762218ac", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^7.0", + "phpunit/php-file-iterator": "^1.4.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-token-stream": "^2.0.1", + "sebastian/code-unit-reverse-lookup": "^1.0.1", + "sebastian/environment": "^3.0", + "sebastian/version": "^2.0.1", + "theseer/tokenizer": "^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-xdebug": "^2.5.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2018-04-06T15:36:58+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2017-11-27T13:52:08+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "791198a2c6254db10131eecfe8c06670700904db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", + "reference": "791198a2c6254db10131eecfe8c06670700904db", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "abandoned": true, + "time": "2017-11-27T05:48:46+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "6.5.14", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "bac23fe7ff13dbdb461481f706f0e9fe746334b7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/bac23fe7ff13dbdb461481f706f0e9fe746334b7", + "reference": "bac23fe7ff13dbdb461481f706f0e9fe746334b7", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "^1.6.1", + "phar-io/manifest": "^1.0.1", + "phar-io/version": "^1.0", + "php": "^7.0", + "phpspec/prophecy": "^1.7", + "phpunit/php-code-coverage": "^5.3", + "phpunit/php-file-iterator": "^1.4.3", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-timer": "^1.0.9", + "phpunit/phpunit-mock-objects": "^5.0.9", + "sebastian/comparator": "^2.1", + "sebastian/diff": "^2.0", + "sebastian/environment": "^3.1", + "sebastian/exporter": "^3.1", + "sebastian/global-state": "^2.0", + "sebastian/object-enumerator": "^3.0.3", + "sebastian/resource-operations": "^1.0", + "sebastian/version": "^2.0.1" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2", + "phpunit/dbunit": "<3.0" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-xdebug": "*", + "phpunit/php-invoker": "^1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.5.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2019-02-01T05:22:47+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "5.0.10", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/cd1cf05c553ecfec36b170070573e540b67d3f1f", + "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.5", + "php": "^7.0", + "phpunit/php-text-template": "^1.2.1", + "sebastian/exporter": "^3.1" + }, + "conflict": { + "phpunit/phpunit": "<6.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.5.11" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "abandoned": true, + "time": "2018-08-09T05:50:03+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2017-03-04T06:30:41+00:00" + }, + { + "name": "sebastian/comparator", + "version": "2.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9", + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/diff": "^2.0 || ^3.0", + "sebastian/exporter": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2018-02-01T13:46:46+00:00" + }, + { + "name": "sebastian/diff", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2017-08-03T08:09:46+00:00" + }, + { + "name": "sebastian/environment", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2017-07-01T08:51:00+00:00" + }, + { + "name": "sebastian/exporter", + "version": "3.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e", + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2019-09-14T09:02:43+00:00" + }, + { + "name": "sebastian/global-state", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2017-04-27T15:39:26+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2017-08-03T12:35:26+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "773f97c67f28de00d397be301821b06708fca0be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", + "reference": "773f97c67f28de00d397be301821b06708fca0be", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "time": "2017-03-29T09:07:27+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2017-03-03T06:23:57+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28T20:34:47+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "2.9.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "2acf168de78487db620ab4bc524135a13cfe6745" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/2acf168de78487db620ab4bc524135a13cfe6745", + "reference": "2acf168de78487db620ab4bc524135a13cfe6745", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "bin": [ + "scripts/phpcs", + "scripts/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "classmap": [ + "CodeSniffer.php", + "CodeSniffer/CLI.php", + "CodeSniffer/Exception.php", + "CodeSniffer/File.php", + "CodeSniffer/Fixer.php", + "CodeSniffer/Report.php", + "CodeSniffer/Reporting.php", + "CodeSniffer/Sniff.php", + "CodeSniffer/Tokens.php", + "CodeSniffer/Reports/", + "CodeSniffer/Tokenizers/", + "CodeSniffer/DocGenerators/", + "CodeSniffer/Standards/AbstractPatternSniff.php", + "CodeSniffer/Standards/AbstractScopeSniff.php", + "CodeSniffer/Standards/AbstractVariableSniff.php", + "CodeSniffer/Standards/IncorrectPatternException.php", + "CodeSniffer/Standards/Generic/Sniffs/", + "CodeSniffer/Standards/MySource/Sniffs/", + "CodeSniffer/Standards/PEAR/Sniffs/", + "CodeSniffer/Standards/PSR1/Sniffs/", + "CodeSniffer/Standards/PSR2/Sniffs/", + "CodeSniffer/Standards/Squiz/Sniffs/", + "CodeSniffer/Standards/Zend/Sniffs/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2018-11-07T22:31:41+00:00" + }, + { + "name": "symfony/config", + "version": "v3.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "d061a451ff6bc170c5454f4ac9b41ad2179e3960" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/d061a451ff6bc170c5454f4ac9b41ad2179e3960", + "reference": "d061a451ff6bc170c5454f4ac9b41ad2179e3960", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/filesystem": "~2.8|~3.0|~4.0", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/dependency-injection": "<3.3", + "symfony/finder": "<3.3" + }, + "require-dev": { + "symfony/dependency-injection": "~3.3|~4.0", + "symfony/event-dispatcher": "~3.3|~4.0", + "symfony/finder": "~3.3|~4.0", + "symfony/yaml": "~3.0|~4.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Config Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-02T16:06:40+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v3.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "495646f13d051cc5a8f77a68b68313dc854080aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/495646f13d051cc5a8f77a68b68313dc854080aa", + "reference": "495646f13d051cc5a8f77a68b68313dc854080aa", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-ctype": "~1.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-02T16:06:40+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.18.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "1c302646f6efc070cd46856e600e5e0684d6b454" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/1c302646f6efc070cd46856e600e5e0684d6b454", + "reference": "1c302646f6efc070cd46856e600e5e0684d6b454", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-14T12:35:20+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v3.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "a7a98f40dcc382a332c3729a6d04b298ffbb8f1f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/a7a98f40dcc382a332c3729a6d04b298ffbb8f1f", + "reference": "a7a98f40dcc382a332c3729a6d04b298ffbb8f1f", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Stopwatch Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-03-15T09:38:08+00:00" + }, + { + "name": "symfony/yaml", + "version": "v3.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "ec3c2ac4d881a4684c1f0317d2107f1a4152bad9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/ec3c2ac4d881a4684c1f0317d2107f1a4152bad9", + "reference": "ec3c2ac4d881a4684c1f0317d2107f1a4152bad9", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "require-dev": { + "symfony/console": "~3.4|~4.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-18T15:58:55+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "time": "2019-06-13T22:48:21+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.9.1", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<3.9.1" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^7.5.13" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2020-07-08T17:02:28+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.4.5" + }, + "platform-dev": [], + "platform-overrides": { + "php": "7.0.8" + }, + "plugin-api-version": "1.1.0" +} diff --git a/vendor/consolidation/annotated-command/dependencies.yml b/vendor/consolidation/annotated-command/dependencies.yml new file mode 100644 index 0000000000..e6bc6c7eef --- /dev/null +++ b/vendor/consolidation/annotated-command/dependencies.yml @@ -0,0 +1,10 @@ +version: 2 +dependencies: +- type: php + path: / + settings: + composer_options: "" + manifest_updates: + filters: + - name: ".*" + versions: "L.Y.Y" diff --git a/vendor/consolidation/annotated-command/infection.json.dist b/vendor/consolidation/annotated-command/infection.json.dist new file mode 100644 index 0000000000..b883a216b3 --- /dev/null +++ b/vendor/consolidation/annotated-command/infection.json.dist @@ -0,0 +1,11 @@ +{ + "timeout": 10, + "source": { + "directories": [ + "src" + ] + }, + "logs": { + "text": "infection-log.txt" + } +} \ No newline at end of file diff --git a/vendor/consolidation/annotated-command/phpunit.xml.dist b/vendor/consolidation/annotated-command/phpunit.xml.dist new file mode 100644 index 0000000000..e680109d1d --- /dev/null +++ b/vendor/consolidation/annotated-command/phpunit.xml.dist @@ -0,0 +1,19 @@ + + + + tests + + + + + + + + + src + + + diff --git a/vendor/consolidation/annotated-command/src/AnnotatedCommand.php b/vendor/consolidation/annotated-command/src/AnnotatedCommand.php new file mode 100644 index 0000000000..40d26af5ce --- /dev/null +++ b/vendor/consolidation/annotated-command/src/AnnotatedCommand.php @@ -0,0 +1,350 @@ +getName(); + } + } + parent::__construct($name); + if ($commandInfo && $commandInfo->hasAnnotation('command')) { + $this->setCommandInfo($commandInfo); + $this->setCommandOptions($commandInfo); + } + } + + public function setCommandCallback($commandCallback) + { + $this->commandCallback = $commandCallback; + return $this; + } + + public function setCommandProcessor($commandProcessor) + { + $this->commandProcessor = $commandProcessor; + return $this; + } + + public function commandProcessor() + { + // If someone is using an AnnotatedCommand, and is NOT getting + // it from an AnnotatedCommandFactory OR not correctly injecting + // a command processor via setCommandProcessor() (ideally via the + // DI container), then we'll just give each annotated command its + // own command processor. This is not ideal; preferably, there would + // only be one instance of the command processor in the application. + if (!isset($this->commandProcessor)) { + $this->commandProcessor = new CommandProcessor(new HookManager()); + } + return $this->commandProcessor; + } + + public function getReturnType() + { + return $this->returnType; + } + + public function setReturnType($returnType) + { + $this->returnType = $returnType; + return $this; + } + + public function getAnnotationData() + { + return $this->annotationData; + } + + public function setAnnotationData($annotationData) + { + $this->annotationData = $annotationData; + return $this; + } + + public function getTopics() + { + return $this->topics; + } + + public function setTopics($topics) + { + $this->topics = $topics; + return $this; + } + + public function setCommandInfo($commandInfo) + { + $this->setDescription($commandInfo->getDescription()); + $this->setHelp($commandInfo->getHelp()); + $this->setAliases($commandInfo->getAliases()); + $this->setAnnotationData($commandInfo->getAnnotations()); + $this->setTopics($commandInfo->getTopics()); + foreach ($commandInfo->getExampleUsages() as $usage => $description) { + $this->addUsageOrExample($usage, $description); + } + $this->setCommandArguments($commandInfo); + $this->setReturnType($commandInfo->getReturnType()); + // Hidden commands available since Symfony 3.2 + // http://symfony.com/doc/current/console/hide_commands.html + if (method_exists($this, 'setHidden')) { + $this->setHidden($commandInfo->getHidden()); + } + return $this; + } + + public function getExampleUsages() + { + return $this->examples; + } + + protected function addUsageOrExample($usage, $description) + { + $this->addUsage($usage); + if (!empty($description)) { + $this->examples[$usage] = $description; + } + } + + public function helpAlter(\DomDocument $originalDom) + { + return HelpDocumentBuilder::alter($originalDom, $this); + } + + protected function setCommandArguments($commandInfo) + { + $this->injectedClasses = $commandInfo->getInjectedClasses(); + $this->setCommandArgumentsFromParameters($commandInfo); + return $this; + } + + protected function setCommandArgumentsFromParameters($commandInfo) + { + $args = $commandInfo->arguments()->getValues(); + foreach ($args as $name => $defaultValue) { + $description = $commandInfo->arguments()->getDescription($name); + $hasDefault = $commandInfo->arguments()->hasDefault($name); + $parameterMode = $this->getCommandArgumentMode($hasDefault, $defaultValue); + $this->addArgument($name, $parameterMode, $description, $defaultValue); + } + return $this; + } + + protected function getCommandArgumentMode($hasDefault, $defaultValue) + { + if (!$hasDefault) { + return InputArgument::REQUIRED; + } + if (is_array($defaultValue)) { + return InputArgument::IS_ARRAY; + } + return InputArgument::OPTIONAL; + } + + public function setCommandOptions($commandInfo, $automaticOptions = []) + { + $inputOptions = $commandInfo->inputOptions(); + + $this->addOptions($inputOptions + $automaticOptions, $automaticOptions); + return $this; + } + + public function addOptions($inputOptions, $automaticOptions = []) + { + foreach ($inputOptions as $name => $inputOption) { + $description = $inputOption->getDescription(); + + if (empty($description) && isset($automaticOptions[$name])) { + $description = $automaticOptions[$name]->getDescription(); + $inputOption = static::inputOptionSetDescription($inputOption, $description); + } + $this->getDefinition()->addOption($inputOption); + } + } + + protected static function inputOptionSetDescription($inputOption, $description) + { + // Recover the 'mode' value, because Symfony is stubborn + $mode = 0; + if ($inputOption->isValueRequired()) { + $mode |= InputOption::VALUE_REQUIRED; + } + if ($inputOption->isValueOptional()) { + $mode |= InputOption::VALUE_OPTIONAL; + } + if ($inputOption->isArray()) { + $mode |= InputOption::VALUE_IS_ARRAY; + } + if (!$mode) { + $mode = InputOption::VALUE_NONE; + } + + $inputOption = new InputOption( + $inputOption->getName(), + $inputOption->getShortcut(), + $mode, + $description, + $inputOption->getDefault() + ); + return $inputOption; + } + + /** + * Returns all of the hook names that may be called for this command. + * + * @return array + */ + public function getNames() + { + return HookManager::getNames($this, $this->commandCallback); + } + + /** + * Add any options to this command that are defined by hook implementations + */ + public function optionsHook() + { + $this->commandProcessor()->optionsHook( + $this, + $this->getNames(), + $this->annotationData + ); + } + + public function optionsHookForHookAnnotations($commandInfoList) + { + foreach ($commandInfoList as $commandInfo) { + $inputOptions = $commandInfo->inputOptions(); + $this->addOptions($inputOptions); + foreach ($commandInfo->getExampleUsages() as $usage => $description) { + if (!in_array($usage, $this->getUsages())) { + $this->addUsageOrExample($usage, $description); + } + } + } + } + + /** + * {@inheritdoc} + */ + protected function interact(InputInterface $input, OutputInterface $output) + { + $this->commandProcessor()->interact( + $input, + $output, + $this->getNames(), + $this->annotationData + ); + } + + protected function initialize(InputInterface $input, OutputInterface $output) + { + // Allow the hook manager a chance to provide configuration values, + // if there are any registered hooks to do that. + $this->commandProcessor()->initializeHook($input, $this->getNames(), $this->annotationData); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + // Validate, run, process, alter, handle results. + return $this->commandProcessor()->process( + $output, + $this->getNames(), + $this->commandCallback, + $this->createCommandData($input, $output) + ); + } + + /** + * This function is available for use by a class that may + * wish to extend this class rather than use annotations to + * define commands. Using this technique does allow for the + * use of annotations to define hooks. + */ + public function processResults(InputInterface $input, OutputInterface $output, $results) + { + $commandData = $this->createCommandData($input, $output); + $commandProcessor = $this->commandProcessor(); + $names = $this->getNames(); + $results = $commandProcessor->processResults( + $names, + $results, + $commandData + ); + return $commandProcessor->handleResults( + $output, + $names, + $results, + $commandData + ); + } + + protected function createCommandData(InputInterface $input, OutputInterface $output) + { + $commandData = new CommandData( + $this->annotationData, + $input, + $output + ); + + // Fetch any classes (e.g. InputInterface / OutputInterface) that + // this command's callback wants passed as a parameter and inject + // it into the command data. + $this->commandProcessor()->injectIntoCommandData($commandData, $this->injectedClasses); + + // Allow the commandData to cache the list of options with + // special default values ('null' and 'true'), as these will + // need special handling. @see CommandData::options(). + $commandData->cacheSpecialDefaults($this->getDefinition()); + + return $commandData; + } +} diff --git a/vendor/consolidation/annotated-command/src/AnnotatedCommandFactory.php b/vendor/consolidation/annotated-command/src/AnnotatedCommandFactory.php new file mode 100644 index 0000000000..9f67ef6480 --- /dev/null +++ b/vendor/consolidation/annotated-command/src/AnnotatedCommandFactory.php @@ -0,0 +1,461 @@ +dataStore = new NullCache(); + $this->commandProcessor = new CommandProcessor(new HookManager()); + $this->addAutomaticOptionProvider($this); + } + + public function setCommandProcessor(CommandProcessor $commandProcessor) + { + $this->commandProcessor = $commandProcessor; + return $this; + } + + /** + * @return CommandProcessor + */ + public function commandProcessor() + { + return $this->commandProcessor; + } + + /** + * Set the 'include all public methods flag'. If true (the default), then + * every public method of each commandFile will be used to create commands. + * If it is false, then only those public methods annotated with @command + * or @name (deprecated) will be used to create commands. + */ + public function setIncludeAllPublicMethods($includeAllPublicMethods) + { + $this->includeAllPublicMethods = $includeAllPublicMethods; + return $this; + } + + public function getIncludeAllPublicMethods() + { + return $this->includeAllPublicMethods; + } + + /** + * @return HookManager + */ + public function hookManager() + { + return $this->commandProcessor()->hookManager(); + } + + /** + * Add a listener that is notified immediately before the command + * factory creates commands from a commandFile instance. This + * listener can use this opportunity to do more setup for the commandFile, + * and so on. + * + * @param CommandCreationListenerInterface $listener + */ + public function addListener(CommandCreationListenerInterface $listener) + { + $this->listeners[] = $listener; + return $this; + } + + /** + * Add a listener that's just a simple 'callable'. + * @param callable $listener + */ + public function addListernerCallback(callable $listener) + { + $this->addListener(new CommandCreationListener($listener)); + return $this; + } + + /** + * Call all command creation listeners + * + * @param object $commandFileInstance + */ + protected function notify($commandFileInstance) + { + foreach ($this->listeners as $listener) { + $listener->notifyCommandFileAdded($commandFileInstance); + } + } + + public function addAutomaticOptionProvider(AutomaticOptionsProviderInterface $optionsProvider) + { + $this->automaticOptionsProviderList[] = $optionsProvider; + } + + public function addCommandInfoAlterer(CommandInfoAltererInterface $alterer) + { + $this->commandInfoAlterers[] = $alterer; + } + + /** + * n.b. This registers all hooks from the commandfile instance as a side-effect. + */ + public function createCommandsFromClass($commandFileInstance, $includeAllPublicMethods = null) + { + // Deprecated: avoid using the $includeAllPublicMethods in favor of the setIncludeAllPublicMethods() accessor. + if (!isset($includeAllPublicMethods)) { + $includeAllPublicMethods = $this->getIncludeAllPublicMethods(); + } + $this->notify($commandFileInstance); + $commandInfoList = $this->getCommandInfoListFromClass($commandFileInstance); + $this->registerCommandHooksFromClassInfo($commandInfoList, $commandFileInstance); + return $this->createCommandsFromClassInfo($commandInfoList, $commandFileInstance, $includeAllPublicMethods); + } + + public function getCommandInfoListFromClass($commandFileInstance) + { + $cachedCommandInfoList = $this->getCommandInfoListFromCache($commandFileInstance); + $commandInfoList = $this->createCommandInfoListFromClass($commandFileInstance, $cachedCommandInfoList); + if (!empty($commandInfoList)) { + $cachedCommandInfoList = array_merge($commandInfoList, $cachedCommandInfoList); + $this->storeCommandInfoListInCache($commandFileInstance, $cachedCommandInfoList); + } + return $cachedCommandInfoList; + } + + protected function storeCommandInfoListInCache($commandFileInstance, $commandInfoList) + { + if (!$this->hasDataStore()) { + return; + } + $cache_data = []; + $serializer = new CommandInfoSerializer(); + foreach ($commandInfoList as $i => $commandInfo) { + $cache_data[$i] = $serializer->serialize($commandInfo); + } + $className = get_class($commandFileInstance); + $this->getDataStore()->set($className, $cache_data); + } + + /** + * Get the command info list from the cache + * + * @param mixed $commandFileInstance + * @return array + */ + protected function getCommandInfoListFromCache($commandFileInstance) + { + $commandInfoList = []; + if (!is_object($commandFileInstance)) { + return []; + } + $className = get_class($commandFileInstance); + if (!$this->getDataStore()->has($className)) { + return []; + } + $deserializer = new CommandInfoDeserializer(); + + $cache_data = $this->getDataStore()->get($className); + foreach ($cache_data as $i => $data) { + if (CommandInfoDeserializer::isValidSerializedData((array)$data)) { + $commandInfoList[$i] = $deserializer->deserialize((array)$data); + } + } + return $commandInfoList; + } + + /** + * Check to see if this factory has a cache datastore. + * @return boolean + */ + public function hasDataStore() + { + return !($this->dataStore instanceof NullCache); + } + + /** + * Set a cache datastore for this factory. Any object with 'set' and + * 'get' methods is acceptable. The key is the classname being cached, + * and the value is a nested associative array of strings. + * + * TODO: Typehint this to SimpleCacheInterface + * + * This is not done currently to allow clients to use a generic cache + * store that does not itself depend on the annotated-command library. + * + * @param Mixed $dataStore + * @return type + */ + public function setDataStore($dataStore) + { + if (!($dataStore instanceof SimpleCacheInterface)) { + $dataStore = new CacheWrapper($dataStore); + } + $this->dataStore = $dataStore; + return $this; + } + + /** + * Get the data store attached to this factory. + */ + public function getDataStore() + { + return $this->dataStore; + } + + protected function createCommandInfoListFromClass($classNameOrInstance, $cachedCommandInfoList) + { + $commandInfoList = []; + + // Ignore special functions, such as __construct and __call, which + // can never be commands. + $commandMethodNames = array_filter( + get_class_methods($classNameOrInstance) ?: [], + function ($m) use ($classNameOrInstance) { + $reflectionMethod = new \ReflectionMethod($classNameOrInstance, $m); + return !$reflectionMethod->isStatic() && !preg_match('#^_#', $m); + } + ); + + foreach ($commandMethodNames as $commandMethodName) { + if (!array_key_exists($commandMethodName, $cachedCommandInfoList)) { + $commandInfo = CommandInfo::create($classNameOrInstance, $commandMethodName); + if (!static::isCommandOrHookMethod($commandInfo, $this->getIncludeAllPublicMethods())) { + $commandInfo->invalidate(); + } + $commandInfoList[$commandMethodName] = $commandInfo; + } + } + + return $commandInfoList; + } + + public function createCommandInfo($classNameOrInstance, $commandMethodName) + { + return CommandInfo::create($classNameOrInstance, $commandMethodName); + } + + public function createCommandsFromClassInfo($commandInfoList, $commandFileInstance, $includeAllPublicMethods = null) + { + // Deprecated: avoid using the $includeAllPublicMethods in favor of the setIncludeAllPublicMethods() accessor. + if (!isset($includeAllPublicMethods)) { + $includeAllPublicMethods = $this->getIncludeAllPublicMethods(); + } + return $this->createSelectedCommandsFromClassInfo( + $commandInfoList, + $commandFileInstance, + function ($commandInfo) use ($includeAllPublicMethods) { + return static::isCommandMethod($commandInfo, $includeAllPublicMethods); + } + ); + } + + public function createSelectedCommandsFromClassInfo($commandInfoList, $commandFileInstance, callable $commandSelector) + { + $commandInfoList = $this->filterCommandInfoList($commandInfoList, $commandSelector); + return array_map( + function ($commandInfo) use ($commandFileInstance) { + return $this->createCommand($commandInfo, $commandFileInstance); + }, + $commandInfoList + ); + } + + protected function filterCommandInfoList($commandInfoList, callable $commandSelector) + { + return array_filter($commandInfoList, $commandSelector); + } + + public static function isCommandOrHookMethod($commandInfo, $includeAllPublicMethods) + { + return static::isHookMethod($commandInfo) || static::isCommandMethod($commandInfo, $includeAllPublicMethods); + } + + public static function isHookMethod($commandInfo) + { + return $commandInfo->hasAnnotation('hook'); + } + + public static function isCommandMethod($commandInfo, $includeAllPublicMethods) + { + // Ignore everything labeled @hook + if (static::isHookMethod($commandInfo)) { + return false; + } + // Include everything labeled @command + if ($commandInfo->hasAnnotation('command')) { + return true; + } + // Skip anything that has a missing or invalid name. + $commandName = $commandInfo->getName(); + if (empty($commandName) || preg_match('#[^a-zA-Z0-9:_-]#', $commandName)) { + return false; + } + // Skip anything named like an accessor ('get' or 'set') + if (preg_match('#^(get[A-Z]|set[A-Z])#', $commandInfo->getMethodName())) { + return false; + } + + // Default to the setting of 'include all public methods'. + return $includeAllPublicMethods; + } + + public function registerCommandHooksFromClassInfo($commandInfoList, $commandFileInstance) + { + foreach ($commandInfoList as $commandInfo) { + if (static::isHookMethod($commandInfo)) { + $this->registerCommandHook($commandInfo, $commandFileInstance); + } + } + } + + /** + * Register a command hook given the CommandInfo for a method. + * + * The hook format is: + * + * @hook type name type + * + * For example, the pre-validate hook for the core:init command is: + * + * @hook pre-validate core:init + * + * If no command name is provided, then this hook will affect every + * command that is defined in the same file. + * + * If no hook is provided, then we will presume that ALTER_RESULT + * is intended. + * + * @param CommandInfo $commandInfo Information about the command hook method. + * @param object $commandFileInstance An instance of the CommandFile class. + */ + public function registerCommandHook(CommandInfo $commandInfo, $commandFileInstance) + { + // Ignore if the command info has no @hook + if (!static::isHookMethod($commandInfo)) { + return; + } + $hookData = $commandInfo->getAnnotation('hook'); + $hook = $this->getNthWord($hookData, 0, HookManager::ALTER_RESULT); + $commandName = $this->getNthWord($hookData, 1); + + // Register the hook + $callback = [$commandFileInstance, $commandInfo->getMethodName()]; + $this->commandProcessor()->hookManager()->add($callback, $hook, $commandName); + + // If the hook has options, then also register the commandInfo + // with the hook manager, so that we can add options and such to + // the commands they hook. + if (!$commandInfo->options()->isEmpty()) { + $this->commandProcessor()->hookManager()->recordHookOptions($commandInfo, $commandName); + } + } + + protected function getNthWord($string, $n, $default = '', $delimiter = ' ') + { + $words = explode($delimiter, $string); + if (!empty($words[$n])) { + return $words[$n]; + } + return $default; + } + + public function createCommand(CommandInfo $commandInfo, $commandFileInstance) + { + $this->alterCommandInfo($commandInfo, $commandFileInstance); + $command = new AnnotatedCommand($commandInfo->getName()); + $commandCallback = [$commandFileInstance, $commandInfo->getMethodName()]; + $command->setCommandCallback($commandCallback); + $command->setCommandProcessor($this->commandProcessor); + $command->setCommandInfo($commandInfo); + $automaticOptions = $this->callAutomaticOptionsProviders($commandInfo); + $command->setCommandOptions($commandInfo, $automaticOptions); + // Annotation commands are never bootstrap-aware, but for completeness + // we will notify on every created command, as some clients may wish to + // use this notification for some other purpose. + $this->notify($command); + return $command; + } + + /** + * Give plugins an opportunity to update the commandInfo + */ + public function alterCommandInfo(CommandInfo $commandInfo, $commandFileInstance) + { + foreach ($this->commandInfoAlterers as $alterer) { + $alterer->alterCommandInfo($commandInfo, $commandFileInstance); + } + } + + /** + * Get the options that are implied by annotations, e.g. @fields implies + * that there should be a --fields and a --format option. + * + * @return InputOption[] + */ + public function callAutomaticOptionsProviders(CommandInfo $commandInfo) + { + $automaticOptions = []; + foreach ($this->automaticOptionsProviderList as $automaticOptionsProvider) { + $automaticOptions += $automaticOptionsProvider->automaticOptions($commandInfo); + } + return $automaticOptions; + } + + /** + * Get the options that are implied by annotations, e.g. @fields implies + * that there should be a --fields and a --format option. + * + * @return InputOption[] + */ + public function automaticOptions(CommandInfo $commandInfo) + { + $automaticOptions = []; + $formatManager = $this->commandProcessor()->formatterManager(); + if ($formatManager) { + $annotationData = $commandInfo->getAnnotations()->getArrayCopy(); + $formatterOptions = new FormatterOptions($annotationData); + $dataType = $commandInfo->getReturnType(); + $automaticOptions = $formatManager->automaticOptions($formatterOptions, $dataType); + } + return $automaticOptions; + } +} diff --git a/vendor/consolidation/annotated-command/src/AnnotationData.php b/vendor/consolidation/annotated-command/src/AnnotationData.php new file mode 100644 index 0000000000..ad7523aacf --- /dev/null +++ b/vendor/consolidation/annotated-command/src/AnnotationData.php @@ -0,0 +1,44 @@ +has($key) ? CsvUtils::toString($this[$key]) : $default; + } + + public function getList($key, $default = []) + { + return $this->has($key) ? CsvUtils::toList($this[$key]) : $default; + } + + public function has($key) + { + return isset($this[$key]); + } + + public function keys() + { + return array_keys($this->getArrayCopy()); + } + + public function set($key, $value = '') + { + $this->offsetSet($key, $value); + return $this; + } + + public function append($key, $value = '') + { + $data = $this->offsetGet($key); + if (is_array($data)) { + $this->offsetSet($key, array_merge($data, $value)); + } elseif (is_scalar($data)) { + $this->offsetSet($key, $data . $value); + } + return $this; + } +} diff --git a/vendor/consolidation/annotated-command/src/Cache/CacheWrapper.php b/vendor/consolidation/annotated-command/src/Cache/CacheWrapper.php new file mode 100644 index 0000000000..ed5a5eeed7 --- /dev/null +++ b/vendor/consolidation/annotated-command/src/Cache/CacheWrapper.php @@ -0,0 +1,49 @@ +dataStore = $dataStore; + } + + /** + * Test for an entry from the cache + * @param string $key + * @return boolean + */ + public function has($key) + { + if (method_exists($this->dataStore, 'has')) { + return $this->dataStore->has($key); + } + $test = $this->dataStore->get($key); + return !empty($test); + } + + /** + * Get an entry from the cache + * @param string $key + * @return array + */ + public function get($key) + { + return (array) $this->dataStore->get($key); + } + + /** + * Store an entry in the cache + * @param string $key + * @param array $data + */ + public function set($key, $data) + { + $this->dataStore->set($key, $data); + } +} diff --git a/vendor/consolidation/annotated-command/src/Cache/NullCache.php b/vendor/consolidation/annotated-command/src/Cache/NullCache.php new file mode 100644 index 0000000000..22906b2992 --- /dev/null +++ b/vendor/consolidation/annotated-command/src/Cache/NullCache.php @@ -0,0 +1,37 @@ +listener = $listener; + } + + public function notifyCommandFileAdded($command) + { + call_user_func($this->listener, $command); + } +} diff --git a/vendor/consolidation/annotated-command/src/CommandCreationListenerInterface.php b/vendor/consolidation/annotated-command/src/CommandCreationListenerInterface.php new file mode 100644 index 0000000000..f3a50eae39 --- /dev/null +++ b/vendor/consolidation/annotated-command/src/CommandCreationListenerInterface.php @@ -0,0 +1,15 @@ +annotationData = $annotationData; + $this->input = $input; + $this->output = $output; + $this->includeOptionsInArgs = true; + } + + /** + * For internal use only; inject an instance to be passed back + * to the command callback as a parameter. + */ + public function injectInstance($injectedInstance) + { + array_unshift($this->injectedInstances, $injectedInstance); + return $this; + } + + /** + * Provide a reference to the instances that will be added to the + * beginning of the parameter list when the command callback is invoked. + */ + public function injectedInstances() + { + return $this->injectedInstances; + } + + /** + * For backwards-compatibility mode only: disable addition of + * options on the end of the arguments list. + */ + public function setIncludeOptionsInArgs($includeOptionsInArgs) + { + $this->includeOptionsInArgs = $includeOptionsInArgs; + return $this; + } + + public function annotationData() + { + return $this->annotationData; + } + + public function formatterOptions() + { + return $this->formatterOptions; + } + + public function setFormatterOptions($formatterOptions) + { + $this->formatterOptions = $formatterOptions; + } + + public function input() + { + return $this->input; + } + + public function output() + { + return $this->output; + } + + public function arguments() + { + return $this->input->getArguments(); + } + + public function options() + { + // We cannot tell the difference between '--foo' (an option without + // a value) and the absence of '--foo' when the option has an optional + // value, and the current vallue of the option is 'null' using only + // the public methods of InputInterface. We'll try to figure out + // which is which by other means here. + $options = $this->getAdjustedOptions(); + + // Make two conversions here: + // --foo=0 wil convert $value from '0' to 'false' for binary options. + // --foo with $value of 'true' will be forced to 'false' if --no-foo exists. + foreach ($options as $option => $value) { + if ($this->shouldConvertOptionToFalse($options, $option, $value)) { + $options[$option] = false; + } + } + + return $options; + } + + /** + * Use 'hasParameterOption()' to attempt to disambiguate option states. + */ + protected function getAdjustedOptions() + { + $options = $this->input->getOptions(); + + // If Input isn't an ArgvInput, then return the options as-is. + if (!$this->input instanceof ArgvInput) { + return $options; + } + + // If we have an ArgvInput, then we can determine if options + // are missing from the command line. If the option value is + // missing from $input, then we will keep the value `null`. + // If it is present, but has no explicit value, then change it its + // value to `true`. + foreach ($options as $option => $value) { + if (($value === null) && ($this->input->hasParameterOption("--$option"))) { + $options[$option] = true; + } + } + + return $options; + } + + protected function shouldConvertOptionToFalse($options, $option, $value) + { + // If the value is 'true' (e.g. the option is '--foo'), then convert + // it to false if there is also an option '--no-foo'. n.b. if the + // commandline has '--foo=bar' then $value will not be 'true', and + // --no-foo will be ignored. + if ($value === true) { + // Check if the --no-* option exists. Note that none of the other + // alteration apply in the $value == true case, so we can exit early here. + $negation_key = 'no-' . $option; + return array_key_exists($negation_key, $options) && $options[$negation_key]; + } + + // If the option is '--foo=0', convert the '0' to 'false' when appropriate. + if ($value !== '0') { + return false; + } + + // The '--foo=0' convertion is only applicable when the default value + // is not in the special defaults list. i.e. you get a literal '0' + // when your default is a string. + return in_array($option, $this->specialDefaults); + } + + public function cacheSpecialDefaults($definition) + { + foreach ($definition->getOptions() as $option => $inputOption) { + $defaultValue = $inputOption->getDefault(); + if (($defaultValue === null) || ($defaultValue === true)) { + $this->specialDefaults[] = $option; + } + } + } + + public function getArgsWithoutAppName() + { + $args = $this->arguments(); + + // When called via the Application, the first argument + // will be the command name. The Application alters the + // input definition to match, adding a 'command' argument + // to the beginning. + if ($this->input->hasArgument('command')) { + array_shift($args); + } + + return $args; + } + + public function getArgsAndOptions() + { + // Get passthrough args, and add the options on the end. + $args = $this->getArgsWithoutAppName(); + if ($this->includeOptionsInArgs) { + $args['options'] = $this->options(); + } + return $args; + } +} diff --git a/vendor/consolidation/annotated-command/src/CommandError.php b/vendor/consolidation/annotated-command/src/CommandError.php new file mode 100644 index 0000000000..bfe257bd21 --- /dev/null +++ b/vendor/consolidation/annotated-command/src/CommandError.php @@ -0,0 +1,32 @@ +message = $message; + // Ensure the exit code is non-zero. The exit code may have + // come from an exception, and those often default to zero if + // a specific value is not provided. + $this->exitCode = $exitCode == 0 ? 1 : $exitCode; + } + public function getExitCode() + { + return $this->exitCode; + } + + public function getOutputData() + { + return $this->message; + } +} diff --git a/vendor/consolidation/annotated-command/src/CommandFileDiscovery.php b/vendor/consolidation/annotated-command/src/CommandFileDiscovery.php new file mode 100644 index 0000000000..bdcce7408f --- /dev/null +++ b/vendor/consolidation/annotated-command/src/CommandFileDiscovery.php @@ -0,0 +1,468 @@ +discoverNamespaced($moduleList, '\Drupal'); + * + * To discover global commands: + * + * $commandFiles = $discovery->discover($drupalRoot, '\Drupal'); + * + * WARNING: + * + * This class is deprecated. Commandfile discovery is complicated, and does + * not work from within phar files. It is recommended to instead use a static + * list of command classes as shown in https://github.com/g1a/starter/blob/master/example + * + * For a better alternative when implementing a plugin mechanism, see + * https://robo.li/extending/#register-command-files-via-psr-4-autoloading + */ +class CommandFileDiscovery +{ + /** @var string[] */ + protected $excludeList; + /** @var string[] */ + protected $searchLocations; + /** @var string */ + protected $searchPattern = '*Commands.php'; + /** @var boolean */ + protected $includeFilesAtBase = true; + /** @var integer */ + protected $searchDepth = 2; + /** @var bool */ + protected $followLinks = false; + /** @var string[] */ + protected $strippedNamespaces; + + public function __construct() + { + $this->excludeList = ['Exclude']; + $this->searchLocations = [ + 'Command', + 'CliTools', // TODO: Maybe remove + ]; + } + + /** + * Specify whether to search for files at the base directory + * ($directoryList parameter to discover and discoverNamespaced + * methods), or only in the directories listed in the search paths. + * + * @param boolean $includeFilesAtBase + */ + public function setIncludeFilesAtBase($includeFilesAtBase) + { + $this->includeFilesAtBase = $includeFilesAtBase; + return $this; + } + + /** + * Set the list of excludes to add to the finder, replacing + * whatever was there before. + * + * @param array $excludeList The list of directory names to skip when + * searching for command files. + */ + public function setExcludeList($excludeList) + { + $this->excludeList = $excludeList; + return $this; + } + + /** + * Add one more location to the exclude list. + * + * @param string $exclude One directory name to skip when searching + * for command files. + */ + public function addExclude($exclude) + { + $this->excludeList[] = $exclude; + return $this; + } + + /** + * Set the search depth. By default, fills immediately in the + * base directory are searched, plus all of the search locations + * to this specified depth. If the search locations is set to + * an empty array, then the base directory is searched to this + * depth. + */ + public function setSearchDepth($searchDepth) + { + $this->searchDepth = $searchDepth; + return $this; + } + + /** + * Specify that the discovery object should follow symlinks. By + * default, symlinks are not followed. + */ + public function followLinks($followLinks = true) + { + $this->followLinks = $followLinks; + return $this; + } + + /** + * Set the list of search locations to examine in each directory where + * command files may be found. This replaces whatever was there before. + * + * @param array $searchLocations The list of locations to search for command files. + */ + public function setSearchLocations($searchLocations) + { + $this->searchLocations = $searchLocations; + return $this; + } + + /** + * Set a particular namespace part to ignore. This is useful in plugin + * mechanisms where the plugin is placed by Composer. + * + * For example, Drush extensions are placed in `./drush/Commands`. + * If the Composer installer path is `"drush/Commands/contrib/{$name}": ["type:drupal-drush"]`, + * then Composer will place the command files in `drush/Commands/contrib`. + * The namespace should not be any different in this instance than if + * the extension were placed in `drush/Commands`, though, so Drush therefore + * calls `ignoreNamespacePart('contrib', 'Commands')`. This causes the + * `contrib` component to be removed from the namespace if it follows + * the namespace `Commands`. If the '$base' parameter is not specified, then + * the ignored portion of the namespace may appear anywhere in the path. + */ + public function ignoreNamespacePart($ignore, $base = '') + { + $replacementPart = '\\'; + if (!empty($base)) { + $replacementPart .= $base . '\\'; + } + $ignoredPart = $replacementPart . $ignore . '\\'; + $this->strippedNamespaces[$ignoredPart] = $replacementPart; + + return $this; + } + + /** + * Add one more location to the search location list. + * + * @param string $location One more relative path to search + * for command files. + */ + public function addSearchLocation($location) + { + $this->searchLocations[] = $location; + return $this; + } + + /** + * Specify the pattern / regex used by the finder to search for + * command files. + */ + public function setSearchPattern($searchPattern) + { + $this->searchPattern = $searchPattern; + return $this; + } + + /** + * Given a list of directories, e.g. Drupal modules like: + * + * core/modules/block + * core/modules/dblog + * modules/default_content + * + * Discover command files in any of these locations. + * + * @param string|string[] $directoryList Places to search for commands. + * + * @return array + */ + public function discoverNamespaced($directoryList, $baseNamespace = '') + { + return $this->discover($this->convertToNamespacedList((array)$directoryList), $baseNamespace); + } + + /** + * Given a simple list containing paths to directories, where + * the last component of the path should appear in the namespace, + * after the base namespace, this function will return an + * associative array mapping the path's basename (e.g. the module + * name) to the directory path. + * + * Module names must be unique. + * + * @param string[] $directoryList A list of module locations + * + * @return array + */ + public function convertToNamespacedList($directoryList) + { + $namespacedArray = []; + foreach ((array)$directoryList as $directory) { + $namespacedArray[basename($directory)] = $directory; + } + return $namespacedArray; + } + + /** + * Search for command files in the specified locations. This is the function that + * should be used for all locations that are NOT modules of a framework. + * + * @param string|string[] $directoryList Places to search for commands. + * @return array + */ + public function discover($directoryList, $baseNamespace = '') + { + $commandFiles = []; + foreach ((array)$directoryList as $key => $directory) { + $itemsNamespace = $this->joinNamespace([$baseNamespace, $key]); + $commandFiles = array_merge( + $commandFiles, + $this->discoverCommandFiles($directory, $itemsNamespace), + $this->discoverCommandFiles("$directory/src", $itemsNamespace) + ); + } + return $this->fixNamespaces($commandFiles); + } + + /** + * fixNamespaces will alter the namespaces in the commandFiles + * result to remove the Composer placement directory, if any. + */ + protected function fixNamespaces($commandFiles) + { + // Do nothing unless the client told us to remove some namespace components. + if (empty($this->strippedNamespaces)) { + return $commandFiles; + } + + // Strip out any part of the namespace the client did not want. + // @see CommandFileDiscovery::ignoreNamespacePart + return array_map( + function ($fqcn) { + return str_replace( + array_keys($this->strippedNamespaces), + array_values($this->strippedNamespaces), + $fqcn + ); + }, + $commandFiles + ); + } + + /** + * Search for command files in specific locations within a single directory. + * + * In each location, we will accept only a few places where command files + * can be found. This will reduce the need to search through many unrelated + * files. + * + * The default search locations include: + * + * . + * CliTools + * src/CliTools + * + * The pattern we will look for is any file whose name ends in 'Commands.php'. + * A list of paths to found files will be returned. + */ + protected function discoverCommandFiles($directory, $baseNamespace) + { + $commandFiles = []; + // In the search location itself, we will search for command files + // immediately inside the directory only. + if ($this->includeFilesAtBase) { + $commandFiles = $this->discoverCommandFilesInLocation( + $directory, + $this->getBaseDirectorySearchDepth(), + $baseNamespace + ); + } + + // In the other search locations, + foreach ($this->searchLocations as $location) { + $itemsNamespace = $this->joinNamespace([$baseNamespace, $location]); + $commandFiles = array_merge( + $commandFiles, + $this->discoverCommandFilesInLocation( + "$directory/$location", + $this->getSearchDepth(), + $itemsNamespace + ) + ); + } + return $commandFiles; + } + + /** + * Return a Finder search depth appropriate for our selected search depth. + * + * @return string + */ + protected function getSearchDepth() + { + return $this->searchDepth <= 0 ? '== 0' : '<= ' . $this->searchDepth; + } + + /** + * Return a Finder search depth for the base directory. If the + * searchLocations array has been populated, then we will only search + * for files immediately inside the base directory; no traversal into + * deeper directories will be done, as that would conflict with the + * specification provided by the search locations. If there is no + * search location, then we will search to whatever depth was specified + * by the client. + * + * @return string + */ + protected function getBaseDirectorySearchDepth() + { + if (!empty($this->searchLocations)) { + return '== 0'; + } + return $this->getSearchDepth(); + } + + /** + * Search for command files in just one particular location. Returns + * an associative array mapping from the pathname of the file to the + * classname that it contains. The pathname may be ignored if the search + * location is included in the autoloader. + * + * @param string $directory The location to search + * @param string $depth How deep to search (e.g. '== 0' or '< 2') + * @param string $baseNamespace Namespace to prepend to each classname + * + * @return array + */ + protected function discoverCommandFilesInLocation($directory, $depth, $baseNamespace) + { + if (!is_dir($directory)) { + return []; + } + $finder = $this->createFinder($directory, $depth); + + $commands = []; + foreach ($finder as $file) { + $relativePathName = $file->getRelativePathname(); + $relativeNamespaceAndClassname = str_replace( + ['/', '-', '.php'], + ['\\', '_', ''], + $relativePathName + ); + $classname = $this->joinNamespace([$baseNamespace, $relativeNamespaceAndClassname]); + $commandFilePath = $this->joinPaths([$directory, $relativePathName]); + $commands[$commandFilePath] = $classname; + } + + return $commands; + } + + /** + * Create a Finder object for use in searching a particular directory + * location. + * + * @param string $directory The location to search + * @param string $depth The depth limitation + * + * @return Finder + */ + protected function createFinder($directory, $depth) + { + $finder = new Finder(); + $finder->files() + ->name($this->searchPattern) + ->in($directory) + ->depth($depth); + + foreach ($this->excludeList as $item) { + $finder->exclude($item); + } + + if ($this->followLinks) { + $finder->followLinks(); + } + + return $finder; + } + + /** + * Combine the items of the provied array into a backslash-separated + * namespace string. Empty and numeric items are omitted. + * + * @param array $namespaceParts List of components of a namespace + * + * @return string + */ + protected function joinNamespace(array $namespaceParts) + { + return $this->joinParts( + '\\', + $namespaceParts, + function ($item) { + return !is_numeric($item) && !empty($item); + } + ); + } + + /** + * Combine the items of the provied array into a slash-separated + * pathname. Empty items are omitted. + * + * @param array $pathParts List of components of a path + * + * @return string + */ + protected function joinPaths(array $pathParts) + { + $path = $this->joinParts( + '/', + $pathParts, + function ($item) { + return !empty($item); + } + ); + return str_replace(DIRECTORY_SEPARATOR, '/', $path); + } + + /** + * Simple wrapper around implode and array_filter. + * + * @param string $delimiter + * @param array $parts + * @param callable $filterFunction + */ + protected function joinParts($delimiter, $parts, $filterFunction) + { + $parts = array_map( + function ($item) use ($delimiter) { + return rtrim($item, $delimiter); + }, + $parts + ); + return implode( + $delimiter, + array_filter($parts, $filterFunction) + ); + } +} diff --git a/vendor/consolidation/annotated-command/src/CommandInfoAltererInterface.php b/vendor/consolidation/annotated-command/src/CommandInfoAltererInterface.php new file mode 100644 index 0000000000..55376d1484 --- /dev/null +++ b/vendor/consolidation/annotated-command/src/CommandInfoAltererInterface.php @@ -0,0 +1,9 @@ +hookManager = $hookManager; + } + + /** + * Return the hook manager + * @return HookManager + */ + public function hookManager() + { + return $this->hookManager; + } + + public function resultWriter() + { + if (!$this->resultWriter) { + $this->setResultWriter(new ResultWriter()); + } + return $this->resultWriter; + } + + public function setResultWriter($resultWriter) + { + $this->resultWriter = $resultWriter; + } + + public function parameterInjection() + { + if (!$this->parameterInjection) { + $this->setParameterInjection(new ParameterInjection()); + } + return $this->parameterInjection; + } + + public function setParameterInjection($parameterInjection) + { + $this->parameterInjection = $parameterInjection; + } + + public function addPrepareFormatter(PrepareFormatter $preparer) + { + $this->prepareOptionsList[] = $preparer; + } + + public function setFormatterManager(FormatterManager $formatterManager) + { + $this->formatterManager = $formatterManager; + $this->resultWriter()->setFormatterManager($formatterManager); + return $this; + } + + public function setDisplayErrorFunction(callable $fn) + { + $this->resultWriter()->setDisplayErrorFunction($fn); + } + + /** + * Set a mode to make the annotated command library re-throw + * any exception that it catches while processing a command. + * + * The default behavior in the current (2.x) branch is to catch + * the exception and replace it with a CommandError object that + * may be processed by the normal output processing passthrough. + * + * In the 3.x branch, exceptions will never be caught; they will + * be passed through, as if setPassExceptions(true) were called. + * This is the recommended behavior. + */ + public function setPassExceptions($passExceptions) + { + $this->passExceptions = $passExceptions; + return $this; + } + + public function commandErrorForException(\Exception $e) + { + if ($this->passExceptions) { + throw $e; + } + return new CommandError($e->getMessage(), $e->getCode()); + } + + /** + * Return the formatter manager + * @return FormatterManager + */ + public function formatterManager() + { + return $this->formatterManager; + } + + public function initializeHook( + InputInterface $input, + $names, + AnnotationData $annotationData + ) { + $initializeDispatcher = new InitializeHookDispatcher($this->hookManager(), $names); + return $initializeDispatcher->initialize($input, $annotationData); + } + + public function optionsHook( + AnnotatedCommand $command, + $names, + AnnotationData $annotationData + ) { + $optionsDispatcher = new OptionsHookDispatcher($this->hookManager(), $names); + $optionsDispatcher->getOptions($command, $annotationData); + } + + public function interact( + InputInterface $input, + OutputInterface $output, + $names, + AnnotationData $annotationData + ) { + $interactDispatcher = new InteractHookDispatcher($this->hookManager(), $names); + return $interactDispatcher->interact($input, $output, $annotationData); + } + + public function process( + OutputInterface $output, + $names, + $commandCallback, + CommandData $commandData + ) { + $result = []; + try { + $result = $this->validateRunAndAlter( + $names, + $commandCallback, + $commandData + ); + return $this->handleResults($output, $names, $result, $commandData); + } catch (\Exception $e) { + $result = $this->commandErrorForException($e); + return $this->handleResults($output, $names, $result, $commandData); + } + } + + public function validateRunAndAlter( + $names, + $commandCallback, + CommandData $commandData + ) { + // Validators return any object to signal a validation error; + // if the return an array, it replaces the arguments. + $validateDispatcher = new ValidateHookDispatcher($this->hookManager(), $names); + $validated = $validateDispatcher->validate($commandData); + if (is_object($validated)) { + return $validated; + } + + // Once we have validated the optins, create the formatter options. + $this->createFormatterOptions($commandData); + + $replaceDispatcher = new ReplaceCommandHookDispatcher($this->hookManager(), $names); + if ($this->logger) { + $replaceDispatcher->setLogger($this->logger); + } + if ($replaceDispatcher->hasReplaceCommandHook()) { + $commandCallback = $replaceDispatcher->getReplacementCommand($commandData); + } + + // Run the command, alter the results, and then handle output and status + $result = $this->runCommandCallback($commandCallback, $commandData); + return $this->processResults($names, $result, $commandData); + } + + public function processResults($names, $result, CommandData $commandData) + { + $processDispatcher = new ProcessResultHookDispatcher($this->hookManager(), $names); + return $processDispatcher->process($result, $commandData); + } + + /** + * Create a FormatterOptions object for use in writing the formatted output. + * @param CommandData $commandData + * @return FormatterOptions + */ + protected function createFormatterOptions($commandData) + { + $options = $commandData->input()->getOptions(); + $formatterOptions = new FormatterOptions($commandData->annotationData()->getArrayCopy(), $options); + foreach ($this->prepareOptionsList as $preparer) { + $preparer->prepare($commandData, $formatterOptions); + } + $commandData->setFormatterOptions($formatterOptions); + return $formatterOptions; + } + + /** + * Handle the result output and status code calculation. + */ + public function handleResults(OutputInterface $output, $names, $result, CommandData $commandData) + { + $statusCodeDispatcher = new StatusDeterminerHookDispatcher($this->hookManager(), $names); + $extractDispatcher = new ExtracterHookDispatcher($this->hookManager(), $names); + + return $this->resultWriter()->handle($output, $result, $commandData, $statusCodeDispatcher, $extractDispatcher); + } + + /** + * Run the main command callback + */ + protected function runCommandCallback($commandCallback, CommandData $commandData) + { + $result = false; + try { + $args = $this->parameterInjection()->args($commandData); + $result = call_user_func_array($commandCallback, array_values($args)); + } catch (\Exception $e) { + $result = $this->commandErrorForException($e); + } + return $result; + } + + public function injectIntoCommandData($commandData, $injectedClasses) + { + $this->parameterInjection()->injectIntoCommandData($commandData, $injectedClasses); + } +} diff --git a/vendor/consolidation/annotated-command/src/CommandResult.php b/vendor/consolidation/annotated-command/src/CommandResult.php new file mode 100644 index 0000000000..6b1a3f80bc --- /dev/null +++ b/vendor/consolidation/annotated-command/src/CommandResult.php @@ -0,0 +1,71 @@ +data = $data; + $this->exitCode = $exitCode; + } + + public static function exitCode($exitCode) + { + return new self(null, $exitCode); + } + + public static function data($data) + { + return new self($data); + } + + public static function dataWithExitCode($data, $exitCode) + { + return new self($data, $exitCode); + } + + public function getExitCode() + { + return $this->exitCode; + } + + public function getOutputData() + { + return $this->data; + } + + public function setOutputData($data) + { + $this->data = $data; + } +} diff --git a/vendor/consolidation/annotated-command/src/Events/CustomEventAwareInterface.php b/vendor/consolidation/annotated-command/src/Events/CustomEventAwareInterface.php new file mode 100644 index 0000000000..806b55dfcb --- /dev/null +++ b/vendor/consolidation/annotated-command/src/Events/CustomEventAwareInterface.php @@ -0,0 +1,20 @@ +hookManager = $hookManager; + } + + /** + * {@inheritdoc} + */ + public function getCustomEventHandlers($eventName) + { + if (!$this->hookManager) { + return []; + } + return $this->hookManager->getHook($eventName, HookManager::ON_EVENT); + } +} diff --git a/vendor/consolidation/annotated-command/src/ExitCodeInterface.php b/vendor/consolidation/annotated-command/src/ExitCodeInterface.php new file mode 100644 index 0000000000..bec902bb6a --- /dev/null +++ b/vendor/consolidation/annotated-command/src/ExitCodeInterface.php @@ -0,0 +1,12 @@ +application = $application; + } + + public function getApplication() + { + return $this->application; + } + + /** + * Run the help command + * + * @command my-help + * @return \Consolidation\AnnotatedCommand\Help\HelpDocument + */ + public function help($commandName = 'help') + { + $command = $this->getApplication()->find($commandName); + + $helpDocument = $this->getHelpDocument($command); + return $helpDocument; + } + + /** + * Create a help document. + */ + protected function getHelpDocument($command) + { + return new HelpDocument($command); + } +} diff --git a/vendor/consolidation/annotated-command/src/Help/HelpDocument.php b/vendor/consolidation/annotated-command/src/Help/HelpDocument.php new file mode 100644 index 0000000000..67609d65e0 --- /dev/null +++ b/vendor/consolidation/annotated-command/src/Help/HelpDocument.php @@ -0,0 +1,65 @@ +generateBaseHelpDom($command); + $dom = $this->alterHelpDocument($command, $dom); + + $this->command = $command; + $this->dom = $dom; + } + + /** + * Convert data into a \DomDocument. + * + * @return \DomDocument + */ + public function getDomData() + { + return $this->dom; + } + + /** + * Create the base help DOM prior to alteration by the Command object. + * @param Command $command + * @return \DomDocument + */ + protected function generateBaseHelpDom(Command $command) + { + // Use Symfony to generate xml text. If other formats are + // requested, convert from xml to the desired form. + $descriptor = new XmlDescriptor(); + return $descriptor->getCommandDocument($command); + } + + /** + * Alter the DOM document per the command object + * @param Command $command + * @param \DomDocument $dom + * @return \DomDocument + */ + protected function alterHelpDocument(Command $command, \DomDocument $dom) + { + if ($command instanceof HelpDocumentAlter) { + $dom = $command->helpAlter($dom); + } + return $dom; + } +} diff --git a/vendor/consolidation/annotated-command/src/Help/HelpDocumentAlter.php b/vendor/consolidation/annotated-command/src/Help/HelpDocumentAlter.php new file mode 100644 index 0000000000..0d7f49c72c --- /dev/null +++ b/vendor/consolidation/annotated-command/src/Help/HelpDocumentAlter.php @@ -0,0 +1,7 @@ +appendChild($commandXML = $dom->createElement('command')); + $commandXML->setAttribute('id', $command->getName()); + $commandXML->setAttribute('name', $command->getName()); + + // Get the original element and its top-level elements. + $originalCommandXML = static::getSingleElementByTagName($dom, $originalDom, 'command'); + $originalUsagesXML = static::getSingleElementByTagName($dom, $originalCommandXML, 'usages'); + $originalDescriptionXML = static::getSingleElementByTagName($dom, $originalCommandXML, 'description'); + $originalHelpXML = static::getSingleElementByTagName($dom, $originalCommandXML, 'help'); + $originalArgumentsXML = static::getSingleElementByTagName($dom, $originalCommandXML, 'arguments'); + $originalOptionsXML = static::getSingleElementByTagName($dom, $originalCommandXML, 'options'); + + // Keep only the first of the elements + $newUsagesXML = $dom->createElement('usages'); + $firstUsageXML = static::getSingleElementByTagName($dom, $originalUsagesXML, 'usage'); + $newUsagesXML->appendChild($firstUsageXML); + + // Create our own elements + $newExamplesXML = $dom->createElement('examples'); + foreach ($command->getExampleUsages() as $usage => $description) { + $newExamplesXML->appendChild($exampleXML = $dom->createElement('example')); + $exampleXML->appendChild($usageXML = $dom->createElement('usage', $usage)); + $exampleXML->appendChild($descriptionXML = $dom->createElement('description', $description)); + } + + // Create our own elements + $newAliasesXML = $dom->createElement('aliases'); + foreach ($command->getAliases() as $alias) { + $newAliasesXML->appendChild($dom->createElement('alias', $alias)); + } + + // Create our own elements + $newTopicsXML = $dom->createElement('topics'); + foreach ($command->getTopics() as $topic) { + $newTopicsXML->appendChild($topicXML = $dom->createElement('topic', $topic)); + } + + // Place the different elements into the element in the desired order + $commandXML->appendChild($newUsagesXML); + $commandXML->appendChild($newExamplesXML); + $commandXML->appendChild($originalDescriptionXML); + $commandXML->appendChild($originalArgumentsXML); + $commandXML->appendChild($originalOptionsXML); + $commandXML->appendChild($originalHelpXML); + $commandXML->appendChild($newAliasesXML); + $commandXML->appendChild($newTopicsXML); + + return $dom; + } + + + protected static function getSingleElementByTagName($dom, $parent, $tagName) + { + // There should always be exactly one '' element. + $elements = $parent->getElementsByTagName($tagName); + $result = $elements->item(0); + + $result = $dom->importNode($result, true); + + return $result; + } +} diff --git a/vendor/consolidation/annotated-command/src/Hooks/AlterResultInterface.php b/vendor/consolidation/annotated-command/src/Hooks/AlterResultInterface.php new file mode 100644 index 0000000000..a94f4723fc --- /dev/null +++ b/vendor/consolidation/annotated-command/src/Hooks/AlterResultInterface.php @@ -0,0 +1,12 @@ +getHooks($hooks); + foreach ($commandEventHooks as $commandEvent) { + if ($commandEvent instanceof EventDispatcherInterface) { + $commandEvent->dispatch(ConsoleEvents::COMMAND, $event); + } + if (is_callable($commandEvent)) { + $commandEvent($event); + } + } + } +} diff --git a/vendor/consolidation/annotated-command/src/Hooks/Dispatchers/ExtracterHookDispatcher.php b/vendor/consolidation/annotated-command/src/Hooks/Dispatchers/ExtracterHookDispatcher.php new file mode 100644 index 0000000000..26bb1d2edd --- /dev/null +++ b/vendor/consolidation/annotated-command/src/Hooks/Dispatchers/ExtracterHookDispatcher.php @@ -0,0 +1,47 @@ +getOutputData(); + } + + $hooks = [ + HookManager::EXTRACT_OUTPUT, + ]; + $extractors = $this->getHooks($hooks); + foreach ($extractors as $extractor) { + $structuredOutput = $this->callExtractor($extractor, $result); + if (isset($structuredOutput)) { + return $structuredOutput; + } + } + + return $result; + } + + protected function callExtractor($extractor, $result) + { + if ($extractor instanceof ExtractOutputInterface) { + return $extractor->extractOutput($result); + } + if (is_callable($extractor)) { + return $extractor($result); + } + } +} diff --git a/vendor/consolidation/annotated-command/src/Hooks/Dispatchers/HookDispatcher.php b/vendor/consolidation/annotated-command/src/Hooks/Dispatchers/HookDispatcher.php new file mode 100644 index 0000000000..aa850eabee --- /dev/null +++ b/vendor/consolidation/annotated-command/src/Hooks/Dispatchers/HookDispatcher.php @@ -0,0 +1,27 @@ +hookManager = $hookManager; + $this->names = $names; + } + + public function getHooks($hooks, $annotationData = null) + { + return $this->hookManager->getHooks($this->names, $hooks, $annotationData); + } +} diff --git a/vendor/consolidation/annotated-command/src/Hooks/Dispatchers/InitializeHookDispatcher.php b/vendor/consolidation/annotated-command/src/Hooks/Dispatchers/InitializeHookDispatcher.php new file mode 100644 index 0000000000..dd12d500dd --- /dev/null +++ b/vendor/consolidation/annotated-command/src/Hooks/Dispatchers/InitializeHookDispatcher.php @@ -0,0 +1,40 @@ +getHooks($hooks, $annotationData); + foreach ($providers as $provider) { + $this->callInitializeHook($provider, $input, $annotationData); + } + } + + protected function callInitializeHook($provider, $input, AnnotationData $annotationData) + { + if ($provider instanceof InitializeHookInterface) { + return $provider->initialize($input, $annotationData); + } + if (is_callable($provider)) { + return $provider($input, $annotationData); + } + } +} diff --git a/vendor/consolidation/annotated-command/src/Hooks/Dispatchers/InteractHookDispatcher.php b/vendor/consolidation/annotated-command/src/Hooks/Dispatchers/InteractHookDispatcher.php new file mode 100644 index 0000000000..6de718ddeb --- /dev/null +++ b/vendor/consolidation/annotated-command/src/Hooks/Dispatchers/InteractHookDispatcher.php @@ -0,0 +1,41 @@ +getHooks($hooks, $annotationData); + foreach ($interactors as $interactor) { + $this->callInteractor($interactor, $input, $output, $annotationData); + } + } + + protected function callInteractor($interactor, $input, $output, AnnotationData $annotationData) + { + if ($interactor instanceof InteractorInterface) { + return $interactor->interact($input, $output, $annotationData); + } + if (is_callable($interactor)) { + return $interactor($input, $output, $annotationData); + } + } +} diff --git a/vendor/consolidation/annotated-command/src/Hooks/Dispatchers/OptionsHookDispatcher.php b/vendor/consolidation/annotated-command/src/Hooks/Dispatchers/OptionsHookDispatcher.php new file mode 100644 index 0000000000..59752266a3 --- /dev/null +++ b/vendor/consolidation/annotated-command/src/Hooks/Dispatchers/OptionsHookDispatcher.php @@ -0,0 +1,44 @@ +getHooks($hooks, $annotationData); + foreach ($optionHooks as $optionHook) { + $this->callOptionHook($optionHook, $command, $annotationData); + } + $commandInfoList = $this->hookManager->getHookOptionsForCommand($command); + if ($command instanceof AnnotatedCommand) { + $command->optionsHookForHookAnnotations($commandInfoList); + } + } + + protected function callOptionHook($optionHook, $command, AnnotationData $annotationData) + { + if ($optionHook instanceof OptionHookInterface) { + return $optionHook->getOptions($command, $annotationData); + } + if (is_callable($optionHook)) { + return $optionHook($command, $annotationData); + } + } +} diff --git a/vendor/consolidation/annotated-command/src/Hooks/Dispatchers/ProcessResultHookDispatcher.php b/vendor/consolidation/annotated-command/src/Hooks/Dispatchers/ProcessResultHookDispatcher.php new file mode 100644 index 0000000000..dca2b23019 --- /dev/null +++ b/vendor/consolidation/annotated-command/src/Hooks/Dispatchers/ProcessResultHookDispatcher.php @@ -0,0 +1,53 @@ +getHooks($hooks, $commandData->annotationData()); + foreach ($processors as $processor) { + $result = $this->callProcessor($processor, $result, $commandData); + } + + return $result; + } + + protected function callProcessor($processor, $result, CommandData $commandData) + { + $processed = null; + if ($processor instanceof ProcessResultInterface) { + $processed = $processor->process($result, $commandData); + } + if (is_callable($processor)) { + $processed = $processor($result, $commandData); + } + if (isset($processed)) { + return $processed; + } + return $result; + } +} diff --git a/vendor/consolidation/annotated-command/src/Hooks/Dispatchers/ReplaceCommandHookDispatcher.php b/vendor/consolidation/annotated-command/src/Hooks/Dispatchers/ReplaceCommandHookDispatcher.php new file mode 100644 index 0000000000..1687a96a0b --- /dev/null +++ b/vendor/consolidation/annotated-command/src/Hooks/Dispatchers/ReplaceCommandHookDispatcher.php @@ -0,0 +1,66 @@ +getReplaceCommandHooks()); + } + + /** + * @return \callable[] + */ + public function getReplaceCommandHooks() + { + $hooks = [ + HookManager::REPLACE_COMMAND_HOOK, + ]; + $replaceCommandHooks = $this->getHooks($hooks); + + return $replaceCommandHooks; + } + + /** + * @param \Consolidation\AnnotatedCommand\CommandData $commandData + * + * @return callable + */ + public function getReplacementCommand(CommandData $commandData) + { + $replaceCommandHooks = $this->getReplaceCommandHooks(); + + // We only take the first hook implementation of "replace-command" as the replacement. Commands shouldn't have + // more than one replacement. + $replacementCommand = reset($replaceCommandHooks); + + if ($this->logger && count($replaceCommandHooks) > 1) { + $command_name = $commandData->annotationData()->get('command', 'unknown'); + $message = "Multiple implementations of the \"replace - command\" hook exist for the \"$command_name\" command.\n"; + foreach ($replaceCommandHooks as $replaceCommandHook) { + $class = get_class($replaceCommandHook[0]); + $method = $replaceCommandHook[1]; + $hook_name = "$class->$method"; + $message .= " - $hook_name\n"; + } + $this->logger->warning($message); + } + + return $replacementCommand; + } +} diff --git a/vendor/consolidation/annotated-command/src/Hooks/Dispatchers/StatusDeterminerHookDispatcher.php b/vendor/consolidation/annotated-command/src/Hooks/Dispatchers/StatusDeterminerHookDispatcher.php new file mode 100644 index 0000000000..911dcb1de1 --- /dev/null +++ b/vendor/consolidation/annotated-command/src/Hooks/Dispatchers/StatusDeterminerHookDispatcher.php @@ -0,0 +1,51 @@ +getExitCode(); + } + + $hooks = [ + HookManager::STATUS_DETERMINER, + ]; + // If the result does not implement ExitCodeInterface, + // then we'll see if there is a determiner that can + // extract a status code from the result. + $determiners = $this->getHooks($hooks); + foreach ($determiners as $determiner) { + $status = $this->callDeterminer($determiner, $result); + if (isset($status)) { + return $status; + } + } + } + + protected function callDeterminer($determiner, $result) + { + if ($determiner instanceof StatusDeterminerInterface) { + return $determiner->determineStatusCode($result); + } + if (is_callable($determiner)) { + return $determiner($result); + } + } +} diff --git a/vendor/consolidation/annotated-command/src/Hooks/Dispatchers/ValidateHookDispatcher.php b/vendor/consolidation/annotated-command/src/Hooks/Dispatchers/ValidateHookDispatcher.php new file mode 100644 index 0000000000..fb4f489f27 --- /dev/null +++ b/vendor/consolidation/annotated-command/src/Hooks/Dispatchers/ValidateHookDispatcher.php @@ -0,0 +1,46 @@ +getHooks($hooks, $commandData->annotationData()); + foreach ($validators as $validator) { + $validated = $this->callValidator($validator, $commandData); + if ($validated === false) { + return new CommandError(); + } + if (is_object($validated)) { + return $validated; + } + } + } + + protected function callValidator($validator, CommandData $commandData) + { + if ($validator instanceof ValidatorInterface) { + return $validator->validate($commandData); + } + if (is_callable($validator)) { + return $validator($commandData); + } + } +} diff --git a/vendor/consolidation/annotated-command/src/Hooks/ExtractOutputInterface.php b/vendor/consolidation/annotated-command/src/Hooks/ExtractOutputInterface.php new file mode 100644 index 0000000000..ad0cdb696a --- /dev/null +++ b/vendor/consolidation/annotated-command/src/Hooks/ExtractOutputInterface.php @@ -0,0 +1,14 @@ +hooks; + } + + /** + * Add a hook + * + * @param mixed $callback The callback function to call + * @param string $hook The name of the hook to add + * @param string $name The name of the command to hook + * ('*' for all) + */ + public function add(callable $callback, $hook, $name = '*') + { + if (empty($name)) { + $name = static::getClassNameFromCallback($callback); + } + $this->hooks[$name][$hook][] = $callback; + return $this; + } + + public function recordHookOptions($commandInfo, $name) + { + $this->hookOptions[$name][] = $commandInfo; + return $this; + } + + public static function getNames($command, $callback) + { + return array_filter( + array_merge( + static::getNamesUsingCommands($command), + [static::getClassNameFromCallback($callback)] + ) + ); + } + + protected static function getNamesUsingCommands($command) + { + return array_merge( + [$command->getName()], + $command->getAliases() + ); + } + + /** + * If a command hook does not specify any particular command + * name that it should be attached to, then it will be applied + * to every command that is defined in the same class as the hook. + * This is controlled by using the namespace + class name of + * the implementing class of the callback hook. + */ + protected static function getClassNameFromCallback($callback) + { + if (!is_array($callback)) { + return ''; + } + $reflectionClass = new \ReflectionClass($callback[0]); + return $reflectionClass->getName(); + } + + /** + * Add a replace command hook + * + * @param type ReplaceCommandHookInterface $provider + * @param type string $command_name The name of the command to replace + */ + public function addReplaceCommandHook(ReplaceCommandHookInterface $replaceCommandHook, $name) + { + $this->hooks[$name][self::REPLACE_COMMAND_HOOK][] = $replaceCommandHook; + return $this; + } + + public function addPreCommandEventDispatcher(EventDispatcherInterface $eventDispatcher, $name = '*') + { + $this->hooks[$name][self::PRE_COMMAND_EVENT][] = $eventDispatcher; + return $this; + } + + public function addCommandEventDispatcher(EventDispatcherInterface $eventDispatcher, $name = '*') + { + $this->hooks[$name][self::COMMAND_EVENT][] = $eventDispatcher; + return $this; + } + + public function addPostCommandEventDispatcher(EventDispatcherInterface $eventDispatcher, $name = '*') + { + $this->hooks[$name][self::POST_COMMAND_EVENT][] = $eventDispatcher; + return $this; + } + + public function addCommandEvent(EventSubscriberInterface $eventSubscriber) + { + // Wrap the event subscriber in a dispatcher and add it + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber($eventSubscriber); + return $this->addCommandEventDispatcher($dispatcher); + } + + /** + * Add an configuration provider hook + * + * @param type InitializeHookInterface $provider + * @param type $name The name of the command to hook + * ('*' for all) + */ + public function addInitializeHook(InitializeHookInterface $initializeHook, $name = '*') + { + $this->hooks[$name][self::INITIALIZE][] = $initializeHook; + return $this; + } + + /** + * Add an option hook + * + * @param type ValidatorInterface $validator + * @param type $name The name of the command to hook + * ('*' for all) + */ + public function addOptionHook(OptionHookInterface $interactor, $name = '*') + { + $this->hooks[$name][self::INTERACT][] = $interactor; + return $this; + } + + /** + * Add an interact hook + * + * @param type ValidatorInterface $validator + * @param type $name The name of the command to hook + * ('*' for all) + */ + public function addInteractor(InteractorInterface $interactor, $name = '*') + { + $this->hooks[$name][self::INTERACT][] = $interactor; + return $this; + } + + /** + * Add a pre-validator hook + * + * @param type ValidatorInterface $validator + * @param type $name The name of the command to hook + * ('*' for all) + */ + public function addPreValidator(ValidatorInterface $validator, $name = '*') + { + $this->hooks[$name][self::PRE_ARGUMENT_VALIDATOR][] = $validator; + return $this; + } + + /** + * Add a validator hook + * + * @param type ValidatorInterface $validator + * @param type $name The name of the command to hook + * ('*' for all) + */ + public function addValidator(ValidatorInterface $validator, $name = '*') + { + $this->hooks[$name][self::ARGUMENT_VALIDATOR][] = $validator; + return $this; + } + + /** + * Add a pre-command hook. This is the same as a validator hook, except + * that it will run after all of the post-validator hooks. + * + * @param type ValidatorInterface $preCommand + * @param type $name The name of the command to hook + * ('*' for all) + */ + public function addPreCommandHook(ValidatorInterface $preCommand, $name = '*') + { + $this->hooks[$name][self::PRE_COMMAND_HOOK][] = $preCommand; + return $this; + } + + /** + * Add a post-command hook. This is the same as a pre-process hook, + * except that it will run before the first pre-process hook. + * + * @param type ProcessResultInterface $postCommand + * @param type $name The name of the command to hook + * ('*' for all) + */ + public function addPostCommandHook(ProcessResultInterface $postCommand, $name = '*') + { + $this->hooks[$name][self::POST_COMMAND_HOOK][] = $postCommand; + return $this; + } + + /** + * Add a result processor. + * + * @param type ProcessResultInterface $resultProcessor + * @param type $name The name of the command to hook + * ('*' for all) + */ + public function addResultProcessor(ProcessResultInterface $resultProcessor, $name = '*') + { + $this->hooks[$name][self::PROCESS_RESULT][] = $resultProcessor; + return $this; + } + + /** + * Add a result alterer. After a result is processed + * by a result processor, an alter hook may be used + * to convert the result from one form to another. + * + * @param type AlterResultInterface $resultAlterer + * @param type $name The name of the command to hook + * ('*' for all) + */ + public function addAlterResult(AlterResultInterface $resultAlterer, $name = '*') + { + $this->hooks[$name][self::ALTER_RESULT][] = $resultAlterer; + return $this; + } + + /** + * Add a status determiner. Usually, a command should return + * an integer on error, or a result object on success (which + * implies a status code of zero). If a result contains the + * status code in some other field, then a status determiner + * can be used to call the appropriate accessor method to + * determine the status code. This is usually not necessary, + * though; a command that fails may return a CommandError + * object, which contains a status code and a result message + * to display. + * @see CommandError::getExitCode() + * + * @param type StatusDeterminerInterface $statusDeterminer + * @param type $name The name of the command to hook + * ('*' for all) + */ + public function addStatusDeterminer(StatusDeterminerInterface $statusDeterminer, $name = '*') + { + $this->hooks[$name][self::STATUS_DETERMINER][] = $statusDeterminer; + return $this; + } + + /** + * Add an output extractor. If a command returns an object + * object, by default it is passed directly to the output + * formatter (if in use) for rendering. If the result object + * contains more information than just the data to render, though, + * then an output extractor can be used to call the appopriate + * accessor method of the result object to get the data to + * rendered. This is usually not necessary, though; it is preferable + * to have complex result objects implement the OutputDataInterface. + * @see OutputDataInterface::getOutputData() + * + * @param type ExtractOutputInterface $outputExtractor + * @param type $name The name of the command to hook + * ('*' for all) + */ + public function addOutputExtractor(ExtractOutputInterface $outputExtractor, $name = '*') + { + $this->hooks[$name][self::EXTRACT_OUTPUT][] = $outputExtractor; + return $this; + } + + public function getHookOptionsForCommand($command) + { + $names = $this->addWildcardHooksToNames($command->getNames(), $command->getAnnotationData()); + return $this->getHookOptions($names); + } + + /** + * @return CommandInfo[] + */ + public function getHookOptions($names) + { + $result = []; + foreach ($names as $name) { + if (isset($this->hookOptions[$name])) { + $result = array_merge($result, $this->hookOptions[$name]); + } + } + return $result; + } + + /** + * Get a set of hooks with the provided name(s). Include the + * pre- and post- hooks, and also include the global hooks ('*') + * in addition to the named hooks provided. + * + * @param string|array $names The name of the function being hooked. + * @param string[] $hooks A list of hooks (e.g. [HookManager::ALTER_RESULT]) + * + * @return callable[] + */ + public function getHooks($names, $hooks, $annotationData = null) + { + return $this->get($this->addWildcardHooksToNames($names, $annotationData), $hooks); + } + + protected function addWildcardHooksToNames($names, $annotationData = null) + { + $names = array_merge( + (array)$names, + ($annotationData == null) ? [] : array_map(function ($item) { + return "@$item"; + }, $annotationData->keys()) + ); + $names[] = '*'; + return array_unique($names); + } + + /** + * Get a set of hooks with the provided name(s). + * + * @param string|array $names The name of the function being hooked. + * @param string[] $hooks The list of hook names (e.g. [HookManager::ALTER_RESULT]) + * + * @return callable[] + */ + public function get($names, $hooks) + { + $result = []; + foreach ((array)$hooks as $hook) { + foreach ((array)$names as $name) { + $result = array_merge($result, $this->getHook($name, $hook)); + } + } + return $result; + } + + /** + * Get a single named hook. + * + * @param string $name The name of the hooked method + * @param string $hook The specific hook name (e.g. alter) + * + * @return callable[] + */ + public function getHook($name, $hook) + { + if (isset($this->hooks[$name][$hook])) { + return $this->hooks[$name][$hook]; + } + return []; + } + + /** + * Call the command event hooks. + * + * TODO: This should be moved to CommandEventHookDispatcher, which + * should become the class that implements EventSubscriberInterface. + * This change would break all clients, though, so postpone until next + * major release. + * + * @param ConsoleCommandEvent $event + */ + public function callCommandEventHooks(ConsoleCommandEvent $event) + { + /* @var Command $command */ + $command = $event->getCommand(); + $dispatcher = new CommandEventHookDispatcher($this, [$command->getName()]); + $dispatcher->callCommandEventHooks($event); + } + + /** + * @{@inheritdoc} + */ + public static function getSubscribedEvents() + { + return [ConsoleEvents::COMMAND => 'callCommandEventHooks']; + } +} diff --git a/vendor/consolidation/annotated-command/src/Hooks/InitializeHookInterface.php b/vendor/consolidation/annotated-command/src/Hooks/InitializeHookInterface.php new file mode 100644 index 0000000000..5d261478df --- /dev/null +++ b/vendor/consolidation/annotated-command/src/Hooks/InitializeHookInterface.php @@ -0,0 +1,15 @@ +stdinHandler = $stdin; + } + + /** + * @inheritdoc + */ + public function stdin() + { + if (!$this->stdinHandler) { + $this->stdinHandler = new StdinHandler(); + } + return $this->stdinHandler; + } +} diff --git a/vendor/consolidation/annotated-command/src/Input/StdinHandler.php b/vendor/consolidation/annotated-command/src/Input/StdinHandler.php new file mode 100644 index 0000000000..473458109f --- /dev/null +++ b/vendor/consolidation/annotated-command/src/Input/StdinHandler.php @@ -0,0 +1,247 @@ +stdin()->contents()); + * } + * } + * + * Command that reads from stdin or file via an option: + * + * /** + * * @command cat + * * @param string $file + * * @default $file - + * * / + * public function cat(InputInterface $input) + * { + * $data = $this->stdin()->select($input, 'file')->contents(); + * } + * + * Command that reads from stdin or file via an option: + * + * /** + * * @command cat + * * @option string $file + * * @default $file - + * * / + * public function cat(InputInterface $input) + * { + * $data = $this->stdin()->select($input, 'file')->contents(); + * } + * + * It is also possible to inject the selected stream into the input object, + * e.g. if you want the contents of the source file to be fed to any Question + * helper et. al. that the $input object is used with. + * + * /** + * * @command example + * * @option string $file + * * @default $file - + * * / + * public function example(InputInterface $input) + * { + * $this->stdin()->setStream($input, 'file'); + * } + * + * + * Inject an alternate source for standard input in tests. Presumes that + * the object under test gets a reference to the StdinHandler via dependency + * injection from the container. + * + * $container->get('stdinHandler')->redirect($pathToTestStdinFileFixture); + * + * You may also inject your stdin file fixture stream into the $input object + * as usual, and then use it with 'select()' or 'setStream()' as shown above. + * + * Finally, this class may also be used in absence of a dependency injection + * container by using the static 'selectStream()' method: + * + * /** + * * @command example + * * @option string $file + * * @default $file - + * * / + * public function example(InputInterface $input) + * { + * $data = StdinHandler::selectStream($input, 'file')->contents(); + * } + * + * To test a method that uses this technique, simply inject your stdin + * fixture into the $input object in your test: + * + * $input->setStream(fopen($pathToFixture, 'r')); + */ +class StdinHandler +{ + protected $path; + protected $stream; + + public static function selectStream(InputInterface $input, $optionOrArg) + { + $handler = new Self(); + + return $handler->setStream($input, $optionOrArg); + } + + /** + * hasPath returns 'true' if the stdin handler has a path to a file. + * + * @return bool + */ + public function hasPath() + { + // Once the stream has been opened, we mask the existence of the path. + return !$this->hasStream() && !empty($this->path); + } + + /** + * hasStream returns 'true' if the stdin handler has opened a stream. + * + * @return bool + */ + public function hasStream() + { + return !empty($this->stream); + } + + /** + * path returns the path to any file that was set as a redirection + * source, or `php://stdin` if none have been. + * + * @return string + */ + public function path() + { + return $this->path ?: 'php://stdin'; + } + + /** + * close closes the input stream if it was opened. + */ + public function close() + { + if ($this->hasStream()) { + fclose($this->stream); + $this->stream = null; + } + return $this; + } + + /** + * redirect specifies a path to a file that should serve as the + * source to read from. If the input path is '-' or empty, + * then output will be taken from php://stdin (or whichever source + * was provided via the 'redirect' method). + * + * @return $this + */ + public function redirect($path) + { + if ($this->pathProvided($path)) { + $this->path = $path; + } + + return $this; + } + + /** + * select chooses the source of the input stream based on whether or + * not the user provided the specified option or argument on the commandline. + * Stdin is selected if there is no user selection. + * + * @param InputInterface $input + * @param string $optionOrArg + * @return $this + */ + public function select(InputInterface $input, $optionOrArg) + { + $this->redirect($this->getOptionOrArg($input, $optionOrArg)); + if (!$this->hasPath() && ($input instanceof StreamableInputInterface)) { + $this->stream = $input->getStream(); + } + + return $this; + } + + /** + * getStream opens and returns the stdin stream (or redirect file). + */ + public function getStream() + { + if (!$this->hasStream()) { + $this->stream = fopen($this->path(), 'r'); + } + return $this->stream; + } + + /** + * setStream functions like 'select', and also sets up the $input + * object to read from the selected input stream e.g. when used + * with a question helper. + */ + public function setStream(InputInterface $input, $optionOrArg) + { + $this->select($input, $optionOrArg); + if ($input instanceof StreamableInputInterface) { + $stream = $this->getStream(); + $input->setStream($stream); + } + return $this; + } + + /** + * contents reads the entire contents of the standard input stream. + * + * @return string + */ + public function contents() + { + // Optimization: use file_get_contents if we have a path to a file + // and the stream has not been opened yet. + if (!$this->hasStream()) { + return file_get_contents($this->path()); + } + $stream = $this->getStream(); + stream_set_blocking($stream, false); // TODO: We did this in backend invoke. Necessary here? + $contents = stream_get_contents($stream); + $this->close(); + + return $contents; + } + + /** + * Returns 'true' if a path was specfied, and that path was not '-'. + */ + protected function pathProvided($path) + { + return !empty($path) && ($path != '-'); + } + + protected function getOptionOrArg(InputInterface $input, $optionOrArg) + { + if ($input->hasOption($optionOrArg)) { + return $input->getOption($optionOrArg); + } + return $input->getArgument($optionOrArg); + } +} diff --git a/vendor/consolidation/annotated-command/src/Options/AlterOptionsCommandEvent.php b/vendor/consolidation/annotated-command/src/Options/AlterOptionsCommandEvent.php new file mode 100644 index 0000000000..b16a8eda36 --- /dev/null +++ b/vendor/consolidation/annotated-command/src/Options/AlterOptionsCommandEvent.php @@ -0,0 +1,92 @@ +application = $application; + } + + /** + * @param ConsoleCommandEvent $event + */ + public function alterCommandOptions(ConsoleCommandEvent $event) + { + /* @var Command $command */ + $command = $event->getCommand(); + $input = $event->getInput(); + if ($command->getName() == 'help') { + // Symfony 3.x prepares $input for us; Symfony 2.x, on the other + // hand, passes it in prior to binding with the command definition, + // so we have to go to a little extra work. It may be inadvisable + // to do these steps for commands other than 'help'. + if (!$input->hasArgument('command_name')) { + $command->ignoreValidationErrors(); + $command->mergeApplicationDefinition(); + $input->bind($command->getDefinition()); + } + + // Symfony Console helpfully swaps 'command_name' and 'command' + // depending on whether the user entered `help foo` or `--help foo`. + // One of these is always `help`, and the other is the command we + // are actually interested in. + $nameOfCommandToDescribe = $event->getInput()->getArgument('command_name'); + if ($nameOfCommandToDescribe == 'help') { + $nameOfCommandToDescribe = $event->getInput()->getArgument('command'); + } + $commandToDescribe = $this->application->find($nameOfCommandToDescribe); + $this->findAndAddHookOptions($commandToDescribe); + } else { + $this->findAndAddHookOptions($command); + } + } + + public function findAndAddHookOptions($command) + { + if (!$command instanceof AnnotatedCommand) { + return; + } + $command->optionsHook(); + } + + + /** + * @{@inheritdoc} + */ + public static function getSubscribedEvents() + { + return [ConsoleEvents::COMMAND => 'alterCommandOptions']; + } +} diff --git a/vendor/consolidation/annotated-command/src/Options/AutomaticOptionsProviderInterface.php b/vendor/consolidation/annotated-command/src/Options/AutomaticOptionsProviderInterface.php new file mode 100644 index 0000000000..1349fe7959 --- /dev/null +++ b/vendor/consolidation/annotated-command/src/Options/AutomaticOptionsProviderInterface.php @@ -0,0 +1,21 @@ +defaultWidth = $defaultWidth; + } + + public function setApplication(Application $application) + { + $this->application = $application; + } + + public function setTerminal($terminal) + { + $this->terminal = $terminal; + } + + public function getTerminal() + { + if (!$this->terminal && class_exists('\Symfony\Component\Console\Terminal')) { + $this->terminal = new \Symfony\Component\Console\Terminal(); + } + return $this->terminal; + } + + public function enableWrap($shouldWrap) + { + $this->shouldWrap = $shouldWrap; + } + + public function prepare(CommandData $commandData, FormatterOptions $options) + { + $width = $this->getTerminalWidth(); + if (!$width) { + $width = $this->defaultWidth; + } + + // Enforce minimum and maximum widths + $width = min($width, $this->getMaxWidth($commandData)); + $width = max($width, $this->getMinWidth($commandData)); + + $options->setWidth($width); + } + + protected function getTerminalWidth() + { + // Don't wrap if wrapping has been disabled. + if (!$this->shouldWrap) { + return 0; + } + + $terminal = $this->getTerminal(); + if ($terminal) { + return $terminal->getWidth(); + } + + return $this->getTerminalWidthViaApplication(); + } + + protected function getTerminalWidthViaApplication() + { + if (!$this->application) { + return 0; + } + $dimensions = $this->application->getTerminalDimensions(); + if ($dimensions[0] == null) { + return 0; + } + + return $dimensions[0]; + } + + protected function getMaxWidth(CommandData $commandData) + { + return $this->maxWidth; + } + + protected function getMinWidth(CommandData $commandData) + { + return $this->minWidth; + } +} diff --git a/vendor/consolidation/annotated-command/src/OutputDataInterface.php b/vendor/consolidation/annotated-command/src/OutputDataInterface.php new file mode 100644 index 0000000000..9102764db7 --- /dev/null +++ b/vendor/consolidation/annotated-command/src/OutputDataInterface.php @@ -0,0 +1,13 @@ +register('Symfony\Component\Console\Input\InputInterface', $this); + $this->register('Symfony\Component\Console\Output\OutputInterface', $this); + } + + public function register($interfaceName, ParameterInjector $injector) + { + $this->injectors[$interfaceName] = $injector; + } + + public function args($commandData) + { + return array_merge( + $commandData->injectedInstances(), + $commandData->getArgsAndOptions() + ); + } + + public function injectIntoCommandData($commandData, $injectedClasses) + { + foreach ($injectedClasses as $injectedClass) { + $injectedInstance = $this->getInstanceToInject($commandData, $injectedClass); + $commandData->injectInstance($injectedInstance); + } + } + + protected function getInstanceToInject(CommandData $commandData, $interfaceName) + { + if (!isset($this->injectors[$interfaceName])) { + return null; + } + + return $this->injectors[$interfaceName]->get($commandData, $interfaceName); + } + + public function get(CommandData $commandData, $interfaceName) + { + switch ($interfaceName) { + case 'Symfony\Component\Console\Input\InputInterface': + return $commandData->input(); + case 'Symfony\Component\Console\Output\OutputInterface': + return $commandData->output(); + } + + return null; + } +} diff --git a/vendor/consolidation/annotated-command/src/ParameterInjector.php b/vendor/consolidation/annotated-command/src/ParameterInjector.php new file mode 100644 index 0000000000..2f2346f62f --- /dev/null +++ b/vendor/consolidation/annotated-command/src/ParameterInjector.php @@ -0,0 +1,10 @@ +reflection = new \ReflectionMethod($classNameOrInstance, $methodName); + $this->methodName = $methodName; + $this->arguments = new DefaultsWithDescriptions(); + $this->options = new DefaultsWithDescriptions(); + + // If the cache came from a newer version, ignore it and + // regenerate the cached information. + if (!empty($cache) && CommandInfoDeserializer::isValidSerializedData($cache) && !$this->cachedFileIsModified($cache)) { + $deserializer = new CommandInfoDeserializer(); + $deserializer->constructFromCache($this, $cache); + $this->docBlockIsParsed = true; + } else { + $this->constructFromClassAndMethod($classNameOrInstance, $methodName); + } + } + + public static function create($classNameOrInstance, $methodName) + { + return new self($classNameOrInstance, $methodName); + } + + public static function deserialize($cache) + { + $cache = (array)$cache; + return new self($cache['class'], $cache['method_name'], $cache); + } + + public function cachedFileIsModified($cache) + { + $path = $this->reflection->getFileName(); + return filemtime($path) != $cache['mtime']; + } + + protected function constructFromClassAndMethod($classNameOrInstance, $methodName) + { + $this->otherAnnotations = new AnnotationData(); + // Set up a default name for the command from the method name. + // This can be overridden via @command or @name annotations. + $this->name = $this->convertName($methodName); + $this->options = new DefaultsWithDescriptions($this->determineOptionsFromParameters(), false); + $this->arguments = $this->determineAgumentClassifications(); + } + + /** + * Recover the method name provided to the constructor. + * + * @return string + */ + public function getMethodName() + { + return $this->methodName; + } + + /** + * Return the primary name for this command. + * + * @return string + */ + public function getName() + { + $this->parseDocBlock(); + return $this->name; + } + + /** + * Set the primary name for this command. + * + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + return $this; + } + + /** + * Return whether or not this method represents a valid command + * or hook. + */ + public function valid() + { + return !empty($this->name); + } + + /** + * If higher-level code decides that this CommandInfo is not interesting + * or useful (if it is not a command method or a hook method), then + * we will mark it as invalid to prevent it from being created as a command. + * We still cache a placeholder record for invalid methods, so that we + * do not need to re-parse the method again later simply to determine that + * it is invalid. + */ + public function invalidate() + { + $this->name = ''; + } + + public function getReturnType() + { + $this->parseDocBlock(); + return $this->returnType; + } + + public function getInjectedClasses() + { + $this->parseDocBlock(); + return $this->injectedClasses; + } + + public function setInjectedClasses($injectedClasses) + { + $this->injectedClasses = $injectedClasses; + return $this; + } + + public function setReturnType($returnType) + { + $this->returnType = $returnType; + return $this; + } + + /** + * Get any annotations included in the docblock comment for the + * implementation method of this command that are not already + * handled by the primary methods of this class. + * + * @return AnnotationData + */ + public function getRawAnnotations() + { + $this->parseDocBlock(); + return $this->otherAnnotations; + } + + /** + * Replace the annotation data. + */ + public function replaceRawAnnotations($annotationData) + { + $this->otherAnnotations = new AnnotationData((array) $annotationData); + return $this; + } + + /** + * Get any annotations included in the docblock comment, + * also including default values such as @command. We add + * in the default @command annotation late, and only in a + * copy of the annotation data because we use the existance + * of a @command to indicate that this CommandInfo is + * a command, and not a hook or anything else. + * + * @return AnnotationData + */ + public function getAnnotations() + { + // Also provide the path to the commandfile that these annotations + // were pulled from and the classname of that file. + $path = $this->reflection->getFileName(); + $className = $this->reflection->getDeclaringClass()->getName(); + return new AnnotationData( + $this->getRawAnnotations()->getArrayCopy() + + [ + 'command' => $this->getName(), + '_path' => $path, + '_classname' => $className, + ] + ); + } + + /** + * Return a specific named annotation for this command as a list. + * + * @param string $name The name of the annotation. + * @return array|null + */ + public function getAnnotationList($name) + { + // hasAnnotation parses the docblock + if (!$this->hasAnnotation($name)) { + return null; + } + return $this->otherAnnotations->getList($name); + ; + } + + /** + * Return a specific named annotation for this command as a string. + * + * @param string $name The name of the annotation. + * @return string|null + */ + public function getAnnotation($name) + { + // hasAnnotation parses the docblock + if (!$this->hasAnnotation($name)) { + return null; + } + return $this->otherAnnotations->get($name); + } + + /** + * Check to see if the specified annotation exists for this command. + * + * @param string $annotation The name of the annotation. + * @return boolean + */ + public function hasAnnotation($annotation) + { + $this->parseDocBlock(); + return isset($this->otherAnnotations[$annotation]); + } + + /** + * Save any tag that we do not explicitly recognize in the + * 'otherAnnotations' map. + */ + public function addAnnotation($name, $content) + { + // Convert to an array and merge if there are multiple + // instances of the same annotation defined. + if (isset($this->otherAnnotations[$name])) { + $content = array_merge((array) $this->otherAnnotations[$name], (array)$content); + } + $this->otherAnnotations[$name] = $content; + } + + /** + * Remove an annotation that was previoudly set. + */ + public function removeAnnotation($name) + { + unset($this->otherAnnotations[$name]); + } + + /** + * Get the synopsis of the command (~first line). + * + * @return string + */ + public function getDescription() + { + $this->parseDocBlock(); + return $this->description; + } + + /** + * Set the command description. + * + * @param string $description The description to set. + */ + public function setDescription($description) + { + $this->description = str_replace("\n", ' ', $description); + return $this; + } + + /** + * Get the help text of the command (the description) + */ + public function getHelp() + { + $this->parseDocBlock(); + return $this->help; + } + /** + * Set the help text for this command. + * + * @param string $help The help text. + */ + public function setHelp($help) + { + $this->help = $help; + return $this; + } + + /** + * Return the list of aliases for this command. + * @return string[] + */ + public function getAliases() + { + $this->parseDocBlock(); + return $this->aliases; + } + + /** + * Set aliases that can be used in place of the command's primary name. + * + * @param string|string[] $aliases + */ + public function setAliases($aliases) + { + if (is_string($aliases)) { + $aliases = explode(',', static::convertListToCommaSeparated($aliases)); + } + $this->aliases = array_filter($aliases); + return $this; + } + + /** + * Get hidden status for the command. + * @return bool + */ + public function getHidden() + { + $this->parseDocBlock(); + return $this->hasAnnotation('hidden'); + } + + /** + * Set hidden status. List command omits hidden commands. + * + * @param bool $hidden + */ + public function setHidden($hidden) + { + $this->hidden = $hidden; + return $this; + } + + /** + * Return the examples for this command. This is @usage instead of + * @example because the later is defined by the phpdoc standard to + * be example method calls. + * + * @return string[] + */ + public function getExampleUsages() + { + $this->parseDocBlock(); + return $this->exampleUsage; + } + + /** + * Add an example usage for this command. + * + * @param string $usage An example of the command, including the command + * name and all of its example arguments and options. + * @param string $description An explanation of what the example does. + */ + public function setExampleUsage($usage, $description) + { + $this->exampleUsage[$usage] = $description; + return $this; + } + + /** + * Overwrite all example usages + */ + public function replaceExampleUsages($usages) + { + $this->exampleUsage = $usages; + return $this; + } + + /** + * Return the topics for this command. + * + * @return string[] + */ + public function getTopics() + { + if (!$this->hasAnnotation('topics')) { + return []; + } + $topics = $this->getAnnotation('topics'); + return explode(',', trim($topics)); + } + + /** + * Return the list of refleaction parameters. + * + * @return ReflectionParameter[] + */ + public function getParameters() + { + return $this->reflection->getParameters(); + } + + /** + * Descriptions of commandline arguements for this command. + * + * @return DefaultsWithDescriptions + */ + public function arguments() + { + return $this->arguments; + } + + /** + * Descriptions of commandline options for this command. + * + * @return DefaultsWithDescriptions + */ + public function options() + { + return $this->options; + } + + /** + * Get the inputOptions for the options associated with this CommandInfo + * object, e.g. via @option annotations, or from + * $options = ['someoption' => 'defaultvalue'] in the command method + * parameter list. + * + * @return InputOption[] + */ + public function inputOptions() + { + if (!isset($this->inputOptions)) { + $this->inputOptions = $this->createInputOptions(); + } + return $this->inputOptions; + } + + protected function addImplicitNoOptions() + { + $opts = $this->options()->getValues(); + foreach ($opts as $name => $defaultValue) { + if ($defaultValue === true) { + $key = 'no-' . $name; + if (!array_key_exists($key, $opts)) { + $description = "Negate --$name option."; + $this->options()->add($key, $description, false); + } + } + } + } + + protected function createInputOptions() + { + $explicitOptions = []; + $this->addImplicitNoOptions(); + + $opts = $this->options()->getValues(); + foreach ($opts as $name => $defaultValue) { + $description = $this->options()->getDescription($name); + + $fullName = $name; + $shortcut = ''; + if (strpos($name, '|')) { + list($fullName, $shortcut) = explode('|', $name, 2); + } + + // Treat the following two cases identically: + // - 'foo' => InputOption::VALUE_OPTIONAL + // - 'foo' => null + // The first form is preferred, but we will convert the value + // to 'null' for storage as the option default value. + if ($defaultValue === InputOption::VALUE_OPTIONAL) { + $defaultValue = null; + } + + if ($defaultValue === false) { + $explicitOptions[$fullName] = new InputOption($fullName, $shortcut, InputOption::VALUE_NONE, $description); + } elseif ($defaultValue === InputOption::VALUE_REQUIRED) { + $explicitOptions[$fullName] = new InputOption($fullName, $shortcut, InputOption::VALUE_REQUIRED, $description); + } elseif (is_array($defaultValue)) { + $optionality = count($defaultValue) ? InputOption::VALUE_OPTIONAL : InputOption::VALUE_REQUIRED; + $explicitOptions[$fullName] = new InputOption( + $fullName, + $shortcut, + InputOption::VALUE_IS_ARRAY | $optionality, + $description, + count($defaultValue) ? $defaultValue : null + ); + } else { + $explicitOptions[$fullName] = new InputOption($fullName, $shortcut, InputOption::VALUE_OPTIONAL, $description, $defaultValue); + } + } + + return $explicitOptions; + } + + /** + * An option might have a name such as 'silent|s'. In this + * instance, we will allow the @option or @default tag to + * reference the option only by name (e.g. 'silent' or 's' + * instead of 'silent|s'). + * + * @param string $optionName + * @return string + */ + public function findMatchingOption($optionName) + { + // Exit fast if there's an exact match + if ($this->options->exists($optionName)) { + return $optionName; + } + $existingOptionName = $this->findExistingOption($optionName); + if (isset($existingOptionName)) { + return $existingOptionName; + } + return $this->findOptionAmongAlternatives($optionName); + } + + /** + * @param string $optionName + * @return string + */ + protected function findOptionAmongAlternatives($optionName) + { + // Check the other direction: if the annotation contains @silent|s + // and the options array has 'silent|s'. + $checkMatching = explode('|', $optionName); + if (count($checkMatching) > 1) { + foreach ($checkMatching as $checkName) { + if ($this->options->exists($checkName)) { + $this->options->rename($checkName, $optionName); + return $optionName; + } + } + } + return $optionName; + } + + /** + * @param string $optionName + * @return string|null + */ + protected function findExistingOption($optionName) + { + // Check to see if we can find the option name in an existing option, + // e.g. if the options array has 'silent|s' => false, and the annotation + // is @silent. + foreach ($this->options()->getValues() as $name => $default) { + if (in_array($optionName, explode('|', $name))) { + return $name; + } + } + } + + /** + * Examine the parameters of the method for this command, and + * build a list of commandline arguements for them. + * + * @return array + */ + protected function determineAgumentClassifications() + { + $result = new DefaultsWithDescriptions(); + $params = $this->reflection->getParameters(); + $optionsFromParameters = $this->determineOptionsFromParameters(); + if ($this->lastParameterIsOptionsArray()) { + array_pop($params); + } + while (!empty($params) && ($params[0]->getClass() != null)) { + $param = array_shift($params); + $injectedClass = $param->getClass()->getName(); + array_unshift($this->injectedClasses, $injectedClass); + } + foreach ($params as $param) { + $this->addParameterToResult($result, $param); + } + return $result; + } + + /** + * Examine the provided parameter, and determine whether it + * is a parameter that will be filled in with a positional + * commandline argument. + */ + protected function addParameterToResult($result, $param) + { + // Commandline arguments must be strings, so ignore any + // parameter that is typehinted to any non-primative class. + if ($param->getClass() != null) { + return; + } + $result->add($param->name); + if ($param->isDefaultValueAvailable()) { + $defaultValue = $param->getDefaultValue(); + if (!$this->isAssoc($defaultValue)) { + $result->setDefaultValue($param->name, $defaultValue); + } + } elseif ($param->isArray()) { + $result->setDefaultValue($param->name, []); + } + } + + /** + * Examine the parameters of the method for this command, and determine + * the disposition of the options from them. + * + * @return array + */ + protected function determineOptionsFromParameters() + { + $params = $this->reflection->getParameters(); + if (empty($params)) { + return []; + } + $param = end($params); + if (!$param->isDefaultValueAvailable()) { + return []; + } + if (!$this->isAssoc($param->getDefaultValue())) { + return []; + } + return $param->getDefaultValue(); + } + + /** + * Determine if the last argument contains $options. + * + * Two forms indicate options: + * - $options = [] + * - $options = ['flag' => 'default-value'] + * + * Any other form, including `array $foo`, is not options. + */ + protected function lastParameterIsOptionsArray() + { + $params = $this->reflection->getParameters(); + if (empty($params)) { + return []; + } + $param = end($params); + if (!$param->isDefaultValueAvailable()) { + return []; + } + return is_array($param->getDefaultValue()); + } + + /** + * Helper; determine if an array is associative or not. An array + * is not associative if its keys are numeric, and numbered sequentially + * from zero. All other arrays are considered to be associative. + * + * @param array $arr The array + * @return boolean + */ + protected function isAssoc($arr) + { + if (!is_array($arr)) { + return false; + } + return array_keys($arr) !== range(0, count($arr) - 1); + } + + /** + * Convert from a method name to the corresponding command name. A + * method 'fooBar' will become 'foo:bar', and 'fooBarBazBoz' will + * become 'foo:bar-baz-boz'. + * + * @param string $camel method name. + * @return string + */ + protected function convertName($camel) + { + $splitter="-"; + $camel=preg_replace('/(?!^)[[:upper:]][[:lower:]]/', '$0', preg_replace('/(?!^)[[:upper:]]+/', $splitter.'$0', $camel)); + $camel = preg_replace("/$splitter/", ':', $camel, 1); + return strtolower($camel); + } + + /** + * Parse the docBlock comment for this command, and set the + * fields of this class with the data thereby obtained. + */ + protected function parseDocBlock() + { + if (!$this->docBlockIsParsed) { + // The parse function will insert data from the provided method + // into this object, using our accessors. + CommandDocBlockParserFactory::parse($this, $this->reflection); + $this->docBlockIsParsed = true; + } + } + + /** + * Given a list that might be 'a b c' or 'a, b, c' or 'a,b,c', + * convert the data into the last of these forms. + */ + protected static function convertListToCommaSeparated($text) + { + return preg_replace('#[ \t\n\r,]+#', ',', $text); + } +} diff --git a/vendor/consolidation/annotated-command/src/Parser/CommandInfoDeserializer.php b/vendor/consolidation/annotated-command/src/Parser/CommandInfoDeserializer.php new file mode 100644 index 0000000000..ae1b5dcd48 --- /dev/null +++ b/vendor/consolidation/annotated-command/src/Parser/CommandInfoDeserializer.php @@ -0,0 +1,89 @@ + 0) && + ($cache['schema'] == CommandInfo::SERIALIZATION_SCHEMA_VERSION) && + self::cachedMethodExists($cache); + } + + public function constructFromCache(CommandInfo $commandInfo, $info_array) + { + $info_array += $this->defaultSerializationData(); + + $commandInfo + ->setName($info_array['name']) + ->replaceRawAnnotations($info_array['annotations']) + ->setAliases($info_array['aliases']) + ->setHelp($info_array['help']) + ->setDescription($info_array['description']) + ->replaceExampleUsages($info_array['example_usages']) + ->setReturnType($info_array['return_type']) + ->setInjectedClasses($info_array['injected_classes']) + ; + + $this->constructDefaultsWithDescriptions($commandInfo->arguments(), (array)$info_array['arguments']); + $this->constructDefaultsWithDescriptions($commandInfo->options(), (array)$info_array['options']); + } + + protected function constructDefaultsWithDescriptions(DefaultsWithDescriptions $defaults, $data) + { + foreach ($data as $key => $info) { + $info = (array)$info; + $defaults->add($key, $info['description']); + if (array_key_exists('default', $info)) { + $defaults->setDefaultValue($key, $info['default']); + } + } + } + + + /** + * Default data. Everything should be provided during serialization; + * this is just as a fallback for unusual circumstances. + * @return array + */ + protected function defaultSerializationData() + { + return [ + 'name' => '', + 'description' => '', + 'help' => '', + 'aliases' => [], + 'annotations' => [], + 'example_usages' => [], + 'return_type' => [], + 'parameters' => [], + 'arguments' => [], + 'options' => [], + 'injected_classes' => [], + 'mtime' => 0, + ]; + } +} diff --git a/vendor/consolidation/annotated-command/src/Parser/CommandInfoSerializer.php b/vendor/consolidation/annotated-command/src/Parser/CommandInfoSerializer.php new file mode 100644 index 0000000000..21562319c8 --- /dev/null +++ b/vendor/consolidation/annotated-command/src/Parser/CommandInfoSerializer.php @@ -0,0 +1,61 @@ +getAnnotations(); + $path = $allAnnotations['_path']; + $className = $allAnnotations['_classname']; + + // Include the minimum information for command info (including placeholder records) + $info = [ + 'schema' => CommandInfo::SERIALIZATION_SCHEMA_VERSION, + 'class' => $className, + 'method_name' => $commandInfo->getMethodName(), + 'mtime' => filemtime($path), + 'injected_classes' => [], + ]; + + // If this is a valid method / hook, then add more information. + if ($commandInfo->valid()) { + $info += [ + 'name' => $commandInfo->getName(), + 'description' => $commandInfo->getDescription(), + 'help' => $commandInfo->getHelp(), + 'aliases' => $commandInfo->getAliases(), + 'annotations' => $commandInfo->getRawAnnotations()->getArrayCopy(), + 'example_usages' => $commandInfo->getExampleUsages(), + 'return_type' => $commandInfo->getReturnType(), + ]; + $info['arguments'] = $this->serializeDefaultsWithDescriptions($commandInfo->arguments()); + $info['options'] = $this->serializeDefaultsWithDescriptions($commandInfo->options()); + $info['injected_classes'] = $commandInfo->getInjectedClasses(); + } + + return $info; + } + + protected function serializeDefaultsWithDescriptions(DefaultsWithDescriptions $defaults) + { + $result = []; + foreach ($defaults->getValues() as $key => $val) { + $result[$key] = [ + 'description' => $defaults->getDescription($key), + ]; + if ($defaults->hasDefault($key)) { + $result[$key]['default'] = $val; + } + } + return $result; + } +} diff --git a/vendor/consolidation/annotated-command/src/Parser/DefaultsWithDescriptions.php b/vendor/consolidation/annotated-command/src/Parser/DefaultsWithDescriptions.php new file mode 100644 index 0000000000..d37fc51b16 --- /dev/null +++ b/vendor/consolidation/annotated-command/src/Parser/DefaultsWithDescriptions.php @@ -0,0 +1,162 @@ +values = $values; + $this->hasDefault = array_filter($this->values, function ($value) { + return isset($value); + }); + $this->descriptions = []; + $this->defaultDefault = $defaultDefault; + } + + /** + * Return just the key : default values mapping + * + * @return array + */ + public function getValues() + { + return $this->values; + } + + /** + * Return true if this set of options is empty + * + * @return + */ + public function isEmpty() + { + return empty($this->values); + } + + /** + * Check to see whether the speicifed key exists in the collection. + * + * @param string $key + * @return boolean + */ + public function exists($key) + { + return array_key_exists($key, $this->values); + } + + /** + * Get the value of one entry. + * + * @param string $key The key of the item. + * @return string + */ + public function get($key) + { + if (array_key_exists($key, $this->values)) { + return $this->values[$key]; + } + return $this->defaultDefault; + } + + /** + * Get the description of one entry. + * + * @param string $key The key of the item. + * @return string + */ + public function getDescription($key) + { + if (array_key_exists($key, $this->descriptions)) { + return $this->descriptions[$key]; + } + return ''; + } + + /** + * Add another argument to this command. + * + * @param string $key Name of the argument. + * @param string $description Help text for the argument. + * @param mixed $defaultValue The default value for the argument. + */ + public function add($key, $description = '', $defaultValue = null) + { + if (!$this->exists($key) || isset($defaultValue)) { + $this->values[$key] = isset($defaultValue) ? $defaultValue : $this->defaultDefault; + } + unset($this->descriptions[$key]); + if (!empty($description)) { + $this->descriptions[$key] = $description; + } + } + + /** + * Change the default value of an entry. + * + * @param string $key + * @param mixed $defaultValue + */ + public function setDefaultValue($key, $defaultValue) + { + $this->values[$key] = $defaultValue; + $this->hasDefault[$key] = true; + return $this; + } + + /** + * Check to see if the named argument definitively has a default value. + * + * @param string $key + * @return bool + */ + public function hasDefault($key) + { + return array_key_exists($key, $this->hasDefault); + } + + /** + * Remove an entry + * + * @param string $key The entry to remove + */ + public function clear($key) + { + unset($this->values[$key]); + unset($this->descriptions[$key]); + } + + /** + * Rename an existing option to something else. + */ + public function rename($oldName, $newName) + { + $this->add($newName, $this->getDescription($oldName), $this->get($oldName)); + $this->clear($oldName); + } +} diff --git a/vendor/consolidation/annotated-command/src/Parser/Internal/BespokeDocBlockParser.php b/vendor/consolidation/annotated-command/src/Parser/Internal/BespokeDocBlockParser.php new file mode 100644 index 0000000000..4b73c967b1 --- /dev/null +++ b/vendor/consolidation/annotated-command/src/Parser/Internal/BespokeDocBlockParser.php @@ -0,0 +1,360 @@ + 'processCommandTag', + 'name' => 'processCommandTag', + 'arg' => 'processArgumentTag', + 'param' => 'processParamTag', + 'return' => 'processReturnTag', + 'option' => 'processOptionTag', + 'default' => 'processDefaultTag', + 'aliases' => 'processAliases', + 'usage' => 'processUsageTag', + 'description' => 'processAlternateDescriptionTag', + 'desc' => 'processAlternateDescriptionTag', + ]; + + public function __construct(CommandInfo $commandInfo, \ReflectionMethod $reflection, $fqcnCache = null) + { + $this->commandInfo = $commandInfo; + $this->reflection = $reflection; + $this->fqcnCache = $fqcnCache ?: new FullyQualifiedClassCache(); + } + + /** + * Parse the docBlock comment for this command, and set the + * fields of this class with the data thereby obtained. + */ + public function parse() + { + $doc = $this->reflection->getDocComment(); + $this->parseDocBlock($doc); + } + + /** + * Save any tag that we do not explicitly recognize in the + * 'otherAnnotations' map. + */ + protected function processGenericTag($tag) + { + $this->commandInfo->addAnnotation($tag->getTag(), $tag->getContent()); + } + + /** + * Set the name of the command from a @command or @name annotation. + */ + protected function processCommandTag($tag) + { + if (!$tag->hasWordAndDescription($matches)) { + throw new \Exception('Could not determine command name from tag ' . (string)$tag); + } + $commandName = $matches['word']; + $this->commandInfo->setName($commandName); + // We also store the name in the 'other annotations' so that is is + // possible to determine if the method had a @command annotation. + $this->commandInfo->addAnnotation($tag->getTag(), $commandName); + } + + /** + * The @description and @desc annotations may be used in + * place of the synopsis (which we call 'description'). + * This is discouraged. + * + * @deprecated + */ + protected function processAlternateDescriptionTag($tag) + { + $this->commandInfo->setDescription($tag->getContent()); + } + + /** + * Store the data from a @param annotation in our argument descriptions. + */ + protected function processParamTag($tag) + { + if ($tag->hasTypeVariableAndDescription($matches)) { + if ($this->ignoredParamType($matches['type'])) { + return; + } + } + return $this->processArgumentTag($tag); + } + + protected function ignoredParamType($paramType) + { + // TODO: We should really only allow a couple of types here, + // e.g. 'string', 'array', 'bool'. Blacklist things we do not + // want for now to avoid breaking commands with weird types. + // Fix in the next major version. + // + // This works: + // return !in_array($paramType, ['string', 'array', 'integer', 'bool']); + return preg_match('#(InputInterface|OutputInterface)$#', $paramType); + } + + /** + * Store the data from a @arg annotation in our argument descriptions. + */ + protected function processArgumentTag($tag) + { + if (!$tag->hasVariable($matches)) { + throw new \Exception('Could not determine argument name from tag ' . (string)$tag); + } + if ($matches['variable'] == $this->optionParamName()) { + return; + } + $this->addOptionOrArgumentTag($tag, $this->commandInfo->arguments(), $matches['variable'], $matches['description']); + } + + /** + * Store the data from an @option annotation in our option descriptions. + */ + protected function processOptionTag($tag) + { + if (!$tag->hasVariable($matches)) { + throw new \Exception('Could not determine option name from tag ' . (string)$tag); + } + $this->addOptionOrArgumentTag($tag, $this->commandInfo->options(), $matches['variable'], $matches['description']); + } + + protected function addOptionOrArgumentTag($tag, DefaultsWithDescriptions $set, $name, $description) + { + $variableName = $this->commandInfo->findMatchingOption($name); + $description = static::removeLineBreaks($description); + list($description, $defaultValue) = $this->splitOutDefault($description); + $set->add($variableName, $description); + if ($defaultValue !== null) { + $set->setDefaultValue($variableName, $defaultValue); + } + } + + protected function splitOutDefault($description) + { + if (!preg_match('#(.*)(Default: *)(.*)#', trim($description), $matches)) { + return [$description, null]; + } + + return [trim($matches[1]), $this->interpretDefaultValue(trim($matches[3]))]; + } + + /** + * Store the data from a @default annotation in our argument or option store, + * as appropriate. + */ + protected function processDefaultTag($tag) + { + if (!$tag->hasVariable($matches)) { + throw new \Exception('Could not determine parameter name for default value from tag ' . (string)$tag); + } + $variableName = $matches['variable']; + $defaultValue = $this->interpretDefaultValue($matches['description']); + if ($this->commandInfo->arguments()->exists($variableName)) { + $this->commandInfo->arguments()->setDefaultValue($variableName, $defaultValue); + return; + } + $variableName = $this->commandInfo->findMatchingOption($variableName); + if ($this->commandInfo->options()->exists($variableName)) { + $this->commandInfo->options()->setDefaultValue($variableName, $defaultValue); + } + } + + /** + * Store the data from a @usage annotation in our example usage list. + */ + protected function processUsageTag($tag) + { + $lines = explode("\n", $tag->getContent()); + $usage = trim(array_shift($lines)); + $description = static::removeLineBreaks(implode("\n", array_map(function ($line) { + return trim($line); + }, $lines))); + + $this->commandInfo->setExampleUsage($usage, $description); + } + + /** + * Process the comma-separated list of aliases + */ + protected function processAliases($tag) + { + $this->commandInfo->setAliases((string)$tag->getContent()); + } + + /** + * Store the data from a @return annotation in our argument descriptions. + */ + protected function processReturnTag($tag) + { + // The return type might be a variable -- '$this'. It will + // usually be a type, like RowsOfFields, or \Namespace\RowsOfFields. + if (!$tag->hasVariableAndDescription($matches)) { + throw new \Exception('Could not determine return type from tag ' . (string)$tag); + } + // Look at namespace and `use` statments to make returnType a fqdn + $returnType = $matches['variable']; + $returnType = $this->findFullyQualifiedClass($returnType); + $this->commandInfo->setReturnType($returnType); + } + + protected function findFullyQualifiedClass($className) + { + if (strpos($className, '\\') !== false) { + return $className; + } + + return $this->fqcnCache->qualify($this->reflection->getFileName(), $className); + } + + private function parseDocBlock($doc) + { + // Remove the leading /** and the trailing */ + $doc = preg_replace('#^\s*/\*+\s*#', '', $doc); + $doc = preg_replace('#\s*\*+/\s*#', '', $doc); + + // Nothing left? Exit. + if (empty($doc)) { + return; + } + + $tagFactory = new TagFactory(); + $lines = []; + + foreach (explode("\n", $doc) as $row) { + // Remove trailing whitespace and leading space + '*'s + $row = rtrim($row); + $row = preg_replace('#^[ \t]*\**#', '', $row); + + if (!$tagFactory->parseLine($row)) { + $lines[] = $row; + } + } + + $this->processDescriptionAndHelp($lines); + $this->processAllTags($tagFactory->getTags()); + } + + protected function processDescriptionAndHelp($lines) + { + // Trim all of the lines individually. + $lines = + array_map( + function ($line) { + return trim($line); + }, + $lines + ); + + // Everything up to the first blank line goes in the description. + $description = array_shift($lines); + while ($this->nextLineIsNotEmpty($lines)) { + $description .= ' ' . array_shift($lines); + } + + // Everything else goes in the help. + $help = trim(implode("\n", $lines)); + + $this->commandInfo->setDescription($description); + $this->commandInfo->setHelp($help); + } + + protected function nextLineIsNotEmpty($lines) + { + if (empty($lines)) { + return false; + } + + $nextLine = trim($lines[0]); + return !empty($nextLine); + } + + protected function processAllTags($tags) + { + // Iterate over all of the tags, and process them as necessary. + foreach ($tags as $tag) { + $processFn = [$this, 'processGenericTag']; + if (array_key_exists($tag->getTag(), $this->tagProcessors)) { + $processFn = [$this, $this->tagProcessors[$tag->getTag()]]; + } + $processFn($tag); + } + } + + protected function lastParameterName() + { + $params = $this->commandInfo->getParameters(); + $param = end($params); + if (!$param) { + return ''; + } + return $param->name; + } + + /** + * Return the name of the last parameter if it holds the options. + */ + public function optionParamName() + { + // Remember the name of the last parameter, if it holds the options. + // We will use this information to ignore @param annotations for the options. + if (!isset($this->optionParamName)) { + $this->optionParamName = ''; + $options = $this->commandInfo->options(); + if (!$options->isEmpty()) { + $this->optionParamName = $this->lastParameterName(); + } + } + + return $this->optionParamName; + } + + protected function interpretDefaultValue($defaultValue) + { + $defaults = [ + 'null' => null, + 'true' => true, + 'false' => false, + "''" => '', + '[]' => [], + ]; + foreach ($defaults as $defaultName => $defaultTypedValue) { + if ($defaultValue == $defaultName) { + return $defaultTypedValue; + } + } + return $defaultValue; + } + + /** + * Given a list that might be 'a b c' or 'a, b, c' or 'a,b,c', + * convert the data into the last of these forms. + */ + protected static function convertListToCommaSeparated($text) + { + return preg_replace('#[ \t\n\r,]+#', ',', $text); + } + + /** + * Take a multiline description and convert it into a single + * long unbroken line. + */ + protected static function removeLineBreaks($text) + { + return trim(preg_replace('#[ \t\n\r]+#', ' ', $text)); + } +} diff --git a/vendor/consolidation/annotated-command/src/Parser/Internal/CommandDocBlockParserFactory.php b/vendor/consolidation/annotated-command/src/Parser/Internal/CommandDocBlockParserFactory.php new file mode 100644 index 0000000000..3421b7493d --- /dev/null +++ b/vendor/consolidation/annotated-command/src/Parser/Internal/CommandDocBlockParserFactory.php @@ -0,0 +1,20 @@ +parse(); + } + + private static function create(CommandInfo $commandInfo, \ReflectionMethod $reflection) + { + return new BespokeDocBlockParser($commandInfo, $reflection); + } +} diff --git a/vendor/consolidation/annotated-command/src/Parser/Internal/CsvUtils.php b/vendor/consolidation/annotated-command/src/Parser/Internal/CsvUtils.php new file mode 100644 index 0000000000..88ed3891fa --- /dev/null +++ b/vendor/consolidation/annotated-command/src/Parser/Internal/CsvUtils.php @@ -0,0 +1,49 @@ +[^\s$]+)[\s]*'; + const VARIABLE_REGEX = '\\$(?P[^\s$]+)[\s]*'; + const VARIABLE_OR_WORD_REGEX = '\\$?(?P[^\s$]+)[\s]*'; + const TYPE_REGEX = '(?P[^\s$]+)[\s]*'; + const WORD_REGEX = '(?P[^\s$]+)[\s]*'; + const DESCRIPTION_REGEX = '(?P.*)'; + const IS_TAG_REGEX = '/^[*\s]*@/'; + + /** + * Check if the provided string begins with a tag + * @param string $subject + * @return bool + */ + public static function isTag($subject) + { + return preg_match(self::IS_TAG_REGEX, $subject); + } + + /** + * Use a regular expression to separate the tag from the content. + * + * @param string $subject + * @param string[] &$matches Sets $matches['tag'] and $matches['description'] + * @return bool + */ + public static function splitTagAndContent($subject, &$matches) + { + $regex = '/' . self::TAG_REGEX . self::DESCRIPTION_REGEX . '/s'; + return preg_match($regex, $subject, $matches); + } + + /** + * DockblockTag constructor + */ + public function __construct($tag, $content = null) + { + $this->tag = $tag; + $this->content = $content; + } + + /** + * Add more content onto a tag during parsing. + */ + public function appendContent($line) + { + $this->content .= "\n$line"; + } + + /** + * Return the tag - e.g. "@foo description" returns 'foo' + * + * @return string + */ + public function getTag() + { + return $this->tag; + } + + /** + * Return the content portion of the tag - e.g. "@foo bar baz boz" returns + * "bar baz boz" + * + * @return string + */ + public function getContent() + { + return $this->content; + } + + /** + * Convert tag back into a string. + */ + public function __toString() + { + return '@' . $this->getTag() . ' ' . $this->getContent(); + } + + /** + * Determine if tag is one of: + * - "@tag variable description" + * - "@tag $variable description" + * - "@tag type $variable description" + * + * @param string $subject + * @param string[] &$matches Sets $matches['variable'] and + * $matches['description']; might set $matches['type']. + * @return bool + */ + public function hasVariable(&$matches) + { + return + $this->hasTypeVariableAndDescription($matches) || + $this->hasVariableAndDescription($matches); + } + + /** + * Determine if tag is "@tag $variable description" + * @param string $subject + * @param string[] &$matches Sets $matches['variable'] and + * $matches['description'] + * @return bool + */ + public function hasVariableAndDescription(&$matches) + { + $regex = '/^\s*' . self::VARIABLE_OR_WORD_REGEX . self::DESCRIPTION_REGEX . '/s'; + return preg_match($regex, $this->getContent(), $matches); + } + + /** + * Determine if tag is "@tag type $variable description" + * + * @param string $subject + * @param string[] &$matches Sets $matches['variable'], + * $matches['description'] and $matches['type']. + * @return bool + */ + public function hasTypeVariableAndDescription(&$matches) + { + $regex = '/^\s*' . self::TYPE_REGEX . self::VARIABLE_REGEX . self::DESCRIPTION_REGEX . '/s'; + return preg_match($regex, $this->getContent(), $matches); + } + + /** + * Determine if tag is "@tag word description" + * @param string $subject + * @param string[] &$matches Sets $matches['word'] and + * $matches['description'] + * @return bool + */ + public function hasWordAndDescription(&$matches) + { + $regex = '/^\s*' . self::WORD_REGEX . self::DESCRIPTION_REGEX . '/s'; + return preg_match($regex, $this->getContent(), $matches); + } +} diff --git a/vendor/consolidation/annotated-command/src/Parser/Internal/FullyQualifiedClassCache.php b/vendor/consolidation/annotated-command/src/Parser/Internal/FullyQualifiedClassCache.php new file mode 100644 index 0000000000..b247e9da0c --- /dev/null +++ b/vendor/consolidation/annotated-command/src/Parser/Internal/FullyQualifiedClassCache.php @@ -0,0 +1,106 @@ +primeCache($filename, $className); + return $this->cached($filename, $className); + } + + protected function cached($filename, $className) + { + return isset($this->classCache[$filename][$className]) ? $this->classCache[$filename][$className] : $className; + } + + protected function primeCache($filename, $className) + { + // If the cache has already been primed, do no further work + if (isset($this->namespaceCache[$filename])) { + return false; + } + + $handle = fopen($filename, "r"); + if (!$handle) { + return false; + } + + $namespaceName = $this->primeNamespaceCache($filename, $handle); + $this->primeUseCache($filename, $handle); + + // If there is no 'use' statement for the className, then + // generate an effective classname from the namespace + if (!isset($this->classCache[$filename][$className])) { + $this->classCache[$filename][$className] = $namespaceName . '\\' . $className; + } + + fclose($handle); + } + + protected function primeNamespaceCache($filename, $handle) + { + $namespaceName = $this->readNamespace($handle); + if (!$namespaceName) { + return false; + } + $this->namespaceCache[$filename] = $namespaceName; + return $namespaceName; + } + + protected function primeUseCache($filename, $handle) + { + $usedClasses = $this->readUseStatements($handle); + if (empty($usedClasses)) { + return false; + } + $this->classCache[$filename] = $usedClasses; + } + + protected function readNamespace($handle) + { + $namespaceRegex = '#^\s*namespace\s+#'; + $line = $this->readNextRelevantLine($handle); + if (!$line || !preg_match($namespaceRegex, $line)) { + return false; + } + + $namespaceName = preg_replace($namespaceRegex, '', $line); + $namespaceName = rtrim($namespaceName, ';'); + return $namespaceName; + } + + protected function readUseStatements($handle) + { + $useRegex = '#^\s*use\s+#'; + $result = []; + while (true) { + $line = $this->readNextRelevantLine($handle); + if (!$line || !preg_match($useRegex, $line)) { + return $result; + } + $usedClass = preg_replace($useRegex, '', $line); + $usedClass = rtrim($usedClass, ';'); + $unqualifiedClass = preg_replace('#.*\\\\#', '', $usedClass); + // If this is an aliased class, 'use \Foo\Bar as Baz', then adjust + if (strpos($usedClass, ' as ')) { + $unqualifiedClass = preg_replace('#.*\sas\s+#', '', $usedClass); + $usedClass = preg_replace('#[a-zA-Z0-9]+\s+as\s+#', '', $usedClass); + } + $result[$unqualifiedClass] = $usedClass; + } + } + + protected function readNextRelevantLine($handle) + { + while (($line = fgets($handle)) !== false) { + if (preg_match('#^\s*\w#', $line)) { + return trim($line); + } + } + return false; + } +} diff --git a/vendor/consolidation/annotated-command/src/Parser/Internal/TagFactory.php b/vendor/consolidation/annotated-command/src/Parser/Internal/TagFactory.php new file mode 100644 index 0000000000..4c48679d5a --- /dev/null +++ b/vendor/consolidation/annotated-command/src/Parser/Internal/TagFactory.php @@ -0,0 +1,67 @@ +current = null; + $this->tags = []; + } + + public function parseLine($line) + { + if (DocblockTag::isTag($line)) { + return $this->createTag($line); + } + if (empty($line)) { + return $this->storeCurrentTag(); + } + return $this->accumulateContent($line); + } + + public function getTags() + { + $this->storeCurrentTag(); + return $this->tags; + } + + protected function createTag($line) + { + DocblockTag::splitTagAndContent($line, $matches); + $this->storeCurrentTag(); + $this->current = new DocblockTag($matches['tag'], $matches['description']); + return true; + } + + protected function storeCurrentTag() + { + if (!$this->current) { + return false; + } + $this->tags[] = $this->current; + $this->current = false; + return true; + } + + protected function accumulateContent($line) + { + if (!$this->current) { + return false; + } + $this->current->appendContent($line); + return true; + } +} diff --git a/vendor/consolidation/annotated-command/src/ResultWriter.php b/vendor/consolidation/annotated-command/src/ResultWriter.php new file mode 100644 index 0000000000..a256384e80 --- /dev/null +++ b/vendor/consolidation/annotated-command/src/ResultWriter.php @@ -0,0 +1,210 @@ +formatterManager = $formatterManager; + return $this; + } + + /** + * Return the formatter manager + * @return FormatterManager + */ + public function formatterManager() + { + return $this->formatterManager; + } + + public function setDisplayErrorFunction(callable $fn) + { + $this->displayErrorFunction = $fn; + return $this; + } + + /** + * Handle the result output and status code calculation. + */ + public function handle(OutputInterface $output, $result, CommandData $commandData, $statusCodeDispatcher = null, $extractDispatcher = null) + { + // A little messy, for backwards compatibility: if the result implements + // ExitCodeInterface, then use that as the exit code. If a status code + // dispatcher returns a non-zero result, then we will never print a + // result. + $status = null; + if ($result instanceof ExitCodeInterface) { + $status = $result->getExitCode(); + } elseif (isset($statusCodeDispatcher)) { + $status = $statusCodeDispatcher->determineStatusCode($result); + if (isset($status) && ($status != 0)) { + return $status; + } + } + // If the result is an integer and no separate status code was provided, then use the result as the status and do no output. + if (is_integer($result) && !isset($status)) { + return $result; + } + $status = $this->interpretStatusCode($status); + + // Get the structured output, the output stream and the formatter + $structuredOutput = $result; + if (isset($extractDispatcher)) { + $structuredOutput = $extractDispatcher->extractOutput($result); + } + if (($status != 0) && is_string($structuredOutput)) { + $output = $this->chooseOutputStream($output, $status); + return $this->writeErrorMessage($output, $status, $structuredOutput, $result); + } + if ($this->dataCanBeFormatted($structuredOutput) && isset($this->formatterManager)) { + return $this->writeUsingFormatter($output, $structuredOutput, $commandData, $status); + } + return $this->writeCommandOutput($output, $structuredOutput, $status); + } + + protected function dataCanBeFormatted($structuredOutput) + { + if (!isset($this->formatterManager)) { + return false; + } + return + is_object($structuredOutput) || + is_array($structuredOutput); + } + + /** + * Determine the formatter that should be used to render + * output. + * + * If the user specified a format via the --format option, + * then always return that. Otherwise, return the default + * format, unless --pipe was specified, in which case + * return the default pipe format, format-pipe. + * + * n.b. --pipe is a handy option introduced in Drush 2 + * (or perhaps even Drush 1) that indicates that the command + * should select the output format that is most appropriate + * for use in scripts (e.g. to pipe to another command). + * + * @return string + */ + protected function getFormat(FormatterOptions $options) + { + // In Symfony Console, there is no way for us to differentiate + // between the user specifying '--format=table', and the user + // not specifying --format when the default value is 'table'. + // Therefore, we must make --field always override --format; it + // cannot become the default value for --format. + if ($options->get('field')) { + return 'string'; + } + $defaults = []; + if ($options->get('pipe')) { + return $options->get('pipe-format', [], 'tsv'); + } + return $options->getFormat($defaults); + } + + /** + * Determine whether we should use stdout or stderr. + */ + protected function chooseOutputStream(OutputInterface $output, $status) + { + // If the status code indicates an error, then print the + // result to stderr rather than stdout + if ($status && ($output instanceof ConsoleOutputInterface)) { + return $output->getErrorOutput(); + } + return $output; + } + + /** + * Call the formatter to output the provided data. + */ + protected function writeUsingFormatter(OutputInterface $output, $structuredOutput, CommandData $commandData, $status = 0) + { + $formatterOptions = $commandData->formatterOptions(); + $format = $this->getFormat($formatterOptions); + $this->formatterManager->write( + $output, + $format, + $structuredOutput, + $formatterOptions + ); + return $status; + } + + /** + * Description + * @param OutputInterface $output + * @param int $status + * @param string $structuredOutput + * @param mixed $originalResult + * @return type + */ + protected function writeErrorMessage($output, $status, $structuredOutput, $originalResult) + { + if (isset($this->displayErrorFunction)) { + call_user_func($this->displayErrorFunction, $output, $structuredOutput, $status, $originalResult); + } else { + $this->writeCommandOutput($output, $structuredOutput); + } + return $status; + } + + /** + * If the result object is a string, then print it. + */ + protected function writeCommandOutput( + OutputInterface $output, + $structuredOutput, + $status = 0 + ) { + // If there is no formatter, we will print strings, + // but can do no more than that. + if (is_string($structuredOutput)) { + $output->writeln($structuredOutput); + } + return $status; + } + + /** + * If a status code was set, then return it; otherwise, + * presume success. + */ + protected function interpretStatusCode($status) + { + if (isset($status)) { + return $status; + } + return 0; + } +} diff --git a/vendor/consolidation/output-formatters/.editorconfig b/vendor/consolidation/output-formatters/.editorconfig new file mode 100644 index 0000000000..095771e673 --- /dev/null +++ b/vendor/consolidation/output-formatters/.editorconfig @@ -0,0 +1,15 @@ +# This file is for unifying the coding style for different editors and IDEs +# editorconfig.org + +root = true + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[**.php] +indent_style = space +indent_size = 4 + diff --git a/vendor/consolidation/output-formatters/.scenarios.lock/finder5/.gitignore b/vendor/consolidation/output-formatters/.scenarios.lock/finder5/.gitignore new file mode 100644 index 0000000000..5657f6ea7d --- /dev/null +++ b/vendor/consolidation/output-formatters/.scenarios.lock/finder5/.gitignore @@ -0,0 +1 @@ +vendor \ No newline at end of file diff --git a/vendor/consolidation/output-formatters/.scenarios.lock/finder5/composer.json b/vendor/consolidation/output-formatters/.scenarios.lock/finder5/composer.json new file mode 100644 index 0000000000..16c532a0c1 --- /dev/null +++ b/vendor/consolidation/output-formatters/.scenarios.lock/finder5/composer.json @@ -0,0 +1,66 @@ +{ + "name": "consolidation/output-formatters", + "description": "Format text by applying transformations provided by plug-in formatters.", + "license": "MIT", + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "autoload": { + "psr-4": { + "Consolidation\\OutputFormatters\\": "../../src" + } + }, + "autoload-dev": { + "psr-4": { + "Consolidation\\TestUtils\\": "../../tests/src" + } + }, + "require": { + "symfony/finder": "^5", + "php": ">=5.4.0", + "dflydev/dot-access-data": "^1.1.0", + "symfony/console": "^2.8|^3|^4" + }, + "require-dev": { + "g1a/composer-test-scenarios": "^3", + "php-coveralls/php-coveralls": "^1", + "phpunit/phpunit": "^5.7.27", + "squizlabs/php_codesniffer": "^2.7", + "symfony/var-dumper": "^2.8|^3|^4", + "victorjonsson/markdowndocs": "^1.3" + }, + "suggest": { + "symfony/var-dumper": "For using the var_dump formatter" + }, + "config": { + "platform": { + "php": "7.2.5" + }, + "optimize-autoloader": true, + "sort-packages": true, + "vendor-dir": "../../vendor" + }, + "scripts": { + "api": "phpdoc-md generate src > docs/api.md", + "cs": "phpcs --standard=PSR2 -n src", + "cbf": "phpcbf --standard=PSR2 -n src", + "unit": "phpunit --colors=always", + "lint": [ + "find src -name '*.php' -print0 | xargs -0 -n1 php -l", + "find tests/src -name '*.php' -print0 | xargs -0 -n1 php -l" + ], + "test": [ + "@lint", + "@unit", + "@cs" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + } +} diff --git a/vendor/consolidation/output-formatters/.scenarios.lock/finder5/composer.lock b/vendor/consolidation/output-formatters/.scenarios.lock/finder5/composer.lock new file mode 100644 index 0000000000..ed32d49a20 --- /dev/null +++ b/vendor/consolidation/output-formatters/.scenarios.lock/finder5/composer.lock @@ -0,0 +1,2938 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "332b2dcb257e6b06110fe52b229c3f16", + "packages": [ + { + "name": "dflydev/dot-access-data", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/3fbd874921ab2c041e899d044585a2ab9795df8a", + "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "Dflydev\\DotAccessData": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "time": "2017-01-20T21:14:22+00:00" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14T16:28:37+00:00" + }, + { + "name": "symfony/console", + "version": "v4.4.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "90933b39c7b312fc3ceaa1ddeac7eb48cb953124" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/90933b39c7b312fc3ceaa1ddeac7eb48cb953124", + "reference": "90933b39c7b312fc3ceaa1ddeac7eb48cb953124", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/polyfill-php80": "^1.15", + "symfony/service-contracts": "^1.1|^2" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/event-dispatcher": "<4.3|>=5", + "symfony/lock": "<4.4", + "symfony/process": "<3.3" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/event-dispatcher": "^4.3", + "symfony/lock": "^4.4|^5.0", + "symfony/process": "^3.4|^4.0|^5.0", + "symfony/var-dumper": "^4.3|^5.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-15T07:58:55+00:00" + }, + { + "name": "symfony/finder", + "version": "v5.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "2c3ba7ad6884e6c4451ce2340e2dc23f6fa3e0d8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/2c3ba7ad6884e6c4451ce2340e2dc23f6fa3e0d8", + "reference": "2c3ba7ad6884e6c4451ce2340e2dc23f6fa3e0d8", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-02T16:23:27+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.18.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/a6977d63bf9a0ad4c65cd352709e230876f9904a", + "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-14T12:35:20+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.18.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "fffa1a52a023e782cdcc221d781fe1ec8f87fcca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fffa1a52a023e782cdcc221d781fe1ec8f87fcca", + "reference": "fffa1a52a023e782cdcc221d781fe1ec8f87fcca", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-14T12:35:20+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.18.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "d87d5766cbf48d72388a9f6b85f280c8ad51f981" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/d87d5766cbf48d72388a9f6b85f280c8ad51f981", + "reference": "d87d5766cbf48d72388a9f6b85f280c8ad51f981", + "shasum": "" + }, + "require": { + "php": ">=7.0.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-14T12:35:20+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d15da7ba4957ffb8f1747218be9e1a121fd298a1", + "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.0" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-07T11:33:47+00:00" + } + ], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "f350df0268e904597e3bd9c4685c53e0e333feea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/f350df0268e904597e3bd9c4685c53e0e333feea", + "reference": "f350df0268e904597e3bd9c4685c53e0e333feea", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2020-05-29T17:27:14+00:00" + }, + { + "name": "g1a/composer-test-scenarios", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/g1a/composer-test-scenarios.git", + "reference": "e7394206d845fd593d325440507fb940bef8cb62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/g1a/composer-test-scenarios/zipball/e7394206d845fd593d325440507fb940bef8cb62", + "reference": "e7394206d845fd593d325440507fb940bef8cb62", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0.0 || ^2.0.0", + "php": ">=5.4" + }, + "require-dev": { + "composer/composer": "^1.10.6 || ^2.0@rc", + "php-coveralls/php-coveralls": "^1.0", + "phpunit/phpunit": "^4.8.36|^6", + "squizlabs/php_codesniffer": "^3.5" + }, + "bin": [ + "scripts/dependency-licenses" + ], + "type": "composer-plugin", + "extra": { + "class": "ComposerTestScenarios\\Plugin", + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "ComposerTestScenarios\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Useful scripts for testing multiple sets of Composer dependencies.", + "time": "2020-09-28T20:54:35+00:00" + }, + { + "name": "guzzle/guzzle", + "version": "v3.8.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/4de0618a01b34aa1c8c33a3f13f396dcd3882eba", + "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3.3", + "symfony/event-dispatcher": ">=2.1" + }, + "replace": { + "guzzle/batch": "self.version", + "guzzle/cache": "self.version", + "guzzle/common": "self.version", + "guzzle/http": "self.version", + "guzzle/inflection": "self.version", + "guzzle/iterator": "self.version", + "guzzle/log": "self.version", + "guzzle/parser": "self.version", + "guzzle/plugin": "self.version", + "guzzle/plugin-async": "self.version", + "guzzle/plugin-backoff": "self.version", + "guzzle/plugin-cache": "self.version", + "guzzle/plugin-cookie": "self.version", + "guzzle/plugin-curlauth": "self.version", + "guzzle/plugin-error-response": "self.version", + "guzzle/plugin-history": "self.version", + "guzzle/plugin-log": "self.version", + "guzzle/plugin-md5": "self.version", + "guzzle/plugin-mock": "self.version", + "guzzle/plugin-oauth": "self.version", + "guzzle/service": "self.version", + "guzzle/stream": "self.version" + }, + "require-dev": { + "doctrine/cache": "*", + "monolog/monolog": "1.*", + "phpunit/phpunit": "3.7.*", + "psr/log": "1.0.*", + "symfony/class-loader": "*", + "zendframework/zend-cache": "<2.3", + "zendframework/zend-log": "<2.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.8-dev" + } + }, + "autoload": { + "psr-0": { + "Guzzle": "src/", + "Guzzle\\Tests": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Guzzle Community", + "homepage": "https://github.com/guzzle/guzzle/contributors" + } + ], + "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "abandoned": "guzzlehttp/guzzle", + "time": "2014-01-28T22:29:15+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.10.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/969b211f9a51aa1f6c01d1d2aef56d3bd91598e5", + "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2020-06-29T13:22:24+00:00" + }, + { + "name": "php-coveralls/php-coveralls", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-coveralls/php-coveralls.git", + "reference": "37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-coveralls/php-coveralls/zipball/37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad", + "reference": "37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-simplexml": "*", + "guzzle/guzzle": "^2.8 || ^3.0", + "php": "^5.3.3 || ^7.0", + "psr/log": "^1.0", + "symfony/config": "^2.1 || ^3.0 || ^4.0", + "symfony/console": "^2.1 || ^3.0 || ^4.0", + "symfony/stopwatch": "^2.0 || ^3.0 || ^4.0", + "symfony/yaml": "^2.0 || ^3.0 || ^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.4.3 || ^6.0" + }, + "suggest": { + "symfony/http-kernel": "Allows Symfony integration" + }, + "bin": [ + "bin/coveralls" + ], + "type": "library", + "autoload": { + "psr-4": { + "Satooshi\\": "src/Satooshi/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kitamura Satoshi", + "email": "with.no.parachute@gmail.com", + "homepage": "https://www.facebook.com/satooshi.jp" + } + ], + "description": "PHP client library for Coveralls API", + "homepage": "https://github.com/php-coveralls/php-coveralls", + "keywords": [ + "ci", + "coverage", + "github", + "test" + ], + "time": "2017-12-06T23:17:56+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.2.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2020-09-03T19:13:55+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "time": "2020-09-17T18:55:26+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "v1.10.3", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "451c3cd1418cf640de218914901e51b064abb093" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", + "reference": "451c3cd1418cf640de218914901e51b064abb093", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", + "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5 || ^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2020-03-05T15:02:03+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "4.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^5.6 || ^7.0", + "phpunit/php-file-iterator": "^1.3", + "phpunit/php-text-template": "^1.2", + "phpunit/php-token-stream": "^1.4.2 || ^2.0", + "sebastian/code-unit-reverse-lookup": "^1.0", + "sebastian/environment": "^1.3.2 || ^2.0", + "sebastian/version": "^1.0 || ^2.0" + }, + "require-dev": { + "ext-xdebug": "^2.1.4", + "phpunit/phpunit": "^5.7" + }, + "suggest": { + "ext-xdebug": "^2.5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2017-04-02T07:44:40+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2017-11-27T13:52:08+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "791198a2c6254db10131eecfe8c06670700904db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", + "reference": "791198a2c6254db10131eecfe8c06670700904db", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "abandoned": true, + "time": "2017-11-27T05:48:46+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "5.7.27", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c", + "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "~1.3", + "php": "^5.6 || ^7.0", + "phpspec/prophecy": "^1.6.2", + "phpunit/php-code-coverage": "^4.0.4", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "^3.2", + "sebastian/comparator": "^1.2.4", + "sebastian/diff": "^1.4.3", + "sebastian/environment": "^1.3.4 || ^2.0", + "sebastian/exporter": "~2.0", + "sebastian/global-state": "^1.1", + "sebastian/object-enumerator": "~2.0", + "sebastian/resource-operations": "~1.0", + "sebastian/version": "^1.0.6|^2.0.1", + "symfony/yaml": "~2.1|~3.0|~4.0" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-xdebug": "*", + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.7.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2018-02-01T05:50:59+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "a23b761686d50a560cc56233b9ecf49597cc9118" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/a23b761686d50a560cc56233b9ecf49597cc9118", + "reference": "a23b761686d50a560cc56233b9ecf49597cc9118", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.6 || ^7.0", + "phpunit/php-text-template": "^1.2", + "sebastian/exporter": "^1.2 || ^2.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "abandoned": true, + "time": "2017-06-30T09:13:00+00:00" + }, + { + "name": "psr/log", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2020-03-23T09:12:05+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2017-03-04T06:30:41+00:00" + }, + { + "name": "sebastian/comparator", + "version": "1.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2 || ~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2017-01-29T09:50:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2017-05-22T07:24:03+00:00" + }, + { + "name": "sebastian/environment", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2016-11-26T07:53:53+00:00" + }, + { + "name": "sebastian/exporter", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2016-11-19T08:54:04+00:00" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2015-10-12T03:26:01+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1311872ac850040a79c3c058bea3e22d0f09cbb7", + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2017-02-18T15:18:39+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2016-11-19T07:33:16+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28T20:34:47+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "2.9.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "2acf168de78487db620ab4bc524135a13cfe6745" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/2acf168de78487db620ab4bc524135a13cfe6745", + "reference": "2acf168de78487db620ab4bc524135a13cfe6745", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "bin": [ + "scripts/phpcs", + "scripts/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "classmap": [ + "CodeSniffer.php", + "CodeSniffer/CLI.php", + "CodeSniffer/Exception.php", + "CodeSniffer/File.php", + "CodeSniffer/Fixer.php", + "CodeSniffer/Report.php", + "CodeSniffer/Reporting.php", + "CodeSniffer/Sniff.php", + "CodeSniffer/Tokens.php", + "CodeSniffer/Reports/", + "CodeSniffer/Tokenizers/", + "CodeSniffer/DocGenerators/", + "CodeSniffer/Standards/AbstractPatternSniff.php", + "CodeSniffer/Standards/AbstractScopeSniff.php", + "CodeSniffer/Standards/AbstractVariableSniff.php", + "CodeSniffer/Standards/IncorrectPatternException.php", + "CodeSniffer/Standards/Generic/Sniffs/", + "CodeSniffer/Standards/MySource/Sniffs/", + "CodeSniffer/Standards/PEAR/Sniffs/", + "CodeSniffer/Standards/PSR1/Sniffs/", + "CodeSniffer/Standards/PSR2/Sniffs/", + "CodeSniffer/Standards/Squiz/Sniffs/", + "CodeSniffer/Standards/Zend/Sniffs/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2018-11-07T22:31:41+00:00" + }, + { + "name": "symfony/config", + "version": "v4.4.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "7c5a1002178a612787c291a4f515f87b19176b61" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/7c5a1002178a612787c291a4f515f87b19176b61", + "reference": "7c5a1002178a612787c291a4f515f87b19176b61", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/filesystem": "^3.4|^4.0|^5.0", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/finder": "<3.4" + }, + "require-dev": { + "symfony/event-dispatcher": "^3.4|^4.0|^5.0", + "symfony/finder": "^3.4|^4.0|^5.0", + "symfony/messenger": "^4.1|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/yaml": "^3.4|^4.0|^5.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Config Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-10-02T07:34:48+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v4.4.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "e17bb5e0663dc725f7cdcafc932132735b4725cd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/e17bb5e0663dc725f7cdcafc932132735b4725cd", + "reference": "e17bb5e0663dc725f7cdcafc932132735b4725cd", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/event-dispatcher-contracts": "^1.1" + }, + "conflict": { + "symfony/dependency-injection": "<3.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "1.1" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/error-handler": "~3.4|~4.4", + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/http-foundation": "^3.4|^4.0|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/stopwatch": "^3.4|^4.0|^5.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-18T14:07:46+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v1.1.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "84e23fdcd2517bf37aecbd16967e83f0caee25a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/84e23fdcd2517bf37aecbd16967e83f0caee25a7", + "reference": "84e23fdcd2517bf37aecbd16967e83f0caee25a7", + "shasum": "" + }, + "require": { + "php": ">=7.1.3" + }, + "suggest": { + "psr/event-dispatcher": "", + "symfony/event-dispatcher-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-06T13:19:58+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v5.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "1a8697545a8d87b9f2f6b1d32414199cc5e20aae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/1a8697545a8d87b9f2f6b1d32414199cc5e20aae", + "reference": "1a8697545a8d87b9f2f6b1d32414199cc5e20aae", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-27T14:02:37+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.18.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "1c302646f6efc070cd46856e600e5e0684d6b454" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/1c302646f6efc070cd46856e600e5e0684d6b454", + "reference": "1c302646f6efc070cd46856e600e5e0684d6b454", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-14T12:35:20+00:00" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.18.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "639447d008615574653fb3bc60d1986d7172eaae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/639447d008615574653fb3bc60d1986d7172eaae", + "reference": "639447d008615574653fb3bc60d1986d7172eaae", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-14T12:35:20+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v4.4.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "6f89e19772cf61b3c65bab329fe0e318259fbd91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/6f89e19772cf61b3c65bab329fe0e318259fbd91", + "reference": "6f89e19772cf61b3c65bab329fe0e318259fbd91", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/service-contracts": "^1.0|^2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Stopwatch Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-02T16:08:58+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v4.4.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "0dc22bdf9d1197467bb04d505355180b6f20bcca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/0dc22bdf9d1197467bb04d505355180b6f20bcca", + "reference": "0dc22bdf9d1197467bb04d505355180b6f20bcca", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php72": "~1.5", + "symfony/polyfill-php80": "^1.15" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", + "symfony/console": "<3.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^3.4|^4.0|^5.0", + "symfony/process": "^4.4|^5.0", + "twig/twig": "^1.34|^2.4|^3.0" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony mechanism for exploring and dumping PHP variables", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-18T08:35:10+00:00" + }, + { + "name": "symfony/yaml", + "version": "v4.4.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "c7885964b1eceb70b0981556d0a9b01d2d97c8d1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/c7885964b1eceb70b0981556d0a9b01d2d97c8d1", + "reference": "c7885964b1eceb70b0981556d0a9b01d2d97c8d1", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "require-dev": { + "symfony/console": "^3.4|^4.0|^5.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-27T03:36:23+00:00" + }, + { + "name": "victorjonsson/markdowndocs", + "version": "1.3.8", + "source": { + "type": "git", + "url": "https://github.com/victorjonsson/PHP-Markdown-Documentation-Generator.git", + "reference": "c5eb16ff5bd15ee60223883ddacba0ab8797268d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/victorjonsson/PHP-Markdown-Documentation-Generator/zipball/c5eb16ff5bd15ee60223883ddacba0ab8797268d", + "reference": "c5eb16ff5bd15ee60223883ddacba0ab8797268d", + "shasum": "" + }, + "require": { + "php": ">=5.5.0", + "symfony/console": ">=2.6" + }, + "require-dev": { + "phpunit/phpunit": "3.7.23" + }, + "bin": [ + "bin/phpdoc-md" + ], + "type": "library", + "autoload": { + "psr-0": { + "PHPDocsMD": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Victor Jonsson", + "email": "kontakt@victorjonsson.se" + } + ], + "description": "Command line tool for generating markdown-formatted class documentation", + "homepage": "https://github.com/victorjonsson/PHP-Markdown-Documentation-Generator", + "time": "2017-04-20T09:52:47+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.9.1", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<3.9.1" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^7.5.13" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2020-07-08T17:02:28+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.4.0" + }, + "platform-dev": [], + "platform-overrides": { + "php": "7.2.5" + }, + "plugin-api-version": "1.1.0" +} diff --git a/vendor/consolidation/output-formatters/.scenarios.lock/install b/vendor/consolidation/output-formatters/.scenarios.lock/install new file mode 100755 index 0000000000..4d8a777705 --- /dev/null +++ b/vendor/consolidation/output-formatters/.scenarios.lock/install @@ -0,0 +1,61 @@ +#!/bin/bash + +SCENARIO=$1 +DEPENDENCIES=${2-install} + +# Convert the aliases 'highest', 'lowest' and 'lock' to +# the corresponding composer update command to run. +case $DEPENDENCIES in + highest) + UPDATE_COMMAND=update + ;; + lowest) + UPDATE_COMMAND='update --prefer-lowest' + ;; + lock|default|"") + UPDATE_COMMAND='' + ;; +esac + +original_name=scenarios +recommended_name=".scenarios.lock" + +base="$original_name" +if [ -d "$recommended_name" ] ; then + base="$recommended_name" +fi + +# If scenario is not specified, install the lockfile at +# the root of the project. +dir="$base/${SCENARIO}" +if [ -z "$SCENARIO" ] || [ "$SCENARIO" == "default" ] ; then + SCENARIO=default + dir=. +fi + +# Test to make sure that the selected scenario exists. +if [ ! -d "$dir" ] ; then + echo "Requested scenario '${SCENARIO}' does not exist." + exit 1 +fi + +echo +echo "::" +echo ":: Switch to ${SCENARIO} scenario" +echo "::" +echo + +set -ex + +composer -n validate --working-dir=$dir --no-check-all --ansi + +if [ ! -z "$UPDATE_COMMAND" ] ; then + composer -n --working-dir=$dir ${UPDATE_COMMAND} --prefer-dist --no-scripts +fi +composer -n --working-dir=$dir install --prefer-dist + +# If called from a CI context, print out some extra information about +# what we just installed. +if [[ -n "$CI" ]] ; then + composer -n --working-dir=$dir info +fi diff --git a/vendor/consolidation/output-formatters/.scenarios.lock/symfony2/.gitignore b/vendor/consolidation/output-formatters/.scenarios.lock/symfony2/.gitignore new file mode 100644 index 0000000000..88e99d50df --- /dev/null +++ b/vendor/consolidation/output-formatters/.scenarios.lock/symfony2/.gitignore @@ -0,0 +1,2 @@ +vendor +composer.lock \ No newline at end of file diff --git a/vendor/consolidation/output-formatters/.scenarios.lock/symfony2/composer.json b/vendor/consolidation/output-formatters/.scenarios.lock/symfony2/composer.json new file mode 100644 index 0000000000..b003dce2e0 --- /dev/null +++ b/vendor/consolidation/output-formatters/.scenarios.lock/symfony2/composer.json @@ -0,0 +1,65 @@ +{ + "name": "consolidation/output-formatters", + "description": "Format text by applying transformations provided by plug-in formatters.", + "license": "MIT", + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "autoload": { + "psr-4": { + "Consolidation\\OutputFormatters\\": "../../src" + } + }, + "autoload-dev": { + "psr-4": { + "Consolidation\\TestUtils\\": "../../tests/src" + } + }, + "require": { + "symfony/console": "^2.8", + "php": ">=5.4.0", + "dflydev/dot-access-data": "^1.1.0", + "symfony/finder": "^2.5|^3|^4|^5" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36", + "g1a/composer-test-scenarios": "^3", + "squizlabs/php_codesniffer": "^2.7", + "symfony/var-dumper": "^2.8|^3|^4", + "victorjonsson/markdowndocs": "^1.3" + }, + "suggest": { + "symfony/var-dumper": "For using the var_dump formatter" + }, + "config": { + "platform": { + "php": "5.4.8" + }, + "optimize-autoloader": true, + "sort-packages": true, + "vendor-dir": "../../vendor" + }, + "scripts": { + "api": "phpdoc-md generate src > docs/api.md", + "cs": "phpcs --standard=PSR2 -n src", + "cbf": "phpcbf --standard=PSR2 -n src", + "unit": "phpunit --colors=always", + "lint": [ + "find src -name '*.php' -print0 | xargs -0 -n1 php -l", + "find tests/src -name '*.php' -print0 | xargs -0 -n1 php -l" + ], + "test": [ + "@lint", + "@unit", + "@cs" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + } +} diff --git a/vendor/consolidation/output-formatters/.scenarios.lock/symfony3/.gitignore b/vendor/consolidation/output-formatters/.scenarios.lock/symfony3/.gitignore new file mode 100644 index 0000000000..5657f6ea7d --- /dev/null +++ b/vendor/consolidation/output-formatters/.scenarios.lock/symfony3/.gitignore @@ -0,0 +1 @@ +vendor \ No newline at end of file diff --git a/vendor/consolidation/output-formatters/.scenarios.lock/symfony3/composer.json b/vendor/consolidation/output-formatters/.scenarios.lock/symfony3/composer.json new file mode 100644 index 0000000000..a4d1f5e784 --- /dev/null +++ b/vendor/consolidation/output-formatters/.scenarios.lock/symfony3/composer.json @@ -0,0 +1,67 @@ +{ + "name": "consolidation/output-formatters", + "description": "Format text by applying transformations provided by plug-in formatters.", + "license": "MIT", + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "autoload": { + "psr-4": { + "Consolidation\\OutputFormatters\\": "../../src" + } + }, + "autoload-dev": { + "psr-4": { + "Consolidation\\TestUtils\\": "../../tests/src" + } + }, + "require": { + "symfony/console": "^3.4", + "symfony/finder": "^3.4", + "symfony/var-dumper": "^3.4", + "php": ">=5.4.0", + "dflydev/dot-access-data": "^1.1.0" + }, + "require-dev": { + "g1a/composer-test-scenarios": "^3", + "php-coveralls/php-coveralls": "^1", + "phpunit/phpunit": "^5.7.27", + "squizlabs/php_codesniffer": "^2.7", + "symfony/var-dumper": "^2.8|^3|^4", + "victorjonsson/markdowndocs": "^1.3" + }, + "suggest": { + "symfony/var-dumper": "For using the var_dump formatter" + }, + "config": { + "platform": { + "php": "5.6.32" + }, + "optimize-autoloader": true, + "sort-packages": true, + "vendor-dir": "../../vendor" + }, + "scripts": { + "api": "phpdoc-md generate src > docs/api.md", + "cs": "phpcs --standard=PSR2 -n src", + "cbf": "phpcbf --standard=PSR2 -n src", + "unit": "phpunit --colors=always", + "lint": [ + "find src -name '*.php' -print0 | xargs -0 -n1 php -l", + "find tests/src -name '*.php' -print0 | xargs -0 -n1 php -l" + ], + "test": [ + "@lint", + "@unit", + "@cs" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + } +} diff --git a/vendor/consolidation/output-formatters/.scenarios.lock/symfony3/composer.lock b/vendor/consolidation/output-formatters/.scenarios.lock/symfony3/composer.lock new file mode 100644 index 0000000000..816a884d2c --- /dev/null +++ b/vendor/consolidation/output-formatters/.scenarios.lock/symfony3/composer.lock @@ -0,0 +1,2517 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "b93f5c37dbac1262cf9e9a1170b9e3ad", + "packages": [ + { + "name": "dflydev/dot-access-data", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/3fbd874921ab2c041e899d044585a2ab9795df8a", + "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "Dflydev\\DotAccessData": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "time": "2017-01-20T21:14:22+00:00" + }, + { + "name": "psr/log", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2020-03-23T09:12:05+00:00" + }, + { + "name": "symfony/console", + "version": "v3.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "b28996bc0a3b08914b2a8609163ec35b36b30685" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/b28996bc0a3b08914b2a8609163ec35b36b30685", + "reference": "b28996bc0a3b08914b2a8609163ec35b36b30685", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/debug": "~2.8|~3.0|~4.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/process": "<3.3" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.3|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "~2.8|~3.0|~4.0", + "symfony/lock": "~3.4|~4.0", + "symfony/process": "~3.3|~4.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-09T05:09:37+00:00" + }, + { + "name": "symfony/debug", + "version": "v3.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "9109e4414e684d0b75276ae203883467476d25d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/9109e4414e684d0b75276ae203883467476d25d0", + "reference": "9109e4414e684d0b75276ae203883467476d25d0", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/http-kernel": "~2.8|~3.0|~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Debug Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-08T22:19:14+00:00" + }, + { + "name": "symfony/finder", + "version": "v3.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "52140652ed31cee3dabd0c481b5577201fa769b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/52140652ed31cee3dabd0c481b5577201fa769b4", + "reference": "52140652ed31cee3dabd0c481b5577201fa769b4", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-02T16:06:40+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.18.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/a6977d63bf9a0ad4c65cd352709e230876f9904a", + "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-14T12:35:20+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v3.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "df8fe9c1c5dc3eb968db32ffa6b699d89fee2606" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/df8fe9c1c5dc3eb968db32ffa6b699d89fee2606", + "reference": "df8fe9c1c5dc3eb968db32ffa6b699d89fee2606", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0" + }, + "require-dev": { + "ext-iconv": "*", + "twig/twig": "~1.34|~2.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "ext-symfony_debug": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony mechanism for exploring and dumping PHP variables", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-18T08:10:16+00:00" + } + ], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2015-06-14T21:17:01+00:00" + }, + { + "name": "g1a/composer-test-scenarios", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/g1a/composer-test-scenarios.git", + "reference": "e7394206d845fd593d325440507fb940bef8cb62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/g1a/composer-test-scenarios/zipball/e7394206d845fd593d325440507fb940bef8cb62", + "reference": "e7394206d845fd593d325440507fb940bef8cb62", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0.0 || ^2.0.0", + "php": ">=5.4" + }, + "require-dev": { + "composer/composer": "^1.10.6 || ^2.0@rc", + "php-coveralls/php-coveralls": "^1.0", + "phpunit/phpunit": "^4.8.36|^6", + "squizlabs/php_codesniffer": "^3.5" + }, + "bin": [ + "scripts/dependency-licenses" + ], + "type": "composer-plugin", + "extra": { + "class": "ComposerTestScenarios\\Plugin", + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "ComposerTestScenarios\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Useful scripts for testing multiple sets of Composer dependencies.", + "time": "2020-09-28T20:54:35+00:00" + }, + { + "name": "guzzle/guzzle", + "version": "v3.9.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle3.git", + "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle3/zipball/0645b70d953bc1c067bbc8d5bc53194706b628d9", + "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3.3", + "symfony/event-dispatcher": "~2.1" + }, + "replace": { + "guzzle/batch": "self.version", + "guzzle/cache": "self.version", + "guzzle/common": "self.version", + "guzzle/http": "self.version", + "guzzle/inflection": "self.version", + "guzzle/iterator": "self.version", + "guzzle/log": "self.version", + "guzzle/parser": "self.version", + "guzzle/plugin": "self.version", + "guzzle/plugin-async": "self.version", + "guzzle/plugin-backoff": "self.version", + "guzzle/plugin-cache": "self.version", + "guzzle/plugin-cookie": "self.version", + "guzzle/plugin-curlauth": "self.version", + "guzzle/plugin-error-response": "self.version", + "guzzle/plugin-history": "self.version", + "guzzle/plugin-log": "self.version", + "guzzle/plugin-md5": "self.version", + "guzzle/plugin-mock": "self.version", + "guzzle/plugin-oauth": "self.version", + "guzzle/service": "self.version", + "guzzle/stream": "self.version" + }, + "require-dev": { + "doctrine/cache": "~1.3", + "monolog/monolog": "~1.0", + "phpunit/phpunit": "3.7.*", + "psr/log": "~1.0", + "symfony/class-loader": "~2.1", + "zendframework/zend-cache": "2.*,<2.3", + "zendframework/zend-log": "2.*,<2.3" + }, + "suggest": { + "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.9-dev" + } + }, + "autoload": { + "psr-0": { + "Guzzle": "src/", + "Guzzle\\Tests": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Guzzle Community", + "homepage": "https://github.com/guzzle/guzzle/contributors" + } + ], + "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "abandoned": "guzzlehttp/guzzle", + "time": "2015-03-18T18:23:50+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2017-10-19T19:58:43+00:00" + }, + { + "name": "php-coveralls/php-coveralls", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-coveralls/php-coveralls.git", + "reference": "37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-coveralls/php-coveralls/zipball/37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad", + "reference": "37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-simplexml": "*", + "guzzle/guzzle": "^2.8 || ^3.0", + "php": "^5.3.3 || ^7.0", + "psr/log": "^1.0", + "symfony/config": "^2.1 || ^3.0 || ^4.0", + "symfony/console": "^2.1 || ^3.0 || ^4.0", + "symfony/stopwatch": "^2.0 || ^3.0 || ^4.0", + "symfony/yaml": "^2.0 || ^3.0 || ^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.4.3 || ^6.0" + }, + "suggest": { + "symfony/http-kernel": "Allows Symfony integration" + }, + "bin": [ + "bin/coveralls" + ], + "type": "library", + "autoload": { + "psr-4": { + "Satooshi\\": "src/Satooshi/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kitamura Satoshi", + "email": "with.no.parachute@gmail.com", + "homepage": "https://www.facebook.com/satooshi.jp" + } + ], + "description": "PHP client library for Coveralls API", + "homepage": "https://github.com/php-coveralls/php-coveralls", + "keywords": [ + "ci", + "coverage", + "github", + "test" + ], + "time": "2017-12-06T23:17:56+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2017-09-11T18:02:19+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "bf329f6c1aadea3299f08ee804682b7c45b326a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/bf329f6c1aadea3299f08ee804682b7c45b326a2", + "reference": "bf329f6c1aadea3299f08ee804682b7c45b326a2", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0", + "phpdocumentor/reflection-common": "^1.0.0", + "phpdocumentor/type-resolver": "^0.4.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2017-11-10T14:09:06+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "time": "2017-07-14T14:27:02+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "v1.10.3", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "451c3cd1418cf640de218914901e51b064abb093" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", + "reference": "451c3cd1418cf640de218914901e51b064abb093", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", + "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5 || ^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2020-03-05T15:02:03+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "4.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^5.6 || ^7.0", + "phpunit/php-file-iterator": "^1.3", + "phpunit/php-text-template": "^1.2", + "phpunit/php-token-stream": "^1.4.2 || ^2.0", + "sebastian/code-unit-reverse-lookup": "^1.0", + "sebastian/environment": "^1.3.2 || ^2.0", + "sebastian/version": "^1.0 || ^2.0" + }, + "require-dev": { + "ext-xdebug": "^2.1.4", + "phpunit/phpunit": "^5.7" + }, + "suggest": { + "ext-xdebug": "^2.5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2017-04-02T07:44:40+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2017-11-27T13:52:08+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.4.12", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1ce90ba27c42e4e44e6d8458241466380b51fa16", + "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "abandoned": true, + "time": "2017-12-04T08:55:13+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "5.7.27", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c", + "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "~1.3", + "php": "^5.6 || ^7.0", + "phpspec/prophecy": "^1.6.2", + "phpunit/php-code-coverage": "^4.0.4", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "^3.2", + "sebastian/comparator": "^1.2.4", + "sebastian/diff": "^1.4.3", + "sebastian/environment": "^1.3.4 || ^2.0", + "sebastian/exporter": "~2.0", + "sebastian/global-state": "^1.1", + "sebastian/object-enumerator": "~2.0", + "sebastian/resource-operations": "~1.0", + "sebastian/version": "^1.0.6|^2.0.1", + "symfony/yaml": "~2.1|~3.0|~4.0" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-xdebug": "*", + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.7.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2018-02-01T05:50:59+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "a23b761686d50a560cc56233b9ecf49597cc9118" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/a23b761686d50a560cc56233b9ecf49597cc9118", + "reference": "a23b761686d50a560cc56233b9ecf49597cc9118", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.6 || ^7.0", + "phpunit/php-text-template": "^1.2", + "sebastian/exporter": "^1.2 || ^2.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "abandoned": true, + "time": "2017-06-30T09:13:00+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2017-03-04T06:30:41+00:00" + }, + { + "name": "sebastian/comparator", + "version": "1.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2 || ~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2017-01-29T09:50:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2017-05-22T07:24:03+00:00" + }, + { + "name": "sebastian/environment", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2016-11-26T07:53:53+00:00" + }, + { + "name": "sebastian/exporter", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2016-11-19T08:54:04+00:00" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2015-10-12T03:26:01+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1311872ac850040a79c3c058bea3e22d0f09cbb7", + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2017-02-18T15:18:39+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2016-11-19T07:33:16+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28T20:34:47+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "2.9.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "2acf168de78487db620ab4bc524135a13cfe6745" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/2acf168de78487db620ab4bc524135a13cfe6745", + "reference": "2acf168de78487db620ab4bc524135a13cfe6745", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "bin": [ + "scripts/phpcs", + "scripts/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "classmap": [ + "CodeSniffer.php", + "CodeSniffer/CLI.php", + "CodeSniffer/Exception.php", + "CodeSniffer/File.php", + "CodeSniffer/Fixer.php", + "CodeSniffer/Report.php", + "CodeSniffer/Reporting.php", + "CodeSniffer/Sniff.php", + "CodeSniffer/Tokens.php", + "CodeSniffer/Reports/", + "CodeSniffer/Tokenizers/", + "CodeSniffer/DocGenerators/", + "CodeSniffer/Standards/AbstractPatternSniff.php", + "CodeSniffer/Standards/AbstractScopeSniff.php", + "CodeSniffer/Standards/AbstractVariableSniff.php", + "CodeSniffer/Standards/IncorrectPatternException.php", + "CodeSniffer/Standards/Generic/Sniffs/", + "CodeSniffer/Standards/MySource/Sniffs/", + "CodeSniffer/Standards/PEAR/Sniffs/", + "CodeSniffer/Standards/PSR1/Sniffs/", + "CodeSniffer/Standards/PSR2/Sniffs/", + "CodeSniffer/Standards/Squiz/Sniffs/", + "CodeSniffer/Standards/Zend/Sniffs/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2018-11-07T22:31:41+00:00" + }, + { + "name": "symfony/config", + "version": "v3.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "d061a451ff6bc170c5454f4ac9b41ad2179e3960" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/d061a451ff6bc170c5454f4ac9b41ad2179e3960", + "reference": "d061a451ff6bc170c5454f4ac9b41ad2179e3960", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/filesystem": "~2.8|~3.0|~4.0", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/dependency-injection": "<3.3", + "symfony/finder": "<3.3" + }, + "require-dev": { + "symfony/dependency-injection": "~3.3|~4.0", + "symfony/event-dispatcher": "~3.3|~4.0", + "symfony/finder": "~3.3|~4.0", + "symfony/yaml": "~3.0|~4.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Config Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-02T16:06:40+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v2.8.52", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a77e974a5fecb4398833b0709210e3d5e334ffb0", + "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^2.0.5|~3.0.0", + "symfony/dependency-injection": "~2.6|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/stopwatch": "~2.3|~3.0.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2018-11-21T14:20:20+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v3.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "495646f13d051cc5a8f77a68b68313dc854080aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/495646f13d051cc5a8f77a68b68313dc854080aa", + "reference": "495646f13d051cc5a8f77a68b68313dc854080aa", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-ctype": "~1.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-02T16:06:40+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.18.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "1c302646f6efc070cd46856e600e5e0684d6b454" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/1c302646f6efc070cd46856e600e5e0684d6b454", + "reference": "1c302646f6efc070cd46856e600e5e0684d6b454", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-14T12:35:20+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v3.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "a7a98f40dcc382a332c3729a6d04b298ffbb8f1f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/a7a98f40dcc382a332c3729a6d04b298ffbb8f1f", + "reference": "a7a98f40dcc382a332c3729a6d04b298ffbb8f1f", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Stopwatch Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-03-15T09:38:08+00:00" + }, + { + "name": "symfony/yaml", + "version": "v3.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "ec3c2ac4d881a4684c1f0317d2107f1a4152bad9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/ec3c2ac4d881a4684c1f0317d2107f1a4152bad9", + "reference": "ec3c2ac4d881a4684c1f0317d2107f1a4152bad9", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "require-dev": { + "symfony/console": "~3.4|~4.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-18T15:58:55+00:00" + }, + { + "name": "victorjonsson/markdowndocs", + "version": "1.3.8", + "source": { + "type": "git", + "url": "https://github.com/victorjonsson/PHP-Markdown-Documentation-Generator.git", + "reference": "c5eb16ff5bd15ee60223883ddacba0ab8797268d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/victorjonsson/PHP-Markdown-Documentation-Generator/zipball/c5eb16ff5bd15ee60223883ddacba0ab8797268d", + "reference": "c5eb16ff5bd15ee60223883ddacba0ab8797268d", + "shasum": "" + }, + "require": { + "php": ">=5.5.0", + "symfony/console": ">=2.6" + }, + "require-dev": { + "phpunit/phpunit": "3.7.23" + }, + "bin": [ + "bin/phpdoc-md" + ], + "type": "library", + "autoload": { + "psr-0": { + "PHPDocsMD": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Victor Jonsson", + "email": "kontakt@victorjonsson.se" + } + ], + "description": "Command line tool for generating markdown-formatted class documentation", + "homepage": "https://github.com/victorjonsson/PHP-Markdown-Documentation-Generator", + "time": "2017-04-20T09:52:47+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.9.1", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<3.9.1" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^7.5.13" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2020-07-08T17:02:28+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.4.0" + }, + "platform-dev": [], + "platform-overrides": { + "php": "5.6.32" + }, + "plugin-api-version": "1.1.0" +} diff --git a/vendor/consolidation/output-formatters/.scenarios.lock/symfony4/.gitignore b/vendor/consolidation/output-formatters/.scenarios.lock/symfony4/.gitignore new file mode 100644 index 0000000000..5657f6ea7d --- /dev/null +++ b/vendor/consolidation/output-formatters/.scenarios.lock/symfony4/.gitignore @@ -0,0 +1 @@ +vendor \ No newline at end of file diff --git a/vendor/consolidation/output-formatters/.scenarios.lock/symfony4/composer.json b/vendor/consolidation/output-formatters/.scenarios.lock/symfony4/composer.json new file mode 100644 index 0000000000..7368e472a3 --- /dev/null +++ b/vendor/consolidation/output-formatters/.scenarios.lock/symfony4/composer.json @@ -0,0 +1,66 @@ +{ + "name": "consolidation/output-formatters", + "description": "Format text by applying transformations provided by plug-in formatters.", + "license": "MIT", + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "autoload": { + "psr-4": { + "Consolidation\\OutputFormatters\\": "../../src" + } + }, + "autoload-dev": { + "psr-4": { + "Consolidation\\TestUtils\\": "../../tests/src" + } + }, + "require": { + "symfony/console": "^4.0", + "php": ">=5.4.0", + "dflydev/dot-access-data": "^1.1.0", + "symfony/finder": "^2.5|^3|^4|^5" + }, + "require-dev": { + "phpunit/phpunit": "^6", + "g1a/composer-test-scenarios": "^3", + "php-coveralls/php-coveralls": "^1", + "squizlabs/php_codesniffer": "^2.7", + "symfony/var-dumper": "^2.8|^3|^4", + "victorjonsson/markdowndocs": "^1.3" + }, + "suggest": { + "symfony/var-dumper": "For using the var_dump formatter" + }, + "config": { + "platform": { + "php": "7.1.3" + }, + "optimize-autoloader": true, + "sort-packages": true, + "vendor-dir": "../../vendor" + }, + "scripts": { + "api": "phpdoc-md generate src > docs/api.md", + "cs": "phpcs --standard=PSR2 -n src", + "cbf": "phpcbf --standard=PSR2 -n src", + "unit": "phpunit --colors=always", + "lint": [ + "find src -name '*.php' -print0 | xargs -0 -n1 php -l", + "find tests/src -name '*.php' -print0 | xargs -0 -n1 php -l" + ], + "test": [ + "@lint", + "@unit", + "@cs" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + } +} diff --git a/vendor/consolidation/output-formatters/.scenarios.lock/symfony4/composer.lock b/vendor/consolidation/output-formatters/.scenarios.lock/symfony4/composer.lock new file mode 100644 index 0000000000..4982bda580 --- /dev/null +++ b/vendor/consolidation/output-formatters/.scenarios.lock/symfony4/composer.lock @@ -0,0 +1,3130 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "a2c830502baaa91f8f4c59cc4815fb06", + "packages": [ + { + "name": "dflydev/dot-access-data", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/3fbd874921ab2c041e899d044585a2ab9795df8a", + "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "Dflydev\\DotAccessData": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "time": "2017-01-20T21:14:22+00:00" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14T16:28:37+00:00" + }, + { + "name": "symfony/console", + "version": "v4.4.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "90933b39c7b312fc3ceaa1ddeac7eb48cb953124" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/90933b39c7b312fc3ceaa1ddeac7eb48cb953124", + "reference": "90933b39c7b312fc3ceaa1ddeac7eb48cb953124", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/polyfill-php80": "^1.15", + "symfony/service-contracts": "^1.1|^2" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/event-dispatcher": "<4.3|>=5", + "symfony/lock": "<4.4", + "symfony/process": "<3.3" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/event-dispatcher": "^4.3", + "symfony/lock": "^4.4|^5.0", + "symfony/process": "^3.4|^4.0|^5.0", + "symfony/var-dumper": "^4.3|^5.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-15T07:58:55+00:00" + }, + { + "name": "symfony/finder", + "version": "v4.4.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "60d08560f9aa72997c44077c40d47aa28a963230" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/60d08560f9aa72997c44077c40d47aa28a963230", + "reference": "60d08560f9aa72997c44077c40d47aa28a963230", + "shasum": "" + }, + "require": { + "php": ">=7.1.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-10-02T07:34:48+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.18.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/a6977d63bf9a0ad4c65cd352709e230876f9904a", + "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-14T12:35:20+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.18.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "fffa1a52a023e782cdcc221d781fe1ec8f87fcca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fffa1a52a023e782cdcc221d781fe1ec8f87fcca", + "reference": "fffa1a52a023e782cdcc221d781fe1ec8f87fcca", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-14T12:35:20+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.18.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "d87d5766cbf48d72388a9f6b85f280c8ad51f981" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/d87d5766cbf48d72388a9f6b85f280c8ad51f981", + "reference": "d87d5766cbf48d72388a9f6b85f280c8ad51f981", + "shasum": "" + }, + "require": { + "php": ">=7.0.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-14T12:35:20+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v1.1.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "b776d18b303a39f56c63747bcb977ad4b27aca26" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/b776d18b303a39f56c63747bcb977ad4b27aca26", + "reference": "b776d18b303a39f56c63747bcb977ad4b27aca26", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "psr/container": "^1.0" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-06T13:19:58+00:00" + } + ], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "f350df0268e904597e3bd9c4685c53e0e333feea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/f350df0268e904597e3bd9c4685c53e0e333feea", + "reference": "f350df0268e904597e3bd9c4685c53e0e333feea", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2020-05-29T17:27:14+00:00" + }, + { + "name": "g1a/composer-test-scenarios", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/g1a/composer-test-scenarios.git", + "reference": "e7394206d845fd593d325440507fb940bef8cb62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/g1a/composer-test-scenarios/zipball/e7394206d845fd593d325440507fb940bef8cb62", + "reference": "e7394206d845fd593d325440507fb940bef8cb62", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0.0 || ^2.0.0", + "php": ">=5.4" + }, + "require-dev": { + "composer/composer": "^1.10.6 || ^2.0@rc", + "php-coveralls/php-coveralls": "^1.0", + "phpunit/phpunit": "^4.8.36|^6", + "squizlabs/php_codesniffer": "^3.5" + }, + "bin": [ + "scripts/dependency-licenses" + ], + "type": "composer-plugin", + "extra": { + "class": "ComposerTestScenarios\\Plugin", + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "ComposerTestScenarios\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Useful scripts for testing multiple sets of Composer dependencies.", + "time": "2020-09-28T20:54:35+00:00" + }, + { + "name": "guzzle/guzzle", + "version": "v3.8.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/4de0618a01b34aa1c8c33a3f13f396dcd3882eba", + "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3.3", + "symfony/event-dispatcher": ">=2.1" + }, + "replace": { + "guzzle/batch": "self.version", + "guzzle/cache": "self.version", + "guzzle/common": "self.version", + "guzzle/http": "self.version", + "guzzle/inflection": "self.version", + "guzzle/iterator": "self.version", + "guzzle/log": "self.version", + "guzzle/parser": "self.version", + "guzzle/plugin": "self.version", + "guzzle/plugin-async": "self.version", + "guzzle/plugin-backoff": "self.version", + "guzzle/plugin-cache": "self.version", + "guzzle/plugin-cookie": "self.version", + "guzzle/plugin-curlauth": "self.version", + "guzzle/plugin-error-response": "self.version", + "guzzle/plugin-history": "self.version", + "guzzle/plugin-log": "self.version", + "guzzle/plugin-md5": "self.version", + "guzzle/plugin-mock": "self.version", + "guzzle/plugin-oauth": "self.version", + "guzzle/service": "self.version", + "guzzle/stream": "self.version" + }, + "require-dev": { + "doctrine/cache": "*", + "monolog/monolog": "1.*", + "phpunit/phpunit": "3.7.*", + "psr/log": "1.0.*", + "symfony/class-loader": "*", + "zendframework/zend-cache": "<2.3", + "zendframework/zend-log": "<2.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.8-dev" + } + }, + "autoload": { + "psr-0": { + "Guzzle": "src/", + "Guzzle\\Tests": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Guzzle Community", + "homepage": "https://github.com/guzzle/guzzle/contributors" + } + ], + "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "abandoned": "guzzlehttp/guzzle", + "time": "2014-01-28T22:29:15+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.10.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/969b211f9a51aa1f6c01d1d2aef56d3bd91598e5", + "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2020-06-29T13:22:24+00:00" + }, + { + "name": "phar-io/manifest", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "phar-io/version": "^1.0.1", + "php": "^5.6 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "time": "2017-03-05T18:14:27+00:00" + }, + { + "name": "phar-io/version", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "time": "2017-03-05T17:38:23+00:00" + }, + { + "name": "php-coveralls/php-coveralls", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-coveralls/php-coveralls.git", + "reference": "37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-coveralls/php-coveralls/zipball/37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad", + "reference": "37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-simplexml": "*", + "guzzle/guzzle": "^2.8 || ^3.0", + "php": "^5.3.3 || ^7.0", + "psr/log": "^1.0", + "symfony/config": "^2.1 || ^3.0 || ^4.0", + "symfony/console": "^2.1 || ^3.0 || ^4.0", + "symfony/stopwatch": "^2.0 || ^3.0 || ^4.0", + "symfony/yaml": "^2.0 || ^3.0 || ^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.4.3 || ^6.0" + }, + "suggest": { + "symfony/http-kernel": "Allows Symfony integration" + }, + "bin": [ + "bin/coveralls" + ], + "type": "library", + "autoload": { + "psr-4": { + "Satooshi\\": "src/Satooshi/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kitamura Satoshi", + "email": "with.no.parachute@gmail.com", + "homepage": "https://www.facebook.com/satooshi.jp" + } + ], + "description": "PHP client library for Coveralls API", + "homepage": "https://github.com/php-coveralls/php-coveralls", + "keywords": [ + "ci", + "coverage", + "github", + "test" + ], + "time": "2017-12-06T23:17:56+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/6568f4687e5b41b054365f9ae03fcb1ed5f2069b", + "reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2020-04-27T09:25:28+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "4.3.4", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/da3fd972d6bafd628114f7e7e036f45944b62e9c", + "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c", + "shasum": "" + }, + "require": { + "php": "^7.0", + "phpdocumentor/reflection-common": "^1.0.0 || ^2.0.0", + "phpdocumentor/type-resolver": "~0.4 || ^1.0.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "doctrine/instantiator": "^1.0.5", + "mockery/mockery": "^1.0", + "phpdocumentor/type-resolver": "0.4.*", + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2019-12-28T18:55:12+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", + "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", + "shasum": "" + }, + "require": { + "php": "^7.1", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "^7.1", + "mockery/mockery": "~1", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "time": "2019-08-22T18:11:29+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "v1.10.3", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "451c3cd1418cf640de218914901e51b064abb093" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", + "reference": "451c3cd1418cf640de218914901e51b064abb093", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", + "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5 || ^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2020-03-05T15:02:03+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "5.3.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "c89677919c5dd6d3b3852f230a663118762218ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c89677919c5dd6d3b3852f230a663118762218ac", + "reference": "c89677919c5dd6d3b3852f230a663118762218ac", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^7.0", + "phpunit/php-file-iterator": "^1.4.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-token-stream": "^2.0.1", + "sebastian/code-unit-reverse-lookup": "^1.0.1", + "sebastian/environment": "^3.0", + "sebastian/version": "^2.0.1", + "theseer/tokenizer": "^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-xdebug": "^2.5.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2018-04-06T15:36:58+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2017-11-27T13:52:08+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "791198a2c6254db10131eecfe8c06670700904db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", + "reference": "791198a2c6254db10131eecfe8c06670700904db", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "abandoned": true, + "time": "2017-11-27T05:48:46+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "6.5.14", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "bac23fe7ff13dbdb461481f706f0e9fe746334b7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/bac23fe7ff13dbdb461481f706f0e9fe746334b7", + "reference": "bac23fe7ff13dbdb461481f706f0e9fe746334b7", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "^1.6.1", + "phar-io/manifest": "^1.0.1", + "phar-io/version": "^1.0", + "php": "^7.0", + "phpspec/prophecy": "^1.7", + "phpunit/php-code-coverage": "^5.3", + "phpunit/php-file-iterator": "^1.4.3", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-timer": "^1.0.9", + "phpunit/phpunit-mock-objects": "^5.0.9", + "sebastian/comparator": "^2.1", + "sebastian/diff": "^2.0", + "sebastian/environment": "^3.1", + "sebastian/exporter": "^3.1", + "sebastian/global-state": "^2.0", + "sebastian/object-enumerator": "^3.0.3", + "sebastian/resource-operations": "^1.0", + "sebastian/version": "^2.0.1" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2", + "phpunit/dbunit": "<3.0" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-xdebug": "*", + "phpunit/php-invoker": "^1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.5.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2019-02-01T05:22:47+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "5.0.10", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/cd1cf05c553ecfec36b170070573e540b67d3f1f", + "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.5", + "php": "^7.0", + "phpunit/php-text-template": "^1.2.1", + "sebastian/exporter": "^3.1" + }, + "conflict": { + "phpunit/phpunit": "<6.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.5.11" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "abandoned": true, + "time": "2018-08-09T05:50:03+00:00" + }, + { + "name": "psr/log", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2020-03-23T09:12:05+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2017-03-04T06:30:41+00:00" + }, + { + "name": "sebastian/comparator", + "version": "2.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9", + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/diff": "^2.0 || ^3.0", + "sebastian/exporter": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2018-02-01T13:46:46+00:00" + }, + { + "name": "sebastian/diff", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2017-08-03T08:09:46+00:00" + }, + { + "name": "sebastian/environment", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2017-07-01T08:51:00+00:00" + }, + { + "name": "sebastian/exporter", + "version": "3.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e", + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2019-09-14T09:02:43+00:00" + }, + { + "name": "sebastian/global-state", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2017-04-27T15:39:26+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2017-08-03T12:35:26+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "773f97c67f28de00d397be301821b06708fca0be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", + "reference": "773f97c67f28de00d397be301821b06708fca0be", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "time": "2017-03-29T09:07:27+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2017-03-03T06:23:57+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28T20:34:47+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "2.9.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "2acf168de78487db620ab4bc524135a13cfe6745" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/2acf168de78487db620ab4bc524135a13cfe6745", + "reference": "2acf168de78487db620ab4bc524135a13cfe6745", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "bin": [ + "scripts/phpcs", + "scripts/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "classmap": [ + "CodeSniffer.php", + "CodeSniffer/CLI.php", + "CodeSniffer/Exception.php", + "CodeSniffer/File.php", + "CodeSniffer/Fixer.php", + "CodeSniffer/Report.php", + "CodeSniffer/Reporting.php", + "CodeSniffer/Sniff.php", + "CodeSniffer/Tokens.php", + "CodeSniffer/Reports/", + "CodeSniffer/Tokenizers/", + "CodeSniffer/DocGenerators/", + "CodeSniffer/Standards/AbstractPatternSniff.php", + "CodeSniffer/Standards/AbstractScopeSniff.php", + "CodeSniffer/Standards/AbstractVariableSniff.php", + "CodeSniffer/Standards/IncorrectPatternException.php", + "CodeSniffer/Standards/Generic/Sniffs/", + "CodeSniffer/Standards/MySource/Sniffs/", + "CodeSniffer/Standards/PEAR/Sniffs/", + "CodeSniffer/Standards/PSR1/Sniffs/", + "CodeSniffer/Standards/PSR2/Sniffs/", + "CodeSniffer/Standards/Squiz/Sniffs/", + "CodeSniffer/Standards/Zend/Sniffs/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2018-11-07T22:31:41+00:00" + }, + { + "name": "symfony/config", + "version": "v4.4.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "7c5a1002178a612787c291a4f515f87b19176b61" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/7c5a1002178a612787c291a4f515f87b19176b61", + "reference": "7c5a1002178a612787c291a4f515f87b19176b61", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/filesystem": "^3.4|^4.0|^5.0", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/finder": "<3.4" + }, + "require-dev": { + "symfony/event-dispatcher": "^3.4|^4.0|^5.0", + "symfony/finder": "^3.4|^4.0|^5.0", + "symfony/messenger": "^4.1|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/yaml": "^3.4|^4.0|^5.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Config Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-10-02T07:34:48+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v4.4.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "e17bb5e0663dc725f7cdcafc932132735b4725cd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/e17bb5e0663dc725f7cdcafc932132735b4725cd", + "reference": "e17bb5e0663dc725f7cdcafc932132735b4725cd", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/event-dispatcher-contracts": "^1.1" + }, + "conflict": { + "symfony/dependency-injection": "<3.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "1.1" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/error-handler": "~3.4|~4.4", + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/http-foundation": "^3.4|^4.0|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/stopwatch": "^3.4|^4.0|^5.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-18T14:07:46+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v1.1.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "84e23fdcd2517bf37aecbd16967e83f0caee25a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/84e23fdcd2517bf37aecbd16967e83f0caee25a7", + "reference": "84e23fdcd2517bf37aecbd16967e83f0caee25a7", + "shasum": "" + }, + "require": { + "php": ">=7.1.3" + }, + "suggest": { + "psr/event-dispatcher": "", + "symfony/event-dispatcher-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-06T13:19:58+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v4.4.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "ebc51494739d3b081ea543ed7c462fa73a4f74db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/ebc51494739d3b081ea543ed7c462fa73a4f74db", + "reference": "ebc51494739d3b081ea543ed7c462fa73a4f74db", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-ctype": "~1.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-27T13:54:16+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.18.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "1c302646f6efc070cd46856e600e5e0684d6b454" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/1c302646f6efc070cd46856e600e5e0684d6b454", + "reference": "1c302646f6efc070cd46856e600e5e0684d6b454", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-14T12:35:20+00:00" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.18.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "639447d008615574653fb3bc60d1986d7172eaae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/639447d008615574653fb3bc60d1986d7172eaae", + "reference": "639447d008615574653fb3bc60d1986d7172eaae", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-14T12:35:20+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v4.4.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "6f89e19772cf61b3c65bab329fe0e318259fbd91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/6f89e19772cf61b3c65bab329fe0e318259fbd91", + "reference": "6f89e19772cf61b3c65bab329fe0e318259fbd91", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/service-contracts": "^1.0|^2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Stopwatch Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-02T16:08:58+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v4.4.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "0dc22bdf9d1197467bb04d505355180b6f20bcca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/0dc22bdf9d1197467bb04d505355180b6f20bcca", + "reference": "0dc22bdf9d1197467bb04d505355180b6f20bcca", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php72": "~1.5", + "symfony/polyfill-php80": "^1.15" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", + "symfony/console": "<3.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^3.4|^4.0|^5.0", + "symfony/process": "^4.4|^5.0", + "twig/twig": "^1.34|^2.4|^3.0" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony mechanism for exploring and dumping PHP variables", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-18T08:35:10+00:00" + }, + { + "name": "symfony/yaml", + "version": "v4.4.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "c7885964b1eceb70b0981556d0a9b01d2d97c8d1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/c7885964b1eceb70b0981556d0a9b01d2d97c8d1", + "reference": "c7885964b1eceb70b0981556d0a9b01d2d97c8d1", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "require-dev": { + "symfony/console": "^3.4|^4.0|^5.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-27T03:36:23+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "time": "2019-06-13T22:48:21+00:00" + }, + { + "name": "victorjonsson/markdowndocs", + "version": "1.3.8", + "source": { + "type": "git", + "url": "https://github.com/victorjonsson/PHP-Markdown-Documentation-Generator.git", + "reference": "c5eb16ff5bd15ee60223883ddacba0ab8797268d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/victorjonsson/PHP-Markdown-Documentation-Generator/zipball/c5eb16ff5bd15ee60223883ddacba0ab8797268d", + "reference": "c5eb16ff5bd15ee60223883ddacba0ab8797268d", + "shasum": "" + }, + "require": { + "php": ">=5.5.0", + "symfony/console": ">=2.6" + }, + "require-dev": { + "phpunit/phpunit": "3.7.23" + }, + "bin": [ + "bin/phpdoc-md" + ], + "type": "library", + "autoload": { + "psr-0": { + "PHPDocsMD": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Victor Jonsson", + "email": "kontakt@victorjonsson.se" + } + ], + "description": "Command line tool for generating markdown-formatted class documentation", + "homepage": "https://github.com/victorjonsson/PHP-Markdown-Documentation-Generator", + "time": "2017-04-20T09:52:47+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.9.1", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<3.9.1" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^7.5.13" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2020-07-08T17:02:28+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.4.0" + }, + "platform-dev": [], + "platform-overrides": { + "php": "7.1.3" + }, + "plugin-api-version": "1.1.0" +} diff --git a/vendor/consolidation/output-formatters/CHANGELOG.md b/vendor/consolidation/output-formatters/CHANGELOG.md new file mode 100644 index 0000000000..87d0ac93d6 --- /dev/null +++ b/vendor/consolidation/output-formatters/CHANGELOG.md @@ -0,0 +1,116 @@ +# Change Log + +### 3.5.1 - 10 October 2020 + +- Allow symfony/finder ^5 in output-formatters 3.x. (#87) + +### 3.5.0 - 30 May 2019 + +- Add `@default-table-fields` to specify the fields to use with the table formatter and other "human readable" output formats. + +### 3.4.1 - 13 March 2019 + +- Add enclosure and escape character options for CsvFormatter. (#79) + +### 3.4.0 - 19 October 2018 + +- Add an UnstucturedInterface marker interface, and update the 'string' format to not accept data types that implement this interface unless they also implement StringTransformationInterface. + +### 3.3.2 - 18 October 2018 + +- Add a 'null' output formatter that accepts all data types and never produces output + +### 3.3.0 & 3.3.1 - 15 October 2018 + +- Add UnstructuredListData and UnstructuredData to replace deprecated ListDataFromKeys +- Support --field and --fields in commands that return UnstructuredData / UnstructuredListData +- Support field remapping, e.g. `--fields=original as remapped` +- Support field addressing, e.g. `--fields=a.b.c` +- Automatically convert from RowsOfFields to UnstruturedListData and from PropertyList to UnstructuredData when user utilizes field remapping or field addressing features. + +### 3.2.1 - 25 May 2018 + +- Rename g1a/composer-test-scenarios + +### 3.2.0 - 20 March 2018 + +- Add RowsOfFieldsWithMetadata: allows commands to return an object with metadata that shows up in yaml/json (& etc.) formats, but is not shown in table/csv (& etc.). +- Add NumericCellRenderer: allows commands to attach a renderer that will right-justify and add commas to numbers in a column. +- Add optional var_dump output format. + +### 3.1.13 - 29 November 2017 + +- Allow XML output for RowsOfFields (#60). +- Allow Symfony 4 components and add make tests run on three versions of Symfony. + +### 3.1.12 - 12 October 2017 + +- Bugfix: Use InputOption::VALUE_REQUIRED instead of InputOption::VALUE_OPTIONAL + for injected options such as --format and --fields. +- Bugfix: Ignore empty properties in the property parser. + +### 3.1.11 - 17 August 2017 + +- Add ListDataFromKeys marker data type. + +### 3.1.10 - 6 June 2017 + +- Typo in CalculateWidths::distributeLongColumns causes failure for some column width distributions + +### 3.1.9 - 8 May 2017 + +- Improve wrapping algorithm + +### 3.1.7 - 20 Jan 2017 + +- Add Windows testing + +### 3.1.6 - 8 Jan 2017 + +- Move victorjonsson/markdowndocs to require-dev + +### 3.1.5 - 23 November 2016 + +- When converting from XML to an array, use the 'id' or 'name' element as the array key value. + +### 3.1.4 - 20 November 2016 + +- Add a 'list delimiter' formatter option, so that we can create a Drush-style table for property lists. + +### 3.1.1 ~ 3.1.3 - 18 November 2016 + +- Fine-tune wordwrapping. + +### 3.1.0 - 17 November 2016 + +- Add wordwrapping to table formatter. + +### 3.0.0 - 14 November 2016 + +- **Breaking** The RenderCellInterface is now provided a reference to the entire row data. Existing clients need only add the new parameter to their method defnition to update. +- Rename AssociativeList to PropertyList, as many people seemed to find the former name confusing. AssociativeList is still available for use to preserve backwards compatibility, but it is deprecated. + + +### 2.1.0 - 7 November 2016 + +- Add RenderCellCollections to structured lists, so that commands may add renderers to structured data without defining a new structured data subclass. +- Throw an exception if the client requests a field that does not exist. +- Remove unwanted extra layer of nesting when formatting an PropertyList with an array formatter (json, yaml, etc.). + + +### 2.0.0 - 30 September 2016 + +- **Breaking** The default `string` format now converts non-string results into a tab-separated-value table if possible. Commands may select a single field to emit in this instance with an annotation: `@default-string-field email`. By this means, a given command may by default emit a single value, but also provide more rich output that may be shown by selecting --format=table, --format=yaml or the like. This change might cause some commands to produce output in situations that previously were not documented as producing output. +- **Breaking** FormatterManager::addFormatter() now takes the format identifier and a FormatterInterface, rather than an identifier and a Formatter classname (string). +- --field is a synonym for --fields with a single field. +- Wildcards and regular expressions can now be used in --fields expressions. + + +### 1.1.0 - 14 September 2016 + +Add tab-separated-value (tsv) formatter. + + +### 1.0.0 - 19 May 2016 + +First stable release. diff --git a/vendor/consolidation/output-formatters/CONTRIBUTING.md b/vendor/consolidation/output-formatters/CONTRIBUTING.md new file mode 100644 index 0000000000..4d843cf745 --- /dev/null +++ b/vendor/consolidation/output-formatters/CONTRIBUTING.md @@ -0,0 +1,31 @@ +# Contributing to Consolidation + +Thank you for your interest in contributing to the Consolidation effort! Consolidation aims to provide reusable, loosely-coupled components useful for building command-line tools. Consolidation is built on top of Symfony Console, but aims to separate the tool from the implementation details of Symfony. + +Here are some of the guidelines you should follow to make the most of your efforts: + +## Code Style Guidelines + +Consolidation adheres to the [PSR-2 Coding Style Guide](http://www.php-fig.org/psr/psr-2/) for PHP code. + +## Pull Request Guidelines + +Every pull request is run through: + + - phpcs -n --standard=PSR2 src + - phpunit + - [Scrutinizer](https://scrutinizer-ci.com/g/consolidation/output-formatters/) + +It is easy to run the unit tests and code sniffer locally; just run: + + - composer cs + +To run the code beautifier, which will fix many of the problems reported by phpcs: + + - composer cbf + +These two commands (`composer cs` and `composer cbf`) are defined in the `scripts` section of [composer.json](composer.json). + +After submitting a pull request, please examine the Scrutinizer report. It is not required to fix all Scrutinizer issues; you may ignore recommendations that you disagree with. The spacing patches produced by Scrutinizer do not conform to PSR2 standards, and therefore should never be applied. DocBlock patches may be applied at your discression. Things that Scrutinizer identifies as a bug nearly always need to be addressed. + +Pull requests must pass phpcs and phpunit in order to be merged; ideally, new functionality will also include new unit tests. diff --git a/vendor/consolidation/output-formatters/LICENSE b/vendor/consolidation/output-formatters/LICENSE new file mode 100644 index 0000000000..3ce4262e0e --- /dev/null +++ b/vendor/consolidation/output-formatters/LICENSE @@ -0,0 +1,18 @@ +Copyright (c) 2016-2020 Consolidation Org Developers + + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +DEPENDENCY LICENSES: + +Name Version License +dflydev/dot-access-data v1.1.0 MIT +psr/log 1.1.3 MIT +symfony/console v3.4.45 MIT +symfony/debug v3.4.45 MIT +symfony/finder v3.4.45 MIT +symfony/polyfill-mbstring v1.18.1 MIT \ No newline at end of file diff --git a/vendor/consolidation/output-formatters/README.md b/vendor/consolidation/output-formatters/README.md new file mode 100644 index 0000000000..59bcc7d40b --- /dev/null +++ b/vendor/consolidation/output-formatters/README.md @@ -0,0 +1,332 @@ +# Consolidation\OutputFormatters + +Apply transformations to structured data to write output in different formats. + +[![Travis CI](https://travis-ci.org/consolidation/output-formatters.svg?branch=master)](https://travis-ci.org/consolidation/output-formatters) +[![Windows CI](https://ci.appveyor.com/api/projects/status/umyfuujca6d2g2k6?svg=true)](https://ci.appveyor.com/project/greg-1-anderson/output-formatters) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/consolidation/output-formatters/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/consolidation/output-formatters/?branch=master) +[![Coverage Status](https://coveralls.io/repos/github/consolidation/output-formatters/badge.svg?branch=master)](https://coveralls.io/github/consolidation/output-formatters?branch=master) +[![License](https://poser.pugx.org/consolidation/output-formatters/license)](https://packagist.org/packages/consolidation/output-formatters) + +## Component Status + +Currently in use in [Robo](https://github.com/consolidation/Robo) (1.x+), [Drush](https://github.com/drush-ops/drush) (9.x+) and [Terminus](https://github.com/pantheon-systems/terminus) (1.x+). + +## Motivation + +Formatters are used to allow simple commandline tool commands to be implemented in a manner that is completely independent from the Symfony Console output interfaces. A command receives its input via its method parameters, and returns its result as structured data (e.g. a php standard object or array). The structured data is then formatted by a formatter, and the result is printed. + +This process is managed by the [Consolidation/AnnotationCommand](https://github.com/consolidation/annotation-command) project. + +## Library Usage + +This is a library intended to be used in some other project. Require from your composer.json file: +``` + "require": { + "consolidation/output-formatters": "~3" + }, +``` +If you require the feature that allows a data table to be automatically reduced to a single element when the `string` format is selected, then you should require version ^2 instead. In most other respects, the 1.x and 2.x versions are compatible. See the [CHANGELOG](CHANGELOG.md) for details. + +## Example Formatter + +Simple formatters are very easy to write. +```php +class YamlFormatter implements FormatterInterface +{ + public function write(OutputInterface $output, $data, FormatterOptions $options) + { + $dumper = new Dumper(); + $output->writeln($dumper->dump($data)); + } +} +``` +The formatter is passed the set of `$options` that the user provided on the command line. These may optionally be examined to alter the behavior of the formatter, if needed. + +Formatters may also implement different interfaces to alter the behavior of the rendering engine. + +- `ValidationInterface`: A formatter should implement this interface to test to see if the provided data type can be processed. Any formatter that does **not** implement this interface is presumed to operate exclusively on php arrays. The formatter manager will always convert any provided data into an array before passing it to a formatter that does not implement ValidationInterface. These formatters will not be made available when the returned data type cannot be converted into an array. +- `OverrideRestructureInterface`: A formatter that implements this interface will be given the option to act on the provided structured data object before it restructures itself. See the section below on structured data for details on data restructuring. +- `UnstructuredInterface`: A formatter that implements this interface will not be able to be formatted by the `string` formatter by default. Data structures that do not implement this interface will be automatically converted to a string when applicable; if this conversion fails, then no output is produced. +- `StringTransformationInterface`: Implementing this interface allows a data type to provide a specific implementation for the conversion of the data to a string. Data types that implement both `UnstructuredInterface` and `StringTransformationInterface` may be used with the `string` format. + +## Configuring Formats for a Command + +Commands declare what type of data they return using a `@return` annotation, as usual: +```php + /** + * Demonstrate formatters. Default format is 'table'. + * + * @field-labels + * first: I + * second: II + * third: III + * @default-string-field second + * @usage try:formatters --format=yaml + * @usage try:formatters --format=csv + * @usage try:formatters --fields=first,third + * @usage try:formatters --fields=III,II + * + * @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields + */ + public function tryFormatters($somthing = 'default', $options = ['format' => 'table', 'fields' => '']) + { + $outputData = [ + 'en' => [ 'first' => 'One', 'second' => 'Two', 'third' => 'Three' ], + 'de' => [ 'first' => 'Eins', 'second' => 'Zwei', 'third' => 'Drei' ], + 'jp' => [ 'first' => 'Ichi', 'second' => 'Ni', 'third' => 'San' ], + 'es' => [ 'first' => 'Uno', 'second' => 'Dos', 'third' => 'Tres' ], + ]; + return new RowsOfFields($outputData); + } +``` +The output-formatters library determines which output formats are applicable to the command by querying all available formats, and selecting any that are able to process the data type that is returned. Thus, if a new format is added to a program, it will automatically be available via any command that it works with. It is not necessary to hand-select the available formats on every command individually. + +### Structured Data + +Most formatters will operate on any array or ArrayObject data. Some formatters require that specific data types be used. The following data types, all of which are subclasses of ArrayObject, are available for use: + +- `RowsOfFields`: Each row contains an associative array of field:value pairs. It is also assumed that the fields of each row are the same for every row. This format is ideal for displaying in a table, with labels in the top row. +- `RowsOfFieldsWithMetadata`: Equivalent to `RowsOfFields`, but allows for metadata to be attached to the result. The metadata is not displayed in table format, but is evident if the data is converted to another format (e.g. `yaml` or `json`). The table data may either be nested inside of a specially-designated element, with other elements being used as metadata, or, alternately, the metadata may be nested inside of an element, with all other elements being used as data. +- `PropertyList`: Each row contains a field:value pair. Each field is unique. This format is ideal for displaying in a table, with labels in the first column and values in the second common. +- `UnstructuredListData`: The result is assumed to be a list of items, with the key of each row being used as the row id. The data elements may contain any sort of array data. The elements on each row do not need to be uniform, and the data may be nested to arbitrary depths. +- `UnstruturedData`: The result is an unstructured array nested to arbitrary levels. +- `DOMDocument`: The standard PHP DOM document class may be used by functions that need to be able to presicely specify the exact attributes and children when the XML output format is used. +- `ListDataFromKeys`: This data structure is deprecated. Use `UnstructuredListData` instead. + +Commands that need to produce XML output should return a DOMDocument as its return type. The formatter manager will do its best to convert from an array to a DOMDocument, or from a DOMDocument to an array, as needed. It is important to note that a DOMDocument does not have a 1-to-1 mapping with a PHP array. DOM elements contain both attributes and elements; a simple string property 'foo' may be represented either as or value. Also, there may be multiple XML elements with the same name, whereas php associative arrays must always have unique keys. When converting from an array to a DOM document, the XML formatter will default to representing the string properties of an array as attributes of the element. Sets of elements with the same name may be used only if they are wrapped in a containing parent element--e.g. onetwo. The XMLSchema class may be used to provide control over whether a property is rendered as an attribute or an element; however, in instances where the schema of the XML output is important, it is best for a function to return its result as a DOMDocument rather than an array. + +A function may also define its own structured data type to return, usually by extending one of the types mentioned above. If a custom structured data class implements an appropriate interface, then it can provide its own conversion function to one of the other data types: + +- `DomDataInterface`: The data object may produce a DOMDocument via its `getDomData()` method, which will be called in any instance where a DOM document is needed--typically with the xml formatter. +- `ListDataInterface`: Any structured data object that implements this interface may use the `getListData()` method to produce the data set that will be used with the list formatter. +- `TableDataInterface`: Any structured data object that implements this interface may use the `getTableData()` method to produce the data set that will be used with the table formatter. +- `RenderCellInterface`: Structured data can also provide fine-grain control over how each cell in a table is rendered by implementing the RenderCellInterface. See the section below for information on how this is done. +- `RestructureInterface`: The restructure interface can be implemented by a structured data object to restructure the data in response to options provided by the user. For example, the RowsOfFields and PropertyList data types use this interface to select and reorder the fields that were selected to appear in the output. Custom data types usually will not need to implement this interface, as they can inherit this behavior by extending RowsOfFields or PropertyList. + +Additionally, structured data may be simplified to arrays via an array simplification object. To provide an array simplifier, implement `SimplifyToArrayInterface`, and register the simplifier via `FormatterManager::addSimplifier()`. + +### Fields + +Some commands produce output that contain *fields*. A field may be either the key in a key/value pair, or it may be the label used in tabular output and so on. + +#### Declaring Default Fields + +If a command declares a very large number of fields, it is possible to display only a subset of the available options by way of the `@default-fields` annotation. The following example comes from Drush: +```php + /** + * @command cache:get + * @field-labels + * cid: Cache ID + * data: Data + * created: Created + * expire: Expire + * tags: Tags + * checksum: Checksum + * valid: Valid + * @default-fields cid,data,created,expire,tags + * @return \Consolidation\OutputFormatters\StructuredData\PropertyList + */ + public function get($cid, $bin = 'default', $options = ['format' => 'json']) + { + $result = ... + return new PropertyList($result); + } +``` +All of the available fields will be listed in the `help` output for the command, and may be selected by listing the desired fields explicitly via the `--fields` option. + +To include all avalable fields, use `--fields=*`. + +Note that using the `@default-fields` annotation will reduce the number of fields included in the output for all formats, including unstructured formats such as json and yaml. To specify a reduced set of fields to display only when using a human-readable output format (e.g. table), use the `@default-table-fields` annotation instead. + +#### Reordering Fields + +Commands that return table structured data with fields can be filtered and/or re-ordered by using the `--fields` option. These structured data types can also be formatted into a more generic type such as yaml or json, even after being filtered. This capabilities are not available if the data is returned in a bare php array. One of `RowsOfFields`, `PropertyList` or `UnstructuredListData` (or similar) must be used. + +When the `--fields` option is provided, the user may stipulate the exact fields to list on each row, and what order they should appear in. For example, if a command usually produces output using the `RowsOfFields` data type, as shown below: +``` +$ ./app try:formatters + ------ ------ ------- + I II III + ------ ------ ------- + One Two Three + Eins Zwei Drei + Ichi Ni San + Uno Dos Tres + ------ ------ ------- +``` +Then the third and first fields may be selected as follows: +``` + $ ./app try:formatters --fields=III,I + ------- ------ + III I + ------- ------ + Three One + Drei Eins + San Ichi + Tres Uno + ------- ------ +``` +To select a single column and strip away all formatting, use the `--field` option: +``` +$ ./app try:formatters --field=II +Two +Zwei +Ni +Dos +``` +Commands that produce deeply-nested data structures using the `UnstructuredData` and `UnstructuredListData` data type may also be manipulated using the `--fields` and `--field` options. It is possible to address items deep in the heirarchy using dot notation. + +The `UnstructuredData` type represents a single nested array with no requirements for uniform structure. The `UnstructuredListData` type is similar; it represents a list of `UnstructuredData` types. It is not required for the different elements in the list to have all of the same fields or structure, although it is expected that there will be a certain degree of similarity. + +In the example below, a command returns a list of stores of different kinds. Each store has common top-level elements such as `name`, `products` and `sale-items`. Each store might have different sorts of products with different attributes: +``` +$ ./app try:nested +bills-hardware: + name: 'Bill''s Hardware' + products: + tools: + electric-drill: + price: '79.98' + screwdriver: + price: '8.99' + sale-items: + screwdriver: '4.99' +alberts-supermarket: + name: 'Albert''s Supermarket' + products: + fruits: + strawberries: + price: '2' + units: lbs + watermellons: + price: '5' + units: each + sale-items: + watermellons: '4.50' +``` +Just as is the case with tabular output, it is possible to select only a certain set of fields to display with each output item: +``` +$ ./app try:nested --fields=sale-items +bills-hardware: + sale-items: + screwdriver: '4.99' +alberts-supermarket: + sale-items: + watermellons: '4.50' +``` +With unstructured data, it is also possible to remap the name of the field to something else: +``` +$ ./robo try:nested --fields='sale-items as items' +bills-hardware: + items: + screwdriver: '4.99' +alberts-supermarket: + items: + watermellons: '4.50' +``` +The field name `.` is special, though: it indicates that the named element should be omitted, and its value or children should be applied directly to the result row: +``` +$ ./app try:nested --fields='sale-items as .' +bills-hardware: + screwdriver: '4.99' +alberts-supermarket: + watermellons: '4.50' +``` +Finally, it is also possible to reach down into nested data structures and pull out information about an element or elements identified using "dot" notation: +``` +$ ./app try:nested --fields=products.fruits.strawberries +bills-hardware: { } +alberts-supermarket: + strawberries: + price: '2' + units: lbs +``` +Commands that use `RowsOfFields` or `PropertyList` return type will be automatically converted to `UnstructuredListData` or `UnstructuredData`, respectively, whenever any field remapping is done. This will only work for data types such as `yaml` or `json` that can render unstructured data types. It is not possible to render unstructured data in a table, even if the resulting data happens to be uniform. + +### Filtering Specific Rows + +A command may allow the user to filter specific rows of data using simple boolean logic and/or regular expressions. For details, see the external library [consolidation/filter-via-dot-access-data](https://github.com/consolidation/filter-via-dot-access-data) that provides this capability. + +## Rendering Table Cells + +By default, both the RowsOfFields and PropertyList data types presume that the contents of each cell is a simple string. To render more complicated cell contents, create a custom structured data class by extending either RowsOfFields or PropertyList, as desired, and implement RenderCellInterface. The `renderCell()` method of your class will then be called for each cell, and you may act on it as appropriate. +```php +public function renderCell($key, $cellData, FormatterOptions $options, $rowData) +{ + // 'my-field' is always an array; convert it to a comma-separated list. + if ($key == 'my-field') { + return implode(',', $cellData); + } + // MyStructuredCellType has its own render function + if ($cellData instanceof MyStructuredCellType) { + return $cellData->myRenderfunction(); + } + // If we do not recognize the cell data, return it unchnaged. + return $cellData; +} +``` +Note that if your data structure is printed with a formatter other than one such as the table formatter, it will still be reordered per the selected fields, but cell rendering will **not** be done. + +The RowsOfFields and PropertyList data types also allow objects that implement RenderCellInterface, as well as anonymous functions to be added directly to the data structure object itself. If this is done, then the renderer will be called for each cell in the table. An example of an attached renderer implemented as an anonymous function is shown below. +```php + return (new RowsOfFields($data))->addRendererFunction( + function ($key, $cellData, FormatterOptions $options, $rowData) { + if ($key == 'my-field') { + return implode(',', $cellData); + } + return $cellData; + } + ); +``` +This project also provides a built-in cell renderer, NumericCellRenderer, that adds commas at the thousands place and right-justifies columns identified as numeric. An example of a numeric renderer attached to two columns of a data set is shown below. +```php +use Consolidation\OutputFormatters\StructuredData\NumericCellRenderer; +... + return (new RowsOfFields($data))->addRenderer( + new NumericCellRenderer($data, ['population','cats-per-capita']) + ); +``` + +## API Usage + +It is recommended to use [Consolidation/AnnotationCommand](https://github.com/consolidation/annotation-command) to manage commands and formatters. See the [AnnotationCommand API Usage](https://github.com/consolidation/annotation-command#api-usage) for details. + +The FormatterManager may also be used directly, if desired: +```php +/** + * @param OutputInterface $output Output stream to write to + * @param string $format Data format to output in + * @param mixed $structuredOutput Data to output + * @param FormatterOptions $options Configuration informatin and User options + */ +function doFormat( + OutputInterface $output, + string $format, + array $data, + FormatterOptions $options) +{ + $formatterManager = new FormatterManager(); + $formatterManager->write(output, $format, $data, $options); +} +``` +The FormatterOptions class is used to hold the configuration for the command output--things such as the default field list for tabular output, and so on--and also the current user-selected options to use during rendering, which may be provided using a Symfony InputInterface object: +``` +public function execute(InputInterface $input, OutputInterface $output) +{ + $options = new FormatterOptions(); + $options + ->setInput($input) + ->setFieldLabels(['id' => 'ID', 'one' => 'First', 'two' => 'Second']) + ->setDefaultStringField('id'); + + $data = new RowsOfFields($this->getSomeData($input)); + return $this->doFormat($output, $options->getFormat(), $data, $options); +} +``` +## Comparison to Existing Solutions + +Formatters have been in use in Drush since version 5. Drush allows formatters to be defined using simple classes, some of which may be configured using metadata. Furthermore, nested formatters are also allowed; for example, a list formatter may be given another formatter to use to format each of its rows. Nested formatters also require nested metadata, causing the code that constructed formatters to become very complicated and unweildy. + +Consolidation/OutputFormatters maintains the simplicity of use provided by Drush formatters, but abandons nested metadata configuration in favor of using code in the formatter to configure itself, in order to keep the code simpler. + diff --git a/vendor/consolidation/output-formatters/composer.json b/vendor/consolidation/output-formatters/composer.json new file mode 100644 index 0000000000..e3d6e8cd4e --- /dev/null +++ b/vendor/consolidation/output-formatters/composer.json @@ -0,0 +1,121 @@ +{ + "name": "consolidation/output-formatters", + "description": "Format text by applying transformations provided by plug-in formatters.", + "license": "MIT", + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "autoload":{ + "psr-4":{ + "Consolidation\\OutputFormatters\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Consolidation\\TestUtils\\": "tests/src" + } + }, + "require": { + "php": ">=5.4.0", + "dflydev/dot-access-data": "^1.1.0", + "symfony/console": "^2.8|^3|^4", + "symfony/finder": "^2.5|^3|^4|^5" + }, + "require-dev": { + "g1a/composer-test-scenarios": "^3", + "php-coveralls/php-coveralls": "^1", + "phpunit/phpunit": "^5.7.27", + "squizlabs/php_codesniffer": "^2.7", + "symfony/var-dumper": "^2.8|^3|^4", + "victorjonsson/markdowndocs": "^1.3" + }, + "suggest": { + "symfony/var-dumper": "For using the var_dump formatter" + }, + "config": { + "optimize-autoloader": true, + "sort-packages": true, + "platform": { + "php": "5.6.32" + } + }, + "scripts": { + "api": "phpdoc-md generate src > docs/api.md", + "cs": "phpcs --standard=PSR2 -n src", + "cbf": "phpcbf --standard=PSR2 -n src", + "unit": "phpunit --colors=always", + "lint": [ + "find src -name '*.php' -print0 | xargs -0 -n1 php -l", + "find tests/src -name '*.php' -print0 | xargs -0 -n1 php -l" + ], + "test": [ + "@lint", + "@unit", + "@cs" + ] + }, + "extra": { + "scenarios": { + "finder5": { + "require": { + "symfony/finder": "^5" + }, + "config": { + "platform": { + "php": "7.2.5" + } + } + }, + "symfony4": { + "require": { + "symfony/console": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^6" + }, + "config": { + "platform": { + "php": "7.1.3" + } + } + }, + "symfony3": { + "require": { + "symfony/console": "^3.4", + "symfony/finder": "^3.4", + "symfony/var-dumper": "^3.4" + }, + "config": { + "platform": { + "php": "5.6.32" + } + } + }, + "symfony2": { + "require": { + "symfony/console": "^2.8" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36" + }, + "remove": [ + "php-coveralls/php-coveralls" + ], + "config": { + "platform": { + "php": "5.4.8" + } + }, + "scenario-options": { + "create-lockfile": "false" + } + } + }, + "branch-alias": { + "dev-master": "3.x-dev" + } + } +} diff --git a/vendor/consolidation/output-formatters/composer.lock b/vendor/consolidation/output-formatters/composer.lock new file mode 100644 index 0000000000..d432b0d74e --- /dev/null +++ b/vendor/consolidation/output-formatters/composer.lock @@ -0,0 +1,2517 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "067804939d9b5ba5a90e64c9fe3cc29e", + "packages": [ + { + "name": "dflydev/dot-access-data", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/3fbd874921ab2c041e899d044585a2ab9795df8a", + "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "Dflydev\\DotAccessData": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "time": "2017-01-20T21:14:22+00:00" + }, + { + "name": "psr/log", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2020-03-23T09:12:05+00:00" + }, + { + "name": "symfony/console", + "version": "v3.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "b28996bc0a3b08914b2a8609163ec35b36b30685" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/b28996bc0a3b08914b2a8609163ec35b36b30685", + "reference": "b28996bc0a3b08914b2a8609163ec35b36b30685", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/debug": "~2.8|~3.0|~4.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/process": "<3.3" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.3|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "~2.8|~3.0|~4.0", + "symfony/lock": "~3.4|~4.0", + "symfony/process": "~3.3|~4.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-09T05:09:37+00:00" + }, + { + "name": "symfony/debug", + "version": "v3.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "9109e4414e684d0b75276ae203883467476d25d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/9109e4414e684d0b75276ae203883467476d25d0", + "reference": "9109e4414e684d0b75276ae203883467476d25d0", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/http-kernel": "~2.8|~3.0|~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Debug Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-08T22:19:14+00:00" + }, + { + "name": "symfony/finder", + "version": "v3.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "52140652ed31cee3dabd0c481b5577201fa769b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/52140652ed31cee3dabd0c481b5577201fa769b4", + "reference": "52140652ed31cee3dabd0c481b5577201fa769b4", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-02T16:06:40+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.18.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/a6977d63bf9a0ad4c65cd352709e230876f9904a", + "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-14T12:35:20+00:00" + } + ], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2015-06-14T21:17:01+00:00" + }, + { + "name": "g1a/composer-test-scenarios", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/g1a/composer-test-scenarios.git", + "reference": "e7394206d845fd593d325440507fb940bef8cb62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/g1a/composer-test-scenarios/zipball/e7394206d845fd593d325440507fb940bef8cb62", + "reference": "e7394206d845fd593d325440507fb940bef8cb62", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0.0 || ^2.0.0", + "php": ">=5.4" + }, + "require-dev": { + "composer/composer": "^1.10.6 || ^2.0@rc", + "php-coveralls/php-coveralls": "^1.0", + "phpunit/phpunit": "^4.8.36|^6", + "squizlabs/php_codesniffer": "^3.5" + }, + "bin": [ + "scripts/dependency-licenses" + ], + "type": "composer-plugin", + "extra": { + "class": "ComposerTestScenarios\\Plugin", + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "ComposerTestScenarios\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Useful scripts for testing multiple sets of Composer dependencies.", + "time": "2020-09-28T20:54:35+00:00" + }, + { + "name": "guzzle/guzzle", + "version": "v3.9.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle3.git", + "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle3/zipball/0645b70d953bc1c067bbc8d5bc53194706b628d9", + "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3.3", + "symfony/event-dispatcher": "~2.1" + }, + "replace": { + "guzzle/batch": "self.version", + "guzzle/cache": "self.version", + "guzzle/common": "self.version", + "guzzle/http": "self.version", + "guzzle/inflection": "self.version", + "guzzle/iterator": "self.version", + "guzzle/log": "self.version", + "guzzle/parser": "self.version", + "guzzle/plugin": "self.version", + "guzzle/plugin-async": "self.version", + "guzzle/plugin-backoff": "self.version", + "guzzle/plugin-cache": "self.version", + "guzzle/plugin-cookie": "self.version", + "guzzle/plugin-curlauth": "self.version", + "guzzle/plugin-error-response": "self.version", + "guzzle/plugin-history": "self.version", + "guzzle/plugin-log": "self.version", + "guzzle/plugin-md5": "self.version", + "guzzle/plugin-mock": "self.version", + "guzzle/plugin-oauth": "self.version", + "guzzle/service": "self.version", + "guzzle/stream": "self.version" + }, + "require-dev": { + "doctrine/cache": "~1.3", + "monolog/monolog": "~1.0", + "phpunit/phpunit": "3.7.*", + "psr/log": "~1.0", + "symfony/class-loader": "~2.1", + "zendframework/zend-cache": "2.*,<2.3", + "zendframework/zend-log": "2.*,<2.3" + }, + "suggest": { + "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.9-dev" + } + }, + "autoload": { + "psr-0": { + "Guzzle": "src/", + "Guzzle\\Tests": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Guzzle Community", + "homepage": "https://github.com/guzzle/guzzle/contributors" + } + ], + "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "abandoned": "guzzlehttp/guzzle", + "time": "2015-03-18T18:23:50+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2017-10-19T19:58:43+00:00" + }, + { + "name": "php-coveralls/php-coveralls", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-coveralls/php-coveralls.git", + "reference": "37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-coveralls/php-coveralls/zipball/37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad", + "reference": "37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-simplexml": "*", + "guzzle/guzzle": "^2.8 || ^3.0", + "php": "^5.3.3 || ^7.0", + "psr/log": "^1.0", + "symfony/config": "^2.1 || ^3.0 || ^4.0", + "symfony/console": "^2.1 || ^3.0 || ^4.0", + "symfony/stopwatch": "^2.0 || ^3.0 || ^4.0", + "symfony/yaml": "^2.0 || ^3.0 || ^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.4.3 || ^6.0" + }, + "suggest": { + "symfony/http-kernel": "Allows Symfony integration" + }, + "bin": [ + "bin/coveralls" + ], + "type": "library", + "autoload": { + "psr-4": { + "Satooshi\\": "src/Satooshi/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kitamura Satoshi", + "email": "with.no.parachute@gmail.com", + "homepage": "https://www.facebook.com/satooshi.jp" + } + ], + "description": "PHP client library for Coveralls API", + "homepage": "https://github.com/php-coveralls/php-coveralls", + "keywords": [ + "ci", + "coverage", + "github", + "test" + ], + "time": "2017-12-06T23:17:56+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2017-09-11T18:02:19+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "bf329f6c1aadea3299f08ee804682b7c45b326a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/bf329f6c1aadea3299f08ee804682b7c45b326a2", + "reference": "bf329f6c1aadea3299f08ee804682b7c45b326a2", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0", + "phpdocumentor/reflection-common": "^1.0.0", + "phpdocumentor/type-resolver": "^0.4.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2017-11-10T14:09:06+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "time": "2017-07-14T14:27:02+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "v1.10.3", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "451c3cd1418cf640de218914901e51b064abb093" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", + "reference": "451c3cd1418cf640de218914901e51b064abb093", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", + "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5 || ^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2020-03-05T15:02:03+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "4.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^5.6 || ^7.0", + "phpunit/php-file-iterator": "^1.3", + "phpunit/php-text-template": "^1.2", + "phpunit/php-token-stream": "^1.4.2 || ^2.0", + "sebastian/code-unit-reverse-lookup": "^1.0", + "sebastian/environment": "^1.3.2 || ^2.0", + "sebastian/version": "^1.0 || ^2.0" + }, + "require-dev": { + "ext-xdebug": "^2.1.4", + "phpunit/phpunit": "^5.7" + }, + "suggest": { + "ext-xdebug": "^2.5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2017-04-02T07:44:40+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2017-11-27T13:52:08+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.4.12", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1ce90ba27c42e4e44e6d8458241466380b51fa16", + "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "abandoned": true, + "time": "2017-12-04T08:55:13+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "5.7.27", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c", + "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "~1.3", + "php": "^5.6 || ^7.0", + "phpspec/prophecy": "^1.6.2", + "phpunit/php-code-coverage": "^4.0.4", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "^3.2", + "sebastian/comparator": "^1.2.4", + "sebastian/diff": "^1.4.3", + "sebastian/environment": "^1.3.4 || ^2.0", + "sebastian/exporter": "~2.0", + "sebastian/global-state": "^1.1", + "sebastian/object-enumerator": "~2.0", + "sebastian/resource-operations": "~1.0", + "sebastian/version": "^1.0.6|^2.0.1", + "symfony/yaml": "~2.1|~3.0|~4.0" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-xdebug": "*", + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.7.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2018-02-01T05:50:59+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "a23b761686d50a560cc56233b9ecf49597cc9118" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/a23b761686d50a560cc56233b9ecf49597cc9118", + "reference": "a23b761686d50a560cc56233b9ecf49597cc9118", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.6 || ^7.0", + "phpunit/php-text-template": "^1.2", + "sebastian/exporter": "^1.2 || ^2.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "abandoned": true, + "time": "2017-06-30T09:13:00+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2017-03-04T06:30:41+00:00" + }, + { + "name": "sebastian/comparator", + "version": "1.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2 || ~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2017-01-29T09:50:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2017-05-22T07:24:03+00:00" + }, + { + "name": "sebastian/environment", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2016-11-26T07:53:53+00:00" + }, + { + "name": "sebastian/exporter", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2016-11-19T08:54:04+00:00" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2015-10-12T03:26:01+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1311872ac850040a79c3c058bea3e22d0f09cbb7", + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2017-02-18T15:18:39+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2016-11-19T07:33:16+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28T20:34:47+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "2.9.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "2acf168de78487db620ab4bc524135a13cfe6745" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/2acf168de78487db620ab4bc524135a13cfe6745", + "reference": "2acf168de78487db620ab4bc524135a13cfe6745", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "bin": [ + "scripts/phpcs", + "scripts/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "classmap": [ + "CodeSniffer.php", + "CodeSniffer/CLI.php", + "CodeSniffer/Exception.php", + "CodeSniffer/File.php", + "CodeSniffer/Fixer.php", + "CodeSniffer/Report.php", + "CodeSniffer/Reporting.php", + "CodeSniffer/Sniff.php", + "CodeSniffer/Tokens.php", + "CodeSniffer/Reports/", + "CodeSniffer/Tokenizers/", + "CodeSniffer/DocGenerators/", + "CodeSniffer/Standards/AbstractPatternSniff.php", + "CodeSniffer/Standards/AbstractScopeSniff.php", + "CodeSniffer/Standards/AbstractVariableSniff.php", + "CodeSniffer/Standards/IncorrectPatternException.php", + "CodeSniffer/Standards/Generic/Sniffs/", + "CodeSniffer/Standards/MySource/Sniffs/", + "CodeSniffer/Standards/PEAR/Sniffs/", + "CodeSniffer/Standards/PSR1/Sniffs/", + "CodeSniffer/Standards/PSR2/Sniffs/", + "CodeSniffer/Standards/Squiz/Sniffs/", + "CodeSniffer/Standards/Zend/Sniffs/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2018-11-07T22:31:41+00:00" + }, + { + "name": "symfony/config", + "version": "v3.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "d061a451ff6bc170c5454f4ac9b41ad2179e3960" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/d061a451ff6bc170c5454f4ac9b41ad2179e3960", + "reference": "d061a451ff6bc170c5454f4ac9b41ad2179e3960", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/filesystem": "~2.8|~3.0|~4.0", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/dependency-injection": "<3.3", + "symfony/finder": "<3.3" + }, + "require-dev": { + "symfony/dependency-injection": "~3.3|~4.0", + "symfony/event-dispatcher": "~3.3|~4.0", + "symfony/finder": "~3.3|~4.0", + "symfony/yaml": "~3.0|~4.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Config Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-02T16:06:40+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v2.8.52", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a77e974a5fecb4398833b0709210e3d5e334ffb0", + "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^2.0.5|~3.0.0", + "symfony/dependency-injection": "~2.6|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/stopwatch": "~2.3|~3.0.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2018-11-21T14:20:20+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v3.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "495646f13d051cc5a8f77a68b68313dc854080aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/495646f13d051cc5a8f77a68b68313dc854080aa", + "reference": "495646f13d051cc5a8f77a68b68313dc854080aa", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-ctype": "~1.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-02T16:06:40+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.18.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "1c302646f6efc070cd46856e600e5e0684d6b454" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/1c302646f6efc070cd46856e600e5e0684d6b454", + "reference": "1c302646f6efc070cd46856e600e5e0684d6b454", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-14T12:35:20+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v3.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "a7a98f40dcc382a332c3729a6d04b298ffbb8f1f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/a7a98f40dcc382a332c3729a6d04b298ffbb8f1f", + "reference": "a7a98f40dcc382a332c3729a6d04b298ffbb8f1f", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Stopwatch Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-03-15T09:38:08+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v3.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "df8fe9c1c5dc3eb968db32ffa6b699d89fee2606" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/df8fe9c1c5dc3eb968db32ffa6b699d89fee2606", + "reference": "df8fe9c1c5dc3eb968db32ffa6b699d89fee2606", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0" + }, + "require-dev": { + "ext-iconv": "*", + "twig/twig": "~1.34|~2.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "ext-symfony_debug": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony mechanism for exploring and dumping PHP variables", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-18T08:10:16+00:00" + }, + { + "name": "symfony/yaml", + "version": "v3.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "ec3c2ac4d881a4684c1f0317d2107f1a4152bad9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/ec3c2ac4d881a4684c1f0317d2107f1a4152bad9", + "reference": "ec3c2ac4d881a4684c1f0317d2107f1a4152bad9", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "require-dev": { + "symfony/console": "~3.4|~4.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-18T15:58:55+00:00" + }, + { + "name": "victorjonsson/markdowndocs", + "version": "1.3.8", + "source": { + "type": "git", + "url": "https://github.com/victorjonsson/PHP-Markdown-Documentation-Generator.git", + "reference": "c5eb16ff5bd15ee60223883ddacba0ab8797268d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/victorjonsson/PHP-Markdown-Documentation-Generator/zipball/c5eb16ff5bd15ee60223883ddacba0ab8797268d", + "reference": "c5eb16ff5bd15ee60223883ddacba0ab8797268d", + "shasum": "" + }, + "require": { + "php": ">=5.5.0", + "symfony/console": ">=2.6" + }, + "require-dev": { + "phpunit/phpunit": "3.7.23" + }, + "bin": [ + "bin/phpdoc-md" + ], + "type": "library", + "autoload": { + "psr-0": { + "PHPDocsMD": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Victor Jonsson", + "email": "kontakt@victorjonsson.se" + } + ], + "description": "Command line tool for generating markdown-formatted class documentation", + "homepage": "https://github.com/victorjonsson/PHP-Markdown-Documentation-Generator", + "time": "2017-04-20T09:52:47+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.9.1", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<3.9.1" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^7.5.13" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2020-07-08T17:02:28+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.4.0" + }, + "platform-dev": [], + "platform-overrides": { + "php": "5.6.32" + }, + "plugin-api-version": "1.1.0" +} diff --git a/vendor/consolidation/output-formatters/mkdocs.yml b/vendor/consolidation/output-formatters/mkdocs.yml new file mode 100644 index 0000000000..4f36f2fa93 --- /dev/null +++ b/vendor/consolidation/output-formatters/mkdocs.yml @@ -0,0 +1,7 @@ +site_name: Consolidation Output Formatters docs +theme: readthedocs +repo_url: https://github.com/consolidation/output-formatters +include_search: true +pages: +- Home: index.md +- API: api.md diff --git a/vendor/consolidation/output-formatters/phpunit.xml.dist b/vendor/consolidation/output-formatters/phpunit.xml.dist new file mode 100644 index 0000000000..9a90fcecc6 --- /dev/null +++ b/vendor/consolidation/output-formatters/phpunit.xml.dist @@ -0,0 +1,29 @@ + + + + tests + + + + + + + + + src + + FormatterInterface.php.php + OverrideRestructureInterface.php.php + RestructureInterface.php.php + ValidationInterface.php + Formatters/RenderDataInterface.php + StructuredData/ListDataInterface.php.php + StructuredData/RenderCellInterface.php.php + StructuredData/TableDataInterface.php.php + + + + diff --git a/vendor/consolidation/output-formatters/src/Exception/AbstractDataFormatException.php b/vendor/consolidation/output-formatters/src/Exception/AbstractDataFormatException.php new file mode 100644 index 0000000000..fa29b1ddd7 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/Exception/AbstractDataFormatException.php @@ -0,0 +1,57 @@ +getName() == 'ArrayObject')) { + return 'an array'; + } + return 'an instance of ' . $data->getName(); + } + if (is_string($data)) { + return 'a string'; + } + if (is_object($data)) { + return 'an instance of ' . get_class($data); + } + throw new \Exception("Undescribable data error: " . var_export($data, true)); + } + + protected static function describeAllowedTypes($allowedTypes) + { + if (is_array($allowedTypes) && !empty($allowedTypes)) { + if (count($allowedTypes) > 1) { + return static::describeListOfAllowedTypes($allowedTypes); + } + $allowedTypes = $allowedTypes[0]; + } + return static::describeDataType($allowedTypes); + } + + protected static function describeListOfAllowedTypes($allowedTypes) + { + $descriptions = []; + foreach ($allowedTypes as $oneAllowedType) { + $descriptions[] = static::describeDataType($oneAllowedType); + } + if (count($descriptions) == 2) { + return "either {$descriptions[0]} or {$descriptions[1]}"; + } + $lastDescription = array_pop($descriptions); + $otherDescriptions = implode(', ', $descriptions); + return "one of $otherDescriptions or $lastDescription"; + } +} diff --git a/vendor/consolidation/output-formatters/src/Exception/IncompatibleDataException.php b/vendor/consolidation/output-formatters/src/Exception/IncompatibleDataException.php new file mode 100644 index 0000000000..ca13a65f48 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/Exception/IncompatibleDataException.php @@ -0,0 +1,19 @@ + '\Consolidation\OutputFormatters\Formatters\NoOutputFormatter', + 'string' => '\Consolidation\OutputFormatters\Formatters\StringFormatter', + 'yaml' => '\Consolidation\OutputFormatters\Formatters\YamlFormatter', + 'xml' => '\Consolidation\OutputFormatters\Formatters\XmlFormatter', + 'json' => '\Consolidation\OutputFormatters\Formatters\JsonFormatter', + 'print-r' => '\Consolidation\OutputFormatters\Formatters\PrintRFormatter', + 'php' => '\Consolidation\OutputFormatters\Formatters\SerializeFormatter', + 'var_export' => '\Consolidation\OutputFormatters\Formatters\VarExportFormatter', + 'list' => '\Consolidation\OutputFormatters\Formatters\ListFormatter', + 'csv' => '\Consolidation\OutputFormatters\Formatters\CsvFormatter', + 'tsv' => '\Consolidation\OutputFormatters\Formatters\TsvFormatter', + 'table' => '\Consolidation\OutputFormatters\Formatters\TableFormatter', + 'sections' => '\Consolidation\OutputFormatters\Formatters\SectionsFormatter', + ]; + if (class_exists('Symfony\Component\VarDumper\Dumper\CliDumper')) { + $defaultFormatters['var_dump'] = '\Consolidation\OutputFormatters\Formatters\VarDumpFormatter'; + } + foreach ($defaultFormatters as $id => $formatterClassname) { + $formatter = new $formatterClassname; + $this->addFormatter($id, $formatter); + } + $this->addFormatter('', $this->formatters['string']); + } + + public function addDefaultSimplifiers() + { + // Add our default array simplifier (DOMDocument to array) + $this->addSimplifier(new DomToArraySimplifier()); + } + + /** + * Add a formatter + * + * @param string $key the identifier of the formatter to add + * @param string $formatter the class name of the formatter to add + * @return FormatterManager + */ + public function addFormatter($key, FormatterInterface $formatter) + { + $this->formatters[$key] = $formatter; + return $this; + } + + /** + * Add a simplifier + * + * @param SimplifyToArrayInterface $simplifier the array simplifier to add + * @return FormatterManager + */ + public function addSimplifier(SimplifyToArrayInterface $simplifier) + { + $this->arraySimplifiers[] = $simplifier; + return $this; + } + + /** + * Return a set of InputOption based on the annotations of a command. + * @param FormatterOptions $options + * @return InputOption[] + */ + public function automaticOptions(FormatterOptions $options, $dataType) + { + $automaticOptions = []; + + // At the moment, we only support automatic options for --format + // and --fields, so exit if the command returns no data. + if (!isset($dataType)) { + return []; + } + + $validFormats = $this->validFormats($dataType); + if (empty($validFormats)) { + return []; + } + + $availableFields = $options->get(FormatterOptions::FIELD_LABELS); + $hasDefaultStringField = $options->get(FormatterOptions::DEFAULT_STRING_FIELD); + $defaultFormat = $hasDefaultStringField ? 'string' : ($availableFields ? 'table' : 'yaml'); + + if (count($validFormats) > 1) { + // Make an input option for --format + $description = 'Format the result data. Available formats: ' . implode(',', $validFormats); + $automaticOptions[FormatterOptions::FORMAT] = new InputOption(FormatterOptions::FORMAT, '', InputOption::VALUE_REQUIRED, $description, $defaultFormat); + } + + $dataTypeClass = ($dataType instanceof \ReflectionClass) ? $dataType : new \ReflectionClass($dataType); + + if ($availableFields) { + $defaultFields = $options->get(FormatterOptions::DEFAULT_FIELDS, [], ''); + $description = 'Available fields: ' . implode(', ', $this->availableFieldsList($availableFields)); + $automaticOptions[FormatterOptions::FIELDS] = new InputOption(FormatterOptions::FIELDS, '', InputOption::VALUE_REQUIRED, $description, $defaultFields); + } elseif ($dataTypeClass->implementsInterface('Consolidation\OutputFormatters\StructuredData\RestructureInterface')) { + $automaticOptions[FormatterOptions::FIELDS] = new InputOption(FormatterOptions::FIELDS, '', InputOption::VALUE_REQUIRED, 'Limit output to only the listed elements. Name top-level elements by key, e.g. "--fields=name,date", or use dot notation to select a nested element, e.g. "--fields=a.b.c as example".', []); + } + + if (isset($automaticOptions[FormatterOptions::FIELDS])) { + $automaticOptions[FormatterOptions::FIELD] = new InputOption(FormatterOptions::FIELD, '', InputOption::VALUE_REQUIRED, "Select just one field, and force format to 'string'.", ''); + } + + return $automaticOptions; + } + + /** + * Given a list of available fields, return a list of field descriptions. + * @return string[] + */ + protected function availableFieldsList($availableFields) + { + return array_map( + function ($key) use ($availableFields) { + return $availableFields[$key] . " ($key)"; + }, + array_keys($availableFields) + ); + } + + /** + * Return the identifiers for all valid data types that have been registered. + * + * @param mixed $dataType \ReflectionObject or other description of the produced data type + * @return array + */ + public function validFormats($dataType) + { + $validFormats = []; + foreach ($this->formatters as $formatId => $formatterName) { + $formatter = $this->getFormatter($formatId); + if (!empty($formatId) && $this->isValidFormat($formatter, $dataType)) { + $validFormats[] = $formatId; + } + } + sort($validFormats); + return $validFormats; + } + + public function isValidFormat(FormatterInterface $formatter, $dataType) + { + if (is_array($dataType)) { + $dataType = new \ReflectionClass('\ArrayObject'); + } + if (!is_object($dataType) && !class_exists($dataType)) { + return false; + } + if (!$dataType instanceof \ReflectionClass) { + $dataType = new \ReflectionClass($dataType); + } + return $this->isValidDataType($formatter, $dataType); + } + + public function isValidDataType(FormatterInterface $formatter, \ReflectionClass $dataType) + { + if ($this->canSimplifyToArray($dataType)) { + if ($this->isValidFormat($formatter, [])) { + return true; + } + } + // If the formatter does not implement ValidationInterface, then + // it is presumed that the formatter only accepts arrays. + if (!$formatter instanceof ValidationInterface) { + return $dataType->isSubclassOf('ArrayObject') || ($dataType->getName() == 'ArrayObject'); + } + return $formatter->isValidDataType($dataType); + } + + /** + * Format and write output + * + * @param OutputInterface $output Output stream to write to + * @param string $format Data format to output in + * @param mixed $structuredOutput Data to output + * @param FormatterOptions $options Formatting options + */ + public function write(OutputInterface $output, $format, $structuredOutput, FormatterOptions $options) + { + // Convert the data to another format (e.g. converting from RowsOfFields to + // UnstructuredListData when the fields indicate an unstructured transformation + // is requested). + $structuredOutput = $this->convertData($structuredOutput, $options); + + // TODO: If the $format is the default format (not selected by the user), and + // if `convertData` switched us to unstructured data, then select a new default + // format (e.g. yaml) if the selected format cannot render the converted data. + $formatter = $this->getFormatter((string)$format); + + // If the data format is not applicable for the selected formatter, throw an error. + if (!is_string($structuredOutput) && !$this->isValidFormat($formatter, $structuredOutput)) { + $validFormats = $this->validFormats($structuredOutput); + throw new InvalidFormatException((string)$format, $structuredOutput, $validFormats); + } + if ($structuredOutput instanceof FormatterAwareInterface) { + $structuredOutput->setFormatter($formatter); + } + // Give the formatter a chance to override the options + $options = $this->overrideOptions($formatter, $structuredOutput, $options); + $restructuredOutput = $this->validateAndRestructure($formatter, $structuredOutput, $options); + if ($formatter instanceof MetadataFormatterInterface) { + $formatter->writeMetadata($output, $structuredOutput, $options); + } + $formatter->write($output, $restructuredOutput, $options); + } + + protected function validateAndRestructure(FormatterInterface $formatter, $structuredOutput, FormatterOptions $options) + { + // Give the formatter a chance to do something with the + // raw data before it is restructured. + $overrideRestructure = $this->overrideRestructure($formatter, $structuredOutput, $options); + if ($overrideRestructure) { + return $overrideRestructure; + } + + // Restructure the output data (e.g. select fields to display, etc.). + $restructuredOutput = $this->restructureData($structuredOutput, $options); + + // Make sure that the provided data is in the correct format for the selected formatter. + $restructuredOutput = $this->validateData($formatter, $restructuredOutput, $options); + + // Give the original data a chance to re-render the structured + // output after it has been restructured and validated. + $restructuredOutput = $this->renderData($formatter, $structuredOutput, $restructuredOutput, $options); + + return $restructuredOutput; + } + + /** + * Fetch the requested formatter. + * + * @param string $format Identifier for requested formatter + * @return FormatterInterface + */ + public function getFormatter($format) + { + // The client must inject at least one formatter before asking for + // any formatters; if not, we will provide all of the usual defaults + // as a convenience. + if (empty($this->formatters)) { + $this->addDefaultFormatters(); + $this->addDefaultSimplifiers(); + } + if (!$this->hasFormatter($format)) { + throw new UnknownFormatException($format); + } + $formatter = $this->formatters[$format]; + return $formatter; + } + + /** + * Test to see if the stipulated format exists + */ + public function hasFormatter($format) + { + return array_key_exists($format, $this->formatters); + } + + /** + * Render the data as necessary (e.g. to select or reorder fields). + * + * @param FormatterInterface $formatter + * @param mixed $originalData + * @param mixed $restructuredData + * @param FormatterOptions $options Formatting options + * @return mixed + */ + public function renderData(FormatterInterface $formatter, $originalData, $restructuredData, FormatterOptions $options) + { + if ($formatter instanceof RenderDataInterface) { + return $formatter->renderData($originalData, $restructuredData, $options); + } + return $restructuredData; + } + + /** + * Determine if the provided data is compatible with the formatter being used. + * + * @param FormatterInterface $formatter Formatter being used + * @param mixed $structuredOutput Data to validate + * @return mixed + */ + public function validateData(FormatterInterface $formatter, $structuredOutput, FormatterOptions $options) + { + // If the formatter implements ValidationInterface, then let it + // test the data and throw or return an error + if ($formatter instanceof ValidationInterface) { + return $formatter->validate($structuredOutput); + } + // If the formatter does not implement ValidationInterface, then + // it will never be passed an ArrayObject; we will always give + // it a simple array. + $structuredOutput = $this->simplifyToArray($structuredOutput, $options); + // If we could not simplify to an array, then throw an exception. + // We will never give a formatter anything other than an array + // unless it validates that it can accept the data type. + if (!is_array($structuredOutput)) { + throw new IncompatibleDataException( + $formatter, + $structuredOutput, + [] + ); + } + return $structuredOutput; + } + + protected function simplifyToArray($structuredOutput, FormatterOptions $options) + { + // We can do nothing unless the provided data is an object. + if (!is_object($structuredOutput)) { + return $structuredOutput; + } + // Check to see if any of the simplifiers can convert the given data + // set to an array. + $outputDataType = new \ReflectionClass($structuredOutput); + foreach ($this->arraySimplifiers as $simplifier) { + if ($simplifier->canSimplify($outputDataType)) { + $structuredOutput = $simplifier->simplifyToArray($structuredOutput, $options); + } + } + // Convert data structure back into its original form, if necessary. + if ($structuredOutput instanceof OriginalDataInterface) { + return $structuredOutput->getOriginalData(); + } + // Convert \ArrayObjects to a simple array. + if ($structuredOutput instanceof \ArrayObject) { + return $structuredOutput->getArrayCopy(); + } + return $structuredOutput; + } + + protected function canSimplifyToArray(\ReflectionClass $structuredOutput) + { + foreach ($this->arraySimplifiers as $simplifier) { + if ($simplifier->canSimplify($structuredOutput)) { + return true; + } + } + return false; + } + + /** + * Convert from one format to another if necessary prior to restructuring. + */ + public function convertData($structuredOutput, FormatterOptions $options) + { + if ($structuredOutput instanceof ConversionInterface) { + return $structuredOutput->convert($options); + } + return $structuredOutput; + } + + /** + * Restructure the data as necessary (e.g. to select or reorder fields). + * + * @param mixed $structuredOutput + * @param FormatterOptions $options + * @return mixed + */ + public function restructureData($structuredOutput, FormatterOptions $options) + { + if ($structuredOutput instanceof RestructureInterface) { + return $structuredOutput->restructure($options); + } + return $structuredOutput; + } + + /** + * Allow the formatter access to the raw structured data prior + * to restructuring. For example, the 'list' formatter may wish + * to display the row keys when provided table output. If this + * function returns a result that does not evaluate to 'false', + * then that result will be used as-is, and restructuring and + * validation will not occur. + * + * @param mixed $structuredOutput + * @param FormatterOptions $options + * @return mixed + */ + public function overrideRestructure(FormatterInterface $formatter, $structuredOutput, FormatterOptions $options) + { + if ($formatter instanceof OverrideRestructureInterface) { + return $formatter->overrideRestructure($structuredOutput, $options); + } + } + + /** + * Allow the formatter to mess with the configuration options before any + * transformations et. al. get underway. + * @param FormatterInterface $formatter + * @param mixed $structuredOutput + * @param FormatterOptions $options + * @return FormatterOptions + */ + public function overrideOptions(FormatterInterface $formatter, $structuredOutput, FormatterOptions $options) + { + // Set the "Human Readable" option if the formatter has the HumanReadable marker interface + if ($formatter instanceof HumanReadableFormat) { + $options->setHumanReadable(); + } + // The formatter may also make dynamic adjustment to the options. + if ($formatter instanceof OverrideOptionsInterface) { + return $formatter->overrideOptions($structuredOutput, $options); + } + return $options; + } +} diff --git a/vendor/consolidation/output-formatters/src/Formatters/CsvFormatter.php b/vendor/consolidation/output-formatters/src/Formatters/CsvFormatter.php new file mode 100644 index 0000000000..df99d88888 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/Formatters/CsvFormatter.php @@ -0,0 +1,131 @@ +validDataTypes() + ); + } + // If the data was provided to us as a single array, then + // convert it to a single row. + if (is_array($structuredData) && !empty($structuredData)) { + $firstRow = reset($structuredData); + if (!is_array($firstRow)) { + return [$structuredData]; + } + } + return $structuredData; + } + + /** + * Return default values for formatter options + * @return array + */ + protected function getDefaultFormatterOptions() + { + return [ + FormatterOptions::INCLUDE_FIELD_LABELS => true, + FormatterOptions::DELIMITER => ',', + FormatterOptions::CSV_ENCLOSURE => '"', + FormatterOptions::CSV_ESCAPE_CHAR => "\\", + ]; + } + + /** + * @inheritdoc + */ + public function write(OutputInterface $output, $data, FormatterOptions $options) + { + $defaults = $this->getDefaultFormatterOptions(); + + $includeFieldLabels = $options->get(FormatterOptions::INCLUDE_FIELD_LABELS, $defaults); + if ($includeFieldLabels && ($data instanceof TableTransformation)) { + $headers = $data->getHeaders(); + $this->writeOneLine($output, $headers, $options); + } + + foreach ($data as $line) { + $this->writeOneLine($output, $line, $options); + } + } + + /** + * Writes a single a single line of formatted CSV data to the output stream. + * + * @param OutputInterface $output the output stream to write to. + * @param array $data an array of field data to convert to a CSV string. + * @param FormatterOptions $options the specified options for this formatter. + */ + protected function writeOneLine(OutputInterface $output, $data, $options) + { + $defaults = $this->getDefaultFormatterOptions(); + $delimiter = $options->get(FormatterOptions::DELIMITER, $defaults); + $enclosure = $options->get(FormatterOptions::CSV_ENCLOSURE, $defaults); + $escapeChar = $options->get(FormatterOptions::CSV_ESCAPE_CHAR, $defaults); + $output->write($this->csvEscape($data, $delimiter, $enclosure, $escapeChar)); + } + + /** + * Generates a CSV-escaped string from an array of field data. + * + * @param array $data an array of field data to format as a CSV. + * @param string $delimiter the delimiter to use between fields. + * @param string $enclosure character to use when enclosing complex fields. + * @param string $escapeChar character to use when escaping special characters. + * + * @return string|bool the formatted CSV string, or FALSE if the formatting failed. + */ + protected function csvEscape($data, $delimiter = ',', $enclosure = '"', $escapeChar = "\\") + { + $buffer = fopen('php://temp', 'r+'); + if (version_compare(PHP_VERSION, '5.5.4', '>=')) { + fputcsv($buffer, $data, $delimiter, $enclosure, $escapeChar); + } else { + fputcsv($buffer, $data, $delimiter, $enclosure); + } + rewind($buffer); + $csv = fgets($buffer); + fclose($buffer); + return $csv; + } +} diff --git a/vendor/consolidation/output-formatters/src/Formatters/FormatterAwareInterface.php b/vendor/consolidation/output-formatters/src/Formatters/FormatterAwareInterface.php new file mode 100644 index 0000000000..788a4d0833 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/Formatters/FormatterAwareInterface.php @@ -0,0 +1,9 @@ +formatter = $formatter; + } + + public function getFormatter() + { + return $this->formatter; + } + + public function isHumanReadable() + { + return $this->formatter && $this->formatter instanceof \Consolidation\OutputFormatters\Formatters\HumanReadableFormat; + } +} diff --git a/vendor/consolidation/output-formatters/src/Formatters/FormatterInterface.php b/vendor/consolidation/output-formatters/src/Formatters/FormatterInterface.php new file mode 100644 index 0000000000..224e3dca32 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/Formatters/FormatterInterface.php @@ -0,0 +1,18 @@ +writeln(json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + } +} diff --git a/vendor/consolidation/output-formatters/src/Formatters/ListFormatter.php b/vendor/consolidation/output-formatters/src/Formatters/ListFormatter.php new file mode 100644 index 0000000000..01fce52480 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/Formatters/ListFormatter.php @@ -0,0 +1,59 @@ +writeln(implode("\n", $data)); + } + + /** + * @inheritdoc + */ + public function overrideRestructure($structuredOutput, FormatterOptions $options) + { + // If the structured data implements ListDataInterface, + // then we will render whatever data its 'getListData' + // method provides. + if ($structuredOutput instanceof ListDataInterface) { + return $this->renderData($structuredOutput, $structuredOutput->getListData($options), $options); + } + } + + /** + * @inheritdoc + */ + public function renderData($originalData, $restructuredData, FormatterOptions $options) + { + if ($originalData instanceof RenderCellInterface) { + return $this->renderEachCell($originalData, $restructuredData, $options); + } + return $restructuredData; + } + + protected function renderEachCell($originalData, $restructuredData, FormatterOptions $options) + { + foreach ($restructuredData as $key => $cellData) { + $restructuredData[$key] = $originalData->renderCell($key, $cellData, $options, $restructuredData); + } + return $restructuredData; + } +} diff --git a/vendor/consolidation/output-formatters/src/Formatters/MetadataFormatterInterface.php b/vendor/consolidation/output-formatters/src/Formatters/MetadataFormatterInterface.php new file mode 100644 index 0000000000..4147e274c8 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/Formatters/MetadataFormatterInterface.php @@ -0,0 +1,17 @@ +get(FormatterOptions::METADATA_TEMPLATE); + if (!$template) { + return; + } + if (!$structuredOutput instanceof MetadataInterface) { + return; + } + $metadata = $structuredOutput->getMetadata(); + if (empty($metadata)) { + return; + } + $message = $this->interpolate($template, $metadata); + return $output->writeln($message); + } + + /** + * Interpolates context values into the message placeholders. + * + * @author PHP Framework Interoperability Group + * + * @param string $message + * @param array $context + * + * @return string + */ + private function interpolate($message, array $context) + { + // build a replacement array with braces around the context keys + $replace = array(); + foreach ($context as $key => $val) { + if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) { + $replace[sprintf('{%s}', $key)] = $val; + } + } + + // interpolate replacement values into the message and return + return strtr($message, $replace); + } +} diff --git a/vendor/consolidation/output-formatters/src/Formatters/NoOutputFormatter.php b/vendor/consolidation/output-formatters/src/Formatters/NoOutputFormatter.php new file mode 100644 index 0000000000..1439475557 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/Formatters/NoOutputFormatter.php @@ -0,0 +1,40 @@ +writeln(print_r($data, true)); + } +} diff --git a/vendor/consolidation/output-formatters/src/Formatters/RenderDataInterface.php b/vendor/consolidation/output-formatters/src/Formatters/RenderDataInterface.php new file mode 100644 index 0000000000..a4da8e0edc --- /dev/null +++ b/vendor/consolidation/output-formatters/src/Formatters/RenderDataInterface.php @@ -0,0 +1,19 @@ +renderEachCell($originalData, $restructuredData, $options); + } + return $restructuredData; + } + + protected function renderEachCell($originalData, $restructuredData, FormatterOptions $options) + { + foreach ($restructuredData as $id => $row) { + foreach ($row as $key => $cellData) { + $restructuredData[$id][$key] = $originalData->renderCell($key, $cellData, $options, $row); + } + } + return $restructuredData; + } +} diff --git a/vendor/consolidation/output-formatters/src/Formatters/SectionsFormatter.php b/vendor/consolidation/output-formatters/src/Formatters/SectionsFormatter.php new file mode 100644 index 0000000000..89ed270778 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/Formatters/SectionsFormatter.php @@ -0,0 +1,72 @@ +validDataTypes() + ); + } + return $structuredData; + } + + /** + * @inheritdoc + */ + public function write(OutputInterface $output, $tableTransformer, FormatterOptions $options) + { + $table = new Table($output); + $table->setStyle('compact'); + foreach ($tableTransformer as $rowid => $row) { + $rowLabel = $tableTransformer->getRowLabel($rowid); + $output->writeln(''); + $output->writeln($rowLabel); + $sectionData = new PropertyList($row); + $sectionOptions = new FormatterOptions([], $options->getOptions()); + $sectionTableTransformer = $sectionData->restructure($sectionOptions); + $table->setRows($sectionTableTransformer->getTableData(true)); + $table->render(); + } + } +} diff --git a/vendor/consolidation/output-formatters/src/Formatters/SerializeFormatter.php b/vendor/consolidation/output-formatters/src/Formatters/SerializeFormatter.php new file mode 100644 index 0000000000..f815137358 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/Formatters/SerializeFormatter.php @@ -0,0 +1,21 @@ +writeln(serialize($data)); + } +} diff --git a/vendor/consolidation/output-formatters/src/Formatters/StringFormatter.php b/vendor/consolidation/output-formatters/src/Formatters/StringFormatter.php new file mode 100644 index 0000000000..1a008d985c --- /dev/null +++ b/vendor/consolidation/output-formatters/src/Formatters/StringFormatter.php @@ -0,0 +1,95 @@ +implementsInterface('\Consolidation\OutputFormatters\StructuredData\UnstructuredInterface') && !$dataType->implementsInterface('\Consolidation\OutputFormatters\Transformations\StringTransformationInterface')) { + return false; + } + return true; + } + + /** + * @inheritdoc + */ + public function write(OutputInterface $output, $data, FormatterOptions $options) + { + if (is_string($data)) { + return $output->writeln($data); + } + return $this->reduceToSigleFieldAndWrite($output, $data, $options); + } + + /** + * @inheritdoc + */ + public function overrideOptions($structuredOutput, FormatterOptions $options) + { + $defaultField = $options->get(FormatterOptions::DEFAULT_STRING_FIELD, [], ''); + $userFields = $options->get(FormatterOptions::FIELDS, [FormatterOptions::FIELDS => $options->get(FormatterOptions::FIELD)]); + $optionsOverride = $options->override([]); + if (empty($userFields) && !empty($defaultField)) { + $optionsOverride->setOption(FormatterOptions::FIELDS, $defaultField); + } + return $optionsOverride; + } + + /** + * If the data provided to a 'string' formatter is a table, then try + * to emit it in a simplified form (by default, TSV). + * + * @param OutputInterface $output + * @param mixed $data + * @param FormatterOptions $options + */ + protected function reduceToSigleFieldAndWrite(OutputInterface $output, $data, FormatterOptions $options) + { + if ($data instanceof StringTransformationInterface) { + $simplified = $data->simplifyToString($options); + return $output->write($simplified); + } + + $alternateFormatter = new TsvFormatter(); + try { + $data = $alternateFormatter->validate($data); + $alternateFormatter->write($output, $data, $options); + } catch (\Exception $e) { + } + } + + /** + * Always validate any data, though. This format will never + * cause an error if it is selected for an incompatible data type; at + * worse, it simply does not print any data. + */ + public function validate($structuredData) + { + return $structuredData; + } +} diff --git a/vendor/consolidation/output-formatters/src/Formatters/TableFormatter.php b/vendor/consolidation/output-formatters/src/Formatters/TableFormatter.php new file mode 100644 index 0000000000..6d6cfd82bb --- /dev/null +++ b/vendor/consolidation/output-formatters/src/Formatters/TableFormatter.php @@ -0,0 +1,145 @@ +validDataTypes() + ); + } + return $structuredData; + } + + /** + * @inheritdoc + */ + public function write(OutputInterface $output, $tableTransformer, FormatterOptions $options) + { + $headers = []; + $defaults = [ + FormatterOptions::TABLE_STYLE => 'consolidation', + FormatterOptions::INCLUDE_FIELD_LABELS => true, + ]; + + $table = new Table($output); + + static::addCustomTableStyles($table); + + $table->setStyle($options->get(FormatterOptions::TABLE_STYLE, $defaults)); + $isList = $tableTransformer->isList(); + $includeHeaders = $options->get(FormatterOptions::INCLUDE_FIELD_LABELS, $defaults); + $listDelimiter = $options->get(FormatterOptions::LIST_DELIMITER, $defaults); + + $headers = $tableTransformer->getHeaders(); + $data = $tableTransformer->getTableData($includeHeaders && $isList); + + if ($listDelimiter) { + if (!empty($headers)) { + array_splice($headers, 1, 0, ':'); + } + $data = array_map(function ($item) { + array_splice($item, 1, 0, ':'); + return $item; + }, $data); + } + + if ($includeHeaders && !$isList) { + $table->setHeaders($headers); + } + + // todo: $output->getFormatter(); + $data = $this->wrap($headers, $data, $table->getStyle(), $options); + $table->setRows($data); + $table->render(); + } + + /** + * Wrap the table data + * @param array $data + * @param TableStyle $tableStyle + * @param FormatterOptions $options + * @return array + */ + protected function wrap($headers, $data, TableStyle $tableStyle, FormatterOptions $options) + { + $wrapper = new WordWrapper($options->get(FormatterOptions::TERMINAL_WIDTH)); + $wrapper->setPaddingFromStyle($tableStyle); + if (!empty($headers)) { + $headerLengths = array_map(function ($item) { + return strlen($item); + }, $headers); + $wrapper->setMinimumWidths($headerLengths); + } + return $wrapper->wrap($data); + } + + /** + * Add our custom table style(s) to the table. + */ + protected static function addCustomTableStyles($table) + { + // The 'consolidation' style is the same as the 'symfony-style-guide' + // style, except it maintains the colored headers used in 'default'. + $consolidationStyle = new TableStyle(); + $consolidationStyle + ->setHorizontalBorderChar('-') + ->setVerticalBorderChar(' ') + ->setCrossingChar(' ') + ; + $table->setStyleDefinition('consolidation', $consolidationStyle); + } +} diff --git a/vendor/consolidation/output-formatters/src/Formatters/TsvFormatter.php b/vendor/consolidation/output-formatters/src/Formatters/TsvFormatter.php new file mode 100644 index 0000000000..8a827424e1 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/Formatters/TsvFormatter.php @@ -0,0 +1,40 @@ + false, + ]; + } + + protected function writeOneLine(OutputInterface $output, $data, $options) + { + $output->writeln($this->tsvEscape($data)); + } + + protected function tsvEscape($data) + { + return implode("\t", array_map( + function ($item) { + return str_replace(["\t", "\n"], ['\t', '\n'], $item); + }, + $data + )); + } +} diff --git a/vendor/consolidation/output-formatters/src/Formatters/VarDumpFormatter.php b/vendor/consolidation/output-formatters/src/Formatters/VarDumpFormatter.php new file mode 100644 index 0000000000..99d713cca3 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/Formatters/VarDumpFormatter.php @@ -0,0 +1,40 @@ +cloneVar($data); + + if ($output instanceof StreamOutput) { + // When stream output is used the dumper is smart enough to + // determine whether or not to apply colors to the dump. + // @see Symfony\Component\VarDumper\Dumper\CliDumper::supportsColors + $dumper->dump($cloned_data, $output->getStream()); + } else { + // @todo Use dumper return value to get output once we stop support + // VarDumper v2. + $stream = fopen('php://memory', 'r+b'); + $dumper->dump($cloned_data, $stream); + $output->writeln(stream_get_contents($stream, -1, 0)); + fclose($stream); + } + } +} diff --git a/vendor/consolidation/output-formatters/src/Formatters/VarExportFormatter.php b/vendor/consolidation/output-formatters/src/Formatters/VarExportFormatter.php new file mode 100644 index 0000000000..0303ba48cf --- /dev/null +++ b/vendor/consolidation/output-formatters/src/Formatters/VarExportFormatter.php @@ -0,0 +1,21 @@ +writeln(var_export($data, true)); + } +} diff --git a/vendor/consolidation/output-formatters/src/Formatters/XmlFormatter.php b/vendor/consolidation/output-formatters/src/Formatters/XmlFormatter.php new file mode 100644 index 0000000000..85b4e37e92 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/Formatters/XmlFormatter.php @@ -0,0 +1,79 @@ +getDomData(); + } + if ($structuredData instanceof \ArrayObject) { + return $structuredData->getArrayCopy(); + } + if (!is_array($structuredData)) { + throw new IncompatibleDataException( + $this, + $structuredData, + $this->validDataTypes() + ); + } + return $structuredData; + } + + /** + * @inheritdoc + */ + public function write(OutputInterface $output, $dom, FormatterOptions $options) + { + if (is_array($dom)) { + $schema = $options->getXmlSchema(); + $dom = $schema->arrayToXML($dom); + } + $dom->formatOutput = true; + $output->writeln($dom->saveXML()); + } +} diff --git a/vendor/consolidation/output-formatters/src/Formatters/YamlFormatter.php b/vendor/consolidation/output-formatters/src/Formatters/YamlFormatter.php new file mode 100644 index 0000000000..07a8f21d09 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/Formatters/YamlFormatter.php @@ -0,0 +1,27 @@ +writeln(Yaml::dump($data, PHP_INT_MAX, $indent, false, true)); + } +} diff --git a/vendor/consolidation/output-formatters/src/Options/FormatterOptions.php b/vendor/consolidation/output-formatters/src/Options/FormatterOptions.php new file mode 100644 index 0000000000..306cc00474 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/Options/FormatterOptions.php @@ -0,0 +1,405 @@ +configurationData = $configurationData; + $this->options = $options; + } + + /** + * Create a new FormatterOptions object with new configuration data (provided), + * and the same options data as this instance. + * + * @param array $configurationData + * @return FormatterOptions + */ + public function override($configurationData) + { + $override = new self(); + $override + ->setConfigurationData($configurationData + $this->getConfigurationData()) + ->setOptions($this->getOptions()); + return $override; + } + + public function setTableStyle($style) + { + return $this->setConfigurationValue(self::TABLE_STYLE, $style); + } + + public function setDelimiter($delimiter) + { + return $this->setConfigurationValue(self::DELIMITER, $delimiter); + } + + public function setCsvEnclosure($enclosure) + { + return $this->setConfigurationValue(self::CSV_ENCLOSURE, $enclosure); + } + + public function setCsvEscapeChar($escapeChar) + { + return $this->setConfigurationValue(self::CSV_ESCAPE_CHAR, $escapeChar); + } + + public function setListDelimiter($listDelimiter) + { + return $this->setConfigurationValue(self::LIST_DELIMITER, $listDelimiter); + } + + + + public function setIncludeFieldLables($includFieldLables) + { + return $this->setConfigurationValue(self::INCLUDE_FIELD_LABELS, $includFieldLables); + } + + public function setListOrientation($listOrientation) + { + return $this->setConfigurationValue(self::LIST_ORIENTATION, $listOrientation); + } + + public function setRowLabels($rowLabels) + { + return $this->setConfigurationValue(self::ROW_LABELS, $rowLabels); + } + + public function setDefaultFields($fields) + { + return $this->setConfigurationValue(self::DEFAULT_FIELDS, $fields); + } + + public function setFieldLabels($fieldLabels) + { + return $this->setConfigurationValue(self::FIELD_LABELS, $fieldLabels); + } + + public function setDefaultStringField($defaultStringField) + { + return $this->setConfigurationValue(self::DEFAULT_STRING_FIELD, $defaultStringField); + } + + public function setWidth($width) + { + return $this->setConfigurationValue(self::TERMINAL_WIDTH, $width); + } + + public function setHumanReadable($isHumanReadable = true) + { + return $this->setConfigurationValue(self::HUMAN_READABLE, $isHumanReadable); + } + + /** + * Get a formatter option + * + * @param string $key + * @param array $defaults + * @param mixed $default + * @return mixed + */ + public function get($key, $defaults = [], $default = false) + { + $value = $this->fetch($key, $defaults, $default); + return $this->parse($key, $value); + } + + /** + * Return the XmlSchema to use with --format=xml for data types that support + * that. This is used when an array needs to be converted into xml. + * + * @return XmlSchema + */ + public function getXmlSchema() + { + return new XmlSchema(); + } + + /** + * Determine the format that was requested by the caller. + * + * @param array $defaults + * @return string + */ + public function getFormat($defaults = []) + { + return $this->get(self::FORMAT, [], $this->get(self::DEFAULT_FORMAT, $defaults, '')); + } + + /** + * Look up a key, and return its raw value. + * + * @param string $key + * @param array $defaults + * @param mixed $default + * @return mixed + */ + protected function fetch($key, $defaults = [], $default = false) + { + $defaults = $this->defaultsForKey($key, $defaults, $default); + $values = $this->fetchRawValues($defaults); + return $values[$key]; + } + + /** + * Reduce provided defaults to the single item identified by '$key', + * if it exists, or an empty array otherwise. + * + * @param string $key + * @param array $defaults + * @return array + */ + protected function defaultsForKey($key, $defaults, $default = false) + { + if (array_key_exists($key, $defaults)) { + return [$key => $defaults[$key]]; + } + return [$key => $default]; + } + + /** + * Look up all of the items associated with the provided defaults. + * + * @param array $defaults + * @return array + */ + protected function fetchRawValues($defaults = []) + { + return array_merge( + $defaults, + $this->getConfigurationData(), + $this->getOptions(), + $this->getInputOptions($defaults) + ); + } + + /** + * Given the raw value for a specific key, do any type conversion + * (e.g. from a textual list to an array) needed for the data. + * + * @param string $key + * @param mixed $value + * @return mixed + */ + protected function parse($key, $value) + { + $optionFormat = $this->getOptionFormat($key); + if (!empty($optionFormat) && is_string($value)) { + return $this->$optionFormat($value); + } + return $value; + } + + /** + * Convert from a textual list to an array + * + * @param string $value + * @return array + */ + public function parsePropertyList($value) + { + return PropertyParser::parse($value); + } + + /** + * Given a specific key, return the class method name of the + * parsing method for data stored under this key. + * + * @param string $key + * @return string + */ + protected function getOptionFormat($key) + { + $propertyFormats = [ + self::ROW_LABELS => 'PropertyList', + self::FIELD_LABELS => 'PropertyList', + ]; + if (array_key_exists($key, $propertyFormats)) { + return "parse{$propertyFormats[$key]}"; + } + return ''; + } + + /** + * Change the configuration data for this formatter options object. + * + * @param array $configurationData + * @return FormatterOptions + */ + public function setConfigurationData($configurationData) + { + $this->configurationData = $configurationData; + return $this; + } + + /** + * Change one configuration value for this formatter option. + * + * @param string $key + * @param mixed $value + * @return FormetterOptions + */ + protected function setConfigurationValue($key, $value) + { + $this->configurationData[$key] = $value; + return $this; + } + + /** + * Change one configuration value for this formatter option, but only + * if it does not already have a value set. + * + * @param string $key + * @param mixed $value + * @return FormetterOptions + */ + public function setConfigurationDefault($key, $value) + { + if (!array_key_exists($key, $this->configurationData)) { + return $this->setConfigurationValue($key, $value); + } + return $this; + } + + /** + * Return a reference to the configuration data for this object. + * + * @return array + */ + public function getConfigurationData() + { + return $this->configurationData; + } + + /** + * Set all of the options that were specified by the user for this request. + * + * @param array $options + * @return FormatterOptions + */ + public function setOptions($options) + { + $this->options = $options; + return $this; + } + + /** + * Change one option value specified by the user for this request. + * + * @param string $key + * @param mixed $value + * @return FormatterOptions + */ + public function setOption($key, $value) + { + $this->options[$key] = $value; + return $this; + } + + /** + * Return a reference to the user-specified options for this request. + * + * @return array + */ + public function getOptions() + { + return $this->options; + } + + /** + * Provide a Symfony Console InputInterface containing the user-specified + * options for this request. + * + * @param InputInterface $input + * @return type + */ + public function setInput(InputInterface $input) + { + $this->input = $input; + } + + /** + * Return all of the options from the provided $defaults array that + * exist in our InputInterface object. + * + * @param array $defaults + * @return array + */ + public function getInputOptions($defaults) + { + if (!isset($this->input)) { + return []; + } + $options = []; + foreach ($defaults as $key => $value) { + if ($this->input->hasOption($key)) { + $result = $this->input->getOption($key); + if (isset($result)) { + $options[$key] = $this->input->getOption($key); + } + } + } + return $options; + } +} diff --git a/vendor/consolidation/output-formatters/src/Options/OverrideOptionsInterface.php b/vendor/consolidation/output-formatters/src/Options/OverrideOptionsInterface.php new file mode 100644 index 0000000000..04a09e9676 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/Options/OverrideOptionsInterface.php @@ -0,0 +1,17 @@ +getArrayCopy()); + } + + protected function getReorderedFieldLabels($data, $options, $defaults) + { + $reorderer = new ReorderFields(); + $fieldLabels = $reorderer->reorder( + $this->getFields($options, $defaults), + $options->get(FormatterOptions::FIELD_LABELS, $defaults), + $data + ); + return $fieldLabels; + } + + protected function getFields($options, $defaults) + { + $fieldShortcut = $options->get(FormatterOptions::FIELD); + if (!empty($fieldShortcut)) { + return [$fieldShortcut]; + } + $result = $options->get(FormatterOptions::FIELDS); + if (!empty($result)) { + return $result; + } + $isHumanReadable = $options->get(FormatterOptions::HUMAN_READABLE); + if ($isHumanReadable) { + $result = $options->get(FormatterOptions::DEFAULT_TABLE_FIELDS); + if (!empty($result)) { + return $result; + } + } + return $options->get(FormatterOptions::DEFAULT_FIELDS, $defaults); + } + + /** + * A structured list may provide its own set of default options. These + * will be used in place of the command's default options (from the + * annotations) in instances where the user does not provide the options + * explicitly (on the commandline) or implicitly (via a configuration file). + * + * @return array + */ + protected function defaultOptions() + { + return [ + FormatterOptions::FIELDS => [], + FormatterOptions::FIELD_LABELS => [], + ]; + } +} diff --git a/vendor/consolidation/output-formatters/src/StructuredData/AbstractStructuredList.php b/vendor/consolidation/output-formatters/src/StructuredData/AbstractStructuredList.php new file mode 100644 index 0000000000..ee25af686c --- /dev/null +++ b/vendor/consolidation/output-formatters/src/StructuredData/AbstractStructuredList.php @@ -0,0 +1,52 @@ +defaultOptions(); + $fieldLabels = $this->getReorderedFieldLabels($data, $options, $defaults); + + $tableTransformer = $this->instantiateTableTransformation($data, $fieldLabels, $options->get(FormatterOptions::ROW_LABELS, $defaults)); + if ($options->get(FormatterOptions::LIST_ORIENTATION, $defaults)) { + $tableTransformer->setLayout(TableTransformation::LIST_LAYOUT); + } + + return $tableTransformer; + } + + protected function instantiateTableTransformation($data, $fieldLabels, $rowLabels) + { + return new TableTransformation($data, $fieldLabels, $rowLabels); + } + + protected function defaultOptions() + { + return [ + FormatterOptions::ROW_LABELS => [], + FormatterOptions::DEFAULT_FIELDS => [], + ] + parent::defaultOptions(); + } +} diff --git a/vendor/consolidation/output-formatters/src/StructuredData/AssociativeList.php b/vendor/consolidation/output-formatters/src/StructuredData/AssociativeList.php new file mode 100644 index 0000000000..2a8b327bf3 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/StructuredData/AssociativeList.php @@ -0,0 +1,12 @@ +renderFunction = $renderFunction; + } + + /** + * {@inheritdoc} + */ + public function renderCell($key, $cellData, FormatterOptions $options, $rowData) + { + return call_user_func($this->renderFunction, $key, $cellData, $options, $rowData); + } +} diff --git a/vendor/consolidation/output-formatters/src/StructuredData/ConversionInterface.php b/vendor/consolidation/output-formatters/src/StructuredData/ConversionInterface.php new file mode 100644 index 0000000000..6f6447b337 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/StructuredData/ConversionInterface.php @@ -0,0 +1,15 @@ + [ ... rows of field data ... ], + * 'metadata1' => '...', + * 'metadata2' => '...', + * ] + * + * Example 2: nested metadata + * + * [ + * 'metadata' => [ ... metadata items ... ], + * 'rowid1' => [ ... ], + * 'rowid2' => [ ... ], + * ] + * + * It is, of course, also possible that both the data and + * the metadata may be nested inside subelements. + */ +trait MetadataHolderTrait +{ + protected $dataKey = false; + protected $metadataKey = false; + + public function getDataKey() + { + return $this->dataKey; + } + + public function setDataKey($key) + { + $this->dataKey = $key; + return $this; + } + + public function getMetadataKey() + { + return $this->metadataKey; + } + + public function setMetadataKey($key) + { + $this->metadataKey = $key; + return $this; + } + + public function extractData($data) + { + if ($this->metadataKey) { + unset($data[$this->metadataKey]); + } + if ($this->dataKey) { + if (!isset($data[$this->dataKey])) { + return []; + } + return $data[$this->dataKey]; + } + return $data; + } + + public function extractMetadata($data) + { + if (!$this->dataKey && !$this->metadataKey) { + return []; + } + if ($this->dataKey) { + unset($data[$this->dataKey]); + } + if ($this->metadataKey) { + if (!isset($data[$this->metadataKey])) { + return []; + } + return $data[$this->metadataKey]; + } + return $data; + } + + public function reconstruct($data, $metadata) + { + $reconstructedData = ($this->dataKey) ? [$this->dataKey => $data] : $data; + $reconstructedMetadata = ($this->metadataKey) ? [$this->metadataKey => $metadata] : $metadata; + + return $reconstructedData + $reconstructedMetadata; + } +} diff --git a/vendor/consolidation/output-formatters/src/StructuredData/MetadataInterface.php b/vendor/consolidation/output-formatters/src/StructuredData/MetadataInterface.php new file mode 100644 index 0000000000..9650820856 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/StructuredData/MetadataInterface.php @@ -0,0 +1,12 @@ +addRenderer( + * new NumericCellRenderer($data, ['value']) + * ); + * + */ +class NumericCellRenderer implements RenderCellInterface, FormatterAwareInterface +{ + use FormatterAwareTrait; + + protected $data; + protected $renderedColumns; + protected $widths = []; + + /** + * NumericCellRenderer constructor + */ + public function __construct($data, $renderedColumns) + { + $this->data = $data; + $this->renderedColumns = $renderedColumns; + } + + /** + * @inheritdoc + */ + public function renderCell($key, $cellData, FormatterOptions $options, $rowData) + { + if (!$this->isRenderedFormat($options) || !$this->isRenderedColumn($key)) { + return $cellData; + } + if ($this->isRenderedData($cellData)) { + $cellData = $this->formatCellData($cellData); + } + return $this->justifyCellData($key, $cellData); + } + + /** + * Right-justify the cell data. + */ + protected function justifyCellData($key, $cellData) + { + return str_pad($cellData, $this->columnWidth($key), " ", STR_PAD_LEFT); + } + + /** + * Determine if this format is to be formatted. + */ + protected function isRenderedFormat(FormatterOptions $options) + { + return $this->isHumanReadable(); + } + + /** + * Determine if this is a column that should be formatted. + */ + protected function isRenderedColumn($key) + { + return array_key_exists($key, $this->renderedColumns); + } + + /** + * Ignore cell data that should not be formatted. + */ + protected function isRenderedData($cellData) + { + return is_numeric($cellData); + } + + /** + * Format the cell data. + */ + protected function formatCellData($cellData) + { + return number_format($this->convertCellDataToString($cellData)); + } + + /** + * This formatter only works with columns whose columns are strings. + * To use this formatter for another purpose, override this method + * to ensure that the cell data is a string before it is formatted. + */ + protected function convertCellDataToString($cellData) + { + return $cellData; + } + + /** + * Get the cached column width for the provided key. + */ + protected function columnWidth($key) + { + if (!isset($this->widths[$key])) { + $this->widths[$key] = $this->calculateColumnWidth($key); + } + return $this->widths[$key]; + } + + /** + * Using the cached table data, calculate the largest width + * for the data in the table for use when right-justifying. + */ + protected function calculateColumnWidth($key) + { + $width = isset($this->renderedColumns[$key]) ? $this->renderedColumns[$key] : 0; + foreach ($this->data as $row) { + $data = $this->formatCellData($row[$key]); + $width = max(strlen($data), $width); + } + return $width; + } +} diff --git a/vendor/consolidation/output-formatters/src/StructuredData/OriginalDataInterface.php b/vendor/consolidation/output-formatters/src/StructuredData/OriginalDataInterface.php new file mode 100644 index 0000000000..f73856a952 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/StructuredData/OriginalDataInterface.php @@ -0,0 +1,11 @@ +defaultOptions(); + $fields = $this->getFields($options, $defaults); + if (FieldProcessor::hasUnstructuredFieldAccess($fields)) { + return new UnstructuredData($this->getArrayCopy()); + } + return $this; + } + + /** + * Restructure this data for output by converting it into a table + * transformation object. + * + * @param FormatterOptions $options Options that affect output formatting. + * @return Consolidation\OutputFormatters\Transformations\TableTransformation + */ + public function restructure(FormatterOptions $options) + { + $data = [$this->getArrayCopy()]; + $options->setConfigurationDefault('list-orientation', true); + $tableTransformer = $this->createTableTransformation($data, $options); + return $tableTransformer; + } + + public function getListData(FormatterOptions $options) + { + $data = $this->getArrayCopy(); + + $defaults = $this->defaultOptions(); + $fieldLabels = $this->getReorderedFieldLabels([$data], $options, $defaults); + + $result = []; + foreach ($fieldLabels as $id => $label) { + $result[$id] = $data[$id]; + } + return $result; + } + + protected function defaultOptions() + { + return [ + FormatterOptions::LIST_ORIENTATION => true, + ] + parent::defaultOptions(); + } + + protected function instantiateTableTransformation($data, $fieldLabels, $rowLabels) + { + return new PropertyListTableTransformation($data, $fieldLabels, $rowLabels); + } +} diff --git a/vendor/consolidation/output-formatters/src/StructuredData/RenderCellCollectionInterface.php b/vendor/consolidation/output-formatters/src/StructuredData/RenderCellCollectionInterface.php new file mode 100644 index 0000000000..f88573d8b4 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/StructuredData/RenderCellCollectionInterface.php @@ -0,0 +1,18 @@ + [], + RenderCellCollectionInterface::PRIORITY_NORMAL => [], + RenderCellCollectionInterface::PRIORITY_FALLBACK => [], + ]; + + /** + * Add a renderer + * + * @return $this + */ + public function addRenderer(RenderCellInterface $renderer, $priority = RenderCellCollectionInterface::PRIORITY_NORMAL) + { + $this->rendererList[$priority][] = $renderer; + return $this; + } + + /** + * Add a callable as a renderer + * + * @return $this + */ + public function addRendererFunction(callable $rendererFn, $priority = RenderCellCollectionInterface::PRIORITY_NORMAL) + { + $renderer = new CallableRenderer($rendererFn); + return $this->addRenderer($renderer, $priority); + } + + /** + * {@inheritdoc} + */ + public function renderCell($key, $cellData, FormatterOptions $options, $rowData) + { + $flattenedRendererList = array_reduce( + $this->rendererList, + function ($carry, $item) { + return array_merge($carry, $item); + }, + [] + ); + + foreach ($flattenedRendererList as $renderer) { + if ($renderer instanceof FormatterAwareInterface) { + $renderer->setFormatter($this->getFormatter()); + } + $cellData = $renderer->renderCell($key, $cellData, $options, $rowData); + } + return $cellData; + } +} diff --git a/vendor/consolidation/output-formatters/src/StructuredData/RenderCellInterface.php b/vendor/consolidation/output-formatters/src/StructuredData/RenderCellInterface.php new file mode 100644 index 0000000000..ff200846c4 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/StructuredData/RenderCellInterface.php @@ -0,0 +1,22 @@ +defaultOptions(); + $fields = $this->getFields($options, $defaults); + if (FieldProcessor::hasUnstructuredFieldAccess($fields)) { + return new UnstructuredListData($this->getArrayCopy()); + } + return $this; + } + + /** + * Restructure this data for output by converting it into a table + * transformation object. + * + * @param FormatterOptions $options Options that affect output formatting. + * @return Consolidation\OutputFormatters\Transformations\TableTransformation + */ + public function restructure(FormatterOptions $options) + { + $data = $this->getArrayCopy(); + return $this->createTableTransformation($data, $options); + } + + public function getListData(FormatterOptions $options) + { + return array_keys($this->getArrayCopy()); + } + + protected function defaultOptions() + { + return [ + FormatterOptions::LIST_ORIENTATION => false, + ] + parent::defaultOptions(); + } +} diff --git a/vendor/consolidation/output-formatters/src/StructuredData/RowsOfFieldsWithMetadata.php b/vendor/consolidation/output-formatters/src/StructuredData/RowsOfFieldsWithMetadata.php new file mode 100644 index 0000000000..98aedbde58 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/StructuredData/RowsOfFieldsWithMetadata.php @@ -0,0 +1,39 @@ +getArrayCopy(); + $data = $this->extractData($originalData); + $tableTranformer = $this->createTableTransformation($data, $options); + $tableTranformer->setOriginalData($this); + return $tableTranformer; + } + + public function getMetadata() + { + return $this->extractMetadata($this->getArrayCopy()); + } +} diff --git a/vendor/consolidation/output-formatters/src/StructuredData/TableDataInterface.php b/vendor/consolidation/output-formatters/src/StructuredData/TableDataInterface.php new file mode 100644 index 0000000000..98daa09c77 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/StructuredData/TableDataInterface.php @@ -0,0 +1,16 @@ +defaultOptions(); + $fields = $this->getFields($options, $defaults); + + return new UnstructuredDataTransformation($this->getArrayCopy(), FieldProcessor::processFieldAliases($fields)); + } +} diff --git a/vendor/consolidation/output-formatters/src/StructuredData/UnstructuredInterface.php b/vendor/consolidation/output-formatters/src/StructuredData/UnstructuredInterface.php new file mode 100644 index 0000000000..e064a66fc3 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/StructuredData/UnstructuredInterface.php @@ -0,0 +1,14 @@ +defaultOptions(); + $fields = $this->getFields($options, $defaults); + + return new UnstructuredDataListTransformation($this->getArrayCopy(), FieldProcessor::processFieldAliases($fields)); + } +} diff --git a/vendor/consolidation/output-formatters/src/StructuredData/Xml/DomDataInterface.php b/vendor/consolidation/output-formatters/src/StructuredData/Xml/DomDataInterface.php new file mode 100644 index 0000000000..239ea7b6ba --- /dev/null +++ b/vendor/consolidation/output-formatters/src/StructuredData/Xml/DomDataInterface.php @@ -0,0 +1,12 @@ + ['description'], + ]; + $this->elementList = array_merge_recursive($elementList, $defaultElementList); + } + + public function arrayToXML($structuredData) + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $topLevelElement = $this->getTopLevelElementName($structuredData); + $this->addXmlData($dom, $dom, $topLevelElement, $structuredData); + return $dom; + } + + protected function addXmlData(\DOMDocument $dom, $xmlParent, $elementName, $structuredData) + { + $element = $dom->createElement($elementName); + $xmlParent->appendChild($element); + if (is_string($structuredData)) { + $element->appendChild($dom->createTextNode($structuredData)); + return; + } + $this->addXmlChildren($dom, $element, $elementName, $structuredData); + } + + protected function addXmlChildren(\DOMDocument $dom, $xmlParent, $elementName, $structuredData) + { + foreach ($structuredData as $key => $value) { + $this->addXmlDataOrAttribute($dom, $xmlParent, $elementName, $key, $value); + } + } + + protected function addXmlDataOrAttribute(\DOMDocument $dom, $xmlParent, $elementName, $key, $value) + { + $childElementName = $this->getDefaultElementName($elementName); + $elementName = $this->determineElementName($key, $childElementName, $value); + if (($elementName != $childElementName) && $this->isAttribute($elementName, $key, $value)) { + $xmlParent->setAttribute($key, $value); + return; + } + $this->addXmlData($dom, $xmlParent, $elementName, $value); + } + + protected function determineElementName($key, $childElementName, $value) + { + if (is_numeric($key)) { + return $childElementName; + } + if (is_object($value)) { + $value = (array)$value; + } + if (!is_array($value)) { + return $key; + } + if (array_key_exists('id', $value) && ($value['id'] == $key)) { + return $childElementName; + } + if (array_key_exists('name', $value) && ($value['name'] == $key)) { + return $childElementName; + } + return $key; + } + + protected function getTopLevelElementName($structuredData) + { + return 'document'; + } + + protected function getDefaultElementName($parentElementName) + { + $singularName = $this->singularForm($parentElementName); + if (isset($singularName)) { + return $singularName; + } + return 'item'; + } + + protected function isAttribute($parentElementName, $elementName, $value) + { + if (!is_string($value)) { + return false; + } + return !$this->inElementList($parentElementName, $elementName) && !$this->inElementList('*', $elementName); + } + + protected function inElementList($parentElementName, $elementName) + { + if (!array_key_exists($parentElementName, $this->elementList)) { + return false; + } + return in_array($elementName, $this->elementList[$parentElementName]); + } + + protected function singularForm($name) + { + if (substr($name, strlen($name) - 1) == "s") { + return substr($name, 0, strlen($name) - 1); + } + } + + protected function isAssoc($data) + { + return array_keys($data) == range(0, count($data)); + } +} diff --git a/vendor/consolidation/output-formatters/src/StructuredData/Xml/XmlSchemaInterface.php b/vendor/consolidation/output-formatters/src/StructuredData/Xml/XmlSchemaInterface.php new file mode 100644 index 0000000000..a1fad66291 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/StructuredData/Xml/XmlSchemaInterface.php @@ -0,0 +1,73 @@ + + * + * + * blah + * + * + * a + * b + * c + * + * + * + * + * + * + * This could be: + * + * [ + * 'id' => 1, + * 'name' => 'doc', + * 'foobars' => + * [ + * [ + * 'id' => '123', + * 'name' => 'blah', + * 'widgets' => + * [ + * [ + * 'foo' => 'a', + * 'bar' => 'b', + * 'baz' => 'c', + * ] + * ], + * ], + * ] + * ] + * + * The challenge is more in going from an array back to the more + * structured xml format. Note that any given key => string mapping + * could represent either an attribute, or a simple XML element + * containing only a string value. In general, we do *not* want to add + * extra layers of nesting in the data structure to disambiguate between + * these kinds of data, as we want the source data to render cleanly + * into other formats, e.g. yaml, json, et. al., and we do not want to + * force every data provider to have to consider the optimal xml schema + * for their data. + * + * Our strategy, therefore, is to expect clients that wish to provide + * a very specific xml representation to return a DOMDocument, and, + * for other data structures where xml is a secondary concern, then we + * will use some default heuristics to convert from arrays to xml. + */ +interface XmlSchemaInterface +{ + /** + * Convert data to a format suitable for use in a list. + * By default, the array values will be used. Implement + * ListDataInterface to use some other criteria (e.g. array keys). + * + * @return \DOMDocument + */ + public function arrayToXml($structuredData); +} diff --git a/vendor/consolidation/output-formatters/src/Transformations/DomToArraySimplifier.php b/vendor/consolidation/output-formatters/src/Transformations/DomToArraySimplifier.php new file mode 100644 index 0000000000..e7a6c8794d --- /dev/null +++ b/vendor/consolidation/output-formatters/src/Transformations/DomToArraySimplifier.php @@ -0,0 +1,237 @@ +isSubclassOf('\Consolidation\OutputFormatters\StructuredData\Xml\DomDataInterface') || + $dataType->isSubclassOf('DOMDocument') || + ($dataType->getName() == 'DOMDocument'); + } + + public function simplifyToArray($structuredData, FormatterOptions $options) + { + if ($structuredData instanceof DomDataInterface) { + $structuredData = $structuredData->getDomData(); + } + if ($structuredData instanceof \DOMDocument) { + // $schema = $options->getXmlSchema(); + $simplified = $this->elementToArray($structuredData); + $structuredData = array_shift($simplified); + } + return $structuredData; + } + + /** + * Recursively convert the provided DOM element into a php array. + * + * @param \DOMNode $element + * @return array + */ + protected function elementToArray(\DOMNode $element) + { + if ($element->nodeType == XML_TEXT_NODE) { + return $element->nodeValue; + } + $attributes = $this->getNodeAttributes($element); + $children = $this->getNodeChildren($element); + + return array_merge($attributes, $children); + } + + /** + * Get all of the attributes of the provided element. + * + * @param \DOMNode $element + * @return array + */ + protected function getNodeAttributes($element) + { + if (empty($element->attributes)) { + return []; + } + $attributes = []; + foreach ($element->attributes as $key => $attribute) { + $attributes[$key] = $attribute->nodeValue; + } + return $attributes; + } + + /** + * Get all of the children of the provided element, with simplification. + * + * @param \DOMNode $element + * @return array + */ + protected function getNodeChildren($element) + { + if (empty($element->childNodes)) { + return []; + } + $uniformChildrenName = $this->hasUniformChildren($element); + // Check for plurals. + if (in_array($element->nodeName, ["{$uniformChildrenName}s", "{$uniformChildrenName}es"])) { + $result = $this->getUniformChildren($element->nodeName, $element); + } else { + $result = $this->getUniqueChildren($element->nodeName, $element); + } + return array_filter($result); + } + + /** + * Get the data from the children of the provided node in preliminary + * form. + * + * @param \DOMNode $element + * @return array + */ + protected function getNodeChildrenData($element) + { + $children = []; + foreach ($element->childNodes as $key => $value) { + $children[$key] = $this->elementToArray($value); + } + return $children; + } + + /** + * Determine whether the children of the provided element are uniform. + * @see getUniformChildren(), below. + * + * @param \DOMNode $element + * @return boolean + */ + protected function hasUniformChildren($element) + { + $last = false; + foreach ($element->childNodes as $key => $value) { + $name = $value->nodeName; + if (!$name) { + return false; + } + if ($last && ($name != $last)) { + return false; + } + $last = $name; + } + return $last; + } + + /** + * Convert the children of the provided DOM element into an array. + * Here, 'uniform' means that all of the element names of the children + * are identical, and further, the element name of the parent is the + * plural form of the child names. When the children are uniform in + * this way, then the parent element name will be used as the key to + * store the children in, and the child list will be returned as a + * simple list with their (duplicate) element names omitted. + * + * @param string $parentKey + * @param \DOMNode $element + * @return array + */ + protected function getUniformChildren($parentKey, $element) + { + $children = $this->getNodeChildrenData($element); + $simplifiedChildren = []; + foreach ($children as $key => $value) { + if ($this->valueCanBeSimplified($value)) { + $value = array_shift($value); + } + $id = $this->getIdOfValue($value); + if ($id) { + $simplifiedChildren[$parentKey][$id] = $value; + } else { + $simplifiedChildren[$parentKey][] = $value; + } + } + return $simplifiedChildren; + } + + /** + * Determine whether the provided value has additional unnecessary + * nesting. {"color": "red"} is converted to "red". No other + * simplification is done. + * + * @param \DOMNode $value + * @return boolean + */ + protected function valueCanBeSimplified($value) + { + if (!is_array($value)) { + return false; + } + if (count($value) != 1) { + return false; + } + $data = array_shift($value); + return is_string($data); + } + + /** + * If the object has an 'id' or 'name' element, then use that + * as the array key when storing this value in its parent. + * @param mixed $value + * @return string + */ + protected function getIdOfValue($value) + { + if (!is_array($value)) { + return false; + } + if (array_key_exists('id', $value)) { + return trim($value['id'], '-'); + } + if (array_key_exists('name', $value)) { + return trim($value['name'], '-'); + } + } + + /** + * Convert the children of the provided DOM element into an array. + * Here, 'unique' means that all of the element names of the children are + * different. Since the element names will become the key of the + * associative array that is returned, so duplicates are not supported. + * If there are any duplicates, then an exception will be thrown. + * + * @param string $parentKey + * @param \DOMNode $element + * @return array + */ + protected function getUniqueChildren($parentKey, $element) + { + $children = $this->getNodeChildrenData($element); + if ((count($children) == 1) && (is_string($children[0]))) { + return [$element->nodeName => $children[0]]; + } + $simplifiedChildren = []; + foreach ($children as $key => $value) { + if (is_numeric($key) && is_array($value) && (count($value) == 1)) { + $valueKeys = array_keys($value); + $key = $valueKeys[0]; + $value = array_shift($value); + } + if (array_key_exists($key, $simplifiedChildren)) { + throw new \Exception("Cannot convert data from a DOM document to an array, because <$key> appears more than once, and is not wrapped in a <{$key}s> element."); + } + $simplifiedChildren[$key] = $value; + } + return $simplifiedChildren; + } +} diff --git a/vendor/consolidation/output-formatters/src/Transformations/OverrideRestructureInterface.php b/vendor/consolidation/output-formatters/src/Transformations/OverrideRestructureInterface.php new file mode 100644 index 0000000000..51a6ea8720 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/Transformations/OverrideRestructureInterface.php @@ -0,0 +1,17 @@ +getArrayCopy(); + return $data[0]; + } +} diff --git a/vendor/consolidation/output-formatters/src/Transformations/PropertyParser.php b/vendor/consolidation/output-formatters/src/Transformations/PropertyParser.php new file mode 100644 index 0000000000..ec1616afb3 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/Transformations/PropertyParser.php @@ -0,0 +1,38 @@ + 'red', + * 'two' => 'white', + * 'three' => 'blue', + * ] + */ +class PropertyParser +{ + public static function parse($data) + { + if (!is_string($data)) { + return $data; + } + $result = []; + $lines = explode("\n", $data); + foreach ($lines as $line) { + list($key, $value) = explode(':', trim($line), 2) + ['', '']; + if (!empty($key) && !empty($value)) { + $result[$key] = trim($value); + } + } + return $result; + } +} diff --git a/vendor/consolidation/output-formatters/src/Transformations/ReorderFields.php b/vendor/consolidation/output-formatters/src/Transformations/ReorderFields.php new file mode 100644 index 0000000000..40d111d58f --- /dev/null +++ b/vendor/consolidation/output-formatters/src/Transformations/ReorderFields.php @@ -0,0 +1,129 @@ +getSelectedFieldKeys($fields, $fieldLabels); + if (empty($fields)) { + return array_intersect_key($fieldLabels, $firstRow); + } + return $this->reorderFieldLabels($fields, $fieldLabels, $data); + } + + protected function reorderFieldLabels($fields, $fieldLabels, $data) + { + $result = []; + $firstRow = reset($data); + if (!$firstRow) { + $firstRow = $fieldLabels; + } + foreach ($fields as $field) { + if (array_key_exists($field, $firstRow)) { + if (array_key_exists($field, $fieldLabels)) { + $result[$field] = $fieldLabels[$field]; + } + } + } + return $result; + } + + protected function getSelectedFieldKeys($fields, $fieldLabels) + { + if (empty($fieldLabels)) { + return []; + } + if (is_string($fields)) { + $fields = explode(',', $fields); + } + $selectedFields = []; + foreach ($fields as $field) { + $matchedFields = $this->matchFieldInLabelMap($field, $fieldLabels); + if (empty($matchedFields)) { + throw new UnknownFieldException($field); + } + $selectedFields = array_merge($selectedFields, $matchedFields); + } + return $selectedFields; + } + + protected function matchFieldInLabelMap($field, $fieldLabels) + { + $fieldRegex = $this->convertToRegex($field); + return + array_filter( + array_keys($fieldLabels), + function ($key) use ($fieldRegex, $fieldLabels) { + $value = $fieldLabels[$key]; + return preg_match($fieldRegex, $value) || preg_match($fieldRegex, $key); + } + ); + } + + /** + * Convert the provided string into a regex suitable for use in + * preg_match. + * + * Matching occurs in the same way as the Symfony Finder component: + * http://symfony.com/doc/current/components/finder.html#file-name + */ + protected function convertToRegex($str) + { + return $this->isRegex($str) ? $str : Glob::toRegex($str); + } + + /** + * Checks whether the string is a regex. This function is copied from + * MultiplePcreFilterIterator in the Symfony Finder component. + * + * @param string $str + * + * @return bool Whether the given string is a regex + */ + protected function isRegex($str) + { + if (preg_match('/^(.{3,}?)[imsxuADU]*$/', $str, $m)) { + $start = substr($m[1], 0, 1); + $end = substr($m[1], -1); + + if ($start === $end) { + return !preg_match('/[*?[:alnum:] \\\\]/', $start); + } + + foreach (array(array('{', '}'), array('(', ')'), array('[', ']'), array('<', '>')) as $delimiters) { + if ($start === $delimiters[0] && $end === $delimiters[1]) { + return true; + } + } + } + + return false; + } +} diff --git a/vendor/consolidation/output-formatters/src/Transformations/SimplifyToArrayInterface.php b/vendor/consolidation/output-formatters/src/Transformations/SimplifyToArrayInterface.php new file mode 100644 index 0000000000..7b088ae851 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/Transformations/SimplifyToArrayInterface.php @@ -0,0 +1,26 @@ +headers = $fieldLabels; + $this->rowLabels = $rowLabels; + $rows = static::transformRows($data, $fieldLabels); + $this->layout = self::TABLE_LAYOUT; + parent::__construct($rows); + } + + public function setLayout($layout) + { + $this->layout = $layout; + } + + public function getLayout() + { + return $this->layout; + } + + public function isList() + { + return $this->layout == self::LIST_LAYOUT; + } + + /** + * @inheritdoc + */ + public function simplifyToString(FormatterOptions $options) + { + $alternateFormatter = new TsvFormatter(); + $output = new BufferedOutput(); + + try { + $data = $alternateFormatter->validate($this->getArrayCopy()); + $alternateFormatter->write($output, $this->getArrayCopy(), $options); + } catch (\Exception $e) { + } + return $output->fetch(); + } + + protected static function transformRows($data, $fieldLabels) + { + $rows = []; + foreach ($data as $rowid => $row) { + $rows[$rowid] = static::transformRow($row, $fieldLabels); + } + return $rows; + } + + protected static function transformRow($row, $fieldLabels) + { + $result = []; + foreach ($fieldLabels as $key => $label) { + $result[$key] = array_key_exists($key, $row) ? $row[$key] : ''; + } + return $result; + } + + public function getHeaders() + { + return $this->headers; + } + + public function getHeader($key) + { + if (array_key_exists($key, $this->headers)) { + return $this->headers[$key]; + } + return $key; + } + + public function getRowLabels() + { + return $this->rowLabels; + } + + public function getRowLabel($rowid) + { + if (array_key_exists($rowid, $this->rowLabels)) { + return $this->rowLabels[$rowid]; + } + return $rowid; + } + + public function getOriginalData() + { + if (isset($this->originalData)) { + return $this->originalData->reconstruct($this->getArrayCopy(), $this->originalData->getMetadata()); + } + return $this->getArrayCopy(); + } + + public function setOriginalData(MetadataHolderInterface $data) + { + $this->originalData = $data; + } + + public function getTableData($includeRowKey = false) + { + $data = $this->getArrayCopy(); + if ($this->isList()) { + $data = $this->convertTableToList(); + } + if ($includeRowKey) { + $data = $this->getRowDataWithKey($data); + } + return $data; + } + + protected function convertTableToList() + { + $result = []; + foreach ($this as $row) { + foreach ($row as $key => $value) { + $result[$key][] = $value; + } + } + return $result; + } + + protected function getRowDataWithKey($data) + { + $result = []; + $i = 0; + foreach ($data as $key => $row) { + array_unshift($row, $this->getHeader($key)); + $i++; + $result[$key] = $row; + } + return $result; + } +} diff --git a/vendor/consolidation/output-formatters/src/Transformations/UnstructuredDataFieldAccessor.php b/vendor/consolidation/output-formatters/src/Transformations/UnstructuredDataFieldAccessor.php new file mode 100644 index 0000000000..8ef7f12b5b --- /dev/null +++ b/vendor/consolidation/output-formatters/src/Transformations/UnstructuredDataFieldAccessor.php @@ -0,0 +1,36 @@ +data = $data; + } + + public function get($fields) + { + $data = new Data($this->data); + $result = new Data(); + foreach ($fields as $key => $label) { + $item = $data->get($key); + if (isset($item)) { + if ($label == '.') { + if (!is_array($item)) { + return $item; + } + foreach ($item as $key => $value) { + $result->set($key, $value); + } + } else { + $result->set($label, $data->get($key)); + } + } + } + return $result->export(); + } +} diff --git a/vendor/consolidation/output-formatters/src/Transformations/UnstructuredDataListTransformation.php b/vendor/consolidation/output-formatters/src/Transformations/UnstructuredDataListTransformation.php new file mode 100644 index 0000000000..b84a0ebf52 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/Transformations/UnstructuredDataListTransformation.php @@ -0,0 +1,38 @@ +originalData = $data; + $rows = static::transformRows($data, $fields); + parent::__construct($rows); + } + + protected static function transformRows($data, $fields) + { + $rows = []; + foreach ($data as $rowid => $row) { + $rows[$rowid] = UnstructuredDataTransformation::transformRow($row, $fields); + } + return $rows; + } + + public function simplifyToString(FormatterOptions $options) + { + $result = ''; + $iterator = $this->getIterator(); + while ($iterator->valid()) { + $simplifiedRow = UnstructuredDataTransformation::simplifyRow($iterator->current()); + if (isset($simplifiedRow)) { + $result .= "$simplifiedRow\n"; + } + + $iterator->next(); + } + return $result; + } +} diff --git a/vendor/consolidation/output-formatters/src/Transformations/UnstructuredDataTransformation.php b/vendor/consolidation/output-formatters/src/Transformations/UnstructuredDataTransformation.php new file mode 100644 index 0000000000..c1bfd508e5 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/Transformations/UnstructuredDataTransformation.php @@ -0,0 +1,52 @@ +originalData = $data; + $rows = static::transformRow($data, $fields); + parent::__construct($rows); + } + + public function simplifyToString(FormatterOptions $options) + { + return static::simplifyRow($this->getArrayCopy()); + } + + public static function transformRow($row, $fields) + { + if (empty($fields)) { + return $row; + } + $fieldAccessor = new UnstructuredDataFieldAccessor($row); + return $fieldAccessor->get($fields); + } + + public static function simplifyRow($row) + { + if (is_string($row)) { + return $row; + } + if (static::isSimpleArray($row)) { + return implode("\n", $row); + } + // No good way to simplify - just dump a json fragment + return json_encode($row); + } + + protected static function isSimpleArray($row) + { + foreach ($row as $item) { + if (!is_string($item)) { + return false; + } + } + return true; + } +} diff --git a/vendor/consolidation/output-formatters/src/Transformations/WordWrapper.php b/vendor/consolidation/output-formatters/src/Transformations/WordWrapper.php new file mode 100644 index 0000000000..36f9b88d04 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/Transformations/WordWrapper.php @@ -0,0 +1,122 @@ +width = $width; + $this->minimumWidths = new ColumnWidths(); + } + + /** + * Calculate our padding widths from the specified table style. + * @param TableStyle $style + */ + public function setPaddingFromStyle(TableStyle $style) + { + $verticalBorderLen = strlen(sprintf($style->getBorderFormat(), $style->getVerticalBorderChar())); + $paddingLen = strlen($style->getPaddingChar()); + + $this->extraPaddingAtBeginningOfLine = 0; + $this->extraPaddingAtEndOfLine = $verticalBorderLen; + $this->paddingInEachCell = $verticalBorderLen + $paddingLen + 1; + } + + /** + * If columns have minimum widths, then set them here. + * @param array $minimumWidths + */ + public function setMinimumWidths($minimumWidths) + { + $this->minimumWidths = new ColumnWidths($minimumWidths); + } + + /** + * Set the minimum width of just one column + */ + public function minimumWidth($colkey, $width) + { + $this->minimumWidths->setWidth($colkey, $width); + } + + /** + * Wrap the cells in each part of the provided data table + * @param array $rows + * @return array + */ + public function wrap($rows, $widths = []) + { + $auto_widths = $this->calculateWidths($rows, $widths); + + // If no widths were provided, then disable wrapping + if ($auto_widths->isEmpty()) { + return $rows; + } + + // Do wordwrap on all cells. + $newrows = array(); + foreach ($rows as $rowkey => $row) { + foreach ($row as $colkey => $cell) { + $newrows[$rowkey][$colkey] = $this->wrapCell($cell, $auto_widths->width($colkey)); + } + } + + return $newrows; + } + + /** + * Determine what widths we'll use for wrapping. + */ + protected function calculateWidths($rows, $widths = []) + { + // Widths must be provided in some form or another, or we won't wrap. + if (empty($widths) && !$this->width) { + return new ColumnWidths(); + } + + // Technically, `$widths`, if provided here, should be used + // as the exact widths to wrap to. For now we'll just treat + // these as minimum widths + $minimumWidths = $this->minimumWidths->combine(new ColumnWidths($widths)); + + $calculator = new CalculateWidths(); + $dataCellWidths = $calculator->calculateLongestCell($rows); + + $availableWidth = $this->width - $dataCellWidths->paddingSpace($this->paddingInEachCell, $this->extraPaddingAtEndOfLine, $this->extraPaddingAtBeginningOfLine); + + $this->minimumWidths->adjustMinimumWidths($availableWidth, $dataCellWidths); + + return $calculator->calculate($availableWidth, $dataCellWidths, $minimumWidths); + } + + /** + * Wrap one cell. Guard against modifying non-strings and + * then call through to wordwrap(). + * + * @param mixed $cell + * @param string $cellWidth + * @return mixed + */ + protected function wrapCell($cell, $cellWidth) + { + if (!is_string($cell)) { + return $cell; + } + return wordwrap($cell, $cellWidth, "\n", true); + } +} diff --git a/vendor/consolidation/output-formatters/src/Transformations/Wrap/CalculateWidths.php b/vendor/consolidation/output-formatters/src/Transformations/Wrap/CalculateWidths.php new file mode 100644 index 0000000000..04af092082 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/Transformations/Wrap/CalculateWidths.php @@ -0,0 +1,141 @@ +totalWidth() <= $availableWidth) { + return $dataWidths->enforceMinimums($minimumWidths); + } + + // Get the short columns first. If there are none, then distribute all + // of the available width among the remaining columns. + $shortColWidths = $this->getShortColumns($availableWidth, $dataWidths, $minimumWidths); + if ($shortColWidths->isEmpty()) { + return $this->distributeLongColumns($availableWidth, $dataWidths, $minimumWidths); + } + + // If some short columns were removed, then account for the length + // of the removed columns and make a recursive call (since the average + // width may be higher now, if the removed columns were shorter in + // length than the previous average). + $availableWidth -= $shortColWidths->totalWidth(); + $remainingWidths = $dataWidths->removeColumns($shortColWidths->keys()); + $remainingColWidths = $this->calculate($availableWidth, $remainingWidths, $minimumWidths); + + return $shortColWidths->combine($remainingColWidths); + } + + /** + * Calculate the longest cell data from any row of each of the cells. + */ + public function calculateLongestCell($rows) + { + return $this->calculateColumnWidths( + $rows, + function ($cell) { + return strlen($cell); + } + ); + } + + /** + * Calculate the longest word and longest line in the provided data. + */ + public function calculateLongestWord($rows) + { + return $this->calculateColumnWidths( + $rows, + function ($cell) { + return static::longestWordLength($cell); + } + ); + } + + protected function calculateColumnWidths($rows, callable $fn) + { + $widths = []; + + // Examine each row and find the longest line length and longest + // word in each column. + foreach ($rows as $rowkey => $row) { + foreach ($row as $colkey => $cell) { + $value = $fn($cell); + if ((!isset($widths[$colkey]) || ($widths[$colkey] < $value))) { + $widths[$colkey] = $value; + } + } + } + + return new ColumnWidths($widths); + } + + /** + * Return all of the columns whose longest line length is less than or + * equal to the average width. + */ + public function getShortColumns($availableWidth, ColumnWidths $dataWidths, ColumnWidths $minimumWidths) + { + $averageWidth = $dataWidths->averageWidth($availableWidth); + $shortColWidths = $dataWidths->findShortColumns($averageWidth); + return $shortColWidths->enforceMinimums($minimumWidths); + } + + /** + * Distribute the remainig space among the columns that were not + * included in the list of "short" columns. + */ + public function distributeLongColumns($availableWidth, ColumnWidths $dataWidths, ColumnWidths $minimumWidths) + { + // First distribute the remainder without regard to the minimum widths. + $result = $dataWidths->distribute($availableWidth); + + // Find columns that are shorter than their minimum width. + $undersized = $result->findUndersizedColumns($minimumWidths); + + // Nothing too small? Great, we're done! + if ($undersized->isEmpty()) { + return $result; + } + + // Take out the columns that are too small and redistribute the rest. + $availableWidth -= $undersized->totalWidth(); + $remaining = $dataWidths->removeColumns($undersized->keys()); + $distributeRemaining = $this->distributeLongColumns($availableWidth, $remaining, $minimumWidths); + + return $undersized->combine($distributeRemaining); + } + + /** + * Return the length of the longest word in the string. + * @param string $str + * @return int + */ + protected static function longestWordLength($str) + { + $words = preg_split('#[ /-]#', $str); + $lengths = array_map(function ($s) { + return strlen($s); + }, $words); + return max($lengths); + } +} diff --git a/vendor/consolidation/output-formatters/src/Transformations/Wrap/ColumnWidths.php b/vendor/consolidation/output-formatters/src/Transformations/Wrap/ColumnWidths.php new file mode 100644 index 0000000000..6995b6afb9 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/Transformations/Wrap/ColumnWidths.php @@ -0,0 +1,264 @@ +widths = $widths; + } + + public function paddingSpace( + $paddingInEachCell, + $extraPaddingAtEndOfLine = 0, + $extraPaddingAtBeginningOfLine = 0 + ) { + return ($extraPaddingAtBeginningOfLine + $extraPaddingAtEndOfLine + (count($this->widths) * $paddingInEachCell)); + } + + /** + * Find all of the columns that are shorter than the specified threshold. + */ + public function findShortColumns($thresholdWidth) + { + $thresholdWidths = array_fill_keys(array_keys($this->widths), $thresholdWidth); + + return $this->findColumnsUnderThreshold($thresholdWidths); + } + + /** + * Find all of the columns that are shorter than the corresponding minimum widths. + */ + public function findUndersizedColumns($minimumWidths) + { + return $this->findColumnsUnderThreshold($minimumWidths->widths()); + } + + protected function findColumnsUnderThreshold(array $thresholdWidths) + { + $shortColWidths = []; + foreach ($this->widths as $key => $maxLength) { + if (isset($thresholdWidths[$key]) && ($maxLength <= $thresholdWidths[$key])) { + $shortColWidths[$key] = $maxLength; + } + } + + return new ColumnWidths($shortColWidths); + } + + /** + * If the widths specified by this object do not fit within the + * provided avaiable width, then reduce them all proportionally. + */ + public function adjustMinimumWidths($availableWidth, $dataCellWidths) + { + $result = $this->selectColumns($dataCellWidths->keys()); + if ($result->isEmpty()) { + return $result; + } + $numberOfColumns = $dataCellWidths->count(); + + // How many unspecified columns are there? + $unspecifiedColumns = $numberOfColumns - $result->count(); + $averageWidth = $this->averageWidth($availableWidth); + + // Reserve some space for the columns that have no minimum. + // Make sure they collectively get at least half of the average + // width for each column. Or should it be a quarter? + $reservedSpacePerColumn = ($averageWidth / 2); + $reservedSpace = $reservedSpacePerColumn * $unspecifiedColumns; + + // Calculate how much of the available space is remaining for use by + // the minimum column widths after the reserved space is accounted for. + $remainingAvailable = $availableWidth - $reservedSpace; + + // Don't do anything if our widths fit inside the available widths. + if ($result->totalWidth() <= $remainingAvailable) { + return $result; + } + + // Shrink the minimum widths if the table is too compressed. + return $result->distribute($remainingAvailable); + } + + /** + * Return proportional weights + */ + public function distribute($availableWidth) + { + $result = []; + $totalWidth = $this->totalWidth(); + $lastColumn = $this->lastColumn(); + $widths = $this->widths(); + + // Take off the last column, and calculate proportional weights + // for the first N-1 columns. + array_pop($widths); + foreach ($widths as $key => $width) { + $result[$key] = round(($width / $totalWidth) * $availableWidth); + } + + // Give the last column the rest of the available width + $usedWidth = $this->sumWidth($result); + $result[$lastColumn] = $availableWidth - $usedWidth; + + return new ColumnWidths($result); + } + + public function lastColumn() + { + $keys = $this->keys(); + return array_pop($keys); + } + + /** + * Return the number of columns. + */ + public function count() + { + return count($this->widths); + } + + /** + * Calculate how much space is available on average for all columns. + */ + public function averageWidth($availableWidth) + { + if ($this->isEmpty()) { + debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + } + return $availableWidth / $this->count(); + } + + /** + * Return the available keys (column identifiers) from the calculated + * data set. + */ + public function keys() + { + return array_keys($this->widths); + } + + /** + * Set the length of the specified column. + */ + public function setWidth($key, $width) + { + $this->widths[$key] = $width; + } + + /** + * Return the length of the specified column. + */ + public function width($key) + { + return isset($this->widths[$key]) ? $this->widths[$key] : 0; + } + + /** + * Return all of the lengths + */ + public function widths() + { + return $this->widths; + } + + /** + * Return true if there is no data in this object + */ + public function isEmpty() + { + return empty($this->widths); + } + + /** + * Return the sum of the lengths of the provided widths. + */ + public function totalWidth() + { + return static::sumWidth($this->widths()); + } + + /** + * Return the sum of the lengths of the provided widths. + */ + public static function sumWidth($widths) + { + return array_reduce( + $widths, + function ($carry, $item) { + return $carry + $item; + } + ); + } + + /** + * Ensure that every item in $widths that has a corresponding entry + * in $minimumWidths is as least as large as the minimum value held there. + */ + public function enforceMinimums($minimumWidths) + { + $result = []; + if ($minimumWidths instanceof ColumnWidths) { + $minimumWidths = $minimumWidths->widths(); + } + $minimumWidths += $this->widths; + + foreach ($this->widths as $key => $value) { + $result[$key] = max($value, $minimumWidths[$key]); + } + + return new ColumnWidths($result); + } + + /** + * Remove all of the specified columns from this data structure. + */ + public function removeColumns($columnKeys) + { + $widths = $this->widths(); + + foreach ($columnKeys as $key) { + unset($widths[$key]); + } + + return new ColumnWidths($widths); + } + + /** + * Select all columns that exist in the provided list of keys. + */ + public function selectColumns($columnKeys) + { + $widths = []; + + foreach ($columnKeys as $key) { + if (isset($this->widths[$key])) { + $widths[$key] = $this->width($key); + } + } + + return new ColumnWidths($widths); + } + + /** + * Combine this set of widths with another set, and return + * a new set that contains the entries from both. + */ + public function combine(ColumnWidths $combineWith) + { + // Danger: array_merge renumbers numeric keys; that must not happen here. + $combined = $combineWith->widths(); + foreach ($this->widths() as $key => $value) { + $combined[$key] = $value; + } + return new ColumnWidths($combined); + } +} diff --git a/vendor/consolidation/output-formatters/src/Validate/ValidDataTypesInterface.php b/vendor/consolidation/output-formatters/src/Validate/ValidDataTypesInterface.php new file mode 100644 index 0000000000..88cce5314b --- /dev/null +++ b/vendor/consolidation/output-formatters/src/Validate/ValidDataTypesInterface.php @@ -0,0 +1,28 @@ +validDataTypes(), + function ($carry, $supportedType) use ($dataType) { + return + $carry || + ($dataType->getName() == $supportedType->getName()) || + ($dataType->isSubclassOf($supportedType->getName())); + }, + false + ); + } +} diff --git a/vendor/consolidation/output-formatters/src/Validate/ValidationInterface.php b/vendor/consolidation/output-formatters/src/Validate/ValidationInterface.php new file mode 100644 index 0000000000..ad43626c02 --- /dev/null +++ b/vendor/consolidation/output-formatters/src/Validate/ValidationInterface.php @@ -0,0 +1,28 @@ + ['Name', ':', 'Rex', ], + 'species' => ['Species', ':', 'dog', ], + 'food' => ['Food', ':', 'kibble', ], + 'legs' => ['Legs', ':', '4', ], + 'description' => ['Description', ':', 'Rex is a very good dog, Brett. He likes kibble, and has four legs.', ], +]; + +$result = $wrapper->wrap($data); + +var_export($result); + diff --git a/vendor/dflydev/dot-access-data/.gitignore b/vendor/dflydev/dot-access-data/.gitignore new file mode 100644 index 0000000000..987e2a253c --- /dev/null +++ b/vendor/dflydev/dot-access-data/.gitignore @@ -0,0 +1,2 @@ +composer.lock +vendor diff --git a/vendor/dflydev/dot-access-data/.travis.yml b/vendor/dflydev/dot-access-data/.travis.yml new file mode 100644 index 0000000000..61dacddb56 --- /dev/null +++ b/vendor/dflydev/dot-access-data/.travis.yml @@ -0,0 +1,13 @@ +language: php + +php: + - 5.3 + - 5.4 + - 5.5 + - 5.6 + +before_script: + - wget -nc http://getcomposer.org/composer.phar + - php composer.phar install --dev + +script: phpunit --coverage-text diff --git a/vendor/dflydev/dot-access-data/LICENSE b/vendor/dflydev/dot-access-data/LICENSE new file mode 100644 index 0000000000..b6880d433e --- /dev/null +++ b/vendor/dflydev/dot-access-data/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2012 Dragonfly Development Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/dflydev/dot-access-data/README.md b/vendor/dflydev/dot-access-data/README.md new file mode 100644 index 0000000000..308a84b936 --- /dev/null +++ b/vendor/dflydev/dot-access-data/README.md @@ -0,0 +1,118 @@ +Dot Access Data +=============== + +Given a deep data structure, access data by dot notation. + + +Requirements +------------ + + * PHP (5.3+) + + +Usage +----- + +Abstract example: + +```php +use Dflydev\DotAccessData\Data; + +$data = new Data; + +$data->set('a.b.c', 'C'); +$data->set('a.b.d', 'D1'); +$data->append('a.b.d', 'D2'); +$data->set('a.b.e', array('E0', 'E1', 'E2')); + +// C +$data->get('a.b.c'); + +// array('D1', 'D2') +$data->get('a.b.d'); + +// array('E0', 'E1', 'E2') +$data->get('a.b.e'); + +// true +$data->has('a.b.c'); + +// false +$data->has('a.b.d.j'); +``` + +A more concrete example: + +```php +use Dflydev\DotAccessData\Data; + +$data = new Data(array( + 'hosts' => array( + 'hewey' => array( + 'username' => 'hman', + 'password' => 'HPASS', + 'roles' => array('web'), + ), + 'dewey' => array( + 'username' => 'dman', + 'password' => 'D---S', + 'roles' => array('web', 'db'), + 'nick' => 'dewey dman' + ), + 'lewey' => array( + 'username' => 'lman', + 'password' => 'LP@$$', + 'roles' => array('db'), + ), + ) +)); + +// hman +$username = $data->get('hosts.hewey.username'); +// HPASS +$password = $data->get('hosts.hewey.password'); +// array('web') +$roles = $data->get('hosts.hewey.roles'); +// dewey dman +$nick = $data->get('hosts.dewey.nick'); +// Unknown +$nick = $data->get('hosts.lewey.nick', 'Unknown'); + +// DataInterface instance +$dewey = $data->getData('hosts.dewey'); +// dman +$username = $dewey->get('username'); +// D---S +$password = $dewey->get('password'); +// array('web', 'db') +$roles = $dewey->get('roles'); + +// No more lewey +$data->remove('hosts.lewey'); + +// Add DB to hewey's roles +$data->append('hosts.hewey.roles', 'db'); + +$data->set('hosts.april', array( + 'username' => 'aman', + 'password' => '@---S', + 'roles' => array('web'), +)); + +// Check if a key exists (true to this case) +$hasKey = $data->has('hosts.dewey.username'); +``` + + +License +------- + +This library is licensed under the New BSD License - see the LICENSE file +for details. + + +Community +--------- + +If you have questions or want to help out, join us in the +[#dflydev](irc://irc.freenode.net/#dflydev) channel on irc.freenode.net. diff --git a/vendor/dflydev/dot-access-data/composer.json b/vendor/dflydev/dot-access-data/composer.json new file mode 100644 index 0000000000..b200c9f24b --- /dev/null +++ b/vendor/dflydev/dot-access-data/composer.json @@ -0,0 +1,38 @@ +{ + "name": "dflydev/dot-access-data", + "type": "library", + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": ["dot", "access", "data", "notation"], + "license": "MIT", + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + } + ], + "require": { + "php": ">=5.3.2" + }, + "autoload": { + "psr-0": { + "Dflydev\\DotAccessData": "src" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + } +} diff --git a/vendor/dflydev/dot-access-data/phpunit.xml.dist b/vendor/dflydev/dot-access-data/phpunit.xml.dist new file mode 100644 index 0000000000..2b386b3a8f --- /dev/null +++ b/vendor/dflydev/dot-access-data/phpunit.xml.dist @@ -0,0 +1,14 @@ + + + + + ./tests/Dflydev/DotAccessData + + + + + + ./src/Dflydev/DotAccessData/ + + + diff --git a/vendor/dflydev/dot-access-data/src/Dflydev/DotAccessData/Data.php b/vendor/dflydev/dot-access-data/src/Dflydev/DotAccessData/Data.php new file mode 100644 index 0000000000..34bda67e42 --- /dev/null +++ b/vendor/dflydev/dot-access-data/src/Dflydev/DotAccessData/Data.php @@ -0,0 +1,220 @@ +data = $data ?: array(); + } + + /** + * {@inheritdoc} + */ + public function append($key, $value = null) + { + if (0 == strlen($key)) { + throw new \RuntimeException("Key cannot be an empty string"); + } + + $currentValue =& $this->data; + $keyPath = explode('.', $key); + + if (1 == count($keyPath)) { + if (!isset($currentValue[$key])) { + $currentValue[$key] = array(); + } + if (!is_array($currentValue[$key])) { + // Promote this key to an array. + // TODO: Is this really what we want to do? + $currentValue[$key] = array($currentValue[$key]); + } + $currentValue[$key][] = $value; + + return; + } + + $endKey = array_pop($keyPath); + for ( $i = 0; $i < count($keyPath); $i++ ) { + $currentKey =& $keyPath[$i]; + if ( ! isset($currentValue[$currentKey]) ) { + $currentValue[$currentKey] = array(); + } + $currentValue =& $currentValue[$currentKey]; + } + + if (!isset($currentValue[$endKey])) { + $currentValue[$endKey] = array(); + } + if (!is_array($currentValue[$endKey])) { + $currentValue[$endKey] = array($currentValue[$endKey]); + } + // Promote this key to an array. + // TODO: Is this really what we want to do? + $currentValue[$endKey][] = $value; + } + + /** + * {@inheritdoc} + */ + public function set($key, $value = null) + { + if (0 == strlen($key)) { + throw new \RuntimeException("Key cannot be an empty string"); + } + + $currentValue =& $this->data; + $keyPath = explode('.', $key); + + if (1 == count($keyPath)) { + $currentValue[$key] = $value; + + return; + } + + $endKey = array_pop($keyPath); + for ( $i = 0; $i < count($keyPath); $i++ ) { + $currentKey =& $keyPath[$i]; + if (!isset($currentValue[$currentKey])) { + $currentValue[$currentKey] = array(); + } + if (!is_array($currentValue[$currentKey])) { + throw new \RuntimeException("Key path at $currentKey of $key cannot be indexed into (is not an array)"); + } + $currentValue =& $currentValue[$currentKey]; + } + $currentValue[$endKey] = $value; + } + + /** + * {@inheritdoc} + */ + public function remove($key) + { + if (0 == strlen($key)) { + throw new \RuntimeException("Key cannot be an empty string"); + } + + $currentValue =& $this->data; + $keyPath = explode('.', $key); + + if (1 == count($keyPath)) { + unset($currentValue[$key]); + + return; + } + + $endKey = array_pop($keyPath); + for ( $i = 0; $i < count($keyPath); $i++ ) { + $currentKey =& $keyPath[$i]; + if (!isset($currentValue[$currentKey])) { + return; + } + $currentValue =& $currentValue[$currentKey]; + } + unset($currentValue[$endKey]); + } + + /** + * {@inheritdoc} + */ + public function get($key, $default = null) + { + $currentValue = $this->data; + $keyPath = explode('.', $key); + + for ( $i = 0; $i < count($keyPath); $i++ ) { + $currentKey = $keyPath[$i]; + if (!isset($currentValue[$currentKey]) ) { + return $default; + } + if (!is_array($currentValue)) { + return $default; + } + $currentValue = $currentValue[$currentKey]; + } + + return $currentValue === null ? $default : $currentValue; + } + + /** + * {@inheritdoc} + */ + public function has($key) + { + $currentValue = &$this->data; + $keyPath = explode('.', $key); + + for ( $i = 0; $i < count($keyPath); $i++ ) { + $currentKey = $keyPath[$i]; + if ( + !is_array($currentValue) || + !array_key_exists($currentKey, $currentValue) + ) { + return false; + } + $currentValue = &$currentValue[$currentKey]; + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function getData($key) + { + $value = $this->get($key); + if (is_array($value) && Util::isAssoc($value)) { + return new Data($value); + } + + throw new \RuntimeException("Value at '$key' could not be represented as a DataInterface"); + } + + /** + * {@inheritdoc} + */ + public function import(array $data, $clobber = true) + { + $this->data = Util::mergeAssocArray($this->data, $data, $clobber); + } + + /** + * {@inheritdoc} + */ + public function importData(DataInterface $data, $clobber = true) + { + $this->import($data->export(), $clobber); + } + + /** + * {@inheritdoc} + */ + public function export() + { + return $this->data; + } +} diff --git a/vendor/dflydev/dot-access-data/src/Dflydev/DotAccessData/DataInterface.php b/vendor/dflydev/dot-access-data/src/Dflydev/DotAccessData/DataInterface.php new file mode 100644 index 0000000000..3498f26e57 --- /dev/null +++ b/vendor/dflydev/dot-access-data/src/Dflydev/DotAccessData/DataInterface.php @@ -0,0 +1,89 @@ + $v) { + if (!isset($to[$k])) { + $to[$k] = $v; + } else { + $to[$k] = self::mergeAssocArray($to[$k], $v, $clobber); + } + } + + return $to; + } + + return $clobber ? $from : $to; + } +} diff --git a/vendor/dflydev/dot-access-data/tests/Dflydev/DotAccessData/DataTest.php b/vendor/dflydev/dot-access-data/tests/Dflydev/DotAccessData/DataTest.php new file mode 100644 index 0000000000..c75260beca --- /dev/null +++ b/vendor/dflydev/dot-access-data/tests/Dflydev/DotAccessData/DataTest.php @@ -0,0 +1,205 @@ + 'A', + 'b' => array( + 'b' => 'B', + 'c' => array('C1', 'C2', 'C3'), + 'd' => array( + 'd1' => 'D1', + 'd2' => 'D2', + 'd3' => 'D3', + ), + ), + 'c' => array('c1', 'c2', 'c3'), + 'f' => array( + 'g' => array( + 'h' => 'FGH', + ), + ), + 'h' => array( + 'i' => 'I', + ), + 'i' => array( + 'j' => 'J', + ), + ); + } + + protected function runSampleDataTests(DataInterface $data) + { + $this->assertEquals('A', $data->get('a')); + $this->assertEquals('B', $data->get('b.b')); + $this->assertEquals(array('C1', 'C2', 'C3'), $data->get('b.c')); + $this->assertEquals('D3', $data->get('b.d.d3')); + $this->assertEquals(array('c1', 'c2', 'c3'), $data->get('c')); + $this->assertNull($data->get('foo'), 'Foo should not exist'); + $this->assertNull($data->get('f.g.h.i')); + $this->assertEquals($data->get('foo', 'default-value-1'), 'default-value-1', 'Return default value'); + $this->assertEquals($data->get('f.g.h.i', 'default-value-2'), 'default-value-2'); + } + + public function testAppend() + { + $data = new Data($this->getSampleData()); + + $data->append('a', 'B'); + $data->append('c', 'c4'); + $data->append('b.c', 'C4'); + $data->append('b.d.d3', 'D3b'); + $data->append('b.d.d4', 'D'); + $data->append('e', 'E'); + $data->append('f.a', 'b'); + $data->append('h.i', 'I2'); + $data->append('i.k.l', 'L'); + + $this->assertEquals(array('A', 'B'), $data->get('a')); + $this->assertEquals(array('c1', 'c2', 'c3', 'c4'), $data->get('c')); + $this->assertEquals(array('C1', 'C2', 'C3', 'C4'), $data->get('b.c')); + $this->assertEquals(array('D3', 'D3b'), $data->get('b.d.d3')); + $this->assertEquals(array('D'), $data->get('b.d.d4')); + $this->assertEquals(array('E'), $data->get('e')); + $this->assertEquals(array('b'), $data->get('f.a')); + $this->assertEquals(array('I', 'I2'), $data->get('h.i')); + $this->assertEquals(array('L'), $data->get('i.k.l')); + + $this->setExpectedException('RuntimeException'); + + $data->append('', 'broken'); + } + + public function testSet() + { + $data = new Data; + + $this->assertNull($data->get('a')); + $this->assertNull($data->get('b.c')); + $this->assertNull($data->get('d.e')); + + $data->set('a', 'A'); + $data->set('b.c', 'C'); + $data->set('d.e', array('f' => 'F', 'g' => 'G',)); + + $this->assertEquals('A', $data->get('a')); + $this->assertEquals(array('c' => 'C'), $data->get('b')); + $this->assertEquals('C', $data->get('b.c')); + $this->assertEquals('F', $data->get('d.e.f')); + $this->assertEquals(array('e' => array('f' => 'F', 'g' => 'G',)), $data->get('d')); + + $this->setExpectedException('RuntimeException'); + + $data->set('', 'broken'); + } + + public function testSetClobberStringInPath() + { + $data = new Data; + + $data->set('a.b.c', 'Should not be able to write to a.b.c.d.e'); + + $this->setExpectedException('RuntimeException'); + + $data->set('a.b.c.d.e', 'broken'); + } + + public function testRemove() + { + $data = new Data($this->getSampleData()); + + $data->remove('a'); + $data->remove('b.c'); + $data->remove('b.d.d3'); + $data->remove('d'); + $data->remove('d.e.f'); + $data->remove('empty.path'); + + $this->assertNull($data->get('a')); + $this->assertNull($data->get('b.c')); + $this->assertNull($data->get('b.d.d3')); + $this->assertNull(null); + $this->assertEquals('D2', $data->get('b.d.d2')); + + $this->setExpectedException('RuntimeException'); + + $data->remove('', 'broken'); + } + + public function testGet() + { + $data = new Data($this->getSampleData()); + + $this->runSampleDataTests($data); + } + + public function testHas() + { + $data = new Data($this->getSampleData()); + + foreach ( + array('a', 'i', 'b.d', 'f.g.h', 'h.i', 'b.d.d1') as $existentKey + ) { + $this->assertTrue($data->has($existentKey)); + } + + foreach ( + array('p', 'b.b1', 'b.c.C1', 'h.i.I', 'b.d.d1.D1') as $notExistentKey + ) { + $this->assertFalse($data->has($notExistentKey)); + } + } + + public function testGetData() + { + $wrappedData = new Data(array( + 'wrapped' => array( + 'sampleData' => $this->getSampleData() + ), + )); + + $data = $wrappedData->getData('wrapped.sampleData'); + + $this->runSampleDataTests($data); + + $this->setExpectedException('RuntimeException'); + + $data = $wrappedData->getData('wrapped.sampleData.a'); + } + + public function testImport() + { + $data = new Data(); + $data->import($this->getSampleData()); + + $this->runSampleDataTests($data); + } + + public function testImportData() + { + $data = new Data(); + $data->importData(new Data($this->getSampleData())); + + $this->runSampleDataTests($data); + } + + public function testExport() + { + $data = new Data($this->getSampleData()); + + $this->assertEquals($this->getSampleData(), $data->export()); + } +} diff --git a/vendor/dflydev/dot-access-data/tests/Dflydev/DotAccessData/UtilTest.php b/vendor/dflydev/dot-access-data/tests/Dflydev/DotAccessData/UtilTest.php new file mode 100644 index 0000000000..3a7c9ac59a --- /dev/null +++ b/vendor/dflydev/dot-access-data/tests/Dflydev/DotAccessData/UtilTest.php @@ -0,0 +1,121 @@ +assertTrue(Util::isAssoc(array('a' => 'A',))); + $this->assertTrue(Util::isAssoc(array())); + $this->assertFalse(Util::isAssoc(array(1 => 'One',))); + } + + /** + * @dataProvider mergeAssocArrayProvider + */ + public function testMergeAssocArray($message, $to, $from, $clobber, $expectedResult) + { + $result = Util::mergeAssocArray($to, $from, $clobber); + $this->assertEquals($expectedResult, $result, $message); + } + + public function mergeAssocArrayProvider() + { + return array( + + array( + 'Clobber should replace to value with from value for strings (shallow)', + // to + array('a' => 'A'), + // from + array('a' => 'B'), + // clobber + true, + // expected result + array('a' => 'B'), + ), + + array( + 'Clobber should replace to value with from value for strings (deep)', + // to + array('a' => array('b' => 'B',),), + // from + array('a' => array('b' => 'C',),), + // clobber + true, + // expected result + array('a' => array('b' => 'C',),), + ), + + array( + 'Clobber should NOTreplace to value with from value for strings (shallow)', + // to + array('a' => 'A'), + // from + array('a' => 'B'), + // clobber + false, + // expected result + array('a' => 'A'), + ), + + array( + 'Clobber should NOT replace to value with from value for strings (deep)', + // to + array('a' => array('b' => 'B',),), + // from + array('a' => array('b' => 'C',),), + // clobber + false, + // expected result + array('a' => array('b' => 'B',),), + ), + + array( + 'Associative arrays should be combined', + // to + array('a' => array('b' => 'B',),), + // from + array('a' => array('c' => 'C',),), + // clobber + null, + // expected result + array('a' => array('b' => 'B', 'c' => 'C',),), + ), + + array( + 'Arrays should be replaced (with clobber enabled)', + // to + array('a' => array('b', 'c',)), + // from + array('a' => array('B', 'C',),), + // clobber + true, + // expected result + array('a' => array('B', 'C',),), + ), + + array( + 'Arrays should be NOT replaced (with clobber disabled)', + // to + array('a' => array('b', 'c',)), + // from + array('a' => array('B', 'C',),), + // clobber + false, + // expected result + array('a' => array('b', 'c',),), + ), + ); + } +} diff --git a/vendor/dflydev/dot-access-data/tests/bootstrap.php b/vendor/dflydev/dot-access-data/tests/bootstrap.php new file mode 100644 index 0000000000..a10725e959 --- /dev/null +++ b/vendor/dflydev/dot-access-data/tests/bootstrap.php @@ -0,0 +1,13 @@ +> + environment: + UNISH_NO_TIMEOUTS: y + UNISH_DB_URL: mysql://root:@127.0.0.1 + UNISH_DRUPAL_VERSION: << parameters.drupal >> + steps: + - checkout + - prepare_php + - run: composer install --prefer-dist --no-interaction + - run: if [[ -n "$(php --version | grep 'PHP 8')" ]] ; then composer config --unset platform.php && composer remove --dev --no-update symfony/var-dumper && composer update symfony/var-dumper phpunit/phpunit --with-dependencies ; fi + - run: vendor/bin/phpunit --configuration tests + +workflows: + build_test: + jobs: + - lint + - test: + name: test-drupal-9.3-php-8.1 + drupal: "9.3.0" + os: linux81 + - test: + name: test-drupal-9.3-php-8.0 + drupal: "9.3.0" + os: linux80 + - test: + name: test-drupal-9.3-php-7.4 + drupal: "9.3.0" + os: linux74 + - test: + name: test-drupal-9.1-php-7.4 + drupal: "9.1.15" + os: linux74 + - test: + name: test-drupal-7-php-7.4 + drupal: "7" + os: linux74 + - test: + name: test-drupal-7-php-5.6 + drupal: "7" + os: linux56 diff --git a/vendor/drush/drush/.docker/zz-php.ini b/vendor/drush/drush/.docker/zz-php.ini new file mode 100644 index 0000000000..9469a78d30 --- /dev/null +++ b/vendor/drush/drush/.docker/zz-php.ini @@ -0,0 +1,8 @@ +[PHP] +variables_order = GPCS +error_reporting = E_ALL & ~E_DEPRECATED +date.timezone = "UTC" +sendmail_path = "true" +mbstring.http_input = pass +mbstring.http_output = pass +memory_limit = -1 diff --git a/vendor/drush/drush/.gitignore b/vendor/drush/drush/.gitignore new file mode 100644 index 0000000000..cc8bfc4662 --- /dev/null +++ b/vendor/drush/drush/.gitignore @@ -0,0 +1,10 @@ +tests/phpunit.xml +vendor +/lib/Console_Table-* +box.phar +drush.phar +isolation/ +#The mkdocs output directory. +site +# IDE config +.idea/ diff --git a/vendor/drush/drush/CONTRIBUTING.md b/vendor/drush/drush/CONTRIBUTING.md new file mode 100644 index 0000000000..2272eb7f64 --- /dev/null +++ b/vendor/drush/drush/CONTRIBUTING.md @@ -0,0 +1,22 @@ +Drush is built by people like you! Please [join us](https://github.com/drush-ops/drush). + +## Git and Pull requests +* Contributions are submitted, reviewed, and accepted using Github pull requests. [Read this article](https://help.github.com/articles/using-pull-requests) for some details. We use the _Fork and Pull_ model, as described there. +* To help keep track of [your assigned issues](https://github.com/dashboard/issues/assigned), simply open an issue to be added as an [Outside Collaborator](https://github.com/orgs/drush-ops/outside-collaborators). A maintainer can now assign any issue to you at your request. +* The latest changes are in the `master` branch. +* Make a new branch for every feature you're working on. +* Try to make clean commits that are easily readable (including descriptive commit messages!) +* Test before you push. Get familiar with Unish, our test suite. See the test-specific [README.md](tests/README.md) +* Make small pull requests that are easy to review but make sure they do add value by themselves. +* We maintain branches named 7.x, 6.x, etc. These are release branches. From these branches, we make new tags for patch and minor versions. + +## Coding style +* Do write comments. You don't have to comment every line, but if you come up with something thats a bit complex/weird, just leave a comment. Bear in mind that you will probably leave the project at some point and that other people will read your code. Undocumented huge amounts of code are nearly worthless! +* We use [Drupal's coding standards](https://drupal.org/coding-standards). +* Don't overengineer. Don't try to solve any possible problem in one step, but try to solve problems as easy as possible and improve the solution over time! +* Do generalize sooner or later! (if an old solution, quickly hacked together, poses more problems than it solves today, refactor it!) +* Keep it compatible. Do not introduce changes to the public API, or configurations too lightly. Don't make incompatible changes without good reasons! + +## Documentation +* The docs are in the [docs](docs) and [examples](examples) folders in the git repository, so people can easily find the suitable docs for the current git revision. You can read these from within Drush, with the `drush topic` command. +* Documentation should be kept up-to-date. This means, whenever you add a new API method, add a new hook or change the database model, pack the relevant changes to the docs in the same pull request. diff --git a/vendor/drush/drush/README.md b/vendor/drush/drush/README.md new file mode 100644 index 0000000000..61d3408af2 --- /dev/null +++ b/vendor/drush/drush/README.md @@ -0,0 +1,46 @@ +Drush is a command line shell and Unix scripting interface for Drupal. Drush core ships with lots of useful commands for interacting with code like modules/themes/profiles. Similarly, it runs update.php, executes sql queries and DB migrations, and misc utilities like run cron or clear cache. Drush can be extended by [3rd party commandfiles](https://www.drupal.org/project/project_module?f[2]=im_vid_3%3A4654). + +[![Latest Stable Version](https://poser.pugx.org/drush/drush/v/stable.png)](https://packagist.org/packages/drush/drush) [![Total Downloads](https://poser.pugx.org/drush/drush/downloads.png)](https://packagist.org/packages/drush/drush) [![Latest Unstable Version](https://poser.pugx.org/drush/drush/v/unstable.png)](https://packagist.org/packages/drush/drush) [![License](https://poser.pugx.org/drush/drush/license.png)](https://packagist.org/packages/drush/drush) [![Documentation Status](https://readthedocs.org/projects/drush/badge/?version=master)](https://readthedocs.org/projects/drush/?badge=master) + +Resources +----------- +* [Installing (and Upgrading)](http://docs.drush.org/en/8.x/install/) +* [General Documentation](http://docs.drush.org) +* [API Documentation](http://api.drush.org) +* [Drush Commands](http://drushcommands.com) +* To receive notifications on new releases, use GitHub's 'Watch' button and select 'Releases only'. Also, [this atom feed](https://github.com/drush-ops/drush/releases.atom). +* [Drush packages available via Composer](https://packagist.org/search/?type=drupal-drush) +* [A list of modules that include Drush integration](https://www.drupal.org/project/project_module?f[2]=im_vid_3%3A4654&solrsort=ds_project_latest_release+desc) +* Drush comes with a [full test suite](https://github.com/drush-ops/drush/blob/master/tests/README.md) powered by [PHPUnit](https://github.com/sebastianbergmann/phpunit). Each commit gets tested by the awesome [Travis.ci continuous integration service](https://travis-ci.org/drush-ops/drush). + +Support +----------- +* Post support requests to [Drupal Answers](http://drupal.stackexchange.com/questions/tagged/drush). +* Report bugs and request features in the [GitHub Drush Issue Queue](https://github.com/drush-ops/drush/issues). +* Use pull requests (PRs) to contribute to Drush. + +FAQ +------ + +> Q: What does "drush" stand for?
+> A: The Drupal Shell. +> +> Q: How do I pronounce Drush?
+> A: Some people pronounce the *dru* with a long 'u' like Dr*u*pal. Fidelity points +> go to them, but they are in the minority. Most pronounce Drush so that it +> rhymes with hush, rush, flush, etc. This is the preferred pronunciation. +> +> Q: Does Drush have unit tests?
+> A: Drush has an excellent suite of unit tests. See +> [tests/README.md](https://github.com/drush-ops/drush/blob/master/tests/README.md) for more information. + + +Credits +----------- + +* Originally developed by [Arto Bendiken](http://bendiken.net) for Drupal 4.7. +* Redesigned by [Franz Heinzmann](http://unbiskant.org) in May 2007 for Drupal 5. +* Maintained by [Moshe Weitzman](http://drupal.org/moshe) with much help from + the folks listed at https://github.com/orgs/drush-ops/people. + +![Drush Logo](drush_logo-black.png) diff --git a/vendor/drush/drush/appveyor.yml b/vendor/drush/drush/appveyor.yml new file mode 100644 index 0000000000..da0b06fb14 --- /dev/null +++ b/vendor/drush/drush/appveyor.yml @@ -0,0 +1,88 @@ +build: false +shallow_clone: true +platform: 'x86' +clone_folder: C:\projects\drush +matrix: + allow_failures: + # Don't record a failure until we have a passing test suite. + - UNISH_NO_TIMEOUTS: y +branches: + only: + - master + - 8.x + +init: + #https://github.com/composer/composer/blob/master/appveyor.yml + #- SET ANSICON=121x90 (121x90) + +# Inspired by https://github.com/Codeception/base/blob/master/appveyor.yml and https://github.com/phpmd/phpmd/blob/master/appveyor.yml +install: + - cinst -y curl + - SET PATH=C:\Program Files\curl;%PATH% + #which is only needed by the test suite. + - cinst -y which + - SET PATH=C:\Program Files\which;%PATH% + - git clone -q https://github.com/acquia/DevDesktopCommon.git #For tar, cksum, ... + - SET PATH=%APPVEYOR_BUILD_FOLDER%/DevDesktopCommon/bintools-win/msys/bin;%PATH% + - SET PATH=C:\Program Files\MySql\MySQL Server 5.7\bin\;%PATH% + #Install PHP + - cinst -y php + - cd c:\tools\php + - copy php.ini-production php.ini + + - echo extension_dir=ext >> php.ini + - echo extension=php_openssl.dll >> php.ini + - echo date.timezone="UTC" >> php.ini + - echo variables_order="EGPCS" >> php.ini #Debugging related to https://github.com/drush-ops/drush/pull/646. May be unneeded. + - echo mbstring.http_input=pass >> php.ini + - echo mbstring.http_output=pass >> php.ini + - echo sendmail_path=nul >> php.ini + - echo extension=php_mbstring.dll >> php.ini + - echo extension=php_curl.dll >> php.ini + - echo extension=php_pdo_mysql.dll >> php.ini + - echo extension=php_pdo_pgsql.dll >> php.ini + - echo extension=php_pdo_sqlite.dll >> php.ini + - echo extension=php_pgsql.dll >> php.ini + - echo extension=php_gd2.dll >> php.ini + - SET PATH=C:\tools\php;%PATH% + #Install Composer + - cd %APPVEYOR_BUILD_FOLDER% + #- appveyor DownloadFile https://getcomposer.org/composer.phar + - php -r "readfile('http://getcomposer.org/installer');" | php + #Install Drush's dependencies via Composer + - php composer.phar -q install --prefer-dist -n + - SET PATH=%APPVEYOR_BUILD_FOLDER%;%APPVEYOR_BUILD_FOLDER%/vendor/bin;%PATH% + #Create a sandbox for testing. + - mkdir c:\drush_temp + +services: + - mysql + +test_script: + - phpunit --configuration tests %PHPUNIT_ARGS% + +# environment variables +environment: + global: + UNISH_NO_TIMEOUTS: y + UNISH_DB_URL: "mysql://root:Password12!@localhost" + UNISH_TMP: c:\drush_temp + + matrix: + - UNISH_DRUPAL_MAJOR_VERSION: 7 + PHPUNIT_ARGS: --group=base + + - UNISH_DRUPAL_MAJOR_VERSION: 7 + PHPUNIT_ARGS: --group=commands + + - UNISH_DRUPAL_MAJOR_VERSION: 7 + PHPUNIT_ARGS: --group=pm + + - UNISH_DRUPAL_MAJOR_VERSION: 7 + PHPUNIT_ARGS: --group=quick-drupal + + - UNISH_DRUPAL_MAJOR_VERSION: 7 + PHPUNIT_ARGS: --exclude-group=base,make,commands,pm,quick-drupal + + - UNISH_DRUPAL_MAJOR_VERSION: 7 + PHPUNIT_ARGS: --group=make diff --git a/vendor/drush/drush/box.json.dist b/vendor/drush/drush/box.json.dist new file mode 100644 index 0000000000..d8244370f3 --- /dev/null +++ b/vendor/drush/drush/box.json.dist @@ -0,0 +1,34 @@ +{ + "alias": "drush.phar", + "chmod": "0755", + "compactors": [], + "compression": "GZ", + "directories": ["commands", "includes", "lib", "src", "misc"], + "blacklist": [ + "windrush_build/", + "TestTraits/" + ], + "files": ["drush.complete.sh", "drush.info", "drush.launcher", "drush.php"], + "finder": [ + { + "name": ["*.php", "*.php8"], + "exclude": [ + "examples", + "isolation", + "phing", + "test", + "tests", + "test_old", + "Test", + "Tests", + "Tester" + ], + "in": "vendor" + } + ], + "git-commit": "git-commit", + "git-version": "git-version", + "output": "drush.phar", + "main": "drush", + "stub": true +} diff --git a/vendor/drush/drush/commands/backdrop/BackdropBoot.php b/vendor/drush/drush/commands/backdrop/BackdropBoot.php new file mode 100755 index 0000000000..32c5e3d184 --- /dev/null +++ b/vendor/drush/drush/commands/backdrop/BackdropBoot.php @@ -0,0 +1,689 @@ + 'bootstrap_backdrop_root', + BackdropBoot::BOOTSTRAP_SITE => 'bootstrap_backdrop_site', + BackdropBoot::BOOTSTRAP_CONFIGURATION => 'bootstrap_backdrop_configuration', + BackdropBoot::BOOTSTRAP_DATABASE => 'bootstrap_backdrop_database', + BackdropBoot::BOOTSTRAP_FULL => 'bootstrap_backdrop_full', + BackdropBoot::BOOTSTRAP_LOGIN => 'bootstrap_backdrop_login'); + } + + /** + * List of bootstrap phases where Drush should stop and look for commandfiles. + * + * For Backdrop, we try at these bootstrap phases: + * + * - Drush preflight: to find commandfiles in any system location, + * out of a Backdrop installation. + * - Backdrop root: to find commandfiles based on Backdrop core version. + * - Backdrop full: to find commandfiles defined within a Backdrop directory. + * + * Once a command is found, Drush will ensure a bootstrap to the phase + * declared by the command. + * + * @return array of PHASE indexes. + */ + function bootstrap_init_phases() { + return array(BackdropBoot::BOOTSTRAP_ROOT, BackdropBoot::BOOTSTRAP_FULL); + } + + /** + * Validate the BackdropBoot::BOOTSTRAP_ROOT phase. + */ + function bootstrap_backdrop_root_validate() { + $backdrop_root = drush_locate_root(); + drush_set_context('DRUSH_SELECTED_BACKDROP_ROOT', $backdrop_root); + + if (empty($backdrop_root)) { + return drush_bootstrap_error('DRUSH_NO_BACKDROP_ROOT', dt("A Backdrop installation directory could not be found")); + } + if (!$signature = drush_valid_root($backdrop_root)) { + return drush_bootstrap_error('DRUSH_INVALID_BACKDROP_ROOT', dt("The directory !backdrop_root does not contain a valid Backdrop installation", array('!backdrop_root' => $backdrop_root))); + } + drush_bootstrap_value('backdrop_root', realpath($backdrop_root)); + define('DRUSH_BACKDROP_SIGNATURE', $signature); + + return TRUE; + } + + /** + * Execute the BackdropBoot::BOOTSTRAP_ROOT phase. + * + * Bootstrap Drush with a valid Backdrop directory. + * + * In this function, the pwd() will be moved to the root of the Backdrop + * installation. + * + * The DRUSH_BACKDROP_ROOT context, DRUSH_BACKDROP_CORE context, + * BACKDROP_ROOT constant, and the DRUSH_BACKDROP_CORE constant are populated + * from the value that we determined during the validation phase. + * + * We also now load the drushrc.php for this specific Backdrop site. + * We can now include files from the Backdrop directory, and figure + * out more context about the platform, such as the version of Backdrop. + */ + function bootstrap_backdrop_root() { + // Load the config options the installation's /drush directory. + drush_load_config('backdrop'); + + $backdrop_root = drush_set_context('DRUSH_BACKDROP_ROOT', drush_bootstrap_value('backdrop_root')); + chdir($backdrop_root); + $version = $this->get_version($backdrop_root); + + $core = $this->bootstrap_backdrop_core($backdrop_root); + + // DRUSH_BACKDROP_CORE should point to the /core folder. + drush_set_context('DRUSH_BACKDROP_CORE', $core); + define('DRUSH_BACKDROP_CORE', $core); + + _drush_preflight_global_options(); + + drush_log(dt("Initialized Backdrop !version root directory at !backdrop_root", array("!version" => $version, '!backdrop_root' => $backdrop_root))); + } + + + /** + * @param $backdrop_root + * @return string + */ + function bootstrap_backdrop_core($backdrop_root) { + define('BACKDROP_ROOT', $backdrop_root); + $core = $backdrop_root . '/core'; + + require_once $core . '/includes/bootstrap.inc'; + + return $core; + } + + /** + * Validate the BackdropBoot::BOOTSTRAP_SITE phase. + * + * In this function we determine the URL used for the command, and check for a + * valid settings.php file. + * + * To do this, we need to set up the $_SERVER environment variable, + * to allow us to use conf_path() to determine what Backdrop will load + * as a configuration file. + */ + function bootstrap_backdrop_site_validate() { + // Define the selected conf path as soon as we have identified that + // we have selected a Backdrop site. + $drush_uri = $this->get_selected_uri(); + + $this->setup_server_globals($drush_uri); + $conf_path = $this->conf_path($drush_uri); + + drush_set_context('DRUSH_SELECTED_BACKDROP_SITE_CONF_PATH', $conf_path); + + $conf_file = "$conf_path/settings.php"; + if (!file_exists($conf_file)) { + return drush_bootstrap_error('DRUPAL_SITE_SETTINGS_NOT_FOUND', dt("Could not find a Backdrop settings.php file at !file.", array('!file' => $conf_file))); + } + + drush_bootstrap_value('site', $_SERVER['HTTP_HOST']); + drush_bootstrap_value('conf_path', $conf_path); + + return TRUE; + } + + /** + * Execute the BackdropBoot::BOOTSTRAP_SITE phase. + * + * Initialize a site on the Backdrop root. + * + * We now set various contexts that we determined and confirmed to be valid. + * Additionally we load an optional drushrc.php file in the site directory. + */ + function bootstrap_backdrop_site() { + drush_load_config('site'); + + $drush_uri = drush_get_context('DRUSH_SELECTED_URI'); + drush_set_context('DRUSH_URI', $drush_uri); + $site = drush_set_context('DRUSH_BACKDROP_SITE', drush_bootstrap_value('site')); + $conf_path = drush_set_context('DRUSH_BACKDROP_SITE_ROOT', drush_bootstrap_value('conf_path')); + + drush_log(dt("Initialized Backdrop site !site at !site_root", array('!site' => $site, '!site_root' => $conf_path))); + + _drush_preflight_global_options(); + + } + + /** + * Validate the BackdropBoot::BOOTSTRAP_CONFIGURATION phase. + */ + function bootstrap_backdrop_configuration_validate() { + // No validation yet. + return TRUE; + } + + /** + * Execute the BackdropBoot::BOOTSTRAP_CONFIGURATION phase. + * + * Load in the settings.php file an initialize the database information, + * config directories, and $settings variable. + */ + function bootstrap_backdrop_configuration() { + backdrop_bootstrap(BACKDROP_BOOTSTRAP_CONFIGURATION); + + // Set the Drush option for "databases" to work with existing SQL commands + // as though a --db-url or --databases option were passed in. + // In the future it should be possible for bootstrap classes like this one + // to provide the database string directly. + // See https://github.com/drush-ops/drush/issues/1750. + $database = drush_get_option('database', 'default'); + $target = drush_get_option('target', 'default'); + + $db_url = $url = drush_get_option('db-url'); + $databases = drush_get_option('databases'); + if (!$databases) { + if ($db_url) { + $db_spec = drush_convert_db_from_db_url($url); + $db_spec['db_prefix'] = drush_get_option('db-prefix'); + drush_set_option('databases', array($database => array($target => $db_spec))); + } + else { + drush_set_option('databases', $GLOBALS['databases']); + } + } + + // Unset Backdrop error handler and restore drush's one. + restore_error_handler(); + } + + /** + * Execute the BackdropBoot::BOOTSTRAP_DATABASE phase. + */ + function bootstrap_backdrop_database_validate() { + require_once BACKDROP_ROOT . '/core/includes/database/database.inc'; + return db_table_exists('system'); + } + + /** + * Execute the BackdropBoot::BOOTSTRAP_DATABASE phase. + */ + function bootstrap_backdrop_database() { + backdrop_bootstrap(BACKDROP_BOOTSTRAP_DATABASE); + } + + /** + * Execute the BackdropBoot::BOOTSTRAP_FULL phase. + */ + function bootstrap_backdrop_full() { + if (!drush_get_context('DRUSH_QUIET', FALSE)) { + ob_start(); + } + backdrop_bootstrap(BACKDROP_BOOTSTRAP_FULL); + if (!drush_get_context('DRUSH_QUIET', FALSE)) { + ob_end_clean(); + } + } + + /** + * Execute the BackdropBoot::BOOTSTRAP_LOGIN phase. + * + * Log into the bootstrapped Backdrop site with a specific user name or ID. + */ + function bootstrap_backdrop_login() { + $uid_or_name = drush_set_context('DRUSH_USER', drush_get_option('user', 0)); + + if (is_numeric($uid_or_name)) { + $account = user_load($uid_or_name); + } + if (!$account) { + $account = user_load_by_name($uid_or_name); + } + + if ($account) { + $GLOBALS['user'] = $account; + // @todo: Convert Backdrop messages to drush output. + //_drush_log_drupal_messages(); + } + else { + if (is_numeric($uid_or_name)) { + $message = dt('Could not login with user ID !user.', array('!user' => $uid_or_name)); + if ($uid_or_name === 0) { + $message .= ' ' . dt('This is typically caused by importing a MySQL database dump from a faulty tool which re-numbered the anonymous user ID in the users table. See !link for help recovering from this situation.', array('!link' => 'http://drupal.org/node/1029506')); + } + } + else { + $message = dt('Could not login with user account `!user\'.', array('!user' => $uid_or_name)); + } + return drush_set_error('DRUPAL_USER_LOGIN_FAILED', $message); + } + } + + /** + * Set up the $_SERVER globals so that Backdrop will see the same values + * that it does when serving pages via the web server. + * + * @see \Drush\Boot\DrupalBoot::bootstrap_drupal_site_setup_server_global() + */ + protected function setup_server_globals($drush_uri) { + // Fake the necessary HTTP headers that Drupal needs: + if ($drush_uri) { + $backdrop_base_url = parse_url($drush_uri); + // If there's no url scheme set, add http:// and re-parse the url + // so the host and path values are set accurately. + if (!array_key_exists('scheme', $backdrop_base_url)) { + $drush_uri = 'http://' . $drush_uri; + $backdrop_base_url = parse_url($drush_uri); + } + // Fill in defaults. + $backdrop_base_url += array( + 'path' => '', + 'host' => NULL, + 'port' => NULL, + ); + $_SERVER['HTTP_HOST'] = $backdrop_base_url['host']; + + if ($backdrop_base_url['scheme'] == 'https') { + $_SERVER['HTTPS'] = 'on'; + } + + if ($backdrop_base_url['port']) { + $_SERVER['HTTP_HOST'] .= ':' . $backdrop_base_url['port']; + } + $_SERVER['SERVER_PORT'] = $backdrop_base_url['port']; + + $_SERVER['REQUEST_URI'] = $backdrop_base_url['path'] . '/'; + } + else { + $_SERVER['HTTP_HOST'] = 'default'; + $_SERVER['REQUEST_URI'] = '/'; + } + + $_SERVER['PHP_SELF'] = $_SERVER['REQUEST_URI'] . 'index.php'; + $_SERVER['SCRIPT_NAME'] = $_SERVER['PHP_SELF']; + $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; + $_SERVER['REQUEST_METHOD'] = NULL; + + $_SERVER['SERVER_SOFTWARE'] = NULL; + $_SERVER['HTTP_USER_AGENT'] = NULL; + $_SERVER['SCRIPT_FILENAME'] = DRUPAL_ROOT . '/index.php'; + + // Allows the user to drop in db connection info by setting BACKDROP_SETTINGS in the environment + // This is helpful when backdrops database connection is not specified in settings.php such as on Pantheon or Kalabox + if (getenv('BACKDROP_SETTINGS') !== false) { + $_SERVER['BACKDROP_SETTINGS'] = getenv('BACKDROP_SETTINGS'); + } + } + + /** + * Called by Drush when a command is selected, but before it runs. This gives + * the Boot class an opportunity to determine if any minimum requirements + * (e.g. minimum Backdrop version) declared in the command have been met. + * + * @return bool TRUE if command is valid. $command['bootstrap_errors'] + * should be populated with an array of error messages if the command is not + * valid. + */ + function enforce_requirement(&$command) { + drush_enforce_requirement_bootstrap_phase($command); + drush_enforce_requirement_core($command); + drush_enforce_requirement_drush_dependencies($command); + + // @todo: Support Backdrop and(?) Drupal dependencies. + // $this->drush_enforce_requirement_backdrop_dependencies($command); + // $this->drush_enforce_requirement_drupal_dependencies($command); + + return empty($command['bootstrap_errors']); + } + + function report_command_error($command) { + // If we reach this point, command doesn't fit requirements or we have not + // found either a valid or matching command. + + // If no command was found check if it belongs to a disabled module. + // @todo: Implement checking if desired for Backdrop. + //if (!$command) { + // $command = $this->drush_command_belongs_to_disabled_module(); + //} + parent::report_command_error($command); + } + + function command_defaults() { + return array( + 'backdrop dependencies' => array(), + 'bootstrap' => BackdropBoot::BOOTSTRAP_FULL, + ); + } + + function contrib_modules_paths() { + $paths = array(); + if (conf_path() !== '.') { + $paths[] = conf_path() . '/modules'; + } + $paths[] = 'modules'; + return $paths; + } + + function contrib_themes_paths() { + $paths = array(); + if (conf_path() !== '.') { + $paths[] = conf_path() . '/themes'; + } + $paths[] = 'themes'; + return $paths; + } + + function commandfile_searchpaths($phase, $phase_max = FALSE) { + if (!$phase_max) { + $phase_max = $phase; + } + + $searchpath = array(); + switch ($phase) { + case BackdropBoot::BOOTSTRAP_ROOT: + $backdrop_root = drush_get_context('DRUSH_SELECTED_BACKDROP_ROOT'); + $searchpath[] = $backdrop_root . '/../drush'; + $searchpath[] = $backdrop_root . '/drush'; + $searchpath[] = $backdrop_root . '/sites/all/drush'; + + // Add the drupalboot.drush.inc commandfile. + // $searchpath[] = __DIR__; + break; + case BackdropBoot::BOOTSTRAP_SITE: + // If we are going to stop bootstrapping at the site, then + // we will quickly add all commandfiles that we can find for + // any extension associated with the site, whether it is enabled + // or not. If we are, however, going to continue on to bootstrap + // all the way to DRUSH_BOOTSTRAP_DRUPAL_FULL, then we will + // instead wait for that phase, which will more carefully add + // only those Drush commandfiles that are associated with + // enabled modules. + if ($phase_max < BackdropBoot::BOOTSTRAP_FULL) { + $searchpath = array_merge($searchpath, $this->contrib_modules_paths()); + + // Adding commandfiles located within /profiles. Try to limit to one + // profile for speed. Note that Backdrop allows enabling modules from + // a non-active profile so this logic is kinda dodgy. + $cid = $this->install_profile_cid(); + if ($cached = drush_cache_get($cid)) { + $profile = $cached->data; + $searchpath[] = "profiles/$profile/modules"; + $searchpath[] = "profiles/$profile/themes"; + } + else { + // If install_profile is not available, scan all profiles. + $searchpath[] = "profiles"; + $searchpath[] = "sites/all/profiles"; + } + + $searchpath = array_merge($searchpath, $this->contrib_themes_paths()); + } + break; + case BackdropBoot::BOOTSTRAP_CONFIGURATION: + // Nothing to do here anymore. Left for documentation. + break; + case BackdropBoot::BOOTSTRAP_FULL: + // Add enabled module paths, excluding the install profile. Since we are + // bootstrapped we can use the Backdrop API. + $ignored_modules = drush_get_option_list('ignored-modules', array()); + $cid = $this->install_profile_cid(); + if ($cached = drush_cache_get($cid)) { + $ignored_modules[] = $cached->data; + } + foreach (array_diff($this->module_list(), $ignored_modules) as $module) { + $filepath = backdrop_get_path('module', $module); + if ($filepath && $filepath != '/') { + $searchpath[] = $filepath; + } + } + + $searchpath[] = backdrop_get_path('theme', config_get('system.core', 'theme_default')); + $searchpath[] = backdrop_get_path('theme', config_get('system.core', 'theme_admin')); + break; + } + + return $searchpath; + } + + /** + * Returns a list of enabled modules. + * + * This is a simplified version of core's module_list(). + */ + protected function module_list() { + $enabled = array(); + $rsc = drush_db_select('system', 'name', 'type=:type AND status=:status', array(':type' => 'module', ':status' => 1)); + while ($row = drush_db_result($rsc)) { + $enabled[$row] = $row; + } + + return $enabled; + } + + /** + * Build a cache id to store the install_profile for a given site. + * + * @see drush_cid_install_profile(). + */ + protected function install_profile_cid() { + drush_get_cid('install_profile', array(), array(drush_get_context('DRUSH_SELECTED_BACKDROP_SITE_CONF_PATH'))); + } + + /** + * Find the URI that has been selected by the cwd if it was not previously set + * via the --uri / -l option. + * + * @return string The site URI. + * + * @see _drush_bootstrap_selected_uri() + */ + protected function get_selected_uri() { + $uri = drush_get_context('DRUSH_SELECTED_URI'); + if (empty($uri)) { + $site_path = $this->site_path(); + $elements = explode('/', $site_path); + $current = array_pop($elements); + if (!$current) { + $current = 'default'; + } + $uri = 'http://'. $current; + $uri = drush_set_context('DRUSH_SELECTED_URI', $uri); + $this->create_self_alias(); + } + + return $uri; + } + + /** + * Like Backdrop conf_path(), but searching from beneath. + * Allows proper site uri detection in site sub-directories. + * + * Essentially looks for a settings.php file. Drush uses this + * function to find a usable site based on the user's current + * working directory. + * + * @param string + * Search starting path. Defaults to current working directory. + * + * @return + * Current site path (folder containing settings.php) or FALSE if not found. + */ + protected function site_path($path = NULL) { + $site_path = FALSE; + + $path = empty($path) ? drush_cwd() : $path; + // Check the current path. + if (file_exists($path . '/settings.php')) { + $site_path = $path; + } + else { + // Move up dir by dir and check each. Stop if we get to a Backdrop root. + // We don't care if it is DRUSH_SELECTED_BACKDROP_ROOT or some other root. + while (($path = _drush_shift_path_up($path)) && !drush_valid_root($path)) { + if (file_exists($path . '/settings.php')) { + $site_path = $path; + break; + } + } + } + + $site_root = drush_get_context('DRUSH_SELECTED_BACKDROP_ROOT'); + if (file_exists($site_root . '/sites/sites.php')) { + $sites = array(); + // This will overwrite $sites with the desired mappings. + include($site_root . '/sites/sites.php'); + // We do a reverse lookup here to determine the URL given the site key. + if ($match = array_search($site_path, $sites)) { + $site_path = $match; + } + } + + // Last resort: try from site root + if (!$site_path) { + if ($site_root) { + if (file_exists($site_root . '/settings.php')) { + $site_path = $site_root; + } + } + } + + return $site_path; + } + + /** + * Check to see if a '@self' record was created during bootstrap. + * If not, make one now. + * + * @see drush_sitealias_create_self_alias() + */ + protected function create_self_alias() { + $self_record = drush_sitealias_get_record('@self'); + if (!array_key_exists('root', $self_record) && !array_key_exists('remote-host', $self_record)) { + $backdrop_root = drush_get_context('DRUSH_SELECTED_BACKDROP_ROOT'); + $uri = drush_get_context('DRUSH_SELECTED_URI'); + if (!empty($backdrop_root) && !empty($uri)) { + // Create an alias '@self' + _drush_sitealias_cache_alias('@self', array('root' => $backdrop_root, 'uri' => $uri)); + } + } + } +} diff --git a/vendor/drush/drush/commands/backdrop/LICENSE.txt b/vendor/drush/drush/commands/backdrop/LICENSE.txt new file mode 100755 index 0000000000..1f963da0d1 --- /dev/null +++ b/vendor/drush/drush/commands/backdrop/LICENSE.txt @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. + diff --git a/vendor/drush/drush/commands/backdrop/README.md b/vendor/drush/drush/commands/backdrop/README.md new file mode 100755 index 0000000000..ea2ad65ea1 --- /dev/null +++ b/vendor/drush/drush/commands/backdrop/README.md @@ -0,0 +1,67 @@ +Drush Integration for Backdrop CMS +================================== + +This project allows you to use [Drush](https://github.com/drush-ops/drush) with +[Backdrop CMS](https://backdropcms.org). Drush is a command-line tool for +manipulating content management systems. + +This integration is currently capable of executing the following commands: + +- `drush cc`: Clear individual or all caches in Backdrop. +- `drush dl`: Download and unpack contrib modules, themes, and layouts. +- `drush updb`: Run database updates through the Backdrop update.php script. +- `drush sql-*`: MySQL connection commands, such as `sql-cli` or `sql-conf`. +- `drush cron`: Run the regular interval commands in hook_cron(). +- `drush scr`: Execute scripts with the Backdrop API. +- `drush st` : Check the status of a Backdrop site; bootstrap, database connection etc. + +There are many more commands that Drush may execute, but they need to be updated +for use with Backdrop. Although some commands may have worked through Backdrop's +compatibility layer, for now any untested (and possibly dangerous) commands are +not allowed to be run within a Backdrop installation. + +Installation +------------ + +This project requires that you use the "8.x" branch of drush (https://github.com/drush-ops/drush/tree/8.x). Neither older versions of drush nor the new 9.x or master branches will work with this extension. + +If you are using composer to install drush, you can run the following command to require the 8.x version: +`composer global require drush/drush:8.x` + +To install the Backdrop integration for Drush, clone or download this project +into any location that supports Drush commands. The most common location for +custom Drush commands such as this is in your user's home directory. + +- `mkdir ~/.drush/commands` (This may already exist, if so continue.) +- `cd ~/.drush/commands` +- `wget https://github.com/backdrop-contrib/drush/archive/master.zip` +- `unzip master.zip -d backdrop` + +Now switch to a Backdrop site's directory and try a command! `drush cron` works well. + +Usage +----- + +Use Drush as you would normally with a Drupal website. + +License +------- + +This project is GPL v2 software. See the LICENSE.txt file in this directory for +complete text. + +Maintainers +----------- + +- Geoff St. Pierre (https://github.com/serundeputy) +- Nate Haug (https://github.com/quicksketch) + +Credits +------- + +Thanks to all the Drush maintainers for their project, in particular: + +- [Greg Anderson](https://github.com/greg-1-anderson) +- [Moshe Weitzman](https://github.com/weitzman) + +for their help in making Drush for Backdrop possible. diff --git a/vendor/drush/drush/commands/backdrop/backdrop.drush.inc b/vendor/drush/drush/commands/backdrop/backdrop.drush.inc new file mode 100755 index 0000000000..4fce099b76 --- /dev/null +++ b/vendor/drush/drush/commands/backdrop/backdrop.drush.inc @@ -0,0 +1,135 @@ + NULL)); + } +} diff --git a/vendor/drush/drush/commands/backdrop/commands/backdrop_core.drush.inc b/vendor/drush/drush/commands/backdrop/commands/backdrop_core.drush.inc new file mode 100755 index 0000000000..c77b134abf --- /dev/null +++ b/vendor/drush/drush/commands/backdrop/commands/backdrop_core.drush.inc @@ -0,0 +1,316 @@ + 'Run all cron hooks in all active modules for specified site.', + 'callback' => 'backdrop_command_cron', + 'hidden' => TRUE, + ); + + $items['backdrop-updatedb'] = array( + 'description' => 'Apply any database updates required (as with running update.php).', + 'callback' => 'backdrop_command_updatedb', + 'global-options' => array( + 'cache-clear', + ), + 'options' => array( + 'entity-updates' => 'Run automatic entity schema updates at the end of any update hooks. Defaults to --no-entity-updates.', + ), + 'bootstrap' => BackdropBoot::BOOTSTRAP_FULL, + 'hidden' => TRUE, + ); + + $items['backdrop-updatedb-batch-process'] = array( + 'description' => 'Perform update functions', + 'callback' => 'backdrop_command_updatedb_batch_process', + 'arguments' => array( + 'batch-id' => 'The batch ID that will be processed', + ), + 'required-arguments' => TRUE, + 'bootstrap' => BackdropBoot::BOOTSTRAP_CONFIGURATION, + 'hidden' => TRUE, + ); + + $items['backdrop-updatedb-status'] = array( + 'description' => 'List any pending database updates.', + 'callback' => 'backdrop_command_updatedb_status', + 'outputformat' => array( + 'default' => 'table', + 'pipe-format' => 'csv', + 'field-labels' => array('module' => 'Module', 'update_id' => 'Update ID', 'description' => 'Description'), + 'fields-default' => array('module', 'update_id', 'description'), + 'output-data-type' => 'format-table', + ), + 'hidden' => TRUE, + ); + + $items['backdrop-core-status'] = array( + 'description' => 'Provides a birds-eye view of the current Backdrop installation, if any.', + 'callback' => 'backdrop_command_core_status', + 'bootstrap' => DRUSH_BOOTSTRAP_MAX, + 'aliases' => array('status', 'st'), + 'examples' => array( + 'drush core-status version' => 'Show all status lines that contain version information.', + 'drush core-status --pipe' => 'A list key=value items separated by line breaks.', + 'drush core-status drush-version --pipe' => 'Emit just the drush version with no label.', + 'drush core-status config-sync --pipe' => 'Emit just the sync Config directory with no label.', + ), + 'arguments' => array( + 'item' => 'Optional. The status item line(s) to display.', + ), + 'options' => array( + 'show-passwords' => 'Show database password. Defaults to --no-show-passwords.', + 'full' => 'Show all file paths and drush aliases in the report, even if there are a lot.', + 'project' => array( + 'description' => 'One or more projects that should be added to the path list', + 'example-value' => 'foo,bar', + ), + ), + 'outputformat' => array( + 'default' => 'key-value', + 'pipe-format' => 'json', + 'field-labels' => array('backdrop-version' => 'Backdrop version', 'uri' => 'Site URI', 'db-driver' => 'Database driver', 'db-hostname' => 'Database hostname', 'db-port' => 'Database port', 'db-username' => 'Database username', 'db-password' => 'Database password', 'db-name' => 'Database name', 'db-status' => 'Database', 'bootstrap' => 'Backdrop bootstrap', 'user' => 'Drupal user', 'theme' => 'Default theme', 'admin-theme' => 'Administration theme', 'php-bin' => 'PHP executable', 'php-conf' => 'PHP configuration', 'php-os' => 'PHP OS', 'drush-script' => 'Drush script', 'drush-version' => 'Drush version', 'drush-temp' => 'Drush temp directory', 'drush-conf' => 'Drush configuration', 'drush-alias-files' => 'Drush alias files', 'install-profile' => 'Install profile', 'root' => 'Drupal root', 'drupal-settings-file' => 'Backdrop Settings File', 'site-path' => 'Site path', 'root' => 'Drupal root', 'site' => 'Site path', 'themes' => 'Themes path', 'modules' => 'Modules path', 'files' => 'File directory path', 'private' => 'Private file directory path', 'temp' => 'Temporary file directory path', 'config-sync' => 'Sync config path', 'files-path' => 'File directory path', 'temp-path' => 'Temporary file directory path', '%paths' => 'Other paths'), + 'formatted-filter' => '_drush_backdrop_core_status_format_table_data', + 'private-fields' => 'db-password', + 'simplify-single' => TRUE, + 'table-metadata' => array( + 'list-separator' => ' ', + ), + 'output-data-type' => 'format-list', + ), + 'topics' => array('docs-readme'), + ); + + $items['backdrop-unsupported'] = array( + 'description' => 'Fallback command if the provided command is not supported in Backdrop.', + 'callback' => 'backdrop_command_unsupported', + 'hidden' => TRUE, + ); + + return $items; +} + +/** + * Command callback. Runs all cron hooks. + */ +function backdrop_command_cron() { + $result = backdrop_cron_run(); + + if ($result) { + drush_log(dt('Cron run successful.'), 'success'); + } + else { + return drush_set_error('DRUSH_CRON_FAILED', dt('Cron run failed.')); + } + + return TRUE; +} + +/** + * Command handler. Execute update.php code from drush. + */ +function backdrop_command_updatedb() { + if (drush_get_context('DRUSH_SIMULATE')) { + drush_log(dt('updatedb command does not support --simulate option.'), 'ok'); + return TRUE; + } + + include_once __DIR__ . '/../includes/update.inc'; + if (update_main() === FALSE) { + return FALSE; + } + + drush_log(dt('Finished performing updates.'), 'ok'); + return TRUE; +} + + +/** + * Command handler. Run a single update via the batch API. + */ +function backdrop_command_updatedb_batch_process($id) { + include_once __DIR__ . '/../includes/update.inc'; + _update_batch_command($id); +} + +/** + * Command handler. List pending DB updates. + */ +function backdrop_command_updatedb_status() { + require_once DRUSH_BACKDROP_CORE . '/includes/install.inc'; + backdrop_load_updates(); + + include_once __DIR__ . '/../includes/update.inc'; + list($pending) = updatedb_status(); + if (empty($pending)) { + drush_log(dt('No database updates required'), 'ok'); + } + return $pending; +} + +/** + * Helper function for _backdrop_core_site_status_table(). + */ +function _drush_backdrop_core_is_named_in_array($key, $the_array) { + $is_named = FALSE; + + $simplified_key = str_replace(array(' ', '_', '-'), array('', '', ''), $key); + + foreach ($the_array as $name) { + if (stristr($simplified_key, str_replace(array(' ', '_', '-'), array('', '', ''), $name))) { + $is_named = TRUE; + } + } + + return $is_named; +} + +// Adjust the status output for any non-pipe output format +function _drush_backdrop_core_status_format_table_data($output, $metadata) { + if (drush_get_option('full', FALSE) == FALSE) { + // Hide any key that begins with a % + foreach ($output as $key => $value) { + if ($key[0] == '%') { + unset($output[$key]); + } + } + // Hide 'Modules path' and 'Themes path' as well + unset($output['modules']); + unset($output['themes']); + // Shorten the list of alias files if there are too many + if (isset($output['drush-alias-files']) && count($output['drush-alias-files']) > 24) { + $msg = dt("\nThere are !count more alias files. Run with --full to see the full list.", array('!count' => count($output['drush-alias-files']) - 1)); + $output['drush-alias-files'] = array($output['drush-alias-files'][0] , $msg); + } + if (isset($output['drupal-settings-file']) && empty($output['drupal-settings-file'])) { + $output['drupal-settings-file'] = dt('MISSING'); + } + } + return $output; +} + +/** + * Helper function for backdrop_core_status() + */ +function _backdrop_core_site_status_table($project = '') { + $phase = drush_get_context('DRUSH_BOOTSTRAP_PHASE'); + if ($backdrop_root = drush_get_context('DRUSH_BACKDROP_ROOT')) { + + $boot_object = drush_get_bootstrap_object(); + $status_table['backdrop-version'] = $boot_object->get_version($backdrop_root); + $conf_dir = $boot_object->conf_path(); + $settings_file = "$conf_dir/settings.php"; + $status_table['drupal-settings-file'] = file_exists($settings_file) ? $settings_file : ''; + if ($site_root = drush_get_context('DRUSH_BACKDROP_SITE_ROOT')) { + $status_table['uri'] = drush_get_context('DRUSH_URI'); + try { + $sql = drush_sql_get_class(); + $db_spec = $sql->db_spec(); + $status_table['db-driver'] = $db_spec['driver']; + if (!empty($db_spec['unix_socket'])) { + $status_table['db-socket'] = $db_spec['unix_socket']; + } + elseif (isset($db_spec['host'])) { + $status_table['db-hostname'] = $db_spec['host']; + } + $status_table['db-username'] = isset($db_spec['username']) ? $db_spec['username'] : NULL; + $status_table['db-password'] = isset($db_spec['password']) ? $db_spec['password'] : NULL; + $status_table['db-name'] = isset($db_spec['database']) ? $db_spec['database'] : NULL; + $status_table['db-port'] = isset($db_spec['port']) ? $db_spec['port'] : NULL; + + if ($phase > DRUSH_BOOTSTRAP_BACKDROP_CONFIGURATION) { + $status_table['install-profile'] = $boot_object->get_profile(); + if ($phase > DRUSH_BOOTSTRAP_BACKDROP_FULL) { + $status_table['bootstrap'] = dt('Successful'); + if ($phase == DRUSH_BOOTSTRAP_BACKDROP_LOGIN) { + $status_table['user'] = drush_user_get_class()->getCurrentUserAsSingle()->getUsername(); + } + } + } + } + catch (Exception $e) { + // Don't worry be happy. + } + } + // TODO: what is going on here gff. + // if (drush_has_boostrapped(DRUSH_BOOTSTRAP_BACKDROP_FULL)) { + // $status_table['theme'] = drush_theme_get_default(); + // $status_table['admin-theme'] = drush_theme_get_admin(); + // } + } + if ($php_bin = drush_get_option('php')) { + $status_table['php-bin'] = $php_bin; + } + $status_table['php-os'] = PHP_OS; + if ($php_ini_files = _drush_core_config_php_ini_files()) { + $status_table['php-conf'] = $php_ini_files; + } + $status_table['drush-script'] = DRUSH_COMMAND; + $status_table['drush-version'] = DRUSH_VERSION; + $status_table['drush-temp'] = drush_find_tmp(); + $status_table['drush-conf'] = drush_flatten_array(drush_get_context_options('context-path', '')); + $alias_files = _drush_sitealias_find_alias_files(); + $status_table['drush-alias-files'] = $alias_files; + + $paths = _core_path_aliases($project); + if (!empty($paths)) { + foreach ($paths as $target => $one_path) { + $name = $target; + if (substr($name,0,1) == '%') { + $name = substr($name,1); + } + $status_table[$name] = $one_path; + } + } + + // Store the paths into the '%paths' index; this will be + // used by other code, but will not be included in the output + // of the drush status command. + $status_table['%paths'] = $paths; + + return $status_table; +} + +/** + * Command callback. Provides a birds-eye view of the current Backdrop + * installation. + */ +function backdrop_command_core_status() { + $status_table = _backdrop_core_site_status_table(drush_get_option('project','')); + // If args are specified, filter out any entry that is not named + // (in other words, only show lines named by one of the arg values) + $args = func_get_args(); + if (!empty($args)) { + $field_list = $args; + $metadata = drush_get_command_format_metadata('backdrop-core-status'); + foreach ($metadata['field-labels'] as $field_key => $field_label) { + if (_drush_backdrop_core_is_named_in_array($field_label, $args)) { + $field_list[] = $field_key; + } + } + foreach ($status_table as $key => $value) { + if (!_drush_backdrop_core_is_named_in_array($key, $field_list)) { + unset($status_table[$key]); + } + } + } + return $status_table; +} + +/** + * Command callback. Informs the user that the given command is not supported. + */ +function backdrop_command_unsupported() { + drush_log(dt('This command is not supported yet by Backdrop.'), 'failed'); + return FALSE; +} diff --git a/vendor/drush/drush/commands/backdrop/commands/pm/backdrop_pm.drush.inc b/vendor/drush/drush/commands/backdrop/commands/pm/backdrop_pm.drush.inc new file mode 100755 index 0000000000..f4a94c57ca --- /dev/null +++ b/vendor/drush/drush/commands/backdrop/commands/pm/backdrop_pm.drush.inc @@ -0,0 +1,451 @@ + 'Download Backdrop CMS contrib modules.', + 'callback' => 'backdrop_command_pm_download', + 'hidden' => TRUE, + 'arguments' => array( + 'module-name' => array('The name of the module(s) you would like to download.'), + ), + 'options' => array( + 'select' => 'Select the version of the module.', + ), + 'required-arguments' => TRUE, + 'bootstrap' => \Drush\Boot\BackdropBoot::BOOTSTRAP_SITE, + ); + $items['backdrop-pm-update'] = array( + 'description' => 'Update Backdrop core or contrib modules.', + 'callback' => 'backdrop_command_pm_update', + 'arguments' => array( + 'module-name' => array('The name of the module(s) you would like to update.'), + ), + // TODO: show available updates and allow for selection of which to apply. + //'options' => array( + // 'show' => "Select from available updates:" + //), + 'aliases' => array('up',), + 'required-arguments' => TRUE, + 'bootstrap' => \Drush\Boot\BackdropBoot::BOOTSTRAP_SITE, + ); + return $items; +} + +/** + * Command callback. Download a Backdrop CMS contrib project. + * + * @param $projects + * Array of Backdrop CMS contrib projects to download. + */ +function backdrop_command_pm_download() { + $package_handler = backdrop_command_pm_update_validate(); + if (!$package_handler) { + return; + } + $projects = func_get_args(); + $options = drush_get_option('select', ''); + + if (!empty($options)) { + drush_print_r("\t\033[32mChoose one of the available releases for $projects[0] \n"); + // get available releases. + $tags = backdrop_command_get_tags( + "https://updates.backdropcms.org/release-history/$projects[0]/1.x" + ); + + $project_path = backdrop_pm_get_path($tags); + + for($i = 0; $i < count($tags->releases->release); $i++) { + $version = (string) $tags->releases->release[$i]->version; + drush_print_r("\t\t [$i] \t:\t " . $version); + } + // get users's menu selection + $handle = fopen ("php://stdin","r"); + print("\n\tSelect one [0]: "); + print("\033[0m"); + $selection = fgets($handle); + $selection = trim($selection); + // default to latest release if user just hits enter. + if (empty($selection)) { + $selection = 0; + } + + $sel_url = $tags->releases->release[(int) $selection]->download_link; + + // download verssion of the project user selected. + exec( + "wget --quiet --O $project_path/$projects[0].zip $sel_url" + ); + // Extract the zip file. + exec( + "unzip $project_path/$projects[0].zip -d $project_path" + ); + // Remove the zip file. + exec( + "rm $project_path/$projects[0].zip" + ); + backdrop_pm_dl_outcome($project_path, $projects[0]); + } + else { + foreach ($projects as $project) { + if ($project != 'backdrop') { + $html = backdrop_pm_get_from_github( + "https://github.com/backdrop-contrib/$project/releases/latest" + ); + + // Get the release info from backdropcms.org. + $tags = backdrop_pm_get_tags( + "https://updates.backdropcms.org/release-history/$project/1.x" + ); + + $project_path = backdrop_pm_get_path($tags); + $html = explode("\"", $html); + $url = $html[1]; + $latest = explode('/', $url); + $latest = array_reverse($latest); + $module_install_location = $project_path . '/' . $project; + + if (is_dir($module_install_location)) { + drush_log(dt('Module is already installed ... exiting without re-writing module.'), 'error'); + continue; + } + exec( + "wget --quiet -O $project_path/$project.zip https://github.com/backdrop-contrib/$project/releases/download/$latest[0]/$project.zip" + ); + // Extract the zip file. + exec( + "unzip $project_path/$project.zip -d $project_path" + ); + // Remove the zip file. + exec( + "rm $project_path/$project.zip" + ); + backdrop_pm_dl_outcome($module_install_location, $project); + } + // Downloading backdrop itself is a special case. + // TODO: Downloading backdrop itself does not currently work since + // the backdrop drush command hooks are only picked up once a valid + // backdrop installation is detected, so we have a chicken and the egg + // problem here. + elseif ($project == 'backdrop') { + $html = backdrop_pm_get_from_github( + "https://github.com/backdrop/backdrop/releases/latest" + ); + + $html = explode("\"", $html); + $url = $html[1]; + $latest = explode('/', $url); + $latest = array_reverse($latest); + + // Get the core zip file. + exec( + "wget --quiet -O . https://github.com/$project/$project/releases/download/$latest[0]/backdrop.zip" + ); + // Extract the zip file. + exec( + "unzip backdrop.zip" + ); + // Remove the zip file. + exec( + "rm backdrop.zip" + ); + backdrop_pm_dl_outcome('.', 'Backdrop CMS'); + } + } + } +} + +/** + * Helper function for backdrop_command_pm_download(). + * + * Gets the url for the github repo of the contrib module. + */ +function backdrop_pm_get_from_github($url) { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 1); + $content = curl_exec($ch); + curl_close($ch); + return $content; +} + +/** + * Helper function for --select option for download command. + * Gets the tags for a project. + */ +function backdrop_pm_get_tags($url) { + $xml = simplexml_load_file($url); + return $xml; +} + +/** + * Get the path to where to store the module, theme, or layout + * determine project_type from the $tags xml object. + * @param $tags + * xml object. + */ +function backdrop_pm_get_path($tags) { + $type = (string)$tags->type; + if ($type == 'project_module') { + if (file_exists(BACKDROP_ROOT . '/modules/contrib')) { + $project_path = BACKDROP_ROOT . '/modules/contrib'; + } + else { + $project_path = BACKDROP_ROOT . '/modules'; + } + } + elseif ($type == 'project_theme') { + $project_path = BACKDROP_ROOT . '/themes'; + } + elseif ($type == 'project_layout') { + $project_path = BACKDROP_ROOT . '/layouts'; + } + return $project_path; +} + +/** + * Determine success or failure of drush dl command and give the user some + * relevent feedback. + * @param string $module_install_location + * String directory path where module should end up. + * + * @param string $project + * Project name. + */ +function backdrop_pm_dl_outcome($module_install_location, $project) { + if (file_exists($module_install_location)) { + print "\n\t\033[32m Success: \033[0m Project $project downloaded to $module_install_location.\n\n"; + } + else { + print "\n\t\033[31m Error: \033[0m Project $project could not be found.\n\n"; + } +} + +/** + * Command pm-update validate function. + */ +function backdrop_command_pm_update_validate() { + // Check that wget command exists. Disable possible output. + $debug = drush_get_context('DRUSH_DEBUG'); + drush_set_context('DRUSH_DEBUG', FALSE); + $success = drush_shell_exec('wget --version'); + drush_set_context('DRUSH_DEBUG', $debug); + if (!$success) { + return drush_set_error( + 'DRUSH_SHELL_COMMAND_NOT_FOUND', + dt("\n\t\033[1mwget\033[0m executable not found. + Downloading projects with drush depends on wget consider installing wget on your system.\n") + ); + } + return TRUE; +} + +/** + * Command callback for pm-update. + */ +function backdrop_command_pm_update() { + // Call validate function to check for required curl or wget. + $package = backdrop_command_pm_update_validate(); + // If neither curl or wget are available bail. + if (!$package) { + return; + } + $projects = func_get_args(); + $projects[0] = strtolower($projects[0]); + $backdrop_root = BACKDROP_ROOT; + if ($projects[0] === 'backdrop' || $projects[0] == 'core') { + // get current Backdrop version info. + $current_version = BACKDROP_VERSION; + $parse_version = explode('.', $current_version); + $current_version_major = $parse_version[0]; + $current_version_minor = $parse_version[1]; + $current_version_patch = $parse_version[2]; + // Get proposed Backdrop version info. + $xml = backdrop_pm_get_tags("https://updates.backdropcms.org/release-history/backdrop/1.x"); + $download_link = $xml->releases->release->download_link; + $proposed_version = $xml->releases->release->version; + $proposed_version_major = $xml->releases->release->version_major; + $proposed_version_minor = $xml->releases->release->version_minor; + $proposed_version_patch = $xml->releases->release->version_patch; + + if ($current_version == $proposed_version) { + drush_print_r("\n\t\tBackdrop is up to date :).\n"); + } + elseif ($current_version_major < $proposed_version_major) { + drush_print_r("\n\t\tYou can not use drush up to move between Major Versions of Backdrop."); + } + elseif ( + ( + $current_version_major == $proposed_version_major && + $current_version_minor == $proposed_version_minor && + $current_version_patch < $proposed_version_patch + ) || + ( + $current_version_major == $proposed_version_major && + $current_version_minor < $proposed_version_minor + ) + ) { + drush_print_r("\n\t\tCurrent Backdrop Version: $current_version + \t\tProposed Backdrop Version: $proposed_version\n"); + drush_print_r("\tCode updates will be made to Backdrop core.\n"); + drush_print_r("\t\e[33mWARNING\e[0m: Updating core will discard any"); + drush_print_r("\tmodifications made to Backdrop core files. If you have made any"); + drush_print_r("\tmodifications to these files, please back them up before updating so that"); + drush_print_r("\tyou can re-create your modifications in the updated version of the file.\n\n"); + drush_print_r("\tNote: Updating core can potentially break your site."); + drush_print_r("\tIt is NOT recommended to update production sites without prior testing.\n\n"); + $answer = drush_prompt("\t\e[34mDo you wish to continue?\e[0m (y or n)"); + $answer = strtolower($answer); + if ($answer == 'y' || $answer == 'yes') { + $users_home_directory = $_SERVER['HOME']; + $site_name = strtolower(str_replace(' ', '_', config_get('system.core', 'site_name'))); + drush_print_r("\tBacking up current core folder to $users_home_directory/.drush/$site_name-core-$current_version"); + exec("mkdir -p $users_home_directory/.drush/drush-tmp"); + if (!file_exists("$users_home_directory/.drush/drush-tmp/")) { + drush_print_r("\t\e[31mFailed\e[0m: The drush-tmp directory could not be created.\n\n"); + return FALSE; + } + exec("wget --quiet -O $users_home_directory/.drush/drush-tmp/backdrop.zip $download_link"); + exec("unzip $users_home_directory/.drush/drush-tmp/backdrop.zip -d $users_home_directory/.drush/drush-tmp"); + foreach (glob("$users_home_directory/.drush/drush-tmp/backdrop-backdrop*") as $glob) { + rename($glob, "$users_home_directory/.drush/drush-tmp/backdrop"); + } + if (file_exists("$users_home_directory/.drush/drush-tmp/backdrop")) { + // Backup current core directory. + exec("mv $backdrop_root/core $users_home_directory/.drush/$site_name-core-$current_version"); + exec("mv $users_home_directory/.drush/drush-tmp/backdrop/core $backdrop_root/core"); + exec("rm -r $users_home_directory/.drush/drush-tmp"); + } + else { + drush_print_r("\t\e[031mError\e[0m: New version of backdrop could not be found."); + return FALSE; + } + backdrop_command_updatedb(); + drush_print_r("\n\tYou can retrieve your old core files, should you need + to from: $users_home_directory/.drush/core-$current_version"); + if (file_exists("$backdrop_root/core")) { + drush_print_r("\t\e[32mSuccess\033[0m: Backdrop updated to $proposed_version."); + } + else { + drush_print_r("\t\033[31mFailed\033[0m: Upgrade failed."); + } + } + else { + drush_print_r("\tBackdrop core update cancelled."); + } + } + else { + drush_print_r("\n\tNo Backdrop core updates available. Your Backdrop is up to date!\n"); + } + } // end of drush up backdrop. + elseif (!empty($projects[0])) { + if (file_exists("$backdrop_root/modules/contrib")) { + $module_path = "$backdrop_root/modules/contrib"; + } + else { + $module_path = "$backdrop_root/modules"; + } + $ls = scandir($module_path); + $projects[0] = strtolower($projects[0]); + if (in_array($projects[0], $ls)) { + $info = file("$module_path/$projects[0]/$projects[0].info", FILE_IGNORE_NEW_LINES); + foreach($info as $i) { + if (strpos($i, 'version =') !== FALSE) { + $module_version_raw = $i; + } + } + $module_version_arr = explode(' = ', $module_version_raw); + $module_full_version = trim($module_version_arr[1]); + $module_sem_version_arr = explode('-', $module_version_arr[1]); + $module_sem_version = trim($module_sem_version_arr[1]); + $module_core_compat = trim($module_sem_version_arr[0]); + $module_sem_version_parts = explode('.', $module_sem_version); + $module_version_major = $module_sem_version_parts[0]; + $module_version_minor = $module_sem_version_parts[1]; + $module_version_patch = $module_sem_version_parts[2]; + // Get proposed Backdrop version info. + $project = $projects[0]; + $xml = backdrop_pm_get_tags("https://updates.backdropcms.org/release-history/$project/1.x"); + $download_link = $xml->releases->release->download_link; + $proposed_version = $xml->releases->release->version; + $proposed_version_major = $xml->releases->release->version_major; + $proposed_version_minor = $xml->releases->release->version_minor; + $proposed_version_patch = $xml->releases->release->version_patch; + + if (empty($proposed_version)) { + drush_print_r("\tThere are no official releases for \033[1m$project\033[0m. + Upgrading is not possible. + "); + } + elseif ($module_full_version == $proposed_version) { + drush_print_r("\n\t\tProject \033[1m$project\033[0m is up to date :).\n"); + } + elseif ( + ( + $module_version_major == $proposed_version_major && + $module_version_minor == $proposed_version_minor && + $module_version_patch < $proposed_version_patch + ) || + ( + $module_version_major == $proposed_version_major && + $module_version_minor < $proposed_version_minor + ) + ) { + drush_print_r("\n\t\tCurrent \033[1m$project\033[0m Version: $module_full_version"); + drush_print_r("\t\tProposed \033[1m$project\033[0m Version: $proposed_version\n"); + drush_print_r("\t\e[33mWARNING\033[0m: Updating \033[1m$project\033[0m will discard any + modifications made to \033[1m$project\033[0m files. If you have made any + modifications to these files, please back them up before updating so that + you can re-create your modifications in the updated version of the file.\n\n"); + drush_print_r("\tNote: Updating modules can potentially break your site. + It is NOT recommended to update production sites without prior testing.\n\n"); + $answer = drush_prompt("\t\e[34mDo you wish to continue?\e[0m (y or n)"); + $answer = strtolower($answer); + if ($answer == 'y' || $answer == 'yes') { + $users_home_directory = $_SERVER['HOME']; + $site_name = config_get('system.core', 'site_name'); + drush_print_r("\t Backing up current \033[1m$project\033[0m folder to + $users_home_directory/.drush/$site_name-$project-$module_full_version"); + exec("mv $module_path/$project $users_home_directory/.drush/$site_name-$project-$module_full_version"); + exec("mkdir -p $users_home_directory/.drush/drush-tmp"); + exec("wget --quiet -O $users_home_directory/.drush/drush-tmp/$project.zip $download_link"); + exec("unzip $users_home_directory/.drush/drush-tmp/$project.zip -d $users_home_directory/.drush/drush-tmp"); + foreach (glob("$users_home_directory/.drush/drush-tmp/backdrop-contrib-$project-*") as $glob) { + rename($glob, "$users_home_directory/.drush/drush-tmp/$project"); + } + exec("mv $users_home_directory/.drush/drush-tmp/$project $module_path/$project"); + exec("rm -r $users_home_directory/.drush/drush-tmp"); + exec("drush updb -y"); + drush_print_r("\n\tYou can retrieve your old \033[1m$project\033[0m files, should you need + to, from: $users_home_directory/.drush/$site_name-$project-$module_full_version"); + if (file_exists("$module_path/$project")) { + drush_print_r("\t\e[32mSuccess\033[0m: Project \033[1m$project\033[0m updated to $proposed_version."); + } + else { + drush_print_r("\t\033[31mFailed\033[0m: Project $project upgrade failed."); + } + + } + else { + drush_print_r("\tBackdrop core update cancelled."); + } + } + } + else { + drush_print_r("\n\t\e[33mWARNING\033[0m: There is no project \e[1m$projects[0]\033[0m intalled on your backdrop site."); + drush_print_r("\tNo action taken.\n"); + } + } // end of drush up $project[0] + else { + drush_print_r("\n\tYou must specify a project to update. For example:\n"); + drush_print_r("\t\tdrush up project_name"); + drush_print_r("\n\tWhere project_name is the machine name of the project like webform or devel etc.\n"); + } +} diff --git a/vendor/drush/drush/commands/backdrop/commands/pm/backdrop_pm_enable.drush.inc b/vendor/drush/drush/commands/backdrop/commands/pm/backdrop_pm_enable.drush.inc new file mode 100755 index 0000000000..586a7b1724 --- /dev/null +++ b/vendor/drush/drush/commands/backdrop/commands/pm/backdrop_pm_enable.drush.inc @@ -0,0 +1,111 @@ + 'Enable backdrop modules.', + 'callback' => 'backdrop_command_pm_enable', + 'hidden' => TRUE, + 'arguments' => array( + 'module-name' => array('The name of the module(s) you would like to enable.'), + ), + 'required-arguments' => TRUE, + 'aliases' => array('en', 'pm-enable'), + 'bootstrap' => \Drush\Boot\BackdropBoot::BOOTSTRAP_FULL, + ); + + return $items; +} + +/** + * Command callback enable modules. + * + * @see _enable_project(). + */ +function backdrop_command_pm_enable() { + $projects = func_get_args(); + + if (!isset($projects)) { + drush_print_r(dt("\tPlease provide module name(s)\n\n")); + return; + } + + $clear_cache = FALSE; + foreach($projects as $project ) { + if (_enable_project($project)) { + $clear_cache = TRUE; + } + } + + if ($clear_cache) { + backdrop_flush_all_caches(); + } +} + +/** + * Internal function to enable module or theme. + * + * @param string $project + * The project machine name to be enabled. + * + * @return bool + * + * @see backdrop_command_pm_enable_validate() + */ +function _enable_project($project) { + $project_exists = backdrop_command_pm_enable_validate($project); + // If the $project directory does not exist then gracefully fail. + if (!$project_exists) { + drush_print_r("\n\t\e[031mError\e[0m $project does not exist in your Backdrop installation."); + drush_print_r("\tTry downloading $project first with the command: drush dl $project\n"); + return FALSE; + } + $query = db_select('system', 's') + ->fields('s'); + $query->condition('name', $project); + $query->condition('type', 'module'); + $module = $query->execute()->fetchAssoc(); + + if ($module['status']) { + drush_print_r( + "\n\t\e[31m Failed\e[0m to enable module " . $module['name']. ": it is already enabled.\n" + ); + return FALSE; + } + + if (!drush_confirm(dt("Do you want to enable $project?"))) { + drush_print_r( + dt("\n\t\e[033mCancelled\e[0m $project not enabled.\n") + ); + return FALSE; + } + + if (module_enable(array($project), FALSE)) { + drush_print_r("\n\t\e[32mSuccess\e[0m module $project enabled.\n"); + return TRUE; + } + drush_print_r("\n\t\e[31mFailed\e[0m to enable module " . $project); + return FALSE; +} + +/** + * Command pm-update validate function. + * + * @param string $project + * The project that the user is attempting to enable. + */ +function backdrop_command_pm_enable_validate($project) { + $file = file_exists(BACKDROP_ROOT . "/modules/contrib/$project") || + file_exists(BACKDROP_ROOT . "/modules/$project"); + if ($file) { + return TRUE; + } + return FALSE; +} diff --git a/vendor/drush/drush/commands/backdrop/commands/user/backdrop_user.drush.inc b/vendor/drush/drush/commands/backdrop/commands/user/backdrop_user.drush.inc new file mode 100755 index 0000000000..1bc6e87456 --- /dev/null +++ b/vendor/drush/drush/commands/backdrop/commands/user/backdrop_user.drush.inc @@ -0,0 +1,109 @@ + 'Display a one time login link for the given user account (defaults to uid 1).', + 'aliases' => array('uli'), + 'callback' => 'backdrop_command_user_login', + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'handle-remote-commands' => TRUE, + 'arguments' => array( + 'user' => 'An optional uid, user name, or email address for the user to log in as. Default is to log in as uid 1. The uid/name/mail options take priority if specified.', + 'path' => 'Optional path to redirect to after logging in.', + ), + 'options' => array( + 'browser' => 'Optional value denotes which browser to use (defaults to operating system default). Set to 0 to suppress opening a browser.', + 'uid' => 'A uid to log in as.', + 'name' => 'A user name to log in as.', + 'mail' => 'A user mail address to log in as.', + ), + 'examples' => array( + 'drush user-login ryan node/add/blog' => 'Displays and opens default web browser (if configured or detected) for a one-time login link for the user with the username ryan and redirect to the path node/add/blog.', + 'drush user-login --browser=firefox --mail=drush@example.org admin/settings/performance' => 'Open firefox web browser, login as the user with the e-mail address drush@example.org and redirect to the path admin/settings/performance.', + ), + ); + return $items; +} + +/** + * Command callback for user-login. + * Displays a one time login link for the given user. + * + * @param string $user + * Optional $user to login defaults to uid = 1. + * + * @param string $path + * Optional $path redirects user to $path once logged in. + */ +function backdrop_command_user_login($user = NULL, $path = NULL) { + // Fix up arguments based on our assumptions. + $user_object = $uid = FALSE; + $args = func_get_args(); + + // Redispatch if called against a remote-host so a browser is started on the + // the *local* machine. + $alias = drush_get_context('DRUSH_TARGET_SITE_ALIAS'); + if (drush_sitealias_is_remote_site($alias)) { + $return = drush_invoke_process($alias, 'user-login', $args, drush_redispatch_get_options(), array('integrate' => FALSE)); + if ($return['error_status']) { + return drush_set_error('Unable to execute user login.'); + } + else { + $link = $return['object']; + } + } + else { + if (!drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_FULL)) { + // Fail gracefully if unable to bootstrap Drupal. + // drush_bootstrap() has already logged an error. + return FALSE; + } + + if (drush_get_option('uid', FALSE) || drush_get_option('name', FALSE) || drush_get_option('mail', FALSE)) { + // A user option was passed, prefer that to the user argument. + $user = NULL; + // If we only have a single argument and one of the user options is passed, + // then we assume the argument is the path to open. + if (count($args) == 1) { + $path = $args[0]; + } + } + else if (empty($user)) { + // No user option or argument was passed, so we default to uid 1. + $uid = 1; + } + + // Try to load a user from provided options and arguments. + if ($uid || $uid = reset(_drush_user_get_users_from_options_and_arguments($user))) { + if ($user_object = user_load($uid)) { + if ($user_object->status) { + $options = array(); + if ($path) { + $options['query']['destination'] = $path; + } + $link = url(user_pass_reset_url($user_object) . '/login', $options); + } + else { + return drush_set_error("The user account $uid is blocked."); + } + } + else { + return drush_set_error("The user account for uid $uid could not be loaded."); + } + } + else { + return FALSE; // An error has already been logged. + } + } + + drush_start_browser($link); + return $link; +} diff --git a/vendor/drush/drush/commands/backdrop/includes/batch.inc b/vendor/drush/drush/commands/backdrop/includes/batch.inc new file mode 100755 index 0000000000..93d2faadee --- /dev/null +++ b/vendor/drush/drush/commands/backdrop/includes/batch.inc @@ -0,0 +1,252 @@ + 0, + ); + $batch += $process_info; + + // The batch is now completely built. Allow other modules to make changes + // to the batch so that it is easier to reuse batch processes in other + // enviroments. + backdrop_alter('batch', $batch); + + // Assign an arbitrary id: don't rely on a serial column in the 'batch' + // table, since non-progressive batches skip database storage completely. + $batch['id'] = db_next_id(); + $args[] = $batch['id']; + + $batch['progressive'] = TRUE; + + // Move operations to a job queue. Non-progressive batches will use a + // memory-based queue. + foreach ($batch['sets'] as $key => $batch_set) { + _batch_populate_queue($batch, $key); + } + + // Store the batch. + db_insert('batch') + ->fields(array( + 'bid' => $batch['id'], + 'timestamp' => REQUEST_TIME, + 'token' => drush_get_token($batch['id']), + 'batch' => serialize($batch), + )) + ->execute(); + $finished = FALSE; + + while (!$finished) { + $data = drush_invoke_process('@self', $command, $args, array($batch['id'], '-u', $GLOBALS['user']->uid)); + + $finished = drush_get_error() || !$data || (isset($data['context']['drush_batch_process_finished']) && $data['context']['drush_batch_process_finished'] == TRUE); + } + } +} + + +/** + * Initialize the batch command and call the worker function. + * + * Loads the batch record from the database and sets up the requirements + * for the worker, such as registering the shutdown function. + * + * @param id + * The batch id of the batch being processed. + */ +function _drush_batch_command($id) { + $batch =& batch_get(); + + $data = db_query("SELECT batch FROM {batch} WHERE bid = :bid", array( + ':bid' => $id))->fetchField(); + + if ($data) { + $batch = unserialize($data); + } + else { + return FALSE; + } + + if (!isset($batch['running'])) { + $batch['running'] = TRUE; + } + + // Register database update for end of processing. + register_shutdown_function('_drush_batch_shutdown'); + + if (_drush_batch_worker()) { + _drush_batch_finished(); + } +} + + +/** + * Process batch operations + * + * Using the current $batch process each of the operations until the batch + * has been completed or half of the available memory for the process has been + * reached. + */ +function _drush_batch_worker() { + $batch =& batch_get(); + $current_set =& _batch_current_set(); + $set_changed = TRUE; + + if (empty($current_set['start'])) { + $current_set['start'] = microtime(TRUE); + } + $queue = _batch_queue($current_set); + while (!$current_set['success']) { + // If this is the first time we iterate this batch set in the current + // request, we check if it requires an additional file for functions + // definitions. + if ($set_changed && isset($current_set['file']) && is_file($current_set['file'])) { + include_once BACKDROP_ROOT . '/' . $current_set['file']; + } + + $task_message = ''; + // Assume a single pass operation and set the completion level to 1 by + // default. + $finished = 1; + + if ($item = $queue->claimItem()) { + list($function, $args) = $item->data; + + // Build the 'context' array and execute the function call. + $batch_context = array( + 'sandbox' => &$current_set['sandbox'], + 'results' => &$current_set['results'], + 'finished' => &$finished, + 'message' => &$task_message, + ); + // Magic wrap to catch changes to 'message' key. + $batch_context = new DrushBatchContext($batch_context); + + call_user_func_array($function, array_merge($args, array(&$batch_context))); + $finished = $batch_context['finished']; + if ($finished >= 1) { + // Make sure this step is not counted twice when computing $current. + $finished = 0; + // Remove the processed operation and clear the sandbox. + $queue->deleteItem($item); + $current_set['count']--; + $current_set['sandbox'] = array(); + } + } + + // When all operations in the current batch set are completed, browse + // through the remaining sets, marking them 'successfully processed' + // along the way, until we find a set that contains operations. + // _batch_next_set() executes form submit handlers stored in 'control' + // sets (see form_execute_handlers()), which can in turn add new sets to + // the batch. + $set_changed = FALSE; + $old_set = $current_set; + while (empty($current_set['count']) && ($current_set['success'] = TRUE) && _batch_next_set()) { + $current_set = &_batch_current_set(); + $current_set['start'] = microtime(TRUE); + $set_changed = TRUE; + } + + // At this point, either $current_set contains operations that need to be + // processed or all sets have been completed. + $queue = _batch_queue($current_set); + + // If we are in progressive mode, break processing after 1 second. + if (drush_memory_limit() > 0 && (memory_get_usage() * 2) >= drush_memory_limit()) { + drush_log(dt("Batch process has consumed in excess of 50% of available memory. Starting new thread"), "batch"); + // Record elapsed wall clock time. + $current_set['elapsed'] = round((microtime(TRUE) - $current_set['start']) * 1000, 2); + break; + } + } + + // Reporting 100% progress will cause the whole batch to be considered + // processed. If processing was paused right after moving to a new set, + // we have to use the info from the new (unprocessed) set. + if ($set_changed && isset($current_set['queue'])) { + // Processing will continue with a fresh batch set. + $remaining = $current_set['count']; + $total = $current_set['total']; + $progress_message = $current_set['init_message']; + $task_message = ''; + } + else { + // Processing will continue with the current batch set. + $remaining = $old_set['count']; + $total = $old_set['total']; + $progress_message = $old_set['progress_message']; + } + + $current = $total - $remaining + $finished; + $percentage = _batch_api_percentage($total, $current); + return ($percentage == 100); +} + +/** + * End the batch processing: + * Call the 'finished' callbacks to allow custom handling of results, + * and resolve page redirection. + */ +function _drush_batch_finished() { + $batch = &batch_get(); + + // Execute the 'finished' callbacks for each batch set, if defined. + foreach ($batch['sets'] as $batch_set) { + if (isset($batch_set['finished'])) { + // Check if the set requires an additional file for function definitions. + if (isset($batch_set['file']) && is_file($batch_set['file'])) { + include_once BACKDROP_ROOT . '/' . $batch_set['file']; + } + if (is_callable($batch_set['finished'])) { + $queue = _batch_queue($batch_set); + $operations = $queue->getAllItems(); + $elapsed = $batch_set['elapsed'] / 1000; + $elapsed = format_interval($elapsed); + $batch_set['finished']($batch_set['success'], $batch_set['results'], $operations, $elapsed); + } + } + } + + // Clean up the batch table and unset the static $batch variable. + db_delete('batch') + ->condition('bid', $batch['id']) + ->execute(); + foreach ($batch['sets'] as $batch_set) { + if ($queue = _batch_queue($batch_set)) { + $queue->deleteQueue(); + } + } + $_batch = $batch; + $batch = NULL; + drush_set_option('drush_batch_process_finished', TRUE); +} + +/** + * Shutdown function: store the batch data for next request, + * or clear the table if the batch is finished. + */ +function _drush_batch_shutdown() { + if ($batch = batch_get()) { + db_update('batch') + ->fields(array('batch' => serialize($batch))) + ->condition('bid', $batch['id']) + ->execute(); + } +} diff --git a/vendor/drush/drush/commands/backdrop/includes/environment.inc b/vendor/drush/drush/commands/backdrop/includes/environment.inc new file mode 100755 index 0000000000..22e6af924a --- /dev/null +++ b/vendor/drush/drush/commands/backdrop/includes/environment.inc @@ -0,0 +1,348 @@ + $module) { + if (isset($module->info['hidden'])) { + unset($modules[$key]); + } + } + } + + return $modules; +} + +/** + * Returns backdrop required modules, including modules declared as required dynamically. + */ +function _drush_backdrop_required_modules($module_info) { + $required = backdrop_required_modules(); + foreach ($module_info as $name => $module) { + if (isset($module->info['required']) && $module->info['required']) { + $required[] = $name; + } + } + return array_unique($required); +} + +/** + * Return dependencies and its status for modules. + * + * @param $modules + * Array of module names + * @param $module_info + * Backdrop 'files' array for modules as returned by drush_get_modules(). + * @return + * Array with dependencies and status for $modules + */ +function drush_check_module_dependencies($modules, $module_info) { + $status = array(); + foreach ($modules as $key => $module) { + $dependencies = array_reverse($module_info[$module]->requires); + $unmet_dependencies = array_diff(array_keys($dependencies), array_keys($module_info)); + if (!empty($unmet_dependencies)) { + $status[$key]['error'] = array( + 'code' => 'DRUSH_PM_ENABLE_DEPENDENCY_NOT_FOUND', + 'message' => dt('Module !module cannot be enabled because it depends on the following modules which could not be found: !unmet_dependencies', array('!module' => $module, '!unmet_dependencies' => implode(',', $unmet_dependencies))) + ); + } + else { + // check for version incompatibility + foreach ($dependencies as $dependency_name => $v) { + $current_version = $module_info[$dependency_name]->info['version']; + $current_version = str_replace(drush_get_backdrop_core_compatibility() . '-', '', $current_version); + $incompatibility = backdrop_check_incompatibility($v, $current_version); + if (isset($incompatibility)) { + $status[$key]['error'] = array( + 'code' => 'DRUSH_PM_ENABLE_DEPENDENCY_VERSION_MISMATCH', + 'message' => dt('Module !module cannot be enabled because it depends on !dependency !required_version but !current_version is available', array('!module' => $module, '!dependency' => $dependency_name, '!required_version' => $incompatibility, '!current_version' => $current_version)) + ); + } + } + } + $status[$key]['unmet-dependencies'] = $unmet_dependencies; + $status[$key]['dependencies'] = array_keys($dependencies); + } + + return $status; +} + +/** + * Return dependents of modules. + * + * @param $modules + * Array of module names + * @param $module_info + * Backdrop 'files' array for modules as returned by drush_get_modules(). + * @return + * Array with dependents for each one of $modules + */ +function drush_module_dependents($modules, $module_info) { + $dependents = array(); + foreach ($modules as $module) { + $dependents = array_merge($dependents, backdrop_map_assoc(array_keys($module_info[$module]->required_by))); + } + + return array_unique($dependents); +} + +/** + * Returns a list of enabled modules. + * + * This is a simplified version of module_list(). + */ +function drush_module_list() { + $enabled = array(); + $rsc = drush_db_select('system', 'name', 'type=:type AND status=:status', array(':type' => 'module', ':status' => 1)); + while ($row = drush_db_result($rsc)) { + $enabled[$row] = $row; + } + + return $enabled; +} + +/** + * Return a list of extensions from a list of named extensions. + * Both enabled and disabled/uninstalled extensions are returned. + */ +function drush_get_named_extensions_list($extensions) { + $result = array(); + $rsc = drush_db_select('system', array('name', 'status'), 'name IN (:extensions)', array(':extensions' => $extensions)); + while ($row = drush_db_fetch_object($rsc)) { + $result[$row->name] = $row; + } + return $result; +} + +/** + * Enable a list of modules. It is assumed the list contains all the dependencies not already enabled. + * + * @param $modules + * Array of module names + */ +function drush_module_enable($modules) { + // The list of modules already have all the dependencies, but they might not + // be in the correct order. Still pass $enable_dependencies = TRUE so that + // Backdrop will enable the modules in the correct order. + module_enable($modules); + // Flush all caches. + backdrop_flush_all_caches(); +} + +/** + * Disable a list of modules. It is assumed the list contains all dependents not already disabled. + * + * @param $modules + * Array of module names + */ +function drush_module_disable($modules) { + // The list of modules already have all the dependencies, but they might not + // be in the correct order. Still pass $enable_dependencies = TRUE so that + // Backdrop will enable the modules in the correct order. + module_disable($modules); + // Flush all caches. + backdrop_flush_all_caches(); +} + +/** + * Uninstall a list of modules. + * + * @param $modules + * Array of module names + */ +function drush_module_uninstall($modules) { + require_once DRUSH_BACKDROP_CORE . '/includes/install.inc'; + backdrop_uninstall_modules($modules); +} + +/** + * Checks that a given module exists and is enabled. + * + * @see module_exists(). + * + */ +function drush_module_exists($module) { + return module_exists($module); +} + +/** + * Determines which modules are implementing a hook. + * + */ +function drush_module_implements($hook, $sort = FALSE, $reset = FALSE) { + return module_implements($hook, $sort, $reset); +} + +/** + * Invokes a hook in a particular module. + * + */ +function drush_module_invoke($module, $hook) { + $args = func_get_args(); + return call_user_func_array('module_invoke', $args); +} + +/** + * Invokes a hook in all enabled modules that implement it. + * + */ +function drush_module_invoke_all($hook) { + $args = func_get_args(); + return call_user_func_array('module_invoke_all', $args); +} + +/** + * Get complete information for all available themes. + * + * @param $include_hidden + * Boolean to indicate whether hidden themes should be excluded or not. + * @return + * An array containing theme info for all available themes. + */ +function drush_get_themes($include_hidden = TRUE) { + $themes = system_rebuild_theme_data(); + if (!$include_hidden) { + foreach ($themes as $key => $theme) { + if (isset($theme->info['hidden'])) { + unset($themes[$key]); + } + } + } + + return $themes; +} + +/** + * Enable a list of themes. + * + * @param $themes + * Array of theme names. + */ +function drush_theme_enable($themes) { + theme_enable($themes); +} + +/** + * Disable a list of themes. + * + * @param $themes + * Array of theme names. + */ +function drush_theme_disable($themes) { + theme_disable($themes); +} + +/** + * Helper function to obtain the severity levels based on Backdrop version. + * + * This is a copy of watchdog_severity_levels() without t(). + * + * Severity levels, as defined in RFC 3164: http://www.ietf.org/rfc/rfc3164.txt. + * + * @return + * Array of watchdog severity levels. + */ +function drush_watchdog_severity_levels() { + return array( + WATCHDOG_EMERGENCY=> 'emergency', + WATCHDOG_ALERT => 'alert', + WATCHDOG_CRITICAL => 'critical', + WATCHDOG_ERROR => 'error', + WATCHDOG_WARNING => 'warning', + WATCHDOG_NOTICE => 'notice', + WATCHDOG_INFO => 'info', + WATCHDOG_DEBUG => 'debug', + ); +} + +/** + * Helper function to obtain the message types based on backdrop version. + * + * @return + * Array of watchdog message types. + */ +function drush_watchdog_message_types() { + return backdrop_map_assoc(_dblog_get_message_types()); +} + +function _drush_theme_default() { + return variable_get('theme_default', 'garland'); +} + +function _drush_theme_admin() { + return variable_get('admin_theme', drush_theme_get_default()); +} + +function _drush_file_public_path() { + return variable_get('file_public_path', conf_path() . '/files'); +} + +function _drush_file_private_path() { + return variable_get('file_private_path', FALSE); +} + +/** + * Gets the extension name. + * + * @param $info + * The extension info. + * @return string + * The extension name. + */ +function _drush_extension_get_name($info) { + return $info->name; +} + +/** + * Gets the extension type. + * + * @param $info + * The extension info. + * @return string + * The extension type. + */ +function _drush_extension_get_type($info) { + return $info->type; +} + +/** + * Gets the extension path. + * + * @param $info + * The extension info. + * @return string + * The extension path. + */ +function _drush_extension_get_path($info) { + return dirname($info->filename); +} + +/* + * Wrapper for CSRF token generation. + */ +function drush_get_token($value = NULL) { + return backdrop_get_token($value); +} + +/* + * Wrapper for _url(). + */ +function drush_url($path = NULL, array $options = array()) { + return url($path, $options); +} diff --git a/vendor/drush/drush/commands/backdrop/includes/install.inc b/vendor/drush/drush/commands/backdrop/includes/install.inc new file mode 100755 index 0000000000..0984e8dbbb --- /dev/null +++ b/vendor/drush/drush/commands/backdrop/includes/install.inc @@ -0,0 +1,82 @@ +db_spec(); + + $account_pass = drush_get_option('account-pass', drush_generate_password()); + $account_name = drush_get_option('account-name', 'admin'); + $settings = array( + 'parameters' => array( + 'profile' => $profile, + 'locale' => drush_get_option('locale', 'en'), + ), + 'forms' => array( + 'install_settings_form' => array( + 'driver' => $db_spec['driver'], + $db_spec['driver'] => $db_spec, + 'op' => dt('Save and continue'), + ), + 'install_configure_form' => array( + 'site_name' => drush_get_option('site-name', 'Site-Install'), + 'site_mail' => drush_get_option('site-mail', 'admin@example.com'), + 'account' => array( + 'name' => $account_name, + 'mail' => drush_get_option('account-mail', 'admin@example.com'), + 'pass' => array( + 'pass1' => $account_pass, + 'pass2' => $account_pass, + ), + ), + 'update_status_module' => array( + 1 => TRUE, + 2 => TRUE, + ), + 'clean_url' => drush_get_option('clean-url', TRUE), + 'op' => dt('Save and continue'), + ), + ), + ); + + // Merge in the additional options. + foreach ($additional_form_options as $key => $value) { + $current = &$settings['forms']; + foreach (explode('.', $key) as $param) { + $current = &$current[$param]; + } + $current = $value; + } + + $msg = 'Starting Backdrop installation. This takes a while.'; + if (is_null(drush_get_option('notify'))) { + $msg .= ' Consider using the --notify global option.'; + } + drush_log(dt($msg), 'ok'); + drush_op('install_backdrop', $settings); + drush_log(dt('Installation complete. User name: @name User password: @pass', array('@name' => $account_name, '@pass' => $account_pass)), 'ok'); +} diff --git a/vendor/drush/drush/commands/backdrop/includes/update.inc b/vendor/drush/drush/commands/backdrop/includes/update.inc new file mode 100755 index 0000000000..ae415abfac --- /dev/null +++ b/vendor/drush/drush/commands/backdrop/includes/update.inc @@ -0,0 +1,348 @@ + FALSE, 'query' => 'What went wrong'); + * The schema version will not be updated in this case, and all the + * aborted updates will continue to appear on update.php as updates that + * have not yet been run. + * + * @param $module + * The module whose update will be run. + * @param $number + * The update number to run. + * @param $context + * The batch context array + */ +function drush_update_do_one($module, $number, $dependency_map, &$context) { + $function = $module . '_update_' . $number; + + // If this update was aborted in a previous step, or has a dependency that + // was aborted in a previous step, go no further. + if (!empty($context['results']['#abort']) && array_intersect($context['results']['#abort'], array_merge($dependency_map, array($function)))) { + return; + } + + $context['log'] = FALSE; + + $ret = array(); + if (function_exists($function)) { + try { + if ($context['log']) { + Database::startLog($function); + } + + drush_log("Executing " . $function); + $ret['results']['query'] = $function($context['sandbox']); + + // If the update hook returned a status message (common in batch updates), + // show it to the user. + if ($ret['results']['query']) { + drush_log($ret['results']['query'], 'ok'); + } + + $ret['results']['success'] = TRUE; + } + // @TODO We may want to do different error handling for different exception + // types, but for now we'll just print the message. + catch (Exception $e) { + $ret['#abort'] = array('success' => FALSE, 'query' => $e->getMessage()); + drush_set_error('BACKDROP_EXCEPTION', $e->getMessage()); + } + + if ($context['log']) { + $ret['queries'] = Database::getLog($function); + } + } + + if (isset($context['sandbox']['#finished'])) { + $context['finished'] = $context['sandbox']['#finished']; + unset($context['sandbox']['#finished']); + } + + if (!isset($context['results'][$module])) { + $context['results'][$module] = array(); + } + if (!isset($context['results'][$module][$number])) { + $context['results'][$module][$number] = array(); + } + $context['results'][$module][$number] = array_merge($context['results'][$module][$number], $ret); + + if (!empty($ret['#abort'])) { + // Record this function in the list of updates that were aborted. + $context['results']['#abort'][] = $function; + } + + // Record the schema update if it was completed successfully. + if ($context['finished'] == 1 && empty($ret['#abort'])) { + backdrop_set_installed_schema_version($module, $number); + } + + $context['message'] = 'Performed update: ' . $function; +} + +/** + * Check update requirements and report any errors. + */ +function update_check_requirements() { + $warnings = FALSE; + + // Check the system module and update.php requirements only. + $requirements = system_requirements('update'); + $requirements += update_extra_requirements(); + + // If there are issues, report them. + foreach ($requirements as $requirement) { + if (isset($requirement['severity']) && $requirement['severity'] != REQUIREMENT_OK) { + $message = isset($requirement['description']) ? $requirement['description'] : ''; + if (isset($requirement['value']) && $requirement['value']) { + $message .= ' (Currently using ' . $requirement['title'] . ' ' . $requirement['value'] . ')'; + } + $warnings = TRUE; + backdrop_set_message($message, 'warning'); + } + } + return $warnings; +} + + +function update_main_prepare() { + // Some unavoidable errors happen because the database is not yet up-to-date. + // Our custom error handler is not yet installed, so we just suppress them. + drush_errors_off(); + + // We prepare a minimal bootstrap for the update requirements check to avoid + // reaching the PHP memory limit. + $core = DRUSH_BACKDROP_CORE; + require_once $core . '/includes/bootstrap.inc'; + require_once $core . '/includes/common.inc'; + require_once $core . '/includes/file.inc'; + include_once $core . '/includes/unicode.inc'; + + update_prepare_bootstrap(); + backdrop_bootstrap(BACKDROP_BOOTSTRAP_SESSION); + + require_once $core . '/includes/install.inc'; + require_once $core . '/modules/system/system.install'; + + // Load module basics. + include_once $core . '/includes/module.inc'; + $module_list['system']['filename'] = 'core/modules/system/system.module'; + module_list(TRUE, FALSE, FALSE, $module_list); + backdrop_load('module', 'system'); + + // Reset the module_implements() cache so that any new hook implementations + // in updated code are picked up. + module_implements('', FALSE, TRUE); + + // Set up $language, since the installer components require it. + backdrop_language_initialize(); + + // Set up theme system for the maintenance page. + backdrop_maintenance_theme(); + + // Check the update requirements for Backdrop. + update_check_requirements(); + + // update_fix_d7_requirements() needs to run before bootstrapping beyond path. + // So bootstrap to BACKDROP_BOOTSTRAP_LANGUAGE then include unicode.inc. + backdrop_bootstrap(BACKDROP_BOOTSTRAP_LANGUAGE); + + update_fix_requirements(); + + // Clear the module_implements() cache before the full bootstrap. The calls + // above to backdrop_maintenance_theme() and update_check_requirements() have + // invoked hooks before all modules have actually been loaded by the full + // bootstrap. This means that the module_implements() results for any hooks + // that have been invoked, including hook_module_implements_alter(), is a + // smaller set of modules than should be returned normally. + // @see https://github.com/drush-ops/drush/pull/399 + module_implements('', FALSE, TRUE); + + // Now proceed with a full bootstrap. + + drush_bootstrap(\Drush\Boot\BackdropBoot::BOOTSTRAP_FULL); + backdrop_maintenance_theme(); + + drush_errors_on(); + + include_once BACKDROP_ROOT . '/core/includes/batch.inc'; + backdrop_load_updates(); + + update_fix_compatibility(); + + // Change query-strings on css/js files to enforce reload for all users. + _backdrop_flush_css_js(); + // Flush the cache of all data for the update status module. + if (db_table_exists('cache_update')) { + cache_clear_all('*', 'cache_update', TRUE); + } + + module_list(TRUE, FALSE, TRUE); +} + +function update_main() { + update_main_prepare(); + + include_once __DIR__ . '/environment.inc'; + + list($pending, $start) = updatedb_status(); + if ($pending) { + // @todo get table header working. + // $headers = array(dt('Module'), dt('ID'), dt('Description')); + drush_print_table($pending); + if (!drush_confirm(dt('Do you wish to run all pending updates?'))) { + return drush_user_abort(); + } + drush_update_batch($start); + } + else { + drush_log(dt("No database updates required"), 'success'); + } +} + +function _update_batch_command($id) { + update_main_prepare(); + include_once __DIR__ . '/batch.inc'; + _drush_batch_command($id); +} + +/** + * Start the database update batch process. + * + * @param $start + * An array of all the modules and which update to start at. + * @param $redirect + * Path to redirect to when the batch has finished processing. + * @param $url + * URL of the batch processing page (should only be used for separate + * scripts like update.php). + * @param $batch + * Optional parameters to pass into the batch API. + * @param $redirect_callback + * (optional) Specify a function to be called to redirect to the progressive + * processing page. + */ +function drush_update_batch($start) { + // Resolve any update dependencies to determine the actual updates that will + // be run and the order they will be run in. + $updates = update_resolve_dependencies($start); + + // Store the dependencies for each update function in an array which the + // batch API can pass in to the batch operation each time it is called. (We + // do not store the entire update dependency array here because it is + // potentially very large.) + $dependency_map = array(); + foreach ($updates as $function => $update) { + $dependency_map[$function] = !empty($update['reverse_paths']) ? array_keys($update['reverse_paths']) : array(); + } + + $operations = array(); + foreach ($updates as $update) { + if ($update['allowed']) { + // Set the installed version of each module so updates will start at the + // correct place. (The updates are already sorted, so we can simply base + // this on the first one we come across in the above foreach loop.) + if (isset($start[$update['module']])) { + backdrop_set_installed_schema_version($update['module'], $update['number'] - 1); + unset($start[$update['module']]); + } + // Add this update function to the batch. + $function = $update['module'] . '_update_' . $update['number']; + $operations[] = array('drush_update_do_one', array($update['module'], $update['number'], $dependency_map[$function])); + } + } + + $batch['operations'] = $operations; + $batch += array( + 'title' => 'Updating', + 'init_message' => 'Starting updates', + 'error_message' => 'An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.', + 'finished' => 'drush_update_finished', + 'file' => 'includes/update.inc', + ); + batch_set($batch); + + // Command line options to pass to the command. + $args = array(); + $options['u'] = $GLOBALS['user']->uid; + include_once __DIR__ . '/batch.inc'; + _drush_backend_batch_process('updatedb-batch-process', $args, $options); +} + + + +function drush_update_finished($success, $results, $operations) { + // Nothing to do here. All caches already cleared. Kept as documentation of 'finished' callback. +} + +/** + * Return a 2 item array with + * - an array where each item is a 3 item associative array describing a pending update. + * - an array listing the first update to run, keyed by module. + */ +function updatedb_status() { + $pending = update_get_update_list(); + + $return = array(); + // Ensure system module's updates run first. + $start['system'] = array(); + + // Print a list of pending updates for this module and get confirmation. + foreach ($pending as $module => $updates) { + if (isset($updates['start'])) { + foreach ($updates['pending'] as $update_id => $description) { + // Strip cruft from front. + $description = str_replace($update_id . ' - ', '', $description); + $return[] = array('module' => ucfirst($module), 'update_id' => $update_id, 'description' => $description); + } + if (isset($updates['start'])) { + $start[$module] = $updates['start']; + } + } + } + + // Remove System if it has no updates. + if (empty($start['system'])) { + unset($start['system']); + } + + return array($return, $start); +} diff --git a/vendor/drush/drush/commands/core/archive.drush.inc b/vendor/drush/drush/commands/core/archive.drush.inc new file mode 100644 index 0000000000..5361f37ebe --- /dev/null +++ b/vendor/drush/drush/commands/core/archive.drush.inc @@ -0,0 +1,470 @@ + 'Backup your code, files, and database into a single file.', + 'arguments' => array( + 'sites' => 'Optional. Site specifications, delimited by commas. Typically, list subdirectory name(s) under /sites.', + ), + // Most options on sql-dump should not be shown, so just offer a subset. + 'options' => drush_sql_get_table_selection_options() + array( + 'description' => 'Describe the archive contents.', + 'tags' => 'Add tags to the archive manifest. Delimit multiple by commas.', + 'destination' => 'The full path and filename in which the archive should be stored. If omitted, it will be saved to the drush-backups directory and a filename will be generated.', + 'overwrite' => 'Do not fail if the destination file exists; overwrite it instead. Default is --no-overwrite.', + 'generator' => 'The generator name to store in the MANIFEST file. The default is "Drush archive-dump".', + 'generatorversion' => 'The generator version number to store in the MANIFEST file. The default is ' . DRUSH_VERSION . '.', + 'pipe' => 'Only print the destination of the archive. Useful for scripts that don\'t pass --destination.', + 'preserve-symlinks' => 'Preserve symbolic links.', + 'no-core' => 'Exclude Drupal core, so the backup only contains the site specific stuff.', + 'tar-options' => 'Options passed thru to the tar command.', + ), + 'examples' => array( + 'drush archive-dump default,example.com,foo.com' => 'Write an archive containing 3 sites in it.', + 'drush archive-dump @sites' => 'Save archive containing all sites in a multi-site.', + 'drush archive-dump default --destination=/backups/mysite.tar' => 'Save archive to custom location.', + 'drush archive-dump --tar-options="--exclude=.git --exclude=sites/default/files"' => 'Omits any .git directories found in the tree as well as sites/default/files.', + 'drush archive-dump --tar-options="--exclude=%files"' => 'Placeholder %files is replaced with the real path for the current site, and that path is excluded.', + ), + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_SITE, + 'aliases' => array('ard', 'archive-backup', 'arb', 'archive:dump'), + ); + $items['archive-restore'] = array( + 'description' => 'Expand a site archive into a Drupal web site.', + 'arguments' => array( + 'file' => 'The site archive file that should be expanded.', + 'site name' => 'Optional. Which site within the archive you want to restore. Defaults to all.', + ), + 'required-arguments' => 1, + 'options' => array( + 'destination' => 'Specify where the Drupal site should be expanded, including the docroot. Defaults to the current working directory.', + 'db-prefix' => 'An optional table prefix to use during restore.', + 'db-url' => array( + 'description' => 'A Drupal 6 style database URL indicating the target for database restore. If not provided, the archived settings.php is used.', + 'example-value' => 'mysql://root:pass@host/db', + ), + 'db-su' => 'Account to use when creating the new database. Optional.', + 'db-su-pw' => 'Password for the "db-su" account. Optional.', + 'overwrite' => 'Allow drush to overwrite any files in the destination. Default is --no-overwrite.', + 'tar-options' => 'Options passed thru to the tar command.', + ), + 'examples' => array( + 'drush archive-restore ./example.tar.gz' => 'Restore the files and databases for all sites in the archive.', + 'drush archive-restore ./example.tar.gz example.com' => 'Restore the files and database for example.com site.', + 'drush archive-restore ./example.tar.gz --destination=/var/www/example.com/docroot' => 'Restore archive to a custom location.', + 'drush archive-restore ./example.tar.gz --db-url=mysql://root:pass@127.0.0.1/dbname' => 'Restore archive to a new database (and customize settings.php to point there.).', + ), + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'aliases' => array('arr', 'archive:restore'), + ); + return $items; +} + +/** + * Command callback. Generate site archive file. + */ +function drush_archive_dump($sites_subdirs = '@self') { + $include_platform = !drush_get_option('no-core', FALSE); + $tar = drush_get_tar_executable(); + + $sites = array(); + list($aliases, $not_found) = drush_sitealias_resolve_sitespecs(explode(',', $sites_subdirs)); + if (!empty($not_found)) { + drush_log(dt("Some site subdirectories are not valid Drupal sites: @list", array("@list" => implode(', ', $not_found))), LogLevel::WARNING); + } + foreach ($aliases as $key => $alias) { + $sites[$key] = $alias; + + if (($db_record = sitealias_get_databases_from_record($alias))) { + $sites[$key]['databases'] = $db_record; + } + else { + $sites[$key]['databases'] = array(); + drush_log(dt('DB definition not found for !alias', array('!alias' => $key)), LogLevel::NOTICE); + } + } + + // The user can specify a destination filepath or not. That filepath might + // end with .gz, .tgz, or something else. At the end of this command we will + // gzip a file, and we want it to end up with the user-specified name (if + // any), but gzip renames files and refuses to compress files ending with + // .gz and .tgz, making our lives difficult. Solution: + // + // 1. Create a unique temporary base name to which gzip WILL append .gz. + // 2. If no destination is provided, set $dest_dir to a backup directory and + // $final_destination to be the unique name in that dir. + // 3. If a destination is provided, set $dest_dir to that directory and + // $final_destination to the exact name given. + // 4. Set $destination, the actual working file we will build up, to the + // unqiue name in $dest_dir. + // 5. After gzip'ing $destination, rename $destination.gz to + // $final_destination. + // + // Sheesh. + + // Create the unique temporary name. + $prefix = 'none'; + if (!empty($sites)) { + $first = current($sites); + if ( !empty($first['databases']['default']['default']['database']) ) { + $prefix = count($sites) > 1 ? 'multiple_sites' : str_replace('/', '-', $first['databases']['default']['default']['database']); + } + } + $date = gmdate('Ymd_His'); + $temp_dest_name = "$prefix.$date.tar"; + + $final_destination = drush_get_option('destination'); + if (!$final_destination) { + // No destination provided. + $backup = drush_include_engine('version_control', 'backup'); + // TODO: this standard Drush pattern leads to a slightly obtuse directory structure. + $dest_dir = $backup->prepare_backup_dir('archive-dump'); + if (empty($dest_dir)) { + $dest_dir = drush_tempdir(); + } + $final_destination = "$dest_dir/$temp_dest_name.gz"; + } + else { + // Use the supplied --destination. If it is relative, resolve it + // relative to the directory in which drush was invoked. + $command_cwd = getcwd(); + drush_op('chdir', drush_get_context('DRUSH_OLDCWD', getcwd())); + // This doesn't perform realpath on the basename, but that's okay. This is + // not path-based security. We just use it for checking for perms later. + drush_mkdir(dirname($final_destination)); + $dest_dir = realpath(dirname($final_destination)); + $final_destination = $dest_dir . '/' . basename($final_destination); + drush_op('chdir', $command_cwd); + } + + // $dest_dir is either the backup directory or specified directory. Set our + // working file. + $destination = "$dest_dir/$temp_dest_name"; + + // Validate the FINAL destination. It should be a file that does not exist + // (unless --overwrite) in a writable directory (and a writable file if + // it exists). We check all this up front to avoid failing after a long + // dump process. + $overwrite = drush_get_option('overwrite'); + $dest_dir = dirname($final_destination); + $dt_args = array('!file' => $final_destination, '!dir' => $dest_dir); + if (is_dir($final_destination)) { + drush_set_error('DRUSH_ARCHIVE_DEST_IS_DIR', dt('Destination !file must be a file, not a directory.', $dt_args)); + return; + } + else if (file_exists($final_destination)) { + if (!$overwrite) { + drush_set_error('DRUSH_ARCHIVE_DEST_EXISTS', dt('Destination !file exists; specify --overwrite to overwrite.', $dt_args)); + return; + } + else if (!is_writable($final_destination)) { + drush_set_error('DRUSH_ARCHIVE_DEST_FILE_NOT_WRITEABLE', dt('Destination !file is not writable.', $dt_args)); + return; + } + } + else if (!is_writable($dest_dir)) { + drush_set_error('DRUSH_ARCHIVE_DEST_DIR_NOT_WRITEABLE', dt('Destination directory !dir is not writable.', $dt_args)); + return; + } + + // Get the extra options for tar, if any + $tar_extra_options = drush_sitealias_evaluate_paths_in_options(drush_get_option('tar-options', '')); + + // Start adding codebase to the archive. + $docroot_path = realpath(drush_get_context('DRUSH_DRUPAL_ROOT')); + $docroot = basename($docroot_path); + $workdir = dirname($docroot_path); + + if ($include_platform) { + $dereference = (drush_get_option('preserve-symlinks', FALSE)) ? '' : '--dereference '; + // Convert destination path to Unix style for tar on MinGW - see http://drupal.org/node/1844224 + if (drush_is_mingw()) { + $destination_orig = $destination; + $destination = str_replace('\\', '/', $destination); + $destination = preg_replace('$^([a-zA-Z]):$', '/$1', $destination); + } + // Archive Drupal core, excluding sites dir. + drush_shell_cd_and_exec($workdir, "$tar {$tar_extra_options} --exclude \"{$docroot}/sites\" {$dereference}-cf %s %s", $destination, $docroot); + // Add sites/all to the same archive. + drush_shell_cd_and_exec($workdir, "$tar {$tar_extra_options} {$dereference}-rf %s %s", $destination, "{$docroot}/sites/all"); + // Add special files in sites/ to the archive. Last 2 items are new in Drupal8. + $files_to_add = array('sites/README.txt', 'sites/sites.php', 'sites/example.sites.php', 'sites/development.services.yml', 'sites/example.settings.local.php'); + foreach ($files_to_add as $file_to_add) { + if (file_exists($file_to_add)) { + drush_shell_cd_and_exec($workdir, "$tar {$dereference}-rf %s %s", $destination, $docroot . '/' . $file_to_add); + } + } + } + + $tmp = drush_tempdir(); + $all_dbs = array(); + // Dump the default database for each site and add to the archive. + foreach ($sites as $key => $alias) { + if (isset($alias['databases']['default']['default'])) { + $db_spec = $alias['databases']['default']['default']; + // Use a subdirectory name matching the docroot name. + drush_mkdir("{$tmp}/{$docroot}"); + + // Ensure uniqueness by prefixing key if needed. Remove path delimiters. + $dbname = str_replace(DIRECTORY_SEPARATOR, '-', $db_spec['database']); + $result_file = count($sites) == 1 ? "$tmp/$dbname.sql" : str_replace('@', '', "$tmp/$key-$dbname.sql"); + + $all_dbs[$key] = array( + 'file' => basename($result_file), + 'driver' => $db_spec['driver'], + ); + $sql = drush_sql_get_class($db_spec); + $sql->dump($result_file); + drush_shell_cd_and_exec($tmp, "$tar {$tar_extra_options} --dereference -rf %s %s", $destination, basename($result_file)); + } + } + + // Build a manifest file AND add sites/$subdir to archive as we go. + $platform = array( + 'datestamp' => time(), + 'formatversion' => '1.0', + 'generator' => drush_get_option('generator', 'Drush archive-dump'), + 'generatorversion' => drush_get_option('generatorversion', DRUSH_VERSION), + 'description' => drush_get_option('description', ''), + 'tags' => drush_get_option('tags', ''), + 'archiveformat' => ($include_platform ? 'platform' : 'site'), + ); + $contents = drush_export_ini(array('Global' => $platform)); + + $i=0; + foreach ($sites as $key => $alias) { + $status = drush_invoke_process($alias, 'core-status', array(), array(), array('integrate' => FALSE)); + if (!$status || $status['error_log']) { + drush_log(dt('Unable to determine sites directory for !alias', array('!alias' => $key)), LogLevel::WARNING); + } + + // Add the site specific directory to archive. + if (!empty($status['object']['%paths']['%site'])) { + drush_shell_cd_and_exec($workdir, "$tar {$tar_extra_options} --dereference -rf %s %s", $destination, "{$docroot}/sites/" . basename($status['object']['%paths']['%site'])); + } + + $site = array( + 'docroot' => DRUPAL_ROOT, + 'sitedir' => @$status['object']['%paths']['%site'], + 'files-public' => @$status['object']['%paths']['%files'], + 'files-private' => @$status['object']['%paths']['%private'], + ); + $site["database-default-file"] = $all_dbs[$key]['file']; + $site["database-default-driver"] = $all_dbs[$key]['driver']; + // The section title is the sites subdirectory name. + $info[basename($site['sitedir'])] = $site; + $contents .= "\n" . drush_export_ini($info); + unset($info); + $i++; + } + file_put_contents("{$tmp}/MANIFEST.ini", $contents); + + // Add manifest to archive. + drush_shell_cd_and_exec($tmp, "$tar --dereference -rf %s %s", $destination, 'MANIFEST.ini'); + + // Ensure that default/default.settings.php is in the archive. This is needed + // by site-install when restoring a site without any DB. + // NOTE: Windows tar file replace operation is broken so we have to check if file already exists. + // Otherwise it will corrupt the archive. + $res = drush_shell_cd_and_exec($workdir, "$tar -tf %s %s", $destination, $docroot . '/sites/default/default.settings.php'); + $output = drush_shell_exec_output(); + if (!$res || !isset($output[0]) || empty($output[0])) { + drush_shell_cd_and_exec($workdir, "$tar --dereference -vrf %s %s", $destination, $docroot . '/sites/default/default.settings.php'); + } + + // Switch back to original destination in case it was modified for tar on MinGW. + if (!empty($destination_orig)) { + $destination = $destination_orig; + } + + // Compress the archive + if (!drush_shell_exec("gzip --no-name -f %s", $destination)) { + // Clean up the tar file + drush_register_file_for_deletion($destination); + return drush_set_error(DRUSH_APPLICATION_ERROR, dt('Failed to gzip !destination', ['!destination' => $destination])); + } + + // gzip appends .gz unless the name already ends in .gz, .tgz, or .taz. + if ("{$destination}.gz" != $final_destination) { + drush_move_dir("{$destination}.gz", $final_destination, $overwrite); + } + + drush_log(dt('Archive saved to !dest', array('!dest' => $final_destination)), LogLevel::OK); + return $final_destination; +} + +/** + * Command argument complete callback. + * + * @return + * List of site names/aliases for archival. + */ +function archive_archive_dump_complete() { + return array('values' => array_keys(_drush_sitealias_all_list())); +} + +/** + * Command callback. Restore web site(s) from a site archive file. + */ +function drush_archive_restore($file, $site_id = NULL) { + $tmp = drush_tempdir(); + + // Get the extra options for tar, if any + $tar_extra_options = drush_sitealias_evaluate_paths_in_options(drush_get_option('tar-options', '')); + + if (!$files = drush_tarball_extract($file, $tmp, FALSE, $tar_extra_options)) { + return drush_set_error('DRUSH_ARCHIVE_UNABLE_TO_EXTRACT', dt('Unable to extract site archive tarball to !tmp.', array('!tmp' => $tmp))); + } + + $manifest = $tmp . '/MANIFEST.ini'; + if (file_exists($manifest)) { + if (!$ini = parse_ini_file($manifest, TRUE)) { + return drush_set_error('DRUSH_ARCHIVE_UNABLE_TO_PARSE_MANIFEST', dt('Unable to parse MANIFEST.ini in the archive.')); + } + } + else { + $ini = drush_archive_guess_manifest($tmp); + } + + // Backward compatibility: 'archiveformat' did not exist + // in older versions of archive-dump. + if (!isset( $ini['Global']['archiveformat'])) { + $ini['Global']['archiveformat'] = 'platform'; + } + + // Grab the first site in the Manifest and move docroot to destination. + $ini_tmp = $ini; + unset($ini_tmp['Global']); + $first = array_shift($ini_tmp); + $docroot = basename($first['docroot']); + $destination = drush_get_option('destination', realpath('.') . "/$docroot"); + + if ($ini['Global']['archiveformat'] == 'platform') { + // Move the whole platform in-place at once. + if (!drush_move_dir("$tmp/$docroot", $destination, drush_get_option('overwrite'))) { + return drush_set_error('DRUSH_ARCHIVE_UNABLE_TO_RESTORE_FILES', dt('Unable to restore platform to !dest', array('!dest' => $destination))); + } + } + else { + // When no platform is included we do this on a per-site basis. + } + + // Loop over sites and restore databases and append to settings.php. + foreach ($ini as $section => $site) { + if ($section != 'Global' && (!isset($site_id) || $section == $site_id) && !empty($site['database-default-file'])) { + $site_destination = $destination . '/' . $site['sitedir']; + // Restore site, in case not already done above. + if ($ini['Global']['archiveformat'] == 'site') { + if (!drush_move_dir("$tmp/$docroot/" . $site['sitedir'], $site_destination, drush_get_option('overwrite'))) { + return drush_set_error('DRUSH_ARCHIVE_UNABLE_TO_RESTORE_FILES', dt('Unable to restore site to !dest', array('!dest' => $site_destination))); + } + } + + // Restore database. + $sql_file = $tmp . '/' . $site['database-default-file']; + if ($db_url = drush_get_option('db-url')) { + if (empty($site_id) && count($ini) >= 3) { + // TODO: Use drushrc to provide multiple db-urls for multi-restore? + return drush_set_error('DRUSH_ARCHIVE_UNABLE_MULTI_RESTORE', dt('You must specify a site to restore when the archive contains multiple sites and a db-url is provided.')); + } + $db_spec = drush_convert_db_from_db_url($db_url); + } + else { + $site_specification = $destination . '#' . $section; + if ($return = drush_invoke_process($site_specification, 'sql-conf', array(), array('all' => TRUE), array('integrate' => FALSE, 'override-simulated' => TRUE))) { + $databases = $return['object']; + $db_spec = $databases['default']['default']; + } + else { + return drush_set_error('DRUSH_ARCHIVE_UNABLE_DISCOVER_DB', dt('Unable to get database details from db-url option or settings.php', array('!key' => $key))); + } + } + $sql = drush_sql_get_class($db_spec); + $sql->drop_or_create(); + $sql->query(NULL, $sql_file); + + // Append new DB info to settings.php. + if ($db_url) { + $settingsfile = $destination . '/' . $site['sitedir'] . '/settings.php'; + //If settings.php doesn't exist in the archive, create it from default.settings.php. + if (!file_exists($settingsfile)) { + drush_op('copy', $destination . '/sites/default/default.settings.php', $settingsfile); + } + // Need to do something here or else we can't write. + chmod($settingsfile, 0664); + file_put_contents($settingsfile, "\n// Appended by drush archive-restore command.\n", FILE_APPEND); + if (drush_drupal_major_version($destination) >= 7) { + file_put_contents($settingsfile, "\n" . '$databases = ' . var_export(drush_sitealias_convert_db_from_db_url($db_url), TRUE) . ";\n", FILE_APPEND); + } + else { + file_put_contents($settingsfile, "\n" . '$db_url = \'' . $db_url . "';\n", FILE_APPEND); + } + drush_log(dt('Drush appended the new database configuration at settings.php. Optionally remove the old configuration manually.'), LogLevel::OK); + } + } + } + drush_log(dt('Archive restored to !dest', array('!dest' => $destination)), LogLevel::OK); + + return $destination; +} + + +/** + * Command argument complete callback. + * + * @return + * Strong glob of files to complete on. + */ +function archive_archive_restore_complete() { + return array( + 'files' => array( + 'directories' => array( + 'pattern' => '*', + 'flags' => GLOB_ONLYDIR, + ), + 'tar' => array( + 'pattern' => '*.tar.gz', + ), + ), + ); +} + +/** + * Try to find docroot and DB dump file in an extracted archive. + * + * @param string $path The location of the extracted archive. + * @return array The manifest data. + */ +function drush_archive_guess_manifest($path) { + $db_file = drush_scan_directory($path, '/\.sql$/', array('.', '..', 'CVS'), 0, 0); + + if (file_exists($path . '/index.php')) { + $docroot = './'; + } + else { + $directories = glob($path . '/*' , GLOB_ONLYDIR); + $docroot = reset($directories); + } + + $ini = array( + 'Global' => array( + // Very crude detection of a platform... + 'archiveformat' => (drush_drupal_version($docroot) ? 'platform' : 'site'), + ), + 'default' => array( + 'docroot' => $docroot, + 'sitedir' => 'sites/default', + 'database-default-file' => key($db_file), + ), + ); + + return $ini; +} diff --git a/vendor/drush/drush/commands/core/cache.drush.inc b/vendor/drush/drush/commands/core/cache.drush.inc new file mode 100644 index 0000000000..59cd97783b --- /dev/null +++ b/vendor/drush/drush/commands/core/cache.drush.inc @@ -0,0 +1,309 @@ + 'Fetch a cached object and display it.', + 'examples' => array( + 'drush cache-get schema' => 'Display the data for the cache id "schema" from the "cache" bin.', + 'drush cache-get update_available_releases update' => 'Display the data for the cache id "update_available_releases" from the "update" bin.', + ), + 'arguments' => array( + 'cid' => 'The id of the object to fetch.', + 'bin' => 'Optional. The cache bin to fetch from.', + ), + 'required-arguments' => 1, + 'callback' => 'drush_cache_command_get', + 'outputformat' => array( + 'default' => 'print-r', + 'pipe-format' => 'var_export', + 'output-data-type' => TRUE, + ), + 'aliases' => array('cg','cache:get'), + ); + $items['cache-clear'] = array( + 'bootstrap' => DRUSH_BOOTSTRAP_MAX, + 'description' => 'Clear a specific cache, or all drupal caches.', + 'arguments' => array( + 'type' => 'The particular cache to clear. Omit this argument to choose from available caches.', + ), + 'callback' => 'drush_cache_command_clear', + 'aliases' => array('cc','cache:clear'), + ); + $items['cache-set'] = array( + 'description' => 'Cache an object expressed in JSON or var_export() format.', + 'arguments' => array( + 'cid' => 'The id of the object to set.', + 'data' => 'The object to set in the cache. Use \'-\' to read the object from STDIN.', + 'bin' => 'Optional. The cache bin to store the object in.', + 'expire' => 'Optional. CACHE_PERMANENT, CACHE_TEMPORARY, or a Unix timestamp.', + 'tags' => 'An array of cache tags.', + ), + 'required-arguments' => 2, + 'options' => array( + // Note that this is not an outputformat option. + 'format' => 'Format to parse the object. Use "string" for string (default), and "json" for JSON.', + 'cache-get' => 'If the object is the result a previous fetch from the cache, only store the value in the "data" property of the object in the cache.', + ), + 'callback' => 'drush_cache_command_set', + 'aliases' => array('cs','cache:set'), + ); + $items['cache-rebuild'] = array( + 'description' => 'Rebuild a Drupal 8 site and clear all its caches.', + 'options' => array(), + 'arguments' => array(), + // Bootstrap to DRUSH_BOOTSTAP_DRUPAL_SITE to pick the correct site. + // Further bootstrap is done by the rebuild script. + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_SITE, + 'core' => array('8+'), + 'aliases' => array('cr', 'rebuild', 'cache:rebuild'), + ); + + return $items; +} + +/** + * Command argument complete callback. + * + * @return + * Array of clear types. + */ +function cache_cache_clear_complete() { + // Bootstrap as far as possible so that Views and others can list their caches. + drush_bootstrap_max(); + return array('values' => array_keys(drush_cache_clear_types(TRUE))); +} + +function drush_cache_clear_pre_validate($type = NULL) { + $types = drush_cache_clear_types(drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)); + // Check if the provided type ($type) is a valid cache type. + if ($type && !array_key_exists($type, $types)) { + if ($type === 'all' && drush_drupal_major_version() >= 8) { + return drush_set_error(dt('`cache-clear all` is deprecated for Drupal 8 and later. Please use the `cache-rebuild` command instead.')); + } + // If we haven't done a full bootstrap, provide a more + // specific message with instructions to the user on + // bootstrapping a Drupal site for more options. + if (!drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)) { + $all_types = drush_cache_clear_types(TRUE); + if (array_key_exists($type, $all_types)) { + return drush_set_error(dt("'!type' cache requires a working Drupal site to operate on. Use the --root and --uri options, or a site @alias, or cd to a directory containing a Drupal settings.php file.", array('!type' => $type))); + } + else { + return drush_set_error(dt("'!type' cache is not a valid cache type. There may be more cache types available if you select a working Drupal site.", array('!type' => $type))); + } + } + return drush_set_error(dt("'!type' cache is not a valid cache type.", array('!type' => $type))); + } +} + +/** + * Command callback for drush cache-clear. + */ +function drush_cache_command_clear($type = NULL) { + if (!drush_get_option('cache-clear', TRUE)) { + drush_log(dt("Skipping cache-clear operation due to --cache-clear=0 option."), LogLevel::OK); + return TRUE; + } + $types = drush_cache_clear_types(drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)); + + if (!isset($type)) { + // Don't offer 'all' unless Drush has bootstrapped the Drupal site + if (!drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)) { + unset($types['all']); + } + $type = drush_choice($types, 'Enter a number to choose which cache to clear.', '!key'); + if (empty($type)) { + return drush_user_abort(); + } + } + // Do it. + drush_op($types[$type]); + if ($type == 'all' && !drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)) { + drush_log(dt("No Drupal site found, only 'drush' cache was cleared."), LogLevel::WARNING); + } + else { + drush_log(dt("'!name' cache was cleared.", array('!name' => $type)), LogLevel::SUCCESS); + } +} + +/** + * Print an object returned from the cache. + * + * @param $cid + * The cache ID of the object to fetch. + * @param $bin + * A specific bin to fetch from. If not specified, the default bin is used. + */ +function drush_cache_command_get($cid = NULL, $bin = NULL) { + drush_include_engine('drupal', 'cache'); + $result = drush_op('_drush_cache_command_get', $cid, $bin); + + if (empty($result)) { + return drush_set_error('DRUSH_CACHE_OBJECT_NOT_FOUND', dt('The !cid object in the !bin bin was not found.', array('!cid' => $cid, '!bin' => $bin ? $bin : _drush_cache_bin_default()))); + } + return $result; +} + +/** + * Set an object in the cache. + * + * @param $cid + * The cache ID of the object to fetch. + * @param $data + * The data to save to the cache, or '-' to read from STDIN. + * @param $bin + * A specific bin to fetch from. If not specified, the default bin is used. + * @param $expire + * The expiry timestamp for the cached object. + * @param $tags + * Cache tags for the cached object. + */ +function drush_cache_command_set($cid = NULL, $data = '', $bin = NULL, $expire = NULL, $tags = array()) { + // In addition to prepare, this also validates. Can't easily be in own validate callback as + // reading once from STDIN empties it. + $data = drush_cache_set_prepare_data($data); + if ($data === FALSE && drush_get_error()) { + // An error was logged above. + return; + } + + drush_include_engine('drupal', 'cache'); + return drush_op('_drush_cache_command_set', $cid, $data, $bin, $expire, $tags); +} + +function drush_cache_set_prepare_data($data) { + if ($data == '-') { + $data = file_get_contents("php://stdin"); + } + + // Now, we parse the object. + switch (drush_get_option('format', 'string')) { + case 'json': + $data = drush_json_decode($data); + break; + } + + if (drush_get_option('cache-get')) { + // $data might be an object. + if (is_object($data) && $data->data) { + $data = $data->data; + } + // But $data returned from `drush cache-get --format=json` will be an array. + elseif (is_array($data) && isset($data['data'])) { + $data = $data['data']; + } + else { + // If $data is neither object nor array and cache-get was specified, then + // there is a problem. + return drush_set_error('CACHE_INVALID_FORMAT', dt("'cache-get' was specified as an option, but the data is neither an object or an array.")); + } + } + + return $data; +} + +/** + * All types of caches available for clearing. Contrib commands can alter in their own. + */ +function drush_cache_clear_types($include_bootstrapped_types = FALSE) { + drush_include_engine('drupal', 'cache'); + $types = _drush_cache_clear_types($include_bootstrapped_types); + + // Include the appropriate environment engine, so callbacks can use core + // version specific cache clearing functions directly. + drush_include_engine('drupal', 'environment'); + + // Command files may customize $types as desired. + drush_command_invoke_all_ref('drush_cache_clear', $types, $include_bootstrapped_types); + + return $types; +} + +/** + * Clear caches internal to drush core. + */ +function drush_cache_clear_drush() { + drush_cache_clear_all(NULL, 'default'); // commandfiles, etc. + drush_cache_clear_all(NULL, 'complete'); // completion + // Release XML. We don't clear tarballs since those never change. + $matches = drush_scan_directory(drush_directory_cache('download'), "/^https---updates.drupal.org-release-history/", array('.', '..')); + array_map('unlink', array_keys($matches)); +} + +/** + * Rebuild a Drupal 8 site. + * + * This is a transpose of core/rebuild.php. Additionally + * it also clears drush cache and drupal render cache. + */ +function drush_cache_rebuild() { + if (!drush_get_option('cache-clear', TRUE)) { + drush_log(dt("Skipping cache-clear operation due to --cache-clear=0 option."), LogLevel::OK); + return TRUE; + } + chdir(DRUPAL_ROOT); + + // Clear the APC cache to ensure APC class loader is reset. + if (function_exists('apc_fetch')) { + apc_clear_cache('user'); + } + // Clear user cache for all major platforms. + $user_caches = [ + 'apcu_clear_cache', + 'wincache_ucache_clear', + 'xcache_clear_cache', + ]; + foreach (array_filter($user_caches, 'is_callable') as $cache) { + call_user_func($cache); + } + + $autoloader = drush_drupal_load_autoloader(DRUPAL_ROOT); + require_once DRUSH_DRUPAL_CORE . '/includes/utility.inc'; + + $request = Request::createFromGlobals(); + // Ensure that the HTTP method is set, which does not happen with Request::createFromGlobals(). + $request->setMethod('GET'); + // Manually resemble early bootstrap of DrupalKernel::boot(). + require_once DRUSH_DRUPAL_CORE . '/includes/bootstrap.inc'; + DrupalKernel::bootEnvironment(); + // Avoid 'Only variables should be passed by reference' + $root = DRUPAL_ROOT; + $site_path = DrupalKernel::findSitePath($request); + Settings::initialize($root, $site_path, $autoloader); + + // Use our error handler since _drupal_log_error() depends on an unavailable theme system (ugh). + set_error_handler('drush_error_handler'); + + // drupal_rebuild() calls drupal_flush_all_caches() itself, so we don't do it manually. + drupal_rebuild($autoloader, $request); + drush_log(dt('Cache rebuild complete.'), LogLevel::OK); + + // As this command replaces `drush cache-clear all` for Drupal 8 users, clear + // the Drush cache as well, for consistency with that behavior. + drush_cache_clear_drush(); +} + diff --git a/vendor/drush/drush/commands/core/cli.drush.inc b/vendor/drush/drush/commands/core/cli.drush.inc new file mode 100644 index 0000000000..3e99ca3a19 --- /dev/null +++ b/vendor/drush/drush/commands/core/cli.drush.inc @@ -0,0 +1,271 @@ + 'Open an interactive shell on a Drupal site.', + 'remote-tty' => TRUE, + 'aliases' => array('php', 'core:cli'), + 'bootstrap' => DRUSH_BOOTSTRAP_MAX, + 'topics' => array('docs-repl'), + 'options' => array( + 'version-history' => 'Use command history based on Drupal version (Default is per site).', + 'cwd' => 'Changes the working directory of the shell (Default is the project root directory)', + ), + ); + $items['docs-repl'] = array( + 'description' => 'repl.md', + 'hidden' => TRUE, + 'topic' => TRUE, + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'callback' => 'drush_print_file', + 'callback arguments' => array(drush_get_context('DOC_PREFIX', DRUSH_BASE_PATH) . '/docs/repl.md'), + 'aliases' => array('docs:repl'), + ); + return $items; +} + +/** + * Command callback. + */ +function drush_cli_core_cli() { + $drupal_major_version = drush_drupal_major_version(); + $configuration = new \Psy\Configuration(); + + // Set the Drush specific history file path. + $configuration->setHistoryFile(drush_history_path_cli()); + + // Disable checking for updates. Our dependencies are managed with Composer. + $configuration->setUpdateCheck(Checker::NEVER); + + $shell = new Shell($configuration); + + if ($drupal_major_version >= 8) { + // Register the assertion handler so exceptions are thrown instead of errors + // being triggered. This plays nicer with PsySH. + Handle::register(); + $shell->setScopeVariables(['container' => \Drupal::getContainer()]); + + // Add Drupal 8 specific casters to the shell configuration. + $configuration->addCasters(_drush_core_cli_get_casters()); + } + + // Add Drush commands to the shell. + $commands = [new DrushHelpCommand()]; + + foreach (drush_commands_categorize(_drush_core_cli_get_commands()) as $category_data) { + $category_title = (string) $category_data['title']; + foreach ($category_data['commands'] as $command_config) { + $command = new DrushCommand($command_config); + // Set the category label on each. + $command->setCategory($category_title); + $commands[] = $command; + } + } + + $shell->addCommands($commands); + + // PsySH will never return control to us, but our shutdown handler will still + // run after the user presses ^D. Mark this command as completed to avoid a + // spurious error message. + drush_set_context('DRUSH_EXECUTION_COMPLETED', TRUE); + + // Run the terminate event before the shell is run. Otherwise, if the shell + // is forking processes (the default), any child processes will close the + // database connection when they are killed. So when we return back to the + // parent process after, there is no connection. This will be called after the + // command in preflight still, but the subscriber instances are already + // created from before. Call terminate() regardless, this is a no-op for all + // DrupalBoot classes except DrupalBoot8. + if ($bootstrap = drush_get_bootstrap_object()) { + $bootstrap->terminate(); + } + + // To fix the above problem in Drupal 7, the connection can be closed manually. + // This will make sure a new connection is created again in child loops. So + // any shutdown functions will still run ok after the shell has exited. + if ($drupal_major_version == 7) { + Database::closeConnection(); + } + + // if the cwd option is passed, lets change the current working directory to wherever + // the user wants to go before we lift psysh. + if ($cwd = drush_get_option('cwd',FALSE)) { + chdir($cwd); + } + + $shell->run(); +} + +/** + * Returns a filtered list of Drush commands used for CLI commands. + * + * @return array + */ +function _drush_core_cli_get_commands() { + $commands = drush_get_commands(); + $ignored_commands = ['help', 'drush-psysh', 'php-eval', 'core-cli', 'php']; + $php_keywords = _drush_core_cli_get_php_keywords(); + + foreach ($commands as $name => $config) { + // Ignore some commands that don't make sense inside PsySH, are PHP keywords + // are hidden, or are aliases. + if (in_array($name, $ignored_commands) || in_array($name, $php_keywords) || !empty($config['hidden']) || ($name !== $config['command'])) { + unset($commands[$name]); + } + else { + // Make sure the command aliases don't contain any PHP keywords. + if (!empty($config['aliases'])) { + $commands[$name]['aliases'] = array_diff($commands[$name]['aliases'], $php_keywords); + } + } + } + + return $commands; +} + +/** + * Returns a mapped array of casters for use in the shell. + * + * These are Symfony VarDumper casters. + * See http://symfony.com/doc/current/components/var_dumper/advanced.html#casters + * for more information. + * + * @return array. + * An array of caster callbacks keyed by class or interface. + */ +function _drush_core_cli_get_casters() { + return [ + 'Drupal\Core\Entity\ContentEntityInterface' => 'Drush\Psysh\Caster::castContentEntity', + 'Drupal\Core\Field\FieldItemListInterface' => 'Drush\Psysh\Caster::castFieldItemList', + 'Drupal\Core\Field\FieldItemInterface' => 'Drush\Psysh\Caster::castFieldItem', + 'Drupal\Core\Config\Entity\ConfigEntityInterface' => 'Drush\Psysh\Caster::castConfigEntity', + 'Drupal\Core\Config\ConfigBase' => 'Drush\Psysh\Caster::castConfig', + 'Drupal\Component\DependencyInjection\Container' => 'Drush\Psysh\Caster::castContainer', + ]; +} + +/** + * Returns the file path for the CLI history. + * + * This can either be site specific (default) or Drupal version specific. + * + * @return string. + */ +function drush_history_path_cli() { + $cli_directory = drush_directory_cache('cli'); + + // If only the Drupal version is being used for the history. + if (drush_get_option('version-history', FALSE)) { + $drupal_major_version = drush_drupal_major_version(); + $file_name = "drupal-$drupal_major_version"; + } + // If there is an alias, use that in the site specific name. Otherwise, + // use a hash of the root path. + else { + if ($alias = drush_get_context('DRUSH_TARGET_SITE_ALIAS')) { + $site = drush_sitealias_get_record($alias); + $site_suffix = $site['#name']; + } + else { + $site_suffix = md5(DRUPAL_ROOT); + } + + $file_name = "drupal-site-$site_suffix"; + } + + $full_path = "$cli_directory/$file_name"; + + // Output the history path if verbose is enabled. + if (drush_get_context('DRUSH_VERBOSE')) { + drush_log(dt('History: @full_path', ['@full_path' => $full_path]), LogLevel::INFO); + } + + return $full_path; +} + +/** + * Returns a list of PHP keywords. + * + * This will act as a blacklist for command and alias names. + * + * @return array + */ +function _drush_core_cli_get_php_keywords() { + return [ + '__halt_compiler', + 'abstract', + 'and', + 'array', + 'as', + 'break', + 'callable', + 'case', + 'catch', + 'class', + 'clone', + 'const', + 'continue', + 'declare', + 'default', + 'die', + 'do', + 'echo', + 'else', + 'elseif', + 'empty', + 'enddeclare', + 'endfor', + 'endforeach', + 'endif', + 'endswitch', + 'endwhile', + 'eval', + 'exit', + 'extends', + 'final', + 'for', + 'foreach', + 'function', + 'global', + 'goto', + 'if', + 'implements', + 'include', + 'include_once', + 'instanceof', + 'insteadof', + 'interface', + 'isset', + 'list', + 'namespace', + 'new', + 'or', + 'print', + 'private', + 'protected', + 'public', + 'require', + 'require_once', + 'return', + 'static', + 'switch', + 'throw', + 'trait', + 'try', + 'unset', + 'use', + 'var', + 'while', + 'xor', + ]; +} diff --git a/vendor/drush/drush/commands/core/config.drush.inc b/vendor/drush/drush/commands/core/config.drush.inc new file mode 100644 index 0000000000..3f2b199405 --- /dev/null +++ b/vendor/drush/drush/commands/core/config.drush.inc @@ -0,0 +1,1070 @@ + array('config')); + $items['config-get'] = array( + 'description' => 'Display a config value, or a whole configuration object.', + 'arguments' => array( + 'config-name' => 'The config object name, for example "system.site".', + 'key' => 'The config key, for example "page.front". Optional.', + ), + 'required-arguments' => 1, + 'options' => array( + 'source' => array( + 'description' => 'The config storage source to read. Additional labels may be defined in settings.php', + 'example-value' => 'sync', + 'value' => 'required', + ), + 'include-overridden' => array( + 'description' => 'Include overridden values.', + ) + ), + 'examples' => array( + 'drush config-get system.site' => 'Displays the system.site config.', + 'drush config-get system.site page.front' => 'gets system.site:page.front value.', + ), + 'outputformat' => array( + 'default' => 'yaml', + 'pipe-format' => 'var_export', + ), + 'aliases' => array('cget', 'config:get'), + 'core' => array('8+'), + ); + + $items['config-set'] = array( + 'description' => 'Set config value directly. Does not perform a config import.', + 'arguments' => array( + 'config-name' => 'The config object name, for example "system.site".', + 'key' => 'The config key, for example "page.front".', + 'value' => 'The value to assign to the config key. Use \'-\' to read from STDIN.', + ), + 'options' => array( + 'format' => array( + 'description' => 'Format to parse the object. Use "string" for string (default), and "yaml" for YAML.', + 'example-value' => 'yaml', + 'value' => 'required', + ), + // A convenient way to pass a multiline value within a backend request. + 'value' => array( + 'description' => 'The value to assign to the config key (if any).', + 'hidden' => TRUE, + ), + ), + 'examples' => array( + 'drush config-set system.site page.front node' => 'Sets system.site:page.front to "node".', + ), + 'aliases' => array('cset', 'config:set'), + 'core' => array('8+'), + ); + + $items['config-export'] = array( + 'description' => 'Export configuration to a directory.', + 'core' => array('8+'), + 'aliases' => array('cex', 'config:export'), + 'arguments' => array( + 'label' => "A config directory label (i.e. a key in \$config_directories array in settings.php). Defaults to 'sync'", + ), + 'options' => array( + 'add' => 'Run `git add -p` after exporting. This lets you choose which config changes to sync for commit.', + 'commit' => 'Run `git add -A` and `git commit` after exporting. This commits everything that was exported without prompting.', + 'message' => 'Commit comment for the exported configuration. Optional; may only be used with --commit or --push.', + 'push' => 'Run `git push` after committing. Implies --commit.', + 'remote' => array( + 'description' => 'The remote git branch to use to push changes. Defaults to "origin".', + 'example-value' => 'origin', + ), + 'branch' => array( + 'description' => 'Make commit on provided working branch. Ignored if used without --commit or --push.', + 'example-value' => 'branchname', + ), + 'destination' => 'An arbitrary directory that should receive the exported files. An alternative to label argument.', + ), + 'examples' => array( + 'drush config-export --destination' => 'Export configuration; Save files in a backup directory named config-export.', + ), + ); + + $items['config-import'] = array( + 'description' => 'Import config from a config directory.', + 'arguments' => array( + 'label' => "A config directory label (i.e. a key in \$config_directories array in settings.php). Defaults to 'sync'", + ), + 'options' => array( + 'preview' => array( + 'description' => 'Format for displaying proposed changes. Recognized values: list, diff. Defaults to list.', + 'example-value' => 'list', + ), + 'source' => array( + 'description' => 'An arbitrary directory that holds the configuration files. An alternative to label argument', + ), + 'partial' => array( + 'description' => 'Allows for partial config imports from the source directory. Only updates and new configs will be processed with this flag (missing configs will not be deleted).', + ), + ), + 'core' => array('8+'), + 'examples' => array( + 'drush config-import --partial' => 'Import configuration; do not remove missing configuration.', + ), + 'aliases' => array('cim', 'config:import'), + ); + + $items['config-list'] = array( + 'description' => 'List config names by prefix.', + 'core' => array('8+'), + 'aliases' => array('cli', 'config:list'), + 'arguments' => array( + 'prefix' => 'The config prefix. For example, "system". No prefix will return all names in the system.', + ), + 'examples' => array( + 'drush config-list system' => 'Return a list of all system config names.', + 'drush config-list "image.style"' => 'Return a list of all image styles.', + 'drush config-list --format="json"' => 'Return all config names as json.', + ), + 'outputformat' => array( + 'default' => 'list', + 'pipe-format' => 'var_export', + 'output-data-type' => 'format-list', + ), + ); + + $items['config-edit'] = $deps + array( + 'description' => 'Open a config file in a text editor. Edits are imported into active configuration after closing editor.', + 'core' => array('8+'), + 'aliases' => array('cedit', 'config:edit'), + 'arguments' => array( + 'config-name' => 'The config object name, for example "system.site".', + ), + 'global-options' => array('editor', 'bg'), + 'allow-additional-options' => array('config-import'), + 'examples' => array( + 'drush config-edit image.style.large' => 'Edit the image style configurations.', + 'drush config-edit' => 'Choose a config file to edit.', + 'drush config-edit --choice=2' => 'Edit the second file in the choice list.', + 'drush --bg config-edit image.style.large' => 'Return to shell prompt as soon as the editor window opens.', + ), + ); + + $items['config-delete'] = array( + 'description' => 'Delete a configuration object.', + 'core' => array('8+'), + 'aliases' => array('cdel', 'config:delete'), + 'arguments' => array( + 'config-name' => 'The config object name, for example "system.site".', + 'key' => 'A config key to clear, for example "page.front".', + ), + 'required-arguments' => 1, + ); + + $items['config-pull'] = array( + 'description' => 'Export and transfer config from one environment to another.', + // 'core' => array('8+'), Operates on remote sites so not possible to declare this locally. + 'drush dependencies' => array('config', 'core'), // core-rsync, core-execute. + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'aliases' => array('cpull', 'config:pull'), + 'arguments' => array( + 'source' => 'A site-alias or the name of a subdirectory within /sites whose config you want to copy from.', + 'target' => 'A site-alias or the name of a subdirectory within /sites whose config you want to replace.', + ), + 'required-arguments' => TRUE, + 'allow-additional-options' => array(), // Most options from config-export and core-rsync unusable. + 'examples' => array( + 'drush config-pull @prod @stage' => "Export config from @prod and transfer to @stage.", + 'drush config-pull @prod @self --label=vcs' => "Export config from @prod and transfer to the 'vcs' config directory of current site.", + ), + 'options' => array( + 'safe' => 'Validate that there are no git uncommitted changes before proceeding', + 'label' => "A config directory label (i.e. a key in \$config_directories array in settings.php). Defaults to 'sync'", + 'runner' => 'Where to run the rsync command; defaults to the local site. Can also be "source" or "destination".', + ), + 'topics' => array('docs-aliases', 'docs-config-exporting'), + ); + + return $items; +} + +/** + * Implements hook_drush_help_alter(). + */ +function config_drush_help_alter(&$command) { + // Hide additional-options which are for internal use only. + if ($command['command'] == 'config-edit') { + $command['options']['source']['hidden'] = TRUE; + $command['options']['partial']['hidden'] = TRUE; + } +} + +/** + * Config list command callback + * + * @param string $prefix + * The config prefix to retrieve, or empty to return all. + */ +function drush_config_list($prefix = '') { + $names = \Drupal::configFactory()->listAll($prefix); + + if (empty($names)) { + // Just in case there is no config. + if (!$prefix) { + return drush_set_error(dt('No config storage names found.')); + } + else { + return drush_set_error(dt('No config storage names found matching @prefix', array('@prefix' => $prefix))); + } + } + + return $names; +} + +/** + * Config get command callback. + * + * @param $config_name + * The config name. + * @param $key + * The config key. + */ +function drush_config_get($config_name, $key = NULL) { + if (!isset($key)) { + return drush_config_get_object($config_name); + } + else { + return drush_config_get_value($config_name, $key); + } +} + +/** + * Config delete command callback. + * + * @param $config_name + * The config name. + * @param $key + * A config key to clear, for example "page.front". + */ +function drush_config_delete($config_name, $key = null) { + $config =\Drupal::service('config.factory')->getEditable($config_name); + if ($config->isNew()) { + return drush_set_error('DRUSH_CONFIG_ERROR', dt('Configuration name not recognized. Use config-list to see all names.')); + } + else { + if ($key) { + if ($config->get($key) === null) { + return drush_set_error('DRUSH_CONFIG_ERROR', dt('Configuration key !key not found.', array('!key' => $key))); + } + $config->clear($key)->save(); + } + else { + $config->delete(); + } + } +} + +/** + * Config set command callback. + * + * @param $config_name + * The config name. + * @param $key + * The config key. + * @param $data + * The data to save to config. + */ +function drush_config_set($config_name, $key = NULL, $data = NULL) { + // This hidden option is a convenient way to pass a value without passing a key. + $data = drush_get_option('value', $data); + + if (!isset($data)) { + return drush_set_error('DRUSH_CONFIG_ERROR', dt('No config value specified.')); + } + + $config = \Drupal::configFactory()->getEditable($config_name); + // Check to see if config key already exists. + if ($config->get($key) === NULL) { + $new_key = TRUE; + } + else { + $new_key = FALSE; + } + + // Special flag indicating that the value has been passed via STDIN. + if ($data === '-') { + $data = stream_get_contents(STDIN); + } + + // Now, we parse the value. + switch (drush_get_option('format', 'string')) { + case 'yaml': + $parser = new Parser(); + $data = $parser->parse($data, TRUE); + } + + if (is_array($data) && drush_confirm(dt('Do you want to update or set multiple keys on !name config.', array('!name' => $config_name)))) { + foreach ($data as $key => $value) { + $config->set($key, $value); + } + return $config->save(); + } + else { + $confirmed = FALSE; + if ($config->isNew() && drush_confirm(dt('!name config does not exist. Do you want to create a new config object?', array('!name' => $config_name)))) { + $confirmed = TRUE; + } + elseif ($new_key && drush_confirm(dt('!key key does not exist in !name config. Do you want to create a new config key?', array('!key' => $key, '!name' => $config_name)))) { + $confirmed = TRUE; + } + elseif (drush_confirm(dt('Do you want to update !key key in !name config?', array('!key' => $key, '!name' => $config_name)))) { + $confirmed = TRUE; + } + if ($confirmed && !drush_get_context('DRUSH_SIMULATE')) { + return $config->set($key, $data)->save(); + } + } +} + +/* + * If provided $destination is not TRUE and not empty, make sure it is writable. + */ +function drush_config_export_validate() { + $destination = drush_get_option('destination'); + if ($destination === TRUE) { + // We create a dir in command callback. No need to validate. + return; + } + + if (!empty($destination)) { + $additional = array(); + $values = drush_sitealias_evaluate_path($destination, $additional, TRUE); + if (!isset($values['path'])) { + return drush_set_error('config_export_target', 'The destination directory could not be evaluated.'); + } + $destination = $values['path']; + drush_set_option('destination', $destination); + if (!file_exists($destination)) { + $parent = dirname($destination); + if (!is_dir($parent)) { + return drush_set_error('config_export_target', 'The destination parent directory does not exist.'); + } + if (!is_writable($parent)) { + return drush_set_error('config_export_target', 'The destination parent directory is not writable.'); + } + } + else { + if (!is_dir($destination)) { + return drush_set_error('config_export_target', 'The destination is not a directory.'); + } + if (!is_writable($destination)) { + return drush_set_error('config_export_target', 'The destination directory is not writable.'); + } + } + } +} + +/** + * Command callback: Export config to specified directory (usually sync). + */ +function drush_config_export($destination = NULL) { + global $config_directories; + + // Determine which target directory to use. + if ($target = drush_get_option('destination')) { + if ($target === TRUE) { + // User did not pass a specific value for --destination. Make one. + /** @var drush_version_control_backup $backup */ + $backup = drush_include_engine('version_control', 'backup'); + $destination_dir = $backup->prepare_backup_dir('config-export'); + } + else { + $destination_dir = $target; + // It is important to be able to specify a destination directory that + // does not exist yet, for exporting on remote systems + drush_mkdir($destination_dir); + } + } + elseif ($config_directories === NULL) { + // For Drupal 8.8+ see https://www.drupal.org/node/3018145 change. + $destination_dir = Settings::get('config_sync_directory'); + } + else { + $choices = drush_map_assoc(array_keys($config_directories)); + unset($choices[CONFIG_ACTIVE_DIRECTORY]); + if (!isset($destination) && count($choices) >= 2) { + $destination = drush_choice($choices, 'Choose a destination.'); + if (empty($destination)) { + return drush_user_abort(); + } + } + elseif (!isset($destination)) { + $destination = CONFIG_SYNC_DIRECTORY; + } + $destination_dir = drush_config_get_config_directory($destination); + } + + // Prepare a new branch, if applicable + $remote = drush_get_option('push', FALSE); + $original_branch = FALSE; + $branch = FALSE; + if ($remote) { + // Get the branch that we're on at the moment + $result = drush_shell_cd_and_exec($destination_dir, 'git rev-parse --abbrev-ref HEAD'); + if (!$result) { + return drush_set_error('DRUSH_CONFIG_EXPORT_NO_GIT', dt("The drush config-export command requires that the selected configuration directory !dir be under git revision control when using --commit or --push options.", array('!dir' => $destination_dir))); + } + $output = drush_shell_exec_output(); + $original_branch = $output[0]; + $branch = drush_get_option('branch', FALSE); + if (!$branch) { + $branch = $original_branch; + } + if ($branch != $original_branch) { + // Switch to the working branch; create it if it does not exist. + // We do NOT want to use -B here, as we do NOT want to reset the + // branch if it already exists. + $result = drush_shell_cd_and_exec($destination_dir, 'git checkout %s', $branch); + if (!$result) { + $result = drush_shell_cd_and_exec($destination_dir, 'git checkout -b %s', $branch); + } + } + } + + // Do the actual config export operation + $result = _drush_config_export($destination, $destination_dir, $branch); + + // Regardless of the result of the export, reset to our original branch. + if ($branch != $original_branch) { + drush_shell_cd_and_exec($destination_dir, 'git checkout %s', $original_branch); + } + + return $result; +} + +function _drush_config_export($destination, $destination_dir, $branch) { + if (!defined('CONFIG_SYNC_DIRECTORY')) { + // For Drupal 8.8+ see https://www.drupal.org/node/3018145 change. + define('CONFIG_SYNC_DIRECTORY', Settings::get('config_sync_directory')); + } + $commit = drush_get_option('commit'); + $comment = drush_get_option('message', 'Exported configuration.'); + if (count(glob($destination_dir . '/*')) > 0) { + // Retrieve a list of differences between the active and target configuration (if any). + if ($destination == CONFIG_SYNC_DIRECTORY) { + $target_storage = \Drupal::service('config.storage.sync'); + } + else { + $target_storage = new FileStorage($destination_dir); + } + /** @var \Drupal\Core\Config\StorageInterface $active_storage */ + $active_storage = \Drupal::service('config.storage'); + $comparison_source = $active_storage; + + $config_comparer = new StorageComparer($comparison_source, $target_storage, \Drupal::service('config.manager')); + if (!$config_comparer->createChangelist()->hasChanges()) { + return drush_log(dt('The active configuration is identical to the configuration in the export directory (!target).', array('!target' => $destination_dir)), LogLevel::OK); + } + + drush_print("Differences of the active config to the export directory:\n"); + $change_list = array(); + foreach ($config_comparer->getAllCollectionNames() as $collection) { + $change_list[$collection] = $config_comparer->getChangelist(NULL, $collection); + } + // Print a table with changes in color, then re-generate again without + // color to place in the commit comment. + _drush_print_config_changes_table($change_list); + $tbl = _drush_format_config_changes_table($change_list); + $output = $tbl->getTable(); + if (!stristr(PHP_OS, 'WIN')) { + $output = str_replace("\r\n", PHP_EOL, $output); + } + $comment .= "\n\n$output"; + + if (!$commit && !drush_confirm(dt('The .yml files in your export directory (!target) will be deleted and replaced with the active config.', array('!target' => $destination_dir)))) { + return drush_user_abort(); + } + // Only delete .yml files, and not .htaccess or .git. + $target_storage->deleteAll(); + } + + // Write all .yml files. + $source_storage = \Drupal::service('config.storage'); + if ($destination == CONFIG_SYNC_DIRECTORY) { + $destination_storage = \Drupal::service('config.storage.sync'); + } + else { + $destination_storage = new FileStorage($destination_dir); + } + + foreach ($source_storage->listAll() as $name) { + $destination_storage->write($name, $source_storage->read($name)); + } + + // Export configuration collections. + foreach (\Drupal::service('config.storage')->getAllCollectionNames() as $collection) { + $source_storage = $source_storage->createCollection($collection); + $destination_storage = $destination_storage->createCollection($collection); + foreach ($source_storage->listAll() as $name) { + $destination_storage->write($name, $source_storage->read($name)); + } + } + + drush_log(dt('Configuration successfully exported to !target.', array('!target' => $destination_dir)), LogLevel::SUCCESS); + drush_backend_set_result($destination_dir); + + // Commit and push, or add exported configuration if requested. + $remote = drush_get_option('push', FALSE); + if ($commit || $remote) { + // There must be changed files at the destination dir; if there are not, then + // we will skip the commit-and-push step + $result = drush_shell_cd_and_exec($destination_dir, 'git status --porcelain .'); + if (!$result) { + return drush_set_error('DRUSH_CONFIG_EXPORT_FAILURE', dt("`git status` failed.")); + } + $uncommitted_changes = drush_shell_exec_output(); + if (!empty($uncommitted_changes)) { + $result = drush_shell_cd_and_exec($destination_dir, 'git add -A .'); + if (!$result) { + return drush_set_error('DRUSH_CONFIG_EXPORT_FAILURE', dt("`git add -A` failed.")); + } + $comment_file = drush_save_data_to_temp_file($comment); + $result = drush_shell_cd_and_exec($destination_dir, 'git commit --file=%s', $comment_file); + if (!$result) { + return drush_set_error('DRUSH_CONFIG_EXPORT_FAILURE', dt("`git commit` failed. Output:\n\n!output", array('!output' => implode("\n", drush_shell_exec_output())))); + } + if ($remote) { + // Remote might be FALSE, if --push was not specified, or + // it might be TRUE if --push was not given a value. + if (!is_string($remote)) { + $remote = 'origin'; + } + $result = drush_shell_cd_and_exec($destination_dir, 'git push --set-upstream %s %s', $remote, $branch); + if (!$result) { + return drush_set_error('DRUSH_CONFIG_EXPORT_FAILURE', dt("`git push` failed.")); + } + } + } + } + elseif (drush_get_option('add')) { + drush_shell_exec_interactive('git add -p %s', $destination_dir); + } + + $values = array( + 'destination' => $destination_dir, + ); + return $values; +} + +function drush_config_import_validate() { + drush_include_engine('drupal', 'environment'); + if (drush_get_option('partial') && !drush_module_exists('config')) { + return drush_set_error('config_import_partial', 'Enable the config module in order to use the --partial option.'); + } + if ($source = drush_get_option('source')) { + if (!file_exists($source)) { + return drush_set_error('config_import_target', 'The source directory does not exist.'); + } + if (!is_dir($source)) { + return drush_set_error('config_import_target', 'The source is not a directory.'); + } + } +} + +/** + * Command callback. Import from specified config directory (defaults to sync). + */ +function drush_config_import($source = NULL) { + global $config_directories; + if (!defined('CONFIG_SYNC_DIRECTORY')) { + // For Drupal 8.8+ see https://www.drupal.org/node/3018145 change. + define('CONFIG_SYNC_DIRECTORY', Settings::get('config_sync_directory')); + } + + // Determine source directory. + if ($target = drush_get_option('source')) { + $source_dir = $target; + } + else { + $source = CONFIG_SYNC_DIRECTORY; + if (!empty($config_directories) && defined('CONFIG_ACTIVE_DIRECTORY')) { + $choices = drush_map_assoc(array_keys($config_directories)); + unset($choices[CONFIG_ACTIVE_DIRECTORY]); + if (!isset($source) && count($choices) >= 2) { + $source= drush_choice($choices, 'Choose a source.'); + if (empty($source)) { + return drush_user_abort(); + } + } + elseif (!isset($source)) { + $source = CONFIG_SYNC_DIRECTORY; + } + } + $source_dir = drush_config_get_config_directory($source); + } + + if ($source == CONFIG_SYNC_DIRECTORY) { + $source_storage = \Drupal::service('config.storage.sync'); + } + else { + $source_storage = new FileStorage($source_dir); + } + + // Determine $source_storage in partial and non-partial cases. + /** @var \Drupal\Core\Config\StorageInterface $active_storage */ + $active_storage = \Drupal::service('config.storage'); + if (drush_get_option('partial')) { + $replacement_storage = new StorageReplaceDataWrapper($active_storage); + foreach ($source_storage->listAll() as $name) { + $data = $source_storage->read($name); + $replacement_storage->replaceData($name, $data); + } + $source_storage = $replacement_storage; + } + + /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */ + $config_manager = \Drupal::service('config.manager'); + $storage_comparer = new StorageComparer($source_storage, $active_storage, $config_manager); + + if (!$storage_comparer->createChangelist()->hasChanges()) { + return drush_log(dt('There are no changes to import.'), LogLevel::OK); + } + + if (drush_get_option('preview', 'list') == 'list') { + $change_list = array(); + foreach ($storage_comparer->getAllCollectionNames() as $collection) { + $change_list[$collection] = $storage_comparer->getChangelist(NULL, $collection); + } + _drush_print_config_changes_table($change_list); + } + else { + // Copy active storage to the temporary directory. + $temp_dir = drush_tempdir(); + $temp_storage = new FileStorage($temp_dir); + $source_dir_storage = new FileStorage($source_dir); + foreach ($source_dir_storage->listAll() as $name) { + if ($data = $active_storage->read($name)) { + $temp_storage->write($name, $data); + } + } + drush_shell_exec('diff -x %s -u %s %s', '*.git', $temp_dir, $source_dir); + $output = drush_shell_exec_output(); + drush_print(implode("\n", $output)); + } + + if (drush_confirm(dt('Import the listed configuration changes?'))) { + return drush_op('_drush_config_import', $storage_comparer); + } +} + +// Copied from submitForm() at /core/modules/config/src/Form/ConfigSync.php +function _drush_config_import(StorageComparer $storage_comparer) { + $config_importer = _drush_create_config_importer($storage_comparer); + if ($config_importer->alreadyImporting()) { + drush_log('Another request may be synchronizing configuration already.', LogLevel::WARNING); + } + else{ + try { + // This is the contents of \Drupal\Core\Config\ConfigImporter::import. + // Copied here so we can log progress. + if ($config_importer->hasUnprocessedConfigurationChanges()) { + $sync_steps = $config_importer->initialize(); + foreach ($sync_steps as $step) { + $context = array(); + do { + $config_importer->doSyncStep($step, $context); + if (isset($context['message'])) { + drush_log(str_replace('Synchronizing', 'Synchronized', (string)$context['message']), LogLevel::OK); + } + } while ($context['finished'] < 1); + } + } + if ($config_importer->getErrors()) { + throw new \Drupal\Core\Config\ConfigException('Errors occurred during import'); + } + else { + drush_log('The configuration was imported successfully.', LogLevel::SUCCESS); + } + } + catch (ConfigException $e) { + // Return a negative result for UI purposes. We do not differentiate + // between an actual synchronization error and a failed lock, because + // concurrent synchronizations are an edge-case happening only when + // multiple developers or site builders attempt to do it without + // coordinating. + $message = 'The import failed due for the following reasons:' . "\n"; + $message .= implode("\n", $config_importer->getErrors()); + + watchdog_exception('config_import', $e); + return drush_set_error('config_import_fail', $message); + } + } +} + +function _drush_create_config_importer(StorageComparer $storage_comparer) { + // Drupal 9 ConfigImporter + if (drush_drupal_major_version() >= 9) { + return new ConfigImporter( + $storage_comparer, + \Drupal::service('event_dispatcher'), + \Drupal::service('config.manager'), + \Drupal::lock(), + \Drupal::service('config.typed'), + \Drupal::moduleHandler(), + \Drupal::service('module_installer'), + \Drupal::service('theme_handler'), + \Drupal::service('string_translation'), + \Drupal::service('extension.list.module') + ); + } + // Drupal 8 ConfigImporter + return new ConfigImporter( + $storage_comparer, + \Drupal::service('event_dispatcher'), + \Drupal::service('config.manager'), + \Drupal::lock(), + \Drupal::service('config.typed'), + \Drupal::moduleHandler(), + \Drupal::service('module_installer'), + \Drupal::service('theme_handler'), + \Drupal::service('string_translation') + ); +} + +/** + * Edit command callback. + */ +function drush_config_edit($config_name = '') { + // Identify and validate input. + if ($config_name) { + $config = \Drupal::configFactory()->get($config_name); + if ($config->isNew()) { + return drush_set_error(dt('Config !name does not exist', array('!name' => $config_name))); + } + } + else { + $config_names = \Drupal::configFactory()->listAll(); + $choice = drush_choice($config_names, 'Choose a configuration.'); + if (empty($choice)) { + return drush_user_abort(); + } + else { + $config_name = $config_names[$choice]; + $config = \Drupal::configFactory()->get($config_name); + } + } + + $active_storage = $config->getStorage(); + $contents = $active_storage->read($config_name); + + // Write tmp YAML file for editing + $temp_dir = drush_tempdir(); + $temp_storage = new FileStorage($temp_dir); + $temp_storage->write($config_name, $contents); + + $exec = drush_get_editor(); + drush_shell_exec_interactive($exec, $temp_storage->getFilePath($config_name)); + + // Perform import operation if user did not immediately exit editor. + if (!drush_get_option('bg', FALSE)) { + $options = drush_redispatch_get_options() + array('partial' => TRUE, 'source' => $temp_dir); + $backend_options = array('interactive' => TRUE); + return (bool) drush_invoke_process('@self', 'config-import', array(), $options, $backend_options); + } +} + +/** + * Config pull validate callback + * + */ +function drush_config_pull_validate($source, $destination) { + if (drush_get_option('safe')) { + $return = drush_invoke_process($destination, 'core-execute', array('git diff --quiet'), array('escape' => 0)); + if ($return['error_status']) { + return drush_set_error('DRUSH_GIT_DIRTY', 'There are uncommitted changes in your git working copy.'); + } + } +} + +/** + * Config pull command callback + * + * @param string $label + * The config label which receives the transferred files. + */ +function drush_config_pull($source, $destination) { + // @todo drush_redispatch_get_options() assumes you will execute same command. Not good. + $global_options = drush_redispatch_get_options() + array( + 'strict' => 0, + ); + + // @todo If either call is made interactive, we don't get an $return['object'] back. + $backend_options = array('interactive' => FALSE); + if (drush_get_context('DRUSH_SIMULATE')) { + $backend_options['backend-simulate'] = TRUE; + } + + $export_options = array( + // Use the standard backup directory on Destination. + 'destination' => TRUE, + ); + drush_log(dt('Starting to export configuration on Target.'), LogLevel::OK); + $return = drush_invoke_process($source, 'config-export', array(), $global_options + $export_options, $backend_options); + if ($return === FALSE || $return['error_status']) { + return drush_set_error('DRUSH_CONFIG_PULL_EXPORT_FAILED', dt('Config-export failed.')); + } + else { + // Trailing slash assures that transfer files and not the containing dir. + $export_path = $return['object'] . '/'; + } + + $rsync_options = array( + 'remove-source-files' => TRUE, + 'delete' => TRUE, + 'exclude-paths' => '.htaccess', + 'yes' => TRUE, // No need to prompt as destination is always the target config directory. + ); + $label = drush_get_option('label', 'sync'); + $runner = drush_get_runner($source, $destination, drush_get_option('runner', FALSE)); + drush_log(dt('Starting to rsync configuration files from !source to !dest.', array('!source' => $source, '!dest' => $destination)), LogLevel::OK); + // This comment applies similarly to sql-sync's use of core-rsync. + // Since core-rsync is a strict-handling command and drush_invoke_process() puts options at end, we can't send along cli options to rsync. + // Alternatively, add options like --ssh-options to a site alias (usually on the machine that initiates the sql-sync). + $return = drush_invoke_process($runner, 'core-rsync', array("$source:$export_path", "$destination:%config-$label"), $rsync_options); + if ($return['error_status']) { + return drush_set_error('DRUSH_CONFIG_PULL_RSYNC_FAILED', dt('Config-pull rsync failed.')); + } + + drush_backend_set_result($return['object']); +} + +/** + * Show and return a config object + * + * @param $config_name + * The config object name. + */ +function drush_config_get_object($config_name) { + $source = drush_get_option('source', 'active'); + $include_overridden = drush_get_option('include-overridden', FALSE); + + if ($include_overridden) { + // Displaying overrides only applies to active storage. + $config = \Drupal::config($config_name); + $data = $config->get(); + } + elseif ($source == 'active') { + $config = \Drupal::service('config.storage'); + $data = $config->read($config_name); + } + elseif ($source == 'sync') { + $config = \Drupal::service('config.storage.sync'); + $data = $config->read($config_name); + } + else { + return drush_set_error(dt('Unknown value !value for config source.', array('!value' => $source))); + } + + if ($data === FALSE) { + return drush_set_error(dt('Config !name does not exist in !source configuration.', array('!name' => $config_name, '!source' => $source))); + } + if (empty($data)) { + drush_log(dt('Config !name exists but has no data.', array('!name' => $config_name)), LogLevel::NOTICE); + return; + } + return $data; +} + +/** + * Show and return a value from config system. + * + * @param $config_name + * The config name. + * @param $key + * The config key. + */ +function drush_config_get_value($config_name, $key) { + $data = drush_config_get_object($config_name); + $parts = explode('.', $key); + if (count($parts) == 1) { + $value = isset($data[$key]) ? $data[$key] : NULL; + } + else { + $value = NestedArray::getValue($data, $parts, $key_exists); + $value = $key_exists ? $value : NULL; + } + + $returns[$config_name . ':' . $key] = $value; + + if ($value === NULL) { + return drush_set_error('DRUSH_CONFIG_ERROR', dt('No matching key found in !name config.', array('!name' => $config_name))); + } + else { + return $returns; + } +} + +/** + * Print a table of config changes. + * + * @param array $config_changes + * An array of changes keyed by collection. + */ +function _drush_format_config_changes_table(array $config_changes, $use_color = FALSE) { + if (!$use_color) { + $red = "%s"; + $yellow = "%s"; + $green = "%s"; + } + else { + $red = "\033[31;40m\033[1m%s\033[0m"; + $yellow = "\033[1;33;40m\033[1m%s\033[0m"; + $green = "\033[1;32;40m\033[1m%s\033[0m"; + } + + $rows = array(); + $rows[] = array('Collection', 'Config', 'Operation'); + foreach ($config_changes as $collection => $changes) { + foreach ($changes as $change => $configs) { + switch ($change) { + case 'delete': + $colour = $red; + break; + case 'update': + $colour = $yellow; + break; + case 'create': + $colour = $green; + break; + default: + $colour = "%s"; + break; + } + foreach($configs as $config) { + $rows[] = array( + $collection, + $config, + sprintf($colour, $change) + ); + } + } + } + $tbl = _drush_format_table($rows); + return $tbl; +} + +/** + * Print a table of config changes. + * + * @param array $config_changes + * An array of changes keyed by collection. + */ +function _drush_print_config_changes_table(array $config_changes) { + $tbl = _drush_format_config_changes_table($config_changes, !drush_get_context('DRUSH_NOCOLOR')); + + $output = $tbl->getTable(); + if (!stristr(PHP_OS, 'WIN')) { + $output = str_replace("\r\n", PHP_EOL, $output); + } + + drush_print(rtrim($output)); + return $tbl; +} + +/** + * Command argument complete callback. + */ +function config_config_get_complete() { + return _drush_config_names_complete(); +} + +/** + * Command argument complete callback. + */ +function config_config_set_complete() { + return _drush_config_names_complete(); +} + +/** + * Command argument complete callback. + */ +function config_config_view_complete() { + return _drush_config_names_complete(); +} + +/** + * Command argument complete callback. + */ +function config_config_edit_complete() { + return _drush_config_names_complete(); +} + +/** + * Command argument complete callback. + */ +function config_config_import_complete() { + return _drush_config_directories_complete(); +} + +/** + * Command argument complete callback. + */ +function config_config_export_complete() { + return _drush_config_directories_complete(); +} + +/** + * Command argument complete callback. + */ +function config_config_pull_complete() { + return array('values' => array_keys(_drush_sitealias_all_list())); +} + +/** + * Helper function for command argument complete callback. + * + * @return + * Array of available config directories. + */ +function _drush_config_directories_complete() { + drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION); + global $config_directories; + return array('values' => array_keys($config_directories)); +} + +/** + * Helper function for command argument complete callback. + * + * @return + * Array of available config names. + */ +function _drush_config_names_complete() { + drush_bootstrap_max(); + return array('values' => $storage = \Drupal::service('config.storage')->listAll()); +} diff --git a/vendor/drush/drush/commands/core/core.drush.inc b/vendor/drush/drush/commands/core/core.drush.inc new file mode 100644 index 0000000000..98e890778c --- /dev/null +++ b/vendor/drush/drush/commands/core/core.drush.inc @@ -0,0 +1,1400 @@ +' + * + * @param + * A string with the help section (prepend with 'drush:') + * + * @return + * A string with the help text for your command. + */ +function core_drush_help($section) { + switch ($section) { + case 'meta:core:title': + return dt("Core Drush commands"); + case 'drush:php-script': + return dt("Runs the given php script(s) after a full Drupal bootstrap. A useful alternative to eval command when your php is lengthy or you can't be bothered to figure out bash quoting. If you plan to share a script with others, consider making a full drush command instead, since that's more self-documenting. Drush provides commandline options to the script via drush_get_option('option-name'), and commandline arguments can be accessed either via drush_get_arguments(), which returns all arguments in an array, or drush_shift(), which removes the next argument from the list and returns it."); + case 'drush:rsync': + return dt("Sync the entire drupal directory or a subdirectory to a using ssh. Excludes reserved files and directories for supported VCSs. Useful for pushing copies of your tree to a staging server, or retrieving a files directory from a remote site. Relative paths start from the Drupal root directory if a site alias is used; otherwise they start from the current working directory."); + case 'error:DRUSH_DRUPAL_DB_ERROR': + $message = dt("Drush was not able to start (bootstrap) the Drupal database.\n"); + $message .= dt("Hint: This may occur when Drush is trying to:\n"); + $message .= dt(" * bootstrap a site that has not been installed or does not have a configured database. In this case you can select another site with a working database setup by specifying the URI to use with the --uri parameter on the command line. See `drush topic docs-aliases` for details.\n"); + $message .= dt(" * connect the database through a socket. The socket file may be wrong or the php-cli may have no access to it in a jailed shell. See http://drupal.org/node/1428638 for details.\n"); + $message .= dt(" * connect to the database through a cli command using the --defaults-extra-file parameter to pass credentials through a tmp file. This method can break if other credentials are specified in a ~/.my.cnf file in your home directory. You may have to delete or rename the ~/.my.cnf file in your home directory.\n"); + $message .= dt("\nDrush was attempting to connect to: \n!credentials\n", array('!credentials' => _core_site_credentials(12))); + return $message; + case 'error:DRUSH_DRUPAL_BOOTSTRAP_ERROR': + $message = dt("Drush was not able to start (bootstrap) Drupal.\n"); + $message .= dt("Hint: This error can only occur once the database connection has already been successfully initiated, therefore this error generally points to a site configuration issue, and not a problem connecting to the database.\n"); + $message .= dt("\nDrush was attempting to connect to: \n!credentials\n", array('!credentials' => _core_site_credentials(12))); + return $message; + break; + } +} + +/** + * Implements hook_drush_help_alter(). + */ +function core_drush_help_alter(&$command) { + // Drupal 8+ only options. + if (drush_drupal_major_version() < 8) { + if ($command['commandfile'] == 'core' && $command['command'] == 'updatedb') { + unset($command['options']['entity-updates']); + } + } +} + +/** + * Implementation of hook_drush_command(). + * + * In this hook, you specify which commands your + * drush module makes available, what it does and + * description. + * + * Notice how this structure closely resembles how + * you define menu hooks. + * + * @return + * An associative array describing your command(s). + */ +function core_drush_command() { + $items = array(); + + $items['version'] = array( + 'description' => 'Show drush version.', + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, // No bootstrap. + 'options' => array( + 'pipe' => 'Print just the version number, and nothing else.', + ), + 'outputformat' => array( + 'default' => 'key-value', + 'pipe-format' => 'string', + 'label' => 'Drush Version', + 'output-data-type' => 'format-single', + ), + ); + $items['core-cron'] = array( + 'description' => 'Run all cron hooks in all active modules for specified site.', + 'aliases' => array('cron', 'core:cron'), + 'topics' => array('docs-cron'), + ); + $items['updatedb'] = array( + 'description' => 'Apply any database updates required (as with running update.php).', + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_SITE, + 'global-options' => array( + 'cache-clear', + ), + 'options' => array( + 'entity-updates' => 'Run automatic entity schema updates at the end of any update hooks. Defaults to --no-entity-updates.', + ), + 'aliases' => array('updb'), + ); + $items['entity-updates'] = array( + 'description' => 'Apply pending entity schema updates.', + 'aliases' => array('entup', 'entity:updates'), + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL, + 'core' => array('8+'), + ); + $items['twig-compile'] = array( + 'description' => 'Compile all Twig template(s).', + 'aliases' => array('twigc', 'twig:compile'), + 'core' => array('8+'), + ); + $items['updatedb-status'] = array( + 'description' => 'List any pending database updates.', + 'outputformat' => array( + 'default' => 'table', + 'pipe-format' => 'csv', + 'field-labels' => array('module' => 'Module', 'update_id' => 'Update ID', 'description' => 'Description'), + 'fields-default' => array('module', 'update_id', 'description'), + 'output-data-type' => 'format-table', + ), + 'aliases' => array('updbst', 'updatedb:status'), + ); + $items['core-config'] = array( + 'description' => 'Edit drushrc, site alias, and Drupal settings.php files.', + 'bootstrap' => DRUSH_BOOTSTRAP_MAX, + 'arguments' => array( + 'filter' => 'A substring for filtering the list of files. Omit this argument to choose from loaded files.', + ), + 'global-options' => array('editor', 'bg'), + 'examples' => array( + 'drush core-config' => 'Pick from a list of config/alias/settings files. Open selected in editor.', + 'drush --bg core-config' => 'Return to shell prompt as soon as the editor window opens.', + 'drush core-config etc' => 'Edit the global configuration file.', + 'drush core-config demo.alia' => 'Edit a particular alias file.', + 'drush core-config sett' => 'Edit settings.php for the current Drupal site.', + 'drush core-config --choice=2' => 'Edit the second file in the choice list.', + ), + 'aliases' => array('conf', 'config', 'core:config'), + ); + $items['core-status'] = array( + 'description' => 'Provides a birds-eye view of the current Drupal installation, if any.', + 'bootstrap' => DRUSH_BOOTSTRAP_MAX, + 'aliases' => array('status', 'st', 'core:status'), + 'examples' => array( + 'drush core-status version' => 'Show all status lines that contain version information.', + 'drush core-status --pipe' => 'A list key=value items separated by line breaks.', + 'drush core-status drush-version --pipe' => 'Emit just the drush version with no label.', + 'drush core-status config-sync --pipe' => 'Emit just the sync Config directory with no label.', + ), + 'arguments' => array( + 'item' => 'Optional. The status item line(s) to display.', + ), + 'options' => array( + 'show-passwords' => 'Show database password. Defaults to --no-show-passwords.', + 'full' => 'Show all file paths and drush aliases in the report, even if there are a lot.', + 'project' => array( + 'description' => 'One or more projects that should be added to the path list', + 'example-value' => 'foo,bar', + ), + ), + 'outputformat' => array( + 'default' => 'key-value', + 'pipe-format' => 'json', + 'field-labels' => array('drupal-version' => 'Drupal version', 'uri' => 'Site URI', 'db-driver' => 'Database driver', 'db-hostname' => 'Database hostname', 'db-port' => 'Database port', 'db-username' => 'Database username', 'db-password' => 'Database password', 'db-name' => 'Database name', 'db-status' => 'Database', 'bootstrap' => 'Drupal bootstrap', 'user' => 'Drupal user', 'theme' => 'Default theme', 'admin-theme' => 'Administration theme', 'php-bin' => 'PHP executable', 'php-conf' => 'PHP configuration', 'php-os' => 'PHP OS', 'drush-script' => 'Drush script', 'drush-version' => 'Drush version', 'drush-temp' => 'Drush temp directory', 'drush-conf' => 'Drush configuration', 'drush-alias-files' => 'Drush alias files', 'install-profile' => 'Install profile', 'root' => 'Drupal root', 'drupal-settings-file' => 'Drupal Settings File', 'site-path' => 'Site path', 'root' => 'Drupal root', 'site' => 'Site path', 'themes' => 'Themes path', 'modules' => 'Modules path', 'files' => 'File directory path', 'private' => 'Private file directory path', 'temp' => 'Temporary file directory path', 'config-sync' => 'Sync config path', 'files-path' => 'File directory path', 'temp-path' => 'Temporary file directory path', '%paths' => 'Other paths'), + 'formatted-filter' => '_drush_core_status_format_table_data', + 'private-fields' => 'db-password', + 'simplify-single' => TRUE, + 'table-metadata' => array( + 'list-separator' => ' ', + ), + 'output-data-type' => 'format-list', + ), + 'topics' => array('docs-readme'), + ); + + $items['core-requirements'] = array( + 'description' => 'Provides information about things that may be wrong in your Drupal installation, if any.', + 'aliases' => array('status-report','rq', 'core:requirements'), + 'examples' => array( + 'drush core-requirements' => 'Show all status lines from the Status Report admin page.', + 'drush core-requirements --severity=2' => 'Show only the red lines from the Status Report admin page.', + 'drush core-requirements --pipe' => 'Print out a short report in JSON format, where severity 2=error, 1=warning, and 0/-1=OK', + ), + 'options' => array( + 'severity' => array( + 'description' => 'Only show status report messages with a severity greater than or equal to the specified value.', + 'value' => 'required', + 'example-value' => '3', + ), + 'ignore' => 'Comma-separated list of requirements to remove from output. Run with --pipe to see key values to use.', + ), + 'outputformat' => array( + 'default' => 'table', + 'pipe-format' => 'json', + 'field-labels' => array('title' => 'Title', 'severity' => 'Severity', 'sid' => 'SID', 'description' => 'Description', 'value' => 'Summary', 'reason' => 'Reason', 'weight' => 'Weight'), + 'fields-default' => array('title', 'severity', 'description'), + 'column-widths' => array('severity' => 8), + 'concatenate-columns' => array('description' => array('value', 'description')), + 'strip-tags' => TRUE, + 'ini-item' => 'sid', + 'key-value-item' => 'severity', + 'list-metadata' => array( + 'list-item' => 'severity', + ), + 'output-data-type' => 'format-table', + ), + ); + $items['php-eval'] = array( + 'description' => 'Evaluate arbitrary php code after bootstrapping Drupal (if available).', + 'examples' => array( + 'drush php-eval \'variable_set("hello", "world");\'' => 'Sets the hello variable using Drupal API.', + 'drush php-eval \'$node = node_load(1); print $node->title;\'' => 'Loads node with nid 1 and then prints its title.', + 'drush php-eval "file_unmanaged_copy(\'$HOME/Pictures/image.jpg\', \'public://image.jpg\');"' => 'Copies a file whose path is determined by an environment\'s variable. Note the use of double quotes so the variable $HOME gets replaced by its value.', + 'drush php-eval "node_access_rebuild();"' => 'Rebuild node access permissions.', + ), + 'arguments' => array( + 'code' => 'PHP code', + ), + 'required-arguments' => TRUE, + 'allow-additional-options' => TRUE, + 'bootstrap' => DRUSH_BOOTSTRAP_MAX, + 'aliases' => array('eval', 'ev', 'php:eval'), + 'outputformat' => array( + 'default' => 'var_export', + ), + ); + $items['php-script'] = array( + 'description' => "Run php script(s).", + 'examples' => array( + 'drush php-script scratch' => 'Run scratch.php script. See commands/core directory.', + 'drush php-script example --script-path=/path/to/scripts:/another/path' => 'Run script from specified paths', + 'drush php-script' => 'List all available scripts.', + '' => '', + "#!/usr/bin/env drush\n "Execute php code with a full Drupal bootstrap directly from a shell script.", + ), + 'arguments' => array( + 'filename' => 'Optional. The file you wish to execute (without extension). If omitted, list files ending in .php in the current working directory and specified script-path. Some might not be real drush scripts. Beware.', + ), + 'options' => array( + 'script-path' => array( + 'description' => "Additional paths to search for scripts, separated by : (Unix-based systems) or ; (Windows).", + 'example-value' => '~/scripts', + ), + ), + 'allow-additional-options' => TRUE, + 'bootstrap' => DRUSH_BOOTSTRAP_MAX, + 'aliases' => array('scr', 'php:script'), + 'topics' => array('docs-examplescript', 'docs-scripts'), + ); + $items['core-execute'] = array( + 'description' => 'Execute a shell command. Usually used with a site alias.', + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, // No bootstrap. + 'arguments' => array( + 'command' => 'The shell command to be executed.', + ), + 'options' => array( + 'escape' => 'Escape parameters before executing them with the shell. Default is escape; use --no-escape to disable.', + ) + drush_shell_exec_proc_build_options(), + 'required-arguments' => TRUE, + 'allow-additional-options' => TRUE, + 'handle-remote-commands' => TRUE, + 'strict-option-handling' => TRUE, + 'examples' => array( + 'drush core-execute git pull origin rebase' => 'Retrieve latest code from git', + ), + 'aliases' => array('exec', 'execute', 'core:execute'), + 'topics' => array('docs-aliases'), + ); + $items['core-rsync'] = array( + 'description' => 'Rsync the Drupal tree to/from another server using ssh.', + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, // No bootstrap. + 'arguments' => array( + 'source' => 'May be rsync path or site alias. See rsync documentation and example.aliases.drushrc.php.', + 'destination' => 'May be rsync path or site alias. See rsync documentation and example.aliases.drushrc.php.', + ), + 'options' => array( + 'mode' => 'The unary flags to pass to rsync; --mode=rultz implies rsync -rultz. Default is -akz.', + 'exclude-conf' => 'Excludes settings.php from being rsynced. Default.', + 'include-conf' => 'Allow settings.php to be rsynced. Default is to exclude settings.php.', + 'include-vcs' => 'Include special version control directories (e.g. .svn). Default is to exclude vcs files.', + 'exclude-files' => 'Exclude the files directory.', + 'exclude-sites' => 'Exclude all directories in "sites/" except for "sites/all".', + 'exclude-other-sites' => 'Exclude all directories in "sites/" except for "sites/all" and the site directory for the site being synced. Note: if the site directory is different between the source and destination, use --exclude-sites followed by "drush rsync @from:%site @to:%site"', + 'exclude-paths' => 'List of paths to exclude, seperated by : (Unix-based systems) or ; (Windows).', + 'include-paths' => 'List of paths to include, seperated by : (Unix-based systems) or ; (Windows).', + '{rsync-option-name}' => "Replace {rsync-option-name} with the rsync option (or option='value') that you would like to pass through to rsync. Examples include --delete, --exclude=*.sql, --filter='merge /etc/rsync/default.rules', etc. See the rsync documentation for a complete explanation of all the rsync options and values.", + 'rsync-version' => 'Set to the version of rsync you are using to signal Drush to go into backwards-compatibility mode when using very old versions of rsync. For example, --rsync-version=2.6.8 or earlier will cause Drush to avoid the --remove-source-files flag.', + + ), + 'strict-option-handling' => TRUE, + 'examples' => array( + 'drush rsync @dev @stage' => 'Rsync Drupal root from Drush alias dev to the alias stage. Either or both may be remote.', + 'drush rsync ./ @stage:%files/img' => 'Rsync all files in the current directory to the \'img\' directory in the file storage folder on the Drush alias stage.', + 'drush -s rsync @dev @stage --exclude=*.sql --delete' => "Simulate Rsync Drupal root from the Drush alias dev to the alias stage (one of which must be local), excluding all files that match the filter '*.sql' and delete all files on the destination that are no longer on the source.", + ), + 'aliases' => array('rsync', 'core:rsync'), + 'topics' => array('docs-aliases'), + ); + $items['drupal-directory'] = array( + 'description' => 'Return the filesystem path for modules/themes and other key folders.', + 'arguments' => array( + 'target' => 'A module/theme name, or special names like root, files, private, or an alias : path alias string such as @alias:%files. Defaults to root.', + ), + 'options' => array( + 'component' => "The portion of the evaluated path to return. Defaults to 'path'; 'name' returns the site alias of the target.", + 'local-only' => "Reject any target that specifies a remote site.", + ), + 'examples' => array( + 'cd `drush dd devel`' => 'Navigate into the devel module directory', + 'cd `drush dd` ' => 'Navigate to the root of your Drupal site', + 'cd `drush dd files`' => 'Navigate to the files directory.', + 'drush dd @alias:%files' => 'Print the path to the files directory on the site @alias.', + 'edit `drush dd devel`/devel.module' => "Open devel module in your editor (customize 'edit' for your editor)", + ), + 'aliases' => array('dd', 'drupal:directory'), + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + ); + + $items['batch-process'] = array( + 'description' => 'Process operations in the specified batch set', + 'hidden' => TRUE, + 'arguments' => array( + 'batch-id' => 'The batch id that will be processed.', + ), + 'required-arguments' => TRUE, + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_LOGIN, + 'aliases' => array('batch:process'), + ); + + $items['updatedb-batch-process'] = array( + 'description' => 'Perform update functions', + 'hidden' => TRUE, + 'arguments' => array( + 'batch-id' => 'The batch id that will be processed', + ), + 'required-arguments' => TRUE, + // Drupal 7 needs DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION, while Drupal 8 needs _FULL. + // Therefore we bootstrap to _FULL in commands/core/drupal/update.inc. + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION, + 'aliases' => array('updatedb:batch-process'), + ); + $items['core-global-options'] = array( + 'description' => 'All global options', + 'hidden' => TRUE, + 'topic' => TRUE, + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'outputformat' => array( + 'default' => 'table', + 'pipe-format' => 'csv', + 'field-labels' => array('label' => 'Label', 'description' => 'Description'), + 'output-data-type' => 'format-table', + ), + 'aliases' => array('core:global-options'), + ); + $items['core-quick-drupal'] = array( + 'description' => 'Download, install, serve and login to Drupal with minimal configuration and dependencies.', + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'aliases' => array('qd', 'cutie', 'core:quick:drupal'), + 'arguments' => array( + 'site' => 'Short name for the site to be created - used as a directory name and as sqlite file name. Optional - if omitted timestamped "quick-drupal" directory will be used instead.', + 'projects' => 'A list of projects to download into the new site. If projects contain extensions (modules or themes) with the same name they will be enabled by default. See --enable option to control this behaviour further.', + ), + 'examples' => array( + 'drush qd' => 'Download and install stable release of Drupal into a timestamped directory, start server, and open the site logged in as admin.', + 'drush qd --profile=minimal --cache --core=drupal-8.0.x --yes' => 'Fire up dev release of Drupal site with minimal install profile.', + 'drush qd testsite devel --server=:8081/admin --browser=firefox --cache --yes' => 'Fire up stable release (using the cache) of Drupal site called "testsite", download and enable devel module, start a server on port 8081 and open /admin in firefox.', + 'drush qd commercesite --core=commerce_kickstart --profile=commerce_kickstart --cache --yes --watchdog' => 'Download and install the "Commerce Kickstart" distribution/install profile, display watchdog messages on the server console.', + 'drush qd --makefile=mysite.make' => 'Create and install a site from a makefile.', + ), + ); + // Add in options/engines. + drush_core_quick_drupal_options($items); + // Add in topics for engines + $items += drush_get_engine_topics(); + return $items; +} + +/** + * Command argument complete callback. + * + * @return + * Array of available profile names. + */ +function core_site_install_complete() { + $max = drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_ROOT); + if ($max >= DRUSH_BOOTSTRAP_DRUPAL_ROOT) { + return array('values' => array_keys(drush_find_profiles(DRUPAL_ROOT))); + } +} + +/** + * Command argument complete callback. + * + * @return + * Array of available site aliases. + */ +function core_core_rsync_complete() { + return array('values' => array_keys(_drush_sitealias_all_list())); +} + +/** + * @defgroup engines Engine types + * @{ + */ + +/** + * Implementation of hook_drush_engine_type_info(). + */ +function core_drush_engine_type_info() { + $info = array(); + $info['drupal'] = array(); + return $info; +} + +/** + * Implements hook_drush_engine_ENGINE_TYPE(). + */ +function core_drush_engine_drupal() { + $engines = array( + 'batch' => array(), + 'update'=> array(), + 'environment' => array(), + 'site_install' => array(), + 'pm' => array(), + 'cache' => array(), + 'image' => array(), + ); + return $engines; +} + +/** + * @} End of "Engine types". + */ + +/** + * Command handler. Apply pending entity schema updates. + */ +function drush_core_entity_updates() { + if (drush_get_context('DRUSH_SIMULATE')) { + drush_log(dt('entity-updates command does not support --simulate option.'), LogLevel::OK); + } + + drush_include_engine('drupal', 'update'); + if (entity_updates_main() === FALSE) { + return FALSE; + } + + drush_drupal_cache_clear_all(); + + drush_log(dt('Finished performing updates.'), LogLevel::OK); +} + +/** + * Command handler. Execute update.php code from drush. + */ +function drush_core_updatedb() { + if (drush_get_context('DRUSH_SIMULATE')) { + drush_log(dt('updatedb command does not support --simulate option.'), LogLevel::OK); + return TRUE; + } + + drush_include_engine('drupal', 'update'); + $result = update_main(); + if ($result === FALSE) { + return FALSE; + } + elseif ($result > 0) { + // Clear all caches in a new process. We just performed major surgery. + drush_drupal_cache_clear_all(); + + drush_log(dt('Finished performing updates.'), LogLevel::OK); + } +} + +/** + * Command handler. List pending DB updates. + */ +function drush_core_updatedb_status() { + require_once DRUSH_DRUPAL_CORE . '/includes/install.inc'; + drupal_load_updates(); + drush_include_engine('drupal', 'update'); + list($pending, $start) = updatedb_status(); + if (empty($pending)) { + drush_log(dt("No database updates required"), LogLevel::OK); + } + return $pending; +} + +function _core_site_credentials($right_margin = 0) { + // Leave some space on the right so that we can put the result into the + // drush_log, which will again wordwrap the result. + $original_columns = drush_get_context('DRUSH_COLUMNS', 80); + drush_set_context('DRUSH_COLUMNS', $original_columns - $right_margin); + $status_table = _core_site_status_table(); + $metadata = drush_get_command_format_metadata('core-status'); + $output = drush_format($status_table, $metadata, 'key-value'); + drush_set_context('DRUSH_COLUMNS', $original_columns); + return $output; +} + +function _core_path_aliases($project = '') { + $paths = array(); + $site_wide = drush_drupal_sitewide_directory(); + $boot = drush_get_bootstrap_object(); + if ($drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT')) { + $paths['%root'] = $drupal_root; + if ($site_root = drush_get_context('DRUSH_DRUPAL_SITE_ROOT')) { + $paths['%site'] = $site_root; + if (is_dir($modules_path = $boot->conf_path() . '/modules')) { + $paths['%modules'] = $modules_path; + } + else { + $paths['%modules'] = ltrim($site_wide . '/modules', '/'); + } + if (is_dir($themes_path = $boot->conf_path() . '/themes')) { + $paths['%themes'] = $themes_path; + } + else { + $paths['%themes'] = ltrim($site_wide . '/themes', '/'); + } + if (drush_drupal_major_version() >= 8 && drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION)) { + try { + $config_labels = ['sync' => 'sync']; + if (isset($GLOBALS['config_directories'])) { + $config_labels = $GLOBALS['config_directories']; + } + + foreach ($config_labels as $label => $unused) { + $path = drush_config_get_config_directory($label); + if (!empty($path)) { + $paths["%config-$label"] = $path; + } + } + } + catch (Exception $e) { + // Nothing to do. + } + } + + if (drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)) { + $paths['%files'] = drush_file_get_public(); + if ($private_path = drush_file_get_private()) { + $paths['%private'] = $private_path; + } + } + + if (function_exists('file_directory_temp')) { + $paths['%temp'] = file_directory_temp(); + } + // If the 'project' parameter was specified, then search + // for a project (or a few) and add its path to the path list + if (!empty($project)) { + drush_include_engine('drupal', 'environment'); + $projects = array_merge(drush_get_modules(), drush_get_themes()); + foreach(explode(',', $project) as $target) { + if (array_key_exists($target, $projects)) { + $paths['%' . $target] = $drupal_root . '/' . _drush_extension_get_path($projects[$target]); + } + } + } + } + } + + // Add in all of the global paths from $options['path-aliases'] + $paths = array_merge($paths, drush_get_option('path-aliases', array())); + + return $paths; +} + +function _core_site_status_table($project = '') { + $phase = drush_get_context('DRUSH_BOOTSTRAP_PHASE'); + if ($drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT')) { + $status_table['drupal-version'] = drush_drupal_version(); + $boot_object = drush_get_bootstrap_object(); + $conf_dir = $boot_object->conf_path(); + $settings_file = "$conf_dir/settings.php"; + $status_table['drupal-settings-file'] = file_exists($settings_file) ? $settings_file : ''; + if ($site_root = drush_get_context('DRUSH_DRUPAL_SITE_ROOT')) { + $status_table['uri'] = drush_get_context('DRUSH_URI'); + try { + $sql = drush_sql_get_class(); + $db_spec = $sql->db_spec(); + $status_table['db-driver'] = $db_spec['driver']; + if (!empty($db_spec['unix_socket'])) { + $status_table['db-socket'] = $db_spec['unix_socket']; + } + elseif (isset($db_spec['host'])) { + $status_table['db-hostname'] = $db_spec['host']; + } + $status_table['db-username'] = isset($db_spec['username']) ? $db_spec['username'] : NULL; + $status_table['db-password'] = isset($db_spec['password']) ? $db_spec['password'] : NULL; + $status_table['db-name'] = isset($db_spec['database']) ? $db_spec['database'] : NULL; + $status_table['db-port'] = isset($db_spec['port']) ? $db_spec['port'] : NULL; + if ($phase > DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION) { + $status_table['install-profile'] = $boot_object->get_profile(); + if ($phase > DRUSH_BOOTSTRAP_DRUPAL_DATABASE) { + $status_table['db-status'] = dt('Connected'); + if ($phase > DRUSH_BOOTSTRAP_DRUPAL_FULL) { + $status_table['bootstrap'] = dt('Successful'); + if ($phase == DRUSH_BOOTSTRAP_DRUPAL_LOGIN) { + $status_table['user'] = drush_user_get_class()->getCurrentUserAsSingle()->getUsername(); + } + } + } + } + } + catch (Exception $e) { + // Don't worry be happy. + } + } + if (drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)) { + $status_table['theme'] = drush_theme_get_default(); + $status_table['admin-theme'] = drush_theme_get_admin(); + } + } + if ($php_bin = drush_get_option('php')) { + $status_table['php-bin'] = $php_bin; + } + $status_table['php-os'] = PHP_OS; + if ($php_ini_files = _drush_core_config_php_ini_files()) { + $status_table['php-conf'] = $php_ini_files; + } + $status_table['drush-script'] = DRUSH_COMMAND; + $status_table['drush-version'] = DRUSH_VERSION; + $status_table['drush-temp'] = drush_find_tmp(); + $status_table['drush-conf'] = drush_flatten_array(drush_get_context_options('context-path', '')); + $alias_files = _drush_sitealias_find_alias_files(); + $status_table['drush-alias-files'] = $alias_files; + + $paths = _core_path_aliases($project); + if (!empty($paths)) { + foreach ($paths as $target => $one_path) { + $name = $target; + if (substr($name,0,1) == '%') { + $name = substr($name,1); + } + $status_table[$name] = $one_path; + } + } + + // Store the paths into the '%paths' index; this will be + // used by other code, but will not be included in the output + // of the drush status command. + $status_table['%paths'] = $paths; + + return $status_table; +} + +// Adjust the status output for any non-pipe output format +function _drush_core_status_format_table_data($output, $metadata) { + if (drush_get_option('full', FALSE) == FALSE) { + // Hide any key that begins with a % + foreach ($output as $key => $value) { + if ($key[0] == '%') { + unset($output[$key]); + } + } + // Hide 'Modules path' and 'Themes path' as well + unset($output['modules']); + unset($output['themes']); + // Shorten the list of alias files if there are too many + if (isset($output['drush-alias-files']) && count($output['drush-alias-files']) > 24) { + $msg = dt("\nThere are !count more alias files. Run with --full to see the full list.", array('!count' => count($output['drush-alias-files']) - 1)); + $output['drush-alias-files'] = array($output['drush-alias-files'][0] , $msg); + } + if (isset($output['drupal-settings-file']) && empty($output['drupal-settings-file'])) { + $output['drupal-settings-file'] = dt('MISSING'); + } + } + return $output; +} + +/** + * Command callback. Runs all cron hooks. + */ +function drush_core_cron() { + if (drush_drupal_major_version() < 8) { + $result = drupal_cron_run(); + } + else { + $result = \Drupal::service('cron')->run(); + } + if ($result) { + drush_log(dt('Cron run successful.'), LogLevel::SUCCESS); + } + else { + return drush_set_error('DRUSH_CRON_FAILED', dt('Cron run failed.')); + } +} + +/** + * Command callback. Edit drushrc and alias files. + */ +function drush_core_config($filter = NULL) { + $all = drush_core_config_load(); + + // Apply any filter that was supplied. + if ($filter) { + foreach ($all as $key => $file) { + if (strpos($file, $filter) === FALSE) { + unset($all[$key]); + } + } + } + $all = drush_map_assoc(array_values($all)); + + $exec = drush_get_editor(); + if (count($all) == 1) { + $filepath = current($all); + } + else { + $choice = drush_choice($all, 'Enter a number to choose which file to edit.', '!key'); + if (!$choice) { + return drush_user_abort(); + } + $filepath = $all[$choice]; + } + return drush_shell_exec_interactive($exec, $filepath, $filepath); +} + +/** + * Command argument complete callback. + * + * @return + * Array of available configuration files for editing. + */ +function core_core_config_complete() { + return array('values' => drush_core_config_load(FALSE)); +} + +function drush_core_config_load($headers = TRUE) { + $php_header = $php = $rcs_header = $rcs = $aliases_header = $aliases = $drupal_header = $drupal = array(); + $php = _drush_core_config_php_ini_files(); + if (!empty($php)) { + if ($headers) { + $php_header = array('phpini' => '-- PHP ini files --'); + } + } + + $bash = _drush_core_config_bash_files(); + if (!empty($bash)) { + if ($headers) { + $bash_header = array('bash' => '-- Bash files --'); + } + } + + drush_sitealias_load_all(); + if ($rcs = drush_get_context_options('context-path', TRUE)) { + if ($headers) { + $rcs_header = array('drushrc' => '-- Drushrc --'); + } + } + if ($aliases = drush_get_context('drush-alias-files')) { + if ($headers) { + $aliases_header = array('aliases' => '-- Aliases --'); + } + } + if ($site_root = drush_get_context('DRUSH_DRUPAL_SITE_ROOT')) { + $drupal[] = realpath($site_root . '/settings.php'); + if (file_exists($site_root . '/settings.local.php')) { + $drupal[] = realpath($site_root . '/settings.local.php'); + } + $drupal[] = realpath(DRUPAL_ROOT . '/.htaccess'); + if ($headers) { + $drupal_header = array('drupal' => '-- Drupal --'); + } + } + return array_merge($php_header, $php, $bash_header, $bash, $rcs_header, $rcs, $aliases_header, $aliases, $drupal_header, $drupal); +} + +function _drush_core_config_php_ini_files() { + $ini_files = array(); + $ini_files[] = php_ini_loaded_file(); + if ($drush_ini = getenv('DRUSH_INI')) { + if (file_exists($drush_ini)) { + $ini_files[] = $drush_ini; + } + } + foreach (array(DRUSH_BASE_PATH, '/etc/drush', drush_server_home() . '/.drush') as $ini_dir) { + if (file_exists($ini_dir . "/drush.ini")) { + $ini_files[] = realpath($ini_dir . "/drush.ini"); + } + } + return array_unique($ini_files); +} + +function _drush_core_config_bash_files() { + $bash_files = array(); + $home = drush_server_home(); + if ($bashrc = drush_init_find_bashrc($home)) { + $bash_files[] = $bashrc; + } + $prompt = $home . '/.drush/drush.prompt.sh'; + if (file_exists($prompt)) { + $bash_files[] = $prompt; + } + return $bash_files; +} + +/** + * Command callback. Provides information from the 'Status Reports' admin page. + */ +function drush_core_requirements() { + include_once DRUSH_DRUPAL_CORE . '/includes/install.inc'; + $severities = array( + REQUIREMENT_INFO => t('Info'), + REQUIREMENT_OK => t('OK'), + REQUIREMENT_WARNING => t('Warning'), + REQUIREMENT_ERROR => t('Error'), + ); + + drupal_load_updates(); + + drush_include_engine('drupal', 'environment'); + $requirements = drush_module_invoke_all('requirements', 'runtime'); + // If a module uses "$requirements[] = " instead of + // "$requirements['label'] = ", then build a label from + // the title. + foreach($requirements as $key => $info) { + if (is_numeric($key)) { + unset($requirements[$key]); + $new_key = strtolower(str_replace(' ', '_', $info['title'])); + $requirements[$new_key] = $info; + } + } + $ignore_requirements = drush_get_option_list('ignore'); + foreach ($ignore_requirements as $ignore) { + unset($requirements[$ignore]); + } + ksort($requirements); + + $min_severity = drush_get_option('severity', -1); + foreach($requirements as $key => $info) { + $severity = array_key_exists('severity', $info) ? $info['severity'] : -1; + $requirements[$key]['sid'] = $severity; + $requirements[$key]['severity'] = $severities[$severity]; + if ($severity < $min_severity) { + unset($requirements[$key]); + } + if (isset($requirements[$key]['description'])) { + $requirements[$key]['description'] = drush_render($requirements[$key]['description']); + } + } + return $requirements; +} + +/** + * Command callback. Provides a birds-eye view of the current Drupal + * installation. + */ +function drush_core_status() { + $status_table = _core_site_status_table(drush_get_option('project','')); + // If args are specified, filter out any entry that is not named + // (in other words, only show lines named by one of the arg values) + $args = func_get_args(); + if (!empty($args)) { + $field_list = $args; + $metadata = drush_get_command_format_metadata('core-status'); + foreach ($metadata['field-labels'] as $field_key => $field_label) { + if (_drush_core_is_named_in_array($field_label, $args)) { + $field_list[] = $field_key; + } + } + foreach ($status_table as $key => $value) { + if (!_drush_core_is_named_in_array($key, $field_list)) { + unset($status_table[$key]); + } + } + } + return $status_table; +} + +// Command callback. Show all global options. Exposed via topic command. +function drush_core_global_options() { + drush_print(dt('These options are applicable to most drush commands. Most options can be disabled by using --no-option (i.e. --no-debug to disable --debug.)')); + drush_print(); + $fake = drush_global_options_command(FALSE); + return drush_format_help_section($fake, 'options'); +} + +function _drush_core_is_named_in_array($key, $the_array) { + $is_named = FALSE; + + $simplified_key = str_replace(array(' ', '_', '-'), array('', '', ''), $key); + + foreach ($the_array as $name) { + if (stristr($simplified_key, str_replace(array(' ', '_', '-'), array('', '', ''), $name))) { + $is_named = TRUE; + } + } + + return $is_named; +} + +/** + * Callback for core-quick-drupal command. + */ +function drush_core_quick_drupal() { + $requests = FALSE; + $make_projects = array(); + $args = func_get_args(); + $name = drush_get_option('use-name'); + drush_set_option('backend', TRUE); + drush_set_option('strict', FALSE); // We fail option validation because do so much internal drush_invoke(). + $makefile = drush_get_option('makefile'); + $root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT'); + if (drush_get_option('use-existing', ($root != FALSE))) { + if (!$root) { + return drush_set_error('QUICK_DRUPAL_NO_ROOT_SPECIFIED', 'Must specify site with --root when using --use-existing.'); + } + // If a --db-url was not explicitly provided, and if there is already + // a settings.php file provided, then use the db-url defined inside it. + if (!drush_get_option('db-url', FALSE)) { + $values = drush_invoke_process('@self', 'site-alias', array('@self'), array('with-db-url' => TRUE), array('integrate' => FALSE, 'override-simulated' => TRUE)); + if (!empty($values['object']['self']['db-url'])) { + drush_set_option('db-url', $values['object']['self']['db-url']); + } + } + if (empty($name)) { + $name = basename($root); + } + $base = dirname($root); + } + else { + if (!empty($args) && empty($name)) { + $name = array_shift($args); + } + if (empty($name)) { + $name = 'quick-drupal-' . gmdate('YmdHis', $_SERVER['REQUEST_TIME']); + } + $root = drush_get_option('root', FALSE); + $core = drush_get_option('core', 'drupal'); + $project_rename = $core; + if ($root) { + $base = dirname($root); + $project_rename = basename($root); + } + else { + $base = getcwd() . '/' . $name; + $root = $base . '/' . $core; + } + if (!empty($makefile)) { + // Invoke 'drush make $makefile'. + $result = drush_invoke_process('@none', 'make', array($makefile, $root), array('core-quick-drupal' => TRUE)); + if ($result['error_status'] != 0) { + return drush_set_error('DRUSH_QD_MAKE', 'Could not make; aborting.'); + } + $make_projects = array_diff(array_keys($result['object']['projects']), array('drupal')); + } + else { + drush_mkdir($base); + drush_set_option('destination', $base); + drush_set_option('drupal-project-rename', $project_rename); + if (drush_invoke('pm-download', array($core)) === FALSE) { + return drush_set_error('QUICK_DRUPAL_CORE_DOWNLOAD_FAIL', 'Drupal core download/extract failed.'); + } + } + } + if (!drush_get_option('db-url', FALSE)) { + drush_set_option('db-url', 'sqlite://sites/' . strtolower(drush_get_option('sites-subdir', 'default')) . "/files/$name.sqlite"); + } + // We have just created a site root where one did not exist before. + // We therefore must manually reset DRUSH_SELECTED_DRUPAL_ROOT to + // our new root, and force a bootstrap to DRUSH_BOOTSTRAP_DRUPAL_ROOT. + drush_set_context('DRUSH_SELECTED_DRUPAL_ROOT', $root); + if (!drush_bootstrap_to_phase(DRUSH_BOOTSTRAP_DRUPAL_ROOT)) { + return drush_set_error('QUICK_DRUPAL_ROOT_LOCATE_FAIL', 'Unable to locate Drupal root directory.'); + } + if (!empty($args)) { + $requests = pm_parse_arguments($args, FALSE); + } + if ($requests) { + // Unset --destination, so that downloads go to the site directories. + drush_unset_option('destination'); + if (drush_invoke('pm-download', $requests) === FALSE) { + return drush_set_error('QUICK_DRUPAL_PROJECT_DOWNLOAD_FAIL', 'Project download/extract failed.'); + } + } + drush_invoke('site-install', array(drush_get_option('profile'))); + // Log in with the admin user. + // TODO: If site-install is given a sites-subdir other than 'default', + // then it will bootstrap to DRUSH_BOOTSTRAP_DRUPAL_SITE get the installer + // to recognize the desired site directory. This somehow interferes + // with our desire to bootstrap to DRUSH_BOOTSTRAP_DRUPAL_LOGIN here. + // We could do the last few steps in a new process if uri is not 'default'. + drush_set_option('user', '1'); + if (!drush_bootstrap_to_phase(DRUSH_BOOTSTRAP_DRUPAL_LOGIN)) { + return drush_set_error('QUICK_DRUPAL_INSTALL_FAIL', 'Drupal core install failed.'); + } + $enable = array_merge(pm_parse_arguments(drush_get_option('enable', $requests)), $make_projects); + if (!empty($enable)) { + if (drush_invoke('pm-enable', $enable) === FALSE) { + return drush_set_error('QUICK_DRUPAL_PROJECT_ENABLE_FAIL', 'Project enable failed.'); + } + } + $server = drush_get_option('server', '/'); + if ($server) { + $server_uri = runserver_uri($server); + _drush_core_qd_cache_uri($server_uri); + } + if (!drush_get_option('no-server', FALSE)) { + if ($server) { + // Current CLI user is also the web server user, which is for development + // only. Hence we can safely make the site directory writable. This makes + // it easier to delete and edit settings.php. + $boot = drush_get_bootstrap_object(); + @chmod($boot->conf_path(), 0700); + drush_invoke_process(array('root' => $root, 'uri' => $server_uri), 'runserver', array($server)); + } + } + else { + drush_print(dt('Login URL: ') . drush_invoke('user-login')); + } +} + +// Write a drushrc.php to cache the server information for future Drush calls +function _drush_core_qd_cache_uri($uri) { + $server = $uri['host']; + if (!empty($uri["port"])) { + $server .= ':' . $uri["port"]; + } + $dir = DRUPAL_ROOT . '/drush'; + $target_file = $dir . '/drushrc.php'; + drush_log(dt("Caching 'uri' !uri in !target", array('!uri' => $server, '!target' => $target_file)), LogLevel::OK); + $create_file = TRUE; + if (file_exists($target_file)) { + // Don't bother to ask with --use-existing; just + // continue. + if (drush_get_option('use-existing', FALSE)) { + $create_file = FALSE; + } + else { + $create_file = drush_confirm(dt('!target already exists. Overwrite?', array('!target' => $target_file))); + } + } + $content = << 'Drupal core to download. Defaults to "drupal" (latest stable version).', + 'use-existing' => 'Use an existing Drupal root, specified with --root. Overrides --core. Defaults to true when run from an existing site.', + 'profile' => 'The install profile to use. Defaults to standard.', + 'enable' => 'Specific extensions (modules or themes) to enable. By default, extensions with the same name as requested projects will be enabled automatically.', + 'server' => 'Host IP address and port number to bind to and path to open in web browser (hyphen to clear a default path), all elements optional. See runserver examples for shorthand.', + 'no-server' => 'Avoid starting runserver (and browser) for the created Drupal site.', + 'browser' => 'Optional name of a browser to open site in. If omitted the OS default browser will be used. Set --no-browser to disable.', + 'use-name' => array('hidden' => TRUE, 'description' => 'Overrides "name" argument.'), + 'makefile' => array('description' => 'Makefile to use. Makefile must specify which version of Drupal core to build.', 'example-value' => 'mysite.make', 'value' => 'optional'), + 'root' => array('description' => 'Path to Drupal root.', 'example-value' => '/path/to/root', 'value' => 'optional'), + ); + $pm = pm_drush_command(); + foreach ($pm['pm-download']['options'] as $option => $description) { + if (is_array($description)) { + $description = $description['description']; + } + $options[$option] = 'Download option: ' . $description; + } + // Unset a few options that are not usable here, as we control them ourselves + // or they are otherwise implied by the environment. + unset($options['destination']); + unset($options['drupal-project-rename']); + unset($options['default-major']); + unset($options['use-site-dir']); + $si = site_install_drush_command(); + foreach ($si['site-install']['options'] as $option => $description) { + if (is_array($description)) { + $description = $description['description']; + } + $options[$option] = 'Site install option: ' . $description; + } + unset($options['sites-subdir']); + $runserver = runserver_drush_command(); + foreach ($runserver['runserver']['options'] as $option => $description) { + $options[$option] = 'Runserver option: ' . $description; + } + unset($options['user']); + $items['core-quick-drupal']['options'] = $options; + $items['core-quick-drupal']['engines'] = $pm['pm-download']['engines']; +} + +/** + * Command callback. Runs "naked" php scripts + * and drush "shebang" scripts ("#!/usr/bin/env drush"). + */ +function drush_core_php_script() { + $found = FALSE; + $script = NULL; + if ($args = func_get_args()) { + $script = $args[0]; + } + + if ($script == '-') { + return eval(stream_get_contents(STDIN)); + } + elseif (file_exists($script)) { + $found = $script; + } + else { + // Array of paths to search for scripts + $searchpath['DIR'] = dirname(__FILE__); + $searchpath['cwd'] = drush_cwd(); + + // Additional script paths, specified by 'script-path' option + if ($script_path = drush_get_option('script-path', FALSE)) { + foreach (explode(PATH_SEPARATOR, $script_path) as $path) { + $searchpath[] = $path; + } + } + drush_log(dt('Searching for scripts in ') . implode(',', $searchpath), LogLevel::DEBUG); + + if (!isset($script)) { + // List all available scripts. + $all = array(); + foreach($searchpath as $key => $path) { + $recurse = !(($key == 'cwd') || ($path == '/')); + $all = array_merge( $all , array_keys(drush_scan_directory($path, '/\.php$/', array('.', '..', 'CVS'), NULL, $recurse)) ); + } + drush_print(implode("\n", $all)); + } + else { + // Execute the specified script. + foreach($searchpath as $path) { + $script_filename = $path . '/' . $script; + if (file_exists($script_filename . '.php')) { + $script_filename .= '.php'; + } + if (file_exists($script_filename)) { + $found = $script_filename; + break; + } + $all[] = $script_filename; + } + if (!$found) { + return drush_set_error('DRUSH_TARGET_NOT_FOUND', dt('Unable to find any of the following: @files', array('@files' => implode(', ', $all)))); + } + } + } + + if ($found) { + // Set the DRUSH_SHIFT_SKIP to two; this will cause + // drush_shift to skip the next two arguments the next + // time it is called. This allows scripts to get all + // arguments, including the 'php-script' and script + // pathname, via drush_get_arguments(), or it can process + // just the arguments that are relevant using drush_shift(). + drush_set_context('DRUSH_SHIFT_SKIP', 2); + if (_drush_core_eval_shebang_script($found) === FALSE) { + return include($found); + } + } +} + +function drush_core_php_eval($php) { + return eval($php . ';'); +} + +/** + * Evaluate a script that begins with #!drush php-script + */ +function _drush_core_eval_shebang_script($script_filename) { + $found = FALSE; + $fp = fopen($script_filename, "r"); + if ($fp !== FALSE) { + $line = fgets($fp); + if (_drush_is_drush_shebang_line($line)) { + $first_script_line = ''; + while ($line = fgets($fp)) { + $line = trim($line); + if ($line == ' $target))); + } +} + +/** + * Called for `drush version` or `drush --version` + */ +function drush_core_version() { + return DRUSH_VERSION; +} + +/** + * Command callback. Execute specified shell code. Often used by shell aliases + * that start with !. + */ +function drush_core_execute() { + $result = TRUE; + $escape = drush_get_option('escape', TRUE); + // Get all of the args and options that appear after the command name. + $args = drush_get_original_cli_args_and_options(); + if ($escape) { + for ($x = 0; $x < count($args); $x++) { + // escape all args except for command separators. + if (!in_array($args[$x], array('&&', '||', ';'))) { + $args[$x] = drush_escapeshellarg($args[$x]); + } + } + } + $cmd = implode(' ', $args); + // If we selected a Drupal site, then cwd to the site root prior to exec + $cwd = FALSE; + if ($selected_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT')) { + if (is_dir($selected_root)) { + $cwd = getcwd(); + drush_op('chdir', $selected_root); + } + } + if ($alias = drush_get_context('DRUSH_TARGET_SITE_ALIAS')) { + $site = drush_sitealias_get_record($alias); + if (!empty($site['site-list'])) { + $sites = drush_sitealias_resolve_sitelist($site); + foreach ($sites as $site_name => $site_spec) { + $result = _drush_core_execute_cmd($site_spec, $cmd); + if (!$result) { + break; + } + } + } + else { + $result = _drush_core_execute_cmd($site, $cmd); + } + } + else { + // Must be a local command. + $result = (drush_shell_proc_open($cmd) == 0); + } + // Restore the cwd if we changed it + if ($cwd) { + drush_op('chdir', $selected_root); + } + if (!$result) { + return drush_set_error('CORE_EXECUTE_FAILED', dt("Command !command failed.", array('!command' => $cmd))); + } + return $result; +} + +function drush_core_twig_compile() { + require_once DRUSH_DRUPAL_CORE . "/themes/engines/twig/twig.engine"; + // Scan all enabled modules and themes. + // @todo refactor since \Drush\Boot\DrupalBoot::commandfile_searchpaths is similar. + $ignored_modules = drush_get_option_list('ignored-modules', array()); + $cid = drush_cid_install_profile(); + if ($cached = drush_cache_get($cid)) { + $ignored_modules[] = $cached->data; + } + foreach (array_diff(drush_module_list(), $ignored_modules) as $module) { + $searchpaths[] = drupal_get_path('module', $module); + } + + $themes = drush_theme_list(); + foreach ($themes as $name => $theme) { + $searchpaths[] = $theme->getPath(); + } + + foreach ($searchpaths as $searchpath) { + foreach ($file = drush_scan_directory($searchpath, '/\.html.twig/', array('tests')) as $file) { + $relative = str_replace(drush_get_context('DRUSH_DRUPAL_ROOT'). '/', '', $file->filename); + // @todo Dynamically disable twig debugging since there is no good info there anyway. + twig_render_template($relative, array('theme_hook_original' => '')); + drush_log(dt('Compiled twig template !path', array('!path' => $relative)), LogLevel::NOTICE); + } + } +} + +/** + * Helper function for drush_core_execute: run one shell command + */ +function _drush_core_execute_cmd($site, $cmd) { + if (!empty($site['remote-host'])) { + // Remote, so execute an ssh command with a bash fragment at the end. + $exec = drush_shell_proc_build($site, $cmd, TRUE); + return (drush_shell_proc_open($exec) == 0); + } + elseif (!empty($site['root']) && is_dir($site['root'])) { + return (drush_shell_proc_open('cd ' . drush_escapeshellarg($site['root']) . ' && ' . $cmd) == 0); + } + return (drush_shell_proc_open($cmd) == 0); +} diff --git a/vendor/drush/drush/commands/core/docs.drush.inc b/vendor/drush/drush/commands/core/docs.drush.inc new file mode 100644 index 0000000000..eadeabb0bf --- /dev/null +++ b/vendor/drush/drush/commands/core/docs.drush.inc @@ -0,0 +1,332 @@ + TRUE to indicate the command is a topic (REQUIRED) + // Begin the topic name with the name of the commandfile (just like + // any other command). + // + $items['docs-readme'] = array( + 'description' => 'README.md', + 'hidden' => TRUE, + 'topic' => TRUE, + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'callback' => 'drush_print_file', + 'callback arguments' => array($docs_dir . '/README.md'), + 'aliases' => array('docs:readme'), + ); + $items['docs-bisect'] = array( + 'description' => 'git bisect and Drush may be used together to find the commit an error was introduced in.', + 'hidden' => TRUE, + 'topic' => TRUE, + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'callback' => 'drush_print_file', + 'callback arguments' => array($docs_dir . '/examples/git-bisect.example.sh'), + 'aliases' => array('docs:bisect'), + ); + $items['docs-bashrc'] = array( + 'description' => 'Bashrc customization examples for Drush.', + 'hidden' => TRUE, + 'topic' => TRUE, + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'callback' => 'drush_print_file', + 'callback arguments' => array($docs_dir . '/examples/example.bashrc'), + 'aliases' => array('docs:bashrc'), + ); + $items['docs-configuration'] = array( + 'description' => 'Configuration overview with examples from example.drushrc.php.', + 'hidden' => TRUE, + 'topic' => TRUE, + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'callback' => 'drush_print_file', + 'callback arguments' => array($docs_dir . '/examples/example.drushrc.php'), + 'aliases' => array('docs:configuration'), + ); + $items['docs-config-exporting'] = array( + 'description' => 'Drupal configuration export instructions, including customizing configuration by environment.', + 'hidden' => TRUE, + 'topic' => TRUE, + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'callback' => 'drush_print_file', + 'callback arguments' => array($docs_dir . '/docs/config-exporting.md'), + 'aliases' => array('docs:config:exporting'), + ); + $items['docs-aliases'] = array( + 'description' => 'Site aliases overview on creating your own aliases for commonly used Drupal sites with examples from example.aliases.drushrc.php.', + 'hidden' => TRUE, + 'topic' => TRUE, + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'callback' => 'drush_print_file', + 'callback arguments' => array($docs_dir . '/examples/example.aliases.drushrc.php'), + 'aliases' => array('docs:aliases'), + ); + $items['docs-ini-files'] = array( + 'description' => 'php.ini or drush.ini configuration to set PHP values for use with Drush.', + 'hidden' => TRUE, + 'topic' => TRUE, + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'callback' => 'drush_print_file', + 'callback arguments' => array($docs_dir . '/examples/example.drush.ini'), + 'aliases' => array('docs:ini-files'), + ); + $items['docs-bastion'] = array( + 'description' => 'Bastion server configuration: remotely operate on a Drupal sites behind a firewall.', + 'hidden' => TRUE, + 'topic' => TRUE, + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'callback' => 'drush_print_file', + 'callback arguments' => array($docs_dir . '/docs/bastion.md'), + 'aliases' => array('docs:bastion'), + ); + $items['docs-bootstrap'] = array( + 'description' => 'Bootstrap explanation: how Drush starts up and prepares the Drupal environment for use with the command.', + 'hidden' => TRUE, + 'topic' => TRUE, + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'callback' => 'drush_print_file', + 'callback arguments' => array($docs_dir . '/docs/bootstrap.md'), + 'aliases' => array('docs:bootstrap'), + ); + $items['docs-cron'] = array( + 'description' => 'Crontab instructions for running your Drupal cron tasks via `drush cron`.', + 'hidden' => TRUE, + 'topic' => TRUE, + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'callback' => 'drush_print_file', + 'callback arguments' => array($docs_dir . '/docs/cron.md'), + 'aliases' => array('docs:cron'), + ); + $items['docs-scripts'] = array( + 'description' => 'Shell script overview on writing simple sequences of Drush statements.', + 'hidden' => TRUE, + 'topic' => TRUE, + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'callback' => 'drush_print_file', + 'callback arguments' => array($docs_dir . '/docs/shellscripts.md'), + 'aliases' => array('docs:scripts'), + ); + $items['docs-shell-aliases'] = array( + 'description' => 'Shell alias overview on creating your own aliases for commonly used Drush commands.', + 'hidden' => TRUE, + 'topic' => TRUE, + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'callback' => 'drush_print_file', + 'callback arguments' => array($docs_dir . '/docs/shellaliases.md'), + 'aliases' => array('docs:shell-aliases'), + ); + $items['docs-commands'] = array( + 'description' => 'Drush command instructions on creating your own Drush commands.', + 'hidden' => TRUE, + 'topic' => TRUE, + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'callback' => 'drush_print_file', + 'callback arguments' => array($docs_dir . '/docs/commands.md'), + 'aliases' => array('docs:commands'), + ); + $items['docs-errorcodes'] = array( + 'description' => 'Error code list containing all identifiers used with drush_set_error.', + 'hidden' => TRUE, + 'topic' => TRUE, + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'aliases' => array('docs:errorcodes'), + ); + $items['docs-api'] = array( + 'description' => 'Drush API', + 'hidden' => TRUE, + 'topic' => TRUE, + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'callback' => 'drush_print_file', + 'callback arguments' => array($docs_dir . '/drush.api.php'), + 'aliases' => array('docs:api'), + ); + $items['docs-context'] = array( + 'description' => 'Contexts overview explaining how Drush manages command line options and configuration file settings.', + 'hidden' => TRUE, + 'topic' => TRUE, + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'callback' => 'drush_print_file', + 'callback arguments' => array($docs_dir . '/docs/context.md'), + 'aliases' => array('docs:context'), + ); + $items['docs-examplescript'] = array( + 'description' => 'Example Drush script.', + 'hidden' => TRUE, + 'topic' => TRUE, + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'callback' => 'drush_print_file', + 'callback arguments' => array($docs_dir . '/examples/helloworld.script'), + 'aliases' => array('docs:examplescript'), + ); + $items['docs-examplecommand'] = array( + 'description' => 'Example Drush command file.', + 'hidden' => TRUE, + 'topic' => TRUE, + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'callback' => 'drush_print_file', + 'callback arguments' => array($docs_dir . '/examples/sandwich.drush.inc'), + 'aliases' => array('docs:examplecommand'), + ); + $items['docs-example-sync-extension'] = array( + 'description' => 'Example Drush commandfile that extends sql-sync to enable development modules in the post-sync hook.', + 'hidden' => TRUE, + 'topic' => TRUE, + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'callback' => 'drush_print_file', + 'callback arguments' => array($docs_dir . '/examples/sync_enable.drush.inc'), + 'aliases' => array('docs:example-sync-extension'), + ); + $items['docs-example-sync-via-http'] = array( + 'description' => 'Example Drush commandfile that extends sql-sync to allow transfer of the sql dump file via http rather than ssh and rsync.', + 'hidden' => TRUE, + 'topic' => TRUE, + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'callback' => 'drush_print_file', + 'callback arguments' => array($docs_dir . '/examples/sync_via_http.drush.inc'), + 'aliases' => array('docs:example-sync-via-http'), + ); + $items['docs-policy'] = array( + 'description' => 'Example policy file.', + 'hidden' => TRUE, + 'topic' => TRUE, + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'callback' => 'drush_print_file', + 'callback arguments' => array($docs_dir . '/examples/policy.drush.inc'), + 'aliases' => array('docs:policy'), + ); + $items['docs-strict-options'] = array( + 'description' => 'Strict option handling, and how commands that use it differ from regular Drush commands.', + 'hidden' => TRUE, + 'topic' => TRUE, + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'callback' => 'drush_print_file', + 'callback arguments' => array($docs_dir . '/docs/strict-options.md'), + 'aliases' => array('docs:strict-options'), + ); + return $items; +} + +/** + * docs-errorcodes command. Print a list of all error codes + * that can be found. + */ +function drush_docs_errorcodes() { + $header = << $command) { + $files = array_merge($files, drush_command_get_includes($command_name)); + } + // We will also search through all of the .inc files in the + // drush includes directory + $drush_include_files = drush_scan_directory(DRUSH_BASE_PATH . '/includes', '/.*\.inc$/', array('.', '..', 'CVS'), 0, FALSE); + foreach ($drush_include_files as $filename => $info) { + $files[$filename] = 'include'; + } + + // Extract error messages from all command files + $error_list = array(); + foreach ($files as $file => $commandfile) { + _drush_docs_find_set_error_calls($error_list, $file, $commandfile); + } + // Order error messages alphabetically by key + ksort($error_list); + // Convert to a table + $data = array(); + foreach ($error_list as $error_code => $error_messages) { + $data[] = array($error_code, '-', implode("\n", $error_messages)); + } + + $tmpfile = drush_tempnam('drush-errorcodes.'); + file_put_contents($tmpfile, $header); + $handle = fopen($tmpfile, 'a'); + drush_print_table($data, FALSE, array(0 => 35), $handle); + fclose($handle); + drush_print_file($tmpfile); +} + +/** + * Search through a php source file looking for calls to + * the function drush_set_error. If found, and if the + * first parameter is an uppercase alphanumeric identifier, + * then record the error code and the error message in our table. + */ +function _drush_docs_find_set_error_calls(&$error_list, $filename, $shortname) { + $lines = file($filename); + foreach ($lines as $line) { + $matches = array(); + // Find the error code after the drush_set_error call. The error code + // should consist of uppercase letters and underscores only (numbers thrown in just in case) + $match_result = preg_match("/.*drush_set_error[^'\"]['\"]([A-Z0-9_]*)['\"][^,]*,[^'\"]*(['\"])/", $line, $matches); + if ($match_result) { + $error_code = $matches[1]; + $quote_char = $matches[2]; + $error_message = ""; + $message_start = strlen($matches[0]) - 1; + + // Regex adapted from http://stackoverflow.com/questions/1824325/regex-expression-for-escaped-quoted-string-wont-work-in-phps-preg-match-allif ($quote_char == '"') { + if ($quote_char == '"') { + $regex = '/"((?:[^\\\]*?(?:\\\")?)*?)"/'; + } + else { + $regex = "/'((?:[^\\\]*?(?:\\\')?)*?)'/"; + } + $match_result = preg_match($regex, $line, $matches, 0, $message_start); + + if ($match_result) { + $error_message = $matches[1]; + } + $error_list[$error_code] = array_key_exists($error_code, $error_list) ? array_merge($error_list[$error_code], array($error_message)) : array($error_message); + } + } +} diff --git a/vendor/drush/drush/commands/core/drupal/batch.inc b/vendor/drush/drush/commands/core/drupal/batch.inc new file mode 100644 index 0000000000..574df084f4 --- /dev/null +++ b/vendor/drush/drush/commands/core/drupal/batch.inc @@ -0,0 +1,264 @@ + 0, + ); + $batch += $process_info; + + // The batch is now completely built. Allow other modules to make changes + // to the batch so that it is easier to reuse batch processes in other + // enviroments. + \Drupal::moduleHandler()->alter('batch', $batch); + + // Assign an arbitrary id: don't rely on a serial column in the 'batch' + // table, since non-progressive batches skip database storage completely. + $batch['id'] = \Drupal::database()->nextId(); + $args[] = $batch['id']; + + $batch['progressive'] = TRUE; + + // Move operations to a job queue. Non-progressive batches will use a + // memory-based queue. + foreach ($batch['sets'] as $key => $batch_set) { + _batch_populate_queue($batch, $key); + } + + drush_include_engine('drupal', 'environment'); + // Store the batch. + /** @var \Drupal\Core\Batch\BatchStorage $batch_storage */ + $batch_storage = \Drupal::service('batch.storage'); + $batch_storage->create($batch); + $finished = FALSE; + + while (!$finished) { + $data = drush_invoke_process('@self', $command, $args, array('user' => drush_user_get_class()->getCurrentUserAsSingle()->id())); + + $finished = drush_get_error() || !$data || (isset($data['context']['drush_batch_process_finished']) && $data['context']['drush_batch_process_finished'] == TRUE); + } + } +} + +/** + * Initialize the batch command and call the worker function. + * + * Loads the batch record from the database and sets up the requirements + * for the worker, such as registering the shutdown function. + * + * @param id + * The batch id of the batch being processed. + * + * @return bool|array + * A results array. + */ +function _drush_batch_command($id) { + $batch =& batch_get(); + + $data = Database::getConnection()->select('batch', 'b') + ->fields('b', ['batch']) + ->condition('bid', $id) + ->execute() + ->fetchField(); + + if ($data) { + $batch = unserialize($data); + } + else { + return FALSE; + } + + if (!isset($batch['running'])) { + $batch['running'] = TRUE; + } + + // Register database update for end of processing. + register_shutdown_function('_drush_batch_shutdown'); + + if (_drush_batch_worker()) { + return _drush_batch_finished(); + } + else { + return ['drush_batch_process_finished' => FALSE]; + } +} + +/** + * Process batch operations + * + * Using the current $batch process each of the operations until the batch + * has been completed or half of the available memory for the process has been + * reached. + */ +function _drush_batch_worker() { + $batch =& batch_get(); + $current_set =& _batch_current_set(); + $set_changed = TRUE; + + if (empty($current_set['start'])) { + $current_set['start'] = microtime(TRUE); + } + $queue = _batch_queue($current_set); + while (!$current_set['success']) { + // If this is the first time we iterate this batch set in the current + // request, we check if it requires an additional file for functions + // definitions. + if ($set_changed && isset($current_set['file']) && is_file($current_set['file'])) { + include_once DRUPAL_ROOT . '/' . $current_set['file']; + } + + $task_message = ''; + // Assume a single pass operation and set the completion level to 1 by + // default. + $finished = 1; + + if ($item = $queue->claimItem()) { + list($function, $args) = $item->data; + + // Build the 'context' array and execute the function call. + $batch_context = array( + 'sandbox' => &$current_set['sandbox'], + 'results' => &$current_set['results'], + 'finished' => &$finished, + 'message' => &$task_message, + ); + // Magic wrap to catch changes to 'message' key. + $batch_context = new DrushBatchContext($batch_context); + + // Tolerate recoverable errors. + // See https://github.com/drush-ops/drush/issues/1930 + $halt_on_error = drush_get_option('halt-on-error', TRUE); + drush_set_option('halt-on-error', FALSE); + call_user_func_array($function, array_merge($args, array(&$batch_context))); + drush_set_option('halt-on-error', $halt_on_error); + + $finished = $batch_context['finished']; + if ($finished >= 1) { + // Make sure this step is not counted twice when computing $current. + $finished = 0; + // Remove the processed operation and clear the sandbox. + $queue->deleteItem($item); + $current_set['count']--; + $current_set['sandbox'] = array(); + } + } + + // When all operations in the current batch set are completed, browse + // through the remaining sets, marking them 'successfully processed' + // along the way, until we find a set that contains operations. + // _batch_next_set() executes form submit handlers stored in 'control' + // sets (see form_execute_handlers()), which can in turn add new sets to + // the batch. + $set_changed = FALSE; + $old_set = $current_set; + while (empty($current_set['count']) && ($current_set['success'] = TRUE) && _batch_next_set()) { + $current_set = &_batch_current_set(); + $current_set['start'] = microtime(TRUE); + $set_changed = TRUE; + } + + // At this point, either $current_set contains operations that need to be + // processed or all sets have been completed. + $queue = _batch_queue($current_set); + + // If we are in progressive mode, break processing after 1 second. + if (drush_memory_limit() > 0 && (memory_get_usage() * 2) >= drush_memory_limit()) { + drush_log(dt("Batch process has consumed in excess of 50% of available memory. Starting new thread"), LogLevel::BATCH); + // Record elapsed wall clock time. + $current_set['elapsed'] = round((microtime(TRUE) - $current_set['start']) * 1000, 2); + break; + } + } + + // Reporting 100% progress will cause the whole batch to be considered + // processed. If processing was paused right after moving to a new set, + // we have to use the info from the new (unprocessed) set. + if ($set_changed && isset($current_set['queue'])) { + // Processing will continue with a fresh batch set. + $remaining = $current_set['count']; + $total = $current_set['total']; + $progress_message = $current_set['init_message']; + $task_message = ''; + } + else { + // Processing will continue with the current batch set. + $remaining = $old_set['count']; + $total = $old_set['total']; + $progress_message = $old_set['progress_message']; + } + + $current = $total - $remaining + $finished; + $percentage = _batch_api_percentage($total, $current); + return ($percentage == 100); +} + +/** + * End the batch processing: + * Call the 'finished' callbacks to allow custom handling of results, + * and resolve page redirection. + */ +function _drush_batch_finished() { + $batch = &batch_get(); + + // Execute the 'finished' callbacks for each batch set, if defined. + foreach ($batch['sets'] as $batch_set) { + if (isset($batch_set['finished'])) { + // Check if the set requires an additional file for function definitions. + if (isset($batch_set['file']) && is_file($batch_set['file'])) { + include_once DRUPAL_ROOT . '/' . $batch_set['file']; + } + if (is_callable($batch_set['finished'])) { + $queue = _batch_queue($batch_set); + $operations = $queue->getAllItems(); + $elapsed = $batch_set['elapsed'] / 1000; + $elapsed = \Drupal::service('date.formatter')->formatInterval($elapsed); + call_user_func_array($batch_set['finished'], [$batch_set['success'], $batch_set['results'], $operations, $elapsed]); + } + } + } + + // Clean up the batch table and unset the static $batch variable. + /** @var \Drupal\Core\Batch\BatchStorage $batch_storage */ + $batch_storage = \Drupal::service('batch.storage'); + $batch_storage->delete($batch['id']); + + foreach ($batch['sets'] as $batch_set) { + if ($queue = _batch_queue($batch_set)) { + $queue->deleteQueue(); + } + } + $_batch = $batch; + $batch = NULL; + drush_set_option('drush_batch_process_finished', TRUE); +} + +/** + * Shutdown function: store the batch data for next request, + * or clear the table if the batch is finished. + */ +function _drush_batch_shutdown() { + if ($batch = batch_get()) { + /** @var \Drupal\Core\Batch\BatchStorage $batch_storage */ + $batch_storage = \Drupal::service('batch.storage'); + $batch_storage->update($batch); + } +} diff --git a/vendor/drush/drush/commands/core/drupal/batch_6.inc b/vendor/drush/drush/commands/core/drupal/batch_6.inc new file mode 100644 index 0000000000..11bbb5a8f2 --- /dev/null +++ b/vendor/drush/drush/commands/core/drupal/batch_6.inc @@ -0,0 +1,199 @@ + 0, + ); + $batch += $process_info; + + // Initiate db storage in order to get a batch id. We have to provide + // at least an empty string for the (not null) 'token' column. + db_query("INSERT INTO {batch} (token, timestamp) VALUES ('', %d)", time()); + $batch['id'] = db_last_insert_id('batch', 'bid'); + $args[] = $batch['id']; + + // Actually store the batch data and the token generated form the batch id. + db_query("UPDATE {batch} SET token = '%s', batch = '%s' WHERE bid = %d", drupal_get_token($batch['id']), serialize($batch), $batch['id']); + + $finished = FALSE; + + while (!$finished) { + $data = drush_invoke_process('@self', $command, $args, $options); + $finished = drush_get_error() || !$data || (isset($data['context']['drush_batch_process_finished']) && $data['context']['drush_batch_process_finished'] == TRUE); + } + } +} + +/** + * Initialize the batch command and call the worker function. + * + * Loads the batch record from the database and sets up the requirements + * for the worker, such as registering the shutdown function. + * + * @param id + * The batch id of the batch being processed. + */ +function _drush_batch_command($id) { + $batch =& batch_get(); + // Retrieve the current state of batch from db. + if ($data = db_result(db_query("SELECT batch FROM {batch} WHERE bid = %d", $id))) { + $batch = unserialize($data); + } + else { + return FALSE; + } + if (!isset($batch['running'])) { + $batch['running'] = TRUE; + } + + // Register database update for end of processing. + register_shutdown_function('_drush_batch_shutdown'); + + if (_drush_batch_worker()) { + _drush_batch_finished(); + } +} + +/** + * Process batch operations + * + * Using the current $batch process each of the operations until the batch + * has been completed or half of the available memory for the process has been + * reached. + */ +function _drush_batch_worker() { + $batch =& batch_get(); + $current_set =& _batch_current_set(); + $set_changed = TRUE; + + timer_start('batch_processing'); + + while (!$current_set['success']) { + // If this is the first time we iterate this batch set in the current + // request, we check if it requires an additional file for functions + // definitions. + if ($set_changed && isset($current_set['file']) && is_file($current_set['file'])) { + include_once($current_set['file']); + } + + $finished = 1; + $task_message = ''; + if ((list($function, $args) = reset($current_set['operations'])) && function_exists($function)) { + // Build the 'context' array, execute the function call, + // and retrieve the user message. + $batch_context = array('sandbox' => &$current_set['sandbox'], 'results' => &$current_set['results'], 'finished' => &$finished, 'message' => &$task_message); + // Magic wrap to catch changes to 'message' key. + $batch_context = new DrushBatchContext($batch_context); + // Process the current operation. + call_user_func_array($function, array_merge($args, array(&$batch_context))); + $finished = $batch_context['finished']; + } + + if ($finished >= 1) { + // Make sure this step isn't counted double when computing $current. + $finished = 0; + // Remove the operation and clear the sandbox. + array_shift($current_set['operations']); + $current_set['sandbox'] = array(); + } + + // If the batch set is completed, browse through the remaining sets, + // executing 'control sets' (stored form submit handlers) along the way - + // this might in turn insert new batch sets. + // Stop when we find a set that actually has operations. + $set_changed = FALSE; + $old_set = $current_set; + while (empty($current_set['operations']) && ($current_set['success'] = TRUE) && _batch_next_set()) { + $current_set =& _batch_current_set(); + $set_changed = TRUE; + } + // At this point, either $current_set is a 'real' batch set (has operations), + // or all sets have been completed. + + + // TODO - replace with memory check! + // If we're in progressive mode, stop after 1 second. + if ((memory_get_usage() * 2) >= drush_memory_limit()) { + drush_log(dt("Batch process has consumed in excess of 50% of available memory. Starting new thread"), LogLevel::BATCH); + break; + } + } + + // Gather progress information. + + // Reporting 100% progress will cause the whole batch to be considered + // processed. If processing was paused right after moving to a new set, + // we have to use the info from the new (unprocessed) one. + if ($set_changed && isset($current_set['operations'])) { + // Processing will continue with a fresh batch set. + $remaining = count($current_set['operations']); + $total = $current_set['total']; + $task_message = ''; + } + else { + $remaining = count($old_set['operations']); + $total = $old_set['total']; + } + + $current = $total - $remaining + $finished; + $percentage = $total ? floor($current / $total * 100) : 100; + + return ($percentage == 100); +} + +/** + * End the batch processing: + * Call the 'finished' callbacks to allow custom handling of results, + * and resolve page redirection. + */ +function _drush_batch_finished() { + $batch =& batch_get(); + + // Execute the 'finished' callbacks for each batch set. + foreach ($batch['sets'] as $key => $batch_set) { + if (isset($batch_set['finished'])) { + // Check if the set requires an additional file for functions definitions. + if (isset($batch_set['file']) && is_file($batch_set['file'])) { + include_once($batch_set['file']); + } + if (function_exists($batch_set['finished'])) { + $batch_set['finished']($batch_set['success'], $batch_set['results'], $batch_set['operations']); + } + } + } + + // Cleanup the batch table and unset the global $batch variable. + db_query("DELETE FROM {batch} WHERE bid = %d", $batch['id']); + $_batch = $batch; + $batch = NULL; + drush_set_option('drush_batch_process_finished', TRUE); +} + +/** + * Shutdown function: store the batch data for next request, + * or clear the table if the batch is finished. + */ +function _drush_batch_shutdown() { + if ($batch = batch_get()) { + db_query("UPDATE {batch} SET batch = '%s' WHERE bid = %d", serialize($batch), $batch['id']); + } +} diff --git a/vendor/drush/drush/commands/core/drupal/batch_7.inc b/vendor/drush/drush/commands/core/drupal/batch_7.inc new file mode 100644 index 0000000000..bbb2089d28 --- /dev/null +++ b/vendor/drush/drush/commands/core/drupal/batch_7.inc @@ -0,0 +1,265 @@ + 0, + ); + $batch += $process_info; + + // The batch is now completely built. Allow other modules to make changes + // to the batch so that it is easier to reuse batch processes in other + // enviroments. + drupal_alter('batch', $batch); + + // Assign an arbitrary id: don't rely on a serial column in the 'batch' + // table, since non-progressive batches skip database storage completely. + $batch['id'] = db_next_id(); + $args[] = $batch['id']; + + $batch['progressive'] = TRUE; + + // Move operations to a job queue. Non-progressive batches will use a + // memory-based queue. + foreach ($batch['sets'] as $key => $batch_set) { + _batch_populate_queue($batch, $key); + } + + drush_include_engine('drupal', 'environment'); + // Store the batch. + db_insert('batch') + ->fields(array( + 'bid' => $batch['id'], + 'timestamp' => REQUEST_TIME, + 'token' => drush_get_token($batch['id']), + 'batch' => serialize($batch), + )) + ->execute(); + $finished = FALSE; + + // Not used in D8+ and possibly earlier. + global $user; + + while (!$finished) { + $data = drush_invoke_process('@self', $command, $args, array('user' => drush_user_get_class()->getCurrentUserAsSingle()->id())); + + $finished = drush_get_error() || !$data || (isset($data['context']['drush_batch_process_finished']) && $data['context']['drush_batch_process_finished'] == TRUE); + } + } +} + + +/** + * Initialize the batch command and call the worker function. + * + * Loads the batch record from the database and sets up the requirements + * for the worker, such as registering the shutdown function. + * + * @param id + * The batch id of the batch being processed. + */ +function _drush_batch_command($id) { + $batch =& batch_get(); + + $data = db_query("SELECT batch FROM {batch} WHERE bid = :bid", array( + ':bid' => $id))->fetchField(); + + if ($data) { + $batch = unserialize($data); + } + else { + return FALSE; + } + + if (!isset($batch['running'])) { + $batch['running'] = TRUE; + } + + // Register database update for end of processing. + register_shutdown_function('_drush_batch_shutdown'); + + if (_drush_batch_worker()) { + _drush_batch_finished(); + } +} + + +/** + * Process batch operations + * + * Using the current $batch process each of the operations until the batch + * has been completed or half of the available memory for the process has been + * reached. + */ +function _drush_batch_worker() { + $batch =& batch_get(); + $current_set =& _batch_current_set(); + $set_changed = TRUE; + + if (empty($current_set['start'])) { + $current_set['start'] = microtime(TRUE); + } + $queue = _batch_queue($current_set); + while (!$current_set['success']) { + // If this is the first time we iterate this batch set in the current + // request, we check if it requires an additional file for functions + // definitions. + if ($set_changed && isset($current_set['file']) && is_file($current_set['file'])) { + include_once DRUPAL_ROOT . '/' . $current_set['file']; + } + + $task_message = ''; + // Assume a single pass operation and set the completion level to 1 by + // default. + $finished = 1; + + if ($item = $queue->claimItem()) { + list($function, $args) = $item->data; + + // Build the 'context' array and execute the function call. + $batch_context = array( + 'sandbox' => &$current_set['sandbox'], + 'results' => &$current_set['results'], + 'finished' => &$finished, + 'message' => &$task_message, + ); + // Magic wrap to catch changes to 'message' key. + $batch_context = new DrushBatchContext($batch_context); + + // Tolerate recoverable errors. + // See https://github.com/drush-ops/drush/issues/1930 + $halt_on_error = drush_get_option('halt-on-error', TRUE); + drush_set_option('halt-on-error', FALSE); + call_user_func_array($function, array_merge($args, array(&$batch_context))); + drush_set_option('halt-on-error', $halt_on_error); + + $finished = $batch_context['finished']; + if ($finished >= 1) { + // Make sure this step is not counted twice when computing $current. + $finished = 0; + // Remove the processed operation and clear the sandbox. + $queue->deleteItem($item); + $current_set['count']--; + $current_set['sandbox'] = array(); + } + } + + // When all operations in the current batch set are completed, browse + // through the remaining sets, marking them 'successfully processed' + // along the way, until we find a set that contains operations. + // _batch_next_set() executes form submit handlers stored in 'control' + // sets (see form_execute_handlers()), which can in turn add new sets to + // the batch. + $set_changed = FALSE; + $old_set = $current_set; + while (empty($current_set['count']) && ($current_set['success'] = TRUE) && _batch_next_set()) { + $current_set = &_batch_current_set(); + $current_set['start'] = microtime(TRUE); + $set_changed = TRUE; + } + + // At this point, either $current_set contains operations that need to be + // processed or all sets have been completed. + $queue = _batch_queue($current_set); + + // If we are in progressive mode, break processing after 1 second. + if (drush_memory_limit() > 0 && (memory_get_usage() * 2) >= drush_memory_limit()) { + drush_log(dt("Batch process has consumed in excess of 50% of available memory. Starting new thread"), LogLevel::BATCH); + // Record elapsed wall clock time. + $current_set['elapsed'] = round((microtime(TRUE) - $current_set['start']) * 1000, 2); + break; + } + } + + // Reporting 100% progress will cause the whole batch to be considered + // processed. If processing was paused right after moving to a new set, + // we have to use the info from the new (unprocessed) set. + if ($set_changed && isset($current_set['queue'])) { + // Processing will continue with a fresh batch set. + $remaining = $current_set['count']; + $total = $current_set['total']; + $progress_message = $current_set['init_message']; + $task_message = ''; + } + else { + // Processing will continue with the current batch set. + $remaining = $old_set['count']; + $total = $old_set['total']; + $progress_message = $old_set['progress_message']; + } + + $current = $total - $remaining + $finished; + $percentage = _batch_api_percentage($total, $current); + return ($percentage == 100); +} + +/** + * End the batch processing: + * Call the 'finished' callbacks to allow custom handling of results, + * and resolve page redirection. + */ +function _drush_batch_finished() { + $batch = &batch_get(); + + // Execute the 'finished' callbacks for each batch set, if defined. + foreach ($batch['sets'] as $batch_set) { + if (isset($batch_set['finished'])) { + // Check if the set requires an additional file for function definitions. + if (isset($batch_set['file']) && is_file($batch_set['file'])) { + include_once DRUPAL_ROOT . '/' . $batch_set['file']; + } + if (is_callable($batch_set['finished'])) { + $queue = _batch_queue($batch_set); + $operations = $queue->getAllItems(); + $elapsed = $batch_set['elapsed'] / 1000; + $elapsed = format_interval($elapsed); + call_user_func_array($batch_set['finished'], [$batch_set['success'], $batch_set['results'], $operations, $elapsed]); + } + } + } + + // Clean up the batch table and unset the static $batch variable. + db_delete('batch') + ->condition('bid', $batch['id']) + ->execute(); + + foreach ($batch['sets'] as $batch_set) { + if ($queue = _batch_queue($batch_set)) { + $queue->deleteQueue(); + } + } + $_batch = $batch; + $batch = NULL; + drush_set_option('drush_batch_process_finished', TRUE); +} + +/** + * Shutdown function: store the batch data for next request, + * or clear the table if the batch is finished. + */ +function _drush_batch_shutdown() { + if ($batch = batch_get()) { + db_update('batch') + ->fields(array('batch' => serialize($batch))) + ->condition('bid', $batch['id']) + ->execute(); + } +} diff --git a/vendor/drush/drush/commands/core/drupal/cache.inc b/vendor/drush/drush/commands/core/drupal/cache.inc new file mode 100644 index 0000000000..a980d622fe --- /dev/null +++ b/vendor/drush/drush/commands/core/drupal/cache.inc @@ -0,0 +1,101 @@ + 'drush_cache_clear_drush', + 'all' => 'drush_cache_clear_both', + ); + if ($include_bootstrapped_types) { + $types += array( + 'theme-registry' => 'drush_cache_clear_theme_registry', + 'menu' => 'menu_rebuild', + 'css-js' => 'drush_cache_clear_css_js', + 'block' => 'drush_cache_clear_block', + 'module-list' => 'drush_get_modules', + 'theme-list' => 'drush_get_themes', + ); + } + $drupal_version = drush_drupal_major_version(); + + if ($drupal_version >= 7) { + $types['registry'] = 'registry_update'; + } + elseif ($drupal_version == 6 && function_exists('module_exists') && module_exists('autoload')) { + // TODO: move this to autoload module. + $types['registry'] = 'autoload_registry_update'; + } + + return $types; +} + +function drush_cache_clear_theme_registry() { + if (drush_drupal_major_version() >= 7) { + drupal_theme_rebuild(); + } + else { + cache_clear_all('theme_registry', 'cache', TRUE); + } +} + +function drush_cache_clear_menu() { + return menu_router_rebuild(); +} + +function drush_cache_clear_css_js() { + _drupal_flush_css_js(); + drupal_clear_css_cache(); + drupal_clear_js_cache(); +} + +/** + * Clear the cache of the block output. + */ +function drush_cache_clear_block() { + cache_clear_all(NULL, 'cache_block'); +} + +/** + * Clear caches internal to Drush core and Drupal. + */ +function drush_cache_clear_both() { + drush_cache_clear_drush(); + if (drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)) { + drupal_flush_all_caches(); + } +} diff --git a/vendor/drush/drush/commands/core/drupal/cache_8.inc b/vendor/drush/drush/commands/core/drupal/cache_8.inc new file mode 100644 index 0000000000..fca98a7167 --- /dev/null +++ b/vendor/drush/drush/commands/core/drupal/cache_8.inc @@ -0,0 +1,87 @@ +get($cid); +} + +/** + * The default bin. + * + * @return string + */ +function _drush_cache_bin_default() { + return 'default'; +} + +function _drush_cache_command_set($cid, $data, $bin, $expire, $tags) { + if (is_null($bin)) { + $bin = _drush_cache_bin_default(); + } + + // Convert the "expire" argument to a valid value for Drupal's cache_set(). + if ($expire == 'CACHE_TEMPORARY') { + $expire = Cache::TEMPORARY; + } + if (!isset($expire) || $expire == 'CACHE_PERMANENT') { + $expire = Cache::PERMANENT; + } + + return \Drupal::cache($bin)->set($cid, $data, $expire, $tags); +} + +function _drush_cache_clear_types($include_bootstrapped_types) { + $types = array( + 'drush' => 'drush_cache_clear_drush', + ); + if ($include_bootstrapped_types) { + $types += array( + 'theme-registry' => 'drush_cache_clear_theme_registry', + 'router' => 'drush_cache_clear_router', + 'css-js' => 'drush_cache_clear_css_js', + 'module-list' => 'drush_get_modules', + 'theme-list' => 'drush_get_themes', + 'render' => 'drush_cache_clear_render', + ); + } + return $types; +} + +function drush_cache_clear_theme_registry() { + \Drupal::service('theme.registry')->reset(); +} + +function drush_cache_clear_router() { + /** @var \Drupal\Core\Routing\RouteBuilderInterface $router_builder */ + $router_builder = \Drupal::service('router.builder'); + $router_builder->rebuild(); +} + +function drush_cache_clear_css_js() { + _drupal_flush_css_js(); + drupal_clear_css_cache(); + drupal_clear_js_cache(); +} + +/** + * Clear the cache of the block output. + */ +function drush_cache_clear_block() { + // There is no distinct block cache in D8. See https://github.com/drush-ops/drush/issues/1531. + // \Drupal::cache('block')->deleteAll(); +} + +/** + * Clears the render cache entries. + */ +function drush_cache_clear_render() { + Cache::invalidateTags(['rendered']); +} diff --git a/vendor/drush/drush/commands/core/drupal/cache_9.inc b/vendor/drush/drush/commands/core/drupal/cache_9.inc new file mode 100644 index 0000000000..fca98a7167 --- /dev/null +++ b/vendor/drush/drush/commands/core/drupal/cache_9.inc @@ -0,0 +1,87 @@ +get($cid); +} + +/** + * The default bin. + * + * @return string + */ +function _drush_cache_bin_default() { + return 'default'; +} + +function _drush_cache_command_set($cid, $data, $bin, $expire, $tags) { + if (is_null($bin)) { + $bin = _drush_cache_bin_default(); + } + + // Convert the "expire" argument to a valid value for Drupal's cache_set(). + if ($expire == 'CACHE_TEMPORARY') { + $expire = Cache::TEMPORARY; + } + if (!isset($expire) || $expire == 'CACHE_PERMANENT') { + $expire = Cache::PERMANENT; + } + + return \Drupal::cache($bin)->set($cid, $data, $expire, $tags); +} + +function _drush_cache_clear_types($include_bootstrapped_types) { + $types = array( + 'drush' => 'drush_cache_clear_drush', + ); + if ($include_bootstrapped_types) { + $types += array( + 'theme-registry' => 'drush_cache_clear_theme_registry', + 'router' => 'drush_cache_clear_router', + 'css-js' => 'drush_cache_clear_css_js', + 'module-list' => 'drush_get_modules', + 'theme-list' => 'drush_get_themes', + 'render' => 'drush_cache_clear_render', + ); + } + return $types; +} + +function drush_cache_clear_theme_registry() { + \Drupal::service('theme.registry')->reset(); +} + +function drush_cache_clear_router() { + /** @var \Drupal\Core\Routing\RouteBuilderInterface $router_builder */ + $router_builder = \Drupal::service('router.builder'); + $router_builder->rebuild(); +} + +function drush_cache_clear_css_js() { + _drupal_flush_css_js(); + drupal_clear_css_cache(); + drupal_clear_js_cache(); +} + +/** + * Clear the cache of the block output. + */ +function drush_cache_clear_block() { + // There is no distinct block cache in D8. See https://github.com/drush-ops/drush/issues/1531. + // \Drupal::cache('block')->deleteAll(); +} + +/** + * Clears the render cache entries. + */ +function drush_cache_clear_render() { + Cache::invalidateTags(['rendered']); +} diff --git a/vendor/drush/drush/commands/core/drupal/environment.inc b/vendor/drush/drush/commands/core/drupal/environment.inc new file mode 100644 index 0000000000..684e3d4928 --- /dev/null +++ b/vendor/drush/drush/commands/core/drupal/environment.inc @@ -0,0 +1,57 @@ + $module) { + $dependencies = array_reverse($module_info[$module]->requires); + $unmet_dependencies = array_diff(array_keys($dependencies), array_keys($module_info)); + if (!empty($unmet_dependencies)) { + $status[$key]['error'] = array( + 'code' => 'DRUSH_PM_ENABLE_DEPENDENCY_NOT_FOUND', + 'message' => dt('Module !module cannot be enabled because it depends on the following modules which could not be found: !unmet_dependencies', array('!module' => $module, '!unmet_dependencies' => implode(',', $unmet_dependencies))) + ); + } + else { + // check for version incompatibility + foreach ($dependencies as $dependency_name => $v) { + $current_version = $module_info[$dependency_name]->info['version']; + $current_version = str_replace(drush_get_drupal_core_compatibility() . '-', '', $current_version); + if (!$v->isCompatible($current_version)) { + $status[$key]['error'] = array( + 'code' => 'DRUSH_PM_ENABLE_DEPENDENCY_VERSION_MISMATCH', + 'message' => dt('Module !module cannot be enabled because it depends on !dependency !required_version but !current_version is available', array('!module' => $module, '!dependency' => $dependency_name, '!required_version' => $v->getConstraintString(), '!current_version' => $current_version)) + ); + } + } + } + $status[$key]['unmet-dependencies'] = $unmet_dependencies; + $status[$key]['dependencies'] = $dependencies; + } + + return $status; +} diff --git a/vendor/drush/drush/commands/core/drupal/environment_6.inc b/vendor/drush/drush/commands/core/drupal/environment_6.inc new file mode 100644 index 0000000000..4a3a24db24 --- /dev/null +++ b/vendor/drush/drush/commands/core/drupal/environment_6.inc @@ -0,0 +1,420 @@ + $module) { + if (!isset($module->type)) { + $module->type = 'module'; + } + if ((!$include_hidden) && isset($module->info['hidden']) && ($module->info['hidden'])) { + unset($modules[$key]); + } + } + + return $modules; +} + +/** + * Returns drupal required modules, including their dependencies. + * + * A module may alter other module's .info to set a dependency on it. + * See for example http://drupal.org/project/phpass + */ +function _drush_drupal_required_modules($module_info) { + $required = drupal_required_modules(); + foreach ($required as $module) { + $required = array_merge($required, $module_info[$module]->info['dependencies']); + } + return $required; +} + +/** + * Return dependencies and its status for modules. + * + * @param $modules + * Array of module names + * @param $module_info + * Drupal 'files' array for modules as returned by drush_get_modules(). + * @return + * Array with dependencies and status for $modules + */ +function drush_check_module_dependencies($modules, $module_info) { + $status = array(); + foreach ($modules as $key => $module) { + $dependencies = array_reverse($module_info[$module]->info['dependencies']); + $unmet_dependencies = array_diff($dependencies, array_keys($module_info)); + if (!empty($unmet_dependencies)) { + $status[$key]['error'] = array( + 'code' => 'DRUSH_PM_ENABLE_DEPENDENCY_NOT_FOUND', + 'message' => dt('Module !module cannot be enabled because it depends on the following modules which could not be found: !unmet_dependencies', array('!module' => $module, '!unmet_dependencies' => implode(',', $unmet_dependencies))) + ); + } + $status[$key]['unmet-dependencies'] = $unmet_dependencies; + $status[$key]['dependencies'] = array(); + foreach ($dependencies as $dependency) { + $status[$key]['dependencies'][$dependency] = array('name' => $dependency); + } + } + + return $status; +} + +/** + * Return dependents of modules. + * + * @param $modules + * Array of module names + * @param $module_info + * Drupal 'files' array for modules as returned by drush_get_modules(). + * @return + * Array with dependents for each one of $modules + */ +function drush_module_dependents($modules, $module_info) { + $dependents = array(); + foreach ($modules as $module) { + $dependents = array_merge($dependents, $module_info[$module]->info['dependents']); + } + + return array_unique($dependents); +} + +/** + * Returns a list of enabled modules. + * + * This is a simplified version of module_list(). + */ +function drush_module_list() { + $enabled = array(); + $rsc = drush_db_select('system', 'name', 'type=:type AND status=:status', array(':type' => 'module', ':status' => 1)); + while ($row = drush_db_result($rsc)) { + $enabled[$row] = $row; + } + + return $enabled; +} + +/** + * Return a list of extensions from a list of named extensions. + * Both enabled and disabled/uninstalled extensions are returned. + */ +function drush_get_named_extensions_list($extensions) { + $result = array(); + $rsc = drush_db_select('system', array('name', 'status'), 'name IN (:extensions)', array(':extensions' => $extensions)); + while ($row = drush_db_fetch_object($rsc)) { + $result[$row->name] = $row; + } + return $result; +} + +/** + * Enable a list of modules. It is assumed the list contains all the dependencies not already enabled. + * + * @param $modules + * Array of module names + */ +function drush_module_enable($modules) { + // Try to install modules previous to enabling. + foreach ($modules as $module) { + _drupal_install_module($module); + } + module_enable($modules); + drush_system_modules_form_submit(); +} + +/** + * Disable a list of modules. It is assumed the list contains all dependents not already disabled. + * + * @param $modules + * Array of module names + */ +function drush_module_disable($modules) { + module_disable($modules, FALSE); + drush_system_modules_form_submit(); +} + +/** + * Uninstall a list of modules. + * + * @param $modules + * Array of module names + */ +function drush_module_uninstall($modules) { + require_once DRUSH_DRUPAL_CORE . '/includes/install.inc'; + foreach ($modules as $module) { + drupal_uninstall_module($module); + } +} + +/** + * Checks that a given module exists and is enabled. + * + * @see module_exists(). + * + */ +function drush_module_exists($module) { + return module_exists($module); +} + +/** + * Determines which modules are implementing a hook. + * + */ +function drush_module_implements($hook, $sort = FALSE, $reset = FALSE) { + return module_implements($hook, $sort, $reset); +} + +/** + * Invokes a hook in a particular module. + * + */ +function drush_module_invoke($module, $hook) { + $args = func_get_args(); + return call_user_func_array('module_invoke', $args); +} + +/** + * Invokes a hook in all enabled modules that implement it. + * + */ +function drush_module_invoke_all($hook) { + $args = func_get_args(); + return call_user_func_array('module_invoke_all', $args); +} + +/** + * Submit the system modules form. + * + * The modules should already be fully enabled/disabled before calling this + * function. Calling this function just makes sure any activities triggered by + * the form submit (such as admin_role) are completed. + */ +function drush_system_modules_form_submit() { + $active_modules = array(); + foreach (drush_get_modules(FALSE) as $key => $module) { + if ($module->status == 1) { + $active_modules[$key] = $key; + } + } + module_load_include('inc', 'system', 'system.admin'); + $form_state = array('values' => array('status' => $active_modules)); + drupal_execute('system_modules', $form_state); +} + +/** + * Returns a list of enabled themes. Use drush_get_themes() if you need to rebuild + * and include hidden/disabled as well. + * + * @return array + * A list of themes keyed by name. + */ +function drush_theme_list() { + $enabled = array(); + foreach (list_themes() as $key => $info) { + if ($info->status) { + $enabled[$key] = $info; + } + } + return $enabled; +} + +/** + * Get complete information for all available themes. + * + * We need to set the type for those themes that are not already in the system table. + * + * @param $include_hidden + * Boolean to indicate whether hidden themes should be excluded or not. + * @return + * An array containing theme info for all available themes. + */ +function drush_get_themes($include_hidden = TRUE) { + $themes = system_theme_data(); + foreach ($themes as $key => $theme) { + if (!isset($theme->type)) { + $theme->type = 'theme'; + } + if ((!$include_hidden) && isset($theme->info['hidden']) && ($theme->info['hidden'])) { + unset($themes[$key]); + } + } + + return $themes; +} + +/** + * Enable a list of themes. + * + * This function is based on system_themes_form_submit(). + * + * @see system_themes_form_submit() + * @param $themes + * Array of theme names. + */ +function drush_theme_enable($themes) { + drupal_clear_css_cache(); + foreach ($themes as $theme) { + system_initialize_theme_blocks($theme); + } + db_query("UPDATE {system} SET status = 1 WHERE type = 'theme' AND name IN (".db_placeholders($themes, 'text').")", $themes); + list_themes(TRUE); + menu_rebuild(); + module_invoke('locale', 'system_update', $themes); +} + +/** + * Disable a list of themes. + * + * This function is based on system_themes_form_submit(). + * + * @see system_themes_form_submit() + * @param $themes + * Array of theme names. + */ +function drush_theme_disable($themes) { + drupal_clear_css_cache(); + db_query("UPDATE {system} SET status = 0 WHERE type = 'theme' AND name IN (".db_placeholders($themes, 'text').")", $themes); + list_themes(TRUE); + menu_rebuild(); + drupal_rebuild_theme_registry(); + module_invoke('locale', 'system_update', $themes); +} + +/** + * Helper function to obtain the severity levels based on Drupal version. + * + * This is a copy of watchdog_severity_levels() without t(). + * + * Severity levels, as defined in RFC 3164: http://www.ietf.org/rfc/rfc3164.txt. + * + * @return + * Array of watchdog severity levels. + */ +function drush_watchdog_severity_levels() { + return array( + WATCHDOG_EMERG => 'emergency', + WATCHDOG_ALERT => 'alert', + WATCHDOG_CRITICAL => 'critical', + WATCHDOG_ERROR => 'error', + WATCHDOG_WARNING => 'warning', + WATCHDOG_NOTICE => 'notice', + WATCHDOG_INFO => 'info', + WATCHDOG_DEBUG => 'debug', + ); +} + +/** + * Helper function to obtain the message types based on drupal version. + * + * @return + * Array of watchdog message types. + */ +function drush_watchdog_message_types() { + return drupal_map_assoc(_dblog_get_message_types()); +} + +function _drush_theme_default() { + return variable_get('theme_default', 'garland'); +} + +function _drush_theme_admin() { + return variable_get('admin_theme', drush_theme_get_default()); +} + +function _drush_file_public_path() { + if (function_exists('file_directory_path')) { + return file_directory_path(); + } +} + +function _drush_file_private_path() { + // @todo +} + +/** + * Gets the extension name. + * + * @param $info + * The extension info. + * @return string + * The extension name. + */ +function _drush_extension_get_name($info) { + return $info->name; +} + +/** + * Gets the extension type. + * + * @param $info + * The extension info. + * @return string + * The extension type. + */ +function _drush_extension_get_type($info) { + return $info->type; +} + +/** + * Gets the extension path. + * + * @param $info + * The extension info. + * @return string + * The extension path. + */ +function _drush_extension_get_path($info) { + return dirname($info->filename); +} + +/* + * Wrapper for CSRF token generation. + */ +function drush_get_token($value = NULL) { + return drupal_get_token($value); +} + +/* + * Wrapper for _url(). + */ +function drush_url($path = NULL, $options = array()) { + return url($path, $options); +} + +/** + * Output a Drupal render array, object or plain string as plain text. + * + * @param string $data + * Data to render. + * + * @return string + * The plain-text representation of the input. + */ +function drush_render($data) { + if (is_array($data)) { + $data = drupal_render($data); + } + + $data = drupal_html_to_text($data); + return $data; +} diff --git a/vendor/drush/drush/commands/core/drupal/environment_7.inc b/vendor/drush/drush/commands/core/drupal/environment_7.inc new file mode 100644 index 0000000000..47b763bce6 --- /dev/null +++ b/vendor/drush/drush/commands/core/drupal/environment_7.inc @@ -0,0 +1,393 @@ + $module) { + if (isset($module->info['hidden'])) { + unset($modules[$key]); + } + } + } + + return $modules; +} + +/** + * Returns drupal required modules, including modules declared as required dynamically. + */ +function _drush_drupal_required_modules($module_info) { + $required = drupal_required_modules(); + foreach ($module_info as $name => $module) { + if (isset($module->info['required']) && $module->info['required']) { + $required[] = $name; + } + } + return array_unique($required); +} + +/** + * Return dependencies and its status for modules. + * + * @param $modules + * Array of module names + * @param $module_info + * Drupal 'files' array for modules as returned by drush_get_modules(). + * @return + * Array with dependencies and status for $modules + */ +function drush_check_module_dependencies($modules, $module_info) { + $status = array(); + foreach ($modules as $key => $module) { + $dependencies = array_reverse($module_info[$module]->requires); + $unmet_dependencies = array_diff(array_keys($dependencies), array_keys($module_info)); + if (!empty($unmet_dependencies)) { + $status[$key]['error'] = array( + 'code' => 'DRUSH_PM_ENABLE_DEPENDENCY_NOT_FOUND', + 'message' => dt('Module !module cannot be enabled because it depends on the following modules which could not be found: !unmet_dependencies', array('!module' => $module, '!unmet_dependencies' => implode(',', $unmet_dependencies))) + ); + } + else { + // check for version incompatibility + foreach ($dependencies as $dependency_name => $v) { + $current_version = $module_info[$dependency_name]->info['version']; + $current_version = str_replace(drush_get_drupal_core_compatibility() . '-', '', $current_version); + $incompatibility = drupal_check_incompatibility($v, $current_version); + if (isset($incompatibility)) { + $status[$key]['error'] = array( + 'code' => 'DRUSH_PM_ENABLE_DEPENDENCY_VERSION_MISMATCH', + 'message' => dt('Module !module cannot be enabled because it depends on !dependency !required_version but !current_version is available', array('!module' => $module, '!dependency' => $dependency_name, '!required_version' => $incompatibility, '!current_version' => $current_version)) + ); + } + } + } + $status[$key]['unmet-dependencies'] = $unmet_dependencies; + $status[$key]['dependencies'] = $dependencies; + } + + return $status; +} + +/** + * Return dependents of modules. + * + * @param $modules + * Array of module names + * @param $module_info + * Drupal 'files' array for modules as returned by drush_get_modules(). + * @return + * Array with dependents for each one of $modules + */ +function drush_module_dependents($modules, $module_info) { + $dependents = array(); + foreach ($modules as $module) { + $dependents = array_merge($dependents, drupal_map_assoc(array_keys($module_info[$module]->required_by))); + } + + return array_unique($dependents); +} + +/** + * Returns a list of enabled modules. + * + * This is a simplified version of module_list(). + */ +function drush_module_list() { + $enabled = array(); + $rsc = drush_db_select('system', 'name', 'type=:type AND status=:status', array(':type' => 'module', ':status' => 1)); + while ($row = drush_db_result($rsc)) { + $enabled[$row] = $row; + } + + return $enabled; +} + +/** + * Return a list of extensions from a list of named extensions. + * Both enabled and disabled/uninstalled extensions are returned. + */ +function drush_get_named_extensions_list($extensions) { + $result = array(); + $rsc = drush_db_select('system', array('name', 'status'), 'name IN (:extensions)', array(':extensions' => $extensions)); + while ($row = drush_db_fetch_object($rsc)) { + $result[$row->name] = $row; + } + return $result; +} + +/** + * Enable a list of modules. It is assumed the list contains all the dependencies not already enabled. + * + * @param $modules + * Array of module names + */ +function drush_module_enable($modules) { + // The list of modules already have all the dependencies, but they might not + // be in the correct order. Still pass $enable_dependencies = TRUE so that + // Drupal will enable the modules in the correct order. + module_enable($modules); + // Flush all caches. + drupal_flush_all_caches(); +} + +/** + * Disable a list of modules. It is assumed the list contains all dependents not already disabled. + * + * @param $modules + * Array of module names + */ +function drush_module_disable($modules) { + // The list of modules already have all the dependencies, but they might not + // be in the correct order. Still pass $enable_dependencies = TRUE so that + // Drupal will enable the modules in the correct order. + module_disable($modules); + // Flush all caches. + drupal_flush_all_caches(); +} + +/** + * Uninstall a list of modules. + * + * @param $modules + * Array of module names + */ +function drush_module_uninstall($modules) { + require_once DRUSH_DRUPAL_CORE . '/includes/install.inc'; + // Break off 8.x functionality when we get another change. + if (drush_drupal_major_version() >= 8) { + module_uninstall($modules); + } + else { + drupal_uninstall_modules($modules); + } +} + +/** + * Checks that a given module exists and is enabled. + * + * @see module_exists(). + * + */ +function drush_module_exists($module) { + return module_exists($module); +} + +/** + * Determines which modules are implementing a hook. + * + */ +function drush_module_implements($hook, $sort = FALSE, $reset = FALSE) { + return module_implements($hook, $sort, $reset); +} + +/** + * Invokes a hook in a particular module. + * + */ +function drush_module_invoke($module, $hook) { + $args = func_get_args(); + return call_user_func_array('module_invoke', $args); +} + +/** + * Invokes a hook in all enabled modules that implement it. + * + */ +function drush_module_invoke_all($hook) { + $args = func_get_args(); + return call_user_func_array('module_invoke_all', $args); +} + +/** + * Returns a list of enabled themes. Use drush_get_themes() if you need to rebuild + * and include hidden/disabled as well. + * + * @return array + * A list of themes keyed by name. + */ +function drush_theme_list() { + $enabled = array(); + foreach (list_themes() as $key => $info) { + if ($info->status) { + $enabled[$key] = $info; + } + } + return $enabled; +} + +/** + * Get complete information for all available themes. + * + * @param $include_hidden + * Boolean to indicate whether hidden themes should be excluded or not. + * @return + * An array containing theme info for all available themes. + */ +function drush_get_themes($include_hidden = TRUE) { + $themes = system_rebuild_theme_data(); + if (!$include_hidden) { + foreach ($themes as $key => $theme) { + if (isset($theme->info['hidden'])) { + unset($themes[$key]); + } + } + } + + return $themes; +} + +/** + * Enable a list of themes. + * + * @param $themes + * Array of theme names. + */ +function drush_theme_enable($themes) { + theme_enable($themes); +} + +/** + * Disable a list of themes. + * + * @param $themes + * Array of theme names. + */ +function drush_theme_disable($themes) { + theme_disable($themes); +} + +/** + * Helper function to obtain the severity levels based on Drupal version. + * + * This is a copy of watchdog_severity_levels() without t(). + * + * Severity levels, as defined in RFC 3164: http://www.ietf.org/rfc/rfc3164.txt. + * + * @return + * Array of watchdog severity levels. + */ +function drush_watchdog_severity_levels() { + return array( + WATCHDOG_EMERGENCY=> 'emergency', + WATCHDOG_ALERT => 'alert', + WATCHDOG_CRITICAL => 'critical', + WATCHDOG_ERROR => 'error', + WATCHDOG_WARNING => 'warning', + WATCHDOG_NOTICE => 'notice', + WATCHDOG_INFO => 'info', + WATCHDOG_DEBUG => 'debug', + ); +} + +/** + * Helper function to obtain the message types based on drupal version. + * + * @return + * Array of watchdog message types. + */ +function drush_watchdog_message_types() { + return drupal_map_assoc(_dblog_get_message_types()); +} + +function _drush_theme_default() { + return variable_get('theme_default', 'garland'); +} + +function _drush_theme_admin() { + return variable_get('admin_theme', drush_theme_get_default()); +} + +function _drush_file_public_path() { + return variable_get('file_public_path', conf_path() . '/files'); +} + +function _drush_file_private_path() { + return variable_get('file_private_path', FALSE); +} + +/** + * Gets the extension name. + * + * @param $info + * The extension info. + * @return string + * The extension name. + */ +function _drush_extension_get_name($info) { + return $info->name; +} + +/** + * Gets the extension type. + * + * @param $info + * The extension info. + * @return string + * The extension type. + */ +function _drush_extension_get_type($info) { + return $info->type; +} + +/** + * Gets the extension path. + * + * @param $info + * The extension info. + * @return string + * The extension path. + */ +function _drush_extension_get_path($info) { + return dirname($info->filename); +} + +/* + * Wrapper for CSRF token generation. + */ +function drush_get_token($value = NULL) { + return drupal_get_token($value); +} + +/* + * Wrapper for _url(). + */ +function drush_url($path = NULL, array $options = array()) { + return url($path, $options); +} + +/** + * Output a Drupal render array, object or plain string as plain text. + * + * @param string $data + * Data to render. + * + * @return string + * The plain-text representation of the input. + */ +function drush_render($data) { + if (is_array($data)) { + $data = drupal_render($data); + } + + $data = drupal_html_to_text($data); + return $data; +} diff --git a/vendor/drush/drush/commands/core/drupal/environment_8.inc b/vendor/drush/drush/commands/core/drupal/environment_8.inc new file mode 100644 index 0000000000..c5133954de --- /dev/null +++ b/vendor/drush/drush/commands/core/drupal/environment_8.inc @@ -0,0 +1,56 @@ + $module) { + $dependencies = array_reverse($module_info[$module]->requires); + $unmet_dependencies = array_diff(array_keys($dependencies), array_keys($module_info)); + if (!empty($unmet_dependencies)) { + $status[$key]['error'] = array( + 'code' => 'DRUSH_PM_ENABLE_DEPENDENCY_NOT_FOUND', + 'message' => dt('Module !module cannot be enabled because it depends on the following modules which could not be found: !unmet_dependencies', array('!module' => $module, '!unmet_dependencies' => implode(',', $unmet_dependencies))) + ); + } + else { + // check for version incompatibility + foreach ($dependencies as $dependency_name => $v) { + $current_version = $module_info[$dependency_name]->info['version']; + $current_version = str_replace(drush_get_drupal_core_compatibility() . '-', '', $current_version); + $incompatibility = drupal_check_incompatibility($v, $current_version); + if (isset($incompatibility)) { + $status[$key]['error'] = array( + 'code' => 'DRUSH_PM_ENABLE_DEPENDENCY_VERSION_MISMATCH', + 'message' => dt('Module !module cannot be enabled because it depends on !dependency !required_version but !current_version is available', array('!module' => $module, '!dependency' => $dependency_name, '!required_version' => $incompatibility, '!current_version' => $current_version)) + ); + } + } + } + $status[$key]['unmet-dependencies'] = $unmet_dependencies; + $status[$key]['dependencies'] = $dependencies; + } + + return $status; +} diff --git a/vendor/drush/drush/commands/core/drupal/environment_common.inc b/vendor/drush/drush/commands/core/drupal/environment_common.inc new file mode 100644 index 0000000000..accb787354 --- /dev/null +++ b/vendor/drush/drush/commands/core/drupal/environment_common.inc @@ -0,0 +1,398 @@ +getList(); + + foreach ($modules as $key => $module) { + if ((!$include_hidden) && (!empty($module->info['hidden']))) { + unset($modules[$key]); + } + else { + $module->schema_version = drupal_get_installed_schema_version($key); + } + } + + return $modules; +} + +/** + * Returns drupal required modules, including modules declared as required dynamically. + */ +function _drush_drupal_required_modules($module_info) { + $required = drupal_required_modules(); + foreach ($module_info as $name => $module) { + if (isset($module->info['required']) && $module->info['required']) { + $required[] = $name; + } + } + return array_unique($required); +} + +/** + * Return dependents of modules. + * + * @param $modules + * Array of module names + * @param $module_info + * Drupal 'files' array for modules as returned by drush_get_modules(). + * @return + * Array with dependents for each one of $modules + */ +function drush_module_dependents($modules, $module_info) { + $dependents = array(); + foreach ($modules as $module) { + $keys = array_keys($module_info[$module]->required_by); + $dependents = array_merge($dependents, array_combine($keys, $keys)); + } + + return array_unique($dependents); +} + +/** + * Returns a list of enabled modules. + * + * This is a wrapper for module_list(). + */ +function drush_module_list() { + $modules = array_keys(\Drupal::moduleHandler()->getModuleList()); + return array_combine($modules, $modules); +} + +/** + * Installs a given list of modules. + * + * @see \Drupal\Core\Extension\ModuleInstallerInterface::install() + * + */ +function drush_module_install($module_list, $enable_dependencies = TRUE) { + return \Drupal::service('module_installer')->install($module_list, $enable_dependencies); +} + +/** + * Checks that a given module exists and is enabled. + * + * @see \Drupal\Core\Extension\ModuleHandlerInterface::moduleExists() + * + */ +function drush_module_exists($module) { + return \Drupal::moduleHandler()->moduleExists($module); +} + +/** + * Determines which modules are implementing a hook. + * + * @param string $hook + * The hook name. + * @param bool $sort + * Not used in Drupal 8 environment. + * @param bool $reset + * TRUE to reset the hook implementation cache. + * + * @see \Drupal\Core\Extension\ModuleHandlerInterface::getImplementations(). + * @see \Drupal\Core\Extension\ModuleHandlerInterface::resetImplementations(). + * + */ +function drush_module_implements($hook, $sort = FALSE, $reset = FALSE) { + // $sort is there for consistency, but looks like Drupal 8 has no equilavient for it. + // We can sort the list manually later if really needed. + if ($reset == TRUE){ + \Drupal::moduleHandler()->resetImplementations(); + } + return \Drupal::moduleHandler()->getImplementations($hook); +} + +/** + * Return a list of modules from a list of named modules. + * Both enabled and disabled/uninstalled modules are returned. + */ +function drush_get_named_extensions_list($extensions) { + $result = array(); + $modules = drush_get_modules(); + foreach($modules as $name => $module) { + if (in_array($name, $extensions)) { + $result[$name] = $module; + } + } + $themes = drush_get_themes(); + foreach($themes as $name => $theme) { + if (in_array($name, $extensions)) { + $result[$name] = $theme; + } + } + return $result; +} + +/** + * Enable a list of modules. It is assumed the list contains all the dependencies not already enabled. + * + * @param $modules + * Array of module names + */ +function drush_module_enable($modules) { + // The list of modules already have all the dependencies, but they might not + // be in the correct order. Still pass $enable_dependencies = TRUE so that + // Drupal will enable the modules in the correct order. + drush_module_install($modules); + + // Our logger got blown away during the container rebuild above. + $boot = drush_select_bootstrap_class(); + $boot->add_logger(); + + // Flush all caches. No longer needed in D8 per https://github.com/drush-ops/drush/issues/1207 + // drupal_flush_all_caches(); +} + +/** + * Disable a list of modules. It is assumed the list contains all dependents not already disabled. + * + * @param $modules + * Array of module names + */ +function drush_module_disable($modules) { + drush_set_error('DRUSH_MODULE_DISABLE', dt('Drupal 8 does not support disabling modules. Use pm-uninstall instead.')); +} + +/** + * Uninstall a list of modules. + * + * @param $modules + * Array of module names + * + * @see \Drupal\Core\Extension\ModuleInstallerInterface::uninstall() + */ +function drush_module_uninstall($modules) { + \Drupal::service('module_installer')->uninstall($modules); + // Our logger got blown away during the container rebuild above. + $boot = drush_select_bootstrap_class(); + $boot->add_logger(); +} + +/** + * Invokes a hook in a particular module. + * + */ +function drush_module_invoke($module, $hook) { + $args = func_get_args(); + // Remove $module and $hook from the arguments. + unset($args[0], $args[1]); + return \Drupal::moduleHandler()->invoke($module, $hook, $args); +} + +/** + * Invokes a hook in all enabled modules that implement it. + * + */ +function drush_module_invoke_all($hook) { + $args = func_get_args(); + // Remove $hook from the arguments. + array_shift($args); + return \Drupal::moduleHandler()->invokeAll($hook, $args); +} + +/** + * Returns a list of enabled themes. Use drush_get_themes() if you need to rebuild + * and include hidden as well. + * + * @return \Drupal\Core\Extension\Extension[] + * A list of themes keyed by name. + */ +function drush_theme_list() { + $theme_handler = \Drupal::service('theme_handler'); + return $theme_handler->listInfo(); +} + +/** + * Get complete information for all available themes. + * + * @param $include_hidden + * Boolean to indicate whether hidden themes should be excluded or not. + * @return + * An array containing theme info for all available themes. + */ +function drush_get_themes($include_hidden = TRUE) { + $themes = \Drupal::service('theme_handler')->rebuildThemeData(); + foreach ($themes as $key => $theme) { + if (!$include_hidden) { + if (isset($theme->info['hidden'])) { + // Don't exclude default or admin theme. + if ($key != _drush_theme_default() && $key != _drush_theme_admin()) { + unset($themes[$key]); + } + } + } + } + + return $themes; +} + +/** + * Enable a list of themes. + * + * @param $themes + * Array of theme names. + */ +function drush_theme_enable($themes) { + \Drupal::service('theme_handler')->install($themes); +} + +/** + * Disable a list of themes. + * + * @param $themes + * Array of theme names. + */ +function drush_theme_disable($themes) { + drush_set_error('DRUSH_THEME_DISABLE', dt('Drupal 8 does not support disabling themes. Use pm-uninstall instead.')); +} + +/** + * Uninstall a list of themes. + * + * @param $themes + * Array of theme names + * + * @see \Drupal\Core\Extension\ThemeHandlerInterface::uninstall() + */ +function drush_theme_uninstall($themes) { + \Drupal::service('theme_installer')->uninstall($themes); + // Our logger got blown away during the container rebuild above. + $boot = drush_select_bootstrap_class(); + $boot->add_logger(); +} + +/** + * Helper function to obtain the severity levels based on Drupal version. + * + * @return array + * Watchdog severity levels keyed by RFC 3164 severities. + */ +function drush_watchdog_severity_levels() { + return array( + RfcLogLevel::EMERGENCY => LogLevel::EMERGENCY, + RfcLogLevel::ALERT => LogLevel::ALERT, + RfcLogLevel::CRITICAL => LogLevel::CRITICAL, + RfcLogLevel::ERROR => LogLevel::ERROR, + RfcLogLevel::WARNING => LogLevel::WARNING, + RfcLogLevel::NOTICE => LogLevel::NOTICE, + RfcLogLevel::INFO => LogLevel::INFO, + RfcLogLevel::DEBUG => LogLevel::DEBUG, + ); +} + +/** + * Helper function to obtain the message types based on drupal version. + * + * @return + * Array of watchdog message types. + */ +function drush_watchdog_message_types() { + return _dblog_get_message_types(); +} + +function _drush_theme_default() { + return \Drupal::config('system.theme')->get('default'); +} + +function _drush_theme_admin() { + $theme = \Drupal::config('system.theme')->get('admin'); + return empty($theme) ? 'seven' : $theme; +} + +function _drush_file_public_path() { + return PublicStream::basePath(); +} + +function _drush_file_private_path() { + return PrivateStream::basePath(); +} + +/** + * Gets the extension name. + * + * @param $info + * The extension info. + * @return string + * The extension name. + */ +function _drush_extension_get_name($info) { + return $info->getName(); +} + +/** + * Gets the extension type. + * + * @param $info + * The extension info. + * @return string + * The extension type. + */ +function _drush_extension_get_type($info) { + return $info->getType(); +} + +/** + * Gets the extension path. + * + * @param $info + * The extension info. + * @return string + * The extension path. + */ +function _drush_extension_get_path($info) { + return $info->getPath(); +} + +/* + * Wrapper for CSRF token generation. + */ +function drush_get_token($value = NULL) { + return \Drupal::csrfToken()->get($value); +} + +/* + * Wrapper for _url(). + */ +function drush_url($path = NULL, array $options = array()) { + return \Drupal\Core\Url::fromUserInput('/' . $path, $options)->toString(); +} + +/** + * Output a Drupal render array, object or string as plain text. + * + * @param string $data + * Data to render. + * + * @return string + * The plain-text representation of the input. + */ +function drush_render($data) { + if (is_array($data)) { + $data = \Drupal::service('renderer')->renderRoot($data); + } + + $data = \Drupal\Core\Mail\MailFormatHelper::htmlToText($data); + return $data; +} diff --git a/vendor/drush/drush/commands/core/drupal/image.inc b/vendor/drush/drush/commands/core/drupal/image.inc new file mode 100644 index 0000000000..6090b2b7be --- /dev/null +++ b/vendor/drush/drush/commands/core/drupal/image.inc @@ -0,0 +1,43 @@ +getStorage('image_style')->loadMultiple(); +} + +function drush_image_style_load($style_name) { + return \Drupal::entityTypeManager()->getStorage('image_style')->load($style_name); +} + +function drush_image_flush_single($style_name) { + if ($style = drush_image_style_load($style_name)) { + $style->flush(); + drush_log(dt('Image style !style_name flushed', array('!style_name' => $style_name)), LogLevel::SUCCESS); + } +} + +/* + * Command callback. Create an image derivative. + * + * @param string $style_name + * The name of an image style. + * + * @param string $source + * The path to a source image, relative to Drupal root. + */ +function _drush_image_derive($style_name, $source) { + $image_style = drush_image_style_load($style_name); + $derivative_uri = $image_style->buildUri($source); + if ($image_style->createDerivative($source, $derivative_uri)) { + return $derivative_uri; + } +} diff --git a/vendor/drush/drush/commands/core/drupal/image_7.inc b/vendor/drush/drush/commands/core/drupal/image_7.inc new file mode 100644 index 0000000000..bcf1198ccf --- /dev/null +++ b/vendor/drush/drush/commands/core/drupal/image_7.inc @@ -0,0 +1,45 @@ + $style_name)), LogLevel::SUCCESS); + } +} + +/* + * Command callback. Create an image derivative. + * + * @param string $style_name + * The name of an image style. + * + * @param string $source + * The path to a source image, relative to Drupal root. + */ +function _drush_image_derive($style_name, $source) { + $image_style = image_style_load($style_name); + $scheme = file_default_scheme(); + $image_uri = $scheme . '://' . $source; + $derivative_uri = image_style_path($image_style['name'], $image_uri); + if (image_style_create_derivative($image_style, $source, $derivative_uri)) { + return $derivative_uri; + } +} diff --git a/vendor/drush/drush/commands/core/drupal/pm.inc b/vendor/drush/drush/commands/core/drupal/pm.inc new file mode 100644 index 0000000000..d0d44b12c1 --- /dev/null +++ b/vendor/drush/drush/commands/core/drupal/pm.inc @@ -0,0 +1,86 @@ + $extension)), LogLevel::WARNING); + } + elseif (in_array($extension, $required)) { + unset($extensions[$extension]); + $info = $extension_info[$extension]->info; + $explanation = !empty($info['explanation']) ? ' ' . dt('Reason: !explanation.', array('!explanation' => strip_tags($info['explanation']))) : ''; + drush_log(dt('!extension is a required extension and can\'t be uninstalled.', array('!extension' => $extension)) . $explanation, LogLevel::OK); + } + elseif (!$extension_info[$extension]->status) { + unset($extensions[$extension]); + drush_log(dt('!extension is already uninstalled.', array('!extension' => $extension)), LogLevel::OK); + } + elseif (drush_extension_get_type($extension_info[$extension]) == 'module') { + // Add installed dependencies to the list of modules to uninstall. + foreach (drush_module_dependents(array($extension), $extension_info) as $dependent) { + // Check if this dependency is not required, already enabled, and not already already in the list of modules to uninstall. + if (!in_array($dependent, $required) && ($extension_info[$dependent]->status) && !in_array($dependent, $extensions)) { + $extensions[] = $dependent; + } + } + } + } + + // Discard default theme. + $default_theme = drush_theme_get_default(); + if (in_array($default_theme, $extensions)) { + unset($extensions[$default_theme]); + drush_log(dt('!theme is the default theme and can\'t be uninstalled.', array('!theme' => $default_theme)), LogLevel::OK); + } + + // Inform the user which extensions will finally be disabled. + if (empty($extensions)) { + return drush_log(dt('There were no extensions that could be uninstalled.'), LogLevel::OK); + } + else { + drush_print(dt('The following extensions will be uninstalled: !extensions', array('!extensions' => implode(', ', $extensions)))); + if(!drush_confirm(dt('Do you really want to continue?'))) { + return drush_user_abort(); + } + } + + // Classify extensions in themes and modules. + $modules = array(); + $themes = array(); + drush_pm_classify_extensions($extensions, $modules, $themes, $extension_info); + + drush_module_uninstall($modules); + drush_theme_uninstall($themes); + + // Inform the user of final status. + foreach ($extensions as $extension) { + drush_log(dt('!extension was successfully uninstalled.', array('!extension' => $extension)), LogLevel::OK); + } +} diff --git a/vendor/drush/drush/commands/core/drupal/pm_6.inc b/vendor/drush/drush/commands/core/drupal/pm_6.inc new file mode 100644 index 0000000000..c471187842 --- /dev/null +++ b/vendor/drush/drush/commands/core/drupal/pm_6.inc @@ -0,0 +1,3 @@ + $name)), LogLevel::WARNING); + } + + // Discard already disabled extensions. + foreach ($extensions as $name) { + if (!$extension_info[$name]->status) { + if ($extension_info[$name]->type == 'module') { + unset($modules[$name]); + } + else { + unset($themes[$name]); + } + drush_log(dt('!extension is already disabled.', array('!extension' => $name)), LogLevel::OK); + } + } + + // Discard default theme. + if (!empty($themes)) { + $default_theme = drush_theme_get_default(); + if (in_array($default_theme, $themes)) { + unset($themes[$default_theme]); + drush_log(dt('!theme is the default theme and can\'t be disabled.', array('!theme' => $default_theme)), LogLevel::OK); + } + } + + if (!empty($modules)) { + // Add enabled dependents to the list of modules to disable. + $dependents = drush_module_dependents($modules, $extension_info); + $dependents = array_intersect($dependents, drush_module_list()); + $modules = array_merge($modules, $dependents); + + // Discard required modules. + $required = drush_drupal_required_modules($extension_info); + foreach ($required as $module) { + if (isset($modules[$module])) { + unset($modules[$module]); + $info = $extension_info[$module]->info; + // No message for hidden modules. + if (!isset($info['hidden'])) { + $explanation = !empty($info['explanation']) ? ' ' . dt('Reason: !explanation.', array('!explanation' => strip_tags($info['explanation']))) : ''; + drush_log(dt('!module is a required module and can\'t be disabled.', array('!module' => $module)) . $explanation, LogLevel::OK); + } + } + } + } + + // Inform the user which extensions will finally be disabled. + $extensions = array_merge($modules, $themes); + if (empty($extensions)) { + return drush_log(dt('There were no extensions that could be disabled.'), LogLevel::OK); + } + else { + drush_print(dt('The following extensions will be disabled: !extensions', array('!extensions' => implode(', ', $extensions)))); + if(!drush_confirm(dt('Do you really want to continue?'))) { + return drush_user_abort(); + } + } + + // Disable themes. + if (!empty($themes)) { + drush_theme_disable($themes); + } + + // Disable modules and pass dependency validation in form submit. + if (!empty($modules)) { + drush_module_disable($modules); + } + + // Inform the user of final status. + $result_extensions = drush_get_named_extensions_list($extensions); + $problem_extensions = array(); + foreach ($result_extensions as $extension) { + if (!$extension->status) { + drush_log(dt('!extension was disabled successfully.', array('!extension' => $extension->name)), LogLevel::OK); + } + else { + $problem_extensions[] = $extension->name; + } + } + if (!empty($problem_extensions)) { + return drush_set_error('DRUSH_PM_DISABLE_EXTENSION_ISSUE', dt('There was a problem disabling !extension.', array('!extension' => implode(',', $problem_extensions)))); + } +} + +/** + * Command callback. Uninstall one or more modules. + */ +function _drush_pm_uninstall($modules) { + drush_include_engine('drupal', 'environment'); + $module_info = drush_get_modules(); + $required = drush_drupal_required_modules($module_info); + + // Discards modules which are enabled, not found or already uninstalled. + foreach ($modules as $key => $module) { + if (!isset($module_info[$module])) { + // The module does not exist in the system. + unset($modules[$key]); + drush_log(dt('Module !module was not found and will not be uninstalled.', array('!module' => $module)), LogLevel::WARNING); + } + else if ($module_info[$module]->status) { + // The module is enabled. + unset($modules[$key]); + drush_log(dt('!module is not disabled. Use `pm-disable` command before `pm-uninstall`.', array('!module' => $module)), LogLevel::WARNING); + } + else if ($module_info[$module]->schema_version == -1) { // SCHEMA_UNINSTALLED + // The module is uninstalled. + unset($modules[$key]); + drush_log(dt('!module is already uninstalled.', array('!module' => $module)), LogLevel::OK); + } + else { + $dependents = array(); + foreach (drush_module_dependents(array($module), $module_info) as $dependent) { + if (!in_array($dependent, $required) && ($module_info[$dependent]->schema_version != -1)) { + $dependents[] = $dependent; + } + } + if (count($dependents)) { + drush_log(dt('To uninstall !module, the following modules must be uninstalled first: !required', array('!module' => $module, '!required' => implode(', ', $dependents))), LogLevel::ERROR); + unset($modules[$key]); + } + } + } + + // Inform the user which modules will finally be uninstalled. + if (empty($modules)) { + return drush_log(dt('There were no modules that could be uninstalled.'), LogLevel::OK); + } + else { + drush_print(dt('The following modules will be uninstalled: !modules', array('!modules' => implode(', ', $modules)))); + if(!drush_confirm(dt('Do you really want to continue?'))) { + return drush_user_abort(); + } + } + + // Uninstall the modules. + drush_module_uninstall($modules); + + // Inform the user of final status. + foreach ($modules as $module) { + drush_log(dt('!module was successfully uninstalled.', array('!module' => $module)), LogLevel::OK); + } +} + diff --git a/vendor/drush/drush/commands/core/drupal/site_install.inc b/vendor/drush/drush/commands/core/drupal/site_install.inc new file mode 100644 index 0000000000..f0fe0e4272 --- /dev/null +++ b/vendor/drush/drush/commands/core/drupal/site_install.inc @@ -0,0 +1,90 @@ + FALSE) + install_state_defaults(); + try { + install_begin_request($class_loader, $install_state); + $profile = _install_select_profile($install_state); + } + catch (\Exception $e) { + // This is only a best effort to provide a better default, no harm done + // if it fails. + } + if (empty($profile)) { + $profile = 'standard'; + } + } + + $sql = drush_sql_get_class(); + $db_spec = $sql->db_spec(); + + $account_name = drush_get_option('account-name', 'admin'); + $account_pass = drush_get_option('account-pass', FALSE); + $show_password = drush_get_option('show-passwords', !$account_pass); + if (!$account_pass) { + $account_pass = drush_generate_password(); + } + $settings = array( + 'parameters' => array( + 'profile' => $profile, + 'langcode' => drush_get_option('locale', 'en'), + ), + 'forms' => array( + 'install_settings_form' => array( + 'driver' => $db_spec['driver'], + $db_spec['driver'] => $db_spec, + 'op' => dt('Save and continue'), + ), + 'install_configure_form' => array( + 'site_name' => drush_get_option('site-name', 'Site-Install'), + 'site_mail' => drush_get_option('site-mail', 'admin@example.com'), + 'account' => array( + 'name' => $account_name, + 'mail' => drush_get_option('account-mail', 'admin@example.com'), + 'pass' => array( + 'pass1' => $account_pass, + 'pass2' => $account_pass, + ), + ), + 'enable_update_status_module' => TRUE, + 'enable_update_status_emails' => TRUE, + 'clean_url' => drush_get_option('clean-url', TRUE), + 'op' => dt('Save and continue'), + ), + ), + ); + + // Merge in the additional options. + foreach ($additional_form_options as $key => $value) { + $current = &$settings['forms']; + foreach (explode('.', $key) as $param) { + $current = &$current[$param]; + } + $current = $value; + } + + $msg = 'Starting Drupal installation. This takes a while.'; + if (is_null(drush_get_option('notify'))) { + $msg .= ' Consider using the --notify global option.'; + } + drush_log(dt($msg), LogLevel::OK); + drush_op('install_drupal', $class_loader, $settings); + if ($show_password) { + drush_log(dt('Installation complete. User name: @name User password: @pass', array('@name' => $account_name, '@pass' => $account_pass)), LogLevel::OK); + } + else { + drush_log(dt('Installation complete.'), LogLevel::OK); + } + +} diff --git a/vendor/drush/drush/commands/core/drupal/site_install_6.inc b/vendor/drush/drush/commands/core/drupal/site_install_6.inc new file mode 100644 index 0000000000..d989c8c309 --- /dev/null +++ b/vendor/drush/drush/commands/core/drupal/site_install_6.inc @@ -0,0 +1,149 @@ +&1 + // redirect added to the command in drush_shell_exec(). We will actually take out + // all but fatal errors. See http://drupal.org/node/985716 for more information. + $phpcode = 'error_reporting(E_ERROR);' . _drush_site_install6_cookies($profile). ' include("'. $drupal_root .'/install.php");'; + drush_shell_exec('php -r %s', $phpcode); + $cli_output = drush_shell_exec_output(); + $cli_cookie = end($cli_output); + + // We need to bootstrap the database to be able to check the progress of the + // install batch process since we're not duplicating the install process using + // drush_batch functions, but calling the process directly. + drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_DATABASE); + + $status = _drush_site_install6_stage($profile, $cli_cookie, "start"); + if ($status === FALSE) { + return FALSE; + } + + $status = _drush_site_install6_stage($profile, $cli_cookie, "do_nojs"); + if ($status === FALSE) { + return FALSE; + } + + $status = _drush_site_install6_stage($profile, $cli_cookie, "finished"); + if ($status === FALSE) { + return FALSE; + } + + $account_name = drush_get_option('account-name', 'admin'); + $account_pass = drush_get_option('account-pass', FALSE); + $show_password = drush_get_option('show-passwords', !$account_pass); + if (!$account_pass) { + $account_pass = drush_generate_password(); + } + $phpcode = _drush_site_install6_cookies($profile, $cli_cookie); + $post = array ( + "site_name" => drush_get_option('site-name', 'Site-Install'), + "site_mail" => drush_get_option('site-mail', 'admin@example.com'), + "account" => array ( + "name" => $account_name, + "mail" => drush_get_option('account-mail', 'admin@example.com'), + "pass" => array ( + "pass1" => $account_pass, + "pass2" => $account_pass, + ) + ), + "date_default_timezone" => "0", + "clean_url" => drush_get_option('clean-url', TRUE), + "form_id" => "install_configure_form", + "update_status_module" => array("1" => "1"), + ); + // Merge in the additional options. + foreach ($additional_form_options as $key => $value) { + $current = &$post; + foreach (explode('.', $key) as $param) { + $current = &$current[$param]; + } + $current = $value; + } + $phpcode .= ' + $_POST = ' . var_export($post, true) . '; + include("'. $drupal_root .'/install.php");'; + drush_shell_exec('php -r %s', $phpcode); + + if ($show_password) { + drush_log(dt('Installation complete. User name: @name User password: @pass', array('@name' => $account_name, '@pass' => $account_pass)), LogLevel::OK); + } + else { + drush_log(dt('Installation complete.'), LogLevel::OK); + } +} + +/** + * Submit a given op to install.php; if a meta "Refresh" tag + * is returned in the result, then submit that op as well. + */ +function _drush_site_install6_stage($profile, $cli_cookie, $initial_op) { + $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT'); + // Remember the install task at the start of the stage + $install_task = _drush_site_install6_install_task(); + $op = $initial_op; + while (!empty($op)) { + $phpcode = _drush_site_install6_cookies($profile, $cli_cookie). ' $_GET["op"]="' . $op . '"; include("'. $drupal_root .'/install.php");'; + drush_shell_exec('php -r %s', $phpcode); + $output = implode("\n", drush_shell_exec_output()); + // Check for a "Refresh" back to the do_nojs op; e.g.: + // + // If this pattern is NOT found, then go on to the "finished" step. + $matches = array(); + $match_result = preg_match('/http-equiv="Refresh".*op=([a-zA-Z0-9_]*)/', $output, $matches); + if ($match_result) { + $op = $matches[1]; + } + else { + $op = ''; + } + } + if (($install_task == _drush_site_install6_install_task()) && ($initial_op != "finished")) { + return drush_set_error('DRUSH_SITE_INSTALL_FAILED', dt("The site install task '!task' failed.", array('!task' => $install_task))); + } + return TRUE; +} + +/** + * Utility function to grab/set current "cli cookie". + */ +function _drush_site_install6_cookies($profile, $cookie = NULL) { + $drupal_base_url = parse_url(drush_get_context('DRUSH_SELECTED_URI')); + $output = '$_GET=array("profile"=>"' . $profile . '", "locale"=>"' . drush_get_option('locale', 'en') . '", "id"=>"1"); $_REQUEST=&$_GET;'; + $output .= 'define("DRUSH_SITE_INSTALL6", TRUE);$_SERVER["SERVER_SOFTWARE"] = NULL;'; + $output .= '$_SERVER["SCRIPT_NAME"] = "/install.php";'; + $output .= '$_SERVER["HTTP_HOST"] = "'.$drupal_base_url['host'].'";'; + $output .= '$_SERVER["REMOTE_ADDR"] = "127.0.0.1";'; + + if ($cookie) { + $output .= sprintf('$_COOKIE=unserialize("%s");', str_replace('"', '\"', $cookie)); + } + else { + $output .= 'function _cli_cookie_print(){print(serialize(array(session_name()=>session_id())));} register_shutdown_function("_cli_cookie_print");'; + } + + return $output; +} + +/** + * Utility function to check the install_task. We are + * not bootstrapped to a high enough level to use variable_get. + */ +function _drush_site_install6_install_task() { + if ($data = db_result(db_query("SELECT value FROM {variable} WHERE name = 'install_task'",1))) { + $result = unserialize($data); + } + return $result; +} diff --git a/vendor/drush/drush/commands/core/drupal/site_install_7.inc b/vendor/drush/drush/commands/core/drupal/site_install_7.inc new file mode 100644 index 0000000000..c7f3cdfa61 --- /dev/null +++ b/vendor/drush/drush/commands/core/drupal/site_install_7.inc @@ -0,0 +1,93 @@ +db_spec(); + + $account_name = drush_get_option('account-name', 'admin'); + $account_pass = drush_get_option('account-pass', FALSE); + $show_password = drush_get_option('show-passwords', !$account_pass); + if (!$account_pass) { + $account_pass = drush_generate_password(); + } + $settings = array( + 'parameters' => array( + 'profile' => $profile, + 'locale' => drush_get_option('locale', 'en'), + ), + 'forms' => array( + 'install_settings_form' => array( + 'driver' => $db_spec['driver'], + $db_spec['driver'] => $db_spec, + 'op' => dt('Save and continue'), + ), + 'install_configure_form' => array( + 'site_name' => drush_get_option('site-name', 'Site-Install'), + 'site_mail' => drush_get_option('site-mail', 'admin@example.com'), + 'account' => array( + 'name' => $account_name, + 'mail' => drush_get_option('account-mail', 'admin@example.com'), + 'pass' => array( + 'pass1' => $account_pass, + 'pass2' => $account_pass, + ), + ), + 'update_status_module' => array( + 1 => TRUE, + 2 => TRUE, + ), + 'clean_url' => drush_get_option('clean-url', TRUE), + 'op' => dt('Save and continue'), + ), + ), + ); + + // Merge in the additional options. + foreach ($additional_form_options as $key => $value) { + $current = &$settings['forms']; + foreach (explode('.', $key) as $param) { + $current = &$current[$param]; + } + $current = $value; + } + + $msg = 'Starting Drupal installation. This takes a while.'; + if (is_null(drush_get_option('notify'))) { + $msg .= ' Consider using the --notify global option.'; + } + drush_log(dt($msg), LogLevel::OK); + drush_op('install_drupal', $settings); + if ($show_password) { + drush_log(dt('Installation complete. User name: @name User password: @pass', array('@name' => $account_name, '@pass' => $account_pass)), LogLevel::OK); + } + else { + drush_log(dt('Installation complete.'), LogLevel::OK); + } +} diff --git a/vendor/drush/drush/commands/core/drupal/update.inc b/vendor/drush/drush/commands/core/drupal/update.inc new file mode 100644 index 0000000000..71cca5c710 --- /dev/null +++ b/vendor/drush/drush/commands/core/drupal/update.inc @@ -0,0 +1,452 @@ + FALSE, 'query' => 'What went wrong'); + * The schema version will not be updated in this case, and all the + * aborted updates will continue to appear on update.php as updates that + * have not yet been run. + * + * @param $module + * The module whose update will be run. + * @param $number + * The update number to run. + * @param $context + * The batch context array + */ +function drush_update_do_one($module, $number, $dependency_map, &$context) { + $function = $module . '_update_' . $number; + + // Disable config entity overrides. + if (!defined('MAINTENANCE_MODE')) { + define('MAINTENANCE_MODE', 'update'); + } + + // If this update was aborted in a previous step, or has a dependency that + // was aborted in a previous step, go no further. + if (!empty($context['results']['#abort']) && array_intersect($context['results']['#abort'], array_merge($dependency_map, array($function)))) { + return; + } + + $context['log'] = FALSE; + + \Drupal::moduleHandler()->loadInclude($module, 'install'); + + $ret = array(); + if (function_exists($function)) { + try { + if ($context['log']) { + Database::startLog($function); + } + + drush_log("Executing " . $function); + $ret['results']['query'] = $function($context['sandbox']); + $ret['results']['success'] = TRUE; + } + // @TODO We may want to do different error handling for different exception + // types, but for now we'll just print the message. + catch (Exception $e) { + $ret['#abort'] = array('success' => FALSE, 'query' => $e->getMessage()); + drush_set_error('DRUPAL_EXCEPTION', $e->getMessage()); + } + + if ($context['log']) { + $ret['queries'] = Database::getLog($function); + } + } + else { + $ret['#abort'] = array('success' => FALSE); + drush_set_error('DRUSH_UPDATE_FUNCTION_NOT_FOUND', dt('Update function @function not found', array('@function' => $function))); + } + + if (isset($context['sandbox']['#finished'])) { + $context['finished'] = $context['sandbox']['#finished']; + unset($context['sandbox']['#finished']); + } + + if (!isset($context['results'][$module])) { + $context['results'][$module] = array(); + } + if (!isset($context['results'][$module][$number])) { + $context['results'][$module][$number] = array(); + } + $context['results'][$module][$number] = array_merge($context['results'][$module][$number], $ret); + + if (!empty($ret['#abort'])) { + // Record this function in the list of updates that were aborted. + $context['results']['#abort'][] = $function; + } + + // Record the schema update if it was completed successfully. + if ($context['finished'] == 1 && empty($ret['#abort'])) { + drupal_set_installed_schema_version($module, $number); + } + + $context['message'] = 'Performing ' . $function; +} + +/** + * Clears caches and rebuilds the container. + * + * This is called in between regular updates and post updates. Do not use + * drush_drupal_cache_clear_all() as the cache clearing and container rebuild + * must happen in the same process that the updates are run in. + * + * Drupal core's update.php uses drupal_flush_all_caches() directly without + * explicitly rebuilding the container as the container is rebuilt on the next + * HTTP request of the batch. + * + * @see drush_drupal_cache_clear_all() + * @see \Drupal\system\Controller\DbUpdateController::triggerBatch() + */ +function drush_update_cache_rebuild() { + drupal_flush_all_caches(); + \Drupal::service('kernel')->rebuildContainer(); +} + +function update_main() { + // In D8, we expect to be in full bootstrap. + drush_bootstrap_to_phase(DRUSH_BOOTSTRAP_DRUPAL_FULL); + + require_once DRUPAL_ROOT . '/core/includes/install.inc'; + require_once DRUPAL_ROOT . '/core/includes/update.inc'; + drupal_load_updates(); + + // Disables extensions that have a lower Drupal core major version, or too high of a PHP requirement. + // Those are rare, and this function does a full rebuild. So commenting it out as in Drush 10. + // n.b. This function does not exist in Drupal 9. See https://www.drupal.org/node/3026100 + // update_fix_compatibility(); + + // Check requirements before updating. + if (!drush_update_check_requirements()) { + if (!drush_confirm(dt('Requirements check reports errors. Do you wish to continue?'))) { + return drush_user_abort(); + } + } + + // Pending hook_update_N() implementations. + $pending = update_get_update_list(); + + // Pending hook_post_update_X() implementations. + $post_updates = \Drupal::service('update.post_update_registry')->getPendingUpdateInformation(); + + $start = array(); + + $change_summary = []; + if (drush_get_option('entity-updates', FALSE)) { + $change_summary = \Drupal::entityDefinitionUpdateManager()->getChangeSummary(); + } + + // Print a list of pending updates for this module and get confirmation. + if (count($pending) || count($change_summary) || count($post_updates)) { + drush_print(dt('The following updates are pending:')); + drush_print(); + + foreach ($change_summary as $entity_type_id => $changes) { + drush_print($entity_type_id . ' entity type : '); + foreach ($changes as $change) { + drush_print(strip_tags($change), 2); + } + } + + foreach (array('update', 'post_update') as $update_type) { + $updates = $update_type == 'update' ? $pending : $post_updates; + foreach ($updates as $module => $updates) { + if (isset($updates['start'])) { + drush_print($module . ' module : '); + if (!empty($updates['pending'])) { + $start += [$module => array()]; + + $start[$module] = array_merge($start[$module], $updates['pending']); + foreach ($updates['pending'] as $update) { + drush_print(strip_tags($update), 2); + } + } + drush_print(); + } + } + } + + if (!drush_confirm(dt('Do you wish to run all pending updates?'))) { + return drush_user_abort(); + } + + drush_update_batch($start); + } + else { + drush_log(dt("No database updates required"), LogLevel::SUCCESS); + } + + return count($pending) + count($change_summary) + count($post_updates); +} + +/** + * Check update requirements and report any errors. + */ +function drush_update_check_requirements() { + $continue = TRUE; + + \Drupal::moduleHandler()->resetImplementations(); + $requirements = update_check_requirements(); + $severity = drupal_requirements_severity($requirements); + + // If there are issues, report them. + if ($severity != REQUIREMENT_OK) { + if ($severity === REQUIREMENT_ERROR) { + $continue = FALSE; + } + foreach ($requirements as $requirement) { + if (isset($requirement['severity']) && $requirement['severity'] != REQUIREMENT_OK) { + $message = isset($requirement['description']) ? $requirement['description'] : ''; + if (isset($requirement['value']) && $requirement['value']) { + $message .= ' (Currently using '. $requirement['title'] .' '. $requirement['value'] .')'; + } + $log_level = $requirement['severity'] === REQUIREMENT_ERROR ? LogLevel::ERROR : LogLevel::WARNING; + drush_log($message, $log_level); + } + } + } + + return $continue; +} + +function _update_batch_command($id) { + // In D8, we expect to be in full bootstrap. + drush_bootstrap_to_phase(DRUSH_BOOTSTRAP_DRUPAL_FULL); + + drush_batch_command($id); +} + +/** + * Start the database update batch process. + */ +function drush_update_batch() { + $start = drush_get_update_list(); + // Resolve any update dependencies to determine the actual updates that will + // be run and the order they will be run in. + $updates = update_resolve_dependencies($start); + + // Store the dependencies for each update function in an array which the + // batch API can pass in to the batch operation each time it is called. (We + // do not store the entire update dependency array here because it is + // potentially very large.) + $dependency_map = array(); + foreach ($updates as $function => $update) { + $dependency_map[$function] = !empty($update['reverse_paths']) ? array_keys($update['reverse_paths']) : array(); + } + + $operations = array(); + + foreach ($updates as $update) { + if ($update['allowed']) { + // Set the installed version of each module so updates will start at the + // correct place. (The updates are already sorted, so we can simply base + // this on the first one we come across in the above foreach loop.) + if (isset($start[$update['module']])) { + drupal_set_installed_schema_version($update['module'], $update['number'] - 1); + unset($start[$update['module']]); + } + // Add this update function to the batch. + $function = $update['module'] . '_update_' . $update['number']; + $operations[] = array('drush_update_do_one', array($update['module'], $update['number'], $dependency_map[$function])); + } + } + + // Apply post update hooks. + $post_updates = \Drupal::service('update.post_update_registry')->getPendingUpdateFunctions(); + if ($post_updates) { + $operations[] = ['drush_update_cache_rebuild', []]; + foreach ($post_updates as $function) { + $operations[] = ['update_invoke_post_update', [$function]]; + } + } + + // Lastly, perform entity definition updates, which will update storage + // schema if needed. If module update functions need to work with specific + // entity schema they should call the entity update service for the specific + // update themselves. + // @see \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface::applyEntityUpdate() + // @see \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface::applyFieldUpdate() + if (drush_get_option('entity-updates', FALSE) && \Drupal::entityDefinitionUpdateManager()->needsUpdates()) { + $operations[] = array('drush_update_entity_definitions', array()); + } + + $batch['operations'] = $operations; + $batch += array( + 'title' => 'Updating', + 'init_message' => 'Starting updates', + 'error_message' => 'An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.', + 'finished' => 'drush_update_finished', + 'file' => 'includes/update.inc', + ); + batch_set($batch); + \Drupal::service('state')->set('system.maintenance_mode', TRUE); + drush_backend_batch_process('updatedb-batch-process'); + \Drupal::service('state')->set('system.maintenance_mode', FALSE); +} + +/** + * Apply entity schema updates. + */ +function drush_update_entity_definitions(&$context) { + try { + \Drupal::entityDefinitionUpdateManager()->applyUpdates(); + } + catch (EntityStorageException $e) { + watchdog_exception('update', $e); + $variables = Error::decodeException($e); + unset($variables['backtrace']); + // The exception message is run through + // \Drupal\Component\Utility\SafeMarkup::checkPlain() by + // \Drupal\Core\Utility\Error::decodeException(). + $ret['#abort'] = array('success' => FALSE, 'query' => t('%type: !message in %function (line %line of %file).', $variables)); + $context['results']['core']['update_entity_definitions'] = $ret; + $context['results']['#abort'][] = 'update_entity_definitions'; + } +} + +// Copy of protected \Drupal\system\Controller\DbUpdateController::getModuleUpdates. +function drush_get_update_list() { + $return = array(); + $updates = update_get_update_list(); + foreach ($updates as $module => $update) { + $return[$module] = $update['start']; + } + + return $return; +} + +/** + * Process and display any returned update output. + * + * @see \Drupal\system\Controller\DbUpdateController::batchFinished() + * @see \Drupal\system\Controller\DbUpdateController::results() + */ +function drush_update_finished($success, $results, $operations) { + + if (!drush_get_option('cache-clear', TRUE)) { + drush_log(dt("Skipping cache-clear operation due to --cache-clear=0 option."), LogLevel::WARNING); + } + else { + drupal_flush_all_caches(); + } + + foreach ($results as $module => $updates) { + if ($module != '#abort') { + foreach ($updates as $number => $queries) { + foreach ($queries as $query) { + // If there is no message for this update, don't show anything. + if (empty($query['query'])) { + continue; + } + + if ($query['success']) { + drush_log(strip_tags($query['query'])); + } + else { + drush_set_error(dt('Failed: ') . strip_tags($query['query'])); + } + } + } + } + } +} + +/** + * Return a 2 item array with + * - an array where each item is a 3 item associative array describing a pending update. + * - an array listing the first update to run, keyed by module. + */ +function updatedb_status() { + $pending = update_get_update_list(); + + $return = array(); + // Ensure system module's updates run first. + $start['system'] = array(); + + foreach (\Drupal::entityDefinitionUpdateManager()->getChangeSummary() as $entity_type_id => $changes) { + foreach ($changes as $change) { + $return[] = array( + 'module' => dt('@type entity type', array('@type' => $entity_type_id)), 'update_id' => '', 'description' => strip_tags($change)); + } + } + + // Print a list of pending updates for this module and get confirmation. + foreach ($pending as $module => $updates) { + if (isset($updates['start'])) { + foreach ($updates['pending'] as $update_id => $description) { + // Strip cruft from front. + $description = str_replace($update_id . ' - ', '', $description); + $return[] = array('module' => ucfirst($module), 'update_id' => $update_id, 'description' => $description); + } + if (isset($updates['start'])) { + $start[$module] = $updates['start']; + } + } + } + + return array($return, $start); +} + +/** + * Apply pending entity schema updates. + */ +function entity_updates_main() { + $change_summary = \Drupal::entityDefinitionUpdateManager()->getChangeSummary(); + if (!empty($change_summary)) { + drush_print(dt('The following updates are pending:')); + drush_print(); + + foreach ($change_summary as $entity_type_id => $changes) { + drush_print($entity_type_id . ' entity type : '); + foreach ($changes as $change) { + drush_print(strip_tags($change), 2); + } + } + + if (!drush_confirm(dt('Do you wish to run all pending updates?'))) { + return drush_user_abort(); + } + + $operations[] = array('drush_update_entity_definitions', array()); + + + $batch['operations'] = $operations; + $batch += array( + 'title' => 'Updating', + 'init_message' => 'Starting updates', + 'error_message' => 'An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.', + 'finished' => 'drush_update_finished', + 'file' => 'includes/update.inc', + ); + batch_set($batch); + \Drupal::service('state')->set('system.maintenance_mode', TRUE); + drush_backend_batch_process('updatedb-batch-process'); + \Drupal::service('state')->set('system.maintenance_mode', FALSE); + } + else { + drush_log(dt("No entity schema updates required"), LogLevel::SUCCESS); + } +} diff --git a/vendor/drush/drush/commands/core/drupal/update_6.inc b/vendor/drush/drush/commands/core/drupal/update_6.inc new file mode 100644 index 0000000000..2accffc9c8 --- /dev/null +++ b/vendor/drush/drush/commands/core/drupal/update_6.inc @@ -0,0 +1,522 @@ + "''" + * in the $attributes array. If NOT NULL and DEFAULT are set the PostgreSQL + * version will set values of the added column in old rows to the + * DEFAULT value. + * + * @param $ret + * Array to which results will be added. + * @param $table + * Name of the table, without {} + * @param $column + * Name of the column + * @param $type + * Type of column + * @param $attributes + * Additional optional attributes. Recognized attributes: + * not null => TRUE|FALSE + * default => NULL|FALSE|value (the value must be enclosed in '' marks) + * @return + * nothing, but modifies $ret parameter. + */ +function db_add_column(&$ret, $table, $column, $type, $attributes = array()) { + if (array_key_exists('not null', $attributes) and $attributes['not null']) { + $not_null = 'NOT NULL'; + } + if (array_key_exists('default', $attributes)) { + if (!isset($attributes['default'])) { + $default_val = 'NULL'; + $default = 'default NULL'; + } + elseif ($attributes['default'] === FALSE) { + $default = ''; + } + else { + $default_val = "$attributes[default]"; + $default = "default $attributes[default]"; + } + } + + $ret[] = update_sql("ALTER TABLE {". $table ."} ADD $column $type"); + if (!empty($default)) { + $ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $column SET $default"); + } + if (!empty($not_null)) { + if (!empty($default)) { + $ret[] = update_sql("UPDATE {". $table ."} SET $column = $default_val"); + } + $ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $column SET NOT NULL"); + } +} + +/** + * Change a column definition using syntax appropriate for PostgreSQL. + * Save result of SQL commands in $ret array. + * + * Remember that changing a column definition involves adding a new column + * and dropping an old one. This means that any indices, primary keys and + * sequences from serial-type columns are dropped and might need to be + * recreated. + * + * @param $ret + * Array to which results will be added. + * @param $table + * Name of the table, without {} + * @param $column + * Name of the column to change + * @param $column_new + * New name for the column (set to the same as $column if you don't want to change the name) + * @param $type + * Type of column + * @param $attributes + * Additional optional attributes. Recognized attributes: + * not null => TRUE|FALSE + * default => NULL|FALSE|value (with or without '', it won't be added) + * @return + * nothing, but modifies $ret parameter. + */ +function db_change_column(&$ret, $table, $column, $column_new, $type, $attributes = array()) { + if (array_key_exists('not null', $attributes) and $attributes['not null']) { + $not_null = 'NOT NULL'; + } + if (array_key_exists('default', $attributes)) { + if (!isset($attributes['default'])) { + $default_val = 'NULL'; + $default = 'default NULL'; + } + elseif ($attributes['default'] === FALSE) { + $default = ''; + } + else { + $default_val = "$attributes[default]"; + $default = "default $attributes[default]"; + } + } + + $ret[] = update_sql("ALTER TABLE {". $table ."} RENAME $column TO ". $column ."_old"); + $ret[] = update_sql("ALTER TABLE {". $table ."} ADD $column_new $type"); + $ret[] = update_sql("UPDATE {". $table ."} SET $column_new = ". $column ."_old"); + if ($default) { $ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $column_new SET $default"); } + if ($not_null) { $ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $column_new SET NOT NULL"); } + $ret[] = update_sql("ALTER TABLE {". $table ."} DROP ". $column ."_old"); +} + + +/** + * Disable anything in the {system} table that is not compatible with the + * current version of Drupal core. + */ +function update_fix_compatibility() { + $ret = array(); + $incompatible = array(); + $query = db_query("SELECT name, type, status FROM {system} WHERE status = 1 AND type IN ('module','theme')"); + while ($result = db_fetch_object($query)) { + if (update_check_incompatibility($result->name, $result->type)) { + $incompatible[] = $result->name; + drush_log(dt("!type !name is incompatible with this release of Drupal, and will be disabled.", + array("!type" => $result->type, '!name' => $result->name)), LogLevel::WARNING); + } + } + if (!empty($incompatible)) { + + $ret[] = update_sql("UPDATE {system} SET status = 0 WHERE name IN ('". implode("','", $incompatible) ."')"); + } + return $ret; +} + +/** + * Helper function to test compatibility of a module or theme. + */ +function update_check_incompatibility($name, $type = 'module') { + static $themes, $modules; + + // Store values of expensive functions for future use. + if (empty($themes) || empty($modules)) { + drush_include_engine('drupal', 'environment'); + $themes = _system_theme_data(); + $modules = module_rebuild_cache(); + } + + if ($type == 'module' && isset($modules[$name])) { + $file = $modules[$name]; + } + else if ($type == 'theme' && isset($themes[$name])) { + $file = $themes[$name]; + } + if (!isset($file) + || !isset($file->info['core']) + || $file->info['core'] != drush_get_drupal_core_compatibility() + || version_compare(phpversion(), $file->info['php']) < 0) { + return TRUE; + } + return FALSE; +} + +/** + * Perform Drupal 5.x to 6.x updates that are required for update.php + * to function properly. + * + * This function runs when update.php is run the first time for 6.x, + * even before updates are selected or performed. It is important + * that if updates are not ultimately performed that no changes are + * made which make it impossible to continue using the prior version. + * Just adding columns is safe. However, renaming the + * system.description column to owner is not. Therefore, we add the + * system.owner column and leave it to system_update_6008() to copy + * the data from description and remove description. The same for + * renaming locales_target.locale to locales_target.language, which + * will be finished by locale_update_6002(). + */ +function update_fix_d6_requirements() { + $ret = array(); + + if (drupal_get_installed_schema_version('system') < 6000 && !variable_get('update_d6_requirements', FALSE)) { + $spec = array('type' => 'int', 'size' => 'small', 'default' => 0, 'not null' => TRUE); + db_add_field($ret, 'cache', 'serialized', $spec); + db_add_field($ret, 'cache_filter', 'serialized', $spec); + db_add_field($ret, 'cache_page', 'serialized', $spec); + db_add_field($ret, 'cache_menu', 'serialized', $spec); + + db_add_field($ret, 'system', 'info', array('type' => 'text')); + db_add_field($ret, 'system', 'owner', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '')); + if (db_table_exists('locales_target')) { + db_add_field($ret, 'locales_target', 'language', array('type' => 'varchar', 'length' => 12, 'not null' => TRUE, 'default' => '')); + } + if (db_table_exists('locales_source')) { + db_add_field($ret, 'locales_source', 'textgroup', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => 'default')); + db_add_field($ret, 'locales_source', 'version', array('type' => 'varchar', 'length' => 20, 'not null' => TRUE, 'default' => 'none')); + } + variable_set('update_d6_requirements', TRUE); + + // Create the cache_block table. See system_update_6027() for more details. + $schema['cache_block'] = array( + 'fields' => array( + 'cid' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''), + 'data' => array('type' => 'blob', 'not null' => FALSE, 'size' => 'big'), + 'expire' => array('type' => 'int', 'not null' => TRUE, 'default' => 0), + 'created' => array('type' => 'int', 'not null' => TRUE, 'default' => 0), + 'headers' => array('type' => 'text', 'not null' => FALSE), + 'serialized' => array('type' => 'int', 'size' => 'small', 'not null' => TRUE, 'default' => 0) + ), + 'indexes' => array('expire' => array('expire')), + 'primary key' => array('cid'), + ); + db_create_table($ret, 'cache_block', $schema['cache_block']); + + // Create the semaphore table now -- the menu system after 6.15 depends on + // this table, and menu code runs in updates prior to the table being + // created in its original update function, system_update_6054(). + $schema['semaphore'] = array( + 'fields' => array( + 'name' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => ''), + 'value' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => ''), + 'expire' => array( + 'type' => 'float', + 'size' => 'big', + 'not null' => TRUE), + ), + 'indexes' => array('expire' => array('expire')), + 'primary key' => array('name'), + ); + db_create_table($ret, 'semaphore', $schema['semaphore']); + } + + return $ret; +} + +/** + * Check update requirements and report any errors. + */ +function update_check_requirements() { + // Check the system module requirements only. + $requirements = module_invoke('system', 'requirements', 'update'); + $severity = drupal_requirements_severity($requirements); + + // If there are issues, report them. + if ($severity != REQUIREMENT_OK) { + foreach ($requirements as $requirement) { + if (isset($requirement['severity']) && $requirement['severity'] != REQUIREMENT_OK) { + $message = isset($requirement['description']) ? $requirement['description'] : ''; + if (isset($requirement['value']) && $requirement['value']) { + $message .= ' (Currently using '. $requirement['title'] .' '. $requirement['value'] .')'; + } + drush_log($message, LogLevel::WARNING); + } + } + } +} + +/** + * Create the batch table. + * + * This is part of the Drupal 5.x to 6.x migration. + */ +function update_create_batch_table() { + + // If batch table exists, update is not necessary + if (db_table_exists('batch')) { + return; + } + + $schema['batch'] = array( + 'fields' => array( + 'bid' => array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE), + 'token' => array('type' => 'varchar', 'length' => 64, 'not null' => TRUE), + 'timestamp' => array('type' => 'int', 'not null' => TRUE), + 'batch' => array('type' => 'text', 'not null' => FALSE, 'size' => 'big') + ), + 'primary key' => array('bid'), + 'indexes' => array('token' => array('token')), + ); + + $ret = array(); + db_create_table($ret, 'batch', $schema['batch']); + return $ret; +} + +function update_main_prepare() { + global $profile; + // Some unavoidable errors happen because the database is not yet up-to-date. + // Our custom error handler is not yet installed, so we just suppress them. + drush_errors_off(); + + require_once './includes/bootstrap.inc'; + // Minimum load of components. + // This differs from the Drupal 6 update.php workflow for compatbility with + // the Drupal 6 backport of module_implements() caching. + // @see http://drupal.org/node/557542 + drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_DATABASE); + require_once './includes/install.inc'; + require_once './includes/file.inc'; + require_once './modules/system/system.install'; + + // Load module basics. + include_once './includes/module.inc'; + $module_list['system']['filename'] = 'modules/system/system.module'; + $module_list['filter']['filename'] = 'modules/filter/filter.module'; + module_list(TRUE, FALSE, FALSE, $module_list); + module_implements('', FALSE, TRUE); + + drupal_load('module', 'system'); + drupal_load('module', 'filter'); + + // Set up $language, since the installer components require it. + drupal_init_language(); + + // Set up theme system for the maintenance page. + drupal_maintenance_theme(); + + // Check the update requirements for Drupal. + update_check_requirements(); + + drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_FULL); + $profile = variable_get('install_profile', 'default'); + // Updates only run reliably if user ID #1 is logged in. For example, node_delete() requires elevated perms in D5/6. + if (!drush_get_context('DRUSH_USER')) { + drush_set_option('user', 1); + drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_LOGIN); + } + + // This must happen *after* drupal_bootstrap(), since it calls + // variable_(get|set), which only works after a full bootstrap. + _drush_log_update_sql(update_create_batch_table()); + + // Turn error reporting back on. From now on, only fatal errors (which are + // not passed through the error handler) will cause a message to be printed. + drush_errors_on(); + + // Perform Drupal 5.x to 6.x updates that are required for update.php to function properly. + _drush_log_update_sql(update_fix_d6_requirements()); + + // Must unset $theme->status in order to safely rescan and repopulate + // the system table to ensure we have a full picture of the platform. + // This is needed because $theme->status is set to 0 in a call to + // list_themes() done by drupal_maintenance_theme(). + // It is a issue with _system_theme_data() that returns its own cache + // variable and can be modififed by others. When this is fixed in + // drupal core we can remove this unset. + // For reference see: http://drupal.org/node/762754 + $themes = _system_theme_data(); + foreach ($themes as $theme) { + unset($theme->status); + } + drush_get_extensions(); + + include_once './includes/batch.inc'; + drupal_load_updates(); + + // Disable anything in the {system} table that is not compatible with the current version of Drupal core. + _drush_log_update_sql(update_fix_compatibility()); +} + +function update_main() { + update_main_prepare(); + + list($pending, $start) = updatedb_status(); + + // Print a list of pending updates for this module and get confirmation. + if ($pending) { + // @todo get table header working + // array_unshift($pending, array(dt('Module'), dt('ID'), dt('Description'))); + drush_print_table($pending, FALSE); + if (!drush_confirm(dt('Do you wish to run all pending updates?'))) { + return drush_user_abort(); + } + // Proceed with running all pending updates. + $operations = array(); + foreach ($start as $module => $version) { + drupal_set_installed_schema_version($module, $version - 1); + $updates = drupal_get_schema_versions($module); + $max_version = max($updates); + if ($version <= $max_version) { + drush_log(dt('Updating module @module from schema version @start to schema version @max', array('@module' => $module, '@start' => $version - 1, '@max' => $max_version))); + foreach ($updates as $update) { + if ($update >= $version) { + $operations[] = array('_update_do_one', array($module, $update)); + } + } + } + else { + drush_log(dt('No database updates for module @module', array('@module' => $module)), LogLevel::SUCCESS); + } + } + $batch = array( + 'operations' => $operations, + 'title' => 'Updating', + 'init_message' => 'Starting updates', + 'error_message' => 'An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.', + 'finished' => 'update_finished', + ); + batch_set($batch); + $batch =& batch_get(); + $batch['progressive'] = FALSE; + drush_backend_batch_process('updatedb-batch-process'); + } + else { + drush_log(dt("No database updates required"), LogLevel::SUCCESS); + } + + return count($pending); +} + +/** + * A simplified version of the batch_do_one function from update.php + * + * This does not mess with sessions and the like, as it will be used + * from the command line + */ +function _update_do_one($module, $number, &$context) { + // If updates for this module have been aborted + // in a previous step, go no further. + if (!empty($context['results'][$module]['#abort'])) { + return; + } + + $function = $module .'_update_'. $number; + drush_log("Executing $function", LogLevel::SUCCESS); + + if (function_exists($function)) { + $ret = $function($context['sandbox']); + $context['results'][$module] = $ret; + _drush_log_update_sql($ret); + } + + if (isset($ret['#finished'])) { + $context['finished'] = $ret['#finished']; + unset($ret['#finished']); + } + + if ($context['finished'] == 1 && empty($context['results'][$module]['#abort'])) { + drupal_set_installed_schema_version($module, $number); + } + +} + +function _update_batch_command($id) { + update_main_prepare(); + drush_batch_command($id); +} + +/** + * Return a 2 item array with + * - an array where each item is a 3 item associative array describing a pending update. + * - an array listing the first update to run, keyed by module. + */ +function updatedb_status() { + $return = array(); + + $modules = drupal_get_installed_schema_version(NULL, FALSE, TRUE); + foreach ($modules as $module => $schema_version) { + $updates = drupal_get_schema_versions($module); + // Skip incompatible module updates completely, otherwise test schema versions. + if (!update_check_incompatibility($module) && $updates !== FALSE && $schema_version >= 0) { + // module_invoke returns NULL for nonexisting hooks, so if no updates + // are removed, it will == 0. + $last_removed = module_invoke($module, 'update_last_removed'); + if ($schema_version < $last_removed) { + drush_set_error('PROVISION_DRUPAL_UPDATE_FAILED', dt( $module .' module can not be updated. Its schema version is '. $schema_version .'. Updates up to and including '. $last_removed .' have been removed in this release. In order to update '. $module .' module, you will first need to upgrade to the last version in which these updates were available.')); + continue; + } + + $updates = drupal_map_assoc($updates); + + // Record the starting update number for each module. + foreach (array_keys($updates) as $update) { + if ($update > $schema_version) { + $start[$module] = $update; + break; + } + } + if (isset($start['system'])) { + // Ensure system module's updates run first. + $start = array('system' => $start['system']) + $start; + } + + // Record any pending updates. Used for confirmation prompt. + foreach (array_keys($updates) as $update) { + if ($update > $schema_version) { + if (class_exists('ReflectionFunction')) { + // The description for an update comes from its Doxygen. + $func = new ReflectionFunction($module. '_update_'. $update); + $description = trim(str_replace(array("\n", '*', '/'), '', $func->getDocComment())); + } + if (empty($description)) { + $description = dt('description not available'); + } + + $return[] = array('module' => ucfirst($module), 'update_id' => $update, 'description' => $description); + } + } + } + } + + return array($return, $start); +} diff --git a/vendor/drush/drush/commands/core/drupal/update_7.inc b/vendor/drush/drush/commands/core/drupal/update_7.inc new file mode 100644 index 0000000000..9dd4803c25 --- /dev/null +++ b/vendor/drush/drush/commands/core/drupal/update_7.inc @@ -0,0 +1,344 @@ + FALSE, 'query' => 'What went wrong'); + * The schema version will not be updated in this case, and all the + * aborted updates will continue to appear on update.php as updates that + * have not yet been run. + * + * @param $module + * The module whose update will be run. + * @param $number + * The update number to run. + * @param $context + * The batch context array + */ +function drush_update_do_one($module, $number, $dependency_map, &$context) { + $function = $module . '_update_' . $number; + + // If this update was aborted in a previous step, or has a dependency that + // was aborted in a previous step, go no further. + if (!empty($context['results']['#abort']) && array_intersect($context['results']['#abort'], array_merge($dependency_map, array($function)))) { + return; + } + + $context['log'] = FALSE; + + $ret = array(); + if (function_exists($function)) { + try { + if ($context['log']) { + Database::startLog($function); + } + + drush_log("Executing " . $function); + $ret['results']['query'] = $function($context['sandbox']); + + // If the update hook returned a status message (common in batch updates), + // show it to the user. + if ($ret['results']['query']) { + drush_log($ret['results']['query'], LogLevel::OK); + } + + $ret['results']['success'] = TRUE; + } + // @TODO We may want to do different error handling for different exception + // types, but for now we'll just print the message. + catch (Exception $e) { + $ret['#abort'] = array('success' => FALSE, 'query' => $e->getMessage()); + drush_set_error('DRUPAL_EXCEPTION', $e->getMessage()); + } + + if ($context['log']) { + $ret['queries'] = Database::getLog($function); + } + } + + if (isset($context['sandbox']['#finished'])) { + $context['finished'] = $context['sandbox']['#finished']; + unset($context['sandbox']['#finished']); + } + + if (!isset($context['results'][$module])) { + $context['results'][$module] = array(); + } + if (!isset($context['results'][$module][$number])) { + $context['results'][$module][$number] = array(); + } + $context['results'][$module][$number] = array_merge($context['results'][$module][$number], $ret); + + if (!empty($ret['#abort'])) { + // Record this function in the list of updates that were aborted. + $context['results']['#abort'][] = $function; + } + + // Record the schema update if it was completed successfully. + if ($context['finished'] == 1 && empty($ret['#abort'])) { + drupal_set_installed_schema_version($module, $number); + } + + $context['message'] = 'Performed update: ' . $function; +} + +/** + * Check update requirements and report any errors. + */ +function update_check_requirements() { + $warnings = FALSE; + + // Check the system module and update.php requirements only. + $requirements = system_requirements('update'); + $requirements += update_extra_requirements(); + + // If there are issues, report them. + foreach ($requirements as $requirement) { + if (isset($requirement['severity']) && $requirement['severity'] > REQUIREMENT_OK) { + $message = isset($requirement['description']) ? $requirement['description'] : ''; + if (isset($requirement['value']) && $requirement['value']) { + $message .= ' (Currently using ' . $requirement['title'] . ' ' . $requirement['value'] . ')'; + } + $warnings = TRUE; + drupal_set_message($message, LogLevel::WARNING); + } + } + return $warnings; +} + + +function update_main_prepare() { + // Some unavoidable errors happen because the database is not yet up-to-date. + // Our custom error handler is not yet installed, so we just suppress them. + drush_errors_off(); + + // We prepare a minimal bootstrap for the update requirements check to avoid + // reaching the PHP memory limit. + $core = DRUSH_DRUPAL_CORE; + require_once $core . '/includes/bootstrap.inc'; + require_once $core . '/includes/common.inc'; + require_once $core . '/includes/file.inc'; + require_once $core . '/includes/entity.inc'; + include_once $core . '/includes/unicode.inc'; + + update_prepare_d7_bootstrap(); + drupal_bootstrap(DRUPAL_BOOTSTRAP_SESSION); + + require_once $core . '/includes/install.inc'; + require_once $core . '/modules/system/system.install'; + + // Load module basics. + include_once $core . '/includes/module.inc'; + $module_list['system']['filename'] = 'modules/system/system.module'; + module_list(TRUE, FALSE, FALSE, $module_list); + drupal_load('module', 'system'); + + // Reset the module_implements() cache so that any new hook implementations + // in updated code are picked up. + module_implements('', FALSE, TRUE); + + // Set up $language, since the installer components require it. + drupal_language_initialize(); + + // Set up theme system for the maintenance page. + drupal_maintenance_theme(); + + // Check the update requirements for Drupal. + update_check_requirements(); + + // update_fix_d7_requirements() needs to run before bootstrapping beyond path. + // So bootstrap to DRUPAL_BOOTSTRAP_LANGUAGE then include unicode.inc. + drupal_bootstrap(DRUPAL_BOOTSTRAP_LANGUAGE); + + update_fix_d7_requirements(); + + // Clear the module_implements() cache before the full bootstrap. The calls + // above to drupal_maintenance_theme() and update_check_requirements() have + // invoked hooks before all modules have actually been loaded by the full + // bootstrap. This means that the module_implements() results for any hooks + // that have been invoked, including hook_module_implements_alter(), is a + // smaller set of modules than should be returned normally. + // @see https://github.com/drush-ops/drush/pull/399 + module_implements('', FALSE, TRUE); + + // Ensure we re-evaluate the stream wrappers on full bootstrap. + // update_check_requirements() invokes a writeable check and hence loads the + // stream wrappers while not all modules are available. + drupal_static_reset('file_get_stream_wrappers'); + + // Now proceed with a full bootstrap. + + drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_FULL); + drupal_maintenance_theme(); + + drush_errors_on(); + + include_once DRUPAL_ROOT . '/includes/batch.inc'; + drupal_load_updates(); + + update_fix_compatibility(); + + // Change query-strings on css/js files to enforce reload for all users. + _drupal_flush_css_js(); + // Flush the cache of all data for the update status module. + if (db_table_exists('cache_update')) { + cache_clear_all('*', 'cache_update', TRUE); + } + + module_list(TRUE, FALSE, TRUE); +} + +function update_main() { + update_main_prepare(); + + list($pending, $start) = updatedb_status(); + if ($pending) { + // @todo get table header working. + // $headers = array(dt('Module'), dt('ID'), dt('Description')); + drush_print_table($pending); + if (!drush_confirm(dt('Do you wish to run all pending updates?'))) { + return drush_user_abort(); + } + drush_update_batch($start); + } + else { + drush_log(dt("No database updates required"), LogLevel::SUCCESS); + } + + return count($pending); +} + +function _update_batch_command($id) { + update_main_prepare(); + drush_batch_command($id); +} + +/** + * Start the database update batch process. + * + * @param $start + * An array of all the modules and which update to start at. + * @param $redirect + * Path to redirect to when the batch has finished processing. + * @param $url + * URL of the batch processing page (should only be used for separate + * scripts like update.php). + * @param $batch + * Optional parameters to pass into the batch API. + * @param $redirect_callback + * (optional) Specify a function to be called to redirect to the progressive + * processing page. + */ +function drush_update_batch($start) { + // Resolve any update dependencies to determine the actual updates that will + // be run and the order they will be run in. + $updates = update_resolve_dependencies($start); + + // Store the dependencies for each update function in an array which the + // batch API can pass in to the batch operation each time it is called. (We + // do not store the entire update dependency array here because it is + // potentially very large.) + $dependency_map = array(); + foreach ($updates as $function => $update) { + $dependency_map[$function] = !empty($update['reverse_paths']) ? array_keys($update['reverse_paths']) : array(); + } + + $operations = array(); + foreach ($updates as $update) { + if ($update['allowed']) { + // Set the installed version of each module so updates will start at the + // correct place. (The updates are already sorted, so we can simply base + // this on the first one we come across in the above foreach loop.) + if (isset($start[$update['module']])) { + drupal_set_installed_schema_version($update['module'], $update['number'] - 1); + unset($start[$update['module']]); + } + // Add this update function to the batch. + $function = $update['module'] . '_update_' . $update['number']; + $operations[] = array('drush_update_do_one', array($update['module'], $update['number'], $dependency_map[$function])); + } + } + + $batch['operations'] = $operations; + $batch += array( + 'title' => 'Updating', + 'init_message' => 'Starting updates', + 'error_message' => 'An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.', + 'finished' => 'drush_update_finished', + 'file' => 'includes/update.inc', + ); + batch_set($batch); + drush_backend_batch_process('updatedb-batch-process'); +} + + + +function drush_update_finished($success, $results, $operations) { + // Nothing to do here. All caches already cleared. Kept as documentation of 'finished' callback. +} + +/** + * Return a 2 item array with + * - an array where each item is a 3 item associative array describing a pending update. + * - an array listing the first update to run, keyed by module. + */ +function updatedb_status() { + $pending = update_get_update_list(); + + $return = array(); + // Ensure system module's updates run first. + $start['system'] = array(); + + // Print a list of pending updates for this module and get confirmation. + foreach ($pending as $module => $updates) { + if (isset($updates['start'])) { + foreach ($updates['pending'] as $update_id => $description) { + // Strip cruft from front. + $description = str_replace($update_id . ' - ', '', $description); + $return[] = array('module' => ucfirst($module), 'update_id' => $update_id, 'description' => $description); + } + if (isset($updates['start'])) { + $start[$module] = $updates['start']; + } + } + } + return array($return, $start); +} diff --git a/vendor/drush/drush/commands/core/field.drush.inc b/vendor/drush/drush/commands/core/field.drush.inc new file mode 100644 index 0000000000..961e22e8eb --- /dev/null +++ b/vendor/drush/drush/commands/core/field.drush.inc @@ -0,0 +1,392 @@ + 'Create fields and instances. Returns urls for field editing.', + 'core' => array('7+'), + 'arguments' => array( + 'bundle' => 'Content type (for nodes). Name of bundle to attach fields to. Required.', + 'field_spec' => 'Comma delimited triple in the form: field_name,field_type,widget_name. If widget_name is omitted, the default widget will be used. Separate multiple fields by space. If omitted, a wizard will prompt you.' + ), + 'required-arguments' => 1, + 'options' => array( + 'entity_type' => 'Type of entity (e.g. node, user, comment). Defaults to node.', + ), + 'examples' => array( + 'drush field-create article' => 'Define new article fields via interactive prompts.', + 'open `drush field-create article`' => 'Define new article fields and then open field edit form for refinement.', + 'drush field-create article city,text,text_textfield subtitle,text,text_textfield' => 'Create two new fields.' + ), + 'aliases' => array('field:create'), + ); + $items['field-update'] = array( + 'description' => 'Return URL for field editing web page.', + 'core' => array('7+'), + 'arguments' => array( + 'field_name' => 'Name of field that needs updating.', + ), + 'examples' => array( + 'field-update comment_body' => 'Quickly navigate to a field edit web page.', + ), + 'aliases' => array('field:update'), + ); + $items['field-delete'] = array( + 'description' => 'Delete a field and its instances.', + 'core' => array('7+'), + 'arguments' => array( + 'field_name' => 'Name of field to delete.', + ), + 'options' => array( + 'bundle' => 'Only delete the instance attached to this bundle. If omitted, admin can choose to delete one instance or whole field.', + 'entity_type' => 'Disambiguate a particular bundle from identically named bundles. Usually not needed.' + ), + 'examples' => array( + 'field-delete city' => 'Delete the city field and any instances it might have.', + 'field-delete city --bundle=article' => 'Delete the city instance on the article bundle', + ), + 'aliases' => array('field:delete'), + ); + $items['field-clone'] = array( + 'description' => 'Clone a field and all its instances.', + 'core' => array('7+'), + 'arguments' => array( + 'source_field_name' => 'Name of field that will be cloned', + 'target_field_name' => 'Name of new, cloned field.', + ), + 'examples' => array( + 'field-clone tags labels' => 'Copy \'tags\' field into a new field \'labels\' field which has same instances.', + 'open `field-clone tags labels`' => 'Clone field and then open field edit forms for refinement.', + ), + 'aliases' => array('field:clone'), + ); + $items['field-info'] = array( + 'description' => 'View information about fields, field_types, and widgets.', + 'core' => array('7+'), + 'arguments' => array( + 'type' => 'Recognized values: fields, types. If omitted, a choice list appears.', + ), + 'examples' => array( + 'field-info types' => 'Show a table which lists all field types and their available widgets', + ), + 'outputformat' => array( + 'default' => 'table', + 'pipe-format' => 'csv', + 'field-labels' => array( + 'field-name' => 'Field name', + 'type' => 'Field type', + 'bundle' => 'Field bundle', + 'type-name' => 'Type name', + 'widget' => 'Default widget', + 'widgets' => 'Widgets', + ), + 'table-metadata' => array( + 'process-cell' => '_drush_field_info_process_cell', + ), + 'output-data-type' => 'format-table', + 'aliases' => array('field:info'), + ), + ); + return $items; +} + +/** + * Command argument complete callback. + */ +function field_field_create_complete() { + if (drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_FULL)) { + $all = array(); + $info = field_info_bundles(); + foreach ($info as $entity_type => $bundles) { + $all = array_merge($all, array_keys($bundles)); + } + return array('values' => array_unique($bundles)); + } +} + +/** + * Command argument complete callback. + */ +function field_field_update_complete() { + return field_field_complete_field_names(); +} + +/** + * Command argument complete callback. + */ +function field_field_delete_complete() { + return field_field_complete_field_names(); +} + +/** + * Command argument complete callback. + */ +function field_field_clone_complete() { + return field_field_complete_field_names(); +} + +/** + * Command argument complete callback. + */ +function field_field_info_complete() { + return array('values' => array('fields', 'types')); +} + +/** + * List field names for completion. + * + * @return + * Array of available site aliases. + */ +function field_field_complete_field_names() { + if (drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_FULL)) { + $info = field_info_fields(); + return array('values' => array_keys($info)); + } +} + +function drush_field_create($bundle) { + $entity_type = drush_get_option('entity_type', 'node'); + + $args = func_get_args(); + array_shift($args); + if (empty($args)) { + // Just one item in this array for now. + $args[] = drush_field_create_wizard(); + } + + // Iterate over each field spec. + foreach ($args as $string) { + list($name, $type, $widget) = explode(',', $string); + $info = field_info_field($name); + if (empty($info)) { + // Field does not exist already. Create it. + $field = array( + 'field_name' => $name, + 'type' => $type, + ); + drush_op('field_create_field', $field); + } + + // Create the instance. + $instance = array( + 'field_name' => $name, + 'entity_type' => $entity_type, + 'bundle' => $bundle, + ); + if ($widget) { + $instance['widget'] = array('type' => $widget); + } + drush_op('field_create_instance', $instance); + + $urls[] = drush_url(drush_field_ui_bundle_admin_path($entity_type, $bundle) . '/fields/' . $name, array('absolute' => TRUE)); + } + drush_print(implode(' ', $urls)); +} + +// Copy of function _field_ui_bundle_admin_path() since we don't want to load UI module. +function drush_field_ui_bundle_admin_path($entity_type, $bundle_name) { + $bundles = field_info_bundles($entity_type); + $bundle_info = $bundles[$bundle_name]; + if (isset($bundle_info['admin'])) { + return isset($bundle_info['admin']['real path']) ? $bundle_info['admin']['real path'] : $bundle_info['admin']['path']; + } +} + +function drush_field_update($field_name) { + $info = field_info_field($field_name); + foreach ($info['bundles'] as $entity_type => $bundles) { + foreach ($bundles as $bundle) { + $urls[] = drush_url(drush_field_ui_bundle_admin_path($entity_type, $bundle) . '/fields/' . $field_name, array('absolute' => TRUE)); + } + } + drush_print(implode(' ', $urls)); +} + +function drush_field_delete($field_name) { + $info = field_info_field($field_name); + $confirm = TRUE; + + if (!$bundle = drush_get_option('bundle')) { + foreach ($info['bundles'] as $entity_type => $bundles) { + foreach ($bundles as $bundle) { + $all_bundles[] = $bundle; + } + } + if (count($all_bundles) > 1) { + $options = array_merge(array('all' => dt('All bundles')), array_combine($all_bundles, $all_bundles)); + $bundle = drush_choice($options, dt("Choose a particular bundle or 'All bundles'")); + if (!$bundle) { + return drush_user_abort(); + } + $confirm = FALSE; + } + else { + if (!drush_confirm(dt('Do you want to delete the !field_name field?', array('!field_name' => $field_name)))) { + return drush_user_abort(); + } + } + } + + if ($bundle == 'all') { + foreach ($info['bundles'] as $entity_type => $bundles) { + foreach ($bundles as $bundle) { + $instance = field_info_instance($entity_type, $field_name, $bundle); + drush_op('field_delete_instance', $instance); + } + } + } + else { + $entity_type = drush_field_get_entity_from_bundle($bundle); + $instance = field_info_instance($entity_type, $field_name, $bundle); + drush_op('field_delete_instance', $instance); + } + + // If there are no more bundles, delete the field. + $info = field_info_field($field_name); + if (empty($info['bundles'])) { + drush_op('field_delete_field', $field_name); + } +} + +function drush_field_clone($source_field_name, $target_field_name) { + if (!$info = field_info_field($source_field_name)) { + return drush_set_error(dt('!source not found in field list.', array('!source' => $source_field_name))); + } + + unset($info['id']); + $info['field_name'] = $target_field_name; + $target = drush_op('field_create_field', $info); + + foreach ($info['bundles'] as $entity_type => $bundles) { + foreach ($bundles as $bundle) { + $instance = field_info_instance($entity_type, $source_field_name, $bundle); + $instance['field_name'] = $target_field_name; + unset($instance['id']); + $instance['field_id'] = $target['id']; + drush_op('field_create_instance', $instance); + $urls[] = drush_url(drush_field_ui_bundle_admin_path($entity_type, $bundle) . '/fields/' . $target_field_name, array('absolute' => TRUE)); + } + } + + drush_print(implode(' ', $urls)); +} + +function drush_field_info($type = NULL) { + if (!isset($type)) { + // Don't ask in 'pipe' mode -- just default to 'fields'. + if (drush_get_context('DRUSH_PIPE')) { + $type = 'fields'; + } + else { + $type = drush_choice(array_combine(array('types', 'fields'), array('types', 'fields')), dt('Which information do you wish to see?')); + } + } + + $result = array(); + switch ($type) { + case 'fields': + drush_hide_output_fields(array('type-name', 'widget', 'widgets')); + $info = field_info_fields(); + foreach ($info as $field_name => $field) { + $bundle_strs = array(); + foreach ($field['bundles'] as $entity_type => $bundles) { + $bundle_strs += $bundles; + } + $result[$field_name] = array( + 'field-name' => $field_name, + 'type' => $field['type'], + 'bundle' => $bundle_strs, + ); + } + break; + case 'types': + drush_hide_output_fields(array('field-name', 'type', 'bundle')); + $info = field_info_field_types(); + module_load_include('inc', 'field_ui', 'field_ui.admin'); + $widgets = field_info_widget_types(); + foreach ($info as $type_name => $type) { + $widgets = field_ui_widget_type_options($type_name); + $result[$type_name] = array( + 'type-name' => $type_name, + 'widget' => $type['default_widget'], + 'widgets' => $widgets, + ); + } + break; + default: + return drush_set_error('DRUSH_FIELD_INVALID_SELECTION', dt("Argument for drush field-info must be 'fields' or 'types'")); + } + + return $result; +} + +/** + * We need to handle the formatting of cells in table-format + * output specially. In 'types' output, the output data is a simple + * associative array of machine names => human-readable names. + * We choose to show the machine names. In 'fields' output, the + * output data is a list of entity types, each of which contains a list + * of bundles. We comma-separate the bundles, and space-separate + * the entities. + */ +function _drush_field_info_process_cell($data, $metadata) { + $first = reset($data); + if (is_array($first)) { + foreach($data as $entity => $bundles) { + $list[] = drush_format($bundles, array(), 'csv'); + } + return drush_format($list, array(), 'list'); + } + return drush_format(array_keys($data), array(), 'csv'); +} + +/** + * Prompt user enough to create basic field and instance. + * + * @return array $field_spec + * An array of brief field specifications. + */ +function drush_field_create_wizard() { + $specs[] = drush_prompt(dt('Field name')); + module_load_include('inc', 'field_ui', 'field_ui.admin'); + $types = field_ui_field_type_options(); + $field_type = drush_choice($types, dt('Choose a field type')); + $specs[] = $field_type; + $widgets = field_ui_widget_type_options($field_type); + $specs[] = drush_choice($widgets, dt('Choose a widget')); + return implode(',', $specs); +} + +function drush_field_get_entity_from_bundle($bundle) { + if (drush_get_option('entity_type')) { + return drush_get_option('entity_type'); + } + else { + $info = field_info_bundles(); + foreach ($info as $entity_type => $bundles) { + if (isset($bundles[$bundle])) { + return $entity_type; + } + } + } +} diff --git a/vendor/drush/drush/commands/core/help.drush.inc b/vendor/drush/drush/commands/core/help.drush.inc new file mode 100644 index 0000000000..55dbc0ba8d --- /dev/null +++ b/vendor/drush/drush/commands/core/help.drush.inc @@ -0,0 +1,202 @@ +' + * + * @param + * A string with the help section (prepend with 'drush:') + * + * @return + * A string with the help text for your command. + */ +function help_drush_help($section) { + switch ($section) { + case 'drush:help': + return dt("Drush provides an extensive help system that describes both drush commands and topics of general interest. Use `drush help --filter` to present a list of command categories to view, and `drush topic` for a list of topics that go more in-depth on how to use and extend drush."); + } +} + +/** + * Implementation of hook_drush_command(). + * + * In this hook, you specify which commands your + * drush module makes available, what it does and + * description. + * + * Notice how this structure closely resembles how + * you define menu hooks. + * + * @return + * An associative array describing your command(s). + */ +function help_drush_command() { + $items = array(); + + $items['help'] = array( + 'description' => 'Print this help message. See `drush help help` for more options.', + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'allow-additional-options' => array('helpsingle'), + 'options' => array( + 'sort' => 'Sort commands in alphabetical order. Drush waits for full bootstrap before printing any commands when this option is used.', + 'filter' => array( + 'description' => 'Restrict command list to those commands defined in the specified file. Omit value to choose from a list of names.', + 'example-value' => 'category', + 'value' => 'optional', + ), + ), + 'arguments' => array( + 'command' => 'A command name, or command alias.', + ), + 'examples' => array( + 'drush' => 'List all commands.', + 'drush --filter=devel_generate' => 'Show only commands defined in devel_generate.drush.inc', + 'drush help pm-download' => 'Show help for one command.', + 'drush help dl' => 'Show help for one command using an alias.', + 'drush help --format=html' => 'Show an HTML page detailing all available commands.', + 'drush help --format=json' => 'All available comamnds, in a machine parseable format.', + ), + // Use output format system for all formats except the default presentation. + 'outputformat' => array( + 'default' => 'table', + 'field-labels' => array('name' => 'Name', 'description' => 'Description'), + 'output-data-type' => 'format-table', + ), + 'topics' => array('docs-readme'), + ); + return $items; +} + + +/** + * Command argument complete callback. + * + * For now, this can't move to helpsingle since help command is the entry point for both. + * + * @return + * Array of available command names. + */ +function core_help_complete() { + return array('values' => array_keys(drush_get_commands())); +} + +/** + * Command callback for help command. This is the default command, when none + * other has been specified. + */ +function drush_core_help($name = '') { + $format = drush_get_option('format', 'table'); + if ($name) { + // helpsingle command builds output when a command is specified. + $options = drush_redispatch_get_options(); + if ($name != 'help') { + unset($options['help']); + } + $return = drush_invoke_process('@self' ,'helpsingle', func_get_args(), $options); + drush_backend_set_result($return['object']); + return; + } + + // For speed, only bootstrap up to DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION. + drush_bootstrap_max(); + drush_get_commands(true); + $implemented = drush_get_commands(); + ksort($implemented); + $command_categories = drush_commands_categorize($implemented); + if ($format != 'table') { + return $command_categories; + } + else { + $visible = drush_help_visible($command_categories); + + // If the user specified --filter w/out a value, then + // present a choice list of help categories. + if (drush_get_option('filter', FALSE) === TRUE) { + $help_categories = array(); + foreach ($command_categories as $key => $info) { + $description = $info['title']; + if (array_key_exists('summary', $info)) { + $description .= ": " . $info['summary']; + } + $help_categories[$key] = $description; + } + $result = drush_choice($help_categories, 'Select a help category:'); + if (!$result) { + return drush_user_abort(); + } + drush_set_option('filter', $result); + } + // Filter out categories that the user does not want to see + $filter_category = drush_get_option('filter'); + if (!empty($filter_category) && ($filter_category !== TRUE)) { + if (!array_key_exists($filter_category, $command_categories)) { + return drush_set_error('DRUSH_NO_CATEGORY', dt("The specified command category !filter does not exist.", array('!filter' => $filter_category))); + } + $command_categories = array($filter_category => $command_categories[$filter_category]); + } + + // Make a fake command section to hold the global options, then print it. + $global_options_help = drush_global_options_command(TRUE); + if (!drush_get_option('filter')) { + drush_print_help($global_options_help); + } + drush_help_listing_print($command_categories); + drush_backend_set_result($command_categories); + return; + } +} + +// Uncategorize the list of commands. Hiddens have been removed and +// filtering performed. +function drush_help_visible($command_categories) { + $all = array(); + foreach ($command_categories as $category => $info) { + $all = array_merge($all, $info['commands']); + } + return $all; +} + +/** + * Print CLI table listing all commands. + */ +function drush_help_listing_print($command_categories) { + $all_commands = array(); + foreach ($command_categories as $key => $info) { + // Get the commands in this category. + $commands = $info['commands']; + + // Build rows for drush_print_table(). + $rows = array(); + foreach($commands as $cmd => $command) { + $name = $command['aliases'] ? $cmd . ' (' . implode(', ', $command['aliases']) . ')': $cmd; + $rows[$cmd] = array('name' => $name, 'description' => $command['description']); + } + drush_print($info['title'] . ": (" . $key . ")"); + drush_print_table($rows, FALSE, array('name' => 20)); + } +} + +/** + * Build a fake command for the purposes of showing examples and options. + */ +function drush_global_options_command($brief = FALSE) { + $global_options_help = array( + 'description' => 'Execute a drush command. Run `drush help [command]` to view command-specific help. Run `drush topic` to read even more documentation.', + 'sections' => array( + 'options' => 'Global options (see `drush topic core-global-options` for the full list)', + ), + 'options' => drush_get_global_options($brief), + 'examples' => array( + 'drush dl cck zen' => 'Download CCK module and Zen theme.', + 'drush --uri=http://example.com status' => 'Show status command for the example.com multi-site.', + ), + '#brief' => TRUE, + ); + $global_options_help += drush_command_defaults('global-options', 'global_options', __FILE__); + drush_command_invoke_all_ref('drush_help_alter', $global_options_help); + ksort($global_options_help['options']); + + return $global_options_help; +} diff --git a/vendor/drush/drush/commands/core/helpsingle.drush.inc b/vendor/drush/drush/commands/core/helpsingle.drush.inc new file mode 100644 index 0000000000..7c6bee4efc --- /dev/null +++ b/vendor/drush/drush/commands/core/helpsingle.drush.inc @@ -0,0 +1,277 @@ + 'Print help for a single command', + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'allow-additional-options' => TRUE, + 'hidden' => TRUE, + 'arguments' => array( + 'command' => 'A command name, or command alias.', + ), + 'examples' => array( + 'drush help pm-download' => 'Show help for one command.', + 'drush help dl' => 'Show help for one command using an alias.', + ), + 'topics' => array('docs-readme'), + ); + return $items; +} + +/** + * Command callback. Show help for a single command. + */ +function drush_core_helpsingle($commandstring) { + // First check and see if the command can already be found. + $commands = drush_get_commands(); + if (!array_key_exists($commandstring, $commands)) { + // If the command cannot be found, then bootstrap so that + // additional commands will be brought in. + // TODO: We need to do a full bootstrap in order to find module service + // commands. We only need to do this for Drupal 8, though; 7 and earlier + // can stop at DRUSH_BOOTSTRAP_DRUPAL_SITE. Perhaps we could use command + // caching to avoid bootstrapping, if we have collected the commands for + // this site once already. + drush_bootstrap_max(); + $commands = drush_get_commands(); + } + if (array_key_exists($commandstring, $commands)) { + $command = $commands[$commandstring]; + + annotationcommand_adapter_add_hook_options($command); + + drush_print_help($command); + return TRUE; + } + $shell_aliases = drush_get_context('shell-aliases', array()); + if (array_key_exists($commandstring, $shell_aliases)) { + $msg = dt("'@alias-name' is a shell alias. Its value is: !name. See `drush topic docs-shell-aliases` and `drush shell-alias` for more information.", array('@alias-name' => $commandstring, '!name' => $shell_aliases[$commandstring])); + drush_log($msg, 'ok'); + return TRUE; + } + return drush_set_error('DRUSH_COMMAND_NOT_FOUND', dt('Invalid command !command.', array('!command' => $commandstring))); +} + +/** + * Print the help for a single command to the screen. + * + * @param array $command + * A fully loaded $command array. + */ +function drush_print_help($command) { + _drush_help_merge_subcommand_information($command); + + if (!$help = drush_command_invoke_all('drush_help', 'drush:'. $command['command'])) { + $help = array($command['description']); + } + + if ($command['strict-option-handling']) { + $command['topics'][] = 'docs-strict-options'; + } + + // Give commandfiles an opportunity to add examples and options to the command. + drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_SITE); + drush_engine_add_help_topics($command); + drush_command_invoke_all_ref('drush_help_alter', $command); + + drush_print(wordwrap(implode("\n", $help), drush_get_context('DRUSH_COLUMNS', 80))); + drush_print(); + + $global_options = drush_get_global_options(); + foreach ($command['global-options'] as $global_option) { + $command['options'][$global_option] = $global_options[$global_option]; + } + + // Sort command options. + uksort($command['options'], '_drush_help_sort_command_options'); + + // Print command sections help. + foreach ($command['sections'] as $key => $value) { + if (!empty($command[$key])) { + $rows = drush_format_help_section($command, $key); + if ($rows) { + drush_print(dt($value) . ':'); + drush_print_table($rows, FALSE, array('label' => 40)); + unset($rows); + drush_print(); + } + } + } + + // Append aliases if any. + if ($command['aliases']) { + drush_print(dt("Aliases: ") . implode(', ', $command['aliases'])); + } +} + +/** + * Sort command options alphabetically. Engine options at the end. + */ +function _drush_help_sort_command_options($a, $b) { + $engine_a = strpos($a, '='); + $engine_b = strpos($b, '='); + if ($engine_a && !$engine_b) { + return 1; + } + else if (!$engine_a && $engine_b) { + return -1; + } + elseif ($engine_a && $engine_b) { + if (substr($a, 0, $engine_a) == substr($b, 0, $engine_b)) { + return 0; + } + } + return ($a < $b) ? -1 : 1; +} + +/** + * Check to see if the specified command contains an 'allow-additional-options' + * record. If it does, find the additional options that are allowed, and + * add in the help text for the options of all of the sub-commands. + */ +function _drush_help_merge_subcommand_information(&$command) { + // 'allow-additional-options' will either be FALSE (default), + // TRUE ("allow anything"), or an array that lists subcommands + // that are or may be called via drush_invoke by this command. + if (is_array($command['allow-additional-options'])) { + $implemented = drush_get_commands(); + foreach ($command['allow-additional-options'] as $subcommand_name) { + if (array_key_exists($subcommand_name, $implemented)) { + $command['options'] += $implemented[$subcommand_name]['options']; + $command['sub-options'] = array_merge_recursive($command['sub-options'], $implemented[$subcommand_name]['sub-options']); + if (empty($command['arguments'])) { + $command['arguments'] = $implemented[$subcommand_name]['arguments']; + } + $command['topics'] = array_merge($command['topics'], $implemented[$subcommand_name]['topics']); + } + } + } +} + +/** + * Format one named help section from a command record + * + * @param $command + * A command record with help information + * @param $section + * The name of the section to format ('options', 'topic', etc.) + * @returns array + * Formatted rows, suitable for printing via drush_print_table. The returned + * array can be empty. + */ +function drush_format_help_section($command, $section) { + $rows = array(); + $formatter = (function_exists('drush_help_section_formatter_' . $section)) ? 'drush_help_section_formatter_' . $section : 'drush_help_section_default_formatter'; + foreach ($command[$section] as $name => $help_attributes) { + if (!is_array($help_attributes)) { + $help_attributes = array('description' => $help_attributes); + } + $help_attributes['label'] = $name; + call_user_func_array($formatter, array($command, &$help_attributes)); + if (empty($help_attributes['hidden'])) { + $rows[] = array('label' => $help_attributes['label'], 'description' => $help_attributes['description']); + // Process the subsections too, if any + if (!empty($command['sub-' . $section]) && array_key_exists($name, $command['sub-' . $section])) { + $rows = array_merge($rows, _drush_format_help_subsection($command, $section, $name, $formatter)); + } + } + } + return $rows; +} + +/** + * Format one named portion of a subsection from a command record. + * Subsections allow related parts of a help record to be grouped + * together. For example, in the 'options' section, sub-options that + * are related to a particular primary option are stored in a 'sub-options' + * section whose name == the name of the primary option. + * + * @param $command + * A command record with help information + * @param $section + * The name of the section to format ('options', 'topic', etc.) + * @param $subsection + * The name of the subsection (e.g. the name of the primary option) + * @param $formatter + * The name of a function to use to format the rows of the subsection + * @param $prefix + * Characters to prefix to the front of the label (for indentation) + * @returns array + * Formatted rows, suitable for printing via drush_print_table. + */ +function _drush_format_help_subsection($command, $section, $subsection, $formatter, $prefix = ' ') { + $rows = array(); + foreach ($command['sub-' . $section][$subsection] as $name => $help_attributes) { + if (!is_array($help_attributes)) { + $help_attributes = array('description' => $help_attributes); + } + $help_attributes['label'] = $name; + call_user_func_array($formatter, array($command, &$help_attributes)); + if (!array_key_exists('hidden', $help_attributes)) { + $rows[] = array('label' => $prefix . $help_attributes['label'], 'description' => $help_attributes['description']); + // Process the subsections too, if any + if (!empty($command['sub-' . $section]) && array_key_exists($name, $command['sub-' . $section])) { + $rows = array_merge($rows, _drush_format_help_subsection($command, $section, $name, $formatter, $prefix . ' ')); + } + } + } + return $rows; +} + +/** + * The options section formatter. Adds a "--" in front of each + * item label. Also handles short-form and example-value + * components in the help attributes. + */ +function drush_help_section_formatter_options($command, &$help_attributes) { + if ($help_attributes['label'][0] == '-') { + drush_log(dt("Option '!option' of command !command should instead be declared as '!fixed'", array('!option' => $help_attributes['label'], '!command' => $command['command'], '!fixed' => preg_replace('/^--*/', '', $help_attributes['label']))), 'debug'); + } + else { + $help_attributes['label'] = '--' . $help_attributes['label']; + } + if (!empty($help_attributes['required'])) { + $help_attributes['description'] .= " " . dt("Required."); + } + + $prefix = '<'; + $suffix = '>'; + if (array_key_exists('example-value', $help_attributes)) { + if (isset($help_attributes['value']) && $help_attributes['value'] == 'optional') { + $prefix = '['; + $suffix = ']'; + } + $help_attributes['label'] .= '=' . $prefix . $help_attributes['example-value'] . $suffix; + + if (array_key_exists('short-form', $help_attributes)) { + $help_attributes['short-form'] .= " $prefix" . $help_attributes['example-value'] . $suffix; + } + } + if (array_key_exists('short-form', $help_attributes)) { + $help_attributes['label'] = '-' . $help_attributes['short-form'] . ', ' . $help_attributes['label']; + } + drush_help_section_default_formatter($command, $help_attributes); +} + +/** + * The default section formatter. Replaces '[command]' with the + * command name. + */ +function drush_help_section_default_formatter($command, &$help_attributes) { + // '[command]' is a token representing the current command. @see pm_drush_engine_version_control(). + $help_attributes['label'] = str_replace('[command]', $command['command'], $help_attributes['label']); +} diff --git a/vendor/drush/drush/commands/core/image.drush.inc b/vendor/drush/drush/commands/core/image.drush.inc new file mode 100644 index 0000000000..ddfbf04bcd --- /dev/null +++ b/vendor/drush/drush/commands/core/image.drush.inc @@ -0,0 +1,130 @@ + 'Flush all derived images for a given style.', + 'core' => array('7+'), + 'arguments' => array( + 'style' => 'An image style machine name. If not provided, user may choose from a list of names.', + ), + 'options' => array( + 'all' => 'Flush all derived images', + ), + 'examples' => array( + 'drush image-flush' => 'Pick an image style and then delete its images.', + 'drush image-flush thumbnail' => 'Delete all thumbnail images.', + 'drush image-flush --all' => 'Flush all derived images. They will be regenerated on the fly.', + ), + 'aliases' => array('if', 'image:flush'), + ); + $items['image-derive'] = array( + 'description' => 'Create an image derivative.', + 'core' => array('7+'), + 'drupal dependencies' => array('image'), + 'arguments' => array( + 'style' => 'An image style machine name.', + 'source' => 'Path to a source image. Optionally prepend stream wrapper scheme.', + ), + 'required arguments' => TRUE, + 'options' => array(), + 'examples' => array( + 'drush image-derive thumbnail themes/bartik/logo.png' => 'Save thumbnail sized derivative of logo image.', + ), + 'aliases' => array('id', 'image:derive'), + ); + return $items; +} + +/** + * Implements hook_drush_help_alter(). + */ +function image_drush_help_alter(&$command) { + // Drupal 8+ customizations. + if ($command['command'] == 'image-derive' && drush_drupal_major_version() >= 8) { + unset($command['examples']); + $command['examples']['drush image-derive thumbnail core/themes/bartik/logo.png'] = 'Save thumbnail sized derivative of logo image.'; + } +} + +/** + * Command argument complete callback. + * + * @return + * Array of available configuration files for editing. + */ +function image_image_flush_complete() { + drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_FULL); + drush_include_engine('drupal', 'image'); + return array('values' => array_keys(drush_image_styles())); +} + +function drush_image_flush_pre_validate($style_name = NULL) { + drush_include_engine('drupal', 'image'); + if (!empty($style_name) && !$style = drush_image_style_load($style_name)) { + return drush_set_error(dt('Image style !style not recognized.', array('!style' => $style_name))); + } +} + +function drush_image_flush($style_name = NULL) { + drush_include_engine('drupal', 'image'); + if (drush_get_option('all')) { + $style_name = 'all'; + } + + if (empty($style_name)) { + $styles = array_keys(drush_image_styles()); + $choices = array_combine($styles, $styles); + $choices = array_merge(array('all' => 'all'), $choices); + $style_name = drush_choice($choices, dt("Choose a style to flush.")); + if ($style_name === FALSE) { + return drush_user_abort(); + } + } + + if ($style_name == 'all') { + foreach (drush_image_styles() as $style_name => $style) { + drush_image_flush_single($style_name); + } + drush_log(dt('All image styles flushed'), LogLevel::SUCCESS); + } + else { + drush_image_flush_single($style_name); + } +} + +function drush_image_derive_validate($style_name, $source) { + drush_include_engine('drupal', 'image'); + if (!$style = drush_image_style_load($style_name)) { + return drush_set_error(dt('Image style !style not recognized.', array('!style' => $style_name))); + } + + if (!file_exists($source)) { + return drush_set_error(dt('Source file not found - !file.', array('!file' => $source))); + } +} + +/* + * Command callback. Create an image derivative. + * + * @param string $style_name + * The name of an image style. + * + * @param string $source + * The path to a source image, relative to Drupal root. + */ +function drush_image_derive($style_name, $source) { + drush_include_engine('drupal', 'image'); + return _drush_image_derive($style_name, $source); +} diff --git a/vendor/drush/drush/commands/core/init.drush.inc b/vendor/drush/drush/commands/core/init.drush.inc new file mode 100644 index 0000000000..d7c5da8d01 --- /dev/null +++ b/vendor/drush/drush/commands/core/init.drush.inc @@ -0,0 +1,174 @@ + 'Enrich the bash startup file with completion and aliases. Copy .drushrc file to ~/.drush', + 'aliases' => array('init', 'core:init'), + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'package' => 'core', + 'global-options' => array('editor', 'bg'), + 'options' => array( + 'edit' => 'Open the new config file in an editor.', + 'add-path' => "Always add Drush to the \$PATH in the user's .bashrc file, even if it is already in the \$PATH. Use --no-add-path to skip updating .bashrc with the Drush \$PATH. Default is to update .bashrc only if Drush is not already in the \$PATH.", + ), + 'examples' => array( + 'drush core-init --edit' => 'Enrich Bash and open drush config file in editor.', + 'drush core-init --edit --bg' => 'Return to shell prompt as soon as the editor window opens.', + ), + ); + return $items; +} + +/** + * Initialize local Drush configuration + */ +function drush_init_core_init() { + $home = drush_server_home(); + $drush_config_dir = $home . "/.drush"; + $drush_config_file = $drush_config_dir . "/drushrc.php"; + $drush_bashrc = $drush_config_dir . "/drush.bashrc"; + $drush_prompt = $drush_config_dir . "/drush.prompt.sh"; + $drush_complete = $drush_config_dir . "/drush.complete.sh"; + $examples_dir = DRUSH_BASE_PATH . "/examples"; + $example_configuration = $examples_dir . "/example.drushrc.php"; + $example_bashrc = $examples_dir . "/example.bashrc"; + $example_prompt = $examples_dir . "/example.prompt.sh"; + $example_complete = DRUSH_BASE_PATH . "/drush.complete.sh"; + $bashrc_additions = array(); + + // Create a ~/.drush directory if it does not yet exist + if (!is_dir($drush_config_dir)) { + drush_mkdir($drush_config_dir); + } + + // If there is no ~/.drush/drushrc.php, then copy the + // example Drush configuration file here + if (!is_file($drush_config_file)) { + copy($example_configuration, $drush_config_file); + drush_log(dt("Copied example Drush configuration file to !path", array('!path' => $drush_config_file)), LogLevel::OK); + } + + // If Drush is not in the $PATH, then figure out which + // path to add so that Drush can be found globally. + $add_path = drush_get_option('add-path', NULL); + if ((!drush_which("drush") || $add_path) && ($add_path !== FALSE)) { + $drush_path = drush_find_path_to_drush($home); + $drush_path = preg_replace("%^" . preg_quote($home) . "/%", '$HOME/', $drush_path); + + $bashrc_additions["%$drush_path%"] = "\n# Path to Drush, added by 'drush init'.\nexport PATH=\"\$PATH:$drush_path\"\n\n"; + } + + // If there is no ~/.drush/drush.bashrc file, then copy + // the example bashrc file there + if (!is_file($drush_bashrc)) { + copy($example_bashrc, $drush_bashrc); + $pattern = basename($drush_bashrc); + $bashrc_additions["%$pattern%"] = "\n# Include Drush bash customizations.". drush_bash_addition($drush_bashrc); + drush_log(dt("Copied example Drush bash configuration file to !path", array('!path' => $drush_bashrc)), LogLevel::OK); + } + + // If there is no ~/.drush/drush.complete.sh file, then copy it there + if (!is_file($drush_complete)) { + copy($example_complete, $drush_complete); + $pattern = basename($drush_complete); + $bashrc_additions["%$pattern%"] = "\n# Include Drush completion.\n". drush_bash_addition($drush_complete); + drush_log(dt("Copied Drush completion file to !path", array('!path' => $drush_complete)), LogLevel::OK); + } + + // If there is no ~/.drush/drush.prompt.sh file, then copy + // the example prompt.sh file here + if (!is_file($drush_prompt)) { + copy($example_prompt, $drush_prompt); + $pattern = basename($drush_prompt); + $bashrc_additions["%$pattern%"] = "\n# Include Drush prompt customizations.\n". drush_bash_addition($drush_prompt); + drush_log(dt("Copied example Drush prompt file to !path", array('!path' => $drush_prompt)), LogLevel::OK); + } + + // Decide whether we want to add our Bash commands to + // ~/.bashrc or ~/.bash_profile + $bashrc = drush_init_find_bashrc($home); + + // Modify the user's bashrc file, adding our customizations. + $bashrc_contents = ""; + if (file_exists($bashrc)) { + $bashrc_contents = file_get_contents($bashrc); + } + $new_bashrc_contents = $bashrc_contents; + foreach ($bashrc_additions as $pattern => $addition) { + // Only put in the addition if the pattern does not already + // exist in the bashrc file. + if (!preg_match($pattern, $new_bashrc_contents)) { + $new_bashrc_contents = $new_bashrc_contents . $addition; + } + } + if ($new_bashrc_contents != $bashrc_contents) { + if (drush_confirm(dt(implode('', $bashrc_additions) . "Append the above code to !file?", array('!file' => $bashrc)))) { + file_put_contents($bashrc, "\n\n". $new_bashrc_contents); + drush_log(dt("Updated bash configuration file !path", array('!path' => $bashrc)), LogLevel::OK); + drush_log(dt("Start a new shell in order to experience the improvements (e.g. `bash`)."), LogLevel::OK); + if (drush_get_option('edit')) { + $exec = drush_get_editor(); + drush_shell_exec_interactive($exec, $drush_config_file, $drush_config_file); + } + } + else { + return drush_user_abort(); + } + } + else { + drush_log(dt('No code added to !path', array('!path' => $bashrc)), LogLevel::OK); + } +} + +/** + * Determine which .bashrc file is best to use on this platform. + */ +function drush_init_find_bashrc($home) { + return $home . "/.bashrc"; +} + +/** + * Determine where Drush is located, so that we can add + * that location to the $PATH + */ +function drush_find_path_to_drush($home) { + // First test: is Drush inside a vendor directory? + // Does vendor/bin exist? If so, use that. We do + // not have a good way to locate the 'bin' directory + // if it has been relocated in the composer.json config + // section. + if ($vendor_pos = strpos(DRUSH_BASE_PATH, "/vendor/")) { + $vendor_dir = substr(DRUSH_BASE_PATH, 0, $vendor_pos + 7); + $vendor_bin = $vendor_dir . '/bin'; + if (is_dir($vendor_bin)) { + return $vendor_bin; + } + } + + // Fallback is to use the directory that Drush is in. + return DRUSH_BASE_PATH; +} + +function drush_bash_addition($file) { + return << 'Checks for available translation updates.', + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL, + 'aliases' => array('locale:check'), + ]; + $items['locale-update'] = [ + 'description' => 'Updates the available translations.', + 'options' => [ + 'langcodes' => 'A comma-separated list of language codes to update. If omitted, all translations will be updated.' + ], + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL, + 'aliases' => array('locale:update'), + ]; + // @todo Implement proper export and import commands. + return $items; +} + +/** + * Checks for available translation updates. + * + * @see \Drupal\locale\Controller\LocaleController::checkTranslation() + * + * @todo This can be simplified once https://www.drupal.org/node/2631584 lands + * in Drupal core. + */ +function drush_locale_check() { + \Drupal::moduleHandler()->loadInclude('locale', 'inc', 'locale.compare'); + + // Check translation status of all translatable project in all languages. + // First we clear the cached list of projects. Although not strictly + // necessary, this is helpful in case the project list is out of sync. + locale_translation_flush_projects(); + locale_translation_check_projects(); + + // Execute a batch if required. A batch is only used when remote files + // are checked. + if (batch_get()) { + drush_backend_batch_process(); + } +} + +/** + * Imports the available translation updates. + * + * @see TranslationStatusForm::buildForm() + * @see TranslationStatusForm::prepareUpdateData() + * @see TranslationStatusForm::submitForm() + * + * @todo This can be simplified once https://www.drupal.org/node/2631584 lands + * in Drupal core. + */ +function drush_locale_update() { + $module_handler = \Drupal::moduleHandler(); + $module_handler->loadInclude('locale', 'fetch.inc'); + $module_handler->loadInclude('locale', 'bulk.inc'); + + $langcodes = []; + foreach (locale_translation_get_status() as $project_id => $project) { + foreach ($project as $langcode => $project_info) { + if (!empty($project_info->type)) { + $langcodes[] = $langcode; + } + } + } + + if ($passed_langcodes = drush_get_option('langcodes')) { + $langcodes = array_intersect($langcodes, explode(',', $passed_langcodes)); + // @todo Not selecting any language code in the user interface results in + // all translations being updated, so we mimick that behavior here. + } + // Deduplicate the list of langcodes since each project may have added the + // same language several times. + $langcodes = array_unique($langcodes); + + // @todo Restricting by projects is not possible in the user interface and is + // broken when attempting to do it in a hook_form_alter() implementation so + // we do not allow for it here either. + $projects = []; + + // Set the translation import options. This determines if existing + // translations will be overwritten by imported strings. + $options = _locale_translation_default_update_options(); + + // If the status was updated recently we can immediately start fetching the + // translation updates. If the status is expired we clear it an run a batch to + // update the status and then fetch the translation updates. + $last_checked = \Drupal::state()->get('locale.translation_last_checked'); + if ($last_checked < REQUEST_TIME - LOCALE_TRANSLATION_STATUS_TTL) { + locale_translation_clear_status(); + $batch = locale_translation_batch_update_build(array(), $langcodes, $options); + batch_set($batch); + } + else { + // Set a batch to download and import translations. + $batch = locale_translation_batch_fetch_build($projects, $langcodes, $options); + batch_set($batch); + // Set a batch to update configuration as well. + if ($batch = locale_config_batch_update_components($options, $langcodes)) { + batch_set($batch); + } + } + + drush_backend_batch_process(); +} diff --git a/vendor/drush/drush/commands/core/notify.drush.inc b/vendor/drush/drush/commands/core/notify.drush.inc new file mode 100644 index 0000000000..9994d13f95 --- /dev/null +++ b/vendor/drush/drush/commands/core/notify.drush.inc @@ -0,0 +1,212 @@ + 'Use system notifications to signal command completion. If set to a number, commands that finish in fewer seconds will not trigger a notification.', + 'example-value' => 60, + 'never-propagate' => TRUE, + ); + $command['options']['notify-audio'] = array( + 'description' => 'Trigger an audio alert to signal command completion. If set to a number, commands that finish in fewer seconds will not trigger a notification.', + 'example-value' => 60, + 'never-propagate' => TRUE, + ); + $command['sub-options']['notify']['notify-cmd'] = array( + 'description' => 'Specify the shell command to trigger the notification.', + 'never-propagate' => TRUE, + ); + $command['sub-options']['notify']['notify-cmd-audio'] = array( + 'description' => 'Specify the shell command to trigger the audio notification.', + 'never-propagate' => TRUE, + ); + } + } +} + +/** + * Implements hook_drush_help(). + */ +function notify_drush_help($section) { + switch ($section) { + case 'notify:cache-clear': + return dt('Caches have been cleared.'); + case 'notify:site-install:error': + return dt('Failed on site installation'); + } +} + +/** + * Shutdown function to signal on errors. + */ +function drush_notify_shutdown() { + $cmd = drush_get_command(); + + if (empty($cmd['command'])) { + return; + } + + // pm-download handles its own notification. + if ($cmd['command'] != 'pm-download' && drush_notify_allowed($cmd['command'])) { + $msg = dt("Command '!command' completed.", array('!command' => $cmd['command'])); + drush_notify_send(drush_notify_command_message($cmd['command'], $msg)); + } + + if (drush_get_option('notify', FALSE) && drush_get_error()) { + // If the only error is that notify failed, do not try to notify again. + $log = drush_get_error_log(); + if (count($log) == 1 && array_key_exists('NOTIFY_COMMAND_NOT_FOUND', $log)) { + return; + } + + // Send an alert that the command failed. + if (drush_notify_allowed($cmd['command'])) { + $msg = dt("Command '!command' failed.", array('!command' => $cmd['command'])); + drush_notify_send(drush_notify_command_message($cmd['command'] . ':error', $msg)); + } + } +} + +/** + * Determine the message to send on command completion. + * + * @param string $command + * Name of the Drush command for which we check message overrides. + * @param string $default + * (Default: NULL) Default message to use if there are not notification message overrides. + * + * @return string + * Message to use for notification. + */ +function drush_notify_command_message($command, $default = NULL) { + if ($msg = drush_command_invoke_all('drush_help', 'notify:' . $command)) { + $msg = implode("\n", $msg); + } + else { + $msg = $default ? $default : $msg = $command . ': No news is good news.'; + } + + return $msg; +} + +/** + * Prepares and dispatches notifications to delivery mechanisms. + * + * You may avoid routing a message to secondary messaging mechanisms (e.g. audio), + * by direct use of the delivery functions. + * + * @param string $msg + * Message to send via notification. + */ +function drush_notify_send($msg) { + drush_notify_send_text($msg); + if (drush_get_option('notify-audio', FALSE)) { + drush_notify_send_audio($msg); + } +} + +/** + * Send text-based system notification. + * + * This is the automatic, default behavior. It is intended for use with tools + * such as libnotify in Linux and Notification Center on OSX. + * + * @param string $msg + * Message text for delivery. + * + * @return bool + * TRUE on success, FALSE on failure + */ +function drush_notify_send_text($msg) { + $override = drush_get_option('notify-cmd', FALSE); + + if (!empty($override)) { + $cmd = $override; + } + else { + switch (PHP_OS) { + case 'Darwin': + $cmd = 'terminal-notifier -message %s -title Drush'; + $error_message = dt('terminal-notifier command failed. Please install it from https://github.com/alloy/terminal-notifier.'); + break; + case 'Linux': + default: + $icon = drush_normalize_path(DRUSH_BASE_PATH . '/drush_logo-black.png'); + $cmd = "notify-send %s -i $icon"; + $error_message = dt('notify-send command failed. Please install it as per http://coderstalk.blogspot.com/2010/02/how-to-install-notify-send-in-ubuntu.html.'); + break; + } + } + + if (!drush_shell_exec($cmd, $msg)) { + return drush_set_error('NOTIFY_COMMAND_NOT_FOUND', $error_message . ' ' . dt('Or you may specify an alternate command to run by specifying --notify-cmd=')); + } + + return TRUE; +} + +/** + * Send an audio-based system notification. + * + * This function is only automatically invoked with the additional use of the + * --notify-audio flag or configuration state. + * + * @param $msg + * Message for audio recital. + * + * @return bool + * TRUE on success, FALSE on failure + */ +function drush_notify_send_audio($msg) { + $override = drush_get_option('notify-cmd-audio', FALSE); + + if (!empty($override)) { + $cmd = $override; + } + else { + switch (PHP_OS) { + case 'Darwin': + $cmd = 'say %s'; + break; + case 'Linux': + default: + $cmd = drush_get_option('notify-cmd-audio', 'spd-say') . ' %s'; + } + } + + if (!drush_shell_exec($cmd, $msg)) { + return drush_set_error('NOTIFY_COMMAND_NOT_FOUND', dt('The third party notification utility failed.')); + } +} + +/** + * Identify if the given Drush request should trigger a notification. + * + * @param $command + * Name of the command. + * + * @return + * Boolean + */ +function drush_notify_allowed($command) { + $notify = drush_get_option(array('notify', 'notify-audio'), FALSE); + $execution = time() - $_SERVER['REQUEST_TIME']; + + return ($notify === TRUE || + (is_numeric($notify) && $notify > 0 && $execution > $notify)); +} + diff --git a/vendor/drush/drush/commands/core/outputformat.drush.inc b/vendor/drush/drush/commands/core/outputformat.drush.inc new file mode 100644 index 0000000000..7cc84e93df --- /dev/null +++ b/vendor/drush/drush/commands/core/outputformat.drush.inc @@ -0,0 +1,475 @@ + 'Output formatting options selection and use.', + 'topic' => 'docs-output-formats', + 'topic-file' => 'docs/output-formats.md', + 'combine-help' => TRUE, + 'option' => 'format', + 'options' => array( + 'format' => array( + 'description' => 'Select output format.', + 'example-value' => 'json', + ), + 'fields' => array( + 'description' => 'Fields to output.', + 'example-value' => 'field1,field2', + 'value' => 'required', + 'list' => TRUE, + ), + 'list-separator' => array( + 'description' => 'Specify how elements in a list should be separated. In lists of lists, this applies to the elements in the inner lists.', + 'hidden' => TRUE, + ), + 'line-separator' => array( + 'description' => 'In nested lists of lists, specify how the outer lists ("lines") should be separated.', + 'hidden' => TRUE, + ), + 'field-labels' => array( + 'description' => 'Add field labels before first line of data. Default is on; use --no-field-labels to disable.', + 'default' => '1', + 'key' => 'include-field-labels', + ), + ), + // Allow output formats to declare their + // "output data type" instead of their + // "required engine capability" for readability. + 'config-aliases' => array( + 'output-data-type' => 'require-engine-capability', + ), + ); + return $info; +} + +/** + * Implements hook_drush_engine_ENGINE_TYPE(). + * + * The output format types supported are represented by + * the 'engine-capabilities' of the output format engine. + * The different capabilities include: + * + * format-single: A simple string. + * + * format-list: An associative array where the key + * is usually the row label, and the value + * is a simple string. Some list formatters + * render the label, and others (like + * "list" and "csv") throw it away. + * + * format-table: An associative array, where the key + * is the row id, and the value is the + * column data. The column data is also + * an associative array where the key + * is the column id and the value is the + * cell data. The cell data should usually + * be a simple string; however, some + * formatters can recursively format their + * cell contents before rendering (e.g. if + * a cell contains a list of items in an array). + * + * These definitions align with the declared 'output-data-type' + * declared in command records. @see drush_parse_command(). + * + * Any output format that does not declare any engine capabilities + * is expected to be able to render any php data structure that is + * passed to it. + */ +function outputformat_drush_engine_outputformat() { + $common_topic_example = array( + "a" => array("b" => 2, "c" => 3), + "d" => array("e" => 5, "f" => 6) + ); + + $engines = array(); + $engines['table'] = array( + 'description' => 'A formatted, word-wrapped table.', + 'engine-capabilities' => array('format-table'), + ); + $engines['key-value'] = array( + 'description' => 'A formatted list of key-value pairs.', + 'engine-capabilities' => array('format-single', 'format-list', 'format-table'), + 'hidden' => TRUE, + ); + $engines['key-value-list'] = array( + 'implemented-by' => 'list', + 'list-item-type' => 'key-value', + 'description' => 'A list of formatted lists of key-value pairs.', + 'list-field-selection-control' => 1, + 'engine-capabilities' => array('format-table'), + 'hidden' => TRUE, + ); + $engines['json'] = array( + 'machine-parsable' => TRUE, + 'description' => 'Javascript Object Notation.', + 'topic-example' => $common_topic_example, + ); + $engines['string'] = array( + 'machine-parsable' => TRUE, + 'description' => 'A simple string.', + 'engine-capabilities' => array('format-single'), + ); + $engines['message'] = array( + 'machine-parsable' => FALSE, // depends on the label.... + 'hidden' => TRUE, + ); + $engines['print-r'] = array( + 'machine-parsable' => TRUE, + 'description' => 'Output via php print_r function.', + 'verbose-only' => TRUE, + 'topic-example' => $common_topic_example, + ); + $engines['var_export'] = array( + 'machine-parsable' => TRUE, + 'description' => 'An array in executable php format.', + 'topic-example' => $common_topic_example, + ); + $engines['yaml'] = array( + 'machine-parsable' => TRUE, + 'description' => 'Yaml output format.', + 'topic-example' => $common_topic_example, + ); + $engines['php'] = array( + 'machine-parsable' => TRUE, + 'description' => 'A serialized php string.', + 'verbose-only' => TRUE, + 'topic-example' => $common_topic_example, + ); + $engines['config'] = array( + 'machine-parsable' => TRUE, + 'implemented-by' => 'list', + 'list-item-type' => 'var_export', + 'description' => "A configuration file in executable php format. The variable name is \"config\", and the variable keys are taken from the output data array's keys.", + 'metadata' => array( + 'variable-name' => 'config', + ), + 'list-field-selection-control' => -1, + 'engine-capabilities' => array('format-list','format-table'), + 'verbose-only' => TRUE, + ); + $engines['list'] = array( + 'machine-parsable' => TRUE, + 'list-item-type' => 'string', + 'description' => 'A simple list of values.', + // When a table is printed as a list, only the array keys of the rows will print. + 'engine-capabilities' => array('format-list', 'format-table'), + 'topic-example' => array('a', 'b', 'c'), + ); + $engines['nested-csv'] = array( + 'machine-parsable' => TRUE, + 'implemented-by' => 'list', + 'list-separator' => ',', + 'list-item-type' => 'csv-or-string', + 'hidden' => TRUE, + ); + $engines['csv-or-string'] = array( + 'machine-parsable' => TRUE, + 'hidden' => TRUE, + ); + $engines['csv'] = array( + 'machine-parsable' => TRUE, + 'implemented-by' => 'list', + 'list-item-type' => 'nested-csv', + 'labeled-list' => TRUE, + 'description' => 'A list of values, one per row, each of which is a comma-separated list of values.', + 'engine-capabilities' => array('format-table'), + 'topic-example' => array(array('a', 12, 'a@one.com'),array('b', 17, 'b@two.com')), + ); + $engines['variables'] = array( + 'machine-parsable' => TRUE, + 'description' => 'A list of php variable assignments.', + 'engine-capabilities' => array('format-table'), + 'verbose-only' => TRUE, + 'list-field-selection-control' => -1, + 'topic-example' => $common_topic_example, + ); + $engines['labeled-export'] = array( + 'machine-parsable' => TRUE, + 'description' => 'A list of php exports, labeled with a name.', + 'engine-capabilities' => array('format-table'), + 'verbose-only' => TRUE, + 'implemented-by' => 'list', + 'list-item-type' => 'var_export', + 'metadata' => array( + 'label-template' => '!label: !value', + ), + 'list-field-selection-control' => -1, + 'topic-example' => $common_topic_example, + ); + $engines['html'] = array( + 'machine-parsable' => FALSE, + 'description' => 'An HTML representation', + 'engine-capabilities' => array('format-table'), + ); + return $engines; +} + +/** + * Implements hook_drush_command_alter + */ +function outputformat_drush_command_alter(&$command) { + // In --pipe mode, change the default format to the default pipe format, or + // to json, if no default pipe format is given. + if (drush_get_context('DRUSH_PIPE') && (isset($command['engines']['outputformat']))) { + $default_format = isset($command['engines']['outputformat']['pipe-format']) ? $command['engines']['outputformat']['pipe-format'] : 'json'; + $command['engines']['outputformat']['default'] = $default_format; + } +} + +/** + * Implements hook_drush_help_alter(). + */ +function outputformat_drush_help_alter(&$command) { + if (isset($command['engines']['outputformat'])) { + $outputformat = $command['engines']['outputformat']; + // If the command defines specific field labels, + // then modify the help for --fields to include + // specific information about the available fields. + if (isset($outputformat['field-labels'])) { + $all_fields = array(); + $all_fields_description = array(); + foreach ($outputformat['field-labels'] as $field => $human_readable) { + $all_fields[] = $field; + if ((strtolower($field) != strtolower($human_readable)) && !array_key_exists(strtolower($human_readable), $outputformat['field-labels'])) { + $all_fields_description[] = $field . dt(" (or '!other')", array('!other' => strtolower($human_readable))); + } + else { + $all_fields_description[] = $field; + } + } + $field_defaults = isset($outputformat['fields-default']) ? $outputformat['fields-default'] : $all_fields; + $command['options']['fields']['example-value'] = implode(', ', $field_defaults); + $command['options']['fields']['description'] .= ' '. dt('All available fields are: !fields.', array('!fields' => implode(', ', $all_fields_description))); + if (isset($outputformat['fields-default'])) { + $command['options']['full']['description'] = dt("Show the full output, with all fields included."); + } + } + else { + // If the command does not define specific field labels, + // then hide the help for --fields unless the command + // uses output format engines that format tables. + if (isset($outputformat['require-engine-capability']) && is_array($outputformat['require-engine-capability'])) { + if (!in_array('format-table', $outputformat['require-engine-capability'])) { + unset($command['options']['fields']); + unset($command['options']['field-labels']); + } + } + // If the command does define output formats, but does not + // define fields, then just hide the help for the --fields option. + else { + $command['options']['fields']['hidden'] = TRUE; + $command['options']['field-labels']['hidden'] = TRUE; + } + } + + // If the command defines a default pipe format, then + // add '--pipe Equivalent to --format='. + if (isset($outputformat['pipe-format'])) { + if (isset($command['options']['pipe'])) { + $command['options']['pipe'] .= ' '; + } + else { + $command['options']['pipe'] = ''; + } + if (isset($outputformat['pipe-metadata']['message-template'])) { + $command['options']['pipe'] .= dt('Displays output in the form "!message"', array('!message' => $outputformat['pipe-metadata']['message-template'])); + } + else { + $command['options']['pipe'] .= dt("Equivalent to --format=!default.", array('!default' => $outputformat['pipe-format'])); + } + } + } +} + +/** + * Implements hook_drush_engine_topic_additional_text(). + */ +function outputformat_drush_engine_topic_additional_text($engine, $instance, $config) { + $result = array(); + + // If the output format engine has a 'topic-example' in + // its configuration, then format the provided array using + // the output formatter, and insert the result of the + // transform into the topic text. + if ($engine == 'outputformat') { + if (array_key_exists('topic-example', $config)) { + $code = $config['topic-example']; + $formatted = drush_format($code, array(), $instance); + $result[] = dt("Code:\n\nreturn !code;\n\nOutput with --format=!instance:\n\n!formatted", array('!code' => var_export($code, TRUE), '!instance' => $instance, '!formatted' => $formatted)); + } + } + + return $result; +} + +/** + * Interface for output format engines. + */ +class drush_outputformat { + function __construct($config) { + $config += array( + 'column-widths' => array(), + 'field-mappings' => array(), + 'engine-info' => array(), + ); + $config['engine-info'] += array( + 'machine-parsable' => FALSE, + 'metadata' => array(), + ); + $config += $config['engine-info']['metadata']; + $this->engine_config = $config; + } + function format_error($message) { + return drush_set_error('DRUSH_FORMAT_ERROR', dt("The output data could not be processed by the selected format '!type'. !message", array('!type' => $this->engine, '!message' => $message))); + } + function formatter_type() { + return $this->engine; + } + function is_list() { + return FALSE; + } + function formatter_is_simple_list() { + if (!isset($this->sub_engine)) { + return false; + } + return ($this->formatter_type() == 'list') && ($this->sub_engine->supports_single_only()); + } + function data_type($metadata) { + if (isset($metadata['metameta']['require-engine-capability']) && is_array($metadata['metameta']['require-engine-capability'])) { + return $metadata['metameta']['require-engine-capability'][0]; + } + if (isset($metadata['require-engine-capability']) && is_array($metadata['require-engine-capability'])) { + return $metadata['require-engine-capability'][0]; + } + return 'unspecified'; + } + function supported_data_types($metadata = NULL) { + if ($metadata == NULL) { + $metadata = $this->engine_config; + } + if (isset($metadata['metameta']['engine-info']['engine-capabilities'])) { + return $metadata['metameta']['engine-info']['engine-capabilities']; + } + if (isset($metadata['engine-info']['engine-capabilities'])) { + return $metadata['engine-info']['engine-capabilities']; + } + return array(); + } + function supports_single_only($metadata = NULL) { + $supported = $this->supported_data_types($metadata); + return (count($supported) == 1) && ($supported[0] == 'format-single'); + } + function get_info($key) { + if (array_key_exists($key, $this->engine_config)) { + return $this->engine_config[$key]; + } + elseif (isset($this->sub_engine)) { + return $this->sub_engine->get_info($key); + } + return FALSE; + } + /** + * Perform pre-processing and then format() the $input. + */ + function process($input, $metadata = array()) { + $metadata = array_merge_recursive($metadata, $this->engine_config); + if (isset($metadata['private-fields']) && is_array($input)) { + if (!drush_get_option('show-passwords', FALSE)) { + if (!is_array($metadata['private-fields'])) { + $metadata['private-fields'] = array($metadata['private-fields']); + } + foreach ($metadata['private-fields'] as $private) { + drush_unset_recursive($input, $private); + } + } + } + if (isset($metadata[$this->engine . '-metadata'])) { + $engine_specific_metadata = $metadata[$this->engine . '-metadata']; + unset($metadata[$this->engine . '-metadata']); + $metadata = array_merge($metadata, $engine_specific_metadata); + } + if ((drush_get_context('DRUSH_PIPE')) && (isset($metadata['pipe-metadata']))) { + $pipe_specific_metadata = $metadata['pipe-metadata']; + unset($metadata['pipe-metadata']); + $metadata = array_merge($metadata, $pipe_specific_metadata); + } + $machine_parsable = $this->engine_config['engine-info']['machine-parsable']; + $formatter_type = $machine_parsable ? 'parsable' : 'formatted'; + if ((!$machine_parsable) && is_bool($input)) { + $input = $input ? 'TRUE' : 'FALSE'; + } + + // Run $input through any filters that are specified for this formatter. + if (isset($metadata[$formatter_type . '-filter'])) { + $filters = $metadata[$formatter_type . '-filter']; + if (!is_array($filters)) { + $filters = array($filters); + } + foreach ($filters as $filter) { + if (function_exists($filter)) { + $input = $filter($input, $metadata); + } + } + } + if (isset($metadata['field-labels'])) { + foreach (drush_hide_output_fields() as $hidden_field) { + unset($metadata['field-labels'][$hidden_field]); + } + } + return $this->format($input, $metadata); + } + function format($input, $metadata) { + return $input; + } +} + +/** + * Specify that certain fields should not appear in the resulting output. + */ +function drush_hide_output_fields($fields_to_hide = array()) { + $already_hidden = drush_get_context('DRUSH_HIDDEN_OUTPUT_FIELDS'); + if (!is_array($fields_to_hide)) { + $fields_to_hide = array($fields_to_hide); + } + $result = array_merge($already_hidden, $fields_to_hide); + drush_set_context('DRUSH_HIDDEN_OUTPUT_FIELDS', $result); + return $result; +} diff --git a/vendor/drush/drush/commands/core/outputformat/csv_or_string.inc b/vendor/drush/drush/commands/core/outputformat/csv_or_string.inc new file mode 100644 index 0000000000..ad3b992de8 --- /dev/null +++ b/vendor/drush/drush/commands/core/outputformat/csv_or_string.inc @@ -0,0 +1,21 @@ + +Drush help + +

Global Options (see `drush topic core-global-options` for the full list)

+ $row) { + print ''; + foreach ($row as $value) { + print "\n"; + } + print "\n"; + } ?> +
" . htmlspecialchars($value) . "
+

Command list

+ $command) { + print " \n"; + } ?> +
$key" . $command['description'] . "
+

Command detail

+
$command) { + print "\n
$key
\n";
+        drush_core_helpsingle($key);
+        print "
\n"; + } + ?> + + diff --git a/vendor/drush/drush/commands/core/outputformat/json.inc b/vendor/drush/drush/commands/core/outputformat/json.inc new file mode 100644 index 0000000000..7132d97b09 --- /dev/null +++ b/vendor/drush/drush/commands/core/outputformat/json.inc @@ -0,0 +1,26 @@ + array("b" => 2, "c" => 3), + * "d" => array("e" => 5, "f" => 6) + * ); + * + * Output with --format=json: + * + * {"a":{"b":2,"c":3},"d":{"e":5,"f":6}} + */ +class drush_outputformat_json extends drush_outputformat { + function format($input, $metadata) { + return drush_json_encode($input); + } +} diff --git a/vendor/drush/drush/commands/core/outputformat/key_value.inc b/vendor/drush/drush/commands/core/outputformat/key_value.inc new file mode 100644 index 0000000000..5cc85eeedc --- /dev/null +++ b/vendor/drush/drush/commands/core/outputformat/key_value.inc @@ -0,0 +1,81 @@ + "Two B or ! Two B, that is the comparison", + * "c" => "I see that C has gone to Sea" + * ); + * + * Output with --format=key-value: + * + * b : Two B or ! Two B, + * that is the + * comparison + * c : I see that C has gone + * to Sea + * + * Code: + * + * return array( + * "a" => array( + * "b" => "Two B or ! Two B, that is the comparison", + * "c" => "I see that C has gone to Sea" + * ), + * "d" => array( + * "e" => "Elephants and electron microscopes", + * "f" => "My margin is too small" + * ) + * ); + * + * Output with --format=key-value-list: + * + * b : Two B or ! Two B, + * that is the + * comparison + * c : I see that C has gone + * to Sea + * + * e : Elephants and + * electron microscopes + * f : My margin is too + * small + */ +class drush_outputformat_key_value extends drush_outputformat { + function format($input, $metadata) { + if (!is_array($input)) { + if (isset($metadata['label'])) { + $input = array(dt($metadata['label']) => $input); + } + else { + return $this->format_error(dt('No label provided.')); + } + } + $kv_metadata = isset($metadata['table-metadata']) ? $metadata['table-metadata'] : array(); + if ((!isset($kv_metadata['key-value-item'])) && (isset($metadata['field-labels']))) { + $input = drush_select_output_fields($input, $metadata['field-labels'], $metadata['field-mappings']); + } + if (isset($metadata['include-field-labels'])) { + $kv_metadata['include-field-labels'] = $metadata['include-field-labels']; + } + $formatted_table = drush_key_value_to_array_table($input, $kv_metadata); + if ($formatted_table === FALSE) { + return FALSE; + } + return drush_format_table($formatted_table, FALSE, array()); + } +} diff --git a/vendor/drush/drush/commands/core/outputformat/list.inc b/vendor/drush/drush/commands/core/outputformat/list.inc new file mode 100644 index 0000000000..5d6bf4945e --- /dev/null +++ b/vendor/drush/drush/commands/core/outputformat/list.inc @@ -0,0 +1,156 @@ +engine_config as $key => $value) { + if ((substr($key, 0, 4) == 'list') || (substr($key, -6) == 'filter') || (substr($key, 0, 5) == 'field') || ($key == 'options')) { + unset($this->engine_config[$key]); + $list_metadata[$key] = $value; + } + } + foreach ($this->engine_config['engine-info'] as $key => $value) { + if ((substr($key, 0, 4) == 'list') || (substr($key, -6) == 'filter')) { + unset($this->engine_config['engine-info'][$key]); + $list_metadata[$key] = $value; + } + } + $sub_formatter = isset($list_metadata['list-item-type']) ? $list_metadata['list-item-type'] : 'string'; + $this->sub_engine = drush_load_engine('outputformat', $sub_formatter, $this->engine_config); + if (!is_object($this->sub_engine)) { + return drush_set_error('DRUSH_INVALID_SUBFORMATTER', dt("The list output formatter could not load its subformatter: !sub", array('!sub' => $sub_formatter))); + } + $engine_info = $this->engine_config['engine-info']; + $this->engine_config = array( + 'engine-info' => array( + 'machine-parsable' => $this->sub_engine->engine_config['engine-info']['machine-parsable'], + ), + 'metameta' => $this->sub_engine->engine_config, + ) + $list_metadata; + return TRUE; + } + + function format($input, $metadata) { + $output = ''; + if (is_array($input)) { + // If this list is processing output from a command that produces table + // @todo - need different example below? + // output, but our subformatter only supports 'single' items (e.g. csv), + // then we will convert our data such that the output will be the keys + // of the table rows. + if (($this->data_type($metadata) == 'format-table') && ($this->supports_single_only($metadata)) && !isset($metadata['list-item'])) { + // If the user specified exactly one field with --fields, then + // use it to select the data column to use instead of the array key. + if (isset($metadata['field-labels']) && (count($metadata['field-labels']) == 1)) { + $first_label = key($metadata['field-labels']); + $input = drush_output_get_selected_field($input, $first_label); + } + else { + $input = array_keys($input); + } + } + $first = TRUE; + $field_selection_control = isset($metadata['list-field-selection-control']) ? $metadata['list-field-selection-control'] : 0; + $selected_output_fields = false; + if (empty($metadata['metameta'])) { + $metameta = $metadata; + unset($metameta['list-item']); + unset($metameta['list-item-default-value']); + } + else { + $metameta = $metadata['metameta']; + } + $list_separator_key = 'list-separator'; + if ($this->sub_engine->is_list()) { + $list_separator_key = 'line-separator'; + if (isset($metadata['list-separator'])) { + $metameta['list-separator'] = $metadata['list-separator']; + } + } + $separator = isset($metadata[$list_separator_key]) && !empty($metadata[$list_separator_key]) ? $metadata[$list_separator_key] : "\n"; + // @todo - bug? we iterate over a hard coded, single item array? + foreach (array('field-labels') as $key) { + if (isset($metadata[$key])) { + $metameta[$key] = $metadata[$key]; + } + } + + // Include field labels, if specified + if (!isset($metadata['list-item']) && isset($metadata['labeled-list']) && is_array($input) && isset($metadata['field-labels'])) { + if (isset($metadata['include-field-labels']) && $metadata['include-field-labels']) { + array_unshift($input, $metadata['field-labels']); + } + } + foreach ($input as $label => $data) { + // If this output formatter is set to print a single item from each + // element, select that item here. + if (isset($metadata['list-item'])) { + $data = isset($data[$metadata['list-item']]) ? $data[$metadata['list-item']] : $metadata['list-item-default-value']; + } + // If this formatter supports the --fields option, then filter and + // order the fields the user wants here. Note that we need to be + // careful about when we call drush_select_output_fields(); sometimes, + // there will be nested formatters of type 'list', and it would not + // do to select the output fields more than once. + // 'list-field-selection-control can be set to a positive number to + // cause output fields to be selected at a later point in the call chain. + elseif (is_array($data) && isset($metadata['field-labels'])) { + if (!$field_selection_control) { + $data = drush_select_output_fields($data, $metadata['field-labels'], $metadata['field-mappings']); + $selected_output_fields = true; + } + } + $metameta['label'] = $label; + if ($selected_output_fields) { + $metameta['list-field-selection-control'] = -1; + } + elseif ($field_selection_control) { + $metameta['list-field-selection-control'] = $field_selection_control - 1; + } + $formatted_item = $this->sub_engine->format($data, $metameta); + if ($formatted_item === FALSE) { + return FALSE; + } + if (!$first) { + $output .= $separator; + } + if (($separator != "\n") && !empty($separator) && (strpos($formatted_item, $separator) !== FALSE)) { + $formatted_item = drush_wrap_with_quotes($formatted_item); + } + $output .= $formatted_item; + $first = FALSE; + } + } + return $output; + } +} diff --git a/vendor/drush/drush/commands/core/outputformat/message.inc b/vendor/drush/drush/commands/core/outputformat/message.inc new file mode 100644 index 0000000000..725bcbe237 --- /dev/null +++ b/vendor/drush/drush/commands/core/outputformat/message.inc @@ -0,0 +1,31 @@ + 1, 'b' => 2); + * + * Given 'message-template' == 'The first is !a and the second is !b', + * output with --format=message: + * + * The first is 1 and the second is 2 + */ +class drush_outputformat_message extends drush_outputformat { + function format($data, $metadata) { + $result = ''; + if (isset($metadata['message-template'])) { + foreach ($data as $key => $value) { + $data_for_dt['!' . $key] = $value; + } + $result = dt($metadata['message-template'], $data_for_dt); + } + return $result; + } +} diff --git a/vendor/drush/drush/commands/core/outputformat/php.inc b/vendor/drush/drush/commands/core/outputformat/php.inc new file mode 100644 index 0000000000..056314f3a9 --- /dev/null +++ b/vendor/drush/drush/commands/core/outputformat/php.inc @@ -0,0 +1,17 @@ + array("b" => 2, "c" => 3), + * "d" => array("e" => 5, "f" => 6) + * ); + * + * Output with --format=print-r: + * + * Array + * ( + * [a] => Array + * ( + * [b] => 2 + * [c] => 3 + * ) + * + * [d] => Array + * ( + * [e] => 5 + * [f] => 6 + * ) + * ) + */ +class drush_outputformat_print_r extends drush_outputformat { + function format($input, $metadata) { + if (is_string($input)) { + $output = '"' . $input . '"'; + } + elseif (is_array($input) || is_object($input)) { + $output = print_r($input, TRUE); + } + else { + $output = $input; + } + if (isset($metadata['label'])) { + $output = $metadata['label'] . ': ' . $output; + } + return $output; + } +} diff --git a/vendor/drush/drush/commands/core/outputformat/string.inc b/vendor/drush/drush/commands/core/outputformat/string.inc new file mode 100644 index 0000000000..a86e7d172b --- /dev/null +++ b/vendor/drush/drush/commands/core/outputformat/string.inc @@ -0,0 +1,37 @@ + 1) { + return $this->format_error("Multiple rows provided where only one is allowed."); + } + if (!empty($data)) { + $data = reset($data); + } + if (is_array($data)) { + return $this->format_error("Array provided where a string is required."); + } + } + return (string)$data; + } +} diff --git a/vendor/drush/drush/commands/core/outputformat/table.inc b/vendor/drush/drush/commands/core/outputformat/table.inc new file mode 100644 index 0000000000..81a9cfa27b --- /dev/null +++ b/vendor/drush/drush/commands/core/outputformat/table.inc @@ -0,0 +1,51 @@ + array("b" => 2, "c" => 3), + * "d" => array("b" => 5, "c" => 6) + * ); + * + * Output with --format=table: + * + * b c + * 2 3 + * 5 6 + */ +class drush_outputformat_table extends drush_outputformat { + function format($input, $metadata) { + $field_list = isset($metadata['field-labels']) ? $metadata['field-labels'] : array(); + $widths = array(); + $col = 0; + foreach($field_list as $key => $label) { + if (isset($metadata['column-widths'][$key])) { + $widths[$col] = $metadata['column-widths'][$key]; + } + ++$col; + } + $rows = drush_rows_of_key_value_to_array_table($input, $field_list, $metadata); + $field_labels = array_key_exists('include-field-labels', $metadata) && $metadata['include-field-labels']; + if (!$field_labels) { + array_shift($rows); + } + return drush_format_table($rows, $field_labels, $widths); + } +} diff --git a/vendor/drush/drush/commands/core/outputformat/topics/table.html b/vendor/drush/drush/commands/core/outputformat/topics/table.html new file mode 100644 index 0000000000..acc149a450 --- /dev/null +++ b/vendor/drush/drush/commands/core/outputformat/topics/table.html @@ -0,0 +1,56 @@ +The 'table' formatter will convert an associative array into a formatted, +word-wrapped table. Each item in the associative array represents one row +in the table. Each row is similarly composed of associative arrays, with +the key of each item indicating the column, and the value indicating the +contents of the cell. See below for an example source array. +

+The command core-requirements is an example of a command that produces output +in a tabular format. +

+$ drush core-requirements +

+ Title                 Severity  Description
+ Cron maintenance      Error     Last run 2 weeks ago
+ tasks                           Cron has not run recently. For more
+                                 information, see the online handbook entry for
+                                 configuring cron jobs. You can run cron
+                                 manually.
+ Drupal                Info      7.19
+
+(Note: the output above has been shortened for clarity; the actual output +of core-requirements contains additional rows not shown here.) +

+It is possible to determine the available fields by consulting drush +help core requirements: +

+ --fields=<title, severity, description>   Fields to output. All
+                                           available fields are:
+                                           title, severity, sid,
+                                           description, value,
+                                           reason, weight.
+
+It is possible to control the fields that appear in the table, and their +order, by naming the desired fields in the --fields option. The space +between items is optional, so `--fields=title,sid` is valid. +

+Code: +

+return array (
+  'cron' =>
+  array (
+    'title' => 'Cron maintenance tasks',
+    'severity' => 2,
+    'value' => 'Last run 2 weeks ago',
+    'description' => 'Cron has not run recently. For more information, see the online handbook entry for configuring cron jobs. You can run cron manually.',
+    'severity-label' => 'Error',
+  ),
+  'drupal' =>
+  array (
+    'title' => 'Drupal',
+    'value' => '7.19',
+    'severity' => -1,
+    'weight' => -10,
+    'severity-label' => 'Info',
+  ),
+)
+
diff --git a/vendor/drush/drush/commands/core/outputformat/var_export.inc b/vendor/drush/drush/commands/core/outputformat/var_export.inc new file mode 100644 index 0000000000..c82bb9684d --- /dev/null +++ b/vendor/drush/drush/commands/core/outputformat/var_export.inc @@ -0,0 +1,62 @@ + array("b" => 2, "c" => 3), + * "d" => array("e" => 5, "f" => 6) + * ); + * + * Output with --format=var_export: + * + * array ( + * 'a' => + * array ( + * 'b' => 2, + * 'c' => 3, + * ), + * 'd' => + * array ( + * 'e' => 5, + * 'f' => 6, + * ), + * ) + * + * Output with --format=config: (list of export) + * + * $config['a'] = array ( + * 'b' => 2, + * 'c' => 3, + * ); + * $config['d'] = array ( + * 'e' => 5, + * 'f' => 6, + * ); + */ +class drush_outputformat_var_export extends drush_outputformat { + function format($input, $metadata) { + if (isset($metadata['label'])) { + $variable_name = isset($metadata['variable-name']) ? $metadata['variable-name'] : 'variables'; + $variable_name = preg_replace("/[^a-zA-Z0-9_-]/", "", str_replace(' ', '_', $variable_name)); + $label = $metadata['label']; + $label_template = (isset($metadata['label-template'])) ? $metadata['label-template'] : '$!variable["!label"] = !value;'; + $output = dt($label_template, array('!variable' => $variable_name, '!label' => $label, '!value' => var_export($input, TRUE))); + } + else { + $output = drush_var_export($input); + } + return $output; + } +} diff --git a/vendor/drush/drush/commands/core/outputformat/variables.inc b/vendor/drush/drush/commands/core/outputformat/variables.inc new file mode 100644 index 0000000000..7220aab86c --- /dev/null +++ b/vendor/drush/drush/commands/core/outputformat/variables.inc @@ -0,0 +1,58 @@ + array("b" => 2, "c" => 3), + * "d" => array("e" => 5, "f" => 6) + * ); + * + * Output with --format=variables: + * + * $a['b'] = 2; + * $a['c'] = 3; + * $d['e'] = 5; + * $d['f'] = 6; + */ +class drush_outputformat_variables extends drush_outputformat { + function validate() { + $metadata = $this->engine_config; + $this->sub_engine = drush_load_engine('outputformat', 'var_export', $metadata); + if (!is_object($this->sub_engine)) { + return FALSE; + } + return TRUE; + } + + function format($data, $metadata) { + $output = ''; + if (is_array($data)) { + foreach ($data as $variable_name => $section) { + foreach ($section as $label => $value) { + $metameta = array( + 'variable-name' => $variable_name, + 'label' => $label, + ); + $formatted_item = $this->sub_engine->process($value, $metameta); + if ($formatted_item === FALSE) { + return FALSE; + } + $output .= $formatted_item; + $output .= "\n"; + } + } + } + return $output; + } +} diff --git a/vendor/drush/drush/commands/core/outputformat/yaml.inc b/vendor/drush/drush/commands/core/outputformat/yaml.inc new file mode 100644 index 0000000000..26bcd8cffb --- /dev/null +++ b/vendor/drush/drush/commands/core/outputformat/yaml.inc @@ -0,0 +1,26 @@ +setIndentation(2); + // The level where you switch to inline YAML is set to PHP_INT_MAX to + // ensure this does not occur. + $output = $dumper->dump($input, PHP_INT_MAX, NULL, NULL, TRUE); + return $output; + } +} diff --git a/vendor/drush/drush/commands/core/queue.drush.inc b/vendor/drush/drush/commands/core/queue.drush.inc new file mode 100644 index 0000000000..3dcae01526 --- /dev/null +++ b/vendor/drush/drush/commands/core/queue.drush.inc @@ -0,0 +1,97 @@ + 'Run a specific queue by name', + 'arguments' => array( + 'queue_name' => 'The name of the queue to run, as defined in either hook_queue_info or hook_cron_queue_info.', + ), + 'required-arguments' => TRUE, + 'options' => array( + 'time-limit' => 'The maximum number of seconds allowed to run the queue', + ), + 'aliases' => array('queue:run'), + ); + $items['queue-list'] = array( + 'description' => 'Returns a list of all defined queues', + 'outputformat' => array( + 'default' => 'table', + 'pipe-format' => 'csv', + 'field-labels' => array( + 'queue' => 'Queue', + 'items' => 'Items', + 'class' => 'Class', + ), + 'ini-item' => 'items', + 'table-metadata' => array( + 'key-value-item' => 'items', + ), + 'output-data-type' => 'format-table', + 'aliases' => array('queue:list'), + ), + ); + + return $items; +} + +/** + * Validation callback for drush queue-run. + */ +function drush_queue_run_validate($queue_name) { + try { + $queue = drush_queue_get_class(); + $queue->getInfo($queue_name); + } + catch (\Drush\Queue\QueueException $exception) { + return drush_set_error('DRUSH_QUEUE_RUN_VALIDATION_ERROR', $exception->getMessage()); + } +} + +/** + * Return the appropriate queue class. + */ +function drush_queue_get_class() { + return drush_get_class('Drush\Queue\Queue'); +} + +/** + * Command callback for drush queue-run. + * + * Queue runner that is compatible with queues declared using both + * hook_queue_info() and hook_cron_queue_info(). + * + * @param $queue_name + * Arbitrary string. The name of the queue to work with. + */ +function drush_queue_run($queue_name) { + $queue = drush_queue_get_class(); + $time_limit = (int) drush_get_option('time-limit'); + $start = microtime(TRUE); + $count = $queue->run($queue_name, $time_limit); + $elapsed = microtime(TRUE) - $start; + drush_log(dt('Processed @count items from the @name queue in @elapsed sec.', array('@count' => $count, '@name' => $queue_name, '@elapsed' => round($elapsed, 2))), drush_get_error() ? LogLevel::WARNING : LogLevel::OK); +} + +/** + * Command callback for drush queue-list. + */ +function drush_queue_list() { + $queue = drush_queue_get_class(); + return $queue->listQueues(); +} + diff --git a/vendor/drush/drush/commands/core/role.drush.inc b/vendor/drush/drush/commands/core/role.drush.inc new file mode 100644 index 0000000000..cb39ea5ac3 --- /dev/null +++ b/vendor/drush/drush/commands/core/role.drush.inc @@ -0,0 +1,329 @@ += 8) { + foreach ($command['examples'] as $key => $val) { + $newkey = str_replace(array('anonymous user', 'authenticated user'), array('anonymous', 'authenticated'), $key); + $command['examples'][$newkey] = $val; + unset($command['examples'][$key]); + } + } +} + +/** + * Implementation of hook_drush_command(). + */ +function role_drush_command() { + $items['role-create'] = array( + 'description' => 'Create a new role.', + 'examples' => array( + "drush role-create 'test role'" => "Create a new role 'test role' on D6 or D7; auto-assign the rid. On D8, 'test role' is the rid, and the human-readable name is set to 'Test role'.", + "drush role-create 'test role' 'Test role'" => "Create a new role with a machine name of 'test role', and a human-readable name of 'Test role'. On D6 and D7, behaves as the previous example." + ), + 'arguments' => array( + 'machine name' => 'The symbolic machine name for the role. Required.', + 'human-readable name' => 'A descriptive name for the role. Optional; Drupal 8 only. Ignored in D6 and D7.', + ), + 'aliases' => array('rcrt', 'role:create'), + ); + $items['role-delete'] = array( + 'description' => 'Delete a role.', + 'examples' => array( + "drush role-delete 'test role'" => "Delete the role 'test role'.", + ), + 'arguments' => array( + 'machine name' => 'The symbolic machine name for the role. Required. In D6 and D7, this may also be a numeric role ID.', + ), + 'aliases' => array('rdel', 'role:delete'), + ); + $items['role-add-perm'] = array( + 'description' => 'Grant specified permission(s) to a role.', + 'examples' => array( + "drush role-add-perm 'anonymous user' 'post comments'" => 'Allow anon users to post comments.', + "drush role-add-perm 'anonymous user' \"'post comments','access content'\"" => 'Allow anon users to post comments and access content.', + "drush role-add-perm 'authenticated user' --module=node" => 'Select a permission from "node" permissions to add to logged in users.' + ), + 'arguments' => array( + 'role' => 'The role to modify. Required.', + 'permissions' => 'The list of permission to grant, delimited by commas. Required, unless the --module option is used.', + ), + 'required-arguments' => 1, + 'options' => array( + 'module' => 'Select the permission to modify from an interactive list of all permissions available in the specified module.', + ), + 'global-options' => array( + 'cache-clear', + ), + 'aliases' => array('rap', 'role:add:perm'), + ); + + $items['role-remove-perm'] = array( + 'description' => 'Remove specified permission(s) from a role.', + 'examples' => array( + "drush role-remove-perm 'anonymous user' 'access content'" => 'Hide content from anon users.', + ), + 'arguments' => array( + 'role' => 'The role to modify.', + 'permissions' => 'The list of permission to grant, delimited by commas. Required, unless the --module option is used.', + ), + 'required-arguments' => 1, + 'options' => array( + 'module' => 'Select the permission to modify from an interactive list of all permissions available in the specified module.', + ), + 'global-options' => array( + 'cache-clear', + ), + 'aliases' => array('rmp', 'role:remove:perm'), + ); + + $items['role-list'] = array( + 'description' => 'Display a list of all roles defined on the system. If a role name is provided as an argument, then all of the permissions of that role will be listed. If a permission name is provided as an option, then all of the roles that have been granted that permission will be listed.', + 'examples' => array( + "drush role-list --filter='administer nodes'" => 'Display a list of roles that have the administer nodes permission assigned.', + "drush role-list 'anonymous user'" => 'Display all of the permissions assigned to the anon user role.' + ), + 'arguments' => array( + 'role' => 'The role to list. Optional; if specified, lists all permissions assigned to that role. If no role is specified, lists all of the roles available on the system.', + ), + 'options' => array( + 'filter' => 'Limits the list of roles to only those that have been assigned the specified permission. Optional; may not be specified if a role argument is provided.', + ), + 'outputformat' => array( + 'default' => 'table', + 'pipe-format' => 'list', + 'field-labels' => array('rid' => 'ID', 'label' => 'Role Label', 'perm' => "Permission"), + 'output-data-type' => 'format-table', + ), + 'aliases' => array('rls', 'role:list'), + ); + + return $items; +} + +/** + * Create the specified role + */ +function drush_role_create($rid, $role_name = '') { + $role = drush_role_get_class(); + $result = $role->role_create($rid, $role_name); + if ($result !== FALSE) { + drush_log(dt('Created "!role"', array('!role' => $rid)), LogLevel::SUCCESS); + } + return $result; +} + +/** + * Create the specified role + */ +function drush_role_delete($rid) { + $role = drush_role_get_class($rid); + if ($role === FALSE) { + return FALSE; + } + $result = $role->delete(); + if ($result !== FALSE) { + drush_log(dt('Deleted "!role"', array('!role' => $rid)), LogLevel::SUCCESS); + } + return $result; +} + +/** + * Add one or more permission(s) to the specified role. + * + * @param string $rid machine name for a role + * @param null|string $permissions machine names, delimited by commas. + * + * @return bool + */ +function drush_role_add_perm($rid, $permissions = NULL) { + if (is_null($permissions)) { + // Assume --module is used thus inject a FALSE + $perms = array(FALSE); + } + else { + $perms = _convert_csv_to_array($permissions); + } + + $added_perm = FALSE; + + foreach($perms as $perm) { + $result = drush_role_perm('add', $rid, $perm); + if ($result !== FALSE) { + $added_perm = TRUE; + drush_log(dt('Added "!perm" to "!role"', array('!perm' => $perm, '!role' => $result->name)), LogLevel::SUCCESS); + } + } + + if ($added_perm) { + drush_drupal_cache_clear_all(); + } + + return $result; +} + +/** + * Remove permission(s) from the specified role. + * + * @param string $rid machine name for a role + * @param null|string $permissions machine names, delimited by commas. + * + * @return bool + */ +function drush_role_remove_perm($rid, $permissions = NULL) { + if (is_null($permissions)) { + // Assume --module is used thus inject a FALSE + $perms = array(FALSE); + } + else { + $perms = _convert_csv_to_array($permissions); + } + + $removed_perm = FALSE; + + foreach($perms as $perm) { + $result = drush_role_perm('remove', $rid, $perm); + if ($result !== FALSE) { + $removed_perm = TRUE; + drush_log(dt('Removed "!perm" from "!role"', array('!perm' => $perm, '!role' => $result->name)), LogLevel::OK); + } + } + + if ($removed_perm) { + drush_drupal_cache_clear_all(); + } + + return $result; +} + +/** + * Implement permission add / remove operations. + * + * @param string $action 'add' | 'remove' + * @param string $rid + * @param string|null $permission + * + * @return bool|RoleBase + * + * @see drush_set_error() + */ +function drush_role_perm($action, $rid, $permission = NULL) { + $role = drush_role_get_class($rid); + if (!$role) { + return FALSE; + } + + // If a permission wasn't provided, but the module option is specified, + // provide a list of permissions provided by that module. + if (!$permission && $module = drush_get_option('module', FALSE)) { + drush_include_engine('drupal', 'environment'); + if (!drush_module_exists($module)) { + return drush_set_error('DRUSH_ROLE_ERROR', dt('!module not enabled!', array('!module' => $module))); + } + $module_perms = $role->getModulePerms($module); + if (empty($module_perms)) { + return drush_set_error('DRUSH_ROLE_NO_PERMISSIONS', dt('No permissions found for module !module', array('!module' => $module))); + } + $choice = drush_choice($module_perms, "Enter a number to choose which permission to $action."); + if ($choice === FALSE) { + return drush_user_abort(); + } + $permission = $module_perms[$choice]; + } + else { + $permissions = $role->getAllModulePerms(); + if (!in_array($permission, $permissions)) { + return drush_set_error(dt('Could not find the permission: !perm', array('!perm' => $permission))); + } + } + + $result = $role->{$action}($permission); + if ($result === FALSE) { + return FALSE; + } + return $role; +} + +/** + * Get core version specific Role handler class. + * + * @param string $role_name + * @return RoleBase + * + * @see drush_get_class(). + */ +function drush_role_get_class($role_name = DRUSH_ANONYMOUS_RID) { + $args = func_get_args(); + // We cannot change our function default value based on the Drupal major + // version, so we have this workaround. We could do this better with an + // engine (only include the file for the right version, but not going to + // introduce that just for this.) + if ($role_name == DRUSH_ANONYMOUS_RID) { + if (drush_drupal_major_version() >= 8) { + $args[0] = \Drupal\user\RoleInterface::ANONYMOUS_ID; + } else { + $args[0] = DRUPAL_ANONYMOUS_RID; + } + } + return drush_get_class('Drush\Role\Role', $args); +} + +/** + * Displays a list of roles + */ +function drush_role_list($rid = '') { + $result = array(); + if (empty($rid)) { + drush_hide_output_fields(array('perm')); + // Get options passed. + $perm = drush_get_option('filter'); + $roles = array(); + + // Get all roles - if $perm is empty user_roles retrieves all roles. + $roles = user_roles(FALSE, $perm); + if (empty($roles)) { + return drush_set_error('DRUSH_NO_ROLES', dt("No roles found.")); + } + foreach ($roles as $rid => $value) { + $role = drush_role_get_class($rid); + $result[$role->name] = array( + 'rid' => $rid, + 'label' => $role->name, + ); + } + } + else { + drush_hide_output_fields(array('rid', 'label')); + $role = drush_role_get_class($rid); + if (!$role) { + return FALSE; + } + $perms = $role->getPerms(); + foreach ($perms as $permission) { + $result[$permission] = array( + 'perm' => $permission); + } + } + return $result; +} diff --git a/vendor/drush/drush/commands/core/rsync.core.inc b/vendor/drush/drush/commands/core/rsync.core.inc new file mode 100644 index 0000000000..29d1449bf3 --- /dev/null +++ b/vendor/drush/drush/commands/core/rsync.core.inc @@ -0,0 +1,294 @@ + $source))); + } + if (!isset($destination_settings)) { + return drush_set_error('DRUSH_BAD_PATH', dt('Could not evaluate destination path !path.', array('!path' => $destination))); + } + + // If the user path is the same for the source and the destination, then + // always add a slash to the end of the source. If the user path is not + // the same in the source and the destination, then you need to know how + // rsync paths work, and put on the trailing '/' if you want it. + if ($source_settings['user-path'] == $destination_settings['user-path']) { + $source_path .= '/'; + } + // Prompt for confirmation. This is destructive. + if (!drush_get_context('DRUSH_SIMULATE')) { + drush_print(dt("You will delete files in !target and replace with data from !source", array('!source' => $source_path, '!target' => $destination_path))); + if (!drush_confirm(dt('Do you really want to continue?'))) { + return drush_user_abort(); + } + } + + // Next, check to see if both the source and the destination are remote. + // If so, then we'll process this as an rsync from source to local, + // followed by an rsync from local to the destination. + if (drush_sitealias_is_remote_site($source_settings) && drush_sitealias_is_remote_site($destination_settings)) { + return _drush_core_rsync_both_remote($source, $destination, $additional_options, $source_path); + } + + // Exclude settings is the default only when both the source and + // the destination are aliases or site names. Therefore, include + // settings will be the default whenever either the source or the + // destination contains a : or a /. + $include_settings_is_default = (strpos($source . $destination, ':') !== FALSE) || (strpos($source . $destination, '/') !== FALSE); + + $options = _drush_build_rsync_options($additional_options, $include_settings_is_default); + + // Get all of the args and options that appear after the command name. + $original_args = drush_get_original_cli_args_and_options(); + foreach ($original_args as $original_option) { + if ($original_option[0] == '-') { + $options .= ' ' . $original_option; + } + } + + drush_backend_set_result($destination_path); + // Go ahead and call rsync with the paths we determined + return drush_core_exec_rsync($source_path, $destination_path, $options); +} + +/** + * Make a direct call to rsync after the source and destination paths + * have been evaluated. + * + * @param $source + * Any path that can be passed to rsync. + * @param $destination + * Any path that can be passed to rsync. + * @param $additional_options + * An array of options that overrides whatever was passed in on the command + * line (like the 'process' context, but only for the scope of this one + * call). + * @param $include_settings_is_default + * If TRUE, then settings.php will be transferred as part of the rsync unless + * --exclude-conf is specified. If FALSE, then settings.php will be excluded + * from the transfer unless --include-conf is specified. + * @param $live_output + * If TRUE, output goes directly to the terminal using system(). If FALSE, + * rsync is executed with drush_shell_exec() with output in + * drush_shell_exec_output(). + * + * @return + * TRUE on success, FALSE on failure. + */ +function drush_core_call_rsync($source, $destination, $additional_options = array(), $include_settings_is_default = TRUE, $live_output = TRUE) { + $options = _drush_build_rsync_options($additional_options, $include_settings_is_default); + return drush_core_exec_rsync($source, $destination, $options, $additional_options, $live_output); +} + +function drush_core_exec_rsync($source, $destination, $options, $additional_options = array(), $live_output = TRUE) { + $ssh_options = drush_get_option_override($additional_options, 'ssh-options', ''); + $exec = "rsync -e 'ssh $ssh_options' $options $source $destination"; + + if ($live_output) { + $exec_result = drush_op_system($exec); + $result = ($exec_result == 0); + } + else { + $result = drush_shell_exec($exec); + } + + if (!$result) { + drush_set_error('DRUSH_RSYNC_FAILED', dt("Could not rsync from !source to !dest", array('!source' => $source, '!dest' => $destination))); + } + + return $result; +} + +function _drush_build_rsync_options($additional_options, $include_settings_is_default = TRUE) { + $options = ''; + // Exclude vcs reserved files. + if (!_drush_rsync_option_exists('include-vcs', $additional_options)) { + $vcs_files = drush_version_control_reserved_files(); + foreach ($vcs_files as $file) { + $options .= ' --exclude="'.$file.'"'; + } + } + else { + unset($additional_options['include-vcs']); + } + + $mode = '-akz'; + // Process --include-paths and --exclude-paths options the same way + foreach (array('include', 'exclude') as $include_exclude) { + // Get the option --include-paths or --exclude-paths and explode to an array of paths + // that we will translate into an --include or --exclude option to pass to rsync + $inc_ex_path = explode(PATH_SEPARATOR, drush_get_option($include_exclude . '-paths', '')); + foreach ($inc_ex_path as $one_path_to_inc_ex) { + if (!empty($one_path_to_inc_ex)) { + $options .= ' --' . $include_exclude . '="' . $one_path_to_inc_ex . '"'; + } + } + // Remove stuff inserted by evaluate path + unset($additional_options[$include_exclude . '-paths']); + unset($additional_options[$include_exclude . '-files-processed']); + } + // drush_core_rsync passes in $include_settings_is_default such that + // 'exclude-conf' is the default when syncing from one alias to + // another, and 'include-conf' is the default when a path component + // is included. + if ($include_settings_is_default ? _drush_rsync_option_exists('exclude-conf', $additional_options) : !_drush_rsync_option_exists('include-conf', $additional_options)) { + $options .= ' --exclude="settings.php"'; + unset($additional_options['exclude-conf']); + } + if (_drush_rsync_option_exists('exclude-sites', $additional_options)) { + $options .= ' --include="sites/all" --exclude="sites/*"'; + unset($additional_options['exclude-sites']); + } + if (_drush_rsync_option_exists('mode', $additional_options)) { + $mode = "-" . drush_get_option_override($additional_options, 'mode'); + unset($additional_options['mode']); + } + if (drush_get_context('DRUSH_VERBOSE')) { + // the drush_op() will be verbose about the command that gets executed. + $mode .= 'v'; + $options .= ' --stats --progress'; + } + + // Check if the user has set $options['rsync-version'] to enable rsync legacy version support. + // Drush was written for rsync 2.6.9 or later, so assume that version if nothing was explicitly set. + $rsync_version = drush_get_option(array('rsync-version','source-rsync-version','target-rsync-version'), '2.6.9'); + $options_to_exclude = array('ssh-options'); + foreach ($additional_options as $test_option => $value) { + // Downgrade some options for older versions of rsync + if ($test_option == 'remove-source-files') { + if (version_compare($rsync_version, '2.6.4', '<')) { + $test_option = NULL; + drush_log('Rsync does not support --remove-sent-files prior to version 2.6.4; some temporary files may remain undeleted.', LogLevel::WARNING); + } + elseif (version_compare($rsync_version, '2.6.9', '<')) { + $test_option = 'remove-sent-files'; + } + } + if ((isset($test_option)) && !in_array($test_option, $options_to_exclude) && (isset($value) && !is_array($value))) { + if (($value === TRUE) || (!isset($value))) { + $options .= " --$test_option"; + } + else { + $options .= " --$test_option=" . escapeshellarg($value); + } + } + } + + return $mode . $options; +} + +function _drush_rsync_option_exists($option, $additional_options) { + if (array_key_exists($option, $additional_options)) { + return TRUE; + } + else { + return drush_get_option($option, FALSE); + } +} + +/** + * Handle an rsync operation from a remote site to a remote + * site by first rsync'ing to a local location, and then + * copying that location to its final destination. + */ +function _drush_core_rsync_both_remote($source, $destination, $additional_options, $source_path) { + $options = $additional_options + drush_redispatch_get_options(); + + // Make a temporary directory to copy to. There are three + // cases to consider: + // + // 1. rsync @src:file.txt @dest:location + // 2. rsync @src:dir @dest:location + // 3. rsync @src:dir/ @dest:location + // + // We will explain each of these in turn. + // + // 1. Copy a single file. We'll split this up like so: + // + // rsync @src:file.txt /tmp/tmpdir + // rsync /tmp/tmpdir/file.txt @dest:location + // + // Since /tmp/tmpdir is empty, we could also rsync from + // '/tmp/tmpdir/' if we wished. + // + // 2. Copy a directory. A directory with the same name + // is copied to the destination. We'll split this up like so: + // + // rsync @src:dir /tmp/tmpdir + // rsync /tmp/tmpdir/dir @dest:location + // + // The result is that everything in 'dir' is copied to @dest, + // and ends up in 'location/dir'. + // + // 3. Copy the contents of a directory. We will split this + // up as follows: + // + // rsync @src:dir/ /tmp/tmpdir + // rsync /tmp/tmpdir/ @dest:location + // + // After the first rsync, everything in 'dir' will end up in + // tmpdir. The second rsync copies everything in tmpdir to + // @dest:location without creating an encapsulating folder + // in the destination (i.e. there is no 'tmpdir' in the destination). + // + // All three of these cases need to be handled correctly in order + // to ensure the correct results. In all cases the first + // rsync always copies to $tmpDir, however the second rsync has + // two cases that depend on the source path. If the source path ends + // in /, the contents of a directory have been copied to $tmpDir, and + // the contents of $tmpDir must be copied to the destination. Otherwise, + // a specific file or directory has been copied to $tmpDir and that + // specific item, identified by basename($source_path) must be copied to + // the destination. + + $putInTmpPath = drush_tempdir(); + $getFromTmpPath = "$putInTmpPath/"; + if (substr($source_path, -1) !== '/') { + $getFromTmpPath .= basename($source_path); + } + + // Copy from the source to the temporary location. Exit on failure. + $values = drush_invoke_process('@self', 'core-rsync', array($source, $putInTmpPath), $options); + if ($values['error'] != 0) { + return FALSE; + } + + // Copy from the temporary location to the final destination. + $values = drush_invoke_process('@self', 'core-rsync', array($getFromTmpPath, $destination), $options); + + return $values['error'] == 0; +} diff --git a/vendor/drush/drush/commands/core/scratch.php b/vendor/drush/drush/commands/core/scratch.php new file mode 100644 index 0000000000..e6d2cffc05 --- /dev/null +++ b/vendor/drush/drush/commands/core/scratch.php @@ -0,0 +1,20 @@ + 'Show how many items remain to be indexed out of the total.', + 'drupal dependencies' => array('search'), + 'outputformat' => array( + 'default' => 'message', + 'pipe-format' => 'message', + 'field-labels' => array('remaining' => 'Items not yet indexed', 'total' => 'Total items'), + 'message-template' => 'There are !remaining items out of !total still to be indexed.', + 'pipe-metadata' => array( + 'message-template' => '!remaining/!total', + ), + 'output-data-type' => 'format-list', + 'aliases' => array('search:status'), + ), + ); + $items['search-index'] = array( + 'description' => 'Index the remaining search items without wiping the index.', + 'drupal dependencies' => array('search'), + ); + $items['search-reindex'] = array( + 'description' => 'Force the search index to be rebuilt.', + 'drupal dependencies' => array('search'), + 'options' => array( + 'immediate' => 'Rebuild the index immediately, instead of waiting for cron.', + ), + 'aliases' => array('search:index'), + ); + return $items; +} + +function drush_search_status() { + list($remaining, $total) = _drush_search_status(); + return array( + 'remaining' => $remaining, + 'total' => $total, + ); +} + +function _drush_search_status() { + $remaining = 0; + $total = 0; + if (drush_drupal_major_version() >= 8) { + $search_page_repository = \Drupal::service('search.search_page_repository'); + foreach ($search_page_repository->getIndexableSearchPages() as $entity) { + $status = $entity->getPlugin()->indexStatus(); + $remaining += $status['remaining']; + $total += $status['total']; + } + } + elseif (drush_drupal_major_version() == 7) { + foreach (variable_get('search_active_modules', array('node', 'user')) as $module) { + drush_include_engine('drupal', 'environment'); + $status = drush_module_invoke($module, 'search_status'); + $remaining += $status['remaining']; + $total += $status['total']; + } + } + else { + drush_include_engine('drupal', 'environment'); + foreach (drush_module_implements('search') as $module) { + // Special case. Apachesolr recommends disabling core indexing with + // search_cron_limit = 0. Need to avoid infinite status loop. + if ($module == 'node' && variable_get('search_cron_limit', 10) == 0) { + continue; + } + $status = drush_module_invoke($module, 'search', 'status'); + if (isset($status['remaining']) && isset($status['total'])) { + $remaining += $status['remaining']; + $total += $status['total']; + } + } + } + return array($remaining, $total); +} + +function drush_search_index() { + drush_op('_drush_search_index'); + drush_log(dt('The search index has been built.'), LogLevel::OK); +} + +function _drush_search_index() { + list($remaining, $total) = _drush_search_status(); + register_shutdown_function('search_update_totals'); + $failures = 0; + while ($remaining > 0) { + $done = $total - $remaining; + $percent = $done / $total * 100; + drush_log(dt('!percent complete. Remaining items to be indexed: !count', array('!percent' => number_format($percent, 2), '!count' => $remaining)), LogLevel::OK); + $eval = "register_shutdown_function('search_update_totals');"; + + // Use drush_invoke_process() to start subshell. Avoids out of memory issue. + if (drush_drupal_major_version() >= 8) { + $eval = "drush_module_invoke('search', 'cron');"; + } + elseif (drush_drupal_major_version() == 7) { + // If needed, prod drush_module_implements() to recognize our + // hook_node_update_index() implementations. + drush_include_engine('drupal', 'environment'); + $implementations = drush_module_implements('node_update_index'); + if (!in_array('system', $implementations)) { + // Note that this resets module_implements cache. + drush_module_implements('node_update_index', FALSE, TRUE); + } + + foreach (variable_get('search_active_modules', array('node', 'user')) as $module) { + // TODO: Make sure that drush_module_invoke is really available when doing this eval(). + $eval .= " drush_module_invoke('$module', 'update_index');"; + } + } + else { + // If needed, prod module_implements() to recognize our hook_nodeapi() + // implementations. + $implementations = module_implements('nodeapi'); + if (!in_array('system', $implementations)) { + // Note that this resets module_implements cache. + module_implements('nodeapi', FALSE, TRUE); + } + + $eval .= " module_invoke_all('update_index');"; + } + drush_invoke_process('@self', 'php-eval', array($eval)); + $previous_remaining = $remaining; + list($remaining, ) = _drush_search_status(); + // Make sure we're actually making progress. + if ($remaining == $previous_remaining) { + $failures++; + if ($failures == 3) { + drush_log(dt('Indexing stalled with @number items remaining.', array( + '@number' => $remaining, + )), LogLevel::ERROR); + return; + } + } + // Only count consecutive failures. + else { + $failures = 0; + } + } +} + +function drush_search_reindex() { + drush_print(dt("The search index must be fully rebuilt before any new items can be indexed.")); + if (drush_get_option('immediate')) { + drush_print(dt("Rebuilding the index may take a long time.")); + } + if (!drush_confirm(dt('Do you really want to continue?'))) { + return drush_user_abort(); + } + + if (drush_drupal_major_version() == 8) { + // D8 CR: https://www.drupal.org/node/2326575 + $search_page_repository = \Drupal::service('search.search_page_repository'); + foreach ($search_page_repository->getIndexableSearchPages() as $entity) { + $entity->getPlugin()->markForReindex(); + } + } + elseif (drush_drupal_major_version() == 7) { + drush_op('search_reindex'); + } + else { + drush_op('search_wipe'); + } + + if (drush_get_option('immediate')) { + drush_op('_drush_search_index'); + drush_log(dt('The search index has been rebuilt.'), LogLevel::OK); + } + else { + drush_log(dt('The search index will be rebuilt.'), LogLevel::OK); + } +} + +/** + * Fake an implementation of hook_node_update_index() for Drupal 7. + */ +function system_node_update_index($node) { + // Verbose output. + if (drush_get_context('DRUSH_VERBOSE')) { + $nid = $node->nid; + if (is_object($nid)) { + // In D8, this is a FieldItemList. + $nid = $nid->value; + } + + drush_log(dt('Indexing node !nid.', array('!nid' => $nid)), LogLevel::OK); + } +} + +/** + * Fake an implementation of hook_nodeapi() for Drupal 6. + */ +function system_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) { + if ($op == 'update index') { + // Verbose output. + if (drush_get_context('DRUSH_VERBOSE')) { + drush_log(dt('Indexing node !nid.', array('!nid' => $node->nid)), LogLevel::OK); + } + } +} diff --git a/vendor/drush/drush/commands/core/shellalias.drush.inc b/vendor/drush/drush/commands/core/shellalias.drush.inc new file mode 100644 index 0000000000..06c02e9bb8 --- /dev/null +++ b/vendor/drush/drush/commands/core/shellalias.drush.inc @@ -0,0 +1,62 @@ + array_keys($all)); + } +} + +function shellalias_drush_command() { + $items = array(); + + $items['shell-alias'] = array( + 'description' => 'Print all known shell alias records.', + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'arguments' => array( + 'alias' => 'Shell alias to print', + ), + 'outputformat' => array( + 'default' => 'key-value', + 'pipe-format' => 'json', + 'simplify-single' => TRUE, + 'output-data-type' => 'format-list', + ), + 'aliases' => array('sha', 'shell:alias'), + 'examples' => array( + 'drush shell-alias' => 'List all alias records known to drush.', + 'drush shell-alias pull' => 'Print the value of the shell alias \'pull\'.', + ), + ); + return $items; +} + +/** + * Print out the specified shell aliases. + */ +function drush_core_shell_alias($alias = FALSE) { + $shell_aliases = drush_get_context('shell-aliases', array()); + if (!$alias) { + return $shell_aliases; + } + elseif (isset($shell_aliases[$alias])) { + return array($alias => $shell_aliases[$alias]); + } +} diff --git a/vendor/drush/drush/commands/core/site_install.drush.inc b/vendor/drush/drush/commands/core/site_install.drush.inc new file mode 100644 index 0000000000..a9b2693e81 --- /dev/null +++ b/vendor/drush/drush/commands/core/site_install.drush.inc @@ -0,0 +1,286 @@ + 'Install Drupal along with modules/themes/configuration using the specified install profile.', + 'arguments' => array( + // In Drupal 7 installation profiles can be marked as exclusive by placing + // a + // @code + // exclusive: true + // @endcode + // line in the profile's info file. + // See https://www.drupal.org/node/1022020 for more information. + // + // In Drupal 8 you can turn your installation profile into a distribution + // by providing a + // @code + // distribution: + // name: 'Distribution name' + // @endcode + // block in the profile's info YAML file. + // See https://www.drupal.org/node/2210443 for more information. + 'profile' => 'The install profile you wish to run. Defaults to \'default\' in D6, \'standard\' in D7+, unless an install profile is marked as exclusive (or as a distribution in D8+ terminology) in which case that is used.', + 'key=value...' => 'Any additional settings you wish to pass to the profile. Fully supported on D7+, partially supported on D6 (single step configure forms only). The key is in the form [form name].[parameter name] on D7 or just [parameter name] on D6.', + ), + 'options' => array( + 'db-url' => array( + 'description' => 'A Drupal 6 style database URL. Only required for initial install - not re-install.', + 'example-value' => 'mysql://root:pass@host/db', + ), + 'db-prefix' => 'An optional table prefix to use for initial install. Can be a key-value array of tables/prefixes in a drushrc file (not the command line).', + 'db-su' => array( + 'description' => 'Account to use when creating a new database. Must have Grant permission (mysql only). Optional.', + 'example-value' => 'root', + ), + 'db-su-pw' => array( + 'description' => 'Password for the "db-su" account. Optional.', + 'example-value' => 'pass', + ), + 'account-name' => 'uid1 name. Defaults to admin', + 'account-pass' => 'uid1 pass. Defaults to a randomly generated password. If desired, set a fixed password in drushrc.php.', + 'account-mail' => 'uid1 email. Defaults to admin@example.com', + 'locale' => array( + 'description' => 'A short language code. Sets the default site language. Language files must already be present. You may use download command to get them.', + 'example-value' => 'en-GB', + ), + 'clean-url'=> 'Defaults to clean; use --no-clean-url to disable. Note that Drupal 8 and later requires clean.', + 'site-name' => 'Defaults to Site-Install', + 'site-mail' => 'From: for system mailings. Defaults to admin@example.com', + 'sites-subdir' => array( + 'description' => "Name of directory under 'sites' which should be created. Only needed when the subdirectory does not already exist. Defaults to 'default'", + 'value' => 'required', + 'example-value' => 'directory_name', + ), + 'config-dir' => 'A path pointing to a full set of configuration which should be imported after installation.', + ), + 'examples' => array( + 'drush site-install expert --locale=uk' => '(Re)install using the expert install profile. Set default language to Ukrainian.', + 'drush site-install --db-url=mysql://root:pass@localhost:port/dbname' => 'Install using the specified DB params.', + 'drush site-install --db-url=sqlite://sites/example.com/files/.ht.sqlite' => 'Install using SQLite (D7+ only).', + 'drush site-install --account-name=joe --account-pass=mom' => 'Re-install with specified uid1 credentials.', + 'drush site-install standard install_configure_form.site_default_country=FR my_profile_form.my_settings.key=value' => 'Pass additional arguments to the profile (D7 example shown here - for D6, omit the form id).', + "drush site-install standard install_configure_form.update_status_module='array(FALSE,FALSE)'" => 'Disable email notification during install and later (D7). If your server has no mail transfer agent, this gets rid of an error during install.', + 'drush site-install standard install_configure_form.enable_update_status_module=NULL install_configure_form.enable_update_status_emails=NULL' => 'Disable email notification during install and later (D8). If your server has no mail transfer agent, this gets rid of an error during install.', + ), + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_ROOT, + 'aliases' => array('si', 'site:install'), + ); + return $items; +} + +/** + * Implements hook_drush_help_alter(). + */ +function site_install_drush_help_alter(&$command) { + // Drupal version-specific customizations. + if ($command['command'] == 'site-install') { + if (drush_drupal_major_version() >= 8) { + unset($command['options']['clean-url']); + } + else { + unset($command['options']['config-dir']); + } + } +} + +/** + * Command validate. + */ +function drush_core_site_install_validate() { + if ($sites_subdir = drush_get_option('sites-subdir')) { + $lower = strtolower($sites_subdir); + if ($sites_subdir != $lower) { + drush_log(dt('Only lowercase sites-subdir are valid. Switching to !lower.', array('!lower' => $lower)), LogLevel::WARNING); + drush_set_option('sites-subdir', $lower); + } + // Make sure that we will bootstrap to the 'sites-subdir' site. + drush_set_context('DRUSH_SELECTED_URI', 'http://' . $sites_subdir); + drush_sitealias_create_self_alias(); + } + + if ($config = drush_get_option('config-dir')) { + if (!file_exists($config)) { + return drush_set_error('config_import_target', 'The config source directory does not exist.'); + } + if (!is_dir($config)) { + return drush_set_error('config_import_target', 'The config source is not a directory.'); + } + $configFiles = glob("$config/*.yml"); + if (empty($configFiles)) { + drush_log(dt('Configuration import directory !config does not contain any configuration; will skip import.', array('!config' => $config)), LogLevel::WARNING); + drush_set_option('config-dir', ''); + } + } +} + +/** + * Perform setup tasks for installation. + */ +function drush_core_pre_site_install($profile = NULL) { + $sql = drush_sql_get_class(); + if (!$db_spec = $sql->db_spec()) { + drush_set_error(dt('Could not determine database connection parameters. Pass --db-url option.')); + return; + } + + // Make sure URI is set so we get back a proper $alias_record. Needed for quick-drupal. + _drush_bootstrap_selected_uri(); + + $alias_record = drush_sitealias_get_record('@self'); + $sites_subdir = drush_sitealias_local_site_path($alias_record); + // Override with sites-subdir if specified. + if ($dir = drush_get_option('sites-subdir')) { + $sites_subdir = "sites/$dir"; + } + $conf_path = $sites_subdir; + // Handle the case where someuse uses --variables to set the file public path. Won't work on D8+. + $files = !empty($GLOBALS['conf']['files_public_path']) ? $GLOBALS['conf']['files_public_path'] : "$conf_path/files"; + $settingsfile = "$conf_path/settings.php"; + $sitesfile = "sites/sites.php"; + $default = realpath($alias_record['root'] . '/sites/default'); + $sitesfile_write = drush_drupal_major_version() >= 8 && $conf_path != $default && !file_exists($sitesfile); + + if (!file_exists($settingsfile)) { + $msg[] = dt('create a @settingsfile file', array('@settingsfile' => $settingsfile)); + } + if ($sitesfile_write) { + $msg[] = dt('create a @sitesfile file', array('@sitesfile' => $sitesfile)); + } + if ($sql->db_exists()) { + $msg[] = dt("DROP all tables in your '@db' database.", array('@db' => $db_spec['database'])); + } + else { + $msg[] = dt("CREATE the '@db' database.", array('@db' => $db_spec['database'])); + } + + if (!drush_confirm(dt('You are about to ') . implode(dt(' and '), $msg) . ' Do you want to continue?')) { + return drush_user_abort(); + } + + // Can't install without sites subdirectory and settings.php. + if (!file_exists($conf_path)) { + if (!drush_mkdir($conf_path) && !drush_get_context('DRUSH_SIMULATE')) { + drush_set_error(dt('Failed to create directory @conf_path', array('@conf_path' => $conf_path))); + return; + } + } + else { + drush_log(dt('Sites directory @subdir already exists - proceeding.', array('@subdir' => $conf_path))); + } + + if (!drush_file_not_empty($settingsfile)) { + if (!drush_op('copy', 'sites/default/default.settings.php', $settingsfile) && !drush_get_context('DRUSH_SIMULATE')) { + return drush_set_error(dt('Failed to copy sites/default/default.settings.php to @settingsfile', array('@settingsfile' => $settingsfile))); + } + } + + if (drush_drupal_major_version() == 6) { + if (!_drush_core_site_install_add_db_url_to_settings($settingsfile, $db_spec)) { + return drush_set_error(dt('Failed to write database settings to @settingsfile', array('@settingsfile' => $settingsfile))); + } + } + + // Write an empty sites.php if we are on D8 and using multi-site. + if ($sitesfile_write) { + if (!drush_op('copy', 'sites/example.sites.php', $sitesfile) && !drush_get_context('DRUSH_SIMULATE')) { + return drush_set_error(dt('Failed to copy sites/example.sites.php to @sitesfile', array('@sitesfile' => $sitesfile))); + } + } + + // We need to be at least at DRUSH_BOOTSTRAP_DRUPAL_SITE to select the site uri to install to + define('MAINTENANCE_MODE', 'install'); + if (drush_drupal_major_version() == 6) { + // The Drupal 6 installer needs to bootstrap up to the specified site. + drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION); + } + else { + drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_SITE); + } + + if (!$sql->drop_or_create($db_spec)) { + return drush_set_error(dt('Failed to create database: @error', array('@error' => implode(drush_shell_exec_output())))); + } + + return TRUE; +} + +function _drush_core_site_install_add_db_url_to_settings($settingsfile, $db_spec) { + $settingsContents = file_get_contents($settingsfile); + // There's no need to write $db_url if it's already set. + // The regexp below will allow one arbitrary character before $db_url. + // Allowing more/unlimited chars would be unwise as it would match $db_url + // occurrences inside comments. + if (preg_match('#^.?\$db_url *=#m', $settingsContents)) { + return true; + } + // On D6, we have to write $db_url ourselves. In D7+, the installer does it. + if (file_put_contents($settingsfile, "\n" . '$db_url = \'' . drush_convert_db_url(drush_get_option('db-url')) . "';\n", FILE_APPEND) === false) { + return false; + } + + // Instead of parsing and performing string replacement on the configuration file, + // the options are appended and override the defaults. + // Database table prefix + if (!empty($db_spec['db_prefix'])) { + if (is_array($db_spec['db_prefix'])) { + // Write db_prefix configuration as an array + $db_prefix_config = '$db_prefix = ' . var_export($db_spec['db_prefix'], TRUE) . ';'; + } + else { + // Write db_prefix configuration as a string + $db_prefix_config = '$db_prefix = \'' . $db_spec['db_prefix'] . '\';'; + } + file_put_contents($settingsfile, "\n" . $db_prefix_config . "\n", FILE_APPEND); + } + return true; +} + + +/** + * Command callback. + */ +function drush_core_site_install($profile = NULL) { + $args = func_get_args(); + $form_options = array(); + + if ($args) { + // The first argument is the profile. + $profile = array_shift($args); + // Subsequent arguments are additional form values. + foreach ($args as $arg) { + list($key, $value) = explode('=', $arg, 2); + + // Allow for numeric and NULL values to be passed in. + if (is_numeric($value)) { + $value = intval($value); + } + elseif ($value == 'NULL') { + $value = NULL; + } + + $form_options[$key] = $value; + } + } + + // If the profile is not explicitly set, default to the 'minimal' for an issue-free config import. + if (empty($profile) && drush_get_option('config-dir')) { + $profile = 'minimal'; + } + + drush_include_engine('drupal', 'site_install'); + drush_core_site_install_version($profile, $form_options); + + // Post installation, run the configuration import. + if ($config = drush_get_option('config-dir')) { + // Set the destination site UUID to match the source UUID, to bypass a core fail-safe. + $source_storage = new FileStorage($config); + $options = ['yes' => TRUE]; + drush_invoke_process('@self', 'config-set', array('system.site', 'uuid', $source_storage->read('system.site')['uuid']), $options); + // Run a full configuration import. + drush_invoke_process('@self', 'config-import', array(), array('source' => $config) + $options); + } +} diff --git a/vendor/drush/drush/commands/core/sitealias.drush.inc b/vendor/drush/drush/commands/core/sitealias.drush.inc new file mode 100644 index 0000000000..3ac59a9a01 --- /dev/null +++ b/vendor/drush/drush/commands/core/sitealias.drush.inc @@ -0,0 +1,398 @@ + 'drush_sitealias_print', + 'description' => 'Print site alias records for all known site aliases and local sites.', + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'arguments' => array( + 'site' => 'Site specification to print', + ), + 'options' => array( + 'with-db' => 'Include the databases structure in the full alias record.', + 'with-db-url' => 'Include the short-form db-url in the full alias record.', + 'no-db' => 'Do not include the database record in the full alias record (default).', + 'with-optional' => 'Include optional default items.', + 'alias-name' => 'For a single alias, set the name to use in the output.', + 'local-only' => 'Only display sites that are available on the local system (remote-site not set, and Drupal root exists).', + 'show-hidden' => 'Include hidden internal elements in site alias output', + ), + 'outputformat' => array( + 'default' => 'config', + 'pipe-format' => 'var_export', + 'variable-name' => 'aliases', + 'hide-empty-fields' => TRUE, + 'private-fields' => 'password', + 'field-labels' => array('#name' => 'Name', 'root' => 'Root', 'uri' => 'URI', 'remote-host' => 'Host', 'remote-user' => 'User', 'remote-port' => 'Port', 'os' => 'OS', 'ssh-options' => 'SSH options', 'php' => 'PHP'), + 'fields-default' => array('#name', 'root', 'uri', 'remote-host', 'remote-user'), + 'field-mappings' => array('name' => '#name'), + 'output-data-type' => 'format-table', + ), + 'aliases' => array('sa', 'site:alias'), + 'examples' => array( + 'drush site-alias' => 'List all alias records known to drush.', + 'drush site-alias @dev' => 'Print an alias record for the alias \'dev\'.', + 'drush @none site-alias' => 'Print only actual aliases; omit multisites from the local Drupal installation.', + ), + 'topics' => array('docs-aliases'), + ); + $items['site-set'] = array( + 'description' => 'Set a site alias to work on that will persist for the current session.', + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'handle-remote-commands' => TRUE, + 'arguments' => array( + 'site' => 'Site specification to use, or "-" for previous site. Omit this argument to "unset"', + ), + 'aliases' => array('use', 'site:set'), + 'examples' => array( + 'drush site-set @dev' => 'Set the current session to use the @dev alias.', + 'drush site-set user@server/path/to/drupal#sitename' => 'Set the current session to use a remote site via site specification.', + 'drush site-set /path/to/drupal#sitename' => 'Set the current session to use a local site via site specification.', + 'drush site-set -' => 'Go back to the previously-set site (like `cd -`).', + 'drush site-set' => 'Without an argument, any existing site becomes unset.', + ), + ); + return $items; +} + +/** + * Command argument complete callback. + * + * @return + * Array of available site aliases. + */ +function sitealias_site_alias_complete() { + return array('values' => array_keys(_drush_sitealias_all_list())); +} + +/** + * Command argument complete callback. + * + * @return + * Array of available site aliases. + */ +function sitealias_site_set_complete() { + return array('values' => array_keys(_drush_sitealias_all_list())); +} + +/** + * Return a list of all site aliases known to drush. + * + * The array key is the site alias name, and the array value + * is the site specification for the given alias. + */ +function _drush_sitealias_alias_list() { + return drush_get_context('site-aliases'); +} + +/** + * Return a list of all of the local sites at the current drupal root. + * + * The array key is the site folder name, and the array value + * is the site specification for that site. + */ +function _drush_sitealias_site_list() { + $site_list = array(); + $base_path = drush_get_context('DRUSH_DRUPAL_ROOT'); + if ($base_path) { + $base_path .= '/sites'; + $files = drush_scan_directory($base_path, '/settings\.php/', array('.', '..', 'CVS', 'all'), 0, 1); + foreach ($files as $filename => $info) { + if ($info->basename == 'settings.php') { + $alias_record = drush_sitealias_build_record_from_settings_file($filename); + if (!empty($alias_record)) { + $site_list[drush_sitealias_uri_to_site_dir($alias_record['uri'])] = $alias_record; + } + } + } + } + return $site_list; +} + +/** + * Return the list of all site aliases and all local sites. + */ +function _drush_sitealias_all_list() { + drush_sitealias_load_all(); + return array_merge(_drush_sitealias_alias_list(), _drush_sitealias_site_list()); +} + +/** + * Return the list of site aliases (remote or local) that the + * user specified on the command line. If none were specified, + * then all are returned. + */ +function _drush_sitealias_user_specified_list() { + $command = drush_get_command(); + $specifications = $command['arguments']; + $site_list = array(); + + // Iterate over the arguments and convert them to alias records + if (!empty($specifications)) { + list($site_list, $not_found) = drush_sitealias_resolve_sitespecs($specifications); + if (!empty($not_found)) { + return drush_set_error('DRUSH_ALIAS_NOT_FOUND', dt("Not found: @list", array("@list" => implode(', ', $not_found)))); + } + } + // If the user provided no args, then we will return everything. + else { + drush_set_default_outputformat('list'); + $site_list = _drush_sitealias_all_list(); + + // Filter out the hidden items + foreach ($site_list as $site_name => $one_site) { + if (array_key_exists('#hidden', $one_site)) { + unset($site_list[$site_name]); + } + } + } + + // Filter for only local sites if specified. + if (drush_get_option('local-only', FALSE)) { + foreach ($site_list as $site_name => $one_site) { + if ( (array_key_exists('remote-site', $one_site)) || + (!array_key_exists('root', $one_site)) || + (!is_dir($one_site['root'])) + ) { + unset($site_list[$site_name]); + } + } + } + return $site_list; +} + +/** + * Print out the specified site aliases (or else all) using the format + * specified. + */ +function drush_sitealias_print() { + // Try to get the @self alias to be defined. + $phase = drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_SITE); + $site_list = _drush_sitealias_user_specified_list(); + if ($site_list === FALSE) { + return FALSE; + } + ksort($site_list); + $with_db = (drush_get_option('with-db') != NULL) || (drush_get_option('with-db-url') != NULL); + + $site_specs = array(); + foreach ($site_list as $site => $alias_record) { + $result_record = _drush_sitealias_prepare_record($alias_record); + $site_specs[$site] = $result_record; + } + ksort($site_specs); + return $site_specs; +} + +/** + * Given a site alias name, print out a php-syntax + * representation of it. + * + * @param alias_record + * The name of the site alias to print + */ +function _drush_sitealias_prepare_record($alias_record) { + $output_db = drush_get_option('with-db'); + $output_db_url = drush_get_option('with-db-url'); + $output_optional_items = drush_get_option('with-optional'); + + // Make sure that the default items have been added for all aliases + _drush_sitealias_add_static_defaults($alias_record); + + // Include the optional items, if requested + if ($output_optional_items) { + _drush_sitealias_add_transient_defaults($alias_record); + } + + drush_sitealias_resolve_path_references($alias_record); + + if (isset($output_db_url) || isset($output_db)) { + drush_sitealias_add_db_settings($alias_record); + } + // If the user specified --with-db-url, then leave the + // 'db-url' entry in the alias record (unless it is not + // set, in which case we will leave the 'databases' record instead). + if (isset($output_db_url)) { + if (!isset($alias_record['db-url'])) { + $alias_record['db-url'] = drush_sitealias_convert_databases_to_db_url($alias_record['databases']); + } + unset($alias_record['databases']); + } + // If the user specified --with-db, then leave the + // 'databases' entry in the alias record. + else if (isset($output_db)) { + unset($alias_record['db-url']); + } + // If neither --with-db nor --with-db-url were specified, + // then remove both the 'db-url' and the 'databases' entries. + else { + unset($alias_record['db-url']); + unset($alias_record['databases']); + } + + // We don't want certain fields to go into the output + if (!drush_get_option('show-hidden')) { + foreach ($alias_record as $key => $value) { + if ($key[0] == '#') { + unset($alias_record[$key]); + } + } + } + + // We only want to output the 'root' item; don't output the '%root' path alias + if (array_key_exists('path-aliases', $alias_record) && array_key_exists('%root', $alias_record['path-aliases'])) { + unset($alias_record['path-aliases']['%root']); + // If there is nothing left in path-aliases, then clear it out + if (count($alias_record['path-aliases']) == 0) { + unset($alias_record['path-aliases']); + } + } + + return $alias_record; +} + +function _drush_sitealias_print_record($alias_record, $site_alias = '') { + $result_record = _drush_sitealias_prepare_record($alias_record); + + // The alias name will be the same as the site alias name, + // unless the user specified some other name on the command line. + $alias_name = drush_get_option('alias-name'); + if (!isset($alias_name)) { + $alias_name = $site_alias; + if (empty($alias_name) || is_numeric($alias_name)) { + $alias_name = drush_sitealias_uri_to_site_dir($result_record['uri']); + } + } + + // Alias names contain an '@' when referenced, but do + // not contain an '@' when defined. + if (substr($alias_name,0,1) == '@') { + $alias_name = substr($alias_name,1); + } + + $exported_alias = var_export($result_record, TRUE); + drush_print('$aliases[\'' . $alias_name . '\'] = ' . $exported_alias . ';'); +} + +/** + * Use heuristics to attempt to convert from a site directory to a URI. + * This function should only be used when the URI really is unknown, as + * the mapping is not perfect. + * + * @param site_dir + * A directory, such as domain.com.8080.drupal + * + * @return string + * A uri, such as http://domain.com:8080/drupal + */ +function _drush_sitealias_site_dir_to_uri($site_dir) { + // Protect IP addresses NN.NN.NN.NN by converting them + // temporarily to NN_NN_NN_NN for now. + $uri = preg_replace("/([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/", "$1_$2_$3_$4", $site_dir); + // Convert .[0-9]+. into :[0-9]+/ + $uri = preg_replace("/\.([0-9]+)\./", ":$1/", $uri); + // Convert .[0-9]$ into :[0-9] + $uri = preg_replace("/\.([0-9]+)$/", ":$1", $uri); + // Convert .(com|net|org|info). into .(com|net|org|info)/ + $uri = str_replace(array('.com.', '.net.', '.org.', '.info.'), array('.com/', '.net/', '.org/', '.info/'), $uri); + + // If there is a / then convert every . after the / to / + // Then again, if we did this we would break if the path contained a "." + // I hope that the path would never contain a "."... + $pos = strpos($uri, '/'); + if ($pos !== false) { + $uri = substr($uri, 0, $pos + 1) . str_replace('.', '/', substr($uri, $pos + 1)); + } + + // n.b. this heuristic works all the time if there is a port, + // it also works all the time if there is a port and no path, + // but it does not work for domains such as .co.jp with no path, + // and it can fail horribly if someone makes a domain like "info.org". + // Still, I think this is the best we can do short of consulting DNS. + + // Convert from NN_NN_NN_NN back to NN.NN.NN.NN + $uri = preg_replace("/([0-9]+)_([0-9]+)_([0-9]+)_([0-9]+)/", "$1.$2.$3.$4", $site_dir); + + return 'http://' . $uri; +} + +/** + * Validation callback for drush site-set. + */ +function drush_sitealias_site_set_validate() { + if (!function_exists('posix_getppid')) { + $args = array('!command' => 'site-set', '!dependencies' => 'POSIX'); + return drush_set_error('DRUSH_COMMAND_PHP_DEPENDENCY_ERROR', dt('Command !command needs the following PHP extensions installed/enabled to run: !dependencies.', $args)); + } +} + +/** + * Set the DRUPAL_SITE variable by writing it out to a temporary file that we + * then source for persistent site switching. + * + * @param site + * A valid site specification. + */ +function drush_sitealias_site_set($site = '@none') { + if ($filename = drush_sitealias_get_envar_filename()) { + $last_site_filename = drush_sitealias_get_envar_filename('drush-drupal-prev-site-'); + if ($site == '-') { + if (file_exists($last_site_filename)) { + $site = file_get_contents($last_site_filename); + } + else { + $site = '@none'; + } + } + if ($site == '@self') { + $path = drush_cwd(); + $site_record = drush_sitealias_lookup_alias_by_path($path, TRUE); + if (isset($site_record['#name'])) { + $site = '@' . $site_record['#name']; + } + else { + $site = '@none'; + } + // Using 'site-set @self' is quiet if there is no change. + $current = is_file($filename) ? trim(file_get_contents($filename)) : "@none"; + if ($current == $site) { + return; + } + } + if (_drush_sitealias_set_context_by_name($site)) { + if (file_exists($filename)) { + @unlink($last_site_filename); + @rename($filename, $last_site_filename); + } + $success_message = dt("Site set to !site", array('!site' => $site)); + if ($site == '@none') { + if (drush_delete_dir($filename)) { + drush_print($success_message); + } + } + elseif (drush_mkdir(dirname($filename), TRUE)) { + if (file_put_contents($filename, $site)) { + drush_print($success_message); + drush_log(dt("Site information stored in !file", array('!file' => $filename))); + } + } + } + else { + return drush_set_error('DRUPAL_SITE_NOT_FOUND', dt("Could not find a site definition for !site.", array('!site' => $site))); + } + } +} diff --git a/vendor/drush/drush/commands/core/ssh.drush.inc b/vendor/drush/drush/commands/core/ssh.drush.inc new file mode 100644 index 0000000000..ceb4af87af --- /dev/null +++ b/vendor/drush/drush/commands/core/ssh.drush.inc @@ -0,0 +1,82 @@ + 'Connect to a Drupal site\'s server via SSH for an interactive session or to run a shell command', + 'arguments' => array( + 'bash' => 'Bash to execute on target. Optional, except when site-alias is a list.', + ), + 'options' => array( + 'cd' => "Directory to change to. Use a full path, TRUE for the site's Drupal root directory, or --no-cd for the ssh default (usually the remote user's home directory). Defaults to the Drupal root.", + ) + drush_shell_exec_proc_build_options(), + 'handle-remote-commands' => TRUE, + 'strict-option-handling' => TRUE, + 'examples' => array( + 'drush @mysite ssh' => 'Open an interactive shell on @mysite\'s server.', + 'drush @prod ssh ls /tmp' => 'Run "ls /tmp" on @prod site. If @prod is a site list, then ls will be executed on each site.', + 'drush @prod ssh git pull' => 'Run "git pull" on the Drupal root directory on the @prod site.', + ), + 'aliases' => array('ssh', 'site:ssh'), + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'topics' => array('docs-aliases'), + ); + return $items; +} + +/** + * Command callback. + */ +function drush_ssh_site_ssh($command = NULL) { + // Get all of the args and options that appear after the command name. + $args = drush_get_original_cli_args_and_options(); + // n.b. we do not escape the first (0th) arg to allow `drush ssh 'ls /path'` + // to work in addition to the preferred form of `drush ssh ls /path`. + // Supporting the legacy form means that we cannot give the full path to an + // executable if it contains spaces. + for ($x = 1; $x < count($args); $x++) { + $args[$x] = drush_escapeshellarg($args[$x]); + } + $command = implode(' ', $args); + if (!$alias = drush_get_context('DRUSH_TARGET_SITE_ALIAS')) { + return drush_set_error('DRUSH_MISSING_TARGET_ALIAS', 'A site alias is required. The way you call ssh command has changed to `drush @alias ssh`.'); + } + $site = drush_sitealias_get_record($alias); + // If we have multiple sites, run ourselves on each one. Set context back when done. + if (isset($site['site-list'])) { + if (empty($command)) { + drush_set_error('DRUSH_SITE_SSH_COMMAND_REQUIRED', dt('A command is required when multiple site aliases are specified.')); + return; + } + foreach ($site['site-list'] as $alias_single) { + drush_set_context('DRUSH_TARGET_SITE_ALIAS', $alias_single); + drush_ssh_site_ssh($command); + } + drush_set_context('DRUSH_TARGET_SITE_ALIAS', $alias); + return; + } + + if (!drush_sitealias_is_remote_site($alias)) { + // Local sites run their bash without SSH. + $return = drush_invoke_process('@self', 'core-execute', array($command), array('escape' => FALSE)); + return $return['object']; + } + + // We have a remote site - build ssh command and run. + $interactive = FALSE; + $cd = drush_get_option('cd', TRUE); + if (empty($command)) { + $command = 'bash -l'; + $interactive = TRUE; + } + $cmd = drush_shell_proc_build($site, $command, $cd, $interactive); + $status = drush_shell_proc_open($cmd); + if ($status != 0) { + return drush_set_error('DRUSH_SITE_SSH_ERROR', dt('An error @code occurred while running the command `@command`', array('@command' => $cmd, '@code' => $status))); + } +} diff --git a/vendor/drush/drush/commands/core/state.drush.inc b/vendor/drush/drush/commands/core/state.drush.inc new file mode 100644 index 0000000000..cd78014f56 --- /dev/null +++ b/vendor/drush/drush/commands/core/state.drush.inc @@ -0,0 +1,137 @@ + 'Display a state value.', + 'arguments' => array( + 'key' => 'The key name.', + ), + 'required-arguments' => 1, + 'examples' => array( + 'drush state-get system.cron_last' => 'Displays last cron run timestamp', + ), + 'outputformat' => array( + 'default' => 'json', + 'pipe-format' => 'json', + ), + 'aliases' => array('sget', 'state:get'), + 'core' => array('8+'), + ); + + $items['state-set'] = array( + 'description' => 'Set a state value.', + 'arguments' => array( + 'key' => 'The state key, for example "system.cron_last".', + 'value' => 'The value to assign to the state key. Use \'-\' to read from STDIN.', + ), + 'required arguments' => 2, + 'options' => array( + 'format' => array( + 'description' => 'Deprecated. See input-format option.', + 'example-value' => 'boolean', + 'value' => 'required', + ), + 'input-format' => array( + 'description' => 'Type for the value. Use "auto" to detect format from value. Other recognized values are string, integer float, or boolean for corresponding primitive type, or json, yaml for complex types.', + 'example-value' => 'boolean', + 'value' => 'required', + ), + // A convenient way to pass a multiline value within a backend request. + 'value' => array( + 'description' => 'The value to assign to the state key (if any).', + 'hidden' => TRUE, + ), + ), + 'examples' => array( + 'drush state-set system.cron_last 1406682882 --format=integer' => 'Sets a timestamp for last cron run.', + 'php -r "print json_encode(array(\'drupal\', \'simpletest\'));" | drush state-set --format=json foo.name -'=> 'Set a key to a complex value (e.g. array)', + ), + 'aliases' => array('sset', 'state:set'), + 'core' => array('8+'), + ); + + $items['state-delete'] = array( + 'description' => 'Delete a state value.', + 'arguments' => array( + 'key' => 'The state key, for example "system.cron_last".', + ), + 'required arguments' => 1, + 'examples' => array( + 'drush state-del system.cron_last' => 'Delete state entry for system.cron_last.', + ), + 'aliases' => array('sdel', 'state:delete'), + 'core' => array('8+'), + ); + + return $items; +} + +/** + * State get command callback. + * + * @state $key + * The state key. + */ +function drush_state_get($key = NULL) { + return \Drupal::state()->get($key); +} + +/** + * State set command callback. + * + * @param $key + * The config key. + * @param $value + * The data to save to state. + */ +function drush_state_set($key = NULL, $value = NULL) { + // This hidden option is a convenient way to pass a value without passing a key. + $value = drush_get_option('value', $value); + + if (!isset($value)) { + return drush_set_error('DRUSH_STATE_ERROR', dt('No state value specified.')); + } + + // Special flag indicating that the value has been passed via STDIN. + if ($value === '-') { + $value = stream_get_contents(STDIN); + } + + // If the value is a string (usual case, unless we are called from code), + // then format the input. + if (is_string($value)) { + $value = drush_value_format($value, drush_get_option('format', 'auto')); + } + + \Drupal::state()->set($key, $value); +} + +/** + * State delete command callback. + * + * @state $key + * The state key. + */ +function drush_state_delete($key = NULL) { + \Drupal::state()->delete($key); +} diff --git a/vendor/drush/drush/commands/core/topic.drush.inc b/vendor/drush/drush/commands/core/topic.drush.inc new file mode 100644 index 0000000000..3fa63ecf27 --- /dev/null +++ b/vendor/drush/drush/commands/core/topic.drush.inc @@ -0,0 +1,105 @@ + 'Read detailed documentation on a given topic.', + 'arguments' => array( + 'topic name' => 'The name of the topic you wish to view. If omitted, list all topic descriptions (and names in parenthesis).', + ), + 'examples' => array( + 'drush topic' => 'Show all available topics.', + 'drush topic docs-context' => 'Show documentation for the drush context API', + 'drush docs-context' => 'Show documentation for the drush context API', + ), + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'remote-tty' => TRUE, + 'aliases' => array('topic', 'core:topic'), + 'topics' => array('docs-readme'), + ); + + return $items; +} + +/** + * Implement hook_drush_help_alter(). Show 'Topics' section on help detail. + */ +function topic_drush_help_alter(&$command) { + $implemented = drush_get_commands(); + foreach ($command['topics'] as $topic_name) { + // We have a related topic. Inject into the $command so the topic displays. + $command['sections']['topic_section'] = 'Topics'; + $command['topic_section'][$topic_name] = $implemented[$topic_name]['description']; + } +} + +/** + * A command callback. + * + * Show a choice list of available topics and then dispatch to the respective command. + * + * @param string $topic_name + * A command name. + */ +function drush_topic_core_topic($topic_name = NULL) { + $commands = drush_get_commands(); + $topics = drush_get_topics(); + if (isset($topic_name)) { + foreach (drush_get_topics() as $key => $topic) { + if (strstr($key, $topic_name) === FALSE) { + unset($topics[$key]); + } + } + } + if (empty($topics)) { + return drush_set_error('DRUSH_NO_SUCH_TOPIC', dt("No topics on !topic found.", array('!topic' => $topic_name))); + } + if (count($topics) > 1) { + // Show choice list. + foreach ($topics as $key => $topic) { + $choices[$key] = $topic['description']; + } + natcasesort($choices); + if (!$topic_name = drush_choice($choices, dt('Choose a topic'), '!value (!key)', array(5))) { + return drush_user_abort(); + } + } + else { + $keys = array_keys($topics); + $topic_name = array_pop($keys); + } + return drush_dispatch($commands[$topic_name]); +} + +/** + * A command argument complete callback. + * + * @return + * Available topic keys. + */ +function topic_core_topic_complete() { + return array('values' => array_keys(drush_get_topics())); +} + +/** + * Retrieve all defined topics + */ +function drush_get_topics() { + $commands = drush_get_commands(); + foreach ($commands as $key => $command) { + if (!empty($command['topic']) && empty($command['is_alias'])) { + $topics[$key] = $command; + } + } + return $topics; +} diff --git a/vendor/drush/drush/commands/core/usage.drush.inc b/vendor/drush/drush/commands/core/usage.drush.inc new file mode 100644 index 0000000000..b39d2be62e --- /dev/null +++ b/vendor/drush/drush/commands/core/usage.drush.inc @@ -0,0 +1,158 @@ + DRUSH_BOOTSTRAP_NONE, + 'description' => 'Show Drush usage information that has been logged but not sent. ' . $disclaimer, + 'hidden' => TRUE, + 'examples' => array( + 'drush usage-show' => 'Show cached usage statistics.', + '$options[\'drush_usage_log\'] = TRUE;' => 'Specify in a .drushrc.php file that usage information should be logged locally in a usage statistics file.', + ), + 'aliases' => array('ushow', 'usage:show'), + ); + $items['usage-send'] = array( + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'hidden' => TRUE, + 'description' => 'Send anonymous Drush usage information to statistics logging site. ' . $disclaimer, + 'examples' => array( + 'drush usage-send' => 'Immediately send cached usage statistics.', + '$options[\'drush_usage_send\'] = TRUE;' => 'Specify in a .drushrc.php file that usage information should be sent.', + '$options[\'drush_usage_size\'] = 10240;' => 'Specify the frequency (file size) that usage information should be sent.', + ), + 'aliases' => array('usend', 'usage:send'), + ); + return $items; +} + +/** + * Log and/or send usage data to Mongolab. + * + * An organization can implement own hook_drush_exit() to send data to a + * different endpoint. + */ +function usage_drush_exit() { + // Ignore statistics for simulated commands. (n.b. in simulated mode, _drush_usage_mongolab will print rather than send statistics) + if (!drush_get_context('DRUSH_SIMULATE')) { + $file = _drush_usage_get_file(); + if (drush_get_option('drush_usage_log', FALSE)) { + _drush_usage_log(drush_get_command(), $file); + } + if (drush_get_option('drush_usage_send', FALSE)) { + _drush_usage_mongolab($file, drush_get_option('drush_usage_size', 51200)); + } + } +} + +/** + * Set option to send usage to Mongolab. + * + * See usage_drush_exit() for more information. + */ +function drush_usage_send() { + $file = _drush_usage_get_file(TRUE); + if ($file) { + drush_set_option('drush_usage_send', TRUE); + drush_set_option('drush_usage_size', 0); + drush_print(dt('To automatically send anonymous usage data, add the following to a .drushrc.php file: $options[\'drush_usage_send\'] = TRUE;')); + } + else { + return drush_set_error('DRUSH_NO_USAGE_FILE', dt('No usage file; set $options[\'drush_usage_log\'] = TRUE; in a .drushrc.php file to enable.')); + } +} + +/** + * Displays usage file. + */ +function drush_usage_show() { + $file = _drush_usage_get_file(TRUE); + if ($file) { + $json = '[' . file_get_contents($file) . ']'; + $usage_data = json_decode($json); + foreach ($usage_data as $item) { + $cmd = $item->cmd; + $options = (array) $item->opt; + array_unshift($options, ''); + drush_print($cmd . implode(' --', $options)); + } + } + else { + return drush_set_error('DRUSH_NO_USAGE_FILE', dt('No usage file; set $options[\'drush_usage_log\'] = TRUE; in a .drushrc.php file to enable.')); + } +} + +/** + * Returns path to usage file. + */ +function _drush_usage_get_file($required = FALSE) { + $file = drush_directory_cache('usage') . '/usage.txt'; + if (!file_exists($file) && $required) { + return FALSE; + + } + return $file; +} + +function _drush_usage_log($command, $file) { + $options = drush_get_command_options_extended($command); + + $used = drush_get_merged_options(); + $command_specific = array_intersect(array_keys($used), array_keys($options)); + $record = array( + 'date' => $_SERVER['REQUEST_TIME'], + 'cmd' => $command['command'], + 'opt' => $command_specific, + 'major' => DRUSH_MAJOR_VERSION, + 'minor' => DRUSH_MINOR_VERSION, + 'os' => php_uname('s'), + 'host' => md5(php_uname('n') . get_current_user()), + ); + $prequel = (file_exists($file)) ? ",\n" : ""; + if (file_put_contents($file, $prequel . json_encode($record), FILE_APPEND)) { + drush_log(dt('Logged command and option names to local cache.'), LogLevel::DEBUG); + } + else { + drush_log(dt('Failed to log command and option names to local cache.'), LogLevel::DEBUG); + } +} + +// We only send data periodically to save network traffic and delay. Files +// are sent once they grow over 50KB (configurable). +function _drush_usage_mongolab($file, $min_size_to_send) { + $json = '[' . file_get_contents($file) . ']'; + if (filesize($file) > $min_size_to_send) { + $base = 'https://api.mongolab.com/api/1'; + $apikey = '4eb95456e4b0bcd285d8135d'; // submitter account. + $database = 'usage'; + $collection = 'usage'; + $action = "/databases/$database/collections/$collection"; + $url = $base . $action . "?apiKey=$apikey"; + $header = 'Content-Type: application/json'; + if (!drush_shell_exec("wget -q -O - --no-check-certificate --timeout=20 --header=\"$header\" --post-data %s %s", $json, $url)) { + if (!drush_shell_exec("curl -s --connect-timeout 20 --header \"$header\" --data %s %s", $json, $url)) { + drush_log(dt('Drush usage statistics failed to post.'), LogLevel::DEBUG); + return FALSE; + } + } + drush_log(dt('Drush usage statistics successfully posted.'), LogLevel::DEBUG); + // Empty the usage.txt file. + unlink($file); + return TRUE; + } +} diff --git a/vendor/drush/drush/commands/core/variable.drush.inc b/vendor/drush/drush/commands/core/variable.drush.inc new file mode 100644 index 0000000000..b9a1c4a209 --- /dev/null +++ b/vendor/drush/drush/commands/core/variable.drush.inc @@ -0,0 +1,283 @@ + 'Get a list of some or all site variables and values.', + 'core' => array(6,7), + 'arguments' => array( + 'name' => 'A string to filter the variables by. Variables whose name contains the string will be listed.', + ), + 'examples' => array( + 'drush vget' => 'List all variables and values.', + 'drush vget user' => 'List all variables containing the string "user".', + 'drush vget site_mail --exact' => 'Show only the value of the variable with the exact key "site_mail".', + 'drush vget site_mail --exact --pipe' => 'Show only the variable with the exact key "site_mail" without changing the structure of the output.', + ), + 'options' => array( + 'exact' => "Only get the one variable that exactly matches the specified name. Output will contain only the variable's value.", + ), + 'outputformat' => array( + 'default' => 'yaml', + 'pipe-format' => 'config', + 'variable-name' => 'variables', + 'table-metadata' => array( + 'format' => 'var_export', + ), + ), + 'aliases' => array('vget', 'variable:get'), + ); + $items['variable-set'] = array( + 'description' => "Set a variable.", + 'core' => array(6,7), + 'arguments' => array( + 'name' => 'The name of a variable or the first few letters of its name.', + 'value' => 'The value to assign to the variable. Use \'-\' to read the object from STDIN.', + ), + 'required-arguments' => TRUE, + 'options' => array( + 'yes' => 'Skip confirmation if only one variable name matches.', + 'always-set' => array('description' => 'Older synonym for --exact; deprecated.', 'hidden' => TRUE), + 'exact' => 'The exact name of the variable to set has been provided; do not prompt for similarly-named variables.', + 'format' => array( + 'description' => 'Type for the value. Use "auto" to detect format from value. Other recognized values are string, integer float, or boolean for corresponding primitive type, or json, yaml for complex types.', + 'example-value' => 'boolean', + ), + ), + 'examples' => array( + 'drush vset --yes preprocess_css TRUE' => 'Set the preprocess_css variable to true. Skip confirmation if variable already exists.', + 'drush vset --exact maintenance_mode 1' => 'Take the site offline; skips confirmation even if maintenance_mode variable does not exist. Variable is rewritten to site_offline for Drupal 6.', + 'drush vset pr TRUE' => 'Choose from a list of variables beginning with "pr" to set to (bool)true.', + 'php -r "print json_encode(array(\'drupal\', \'simpletest\'));" | drush vset --format=json project_dependency_excluded_dependencies -'=> 'Set a variable to a complex value (e.g. array)', + ), + 'aliases' => array('vset', 'variable:set'), + ); + $items['variable-delete'] = array( + 'core' => array(6,7), + 'description' => "Delete a variable.", + 'arguments' => array( + 'name' => 'The name of a variable or the first few letters of its name.', + ), + 'required-arguments' => TRUE, + 'options' => array( + 'yes' => 'Skip confirmation if only one variable name matches.', + 'exact' => 'Only delete the one variable that exactly matches the specified name.', + ), + 'examples' => array( + 'drush vdel user_pictures' => 'Delete the user_pictures variable.', + 'drush vdel u' => 'Choose from a list of variables beginning with "u" to delete.', + 'drush vdel -y --exact maintenance_mode' => 'Bring the site back online, skipping confirmation. Variable is rewritten to site_offline for Drupal 6.', + ), + 'aliases' => array('vdel', 'variable:delete'), + ); + + return $items; +} + +/** + * Command argument complete callback. + */ +function variable_variable_get_complete() { + return variable_complete_variables(); +} + +/** + * Command argument complete callback. + */ +function variable_variable_set_complete() { + return variable_complete_variables(); +} + +/** + * Command argument complete callback. + */ +function variable_variable_delete_complete() { + return variable_complete_variables(); +} + +/** + * List variables for completion. + * + * @return + * Array of available variables. + */ +function variable_complete_variables() { + if (drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_FULL)) { + global $conf; + return array('values' => array_keys($conf)); + } +} + +/** + * Command callback. + * List your site's variables. + */ +function drush_variable_get() { + global $conf; + $exact = drush_get_option('exact', FALSE); + + $keys = array_keys($conf); + if ($args = func_get_args()) { + $args[0] = drush_variable_name_adjust($args[0]); + if ($exact) { + $keys = in_array($args[0], $keys) ? array($args[0]) : array(); + } + $keys = preg_grep("/{$args[0]}/", $keys); + } + + // In --exact mode, if --pipe is not set, then simplify the return type. + if ($exact && !drush_get_context('DRUSH_PIPE')) { + $key = reset($keys); + $returns = isset($conf[$key]) ? $conf[$key] : FALSE; + } + else { + foreach ($keys as $name) { + $value = $conf[$name]; + $returns[$name] = $value; + } + } + if (empty($keys)) { + return drush_set_error('No matching variable found.'); + } + else { + return $returns; + } +} + +/** + * Command callback. + * Set a variable. + */ +function drush_variable_set() { + $args = func_get_args(); + $value = $args[1]; + if (!isset($value)) { + return drush_set_error('DRUSH_VARIABLE_ERROR', dt('No value specified.')); + } + + $args[0] = drush_variable_name_adjust($args[0]); + $result = drush_variable_like($args[0]); + + $options[] = "$args[0] ". dt('(new variable)'); + $match = FALSE; + while (!$match && $name = drush_db_result($result)) { + if ($name == $args[0]) { + $options[0] = $name; + $match = TRUE; + } + else { + $options[] = $name; + } + } + + if ($value == '-') { + $value = stream_get_contents(STDIN); + } + + // If the value is a string (usual case, unless we are called from code), + // then format the input + if (is_string($value)) { + $value = drush_value_format($value, drush_get_option('format', 'auto')); + } + + // Format the output for display + if (is_array($value)) { + $display = "\n" . var_export($value, TRUE); + } + elseif (is_integer($value)) { + $display = $value; + } + elseif (is_bool($value)) { + $display = $value ? "TRUE" : "FALSE"; + } + else { + $display = '"' . $value . '"'; + } + + // Check 'always-set' for compatibility with older scripts; --exact is preferred. + $always_set = drush_get_option('always-set', FALSE) || drush_get_option('exact', FALSE); + + if ($always_set || count($options) == 1 || $match) { + variable_set($args[0], $value); + drush_log(dt('!name was set to !value.', array('!name' => $args[0], '!value' => $display)), LogLevel::SUCCESS); + return ''; + } + else { + $choice = drush_choice($options, 'Enter a number to choose which variable to set.'); + if ($choice === FALSE) { + return drush_user_abort(); + } + $choice = $options[$choice]; + $choice = str_replace(' ' . dt('(new variable)'), '', $choice); + drush_op('variable_set', $choice, $value); + drush_log(dt('!name was set to !value', array('!name' => $choice, '!value' => $display)), LogLevel::SUCCESS); + } +} + +/** + * Command callback. + * Delete a variable. + */ +function drush_variable_delete() { + $args = func_get_args(); + $args[0] = drush_variable_name_adjust($args[0]); + // Look for similar variable names. + $result = drush_variable_like($args[0]); + + $options = array(); + while ($name = drush_db_result($result)) { + $options[] = $name; + } + if (drush_get_option('exact', FALSE)) { + $options = in_array($args[0], $options) ? array($args[0]) : array(); + } + + if (count($options) == 0) { + drush_print(dt('!name not found.', array('!name' => $args[0]))); + return ''; + } + + if ((count($options) == 1) && drush_get_context('DRUSH_AFFIRMATIVE')) { + drush_op('variable_del', $args[0]); + drush_log(dt('!name was deleted.', array('!name' => $args[0])), LogLevel::SUCCESS); + return ''; + } + else { + $choice = drush_choice($options, 'Enter a number to choose which variable to delete.'); + if ($choice !== FALSE) { + $choice = $options[$choice]; + drush_op('variable_del', $choice); + drush_log(dt('!choice was deleted.', array('!choice' => $choice)), LogLevel::SUCCESS); + } + } +} + +// Query for similar variable names. +function drush_variable_like($arg) { + return drush_db_select('variable', 'name', 'name LIKE :keyword', array(':keyword' => $arg . '%'), NULL, NULL, 'name'); +} + +// Unify similar variable names across different versions of Drupal +function drush_variable_name_adjust($arg) { + if (($arg == 'maintenance_mode') && (drush_drupal_major_version() < 7)) { + $arg = 'site_offline'; + } + if (($arg == 'site_offline') && (drush_drupal_major_version() >= 7)) { + $arg = 'maintenance_mode'; + } + return $arg; +} diff --git a/vendor/drush/drush/commands/core/views.d8.drush.inc b/vendor/drush/drush/commands/core/views.d8.drush.inc new file mode 100644 index 0000000000..009a1a1659 --- /dev/null +++ b/vendor/drush/drush/commands/core/views.d8.drush.inc @@ -0,0 +1,487 @@ + array('8+'), + 'drupal dependencies' => array('views'), + ); + + $items['views-dev'] = array( + 'description' => 'Set the Views settings to more developer-oriented values.', + 'aliases' => array('vd', 'views:dev'), + ) + $base; + + $items['views-list'] = array( + 'description' => 'Get a list of all views in the system.', + 'aliases' => array('vl', 'views:list'), + 'options' => array( + 'name' => array( + 'description' => 'A string contained in the view\'s name to filter the results with.', + 'example-value' => 'node', + 'value' => 'required', + ), + 'tags' => array( + 'description' => 'A comma-separated list of views tags by which to filter the results.', + 'example-value' => 'default', + 'value' => 'required', + ), + 'status' => array( + 'description' => 'Status of the views by which to filter the results. Choices: enabled, disabled.', + 'example-value' => 'enabled', + 'value' => 'required', + ), + ), + 'examples' => array( + 'drush vl' => 'Show a list of all available views.', + 'drush vl --name=blog' => 'Show a list of views which names contain "blog".', + 'drush vl --tags=tag1,tag2' => 'Show a list of views tagged with "tag1" or "tag2".', + 'drush vl --status=enabled' => 'Show a list of enabled views.', + ), + 'outputformat' => array( + 'default' => 'table', + 'pipe-format' => 'list', + 'fields-default' => array('name', 'label', 'description', 'status', 'tag'), + 'field-labels' => array('name' => 'Machine Name', 'label' => 'Name', 'description' => 'Description', 'status' => 'Status', 'tag' => 'Tag'), + 'output-data-type' => 'format-table', + ), + ) + $base; + + $items['views-execute'] = array( + 'description' => 'Execute a view and get the results.', + 'aliases' => array('vex', 'views:execute'), + 'arguments' => array( + 'view' => 'The name of the view to execute.', + 'display' => 'The display ID to execute. If none specified, the default display will be used.', + ), + 'required-arguments' => 1, + 'options' => array( + 'count' => array( + 'description' => 'Display a count of the results instead of each row.', + ), + 'rendered' => array( + 'description' => 'Return the results as rendered HTML output for the display.', + ), + 'show-admin-links' => array( + 'description' => 'Show contextual admin links in the rendered markup.', + ), + ), + 'outputformat' => array( + 'default' => 'print-r', + 'pipe-format' => 'var_export', + ), + 'examples' => array( + 'drush views-execute my_view' => 'Show the result set of the default display for the my_view view.', + 'drush views-execute my_view page_1 --rendered' => 'Show the rendered output of the my_view:page_1 view.', + 'drush views-execute my_view page_1 3 --count' => 'Show a count of my_view:page_1 with an agument of 3 being passed.', + ), + ) + $base; + + $items['views-analyze'] = array( + 'drupal dependencies' => array('views', 'views_ui'), + 'description' => 'Get a list of all Views analyze warnings', + 'aliases' => array('va', 'views:analyze'), + 'options' => array( + 'format' => array( + 'description' => 'Define the output format. Known formats are: json, print_r, and export.', + ), + ), + ) + $base; + + $items['views-enable'] = array( + 'description' => 'Enable the specified views.', + 'arguments' => array( + 'views' => 'A space delimited list of view names.', + ), + 'required-arguments' => 1, + 'aliases' => array('ven', 'views:enable'), + 'examples' => array( + 'drush ven frontpage taxonomy_term' => 'Enable the frontpage and taxonomy_term views.', + ), + ) + $base; + + $items['views-disable'] = array( + 'description' => 'Disable the specified views.', + 'arguments' => array( + 'views' => 'A space delimited list of view names.', + ), + 'required-arguments' => 1, + 'aliases' => array('vdis', 'views:disable'), + 'examples' => array( + 'drush vdis frontpage taxonomy_term' => 'Disable the frontpage and taxonomy_term views.', + ), + ) + $base; + + return $items; +} + +/** + * Command callback function for views-dev command. + * + * Changes the settings to more developer oriented values. + */ +function drush_views_dev() { + $settings = array( + 'ui.show.listing_filters' => TRUE, + 'ui.show.master_display' => TRUE, + 'ui.show.advanced_column' => TRUE, + 'ui.always_live_preview' => FALSE, + 'ui.always_live_preview_button' => TRUE, + 'ui.show.preview_information' => TRUE, + 'ui.show.sql_query.enabled' => TRUE, + 'ui.show.sql_query.where' => 'above', + 'ui.show.performance_statistics' => TRUE, + 'ui.show.additional_queries' => TRUE, + 'debug.output' => TRUE, + 'debug.region' => 'message', + 'ui.show.display_embed' => TRUE, + ); + + $config = \Drupal::configFactory()->getEditable('views.settings'); + + foreach ($settings as $setting => $value) { + $config->set($setting, $value); + // Convert boolean values into a string to print. + if (is_bool($value)) { + $value = $value ? 'TRUE' : 'FALSE'; + } + // Wrap string values in quotes. + elseif (is_string($value)) { + $value = "\"$value\""; + } + drush_log(dt('!setting set to !value', array('!setting' => $setting, '!value' => $value))); + } + + // Save the new config. + $config->save(); + + drush_log(dt('New views configuration saved.'), LogLevel::SUCCESS); +} + +/** + * Callback function for views-list command. + */ +function drush_views_list() { + $disabled_views = array(); + $enabled_views = array(); + + $format = drush_get_option('format', FALSE); + + $views = \Drupal::entityTypeManager()->getStorage('view')->loadMultiple(); + + // Get the --name option. + $name = array_filter(drush_get_option_list('name')); + $with_name = !empty($name) ? TRUE : FALSE; + + // Get the --tags option. + $tags = array_filter(drush_get_option_list('tags')); + $with_tags = !empty($tags) ? TRUE : FALSE; + + // Get the --status option. Store user input appart to reuse it after. + $status = drush_get_option('status', FALSE); + + // Throw an error if it's an invalid status. + if ($status && !in_array($status, array('enabled', 'disabled'))) { + return drush_set_error(dt('Invalid status: @status. Available options are "enabled" or "disabled"', array('@status' => $status))); + } + + // Setup a row for each view. + foreach ($views as $view) { + // If options were specified, check that first mismatch push the loop to the + // next view. + if ($with_name && !stristr($view->id(), $name[0])) { + continue; + } + if ($with_tags && !in_array($view->get('tag'), $tags)) { + continue; + } + + $status_bool = $status == 'enabled'; + if ($status && ($view->status() !== $status_bool)) { + continue; + } + + $row = array( + 'name' => $view->id(), + 'label' => $view->label(), + 'description' => $view->get('description'), + 'status' => $view->status() ? dt('Enabled') : dt('Disabled'), + 'tag' => $view->get('tag'), + ); + + // Place the row in the appropiate array, so we can have disabled views at + // the bottom. + if ($view->status()) { + $enabled_views[] = $row; + } + else{ + $disabled_views[] = $row; + } + } + + // Sort alphabeticaly. + asort($disabled_views); + asort($enabled_views); + + if (count($enabled_views) || count($disabled_views)) { + $rows = array_merge($enabled_views, $disabled_views); + return $rows; + } + else { + drush_log(dt('No views found.')); + } +} + +/** + * Drush views execute command. + */ +function drush_views_execute($view_name, $display_id = NULL) { + $args = func_get_args(); + $view_args = array(); + + // If it's more than 2, we have arguments. A display has to be specified in + // that case. + if (count($args) > 2) { + $view_args = array_slice($args, 2); + } + + if (!$view = Views::getView($view_name)) { + return drush_set_error(dt('View: "@view" not found.', array('@view' => $view_name))); + } + + // Set the display and execute the view. + $view->setDisplay($display_id); + $view->preExecute($view_args); + $view->execute(); + + if (drush_get_option('count', FALSE)) { + drush_set_default_outputformat('string'); + return count($view->result); + } + elseif (!empty($view->result)) { + if (drush_get_option('rendered', FALSE)) { + drush_set_default_outputformat('string'); + // Don't show admin links in markup by default. + $view->hide_admin_links = !drush_get_option('show-admin-links', FALSE); + $output = $view->preview(); + return drupal_render($output); + + } + else { + return $view->result; + } + } + else { + drush_log(dt('No results returned for this view.') ,LogLevel::WARNING); + return NULL; + } +} + +/** + * Drush views analyze command. + */ +function drush_views_analyze() { + $messages = NULL; + $messages_count = 0; + + $format = drush_get_option('format', FALSE); + + $views = \Drupal::entityTypeManager()->getStorage('view')->loadMultiple(); + + if (!empty($views)) { + $analyzer = \Drupal::service('views.analyzer'); + foreach ($views as $view_name => $view) { + $view = $view->getExecutable(); + + if ($messages = $analyzer->getMessages($view)) { + if ($format) { + $output = drush_format($messages, $format); + drush_print($output); + return $output; + } + else { + drush_print($view_name); + foreach ($messages as $message) { + $messages_count++; + drush_print($message['type'] .': '. $message['message'], 2); + } + } + } + } + + drush_log(dt('A total of @total views were analyzed and @messages problems were found.', array('@total' => count($views), '@messages' => $messages_count)), LogLevel::OK); + return $messages; + } + else { + return drush_set_error(dt('There are no views to analyze')); + } +} + +/** + * Drush views enable command. + */ +function drush_views_enable() { + $view_names = func_get_args(); + _views_drush_op('enable', $view_names); +} + +/** + * Drush views disable command. + */ +function drush_views_disable() { + $view_names = func_get_args(); + _views_drush_op('disable', $view_names); +} + +/** + * Perform operations on view objects. + * + * @param string $op + * The operation to perform. + * @param array $view_names + * An array of view names to load and perform this operation on. + */ +function _views_drush_op($op = '', array $view_names = array()) { + $op_types = _views_drush_op_types(); + if (!in_array($op, array_keys($op_types))) { + return drush_set_error(dt('Invalid op type')); + } + + $view_names = array_combine($view_names, $view_names); + + if ($views = \Drupal::entityTypeManager()->getStorage('view')->loadMultiple($view_names)) { + foreach ($views as $view) { + $tokens = array('@view' => $view->id(), '@action' => $op_types[$op]['action']); + + if ($op_types[$op]['validate']($view)) { + $function = 'views_' . $op . '_view'; + drush_op($function, $view); + drush_log(dt('View: @view has been @action', $tokens), LogLevel::SUCCESS); + } + else { + drush_log(dt('View: @view is already @action', $tokens), LogLevel::NOTICE); + } + // Remove this view from the viewnames input list. + unset($view_names[$view->id()]); + } + + return $views; + } + else { + drush_set_error(dt('No views have been loaded')); + } + + // If we have some unmatched/leftover view names that weren't loaded. + if (!empty($view_names)) { + foreach ($view_names as $viewname) { + drush_log(dt('View: @view could not be found.', array('@view' => $viewname)), LogLevel::ERROR); + } + } + +} + +/** + * Returns an array of op types that can be performed on views. + * + * @return array + * An associative array keyed by op type => action name. + */ +function _views_drush_op_types() { + return array( + 'enable' => array( + 'action' => dt('enabled'), + 'validate' => '_views_drush_view_is_disabled', + ), + 'disable' => array( + 'action' => dt('disabled'), + 'validate' => '_views_drush_view_is_enabled', + ), + ); +} + +/** + * Returns whether a view is enabled. + * + * @param Drupal\views\Entity\ViewDrupal\views\ $view + * The view object to check. + * + * @return bool + * TRUE if the View is enabled, FALSE otherwise. + */ +function _views_drush_view_is_enabled(View $view) { + return $view->status(); +} + +/** + * Returns whether a view is disabled. + * + * @param Drupal\views\Entity\View $view + * The view object to check. + * + * @return bool + * TRUE if the View is disabled, FALSE otherwise. + */ +function _views_drush_view_is_disabled(View $view) { + return !$view->status(); +} + +/** + * Implements hook_cache_clear. Adds a cache clear option for views. + */ +function views_drush_cache_clear(&$types, $include_bootstrapped_types) { + if ($include_bootstrapped_types && \Drupal::moduleHandler()->moduleExists('views')) { + $types['views'] = 'views_invalidate_cache'; + } +} + +/** + * Command argument complete callback. + */ +function views_views_enable_complete() { + return _drush_views_complete(); +} + +/** + * Command argument complete callback. + */ +function views_views_disable_complete() { + return _drush_views_complete(); +} + +/** + * Helper function to return a list of view names for complete callbacks. + * + * @return array + * An array of available view names. + */ +function _drush_views_complete() { + drush_bootstrap_max(); + return array('values' => array_keys(\Drupal::entityTypeManager()->getStorage('view')->loadMultiple())); +} diff --git a/vendor/drush/drush/commands/core/watchdog.drush.inc b/vendor/drush/drush/commands/core/watchdog.drush.inc new file mode 100644 index 0000000000..d21cdc0c44 --- /dev/null +++ b/vendor/drush/drush/commands/core/watchdog.drush.inc @@ -0,0 +1,397 @@ + 'Show available message types and severity levels. A prompt will ask for a choice to show watchdog messages.', + 'drupal dependencies' => array('dblog'), + 'outputformat' => array( + 'default' => 'table', + 'pipe-format' => 'var_export', + 'field-labels' => array('wid' => 'ID', 'type' => 'Type', 'message' => 'Message', 'severity' => 'Severity', 'location' => 'Location', 'hostname' => 'Hostname', 'date' => 'Date', 'username' => 'Username'), + 'fields-default' => array('wid', 'date', 'type', 'severity', 'message'), + 'column-widths' => array('type' => 8, 'severity' => 8), + 'output-data-type' => 'format-table', + ), + 'aliases' => array('wd-list', 'watchdog:list'), + ); + $items['watchdog-show'] = array( + 'description' => 'Show watchdog messages.', + 'drupal dependencies' => array('dblog'), + 'arguments' => array( + 'wid' => 'Optional id of a watchdog message to show in detail. If not provided, a listing of most recent 10 messages will be displayed. Alternatively if a string is provided, watchdog messages will be filtered by it.', + ), + 'options' => array( + 'count' => 'The number of messages to show. Defaults to 10.', + 'severity' => 'Restrict to messages of a given severity level.', + 'type' => 'Restrict to messages of a given type.', + 'tail' => 'Continuously show new watchdog messages until interrupted.', + 'sleep-delay' => 'To be used in conjunction with --tail. This is the number of seconds to wait between each poll to the database. Delay is 1 second by default.', + 'extended' => 'Return extended information about each message.', + ), + 'examples' => array( + 'drush watchdog-show' => 'Show a listing of most recent 10 messages.', + 'drush watchdog-show 64' => 'Show in detail message with id 64.', + 'drush watchdog-show "cron run succesful"' => 'Show a listing of most recent 10 messages containing the string "cron run succesful".', + 'drush watchdog-show --count=46' => 'Show a listing of most recent 46 messages.', + 'drush watchdog-show --severity=notice' => 'Show a listing of most recent 10 messages with a severity of notice.', + 'drush watchdog-show --type=php' => 'Show a listing of most recent 10 messages of type php.', + 'drush watchdog-show --tail --extended' => 'Show a listing of most recent 10 messages with extended information about each one and continue showing messages as they are registered in the watchdog.', + 'drush watchdog-show --tail --sleep-delay=2' => 'Do a tail of the watchdog with a delay of two seconds between each poll to the database.', + ), + 'outputformat' => array( + 'default' => 'table', + 'pipe-format' => 'var_export', + 'field-labels' => array('wid' => 'ID', 'type' => 'Type', 'message' => 'Message', 'severity' => 'Severity', 'location' => 'Location', 'hostname' => 'Hostname', 'date' => 'Date', 'username' => 'Username'), + 'fields-default' => array('wid', 'date', 'type', 'severity', 'message'), + 'column-widths' => array('type' => 8, 'severity' => 8), + 'output-data-type' => 'format-table', + ), + 'aliases' => array('wd-show', 'ws', 'watchdog:show'), + ); + $items['watchdog-delete'] = array( + 'description' => 'Delete watchdog messages.', + 'drupal dependencies' => array('dblog'), + 'options' => array( + 'severity' => 'Delete messages of a given severity level.', + 'type' => 'Delete messages of a given type.', + ), + 'examples' => array( + 'drush watchdog-delete all' => 'Delete all messages.', + 'drush watchdog-delete 64' => 'Delete messages with id 64.', + 'drush watchdog-delete "cron run succesful"' => 'Delete messages containing the string "cron run succesful".', + 'drush watchdog-delete --severity=notice' => 'Delete all messages with a severity of notice.', + 'drush watchdog-delete --type=cron' => 'Delete all messages of type cron.', + ), + 'aliases' => array('wd-del', 'wd-delete', 'watchdog:delete'), + ); + return $items; +} + +/** + * Command callback. + */ +function drush_core_watchdog_list() { + drush_include_engine('drupal', 'environment'); + + $options['-- types --'] = dt('== message types =='); + $types = drush_watchdog_message_types(); + foreach ($types as $key => $type) { + $options[$key] = $type; + } + $options['-- levels --'] = dt('== severity levels =='); + $severities = drush_watchdog_severity_levels(); + foreach ($severities as $key => $value) { + $options[$key] = "$value($key)"; + } + $option = drush_choice($options, dt('Select a message type or severity level.')); + if ($option === FALSE) { + return drush_user_abort(); + } + if (isset($types[$option])) { + drush_set_option('type', $types[$option]); + } + else { + drush_set_option('severity', $option - $ntypes); + } + return drush_core_watchdog_show_many(); +} + +/** + * Command callback. + */ +function drush_core_watchdog_show($arg = NULL) { + drush_include_engine('drupal', 'environment'); + + if (is_numeric($arg)) { + return drush_core_watchdog_show_one($arg); + } + else { + return drush_core_watchdog_show_many($arg); + } +} + +/** + * Print a watchdog message. + * + * @param $wid + * The id of the message to show. + */ +function drush_core_watchdog_show_one($wid) { + drush_set_default_outputformat('key-value-list', array('fields-default' => array('wid', 'type', 'message', 'severity', 'date'),)); + $rsc = drush_db_select('watchdog', '*', 'wid = :wid', array(':wid' => $wid), 0, 1); + $result = drush_db_fetch_object($rsc); + if (!$result) { + return drush_set_error(dt('Watchdog message #!wid not found.', array('!wid' => $wid))); + } + $result = core_watchdog_format_result($result, TRUE); + return array($result->wid => (array)$result); +} + +/** + * Print a table of watchdog messages. + * + * @param $filter + * String to filter the message's text by. + */ +function drush_core_watchdog_show_many($filter = NULL) { + $count = drush_get_option('count', 10); + $type = drush_get_option('type'); + $severity = drush_get_option('severity'); + $tail = drush_get_option('tail', FALSE); + $extended = drush_get_option('extended', FALSE); + + $where = core_watchdog_query($type, $severity, $filter); + if ($where === FALSE) { + return drush_log(dt('Aborting.')); + } + $rsc = drush_db_select('watchdog', '*', $where['where'], $where['args'], 0, $count, 'wid', 'DESC'); + if ($rsc === FALSE) { + return drush_log(dt('Aborting.')); + } + $table = array(); + while ($result = drush_db_fetch_object($rsc)) { + $row = core_watchdog_format_result($result, $extended); + $table[$row->wid] = (array)$row; + } + if (empty($table) && !$tail) { + drush_log(dt('No log messages available.'), LogLevel::OK); + return array(); + } + else { + drush_log(dt('Most recent !count watchdog log messages:', array('!count' => $count))); + } + if ($tail) { + $field_list = array('wid' => 'ID', 'date' => 'Date', 'severity' => 'Severity', 'type' => 'Type', 'message' => 'Message'); + $table = array_reverse($table); + $table_rows = drush_rows_of_key_value_to_array_table($table, $field_list, array()); + $tbl = drush_print_table($table_rows, TRUE); + // Reuse the table object to display each line generated while in tail mode. + // To make it possible some hacking is done on the object: + // remove the header and reset the rows on each iteration. + $tbl->_headers = NULL; + // Obtain the last wid. If the table has no rows, start at 0. + if (count($table_rows) > 1) { + $last = array_pop($table_rows); + $last_wid = $last[0]; + } + else { + $last_wid = 0; + } + // Adapt the where snippet. + if ($where['where'] != '') { + $where['where'] .= ' AND '; + } + $where['where'] .= 'wid > :wid'; + // sleep-delay + $sleep_delay = drush_get_option('sleep-delay', 1); + while (TRUE) { + $where['args'][':wid'] = $last_wid; + $table = array(); + // Reset table rows. + $tbl->_data = array(); + $rsc = drush_db_select('watchdog', '*', $where['where'], $where['args'], NULL, NULL, 'wid', 'ASC'); + while ($result = drush_db_fetch_object($rsc)) { + $row = core_watchdog_format_result($result, $extended); + $table[] = array($row->wid, $row->date, $row->severity, $row->type, $row->message); + #$tbl->addRow(array($row->wid, $row->date, $row->severity, $row->type, $row->message)); + $last_wid = $row->wid; + } + $tbl->addData($table); + print $tbl->_buildTable(); + sleep($sleep_delay); + } + } + return $table; +} + +/** + * Format a watchdog database row. + * + * @param $result + * Array. A database result object. + * @param $extended + * Boolean. Return extended message details. + * @return + * Array. The result object with some attributes themed. + */ +function core_watchdog_format_result($result, $extended = FALSE) { + // Severity. + $severities = drush_watchdog_severity_levels(); + $result->severity = $severities[$result->severity]; + + // Date. + $result->date = drush_format_date($result->timestamp, 'custom', 'd/M H:i'); + unset($result->timestamp); + + // Message. + $variables = $result->variables; + if (is_string($variables)) { + $variables = unserialize($variables); + } + if (is_array($variables)) { + $result->message = strtr($result->message, $variables); + } + unset($result->variables); + $message_length = 188; + + // Print all the data available + if ($extended) { + // Possible empty values. + if (empty($result->link)) { + unset($result->link); + } + if (empty($result->referer)) { + unset($result->referer); + } + // Username. + if ($account = drush_user_load($result->uid)) { + $result->username = $account->name; + } + else { + $result->username = dt('Anonymous'); + } + unset($result->uid); + $message_length = PHP_INT_MAX; + } + + if (drush_drupal_major_version() >= 8) { + $result->message = Unicode::truncate(strip_tags(Html::decodeEntities($result->message)), $message_length, FALSE, FALSE); + } + else { + $result->message = truncate_utf8(strip_tags(decode_entities($result->message)), $message_length, FALSE, FALSE); + } + + return $result; +} + +/** + * Command callback. + * + * @param $arg + * The id of the message to delete or 'all'. + */ +function drush_core_watchdog_delete($arg = NULL) { + drush_include_engine('drupal', 'environment'); + + if ($arg == 'all') { + drush_print(dt('All watchdog messages will be deleted.')); + if (!drush_confirm(dt('Do you really want to continue?'))) { + return drush_user_abort(); + } + drush_db_delete('watchdog'); + drush_log(dt('All watchdog messages have been deleted.'), LogLevel::OK); + } + else if (is_numeric($arg)) { + drush_print(dt('Watchdog message #!wid will be deleted.', array('!wid' => $arg))); + if(!drush_confirm(dt('Do you really want to continue?'))) { + return drush_user_abort(); + } + $affected_rows = drush_db_delete('watchdog', 'wid=:wid', array(':wid' => $arg)); + if ($affected_rows == 1) { + drush_log(dt('Watchdog message #!wid has been deleted.', array('!wid' => $arg)), LogLevel::OK); + } + else { + return drush_set_error(dt('Watchdog message #!wid does not exist.', array('!wid' => $arg))); + } + } + else { + $type = drush_get_option('type'); + $severity = drush_get_option('severity'); + if ((!isset($arg))&&(!isset($type))&&(!isset($severity))) { + return drush_set_error(dt('No options provided.')); + } + $where = core_watchdog_query($type, $severity, $arg, 'OR'); + if ($where === FALSE) { + // Drush set error was already called by core_watchdog_query + return FALSE; + } + drush_print(dt('All messages with !where will be deleted.', array('!where' => preg_replace("/message LIKE %$arg%/", "message body containing '$arg'" , strtr($where['where'], $where['args']))))); + if(!drush_confirm(dt('Do you really want to continue?'))) { + return drush_user_abort(); + } + $affected_rows = drush_db_delete('watchdog', $where['where'], $where['args']); + drush_log(dt('!affected_rows watchdog messages have been deleted.', array('!affected_rows' => $affected_rows)), LogLevel::OK); + } +} + +/** + * Build a WHERE snippet based on given parameters. + * + * @param $type + * String. Valid watchdog type. + * @param $severity + * Int or String for a valid watchdog severity message. + * @param $filter + * String. Value to filter watchdog messages by. + * @param $criteria + * ('AND', 'OR'). Criteria for the WHERE snippet. + * @return + * False or array with structure ('where' => string, 'args' => array()) + */ +function core_watchdog_query($type = NULL, $severity = NULL, $filter = NULL, $criteria = 'AND') { + $args = array(); + $conditions = array(); + if ($type) { + $types = drush_watchdog_message_types(); + if (array_search($type, $types) === FALSE) { + $msg = "Unrecognized message type: !type.\nRecognized types are: !types."; + return drush_set_error('WATCHDOG_UNRECOGNIZED_TYPE', dt($msg, array('!type' => $type, '!types' => implode(', ', $types)))); + } + $conditions[] = "type = :type"; + $args[':type'] = $type; + } + if (isset($severity)) { + $severities = drush_watchdog_severity_levels(); + if (isset($severities[$severity])) { + $level = $severity; + } + elseif (($key = array_search($severity, $severities)) !== FALSE) { + $level = $key; + } + else { + $level = FALSE; + } + if ($level === FALSE) { + foreach ($severities as $key => $value) { + $levels[] = "$value($key)"; + } + $msg = "Unknown severity level: !severity.\nValid severity levels are: !levels."; + return drush_set_error(dt($msg, array('!severity' => $severity, '!levels' => implode(', ', $levels)))); + } + $conditions[] = 'severity = :severity'; + $args[':severity'] = $level; + } + if ($filter) { + $conditions[] = "message LIKE :filter"; + $args[':filter'] = '%'.$filter.'%'; + } + + $where = implode(" $criteria ", $conditions); + + return array('where' => $where, 'args' => $args); +} diff --git a/vendor/drush/drush/commands/make/generate.contents.make.inc b/vendor/drush/drush/commands/make/generate.contents.make.inc new file mode 100644 index 0000000000..a7a17e0541 --- /dev/null +++ b/vendor/drush/drush/commands/make/generate.contents.make.inc @@ -0,0 +1,364 @@ + $project) { + $type = (isset($project['type']) && ($project['type'] == 'library')) ? 'libraries' : 'projects'; + if ($previous_type != $project['_type']) { + $previous_type = $project['_type']; + $output[] = '; ' . ucfirst($previous_type) . 's'; + } + unset($project['_type']); + if (!$project && is_string($name)) { + $output[] = $type . '[] = "' . $name . '"'; + continue; + } + $base = $type . '[' . $name . ']'; + if (isset($project['custom_download'])) { + $custom = TRUE; + $output[] = '; Please fill the following out. Type may be one of get, git, bzr or svn,'; + $output[] = '; and url is the url of the download.'; + $output[$base . '[download][type]'] = '""'; + $output[$base . '[download][url]'] = '""'; + unset($project['custom_download']); + } + + $output = array_merge($output, _drush_make_generate_lines($base, $project)); + $output[] = ''; + } + } + $string = ''; + foreach ($output as $k => $v) { + if (!is_numeric($k)) { + $string .= $k . ' = ' . $v; + } + else { + $string .= $v; + } + $string .= "\n"; + } + if ($custom) { + drush_log(dt('Some of the properties in your makefile will have to be manually edited. Please do that now.'), LogLevel::WARNING); + } + return $string; +} + +/** + * Write a makefile based on data parsed from a previous makefile. + * + * @param $file + * The path to the file to write our generated makefile to, or TRUE to + * print to the terminal. + * @param $makefile + * A makefile on which to base our generated one. + */ +function make_generate_from_makefile($file, $makefile) { + if (!$info = make_parse_info_file($makefile)) { + return drush_set_error('MAKE_GENERATE_FAILED_PARSE', dt('Failed to parse makefile :makefile.', array(':makefile' => $makefile))); + } + $projects = drush_get_option('DRUSH_MAKE_PROJECTS', FALSE); + if ($projects === FALSE) { + $projects = make_prepare_projects(FALSE, $info); + if (isset($projects['contrib'])) { + $projects = array_merge($projects['core'], $projects['contrib']); + } + } + + $defaults = isset($info['defaults']) ? $info['defaults'] : array(); + $core = current($projects); + $core = $core['core']; + foreach ($projects as $name => $project) { + // If a specific revision was requested, do not set the version. + if (!isset($project['revision'])) { + $projects[$name]['version'] = isset($project['download']['full_version']) ? $project['download']['full_version'] : ''; + if ($project['type'] != 'core' && strpos($projects[$name]['version'], $project['core']) === 0) { + $projects[$name]['version'] = substr($projects[$name]['version'], strlen($project['core'] . '-')); + } + } + else { + unset($projects[$name]['version']); + } + $projects[$name]['_type'] = $project['type']; + + if ($project['download']['type'] == 'git') { + drush_make_resolve_git_refs($projects[$name]); + } + + // Don't clutter the makefile with defaults + if (is_array($defaults)) { + foreach ($defaults as $type => $defs) { + if ($type == 'projects') { + foreach ($defs as $key => $value) { + if (isset($project[$key]) && $project[$key] == $value) { + unset($projects[$name][$key]); + } + } + } + } + } + if ($project['name'] == $name) { + unset($projects[$name]['name']); + } + if ($project['type'] == 'module' && !isset($info[$name]['type'])) { + unset($projects[$name]['type']); // Module is the default + } + if (!(isset($project['download']['type'])) || ($project['download']['type'] == 'pm')) { + unset($projects[$name]['download']); // PM is the default + } + $ignore = array('build_path', 'contrib_destination', 'core', 'make_directory', 'l10n_url', 'download_type'); + foreach ($ignore as $key) { + unset($projects[$name][$key]); + } + + // Remove the location if it's the default. + if ($projects[$name]['location'] == 'https://updates.drupal.org/release-history') { + unset($projects[$name]['location']); + } + + // Remove empty entries (e.g. 'directory_name') + $projects[$name] = _make_generate_array_filter($projects[$name]); + } + + $libraries = drush_get_option('DRUSH_MAKE_LIBRARIES', FALSE); + if ($libraries === FALSE) { + $libraries = isset($info['libraries']) ? $info['libraries'] : array(); + } + if (is_array($libraries)) { + foreach ($libraries as $name => $library) { + $libraries[$name]['type'] = 'library'; + $libraries[$name]['_type'] = 'librarie'; + + if ($library['download']['type'] == 'git') { + drush_make_resolve_git_refs($libraries[$name]); + } + } + } + + $contents = make_generate_makefile_contents($projects, $libraries, $core, $defaults); + + // Write or print our makefile. + $file = $file !== TRUE ? $file : NULL; + make_generate_print($contents, $file); +} + +/** + * Resolve branches and revisions for git-based projects. + */ +function drush_make_resolve_git_refs(&$project) { + if (!isset($project['download']['branch'])) { + $project['download']['branch'] = drush_make_resolve_git_branch($project); + } + if (!isset($project['download']['revision'])) { + $project['download']['revision'] = drush_make_resolve_git_revision($project); + } +} + +/** + * Resolve branch for a git-based project. + */ +function drush_make_resolve_git_branch($project) { + drush_log(dt('Resolving default branch for repo at: :repo', array(':repo' => $project['download']['url']))); + if (drush_shell_exec("git ls-remote %s HEAD", $project['download']['url'])) { + $head_output = drush_shell_exec_output(); + list($head_commit) = explode("\t", $head_output[0]); + + drush_log(dt('Scanning branches in repo at: :repo', array(':repo' => $project['download']['url']))); + drush_shell_exec("git ls-remote --heads %s", $project['download']['url']); + $heads_output = drush_shell_exec_output(); + $branches = array(); + foreach ($heads_output as $key => $head) { + list($commit, $ref) = explode("\t", $head); + $branches[$commit] = explode("/", $ref)[2]; + } + + $branch = $branches[$head_commit]; + drush_log(dt('Resolved git branch to: :branch', array(':branch' => $branch))); + return $branch; + } + else { + drush_log(dt('Could not resolve branch for `:project` using git repo at :repo', array(':project' => $project['name'], ':repo' => $project['download']['url'])), 'warning'); + } +} + +/** + * Resolve revision for a git-based project. + */ +function drush_make_resolve_git_revision($project) { + drush_log(dt('Resolving head commit on `:branch` branch for repo at: :repo', array(':branch' => $project['download']['branch'], ':repo' => $project['download']['url']))); + if (drush_shell_exec("git ls-remote %s %s", $project['download']['url'], $project['download']['branch'])) { + $head_output = drush_shell_exec_output(); + list($revision) = explode("\t", $head_output[0]); + drush_log(dt('Resolved git revision to: :revision', array(':revision' => $revision))); + return $revision; + } + else { + drush_log(dt('Could not resolve head commit for `:project` using git repo at :repo', array(':project' => $project['name'], ':repo' => $project['download']['url'])), 'warning'); + } +} + +/** + * Generate makefile contents in the appropriate format. + */ +function make_generate_makefile_contents($projects, $libraries = array(), $core = NULL, $defaults = array()) { + $format = drush_get_option('format', 'yaml'); + $func = "make_generate_makefile_contents_$format"; + if (function_exists($func)) { + $contents = call_user_func($func, $projects, $libraries, $core, $defaults); + } + else { + return drush_set_error('MAKE_UNKNOWN_OUTPUT_FORMAT', dt('Generating makefiles in the :format output format is not yet supported. Implement :func() to add such support.', array(':format' => $format, ':func' => $func))); + } + return $contents; +} + +/** + * Generate makefile contents in (legacy) INI format. + */ +function make_generate_makefile_contents_ini($projects, $libraries, $core, $defaults) { + return _drush_make_generate_makefile_contents($projects, $libraries, $core, $defaults); +} + +/** + * Generate makefile contents in YAML format. + */ +function make_generate_makefile_contents_yaml($projects, $libraries, $core, $defaults) { + $info = array( + 'core' => $core, + 'api' => MAKE_API, + 'defaults' => $defaults, + 'projects' => $projects, + 'libraries' => $libraries, + ); + + $info = _make_generate_array_filter($info); + $info = _make_generate_array_filter_key('_type', $info); + $dumper = drush_load_engine('outputformat', 'yaml'); + $yaml = $dumper->format($info, array()); + + return $yaml; +} + +/** + * Helper function to recursively remove empty values from an array (but not + * '0'!). + */ +function _make_generate_array_filter($haystack) { + foreach ($haystack as $key => $value) { + if (is_array($value)) { + $haystack[$key] = _make_generate_array_filter($haystack[$key]); + } + if (empty($value) && $value !== '0') { + unset($haystack[$key]); + } + } + return $haystack; +} + +/** + * Helper function to recursively remove elements matching a specific key from an array. + */ +function _make_generate_array_filter_key($needle, $haystack) { + foreach ($haystack as $key => $value) { + if ($key === $needle) { + unset($haystack[$key]); + } + elseif (is_array($value)) { + $haystack[$key] = _make_generate_array_filter_key($needle, $haystack[$key]); + } + } + return $haystack; +} + +/** + * Print the generated makefile to the terminal, or write it to a file. + * + * @param $contents + * The formatted contents of a makefile. + * @param $file + * (optional) The path to write the makefile. + */ +function make_generate_print($contents, $file = NULL) { + if (!$file) { + drush_print($contents); + } + elseif (file_put_contents($file, $contents)) { + drush_log(dt("Wrote .make file @file", array('@file' => $file)), LogLevel::OK); + } + else { + make_error('FILE_ERROR', dt("Unable to write .make file !file", array('!file' => $file))); + } +} + +/** + * Utility function to generate the line or lines for a key/value pair in the + * make file. + * + * @param $base + * The base for the configuration lines. Values will be appended to it as + * [$key] = $value, or if value is an array itself it will expand into as many + * lines as required. + * @param $values + * May be a single value or an array. + * @return + * An array of strings that represent lines for the make file. + */ +function _drush_make_generate_lines($base, $values) { + $output = array(); + + if (is_array($values)) { + foreach ($values as $key => $value) { + $newbase = $base . '[' . $key . ']'; + $output = array_merge($output, _drush_make_generate_lines($newbase, $value)); + } + } + else { + $output[$base] = '"' . $values . '"'; + } + + return $output; +} + +function _drush_make_generate_defaults($defaults, &$output = array()) { + $output[] = '; Defaults'; + foreach ($defaults as $name => $project) { + $type = 'defaults'; + if (!$project && is_string($name)) { + $output[] = $type . '[] = "' . $name . '"'; + continue; + } + $base = $type . '[' . $name . ']'; + + $output = array_merge($output, _drush_make_generate_lines($base, $project)); + } +} + diff --git a/vendor/drush/drush/commands/make/generate.make.inc b/vendor/drush/drush/commands/make/generate.make.inc new file mode 100644 index 0000000000..220d16b934 --- /dev/null +++ b/vendor/drush/drush/commands/make/generate.make.inc @@ -0,0 +1,308 @@ + 'core'); + if ($core_project != 'drupal') { + $projects[$core_project]['custom_download'] = TRUE; + $projects[$core_project]['type'] = 'core'; + } + else { + // Drupal core - we can determine the version if required. + if (_drush_generate_track_version("drupal", $version_options)) { + $projects[$core_project]["version"] = drush_drupal_version(); + } + } + + $install_profile = drush_drupal_get_profile(); + if (!in_array($install_profile, array('default', 'standard', 'minimal', 'testing')) && $install_profile != '') { + $projects[$install_profile]['type'] + = $projects[$install_profile]['_type'] = 'profile'; + $request = array( + 'name' => $install_profile, + 'drupal_version' => $drupal_major_version, + ); + if (!$release_info->checkProject($request, 'profile')) { + $projects[$install_profile]['custom_download'] = TRUE; + } + } + + // Iterate installed projects to build $projects array. + $extensions = $all_extensions; + $project_info = drush_get_projects($extensions); + foreach ($project_info as $name => $project) { + // Discard the extensions within this project. At the end $extensions will + // contain only extensions part of custom projects (not from drupal.org or + // other update service). + foreach ($project['extensions'] as $ext) { + unset($extensions[$ext]); + } + if ($name == 'drupal') { + continue; + } + $type = $project['type']; + // Discard projects with all modules disabled. + if (($type == 'module') && (!$project['status'])) { + continue; + } + $projects[$name] = array('_type' => $type); + // Check the project is on drupal.org or its own update service. + $request = array( + 'name' => $name, + 'drupal_version' => $drupal_major_version, + ); + if (isset($project['status url'])) { + $request['status url'] = $project['status url']; + $projects[$name]['location'] = $project['status url']; + } + if (!$release_info->checkProject($request, $type)) { + // It is not a project on drupal.org neither an external update service. + $projects[$name]['type'] = $type; + $projects[$name]['custom_download'] = TRUE; + } + // Add 'subdir' if the project is installed in a non-default location. + if (isset($project['path'])) { + $projects[$name] += _drush_generate_makefile_check_path($project); + } + // Add version number if this project's version is to be tracked. + if (_drush_generate_track_version($name, $version_options) && $project["version"]) { + $version = preg_replace("/^" . drush_get_drupal_core_compatibility() . "-/", "", $project["version"]); + // Strip out MINOR+GIT_COMMIT strings for dev releases. + if (substr($version, -4) == '-dev' && strpos($version, '+')) { + $version = substr($version, 0, strrpos($version, '.')) . '.x-dev'; + } + $projects[$name]['version'] = $version; + } + foreach ($project['extensions'] as $extension_name) { + _drush_make_generate_add_patch_files($projects[$name], _drush_extension_get_path($all_extensions[$extension_name])); + } + } + + // Add a project for each unknown extension. + foreach ($extensions as $name => $extension) { + list($project_name, $project_data) = _drush_generate_custom_project($name, $extension, $version_options); + $projects[$project_name] = $project_data; + } + + // Add libraries. + if (function_exists('libraries_get_libraries')) { + $libraries = libraries_get_libraries(); + foreach ($libraries as $library_name => $library_path) { + $path = explode('/', $library_path); + $project_libraries[$library_name] = array( + 'directory_name' => $path[(count($path) - 1)], + 'custom_download' => TRUE, + 'type' => 'library', + '_type' => 'librarie', // For plural. + ); + } + } + return array($projects, $project_libraries); +} + +/** + * Record any patches that were applied to this project + * per information stored in PATCHES.txt. + */ +function _drush_make_generate_add_patch_files(&$project, $location) { + $patchfile = DRUPAL_ROOT . '/' . $location . '/PATCHES.txt'; + if (is_file($patchfile)) { + foreach (file($patchfile) as $line) { + if (substr($line, 0, 2) == '- ') { + $project['patch'][] = trim(substr($line, 2)); + } + } + } +} + +/** + * Create a project record for an extension not downloaded from drupal.org + */ +function _drush_generate_custom_project($name, $extension, $version_options) { + $project['_type'] = drush_extension_get_type($extension); + $project['type'] = drush_extension_get_type($extension); + $location = drush_extension_get_path($extension); + // To start off, we will presume that our custom extension is + // stored in a folder named after its project, and there are + // no subfolders between the .info file and the project root. + $project_name = basename($location); + drush_shell_cd_and_exec($location, 'git rev-parse --git-dir 2> ' . drush_bit_bucket()); + $output = drush_shell_exec_output(); + if (!empty($output)) { + $git_dir = $output[0]; + // Find the actual base of the git repository. + $repo_root = $git_dir == ".git" ? $location : dirname($git_dir); + // If the repository root is at the drupal root or some parent + // of the drupal root, or some other location that could not + // pausibly be a project, then there is nothing we can do. + // (We can't tell Drush make to download some sub-part of a repo, + // can we?) + if ($repo_project_name = _drush_generate_validate_repo_location($repo_root)) { + $project_name = $repo_project_name; + drush_shell_cd_and_exec($repo_root, 'git remote show origin'); + $output = drush_shell_exec_output(); + foreach ($output as $line) { + if (strpos($line, "Fetch URL:") !== FALSE) { + $url = preg_replace('/ *Fetch URL: */', '', $line); + if (!empty($url)) { + // We use the unconventional-looking keys + // `download][type` and `download][url` so that + // we can produce output that appears to be two-dimensional + // arrays from a single-dimensional array. + $project['download][type'] = 'git'; + $project['download][url'] = $url; + + // Fill in the branch as well. + drush_shell_cd_and_exec($repo_root, 'git branch'); + $output = drush_shell_exec_output(); + foreach ($output as $line) { + if ($line[0] == '*') { + $branch = substr($line, 2); + if ($branch != "master") { + $project['download][branch'] = $branch; + } + } + } + + // Put in the commit hash. + drush_shell_cd_and_exec($repo_root, 'git log'); + $output = drush_shell_exec_output(); + if (substr($output[0], 0, 7) == "commit ") { + $revision = substr($output[0], 7); + if (_drush_generate_track_version($project_name, $version_options)) { + $project['download][revision'] = $revision; + } + } + + // Add patch files, if any. + _drush_make_generate_add_patch_files($project, $repo_root); + } + } + } + } + } + // If we could not figure out where the extension came from, then give up and + // flag it as a "custom" download. + if (!isset($project['download][type'])) { + $project['custom_download'] = TRUE; + } + return array($project_name, $project); +} + +/** + * If the user has checked in the Drupal root, or the 'sites/all/modules' + * folder into a git repository, then we do not want to confuse that location + * with a "project". + */ +function _drush_generate_validate_repo_location($repo_root) { + $project_name = basename($repo_root); + // The Drupal root, or any folder immediately inside the Drupal + // root cannot be a project location. + if ((strlen(DRUPAL_ROOT) >= strlen($repo_root)) || (dirname($repo_root) == DRUPAL_ROOT)) { + return NULL; + } + // Also exclude sites/* and sites/*/{modules,themes} and profile/* and + // profile/*/{modules,themes}. + return $project_name; +} + +/** + * Helper function to determine if a given project is to have its version + * tracked. + */ +function _drush_generate_track_version($project, $version_options) { + // A. If --exclude-versions has been specified: + // A.a. if it's a boolean, check the --include-versions option. + if ($version_options["exclude"] === TRUE) { + // A.a.1 if --include-versions has been specified, ensure it's an array. + if (is_array($version_options["include"])) { + return in_array($project, $version_options["include"]); + } + // A.a.2 If no include array, then we're excluding versions for ALL + // projects. + return FALSE; + } + // A.b. if --exclude-versions is an array with items, check this project is in + // it: if so, then return FALSE. + elseif (is_array($version_options["exclude"]) && count($version_options["exclude"])) { + return !in_array($project, $version_options["exclude"]); + } + + // B. If by now no --exclude-versions, but --include-versions is an array, + // examine it for this project. + if (is_array($version_options["include"]) && count($version_options["include"])) { + return in_array($project, $version_options["include"]); + } + + // If none of the above conditions match, include version number by default. + return TRUE; +} + +/** + * Helper function to check for a non-default installation location. + */ +function _drush_generate_makefile_check_path($project) { + $info = array(); + $type = $project['type']; + $path = dirname($project['path']); + // Check to see if the path is in a subdir sites/all/modules or + // profiles/profilename/modules + if (preg_match('@^sites/[a-zA-Z0-9_]*/' . $type . 's/..*@', $path) || preg_match('@^sites/[a-zA-Z0-9_]*/' . $type . 's/..*@', $path)) { + $subdir = preg_replace(array('@^[a-zA-Z0-9_]*/[a-zA-Z0-9_]*/' . $type . 's/*@', "@/$name" . '$@'), '', $path); + if (!empty($subdir)) { + $info['subdir'] = $subdir; + } + } + return $info; +} diff --git a/vendor/drush/drush/commands/make/lock.make.inc b/vendor/drush/drush/commands/make/lock.make.inc new file mode 100644 index 0000000000..d7bc41da28 --- /dev/null +++ b/vendor/drush/drush/commands/make/lock.make.inc @@ -0,0 +1,13 @@ + dirname($download_location), + 'yes' => TRUE, + 'package-handler' => 'wget', + 'source' => $download['status url'], + // This is only relevant for profiles, but we generally want the variant to + // be 'profile-only' so we don't end up with extra copies of core. + 'variant' => $type == 'core' ? 'full' : $download['variant'], + 'cache' => TRUE, + ); + if ($type == 'core') { + $options['drupal-project-rename'] = basename($download_location); + } + if (drush_get_option('no-cache', FALSE)) { + unset($options['cache']); + } + + $backend_options = array(); + if (!drush_get_option(array('verbose', 'debug'), FALSE)) { + $backend_options['integrate'] = TRUE; + $backend_options['log'] = FALSE; + } + + // Perform actual download with `drush pm-download`. + $return = drush_invoke_process('@none', 'pm-download', array($full_project_version), $options, $backend_options); + if (empty($return['error_log'])) { + // @todo Report the URL we used for download. See + // http://drupal.org/node/1452672. + drush_log(dt('@project downloaded.', array('@project' => $full_project_version)), LogLevel::OK); + } +} + +/** + * Downloads a file to the specified location. + * + * @return mixed + * The destination directory on success, FALSE on failure. + */ +function make_download_file($name, $type, $download, $download_location, $cache_duration = DRUSH_CACHE_LIFETIME_DEFAULT) { + if ($filename = _make_download_file($download['url'], $cache_duration)) { + if (!drush_get_option('ignore-checksums') && !_make_verify_checksums($download, $filename)) { + return FALSE; + } + drush_log(dt('@project downloaded from @url.', array('@project' => $name, '@url' => $download['url'])), LogLevel::OK); + $download_filename = isset($download['filename']) ? $download['filename'] : ''; + $subtree = isset($download['subtree']) ? $download['subtree'] : NULL; + return make_download_file_unpack($filename, $download_location, $download_filename, $subtree); + } + make_error('DOWNLOAD_ERROR', dt('Unable to download @project from @url.', array('@project' => $name, '@url' => $download['url']))); + return FALSE; +} + +/** + * Wrapper to drush_download_file(). + * + * @param string $download + * The url of the file to download. + * @param int $cache_duration + * The time in seconds to cache the resultant download. + * + * @return string + * The location of the downloaded file, or FALSE on failure. + */ +function _make_download_file($download, $cache_duration = DRUSH_CACHE_LIFETIME_DEFAULT) { + if (drush_get_option('no-cache', FALSE)) { + $cache_duration = 0; + } + + $tmp_path = make_tmp(); + // Ensure that we aren't including the querystring when generating a filename + // to save our download to. + $file = basename(current(explode('?', $download, 2))); + return drush_download_file($download, $tmp_path . '/' . $file, $cache_duration); +} + +/** + * Unpacks a file to the specified download location. + * + * @return mixed + * The download location on success, FALSE on failure. + */ +function make_download_file_unpack($filename, $download_location, $name, $subtree = NULL) { + $success = FALSE; + + if (drush_file_is_tarball($filename)) { + $tmp_location = drush_tempdir(); + + if (!drush_tarball_extract($filename, $tmp_location)) { + return FALSE; + } + + if ($subtree) { + $tmp_location .= '/' . $subtree; + if (!file_exists($tmp_location)) { + return drush_set_error('DRUSH_MAKE_SUBTREE_NOT_FOUND', dt('Directory !subtree not found within !file', array('!subtree' => $subtree, '!file' => $filename))); + } + } + else { + $files = scandir($tmp_location); + unset($files[0]); // . directory + unset($files[1]); // .. directory + if ((count($files) == 1) && is_dir($tmp_location . '/' . current($files))) { + $tmp_location .= '/' . current($files); + } + } + + $success = drush_move_dir($tmp_location, $download_location, TRUE); + + // Remove the tarball. + if (file_exists($filename)) { + drush_delete_dir($filename, TRUE); + } + } + else { + // If this is an individual file, and no filename has been specified, + // assume the original name. + if (is_file($filename) && !$name) { + $name = basename($filename); + } + + // The destination directory has already been created by + // findDownloadLocation(). + $destination = $download_location . ($name ? '/' . $name : ''); + $success = drush_move_dir($filename, $destination, TRUE); + } + return $success ? $download_location : FALSE; +} + +/** + * Move a downloaded and unpacked file or directory into place. + */ +function _make_download_file_move($tmp_path, $filename, $download_location, $subtree = NULL) { + $lines = drush_scan_directory($tmp_path, '/./', array('.', '..'), 0, FALSE, 'filename', 0, TRUE); + $main_directory = basename($download_location); + if (count($lines) == 1) { + $directory = array_shift($lines); + if ($directory->basename != $main_directory) { + drush_move_dir($directory->filename, $tmp_path . DIRECTORY_SEPARATOR . $main_directory, TRUE); + } + drush_copy_dir($tmp_path . DIRECTORY_SEPARATOR . $main_directory . DIRECTORY_SEPARATOR . $subtree, $download_location, FILE_EXISTS_OVERWRITE); + drush_delete_dir($tmp_path, TRUE); + } + elseif (count($lines) > 1) { + drush_delete_dir($download_location, TRUE); + drush_move_dir($tmp_path . DIRECTORY_SEPARATOR . $subtree, $download_location, TRUE); + } + + // Remove the tarball. + if (file_exists($filename)) { + drush_delete_dir($filename, TRUE); + } + + if (file_exists($tmp_path)) { + drush_delete_dir($tmp_path, TRUE); + } + return TRUE; +} + + +/** + * For backwards compatibility. + */ +function make_download_get($name, $type, $download, $download_location) { + return make_download_file($name, $type, $download, $download_location); +} + +/** + * Copies a folder the specified location. + * + * @return mixed + * The TRUE on success, FALSE on failure. + */ +function make_download_copy($name, $type, $download, $download_location) { + if ($folder = _make_download_copy($download['url'])) { + drush_log(dt('@project copied from @url.', array('@project' => $name, '@url' => $download['url'])), LogLevel::OK); + return drush_copy_dir($folder, $download_location, FILE_EXISTS_OVERWRITE); + } + make_error('COPY_ERROR', dt('Unable to copy @project from @url.', array('@project' => $name, '@url' => $download['url']))); + return FALSE; +} + +/** + * Wrapper to drush_download_copy(). + * + * @param string $folder + * The location of the folder to copy. + * + * @return string + * The location of the folder, or FALSE on failure. + */ +function _make_download_copy($folder) { + if (substr($folder, 0, 7) == 'file://') { + $folder = substr($folder, 7); + } + + if (is_dir($folder)) { + return $folder; + } + return FALSE; +} + +/** + * Checks out a git repository to the specified download location. + * + * Allowed parameters in $download, in order of precedence: + * - 'tag' + * - 'revision' + * - 'branch' + * + * This will also attempt to write out release information to the + * .info file if the 'no-gitinfofile' option is FALSE. If + * $download['full_version'] is present, this will be used, otherwise, + * version will be set in this order of precedence: + * - 'tag' + * - 'branch' + * - 'revision' + * + * @return mixed + * The download location on success, FALSE otherwise. + */ +function make_download_git($name, $type, $download, $download_location) { + $tmp_path = make_tmp(); + $wc = _get_working_copy_option($download); + $checkout_after_clone = TRUE; + // If no download URL specified, assume anonymous clone from git.drupalcode.org. + $download['url'] = isset($download['url']) ? $download['url'] : "https://git.drupalcode.org/project/$name.git"; + // If no working-copy download URL specified, assume it is the same. + $download['wc_url'] = isset($download['wc_url']) ? $download['wc_url'] : $download['url']; + + // If not a working copy, and if --no-cache has not been explicitly + // declared, create a new git reference cache of the remote repository, + // or update the existing cache to fetch recent changes. + // @see package_handler_download_project() + $cache = !$wc && !drush_get_option('no-cache', FALSE); + if ($cache && ($git_cache = drush_directory_cache('git'))) { + $project_cache = $git_cache . '/' . $name . '-' . md5($download['url']); + // Set up a new cache, if it doesn't exist. + if (!file_exists($project_cache)) { + $command = 'git clone --mirror'; + if (drush_get_context('DRUSH_VERBOSE')) { + $command .= ' --verbose --progress'; + } + $command .= ' %s %s'; + drush_shell_cd_and_exec($git_cache, $command, $download['url'], $project_cache); + } + else { + // Update the --mirror clone. + drush_shell_cd_and_exec($project_cache, 'git remote update'); + } + $git_cache = $project_cache; + } + + // Use working-copy download URL if --working-copy specified. + $url = $wc ? $download['wc_url'] : $download['url']; + + $tmp_location = drush_tempdir() . '/' . basename($download_location); + + $command = 'git clone %s %s'; + if (drush_get_context('DRUSH_VERBOSE')) { + $command .= ' --verbose --progress'; + } + if ($cache) { + $command .= ' --reference ' . drush_escapeshellarg($git_cache); + } + + // the shallow clone option is only applicable to git entries which reference a tag or a branch + if (drush_get_option('shallow-clone', FALSE) && + (!empty($download['tag']) || !empty($download['branch']))) { + + $branch = (!empty($download['branch']) ? $download['branch'] : $download['tag']); + $command .= " --depth=1 --branch=${branch}"; + + // since the shallow copy option automatically "checks out" the requested branch, no further + // actions are needed after the clone command + $checkout_after_clone = FALSE; + } + + // Before we can checkout anything, we need to clone the repository. + if (!drush_shell_exec($command, $url, $tmp_location)) { + make_error('DOWNLOAD_ERROR', dt('Unable to clone @project from @url.', array('@project' => $name, '@url' => $url))); + return FALSE; + } + + drush_log(dt('@project cloned from @url.', array('@project' => $name, '@url' => $url)), LogLevel::OK); + + if ($checkout_after_clone) { + // Get the current directory (so we can move back later). + $cwd = getcwd(); + // Change into the working copy of the cloned repo. + chdir($tmp_location); + + // We want to use the most specific target possible, so first try a refspec. + if (!empty($download['refspec'])) { + if (drush_shell_exec("git fetch %s %s", $url, $download['refspec'])) { + drush_log(dt("Fetched refspec !refspec.", array('!refspec' => $download['refspec'])), LogLevel::OK); + + if (drush_shell_exec("git checkout FETCH_HEAD")) { + drush_log(dt("Checked out FETCH_HEAD."), LogLevel::INFO); + } + } + else { + make_error('DOWNLOAD_ERROR', dt("Unable to fetch the refspec @refspec from @project.", array('@refspec' => $download['refspec'], '@project' => $name))); + } + } + + // If there wasn't a refspec, try a tag. + elseif (!empty($download['tag'])) { + // @TODO: change checkout to refs path. + if (drush_shell_exec("git checkout %s", 'refs/tags/' . $download['tag'])) { + drush_log(dt("Checked out tag @tag.", array('@tag' => $download['tag'])), LogLevel::OK); + } + else { + make_error('DOWNLOAD_ERROR', dt("Unable to check out tag @tag.", array('@tag' => $download['tag']))); + } + } + + // If there wasn't a tag, try a specific revision hash. + elseif (!empty($download['revision'])) { + if (drush_shell_exec("git checkout %s", $download['revision'])) { + drush_log(dt("Checked out revision @revision.", array('@revision' => $download['revision'])), LogLevel::OK); + } + else { + make_error('DOWNLOAD_ERROR', dt("Unable to checkout revision @revision", array('@revision' => $download['revision']))); + } + } + + // If not, see if we at least have a branch. + elseif (!empty($download['branch'])) { + if (drush_shell_exec("git checkout %s", $download['branch']) && (trim(implode(drush_shell_exec_output())) != '')) { + drush_log(dt("Checked out branch @branch.", array('@branch' => $download['branch'])), LogLevel::OK); + } + elseif (drush_shell_exec("git checkout -b %s %s", $download['branch'], 'origin/' . $download['branch'])) { + drush_log(dt('Checked out branch origin/@branch.', array('@branch' => $download['branch'])), LogLevel::OK); + } + else { + make_error('DOWNLOAD_ERROR', dt('Unable to check out branch @branch.', array('@branch' => $download['branch']))); + } + } + + if (!empty($download['submodule'])) { + $command = 'git submodule update'; + foreach ($download['submodule'] as $option) { + $command .= ' --%s'; + } + if (call_user_func_array('drush_shell_exec', array_merge(array($command), $download['submodule']))) { + drush_log(dt('Initialized registered submodules.'), LogLevel::OK); + } + else { + make_error('DOWNLOAD_ERROR', dt('Unable to initialize submodules.')); + } + } + + // Move back to last current directory (first line). + chdir($cwd); + } + + // Move the directory into the final resting location. + drush_copy_dir($tmp_location, $download_location, FILE_EXISTS_OVERWRITE); + + return dirname($tmp_location); +} + +/** + * Checks out a Bazaar repository to the specified download location. + * + * @return mixed + * The download location on success, FALSE otherwise. + */ +function make_download_bzr($name, $type, $download, $download_location) { + $tmp_path = make_tmp(); + $tmp_location = drush_tempdir() . '/' . basename($download_location); + $wc = _get_working_copy_option($download); + if (!empty($download['url'])) { + $args = array(); + $command = 'bzr'; + if ($wc) { + $command .= ' branch --use-existing-dir'; + } + else { + $command .= ' export'; + } + if (isset($download['revision'])) { + $command .= ' -r %s'; + $args[] = $download['revision']; + } + $command .= ' %s %s'; + if ($wc) { + $args[] = $download['url']; + $args[] = $tmp_location; + } + else { + $args[] = $tmp_location; + $args[] = $download['url']; + } + array_unshift($args, $command); + if (call_user_func_array('drush_shell_exec', $args)) { + drush_log(dt('@project downloaded from @url.', array('@project' => $name, '@url' => $download['url'])), LogLevel::OK); + drush_copy_dir($tmp_location, $download_location, FILE_EXISTS_OVERWRITE); + return dirname($download_location); + } + } + else { + $download['url'] = dt("unspecified location"); + } + make_error('DOWNLOAD_ERROR', dt('Unable to download @project from @url.', array('@project' => $name, '@url' => $download['url']))); + drush_delete_dir(dirname($tmp_location), TRUE); + return FALSE; +} + +/** + * Checks out an SVN repository to the specified download location. + * + * @return mixed + * The download location on success, FALSE otherwise. + */ +function make_download_svn($name, $type, $download, $download_location) { + $wc = _get_working_copy_option($download); + if (!empty($download['url'])) { + if (!empty($download['interactive'])) { + $function = 'drush_shell_exec_interactive'; + } + else { + $options = ' --non-interactive'; + $function = 'drush_shell_exec'; + } + if (!isset($download['force']) || $download['force']) { + $options = ' --force'; + } + if ($wc) { + $command = 'svn' . $options . ' checkout'; + } + else { + $command = 'svn' . $options . ' export'; + } + + $args = array(); + + if (isset($download['revision'])) { + $command .= ' -r%s'; + $args[] = $download['revision']; + } + + $command .= ' %s %s'; + $args[] = $download['url']; + $args[] = $download_location; + + if (!empty($download['username'])) { + $command .= ' --username %s'; + $args[] = $download['username']; + if (!empty($download['password'])) { + $command .= ' --password %s'; + $args[] = $download['password']; + } + } + array_unshift($args, $command); + $result = call_user_func_array($function, $args); + if ($result) { + $args = array( + '@project' => $name, + '@command' => $command, + '@url' => $download['url'], + ); + drush_log(dt('@project @command from @url.', $args), LogLevel::OK); + return $download_location; + } + else { + $download['url'] = dt("unspecified location"); + } + } + else { + make_error('DOWNLOAD_ERROR', dt('Unable to download @project from @url.', array('@project' => $name, '@url' => $download['url']))); + return FALSE; + } +} + +/** + * Test that any supplied hash values match the hash of the file content. + * + * Unsupported hash algorithms are reported as failure. + */ +function _make_verify_checksums($info, $filename) { + $hash_algos = array('md5', 'sha1', 'sha256', 'sha512'); + // We only have something to do if a key is an + // available function. + if (array_intersect(array_keys($info), $hash_algos)) { + $content = file_get_contents($filename); + foreach ($hash_algos as $algo) { + if (!empty($info[$algo])) { + $hash = _make_hash($algo, $content); + if ($hash !== $info[$algo]) { + $args = array( + '@algo' => $algo, + '@file' => basename($filename), + '@expected' => $info[$algo], + '@hash' => $hash, + ); + make_error('DOWNLOAD_ERROR', dt('Checksum @algo verification failed for @file. Expected @expected, received @hash.', $args)); + return FALSE; + } + } + } + } + return TRUE; +} + +/** + * Calculate the hash of a string for a given algorithm. + */ +function _make_hash($algo, $string) { + switch ($algo) { + case 'md5': + return md5($string); + case 'sha1': + return sha1($string); + default: + return function_exists('hash') ? hash($algo, $string) : ''; + } +} diff --git a/vendor/drush/drush/commands/make/make.drush.inc b/vendor/drush/drush/commands/make/make.drush.inc new file mode 100644 index 0000000000..32e6813627 --- /dev/null +++ b/vendor/drush/drush/commands/make/make.drush.inc @@ -0,0 +1,1237 @@ + 'Restrict the make to this comma-separated list of projects. To specify all projects, pass *.', + 'example-value' => 'views,ctools', + ); + $libraries = array( + 'description' => 'Restrict the make to this comma-separated list of libraries. To specify all libraries, pass *.', + 'example-value' => 'tinymce', + ); + + $items['make'] = array( + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'description' => 'Turns a makefile into a working Drupal codebase.', + 'arguments' => array( + 'makefile' => 'Filename of the makefile to use for this build.', + 'build path' => 'The path at which to build the makefile.', + ), + 'examples' => array( + 'drush make example.make example' => 'Build the example.make makefile in the example directory.', + 'drush make --no-core --contrib-destination=. installprofile.make' => 'Build an installation profile within an existing Drupal site', + 'drush make http://example.com/example.make example' => 'Build the remote example.make makefile in the example directory.', + 'drush make example.make --no-build --lock=example.lock' => 'Write a new makefile to example.lock. All project versions will be resolved.', + ), + 'options' => array( + 'version' => 'Print the make API version and exit.', + 'concurrency' => array( + 'description' => 'Set the number of concurrent projects that will be processed at the same time. The default is 1.', + 'example-value' => '1', + ), + 'contrib-destination' => 'Specify a path under which modules and themes should be placed. Defaults to sites/all for Drupal 6,7 and the corresponding directory in the Drupal root for Drupal 8 and above.', + 'force-complete' => 'Force a complete build even if errors occur.', + 'ignore-checksums' => 'Ignore md5 checksums for downloads.', + 'md5' => array( + 'description' => 'Output an md5 hash of the current build after completion. Use --md5=print to print to stdout.', + 'example-value' => 'print', + 'value' => 'optional', + ), + 'make-update-default-url' => 'The default location to load the XML update information from.', + 'no-build' => 'Do not build a codebase. Makes the `build path` argument optional.', + 'no-cache' => 'Do not use the pm-download caching (defaults to cache enabled).', + 'no-clean' => 'Leave temporary build directories in place instead of cleaning up after completion.', + 'no-core' => 'Do not require a Drupal core project to be specified.', + 'no-recursion' => 'Do not recurse into the makefiles of any downloaded projects; you can also set [do_recursion] = 0 on a per-project basis in the makefile.', + 'no-patch-txt' => 'Do not write a PATCHES.txt file in the directory of each patched project.', + 'no-gitinfofile' => 'Do not modify .info files when cloning from Git.', + 'force-gitinfofile' => 'Force a modification of .info files when cloning from Git even if repository isn\'t hosted on Drupal.org.', + 'no-gitprojectinfo' => 'Do not inject project info into .info files when cloning from Git.', + 'overwrite' => 'Overwrite existing directories. Default is to merge.', + 'prepare-install' => 'Prepare the built site for installation. Generate a properly permissioned settings.php and files directory.', + 'tar' => 'Generate a tar archive of the build. The output filename will be [build path].tar.gz.', + 'test' => 'Run a temporary test build and clean up.', + 'translations' => 'Retrieve translations for the specified comma-separated list of language(s) if available for all projects.', + 'working-copy' => 'Preserves VCS directories, like .git, for projects downloaded using such methods.', + 'download-mechanism' => 'How to download files. Should be autodetected, but this is an override if it doesn\'t work. Options are "curl" and "make" (a native download method).', + 'projects' => $projects, + 'libraries' => $libraries, + 'allow-override' => array( + 'description' => 'Restrict the make options to a comma-separated list. Defaults to unrestricted.', + ), + 'lock' => array( + 'description' => 'Generate a makefile, based on the one passed in, with all versions *resolved*. Defaults to printing to the terminal, but an output file may be provided.', + 'example-value' => 'example.make.lock', + ), + 'shallow-clone' => array( + 'description' => 'For makefile entries which use git for downloading, this option will utilize shallow clones where possible (ie. by using the git-clone\'s depth=1 option). If the "working-copy" option is not desired, this option will significantly speed up makes which involve modules stored in very large git repos. In fact, if "working-copy" option is enabled, this option cannot be used.', + ), + 'bundle-lockfile' => array( + 'description' => 'Generate a lockfile for this build and copy it into the codebase (at sites/all/drush/platform.lock). An alternate path (relative to the Drupal root) can also be specified', + 'example-value' => 'sites/all/drush/example.make.lock', + ), + 'format' => array( + 'description' => 'The format for generated lockfiles. Options are "yaml" or "ini". Defaults to "yaml".', + 'example-value' => 'ini', + ), + 'core-quick-drupal' => array( + 'description' => 'Return project info for use by core-quick-drupal.', + 'hidden' => TRUE, + ), + 'includes' => 'A list of makefiles to include at build-time.', + 'overrides' => 'A list of makefiles to that can override values in other makefiles.', + ), + 'engines' => array('release_info'), + 'topics' => array('docs-make', 'docs-make-example'), + ); + + $items['make-generate'] = array( + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL, + 'description' => 'Generate a makefile from the current Drupal site.', + 'examples' => array( + 'drush generate-makefile example.make' => 'Generate a makefile with ALL projects versioned (should a project have a known version number)', + 'drush generate-makefile example.make --exclude-versions' => 'Generate a makefile with NO projects versioned', + 'drush generate-makefile example.make --exclude-versions=drupal,views,cck' => 'Generate a makefile with ALL projects versioned EXCEPT core, Views and CCK', + 'drush generate-makefile example.make --include-versions=admin_menu,og,ctools (--exclude-versions)' => 'Generate a makefile with NO projects versioned EXCEPT Admin Menu, OG and CTools.', + ), + 'options' => array( + 'exclude-versions' => 'Exclude all version numbers (default is include all version numbers) or optionally specify a list of projects to exclude from versioning', + 'include-versions' => 'Include a specific list of projects, while all other projects remain unversioned in the makefile (so implies --exclude-versions)', + 'format' => array( + 'description' => 'The format for generated makefile. Options are "yaml" or "ini". Defaults to "yaml".', + 'example-value' => 'ini', + ), + ), + 'engines' => array('release_info'), + 'aliases' => array('generate-makefile'), + ); + + $items['make-convert'] = array( + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'description' => 'Convert a legacy makefile into another format. Defaults to converting .make => .make.yml.', + 'arguments' => array( + 'makefile' => 'Filename of the makefile to convert.', + ), + 'options' => array( + 'projects' => $projects, + 'libraries' => $libraries, + 'includes' => 'A list of makefiles to include at build-time.', + 'format' => 'The format to which the make file should be converted. Accepted values include make, composer, and yml.', + ), + 'required-arguments' => TRUE, + 'examples' => array( + 'drush make-convert example.make --format=composer > composer.json' => 'Convert example.make to composer.json', + 'drush make-convert example.make --format=yml > example.make.yml' => 'Convert example.make to example.make.yml', + 'drush make-convert composer.lock --format=make > example.make' => 'Convert composer.lock example.make', + ), + ); + + // Hidden command to build a group of projects. + $items['make-process'] = array( + 'hidden' => TRUE, + 'arguments' => array( + 'directory' => 'The temporary working directory to use', + ), + 'options' => array( + 'projects-location' => 'Name of a temporary file containing json-encoded output of make_projects().', + 'manifest' => 'An array of projects already being processed.', + ), + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'engines' => array('release_info'), + ); + + $items['make-update'] = array( + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'description' => 'Process a makefile and outputs an equivalent makefile with projects version resolved to latest available.', + 'arguments' => array( + 'makefile' => 'Filename of the makefile to use for this build.', + ), + 'options' => array( + 'result-file' => array( + 'description' => 'Save to a file. If not provided, the updated makefile will be dumped to stdout.', + 'example-value' => 'updated.make', + ), + 'format' => array( + 'description' => 'The format for generated lockfiles. Options are "yaml" or "ini". Defaults to "yaml".', + 'example-value' => 'ini', + ), + 'includes' => 'A list of makefiles to include at build-time.', + ), + 'engines' => array('release_info', 'update_status'), + ); + + $items['make-lock'] = array( + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'description' => 'Process a makefile and outputs an equivalent makefile with projects version *resolved*. Respects pinned versions.', + 'arguments' => array( + 'makefile' => 'Filename of the makefile to use for this build.', + ), + 'options' => array( + 'result-file' => array( + 'description' => 'Save to a file. If not provided, the lockfile will be dumped to stdout.', + 'example-value' => 'platform.lock', + ), + 'format' => array( + 'description' => 'The format for generated lockfiles. Options are "yaml" or "ini". Defaults to "yaml".', + 'example-value' => 'ini', + ), + 'includes' => 'A list of makefiles to include at build-time.', + ), + 'allow-additional-options' => TRUE, + 'engines' => array('release_info', 'update_status'), + ); + + // Add docs topic. + $docs_dir = drush_get_context('DOC_PREFIX', DRUSH_BASE_PATH); + $items['docs-make'] = array( + 'description' => 'Drush Make overview with examples', + 'hidden' => TRUE, + 'topic' => TRUE, + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'callback' => 'drush_print_file', + 'callback arguments' => array($docs_dir . '/docs/make.md'), + ); + $items['docs-make-example'] = array( + 'description' => 'Drush Make example makefile', + 'hidden' => TRUE, + 'topic' => TRUE, + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'callback' => 'drush_print_file', + 'callback arguments' => array($docs_dir . '/examples/example.make.yml'), + ); + return $items; +} + +/** + * Command argument complete callback. + * + * @return array + * Strong glob of files to complete on. + */ +function make_make_complete() { + return array( + 'files' => array( + 'directories' => array( + 'pattern' => '*', + 'flags' => GLOB_ONLYDIR, + ), + 'make' => array( + 'pattern' => '*.make', + ), + ), + ); +} + +/** + * Validation callback for make command. + */ +function drush_make_validate($makefile = NULL, $build_path = NULL) { + // Don't validate if --version option is supplied. + if (drush_get_option('version', FALSE)) { + return; + } + + if (drush_get_option('shallow-clone', FALSE) && drush_get_option('working-copy', FALSE)) { + return drush_set_error('MAKE_SHALLOW_CLONE_WORKING_COPY_CONFLICT', dt('You cannot use "--shallow-clone" and "--working-copy" options together.')); + } + + // Error out if the build path is not valid and --no-build was not supplied. + if (!drush_get_option('no-build', FALSE) && !make_build_path($build_path)) { + return FALSE; + } +} + +/** + * Implements drush_hook_pre_COMMAND(). + * + * If --version option is supplied, print it and prevent execution of the command. + */ +function drush_make_pre_make($makefile = NULL, $build_path = NULL) { + if (drush_get_option('version', FALSE)) { + drush_print(dt('Drush make API version !version', array('!version' => MAKE_API))); + drush_print_pipe(MAKE_API); + // Prevent command execution. + return FALSE; + } +} + +/** + * Drush callback; make based on the makefile. + */ +function drush_make($makefile = NULL, $build_path = NULL) { + // Set the cache option based on our '--no-cache' option. + _make_enable_cache(); + + // Build. + if (!drush_get_option('no-build', FALSE)) { + $info = make_parse_info_file($makefile); + drush_log(dt('Beginning to build !makefile.', array('!makefile' => $makefile)), LogLevel::OK); + + // Default contrib destination depends on Drupal core version. + $core_version = str_replace('.x', '', $info['core'][0]); + $sitewide = drush_drupal_sitewide_directory($core_version); + $contrib_destination = drush_get_option('contrib-destination', $sitewide); + + $build_path = make_build_path($build_path); + $make_dir = realpath(dirname($makefile)); + + $success = make_projects(FALSE, $contrib_destination, $info, $build_path, $make_dir); + if ($success) { + make_libraries(FALSE, $contrib_destination, $info, $build_path, $make_dir); + + if (drush_get_option('prepare-install')) { + make_prepare_install($build_path); + } + if ($option = drush_get_option('md5')) { + $md5 = make_md5(); + if ($option === 'print') { + drush_print($md5); + } + else { + drush_log(dt('Build hash: %md5', array('%md5' => $md5)), LogLevel::OK); + } + } + // Only take final build steps if not in testing mode. + if (!drush_get_option('test')) { + if (drush_get_option('tar')) { + make_tar($build_path); + } + else { + make_move_build($build_path); + } + } + make_clean_tmp(); + } + else { + return make_error('MAKE_PROJECTS_FAILED', dt('Drush Make failed to download all projects. See the log above for the specific errors.')); + } + } + + // Process --lock and --bundle-lockfile + $lockfiles = array(); + if ($result_file = drush_get_option('bundle-lockfile', FALSE)) { + if ($result_file === TRUE) { + $result_file = 'sites/all/drush/platform.make'; + } + $lockfiles[] = $build_path . '/' . $result_file; + } + if ($result_file = drush_get_option('lock', FALSE)) { + $lockfiles[] = $result_file; + } + if (count($lockfiles)) { + foreach ($lockfiles as $lockfile) { + if ($lockfile !== TRUE) { + $result_file = drush_normalize_path($lockfile); + drush_mkdir(dirname($result_file), $required = TRUE); + drush_set_option('result-file', $result_file); + } + drush_invoke('make-lock', $makefile); + drush_unset_option('result-file'); + } + } + + // Used by core-quick-drupal command. + // @see drush_core_quick_drupal(). + if (drush_get_option('core-quick-drupal', FALSE)) { + return $info; + } +} + +/** + * Command callback; convert ini makefile to YAML. + */ +function drush_make_convert($source) { + $dest_format = drush_get_option('format', 'yml'); + + // Load source data. + $source_format = pathinfo($source, PATHINFO_EXTENSION); + + if ($source_format == $dest_format || $source_format == 'lock' && $dest_format == 'composer') { + drush_print('The source format cannot be the same as the destination format.'); + } + + // Obtain drush make $info array, converting if necessary. + switch ($source_format) { + case 'make': + case 'yml': + case 'yaml': + $info = make_parse_info_file($source); + break; + case 'lock': + $composer_json_file = str_replace('lock', 'json', $source); + if (!file_exists($composer_json_file)) { + drush_print('Please ensure that a composer.json file is in the same directory as the specified composer.lock file.'); + return FALSE; + } + $composer_json = json_decode(make_get_data($composer_json_file), TRUE); + $composer_lock = json_decode(make_get_data($source), TRUE); + $info = drush_make_convert_composer_to_make($composer_lock, $composer_json); + break; + case 'json': + drush_print('Please use composer.lock instead of composer.json as source for conversion.'); + return FALSE; + break; + } + + // Output into destination formation. + switch ($dest_format) { + case 'yml': + case 'yaml': + $output = drush_make_convert_make_to_yml($info); + break; + + case 'make': + foreach ($info['projects'] as $key => $project) { + $info['projects'][$key]['_type'] = $info['projects'][$key]['type']; + } + foreach ($info['libraries'] as $key => $library) { + $info['libraries'][$key]['_type'] = 'librarie'; + } + $output = _drush_make_generate_makefile_contents($info['projects'], $info['libraries'], $info['core'], $info['defaults']); + + break; + + case 'composer': + $output = drush_make_convert_make_to_composer($info); + break; + } + + drush_print($output); +} + +/** + * Converts a composer.lock array into a traditional drush make array. + * + * @param array $composer_lock + * An array of composer.lock data. + * + * @param array $composer_json + * An array of composer.json data. + * + * @return array A traditional drush make info array. + * A traditional drush make info array. + */ +function drush_make_convert_composer_to_make($composer_lock, $composer_json) { + $info = array( + 'core' => array(), + 'api' => 2, + 'defaults' => array( + 'projects' => array( + 'subdir' => 'contrib', + ), + ), + 'projects' => array(), + 'libraries' => array(), + ); + + // The make generation function requires that projects be grouped by type, + // or else duplicative project groups will be created. + $core = array(); + $modules = array(); + $themes = array(); + $profiles = array(); + $libraries = array(); + foreach ($composer_lock['packages'] as $key => $package) { + if (strpos($package['name'], 'drupal/') === 0 && in_array($package['type'], array('drupal-core', 'drupal-theme', 'drupal-module', 'drupal-profile'))) { + $project_name = str_replace('drupal/', '', $package['name']); + + switch ($package['type']) { + case 'drupal-core': + $project_name = 'drupal'; + $group =& $core; + $group[$project_name]['type'] = 'core'; + $info['core'] = substr($package['version'], 0, 1) . '.x'; + break; + case 'drupal-theme': + $group =& $themes; + $group[$project_name]['type'] = 'theme'; + break; + case 'drupal-module': + $group =& $modules; + $group[$project_name]['type'] = 'module'; + break; + case 'drupal-profile': + $group =& $profiles; + $group[$project_name]['type'] = 'profile'; + break; + } + + $group[$project_name]['download']['type'] = 'git'; + $group[$project_name]['download']['url'] = $package['source']['url']; + // Dev versions should use git branch + revision, otherwise a tag is used. + if (strstr($package['version'], 'dev')) { + // 'dev-' prefix indicates a branch-alias. Stripping the dev prefix from + // the branch name is sufficient. + // @see https://getcomposer.org/doc/articles/aliases.md + if (strpos($package['version'], 'dev-') === 0) { + $group[$project_name]['download']['branch'] = substr($package['version'], 4); + } + // Otherwise, leave as is. Version may already use '-dev' suffix. + else { + $group[$project_name]['download']['branch'] = $package['version']; + } + $group[$project_name]['download']['revision'] = $package['source']['reference']; + } + elseif ($package['type'] == 'drupal-core') { + // For 7.x tags, replace 7.xx.0 with 7.xx. + if ($info['core'] == '7.x') { + $group[$project_name]['download']['tag']= substr($package['version'], 0, 4); + } + else { + $group[$project_name]['download']['tag'] = $package['version']; + } + } + else { + // Make tag versioning drupal-friendly. 8.1.0-alpha1 => 8.x-1.0-alpha1. + $major_version = substr($package['version'], 0 ,1); + $the_rest = substr($package['version'], 2, strlen($package['version'])); + $group[$project_name]['download']['tag'] = "$major_version.x-$the_rest"; + } + + if (!empty($package['extra']['patches_applied'])) { + foreach ($package['extra']['patches_applied'] as $desc => $url) { + $group[$project_name]['patch'][] = $url; + } + } + } + // Include any non-drupal libraries that exist in both .lock and .json. + elseif (!in_array($package['type'], array('composer-plugin', 'metapackage')) + && array_key_exists($package['name'], $composer_json['require'])) { + $project_name = $package['name']; + $libraries[$project_name]['type'] = 'library'; + $libraries[$project_name]['download']['type'] = 'git'; + $libraries[$project_name]['download']['url'] = $package['source']['url']; + $libraries[$project_name]['download']['branch'] = $package['version']; + $libraries[$project_name]['download']['revision'] = $package['source']['reference']; + } + } + + $info['projects'] = $core + $modules + $themes; + $info['libraries'] = $libraries; + + return $info; +} + +/** + * Converts a drush info array to a composer.json array. + * + * @param array $info + * A drush make info array. + * + * @return string + * A json encoded composer.json schema object. + */ +function drush_make_convert_make_to_composer($info) { + $core_major_version = substr($info['core'], 0, 1); + $core_project_name = $core_major_version == 7 ? 'drupal/drupal' : 'drupal/core'; + + if ($core_major_version == 7) { + // Add PHP version and extension requirements. + $php_reqs = array( + 'php' => '>= 5.2.5', + 'ext-curl' => '*', + 'ext-gd' => '*', + 'ext-json' => '*', + 'ext-openssl' => '*', + 'ext-pdo' => '*', + 'ext-pdo_mysql' => '*', + 'ext-xml' => '*', + ); + + // Add default projects. + $projects = array( + 'composer/installers' => '^1.2', + 'cweagans/composer-patches' => '^1.6', + 'drupal-composer/preserve-paths' => '^0.1', + 'drush/drush' => '~8.0', + $core_project_name => str_replace('x', '*', $info['core']), + ); + + $conflict = array( + 'drupal/core' => '8.*', + ); + + $extra = array( + 'installer-paths' => array( + 'web' => array('type:drupal-core'), + 'web/profiles/{$name}' => array('type:drupal-profile'), + 'web/sites/all/drush/{$name}' => array('type:drupal-drush'), + 'web/sites/all/libraries/{$name}' => array('type:drupal-library'), + 'web/sites/all/modules/contrib/{$name}' => array('type:drupal-module'), + 'web/sites/all/themes/contrib/{$name}' => array('type:drupal-theme'), + ), + 'patches' => array(), + 'preserve-paths' => array( + 'web/sites/all/drush', + 'web/sites/all/libraries', + 'web/sites/all/modules/contrib', + 'web/sites/all/modules/custom', + 'web/sites/all/modules/features', + 'web/sites/all/themes/contrib', + 'web/sites/all/themes/custom', + 'web/sites/all/translations', + 'web/sites/default' + ), + ); + } + else { + $php_reqs = array(); + + // Add default projects. + $projects = array( + 'composer/installers' => '^1.2', + 'cweagans/composer-patches' => '^1.6', + 'drush/drush' => '^9.0.0', + $core_project_name => str_replace('x', '*', $info['core']), + ); + + $conflict = array( + 'drupal/drupal' => '*', + ); + + $extra = array( + 'installer-paths' => array( + 'web/core' => array('type:drupal-core'), + 'web/libraries/{$name}' => array('type:drupal-library'), + 'web/modules/contrib/{$name}' => array('type:drupal-module'), + 'web/profiles/contrib/{$name}' => array('type:drupal-profile'), + 'web/themes/contrib/{$name}' => array('type:drupal-theme'), + 'drush/contrib/{$name}' => array('type:drupal-drush'), + ), + 'patches' => array(), + ); + } + + // Iterate over projects, populating composer-friendly array. + foreach ($info['projects'] as $project_name => $project) { + switch ($project['type']) { + case 'core': + $project['name'] = $core_project_name; + $projects[$project['name']] = '^' . str_replace('x', '*', $project['version']); + break; + + default: + $project['name'] = "drupal/$project_name"; + $projects[$project['name']] = drush_make_convert_project_to_composer($project, $core_major_version); + break; + } + + // Add project patches. + if (!empty($project['patch'])) { + foreach($project['patch'] as $key => $patch) { + $patch_description = "Enter {$project['name']} patch #$key description here"; + $extra['patches'][$project['name']][$patch_description] = $patch; + } + } + } + + // Iterate over libraries, populating composer-friendly array. + if (!empty($info['libraries'])) { + foreach ($info['libraries'] as $library_name => $library) { + $library_name = 'Verify project name: ' . $library_name; + $projects[$library_name] = drush_make_convert_project_to_composer($library, $core_major_version); + } + } + + // Sort the projects to simplify pull requests on composer.json due to the + // sort-packages configuration. + ksort($projects); + + $output = array( + 'name' => 'Enter project name here', + 'description' => 'Enter project description here', + 'type' => 'project', + 'repositories' => array( + array('type' => 'composer', 'url' => 'https://packages.drupal.org/' . $core_major_version), + ), + 'require' => array_merge($php_reqs, $projects), + 'conflict'=> $conflict, + 'minimum-stability' => 'dev', + 'prefer-stable' => TRUE, + 'config' => array( + 'sort-packages' => TRUE, + ), + 'extra' => $extra, + ); + + $output = json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + + return $output; +} + +/** + * Converts a make file project array into a composer project version string. + * + * @param array $original_project + * A project dependency, as defined in a make file. + * + * @param string $core_major_version + * The major core version. E.g., 6, 7, 8, etc. + * + * @return string + * The project version, in composer syntax. + * + */ +function drush_make_convert_project_to_composer($original_project, $core_major_version) { + // Typical specified version with major version "x" removed. + if (!empty($original_project['version'])) { + $version = drush_make_convert_version_to_composer($original_project['version']); + } + // Git branch or revision. + elseif (!empty($original_project['download'])) { + switch ($original_project['download']['type']) { + case 'git': + if (!empty($original_project['download']['branch'])) { + $version = drush_make_convert_version_to_composer($original_project['download']['branch']); + } + if (!empty($original_project['download']['tag'])) { + $version = drush_make_convert_version_to_composer($original_project['download']['tag']); + } + if (!empty($project['download']['revision'])) { + $version .= '#' . $original_project['download']['revision']; + } + break; + + default: + $version = 'Enter correct project name and version number'; + break; + } + } + + return $version; +} + +/** + * Converts a drush version into a composer version. + * + * @param string $version + * Original drush version. + * + * @return string + * The converted composer version. + */ +function drush_make_convert_version_to_composer($version) { + $cver = '*'; + if (!empty($version)) { + if (substr($version, -3) === 'dev') { + // Dev versions maintain the 7.x-dev syntax. + $cver = $version; + } + else { + // Replace '1.x' with '^1.*'. + $cver = '^' . str_replace('x', '*', $version); + } + } + + return $cver; +} + +/** + * Converts a drush info array to a YAML array. + * + * @param array $info + * A drush make info array. + * + * @return string + * A yaml encoded info array. + */ +function drush_make_convert_make_to_yml($info) { + // Remove incorrect value. + unset($info['format']); + + // Replace "*" with "~" for project versions. + foreach ($info['projects'] as $key => $project) { + if ($project['version'] == '*') { + $info['projects'][$key]['version'] = '~'; + } + } + + $dumper = drush_load_engine('outputformat', 'yaml'); + $output = $dumper->format($info, array()); + + return $output; +} + +/** + * Drush callback: hidden file to process an individual project. + * + * @param string $directory + * Directory where the project is being built. + */ +function drush_make_process($directory) { + drush_get_engine('release_info'); + + // Set the temporary directory. + make_tmp(TRUE, $directory); + if (!$projects_location = drush_get_option('projects-location')) { + return drush_set_error('MAKE-PROCESS', dt('No projects passed to drush_make_process')); + } + $projects = json_decode(file_get_contents($projects_location), TRUE); + $manifest = drush_get_option('manifest', array()); + + foreach ($projects as $project) { + if ($instance = DrushMakeProject::getInstance($project['type'], $project)) { + $instance->setManifest($manifest); + $instance->make(); + } + else { + make_error('PROJECT-TYPE', dt('Non-existent project type %type on project %project.', array('%type' => $project['type'], '%project' => $project['name']))); + } + } +} + +/** + * Gather additional data on all projects specified in the make file. + */ +function make_prepare_projects($recursion, $info, $contrib_destination = '', $build_path = '', $make_dir = '') { + $release_info = drush_get_engine('release_info'); + + // Nothing to make if the project list is empty. Maybe complain about it. + if (empty($info['projects'])) { + if (drush_get_option('no-core') || $recursion) { + return TRUE; + } + else { + return drush_set_error('MAKE_NO_CORE', dt('No core project specified.')); + } + } + + // Obtain translations to download along with the projects. + $translations = array(); + if (isset($info['translations'])) { + $translations = $info['translations']; + } + if ($arg_translations = drush_get_option('translations', FALSE)) { + $translations = array_merge(explode(',', $arg_translations), $translations); + } + + // Normalize projects. + $projects = array(); + $ignore_checksums = drush_get_option('ignore-checksums'); + foreach ($info['projects'] as $key => $project) { + // Merge the known data onto the project info. + $project += array( + 'name' => $key, + 'type' => 'module', + 'core' => $info['core'], + 'translations' => $translations, + 'build_path' => $build_path, + 'contrib_destination' => $contrib_destination, + 'version' => '', + 'location' => drush_get_option('make-update-default-url', ReleaseInfo::DEFAULT_URL), + 'subdir' => '', + 'directory_name' => '', + 'make_directory' => $make_dir, + 'options' => array(), + ); + // MD5 Checksum. + if ($ignore_checksums) { + unset($project['download']['md5']); + } + elseif (!empty($project['md5'])) { + $project['download']['md5'] = $project['md5']; + } + + // If download components are specified, but not the download + // type, default to git. + if (isset($project['download']) && !isset($project['download']['type'])) { + $project['download']['type'] = 'git'; + } + // Localization server. + if (!isset($project['l10n_url']) && ($project['location'] == ReleaseInfo::DEFAULT_URL)) { + $project['l10n_url'] = MAKE_DEFAULT_L10N_SERVER; + } + // Classify projects in core or contrib. + if ($project['type'] == 'core') { + $project['download_type'] = 'core'; + } + elseif ($project['location'] != ReleaseInfo::DEFAULT_URL || !isset($project['download'])) { + $request = make_prepare_request($project); + $is_core = $release_info->checkProject($request, 'core'); + $project['download_type'] = ($is_core ? 'core' : 'contrib'); + $project['type'] = $is_core ? 'core' : $project['type']; + } + else { + $project['download_type'] = ($project['name'] == 'drupal' ? 'core' : 'contrib'); + } + $projects[$project['download_type']][$project['name']] = $project; + } + + // Verify there're enough cores, but not too many. + $cores = !empty($projects['core']) ? count($projects['core']) : 0; + if (drush_get_option('no-core')) { + unset($projects['core']); + } + elseif ($cores == 0 && !$recursion) { + return drush_set_error('MAKE_NO_CORE', dt('No core project specified.')); + } + elseif ($cores == 1 && $recursion) { + unset($projects['core']); + } + elseif ($cores > 1) { + return drush_set_error('MAKE_MULTIPLE_CORES', dt('More than one core project specified.')); + } + + // Set download type = pm for suitable projects. + foreach (array_keys($projects) as $project_type) { + foreach ($projects[$project_type] as $project) { + if (make_project_needs_release_info($project)) { + $request = make_prepare_request($project, $project_type); + $release = $release_info->selectReleaseBasedOnStrategy($request, '', 'ignore'); + if ($release === FALSE) { + return FALSE; + } + // Override default project type with data from update service. + if (!isset($info['projects'][$project['name']]['type'])) { + $project['type'] = $release_info->get($request)->getType(); + } + + if (!isset($project['download'])) { + $project['download'] = array( + 'type' => 'pm', + 'full_version' => $release['version'], + 'download_link' => $release['download_link'], + 'status url' => $request['status url'], + ); + } + } + $projects[$project_type][$project['name']] = $project; + } + } + if (!$recursion) { + $projects += array( + 'core' => array(), + 'contrib' => array(), + ); + drush_set_option('DRUSH_MAKE_PROJECTS', array_merge($projects['core'], $projects['contrib'])); + } + return $projects; +} + +/** + * Process all projects specified in the make file. + */ +function make_projects($recursion, $contrib_destination, $info, $build_path, $make_dir) { + $projects = make_prepare_projects($recursion, $info, $contrib_destination, $build_path, $make_dir); + // Abort if there was an error processing projects. + if ($projects === FALSE) { + return FALSE; + } + + // Core is built in place, rather than using make-process. + if (!empty($projects['core']) && count($projects['core'])) { + $project = current($projects['core']); + $project = DrushMakeProject::getInstance('core', $project); + $project->make(); + } + + // Process all projects concurrently using make-process. + if (isset($projects['contrib'])) { + $concurrency = drush_get_option('concurrency', 1); + // Generate $concurrency sub-processes to do the actual work. + $invocations = array(); + $thread = 0; + foreach ($projects['contrib'] as $project) { + $thread = ++$thread % $concurrency; + // Ensure that we've set this sub-process up. + if (!isset($invocations[$thread])) { + $invocations[$thread] = array( + 'args' => array( + make_tmp(), + ), + 'options' => array( + 'projects' => array(), + ), + 'site' => array(), + ); + } + // Add the project to this sub-process. + $invocations[$thread]['options']['projects'][] = $project; + // Add the manifest so recursive downloads do not override projects. + $invocations[$thread]['options']['manifest'] = array_keys($projects['contrib']); + } + if (!empty($invocations)) { + // Backend options. + $backend_options = array( + 'concurrency' => $concurrency, + 'method' => 'POST', + ); + + // Store projects in temporary files since passing this much data on the + // pipe buffer can break on certain systems. + _make_write_project_json($invocations); + + $common_options = drush_redispatch_get_options(); + // Merge in stdin options since we process makefiles recursively. See http://drupal.org/node/1510180. + $common_options = array_merge($common_options, drush_get_context('stdin')); + // Package handler should use 'wget'. + $common_options['package-handler'] = 'wget'; + + // Avoid any prompts from CLI. + $common_options['yes'] = TRUE; + + // Use cache unless explicitly turned off. + if (!drush_get_option('no-cache', FALSE)) { + $common_options['cache'] = TRUE; + } + // Unless --verbose or --debug are passed, quiter backend output. + if (empty($common_options['verbose']) && empty($common_options['debug'])) { + $backend_options['#output-label'] = FALSE; + $backend_options['integrate'] = TRUE; + } + $results = drush_backend_invoke_concurrent($invocations, $common_options, $backend_options, 'make-process', '@none'); + if (!empty($results['error_log'])) { + return FALSE; + } + } + } + return TRUE; +} + +/** + * Writes out project data to temporary files. + * + * @param array &$invocations + * An array containing projects sorted by thread. + */ +function _make_write_project_json(array &$invocations) { + foreach ($invocations as $thread => $info) { + $projects = $info['options']['projects']; + unset($invocations[$thread]['options']['projects']); + $temp_file = drush_tempnam('make_projects'); + file_put_contents($temp_file, json_encode($projects)); + $invocations[$thread]['options']['projects-location'] = $temp_file; + } +} + +/** + * Gather additional data on all libraries specified in the make file. + */ +function make_prepare_libraries($recursion, $info, $contrib_destination = '', $build_path = '', $make_dir = '') { + // Nothing to make if the libraries list is empty. + if (empty($info['libraries'])) { + return; + } + + $libraries = array(); + $ignore_checksums = drush_get_option('ignore-checksums'); + foreach ($info['libraries'] as $key => $library) { + if (!is_string($key) || !is_array($library)) { + // TODO Print a prettier message. + continue; + } + // Merge the known data onto the library info. + $library += array( + 'name' => $key, + 'core' => $info['core'], + 'build_path' => $build_path, + 'contrib_destination' => $contrib_destination, + 'subdir' => '', + 'directory_name' => $key, + 'make_directory' => $make_dir, + ); + if ($ignore_checksums) { + unset($library['download']['md5']); + } + $libraries[$key] = $library; + } + if (!$recursion) { + drush_set_option('DRUSH_MAKE_LIBRARIES', $info['libraries']); + } + return $libraries; +} + +/** + * Process all libraries specified in the make file. + */ +function make_libraries($recursion, $contrib_destination, $info, $build_path, $make_dir) { + $libraries = make_prepare_libraries($recursion, $info, $contrib_destination, $build_path, $make_dir); + if (empty($libraries)) { + return; + } + foreach ($libraries as $key => $library) { + $class = DrushMakeProject::getInstance('library', $library); + $class->make(); + } +} + +/** + * The path where the final build will be placed. + */ +function make_build_path($build_path) { + static $saved_path; + if (isset($saved_path)) { + return $saved_path; + } + + // Determine the base of the build. + if (drush_get_option('tar')) { + $build_path = dirname($build_path) . '/' . basename($build_path, '.tar.gz') . '.tar.gz'; + } + elseif (isset($build_path) && (!empty($build_path) || $build_path == '.')) { + $build_path = rtrim($build_path, '/'); + } + // Allow tests to run without a specified base path. + elseif (drush_get_option('test') || drush_confirm(dt("Make new site in the current directory?"))) { + $build_path = '.'; + } + else { + return drush_user_abort(dt('Build aborted.')); + } + if ($build_path != '.' && file_exists($build_path) && !drush_get_option('no-core', FALSE)) { + return drush_set_error('MAKE_PATH_EXISTS', dt('Base path %path already exists.', array('%path' => $build_path))); + } + $saved_path = $build_path; + return $build_path; +} + +/** + * Move the completed build into place. + */ +function make_move_build($build_path) { + $tmp_path = make_tmp(); + $ret = TRUE; + if ($build_path == '.' || (drush_get_option('no-core', FALSE) && file_exists($build_path))) { + $info = drush_scan_directory($tmp_path . DIRECTORY_SEPARATOR . '__build__', '/./', array('.', '..'), 0, FALSE, 'filename', 0, TRUE); + foreach ($info as $file) { + $destination = $build_path . DIRECTORY_SEPARATOR . $file->basename; + if (file_exists($destination)) { + // To prevent the removal of top-level directories such as 'modules' or + // 'themes', descend in a level if the file exists. + // TODO: This only protects one level of directories from being removed. + $overwrite = drush_get_option('overwrite', FALSE) ? FILE_EXISTS_OVERWRITE : FILE_EXISTS_MERGE; + if (is_dir($destination)) { + $files = drush_scan_directory($file->filename, '/./', array('.', '..'), 0, FALSE); + foreach ($files as $file) { + $ret = $ret && drush_copy_dir($file->filename, $destination . DIRECTORY_SEPARATOR . $file->basename, $overwrite); + } + } + else { + $ret = $ret && drush_copy_dir($file->filename, $destination, $overwrite); + } + } + else { + $ret = $ret && drush_copy_dir($file->filename, $destination); + } + } + } + else { + drush_mkdir(dirname($build_path)); + $ret = drush_move_dir($tmp_path . DIRECTORY_SEPARATOR . '__build__', $tmp_path . DIRECTORY_SEPARATOR . basename($build_path), TRUE); + $ret = $ret && drush_copy_dir($tmp_path . DIRECTORY_SEPARATOR . basename($build_path), $build_path); + } + + // Copying to final destination resets write permissions. Re-apply. + if (drush_get_option('prepare-install')) { + $default = $build_path . '/sites/default'; + chmod($default . '/settings.php', 0666); + chmod($default . '/files', 0777); + } + + if (!$ret) { + return drush_set_error('MAKE_CANNOT_MOVE_BUILD', dt("Cannot move build into place.")); + } + return $ret; +} + +/** + * Create a request array suitable for release_info engine. + * + * This is a convenience function to easily integrate drush_make + * with drush release_info engine. + * + * @todo: refactor 'make' to internally work with release_info keys. + * + * @param array $project + * Project array. + * @param string $type + * 'contrib' or 'core'. + */ +function make_prepare_request($project, $type = 'contrib') { + $request = array( + 'name' => $project['name'], + 'drupal_version' => $project['core'], + 'status url' => $project['location'], + ); + if ($project['version'] != '') { + $request['project_version'] = $project['version']; + $request['version'] = $type == 'core' ? $project['version'] : $project['core'] . '-' . $project['version']; + } + return $request; +} + +/** + * Determine if the release information is required for this + * project. When it is determined that it is, this potentially results + * in the use of pm-download to process the project. + * + * If the location of the project is not customized (uses d.o), and + * one of the following is true, then release information is required: + * + * - $project['type'] has not been specified + * - $project['download'] has not been specified + * + * @see make_projects() + */ +function make_project_needs_release_info($project) { + return isset($project['location']) + // Only fetch release info if the project type is unknown OR if + // download attributes are unspecified. + && (!isset($project['type']) || !isset($project['download'])); +} + +/** + * Enables caching if not explicitly disabled. + * + * @return bool + * The previous value of the 'cache' option. + */ +function _make_enable_cache() { + $cache_before = drush_get_option('cache'); + if (!drush_get_option('no-cache', FALSE)) { + drush_set_option('cache', TRUE); + } + return $cache_before; +} diff --git a/vendor/drush/drush/commands/make/make.project.inc b/vendor/drush/drush/commands/make/make.project.inc new file mode 100644 index 0000000000..60e73257c7 --- /dev/null +++ b/vendor/drush/drush/commands/make/make.project.inc @@ -0,0 +1,740 @@ + $value) { + $this->{$key} = $value; + } + if (!empty($this->options['working-copy'])) { + $this->download['working-copy'] = TRUE; + } + // Don't recurse when we're using a pre-built profile tarball. + if ($this->variant == 'projects') { + $this->do_recursion = FALSE; + } + } + + /** + * Get an instance for the type and project. + * + * @param string $type + * Type of project: core, library, module, profile, or translation. + * @param array $project + * Project information. + * + * @return mixed + * An instance for the project or FALSE if invalid type. + */ + public static function getInstance($type, $project) { + if (!isset(self::$self[$type][$project['name']])) { + $class = 'DrushMakeProject_' . $type; + self::$self[$type][$project['name']] = class_exists($class) ? new $class($project) : FALSE; + } + return self::$self[$type][$project['name']]; + } + + /** + * Set the manifest array. + * + * @param array $manifest + * An array of projects as generated by `make_projects`. + */ + public function setManifest($manifest) { + $this->manifest = $manifest; + } + + /** + * Download a project. + */ + function download() { + $this->downloaded = TRUE; + + // In some cases, make_download_factory() is going to need to know the + // full version string of the project we're trying to download. However, + // the version is a project-level attribute, not a download-level + // attribute. So, if we don't already have a full version string in the + // download array (e.g. if it was initialized via the release history XML + // for the PM case), we take the version info from the project-level + // attribute, convert it into a full version string, and stuff it into + // $this->download so that the download backend has access to it, too. + if (!empty($this->version) && empty($this->download['full_version'])) { + $full_version = ''; + $matches = array(); + // Core needs different conversion rules than contrib. + if (!empty($this->type) && $this->type == 'core') { + // Generally, the version for core is already set properly. + $full_version = $this->version; + // However, it might just be something like '7' or '7.x', in which + // case we need to turn that into '7.x-dev'; + if (preg_match('/^\d+(\.x)?$/', $this->version, $matches)) { + // If there's no '.x' already, append it. + if (empty($matches[1])) { + $full_version .= '.x'; + } + $full_version .= '-dev'; + } + } + // Contrib. + else { + // If the version doesn't already define a core version, prepend it. + if (!preg_match('/^\d+\.x-\d+.*$/', $this->version)) { + // Just find the major version from $this->core so we don't end up + // with version strings like '7.12-2.0'. + $core_parts = explode('.', $this->core); + $full_version = $core_parts[0] . '.x-'; + } + $full_version .= $this->version; + // If the project-level version attribute is just a number it's a major + // version. + if (preg_match('/^\d+(\.x)?$/', $this->version, $matches)) { + // If there's no '.x' already, append it. + if (empty($matches[1])) { + $full_version .= '.x'; + } + $full_version .= '-dev'; + } + } + $this->download['full_version'] = $full_version; + } + + $this->download['variant'] = $this->variant; + + if (make_download_factory($this->name, $this->type, $this->download, $this->download_location) === FALSE) { + $this->downloaded = FALSE; + } + return $this->downloaded; + } + + /** + * Build a project. + */ + function make() { + if ($this->made) { + drush_log(dt('Attempt to build project @project more then once prevented.', array('@project' => $this->name))); + return TRUE; + } + $this->made = TRUE; + + if (!isset($this->download_location)) { + $this->download_location = $this->findDownloadLocation(); + } + if ($this->download() === FALSE) { + return FALSE; + } + if (!$this->addLockfile($this->download_location)) { + return FALSE; + } + if (!$this->applyPatches($this->download_location)) { + return FALSE; + } + if (!$this->getTranslations($this->download_location)) { + return FALSE; + } + // Handle .info file re-writing (if so desired). + if (!drush_get_option('no-gitinfofile', FALSE) && isset($this->download['type']) && $this->download['type'] == 'git') { + $this->processGitInfoFiles(); + } + // Clean-up .git directories. + if (!_get_working_copy_option($this->download)) { + $this->removeGitDirectory(); + } + if (!$this->recurse($this->download_location)) { + return FALSE; + } + return TRUE; + } + + /** + * Determine the location to download project to. + */ + function findDownloadLocation() { + $this->path = $this->generatePath(); + $this->project_directory = !empty($this->directory_name) ? $this->directory_name : $this->name; + $this->download_location = $this->path . '/' . $this->project_directory; + // This directory shouldn't exist yet -- if it does, stop, + // unless overwrite has been set to TRUE. + if (is_dir($this->download_location) && !$this->overwrite) { + return drush_set_error('MAKE_DIRECTORY_EXISTS', dt('Directory not empty: !directory', array('!directory' => $this->download_location))); + } + elseif ($this->download['type'] === 'pm') { + // pm-download will create the final contrib directory. + drush_mkdir(dirname($this->download_location)); + } + else { + drush_mkdir($this->download_location); + } + return $this->download_location; + } + + /** + * Rewrite relative URLs and file:/// URLs + * + * relative path -> absolute path using the make_directory + * local file:/// urls -> local paths + * + * @param mixed &$info + * Either an array or a simple url string. The `$info` variable will be + * transformed into an array. + */ + protected function preprocessLocalFileUrl(&$info) { + if (is_string($info)) { + $info = array('url' => $info, 'local' => FALSE); + } + + if (!_drush_is_url($info['url']) && !drush_is_absolute_path($info['url'])) { + $info['url'] = $this->make_directory . '/' . $info['url']; + $info['local'] = TRUE; + } elseif (substr($info['url'], 0, 8) == 'file:///') { + $info['url'] = substr($info['url'], 7); + $info['local'] = TRUE; + } + } + + /** + * Retrieve and apply any patches specified by the makefile to this project. + */ + function applyPatches($project_directory) { + if (empty($this->patch)) { + return TRUE; + } + + $patches_txt = ''; + $local_patches = array(); + $ignore_checksums = drush_get_option('ignore-checksums'); + foreach ($this->patch as $info) { + $this->preprocessLocalFileUrl($info); + + // Download the patch. + if ($filename = _make_download_file($info['url'])) { + $patched = FALSE; + $output = ''; + // Test each patch style; -p1 is the default with git. See + // http://drupal.org/node/1054616 + $patch_levels = array('-p1', '-p0'); + foreach ($patch_levels as $patch_level) { + $checked = drush_shell_cd_and_exec($project_directory, 'git --git-dir=. apply --check %s %s --verbose', $patch_level, $filename); + if ($checked) { + // Apply the first successful style. + $patched = drush_shell_cd_and_exec($project_directory, 'git --git-dir=. apply %s %s --verbose', $patch_level, $filename); + break; + } + } + + // In some rare cases, git will fail to apply a patch, fallback to using + // the 'patch' command. + if (!$patched) { + foreach ($patch_levels as $patch_level) { + // --no-backup-if-mismatch here is a hack that fixes some + // differences between how patch works on windows and unix. + if ($patched = drush_shell_exec("patch %s --no-backup-if-mismatch -d %s < %s", $patch_level, $project_directory, $filename)) { + break; + } + } + } + + if ($output = drush_shell_exec_output()) { + // Log any command output, visible only in --verbose or --debug mode. + drush_log(implode("\n", $output)); + } + + // Set up string placeholders to pass to dt(). + $dt_args = array( + '@name' => $this->name, + '@filename' => basename($filename), + ); + + if ($patched) { + if (!$ignore_checksums && !_make_verify_checksums($info, $filename)) { + return FALSE; + } + $patch_url = $info['url']; + + // If this is a local patch, copy that into place as well. + if ($info['local']) { + $local_patches[] = $info['url']; + // Use a local path for the PATCHES.txt file. + $pathinfo = pathinfo($patch_url); + $patch_url = $pathinfo['basename']; + } + $patches_txt .= '- ' . $patch_url . "\n"; + + drush_log(dt('@name patched with @filename.', $dt_args), LogLevel::OK); + } + else { + make_error('PATCH_ERROR', dt("Unable to patch @name with @filename.", $dt_args)); + } + drush_op('unlink', $filename); + } + else { + make_error('DOWNLOAD_ERROR', 'Unable to download ' . $info['url'] . '.'); + return FALSE; + } + } + if (!empty($patches_txt) && !drush_get_option('no-patch-txt') && !file_exists($project_directory . '/PATCHES.txt')) { + $patches_txt = "The following patches have been applied to this project:\n" . + $patches_txt . + "\nThis file was automatically generated by Drush Make (http://drupal.org/project/drush).\n"; + file_put_contents($project_directory . '/PATCHES.txt', $patches_txt); + drush_log('Generated PATCHES.txt file for ' . $this->name, LogLevel::OK); + + // Copy local patches into place. + foreach ($local_patches as $url) { + $pathinfo = pathinfo($url); + drush_copy_dir($url, $project_directory . '/' . $pathinfo['basename']); + } + } + return TRUE; + } + + /** + * Process info files when downloading things from git. + */ + function processGitInfoFiles() { + // Bail out if this isn't hosted on Drupal.org (unless --force-gitinfofile option was specified). + if (!drush_get_option('force-gitinfofile', FALSE) && isset($this->download['url']) && strpos($this->download['url'], 'drupal.org') === FALSE) { + return; + } + + // Figure out the proper version string to use based on the .make file. + // Best case is the .make file author told us directly. + if (!empty($this->download['full_version'])) { + $full_version = $this->download['full_version']; + } + // Next best is if we have a tag, since those are identical to versions. + elseif (!empty($this->download['tag'])) { + $full_version = $this->download['tag']; + } + // If we have a branch, append '-dev'. + elseif (!empty($this->download['branch'])) { + $full_version = $this->download['branch'] . '-dev'; + } + // Ugh. Not sure what else we can do in this case. + elseif (!empty($this->download['revision'])) { + $full_version = $this->download['revision']; + } + // Probably can never reach this case. + else { + $full_version = 'unknown'; + } + + // If the version string ends in '.x-dev' do the Git magic to figure out + // the appropriate 'rebuild version' string, e.g. '7.x-1.2+7-dev'. + $matches = array(); + if (preg_match('/^(.+).x-dev$/', $full_version, $matches)) { + require_once dirname(__FILE__) . '/../pm/package_handler/git_drupalorg.inc'; + $rebuild_version = drush_pm_git_drupalorg_compute_rebuild_version($this->download_location, $matches[1]); + if ($rebuild_version) { + $full_version = $rebuild_version; + } + } + require_once dirname(__FILE__) . '/../pm/pm.drush.inc'; + if (drush_shell_cd_and_exec($this->download_location, 'git log -1 --pretty=format:%ct')) { + $output = drush_shell_exec_output(); + $datestamp = $output[0]; + } + else { + $datestamp = time(); + } + drush_pm_inject_info_file_metadata($this->download_location, $this->name, $full_version, $datestamp); + } + + /** + * Remove the .git directory from a project. + */ + function removeGitDirectory() { + if (isset($this->download['type']) && $this->download['type'] == 'git' && file_exists($this->download_location . '/.git')) { + drush_delete_dir($this->download_location . '/.git', TRUE); + } + } + + /** + * Add a lock file. + */ + function addLockfile($project_directory) { + if (!empty($this->lock)) { + file_put_contents($project_directory . '/.drush-lock-update', $this->lock); + } + return TRUE; + } + + /** + * Retrieve translations for this project. + */ + function getTranslations($project_directory) { + static $cache = array(); + $langcodes = $this->translations; + if ($langcodes && in_array($this->type, array('core', 'module', 'profile', 'theme'), TRUE)) { + // Support the l10n_path, l10n_url keys from l10n_update. Note that the + // l10n_server key is not supported. + if (isset($this->l10n_path)) { + $update_url = $this->l10n_path; + } + else { + if (isset($this->l10n_url)) { + $l10n_server = $this->l10n_url; + } + else { + $l10n_server = FALSE; + } + if ($l10n_server) { + if (!isset($cache[$l10n_server])) { + $this->preprocessLocalFileUrl($l10n_server); + $l10n_server = $l10n_server['url']; + if ($filename = _make_download_file($l10n_server)) { + $server_info = simplexml_load_string(file_get_contents($filename)); + $cache[$l10n_server] = !empty($server_info->update_url) ? $server_info->update_url : FALSE; + } + } + if ($cache[$l10n_server]) { + $update_url = $cache[$l10n_server]; + } + else { + make_error('XML_ERROR', dt("Could not retrieve l10n update url for !project.", array('!project' => $this->name))); + return FALSE; + } + } + } + if ($update_url) { + $failed = array(); + foreach ($langcodes as $langcode) { + $variables = array( + '%project' => $this->name, + '%release' => $this->download['full_version'], + '%core' => $this->core, + '%language' => $langcode, + '%filename' => '%filename', + ); + $url = strtr($update_url, $variables); + + // Download the translation file. Since its contents are volatile, + // cache for only 4 hours. + if ($filename = _make_download_file($url, 3600 * 4)) { + // If this is the core project type, download the translation file + // and place it in every profile and an additional copy in + // modules/system/translations where it can be detected for import + // by other non-default install profiles. + if ($this->type === 'core') { + $profiles = drush_scan_directory($project_directory . '/profiles', '/.*/', array(), 0, FALSE, 'filename', 0, TRUE); + foreach ($profiles as $profile) { + if (is_dir($project_directory . '/profiles/' . $profile->basename)) { + drush_mkdir($project_directory . '/profiles/' . $profile->basename . '/translations'); + drush_copy_dir($filename, $project_directory . '/profiles/' . $profile->basename . '/translations/' . $langcode . '.po'); + } + } + drush_mkdir($project_directory . '/modules/system/translations'); + drush_copy_dir($filename, $project_directory . '/modules/system/translations/' . $langcode . '.po'); + } + else { + drush_mkdir($project_directory . '/translations'); + drush_copy_dir($filename, $project_directory . '/translations/' . $langcode . '.po', FILE_EXISTS_OVERWRITE); + } + } + else { + $failed[] = $langcode; + } + } + if (empty($failed)) { + drush_log('All translations downloaded for ' . $this->name, LogLevel::OK); + } + else { + drush_log('Unable to download translations for ' . $this->name . ': ' . implode(', ', $failed), LogLevel::WARNING); + } + } + } + return TRUE; + } + + /** + * Generate the proper path for this project type. + * + * @param boolean $base + * Whether include the base part (tmp dir). Defaults to TRUE. + */ + protected function generatePath($base = TRUE) { + $path = array(); + if ($base) { + $path[] = make_tmp(); + $path[] = '__build__'; + } + if (!empty($this->contrib_destination)) { + $path[] = $this->contrib_destination; + } + if (!empty($this->subdir)) { + $path[] = $this->subdir; + } + return implode('/', $path); + } + + /** + * Return the proper path for dependencies to be placed in. + * + * @return string + * The path that dependencies will be placed in. + */ + protected function buildPath($directory) { + return $this->base_contrib_destination; + } + + /** + * Recurse to process additional makefiles that may be found during + * processing. + */ + function recurse($path) { + if (!$this->do_recursion || drush_get_option('no-recursion')) { + drush_log(dt("Preventing recursive makefile parsing for !project", + array("!project" => $this->name)), LogLevel::NOTICE); + return TRUE; + } + $candidates = array( + $this->name . '.make.yml', + $this->name . '.make', + 'drupal-org.make.yml', + 'drupal-org.make', + ); + $makefile = FALSE; + foreach ($candidates as $filename) { + if (file_exists($this->download_location . '/' . $filename)) { + $makefile = $this->download_location . '/' . $filename; + break; + } + } + + if (!$makefile) { + return TRUE; + } + + drush_log(dt("Found makefile: !makefile", array("!makefile" => basename($makefile))), LogLevel::OK); + + // Save the original state of the 'custom' context. + $custom_context = &drush_get_context('custom'); + $original_custom_context_values = $custom_context; + + $info = make_parse_info_file($makefile, TRUE, $this->options); + if (!($info = make_validate_info_file($info))) { + $result = FALSE; + } + else { + // Inherit the translations specified in the extender makefile. + if (!empty($this->translations)) { + $info['translations'] = $this->translations; + } + // Strip out any modules that have already been processed before this. + foreach ($this->manifest as $name) { + unset($info['projects'][$name]); + } + $build_path = $this->buildPath($this->name); + make_projects(TRUE, trim($build_path, '/'), $info, $this->build_path, $this->download_location); + make_libraries(TRUE, trim($build_path, '/'), $info, $this->build_path, $this->download_location); + $result = TRUE; + } + // Restore original 'custom' context so that any + // settings changes made are used. + $custom_context = $original_custom_context_values; + + return $result; + } +} + +/** + * For processing Drupal core projects. + */ +class DrushMakeProject_Core extends DrushMakeProject { + /** + * Override constructor for core to adjust project info. + */ + protected function __construct(&$project) { + parent::__construct($project); + // subdir and contrib_destination are not allowed for core. + $this->subdir = ''; + $this->contrib_destination = ''; + } + + /** + * Determine the location to download project to. + */ + function findDownloadLocation() { + $this->path = $this->download_location = $this->generatePath(); + $this->project_directory = ''; + if (is_dir($this->download_location)) { + return drush_set_error('MAKE_DIRECTORY_EXISTS', dt('Directory not empty: !directory', array('!directory' => $this->download_location))); + } + elseif ($this->download['type'] === 'pm') { + // pm-download will create the final __build__ directory, so nothing to do + // here. + } + else { + drush_mkdir($this->download_location); + } + return $this->download_location; + } +} + +/** + * For processing libraries. + */ +class DrushMakeProject_Library extends DrushMakeProject { + /** + * Override constructor for libraries to properly set contrib destination. + */ + protected function __construct(&$project) { + parent::__construct($project); + // Allow libraries to specify where they should live in the build path. + if (isset($project['destination'])) { + $project_path = $project['destination']; + } + else { + $project_path = 'libraries'; + } + + $this->contrib_destination = ($this->base_contrib_destination != '.' ? $this->base_contrib_destination . '/' : '') . $project_path; + } + + /** + * No recursion for libraries, sorry :-( + */ + function recurse($path) { + // Return TRUE so that processing continues in the make() method. + return TRUE; + } + + /** + * No translations for libraries. + */ + function getTranslations($download_location) { + // Return TRUE so that processing continues in the make() method. + return TRUE; + } +} + +/** + * For processing modules. + */ +class DrushMakeProject_Module extends DrushMakeProject { + /** + * Override constructor for modules to properly set contrib destination. + */ + protected function __construct(&$project) { + parent::__construct($project); + $this->contrib_destination = ($this->base_contrib_destination != '.' ? $this->base_contrib_destination . '/' : '') . 'modules'; + } +} + +/** + * For processing installation profiles. + */ +class DrushMakeProject_Profile extends DrushMakeProject { + /** + * Override contructor for installation profiles to properly set contrib + * destination. + */ + protected function __construct(&$project) { + parent::__construct($project); + $this->contrib_destination = (!empty($this->destination) ? $this->destination : 'profiles'); + } + + /** + * Find the build path. + */ + protected function buildPath($directory) { + return $this->generatePath(FALSE) . '/' . $directory; + } +} + +/** + * For processing themes. + */ +class DrushMakeProject_Theme extends DrushMakeProject { + /** + * Override contructor for themes to properly set contrib destination. + */ + protected function __construct(&$project) { + parent::__construct($project); + $this->contrib_destination = ($this->base_contrib_destination != '.' ? $this->base_contrib_destination . '/' : '') . 'themes'; + } +} + +/** + * For processing translations. + */ +class DrushMakeProject_Translation extends DrushMakeProject { + /** + * Override constructor for translations to properly set contrib destination. + */ + protected function __construct(&$project) { + parent::__construct($project); + switch ($project['core']) { + case '5.x': + // Don't think there's an automatic place we can put 5.x translations, + // so we'll toss them in a translations directory in the Drupal root. + $this->contrib_destination = ($this->base_contrib_destination != '.' ? $this->base_contrib_destination . '/' : '') . 'translations'; + break; + + default: + $this->contrib_destination = ''; + break; + } + } +} diff --git a/vendor/drush/drush/commands/make/make.utilities.inc b/vendor/drush/drush/commands/make/make.utilities.inc new file mode 100644 index 0000000000..d70aa5b0c3 --- /dev/null +++ b/vendor/drush/drush/commands/make/make.utilities.inc @@ -0,0 +1,698 @@ + array_filter(drush_get_option_list('projects')), + 'libraries' => array_filter(drush_get_option_list('libraries')), + ); + $info = make_prune_info_file($info, $include_only); + + if ($info === FALSE || ($info = make_validate_info_file($info)) === FALSE) { + return FALSE; + } + + return $info; +} + +/** + * Parse makefile recursively. + */ +function _make_parse_info_file($makefile, $element = 'includes') { + if (!($data = make_get_data($makefile))) { + return drush_set_error('MAKE_INVALID_MAKE_FILE', dt('Invalid or empty make file: !makefile', array('!makefile' => $makefile))); + } + + // $info['format'] will specify the determined format. + $info = _make_determine_format($data); + + // Set any allowed options. + if (!empty($info['options'])) { + foreach ($info['options'] as $key => $value) { + if (_make_is_override_allowed($key)) { + // n.b. 'custom' context has lower priority than 'cli', so + // options entered on the command line will "mask" makefile options. + drush_set_option($key, $value, 'custom'); + } + } + } + + // Include any makefiles specified on the command line. + if ($include_makefiles = drush_get_option_list('includes', FALSE)) { + drush_unset_option('includes'); // Avoid infinite loop. + $info['includes'] = is_array($info['includes']) ? $info['includes'] : array(); + foreach ($include_makefiles as $include_make) { + if (!array_search($include_make, $info['includes'])) { + $info['includes'][] = $include_make; + } + } + } + + // Override elements with values from makefiles specified on the command line. + if ($overrides = drush_get_option_list('overrides', FALSE)) { + drush_unset_option('overrides'); // Avoid infinite loop. + $info['overrides'] = is_array($info['overrides']) ? $info['overrides'] : array(); + foreach ($overrides as $override) { + if (!array_search($override, $info['overrides'])) { + $info['overrides'][] = $override; + } + } + } + + $info = _make_merge_includes_recursively($info, $makefile); + $info = _make_merge_includes_recursively($info, $makefile, 'overrides'); + + return $info; +} + +/** + * Helper function to merge includes recursively. + */ +function _make_merge_includes_recursively($info, $makefile, $element = 'includes') { + if (!empty($info[$element])) { + if (is_array($info[$element])) { + $includes = array(); + foreach ($info[$element] as $key => $include) { + if (!empty($include)) { + if (!$include_makefile = _make_get_include_path($include, $makefile)) { + return make_error('BUILD_ERROR', dt("Cannot determine include file location: !include", array('!include' => $include))); + } + + if ($element == 'overrides') { + $info = array_replace_recursive($info, _make_parse_info_file($include_makefile, $element)); + } + else { + $info = array_replace_recursive(_make_parse_info_file($include_makefile), $info); + } + unset($info[$element][$key]); + // Move core back to the top of the list, where + // make_generate_from_makefile() expects it. + if (!empty($info['projects'])) { + array_reverse($info['projects']); + } + } + } + } + } + // Ensure $info['projects'] is an associative array, so that we can merge + // includes properly. + make_normalize_info($info); + + return $info; +} + +/** + * Helper function to determine the proper path for an include makefile. + */ +function _make_get_include_path($include, $makefile) { + if (is_array($include) && $include['download']['type'] = 'git') { + $tmp_dir = make_tmp(); + make_download_git($include['makefile'], $include['download']['type'], $include['download'], $tmp_dir); + $include_makefile = $tmp_dir . '/' . $include['makefile']; + } + elseif (is_string($include)) { + $include_path = dirname($makefile); + if (make_valid_url($include, TRUE)) { + $include_makefile = $include; + } + elseif (file_exists($include_path . '/' . $include)) { + $include_makefile = $include_path . '/' . $include; + } + elseif (file_exists($include)) { + $include_makefile = $include; + } + else { + return make_error('BUILD_ERROR', dt("Include file missing: !include", array('!include' => $include))); + } + } + else { + return FALSE; + } + return $include_makefile; +} + +/** + * Expand shorthand elements, so that we have an associative array. + */ +function make_normalize_info(&$info) { + if (isset($info['projects'])) { + foreach($info['projects'] as $key => $project) { + if (is_numeric($key) && is_string($project)) { + unset($info['projects'][$key]); + $info['projects'][$project] = array( + 'version' => '', + ); + } + if (is_string($key) && is_numeric($project)) { + $info['projects'][$key] = array( + 'version' => $project, + ); + } + } + } +} + +/** + * Remove entries in the info file in accordance with the options passed in. + * Entries are either explicitly 'allowed' (with the $include_only parameter) in + * which case all *other* entries will be excluded. + * + * @param array $info + * A parsed info file. + * + * @param array $include_only + * (Optional) Array keyed by entry type (e.g. 'libraries') against an array of + * allowed keys for that type. The special value '*' means 'all entries of + * this type'. If this parameter is omitted, no entries will be excluded. + * + * @return array + * The $info array, pruned if necessary. + */ +function make_prune_info_file($info, $include_only = array()) { + + // We may get passed FALSE in some cases. + // Also we cannot prune an empty array, so no point in this code running! + if (empty($info)) { + return $info; + } + + // We will accrue an explanation of our activities here. + $msg = array(); + $msg['scope'] = dt("Drush make restricted to the following entries:"); + + $pruned = FALSE; + + if (count(array_filter($include_only))) { + $pruned = TRUE; + foreach ($include_only as $type => $keys) { + + if (!isset($info[$type])) { + continue; + } + // For translating + // dt("Projects"); + // dt("Libraries"); + $type_title = dt(ucfirst($type)); + + // Handle the special '*' value. + if (in_array('*', $keys)) { + $msg[$type] = dt("!entry_type: ", array('!entry_type' => $type_title)); + } + + // Handle a (possibly empty) array of keys to include/exclude. + else { + $info[$type] = array_intersect_key($info[$type], array_fill_keys($keys, 1)); + unset($msg[$type]); + if (!empty($info[$type])) { + $msg[$type] = dt("!entry_type: !make_entries", array('!entry_type' => $type_title, '!make_entries' => implode(', ', array_keys($info[$type])))); + } + } + } + } + + if ($pruned) { + // Make it clear to the user what's going on. + drush_log(implode("\n", $msg), LogLevel::OK); + + // Throw an error if these restrictions reduced the make to nothing. + if (empty($info['projects']) && empty($info['libraries'])) { + // This error mentions the options explicitly to make it as clear as + // possible to the user why this error has occurred. + make_error('BUILD_ERROR', dt("All projects and libraries have been excluded. Review the 'projects' and 'libraries' options.")); + } + } + + return $info; +} + +/** + * Validate the make file. + */ +function make_validate_info_file($info) { + // Assume no errors to start. + $errors = FALSE; + + if (empty($info['core'])) { + make_error('BUILD_ERROR', dt("The 'core' attribute is required")); + $errors = TRUE; + } + // Standardize on core. + elseif (preg_match('/^(\d+)(\.(x|(\d+)(-[a-z0-9]+)?))?$/', $info['core'], $matches)) { + // An exact version of core has been specified, so pass that to an + // internal variable for storage. + if (isset($matches[4])) { + $info['core_release'] = $info['core']; + } + // Format the core attribute consistently. + $info['core'] = $matches[1] . '.x'; + } + else { + make_error('BUILD_ERROR', dt("The 'core' attribute !core has an incorrect format.", array('!core' => $info['core']))); + $errors = TRUE; + } + + if (!isset($info['api'])) { + $info['api'] = MAKE_API; + drush_log(dt("You need to specify an API version of two in your makefile:\napi = !api", array("!api" => MAKE_API)), LogLevel::WARNING); + } + elseif ($info['api'] != MAKE_API) { + make_error('BUILD_ERROR', dt("The specified API attribute is incompatible with this version of Drush Make.")); + $errors = TRUE; + } + + $names = array(); + + // Process projects. + if (isset($info['projects'])) { + if (!is_array($info['projects'])) { + make_error('BUILD_ERROR', dt("'projects' attribute must be an array.")); + $errors = TRUE; + } + else { + // Filter out entries that have been forcibly removed via [foo] = FALSE. + $info['projects'] = array_filter($info['projects']); + + foreach ($info['projects'] as $project => $project_data) { + // Project has an attributes array. + if (is_string($project) && is_array($project_data)) { + if (in_array($project, $names)) { + make_error('BUILD_ERROR', dt("Project !project defined twice (remove the first projects[] = !project).", array('!project' => $project))); + $errors = TRUE; + } + $names[] = $project; + foreach ($project_data as $attribute => $value) { + // Prevent malicious attempts to access other areas of the + // filesystem. + if (in_array($attribute, array('subdir', 'directory_name', 'contrib_destination')) && !make_safe_path($value)) { + $args = array( + '!path' => $value, + '!attribute' => $attribute, + '!project' => $project, + ); + make_error('BUILD_ERROR', dt("Illegal path !path for '!attribute' attribute in project !project.", $args)); + $errors = TRUE; + } + } + } + // Cover if there is no project info, it's just a project name. + elseif (is_numeric($project) && is_string($project_data)) { + if (in_array($project_data, $names)) { + make_error('BUILD_ERROR', dt("Project !project defined twice (remove the first projects[] = !project).", array('!project' => $project_data))); + $errors = TRUE; + } + $names[] = $project_data; + unset($info['projects'][$project]); + $info['projects'][$project_data] = array(); + } + // Convert shorthand project version style to array format. + elseif (is_string($project_data)) { + if (in_array($project, $names)) { + make_error('BUILD_ERROR', dt("Project !project defined twice (remove the first projects[] = !project).", array('!project' => $project))); + $errors = TRUE; + } + $names[] = $project; + $info['projects'][$project] = array('version' => $project_data); + } + else { + make_error('BUILD_ERROR', dt('Project !project incorrectly specified.', array('!project' => $project))); + $errors = TRUE; + } + } + } + } + if (isset($info['libraries'])) { + if (!is_array($info['libraries'])) { + make_error('BUILD_ERROR', dt("'libraries' attribute must be an array.")); + $errors = TRUE; + } + else { + // Filter out entries that have been forcibly removed via [foo] = FALSE. + $info['libraries'] = array_filter($info['libraries']); + + foreach ($info['libraries'] as $library => $library_data) { + if (is_array($library_data)) { + foreach ($library_data as $attribute => $value) { + // Unset disallowed attributes. + if (in_array($attribute, array('contrib_destination'))) { + unset($info['libraries'][$library][$attribute]); + } + // Prevent malicious attempts to access other areas of the + // filesystem. + elseif (in_array($attribute, array('contrib_destination', 'directory_name')) && !make_safe_path($value)) { + $args = array( + '!path' => $value, + '!attribute' => $attribute, + '!library' => $library, + ); + make_error('BUILD_ERROR', dt("Illegal path !path for '!attribute' attribute in library !library.", $args)); + $errors = TRUE; + } + } + } + } + } + } + + // Convert shorthand project/library download style to array format. + foreach (array('projects', 'libraries') as $type) { + if (isset($info[$type]) && is_array($info[$type])) { + foreach ($info[$type] as $name => $item) { + if (!empty($item['download']) && is_string($item['download'])) { + $info[$type][$name]['download'] = array('url' => $item['download']); + } + } + } + } + + // Apply defaults after projects[] array has been expanded, but prior to + // external validation. + make_apply_defaults($info); + + foreach (drush_command_implements('make_validate_info') as $module) { + $function = $module . '_make_validate_info'; + $return = $function($info); + if ($return) { + $info = $return; + } + else { + $errors = TRUE; + } + } + + if ($errors) { + return FALSE; + } + return $info; +} + +/** + * Verify the syntax of the given URL. + * + * Copied verbatim from includes/common.inc + * + * @see valid_url + */ +function make_valid_url($url, $absolute = FALSE) { + if ($absolute) { + return (bool) preg_match(" + /^ # Start at the beginning of the text + (?:ftp|https?):\/\/ # Look for ftp, http, or https schemes + (?: # Userinfo (optional) which is typically + (?:(?:[\w\.\-\+!$&'\(\)*\+,;=]|%[0-9a-f]{2})+:)* # a username or a username and password + (?:[\w\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})+@ # combination + )? + (?: + (?:[a-z0-9\-\.]|%[0-9a-f]{2})+ # A domain name or a IPv4 address + |(?:\[(?:[0-9a-f]{0,4}:)*(?:[0-9a-f]{0,4})\]) # or a well formed IPv6 address + ) + (?::[0-9]+)? # Server port number (optional) + (?:[\/|\?] + (?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2}) # The path and query (optional) + *)? + $/xi", $url); + } + else { + return (bool) preg_match("/^(?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2})+$/i", $url); + } +} + +/** + * Find, and possibly create, a temporary directory. + * + * @param boolean $set + * Must be TRUE to create a directory. + * @param string $directory + * Pass in a directory to use. This is required if using any + * concurrent operations. + * + * @todo Merge with drush_tempdir(). + */ +function make_tmp($set = TRUE, $directory = NULL) { + static $tmp_dir; + + if (isset($directory) && !isset($tmp_dir)) { + $tmp_dir = $directory; + } + + if (!isset($tmp_dir) && $set) { + $tmp_dir = drush_find_tmp(); + if (strrpos($tmp_dir, '/') == strlen($tmp_dir) - 1) { + $tmp_dir .= 'make_tmp_' . time() . '_' . uniqid(); + } + else { + $tmp_dir .= '/make_tmp_' . time() . '_' . uniqid(); + } + if (!drush_get_option('no-clean', FALSE)) { + drush_register_file_for_deletion($tmp_dir); + } + if (file_exists($tmp_dir)) { + return make_tmp(TRUE); + } + // Create the directory. + drush_mkdir($tmp_dir); + } + return $tmp_dir; +} + +/** + * Removes the temporary build directory. On failed builds, this is handled by + * drush_register_file_for_deletion(). + */ +function make_clean_tmp() { + if (!($tmp_dir = make_tmp(FALSE))) { + return; + } + if (!drush_get_option('no-clean', FALSE)) { + drush_delete_dir($tmp_dir); + } + else { + drush_log(dt('Temporary directory: !dir', array('!dir' => $tmp_dir)), LogLevel::OK); + } +} + +/** + * Prepare a Drupal installation, copying default.settings.php to settings.php. + */ +function make_prepare_install($build_path) { + $default = make_tmp() . '/__build__/sites/default'; + drush_copy_dir($default . DIRECTORY_SEPARATOR . 'default.settings.php', $default . DIRECTORY_SEPARATOR . 'settings.php', FILE_EXISTS_OVERWRITE); + drush_mkdir($default . '/files'); + chmod($default . DIRECTORY_SEPARATOR . 'settings.php', 0666); + chmod($default . DIRECTORY_SEPARATOR . 'files', 0777); +} + +/** + * Calculate a cksum on each file in the build, and md5 the resulting hashes. + */ +function make_md5() { + return drush_dir_md5(make_tmp()); +} + +/** + * @todo drush_archive_dump() also makes a tar. Consolidate? + */ +function make_tar($build_path) { + $tmp_path = make_tmp(); + + drush_mkdir(dirname($build_path)); + $filename = basename($build_path); + $dirname = basename($build_path, '.tar.gz'); + // Move the build directory to a more human-friendly name, so that tar will + // use it instead. + drush_move_dir($tmp_path . DIRECTORY_SEPARATOR . '__build__', $tmp_path . DIRECTORY_SEPARATOR . $dirname, TRUE); + // Only move the tar file to it's final location if it's been built + // successfully. + if (drush_shell_exec("%s -C %s -Pczf %s %s", drush_get_tar_executable(), $tmp_path, $tmp_path . '/' . $filename, $dirname)) { + drush_move_dir($tmp_path . DIRECTORY_SEPARATOR . $filename, $build_path, TRUE); + }; + // Move the build directory back to it's original location for consistency. + drush_move_dir($tmp_path . DIRECTORY_SEPARATOR . $dirname, $tmp_path . DIRECTORY_SEPARATOR . '__build__'); +} + +/** + * Logs an error unless the --force-complete command line option is specified. + */ +function make_error($error_code, $message) { + if (drush_get_option('force-complete')) { + drush_log("$error_code: $message -- build forced", LogLevel::WARNING); + } + else { + return drush_set_error($error_code, $message); + } +} + +/** + * Checks an attribute's path to ensure it's not maliciously crafted. + * + * @param string $path + * The path to check. + */ +function make_safe_path($path) { + return !preg_match("+^/|^\.\.|/\.\./+", $path); +} +/** + * Get data based on the source. + * + * This is a helper function to abstract the retrieval of data, so that it can + * come from files, STDIN, etc. Currently supports filepath and STDIN. + * + * @param string $data_source + * The path to a file, or '-' for STDIN. + * + * @return string + * The raw data as a string. + */ +function make_get_data($data_source) { + if ($data_source == '-') { + // See http://drupal.org/node/499758 before changing this. + $stdin = fopen('php://stdin', 'r'); + $data = ''; + $has_input = FALSE; + + while ($line = fgets($stdin)) { + $has_input = TRUE; + $data .= $line; + } + + if ($has_input) { + return $data; + } + return FALSE; + } + // Local file. + elseif (!strpos($data_source, '://')) { + $data = file_get_contents($data_source); + } + // Remote file. + else { + $file = _make_download_file($data_source); + $data = file_get_contents($file); + drush_op('unlink', $file); + } + return $data; +} + +/** + * Apply any defaults. + * + * @param array &$info + * A parsed make array. + */ +function make_apply_defaults(&$info) { + if (isset($info['defaults'])) { + $defaults = $info['defaults']; + + foreach ($defaults as $type => $default_data) { + if (isset($info[$type])) { + foreach ($info[$type] as $project => $data) { + $info[$type][$project] = _drush_array_overlay_recursive($default_data, $info[$type][$project]); + } + } + else { + drush_log(dt("Unknown attribute '@type' in defaults array", array('@type' => $type)), LogLevel::WARNING); + } + } + } +} + +/** + * Check if makefile overrides are allowed + * + * @param array $option + * The option to check. + */ +function _make_is_override_allowed ($option) { + $allow_override = drush_get_option('allow-override', 'all'); + + if ($allow_override == 'all') { + $allow_override = array(); + } + elseif (!is_array($allow_override)) { + $allow_override = _convert_csv_to_array($allow_override); + } + + if ((empty($allow_override)) || ((in_array($option, $allow_override)) && (!in_array('none', $allow_override)))) { + return TRUE; + } + drush_log(dt("'!option' not allowed; use --allow-override=!option or --allow-override=all to permit", array("!option" => $option)), LogLevel::WARNING); + return FALSE; +} + +/** + * Gather any working copy options. + * + * @param array $download + * The download array. + */ +function _get_working_copy_option($download) { + $wc = ''; + + if (_make_is_override_allowed('working-copy') && isset ($download['working-copy'])) { + $wc = $download['working-copy']; + } + else { + $wc = drush_get_option('working-copy'); + } + return $wc; +} + +/** + * Given data from stdin, determine format. + * + * @return array|bool + * Returns parsed data if it matches any known format. + */ +function _make_determine_format($data) { + // Most .make files will have a `core` attribute. Use this to determine + // the format. + if (preg_match('/^\s*core:/m', $data)) { + $parsed = ParserYaml::parse($data); + $parsed['format'] = 'yaml'; + return $parsed; + } + elseif (preg_match('/^\s*core\s*=/m', $data)) { + $parsed = ParserIni::parse($data); + $parsed['format'] = 'ini'; + return $parsed; + } + + // If the .make file did not have a core attribute, it is being included + // by another .make file. Test YAML first to avoid segmentation faults from + // preg_match in INI parser. + $yaml_parse_exception = FALSE; + try { + if ($parsed = ParserYaml::parse($data)) { + $parsed['format'] = 'yaml'; + return $parsed; + } + } + catch (\Drush\Internal\Symfony\Yaml\Exception\ParseException $e) { + // Note that an exception was thrown, and display after .ini parsing. + $yaml_parse_exception = $e; + } + + // Try INI format. + if ($parsed = ParserIni::parse($data)) { + $parsed['format'] = 'ini'; + return $parsed; + } + + if ($yaml_parse_exception) { + throw $e; + } + + return drush_set_error('MAKE_STDIN_ERROR', dt('Unknown make file format')); +} diff --git a/vendor/drush/drush/commands/make/update.make.inc b/vendor/drush/drush/commands/make/update.make.inc new file mode 100644 index 0000000000..8dba72eb0e --- /dev/null +++ b/vendor/drush/drush/commands/make/update.make.inc @@ -0,0 +1,72 @@ + $project) { + if (($project['download']['type'] == 'git') && !empty($project['download']['url'])) { + // TODO check that tag or branch are valid version strings (with pm_parse_version()). + if (!empty($project['download']['tag'])) { + $version = $project['download']['tag']; + } + elseif (!empty($project['download']['branch'])) { + $version = $project['download']['branch'] . '-dev'; + } + /* + elseif (!empty($project['download']['refspec'])) { + #TODO# Parse refspec. + } + */ + else { + // If no tag or branch, we can't match a d.o version. + continue; + } + $projects[$project_name] = $project + array( + 'path' => '', + 'label' => $project_name, + 'version' => $version, + ); + } + elseif ($project['download']['type'] == 'pm') { + $projects[$project_name] = $project + array( + 'path' => '', + 'label' => $project_name, + ); + } + } + + // Check for updates. + $update_status = drush_get_engine('update_status'); + $update_info = $update_status->getStatus($projects, TRUE); + + $security_only = drush_get_option('security-only', FALSE); + foreach ($update_info as $project_name => $project_update_info) { + if (!$security_only || ($security_only && $project_update_info['status'] == DRUSH_UPDATESTATUS_NOT_SECURE)) { + $make_projects[$project_name]['download']['full_version'] = $project_update_info['recommended']; + } + } + + // Inject back make projects and generate the updated makefile. + drush_set_option('DRUSH_MAKE_PROJECTS', $make_projects); + make_generate_from_makefile(drush_get_option('result-file'), $makefile); +} + diff --git a/vendor/drush/drush/commands/pm/download.pm.inc b/vendor/drush/drush/commands/pm/download.pm.inc new file mode 100644 index 0000000000..71745df8a5 --- /dev/null +++ b/vendor/drush/drush/commands/pm/download.pm.inc @@ -0,0 +1,394 @@ + $destination))); + if (!drush_get_context('DRUSH_SIMULATE')) { + if (drush_confirm(dt('Would you like to create it?'))) { + drush_mkdir($destination, TRUE); + } + if (!is_dir($destination)) { + return drush_set_error('DRUSH_PM_NO_DESTINATION', dt('Unable to create destination directory !destination.', array('!destination' => $destination))); + } + } + } + if (!is_writable($destination)) { + return drush_set_error('DRUSH_PM_NO_DESTINATION', dt('Destination directory !destination is not writable.', array('!destination' => $destination))); + } + // Ignore --use-site-dir, if given. + if (drush_get_option('use-site-dir', FALSE)) { + drush_set_option('use-site-dir', FALSE); + } + } + + // Validate --variant or enforce a sane default. + $variant = drush_get_option('variant', FALSE); + if ($variant) { + $variants = array('full', 'projects', 'profile-only'); + if (!in_array($variant, $variants)) { + return drush_set_error('DRUSH_PM_PROFILE_INVALID_VARIANT', dt('Invalid variant !variant. Valid values: !variants.', array('!variant' => $variant, '!variants' => implode(', ', $variants)))); + } + } + // 'full' and 'projects' variants are only valid for wget package handler. + $package_handler = drush_get_option('package-handler', 'wget'); + if (($package_handler != 'wget') && ($variant != 'profile-only')) { + $new_variant = 'profile-only'; + if ($variant) { + drush_log(dt('Variant !variant is incompatible with !ph package-handler.', array('!variant' => $variant, '!ph' => $package_handler)), LogLevel::WARNING); + } + } + // If we are working on a drupal root, full variant is not an option. + else if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_ROOT) { + if ((!$variant) || (($variant == 'full') && (!isset($new_variant)))) { + $new_variant = 'projects'; + } + if ($variant == 'full') { + drush_log(dt('Variant full is not a valid option within a Drupal root.'), LogLevel::WARNING); + } + } + + if (isset($new_variant)) { + drush_set_option('variant', $new_variant); + if ($variant) { + drush_log(dt('Switching to --variant=!variant.', array('!variant' => $new_variant)), LogLevel::OK); + } + } +} + +/** + * Command callback. Download Drupal core or any project. + */ +function drush_pm_download() { + $release_info = drush_get_engine('release_info'); + + if (!$requests = pm_parse_arguments(func_get_args(), FALSE)) { + $requests = array('drupal'); + } + + // Pick cli options. + $status_url = drush_get_option('source', ReleaseInfo::DEFAULT_URL); + $restrict_to = drush_get_option('dev', ''); + $select = drush_get_option('select', 'auto'); + $all = drush_get_option('all', FALSE); + // If we've bootstrapped a Drupal site and the user may have the chance + // to select from a list of filtered releases, we want to pass + // the installed project version, if any. + $projects = array(); + if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_FULL) { + if (!$all and in_array($select, array('auto', 'always'))) { + $projects = drush_get_projects(); + } + } + + // Get release history for each request and download the project. + foreach ($requests as $request) { + $request = pm_parse_request($request, $status_url, $projects); + $version = isset($projects[$request['name']]) ? $projects[$request['name']]['version'] : NULL; + $release = $release_info->selectReleaseBasedOnStrategy($request, $restrict_to, $select, $all, $version); + if ($release == FALSE) { + // Stop working on the first failure. Return silently on user abort. + if (drush_get_context('DRUSH_USER_ABORT', FALSE)) { + return FALSE; + } + // Signal that the command failed for all other problems. + return drush_set_error('DRUSH_DOWNLOAD_FAILED', dt("Could not download requested project(s).")); + } + $request['version'] = $release['version']; + + $project_release_info = $release_info->get($request); + $request['project_type'] = $project_release_info->getType(); + + // Determine the name of the directory that will contain the project. + // We face here all the assymetries to make it smooth for package handlers. + // For Drupal core: --drupal-project-rename or drupal-x.y + if (($request['project_type'] == 'core') || + (($request['project_type'] == 'profile') && (drush_get_option('variant', 'full') == 'full'))) { + // Avoid downloading core into existing core. + if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_ROOT) { + if (strpos(realpath(drush_get_option('destination')), DRUPAL_ROOT) !== FALSE) { + return drush_set_error('DRUSH_PM_DOWNLOAD_TRANSLATIONS_FORBIDDEN', dt('It\'s forbidden to download !project core into an existing core.', array('!project' => $request['name']))); + } + } + + if ($rename = drush_get_option('drupal-project-rename', FALSE)) { + if ($rename === TRUE) { + $request['project_dir'] = $request['name']; + } + else { + $request['project_dir'] = $rename; + } + } + else { + // Set to drupal-x.y, the expected name for .tar.gz contents. + // Explicitly needed for cvs package handler. + $request['project_dir'] = strtolower(strtr($release['name'], ' ', '-')); + } + } + // For the other project types we want the project name. Including core + // variant for profiles. Note those come with drupal-x.y in the .tar.gz. + else { + $request['project_dir'] = $request['name']; + } + + // Download the project to a temporary location. + drush_log(dt('Downloading project !name ...', array('!name' => $request['name']))); + $request['full_project_path'] = package_handler_download_project($request, $release); + if (!$request['full_project_path']) { + // Delete the cached update service file since it may be invalid. + $release_info->clearCached($request); + drush_log(dt('Error downloading !name', array('!name' => $request['name']), LogLevel::ERROR)); + continue; + } + + // Determine the install location for the project. User provided + // --destination has preference. + $destination = drush_get_option('destination'); + if (!empty($destination)) { + if (!file_exists($destination)) { + drush_mkdir($destination); + } + $request['project_install_location'] = realpath($destination); + } + else { + $request['project_install_location'] = _pm_download_destination($request['project_type']); + } + + // If user did not provide --destination, then call the + // download-destination-alter hook to give the chance to any commandfiles + // to adjust the install location or abort it. + if (empty($destination)) { + $result = drush_command_invoke_all_ref('drush_pm_download_destination_alter', $request, $release); + if (array_search(FALSE, $result, TRUE) !== FALSE) { + return FALSE; + } + } + + // Load version control engine and detect if (the parent directory of) the + // project install location is under a vcs. + if (!$version_control = drush_pm_include_version_control($request['project_install_location'])) { + continue; + } + + $request['project_install_location'] .= '/' . $request['project_dir']; + + if ($version_control->engine == 'backup') { + // Check if install location already exists. + if (is_dir($request['project_install_location'])) { + if (drush_confirm(dt('Install location !location already exists. Do you want to overwrite it?', array('!location' => $request['project_install_location'])))) { + drush_delete_dir($request['project_install_location'], TRUE); + } + else { + drush_log(dt("Skip installation of !project to !dest.", array('!project' => $request['name'], '!dest' => $request['project_install_location'])), LogLevel::WARNING); + continue; + } + } + } + else { + // Find and unlink all files but the ones in the vcs control directories. + $skip_list = array('.', '..'); + $skip_list = array_merge($skip_list, drush_version_control_reserved_files()); + drush_scan_directory($request['project_install_location'], '/.*/', $skip_list, 'unlink', TRUE, 'filename', 0, TRUE); + } + + // Copy the project to the install location. + if (drush_op('_drush_recursive_copy', $request['full_project_path'], $request['project_install_location'])) { + drush_log(dt("Project !project (!version) downloaded to !dest.", array('!project' => $request['name'], '!version' => $release['version'], '!dest' => $request['project_install_location'])), LogLevel::SUCCESS); + // Adjust full_project_path to the final project location. + $request['full_project_path'] = $request['project_install_location']; + + // If the version control engine is a proper vcs we also need to remove + // orphan directories. + if ($version_control->engine != 'backup') { + $empty_dirs = drush_find_empty_directories($request['full_project_path'], $version_control->reserved_files()); + foreach ($empty_dirs as $empty_dir) { + // Some VCS files are read-only on Windows (e.g., .svn/entries). + drush_delete_dir($empty_dir, TRUE); + } + } + + // Post download actions. + package_handler_post_download($request, $release); + drush_command_invoke_all('drush_pm_post_download', $request, $release); + $version_control->post_download($request); + + // Print release notes if --notes option is set. + if (drush_get_option('notes') && !drush_get_context('DRUSH_PIPE')) { + $project_release_info->getReleaseNotes($release['version'], FALSE); + } + + // Inform the user about available modules a/o themes in the downloaded project. + drush_pm_extensions_in_project($request); + } + else { + // We don't `return` here in order to proceed with downloading additional projects. + drush_set_error('DRUSH_PM_DOWNLOAD_FAILED', dt("Project !project (!version) could not be downloaded to !dest.", array('!project' => $request['name'], '!version' => $release['version'], '!dest' => $request['project_install_location']))); + } + + // Notify about this project. + if (drush_notify_allowed('pm-download')) { + $msg = dt('Project !project (!version) downloaded to !install.', array( + '!project' => $name, + '!version' => $release['version'], + '!install' => $request['project_install_location'], + )); + drush_notify_send(drush_notify_command_message('pm-download', $msg)); + } + } +} + +/** + * Implementation of hook_drush_pm_download_destination_alter(). + * + * Built-in download-destination-alter hook. This particular version of + * the hook will move modules that contain only Drush commands to + * /usr/share/drush/commands if it exists, or $HOME/.drush if the + * site-wide location does not exist. + */ +function pm_drush_pm_download_destination_alter(&$request, $release) { + // A module is a pure Drush command if it has no .info.yml (8+) and contains no + // .drush.inc files. Skip this test for Drush itself, though; we do + // not want to download Drush to the ~/.drush folder. + if (in_array($request['project_type'], array('module', 'utility')) && ($request['name'] != 'drush')) { + $drush_command_files = drush_scan_directory($request['full_project_path'], '/.*\.drush.inc/'); + if (!empty($drush_command_files)) { + $pattern = drush_drupal_major_version() >= 8 ? '/.*\.info/' : '/.*\.module/'; + $module_files = drush_scan_directory($request['full_project_path'], $pattern); + if (empty($module_files)) { + $install_dir = drush_get_context('DRUSH_SITE_WIDE_COMMANDFILES'); + if (!is_dir($install_dir) || !is_writable($install_dir)) { + $install_dir = drush_get_context('DRUSH_PER_USER_CONFIGURATION'); + } + // Make the .drush dir if it does not already exist. + if (!is_dir($install_dir)) { + drush_mkdir($install_dir, FALSE); + } + // Change the location if the mkdir worked. + if (is_dir($install_dir)) { + $request['project_install_location'] = $install_dir; + } + } + // We need to clear the Drush commandfile cache so that + // our newly-downloaded Drush extension commandfiles can be found. + drush_cache_clear_all(); + } + } +} + +/** + * Determines a candidate destination directory for a particular site path. + * + * Optionally attempts to create the directory. + * + * @return String the candidate destination if it exists. + */ +function _pm_download_destination_lookup($type, $drupal_root, $sitepath, $create = FALSE) { + // Profiles in Drupal < 8 + if (($type == 'profile') && (drush_drupal_major_version() < 8)) { + $destination = 'profiles'; + } + // Type: module, theme or profile. + else { + if ($type == 'theme engine') { + $destination = 'themes/engines'; + } else { + $destination = $type . 's'; + } + // Prefer /contrib if it exists. + if ($sitepath) { + $destination = $sitepath . '/' . $destination; + } + $contrib = $destination . '/contrib'; + if (is_dir($contrib)) { + $destination = $contrib; + } + } + if ($create) { + drush_log(dt('Attempting to create destination directory at !dir', array('!dir' => $destination))); + drush_mkdir($destination, TRUE); + } + if (is_dir($destination)) { + drush_log(dt('Using destination directory !dir', array('!dir' => $destination))); + return $destination; + } + drush_log(dt('Could not find destination directory at !dir', array('!dir' => $destination))); + return FALSE; +} + +/** + * Returns the best destination for a particular download type we can find. + * + * It is based on the project type and drupal and site contexts. + */ +function _pm_download_destination($type) { + $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT'); + $site_root = drush_get_context('DRUSH_DRUPAL_SITE_ROOT'); + $full_site_root = (empty($drupal_root) || empty($site_root)) ? '' : $drupal_root .'/'. $site_root; + $sitewide = empty($drupal_root) ? '' : $drupal_root . '/' . drush_drupal_sitewide_directory(); + + $in_site_directory = FALSE; + // Check if we are running within the site directory. + if (strpos(realpath(drush_cwd()), realpath($full_site_root)) !== FALSE || (drush_get_option('use-site-dir', FALSE))) { + $in_site_directory = TRUE; + } + + $destination = ''; + if ($type != 'core') { + // Attempt 1: If we are in a specific site directory, and the destination + // directory already exists, then we use that. + if (empty($destination) && $site_root && $in_site_directory) { + $create_dir = drush_get_option('use-site-dir', FALSE); + $destination = _pm_download_destination_lookup($type, $drupal_root, $full_site_root, $create_dir); + } + // Attempt 2: If the destination directory already exists for + // the sitewide directory, use that. + if (empty($destination) && $drupal_root) { + $destination = _pm_download_destination_lookup($type, $drupal_root, $sitewide); + } + // Attempt 3: If a specific (non default) site directory exists and + // the sitewide directory does not exist, then create destination + // in the site specific directory. + if (empty($destination) && $site_root && $site_root !== 'sites/default' && is_dir($full_site_root) && !is_dir($sitewide)) { + $destination = _pm_download_destination_lookup($type, $drupal_root, $full_site_root, TRUE); + } + // Attempt 4: If sitewide directory exists, then create destination there. + if (empty($destination) && is_dir($sitewide)) { + $destination = _pm_download_destination_lookup($type, $drupal_root, $sitewide, TRUE); + } + // Attempt 5: If site directory exists (even default), then create + // destination in that directory. + if (empty($destination) && $site_root && is_dir($full_site_root)) { + $destination = _pm_download_destination_lookup($type, $drupal_root, $full_site_root, TRUE); + } + } + // Attempt 6: If we didn't find a valid directory yet (or we somehow found + // one that doesn't exist) we always fall back to the current directory. + if (empty($destination) || !is_dir($destination)) { + $destination = drush_cwd(); + } + + return $destination; +} diff --git a/vendor/drush/drush/commands/pm/info.pm.inc b/vendor/drush/drush/commands/pm/info.pm.inc new file mode 100644 index 0000000000..c00c30ca33 --- /dev/null +++ b/vendor/drush/drush/commands/pm/info.pm.inc @@ -0,0 +1,152 @@ + $extension)), LogLevel::WARNING); + continue; + } + if (drush_extension_get_type($info) == 'module') { + $data = _drush_pm_info_module($info); + } + else { + $data = _drush_pm_info_theme($info); + } + $result[$extension] = $data; + } + return $result; +} + +/** + * Output format formatter-filter callback. + * + * @see drush_parse_command() + * @see drush_outputformat + */ +function _drush_pm_info_format_table_data($data) { + $result = array(); + foreach ($data as $extension => $info) { + foreach($info as $key => $value) { + if (is_array($value)) { + if (empty($value)) { + $value = 'none'; + } + else { + $value = implode(', ', $value); + } + } + $result[$extension][$key] = $value; + } + } + return $result; +} + +/** + * Return an array with general info of an extension. + */ +function _drush_pm_info_extension($info) { + $data['extension'] = drush_extension_get_name($info); + $data['project'] = isset($info->info['project'])?$info->info['project']:dt('Unknown'); + $data['type'] = drush_extension_get_type($info); + $data['title'] = $info->info['name']; + $data['config'] = isset($info->info['configure']) ? $info->info['configure'] : dt('None'); + $data['description'] = $info->info['description']; + $data['version'] = $info->info['version']; + $data['date'] = isset($info->info['datestamp']) ? drush_format_date($info->info['datestamp'], 'custom', 'Y-m-d') : NULL; + $data['package'] = $info->info['package']; + $data['core'] = $info->info['core']; + $data['php'] = $info->info['php']; + $data['status'] = drush_get_extension_status($info); + $data['path'] = drush_extension_get_path($info); + + return $data; +} + +/** + * Return an array with info of a module. + */ +function _drush_pm_info_module($info) { + $major_version = drush_drupal_major_version(); + + $data = _drush_pm_info_extension($info); + if ($info->schema_version > 0) { + $schema_version = $info->schema_version; + } + elseif ($info->schema_version == -1) { + $schema_version = "no schema installed"; + } + else { + $schema_version = "module has no schema"; + } + $data['schema_version'] = $schema_version; + if ($major_version == 7) { + $data['files'] = $info->info['files']; + } + $data['requires'] = $info->info['dependencies']; + + if ($major_version == 6) { + $requiredby = $info->info['dependents']; + } + else { + $requiredby = array_keys($info->required_by); + } + $data['required_by'] = $requiredby; + if ($info->status == 1) { + $role = drush_role_get_class(); + $data['permissions'] = $role->getModulePerms(drush_extension_get_name($info)); + } + return $data; +} + +/** + * Return an array with info of a theme. + */ +function _drush_pm_info_theme($info) { + $major_version = drush_drupal_major_version(); + + $data = _drush_pm_info_extension($info); + + $data['core'] = $info->info['core']; + $data['php'] = $info->info['php']; + $data['engine'] = $info->info['engine']; + $data['base_theme'] = isset($info->base_themes) ? implode(', ', $info->base_themes) : ''; + $regions = $info->info['regions']; + $data['regions'] = $regions; + $features = $info->info['features']; + $data['features'] = $features; + if (count($info->info['stylesheets']) > 0) { + $data['stylesheets'] = ''; + foreach ($info->info['stylesheets'] as $media => $files) { + $files = array_keys($files); + $data['media '.$media] = $files; + } + } + if (count($info->info['scripts']) > 0) { + $scripts = array_keys($info->info['scripts']); + $data['scripts'] = $scripts; + } + return $data; +} diff --git a/vendor/drush/drush/commands/pm/package_handler/git_drupalorg.inc b/vendor/drush/drush/commands/pm/package_handler/git_drupalorg.inc new file mode 100644 index 0000000000..c1482bce63 --- /dev/null +++ b/vendor/drush/drush/commands/pm/package_handler/git_drupalorg.inc @@ -0,0 +1,275 @@ +=1.7 + // (avoid drush_shell_exec because we want to run this even in --simulated mode.) + $success = exec('git --version', $git); + $git_version_array = explode(" ", $git[0]); + $git_version = $git_version_array[2]; + + drush_set_context('DRUSH_DEBUG', $debug); + if (!$success) { + return drush_set_error('DRUSH_SHELL_COMMAND_NOT_FOUND', dt('git executable not found.')); + } elseif ($git_version < '1.7') { + return drush_set_error('GIT_VERSION_UNSUPPORTED', dt('Your git version !git_version is not supported; please upgrade to git 1.7 or later.', array('!git_version' => $git_version))); + } + // Check git_deploy is enabled. Only for bootstrapped sites. + if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_FULL) { + drush_include_engine('drupal', 'environment'); + if (!drush_get_option('gitinfofile') && !drush_module_exists('git_deploy')) { + drush_log(dt('git package handler needs git_deploy module enabled to work properly.'), LogLevel::WARNING); + } + } + + return TRUE; +} + +/** + * Download a project. + * + * @param $request + * The project array with name, base and full (final) paths. + * @param $release + * The release details array from drupal.org. + */ +function package_handler_download_project(&$request, $release) { + if ($username = drush_get_option('gitusername')) { + // Uses SSH, which enables pushing changes back to git.drupalcode.org. + $repository = 'git@git.drupalcode.org:project/' . $request['name'] . '.git'; + } + else { + $repository = 'https://git.drupalcode.org/project/' . $request['name'] . '.git'; + } + $request['repository'] = $repository; + $tag = $release['tag']; + + // If the --cache option was given, create a new git reference cache of the + // remote repository, or update the existing cache to fetch recent changes. + if (drush_get_option('cache') && ($cachedir = drush_directory_cache())) { + $gitcache = $cachedir . '/git'; + $projectcache = $gitcache . '/' . $request['name'] . '.git'; + drush_mkdir($gitcache); + // Setup a new cache, if we don't have this project yet. + if (!file_exists($projectcache)) { + // --mirror works similar to --bare, but retrieves all tags, local + // branches, remote branches, and any other refs (notes, stashes, etc). + // @see http://stackoverflow.com/questions/3959924 + $command = 'git clone --mirror'; + if (drush_get_context('DRUSH_VERBOSE')) { + $command .= ' --verbose --progress'; + } + $command .= ' %s %s'; + drush_shell_cd_and_exec($gitcache, $command, $repository, $request['name'] . '.git'); + } + // If we already have this project, update it to speed up subsequent clones. + else { + // A --mirror clone is fully synchronized with `git remote update` instead + // of `git fetch --all`. + // @see http://stackoverflow.com/questions/6150188 + drush_shell_cd_and_exec($projectcache, 'git remote update'); + } + $gitcache = $projectcache; + } + + // Clone the repo into a temporary path. + $clone_path = drush_tempdir(); + + $command = 'git clone'; + $command .= ' ' . drush_get_option('gitcloneparams'); + if (drush_get_option('cache')) { + $command .= ' --reference ' . drush_escapeshellarg($gitcache); + } + if (drush_get_context('DRUSH_VERBOSE')) { + $command .= ' --verbose --progress'; + } + $command .= ' ' . drush_escapeshellarg($repository); + $command .= ' ' . drush_escapeshellarg($clone_path); + if (!drush_shell_exec($command)) { + return drush_set_error('DRUSH_PM_GIT_CHECKOUT_PROBLEMS', dt('Unable to clone project !name from git.drupalcode.org.', array('!name' => $request['name']))); + } + + // Check if the 'tag' from the release feed is a tag or a branch. + // If the tag exists, git will return it + if (!drush_shell_cd_and_exec($clone_path, 'git tag -l ' . drush_escapeshellarg($tag))) { + return drush_set_error('DRUSH_PM_GIT_CHECKOUT_PROBLEMS', dt('Unable to clone project !name from git.drupalcode.org.', array('!name' => $request['name']))); + } + $output = drush_shell_exec_output(); + + if (isset($output[0]) && ($output[0] == $tag)) { + // If we want a tag, simply checkout it. The checkout will end up in + // "detached head" state. + $command = 'git checkout ' . drush_get_option('gitcheckoutparams'); + $command .= ' ' . drush_escapeshellarg($tag); + if (!drush_shell_cd_and_exec($clone_path, $command)) { + return drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', 'Unable to retrieve ' . $request['name'] . ' from git.drupalcode.org.'); + } + } + else { + // Else, we want to checkout a branch. + // First check if we are not already in the correct branch. + if (!drush_shell_cd_and_exec($clone_path, 'git symbolic-ref HEAD')) { + return drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', 'Unable to retrieve ' . $request['name'] . ' from git.drupalcode.org.'); + } + $output = drush_shell_exec_output(); + $current_branch = preg_replace('@^refs/heads/@', '', $output[0]); + + // If we are not on the correct branch already, switch to the correct one. + if ($current_branch != $tag) { + $command = 'git checkout'; + $command .= ' ' . drush_get_option('gitcheckoutparams'); + $command .= ' --track ' . drush_escapeshellarg('origin/' . $tag) . ' -b ' . drush_escapeshellarg($tag); + if (!drush_shell_cd_and_exec($clone_path, $command)) { + return drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', 'Unable to retrieve ' . $request['name'] . ' from git.drupalcode.org.'); + } + } + } + + return $clone_path; +} + +/** + * Update a project (so far, only modules are supported). + * + * @param $request + * The project array with name, base and full (final) paths. + * @param $release + * The release details array from drupal.org. + */ +function package_handler_update_project($request, $release) { + drush_log('Updating project ' . $request['name'] . ' ...'); + + $commands = array(); + if ((!empty($release['version_extra'])) && ($release['version_extra'] == 'dev')) { + // Update the branch of the development repository. + $commands[] = 'git pull'; + $commands[] = drush_get_option('gitpullparams'); + } + else { + // Use a stable repository. + $commands[] = 'git fetch'; + $commands[] = drush_get_option('gitfetchparams'); + $commands[] = ';'; + $commands[] = 'git checkout'; + $commands[] = drush_get_option('gitcheckoutparams'); + $commands[] = $release['version']; + } + + if (!drush_shell_cd_and_exec($request['full_project_path'], implode(' ', $commands))) { + return drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', 'Unable to update ' . $request['name'] . ' from git.drupalcode.org.'); + } + + return TRUE; +} + +/** + * Post download action. + * + * This action take place once the project is placed in its final location. + * + * Here we add the project as a git submodule. + */ +function package_handler_post_download($project, $release) { + if (drush_get_option('gitsubmodule', FALSE)) { + // Obtain the superproject path, then add as submodule. + if (drush_shell_cd_and_exec(dirname($project['full_project_path']), 'git rev-parse --show-toplevel')) { + $output = drush_shell_exec_output(); + $superproject = $output[0]; + // Add the downloaded project as a submodule of its git superproject. + $command = array(); + $command[] = 'git submodule add'; + $command[] = drush_get_option('gitsubmoduleaddparams'); + $command[] = $project['repository']; + // We need the submodule relative path. + $command[] = substr(realpath($project['full_project_path']), strlen(realpath($superproject)) + 1); + if (!drush_shell_cd_and_exec($superproject, implode(' ', $command))) { + return drush_set_error('DRUSH_PM_GIT_CHECKOUT_PROBLEMS', dt('Unable to add !name as a git submodule of !super.', array('!name' => $project['name'], '!super' => $superproject))); + } + } + else { + return drush_set_error('DRUSH_PM_GIT_SUBMODULE_PROBLEMS', dt('Unable to create !project as a git submodule: !dir is not in a Git repository.', array('!project' => $project['name'], '!dir' => dirname($project['full_project_path'])))); + } + } + + if (drush_get_option('gitinfofile', FALSE)) { + $matches = array(); + if (preg_match('/^(.+).x-dev$/', $release['version'], $matches)) { + $full_version = drush_pm_git_drupalorg_compute_rebuild_version($project['full_project_path'], $matches[1]); + } + else { + $full_version = $release['version']; + } + if (drush_shell_cd_and_exec(dirname($project['full_project_path']), 'git log -1 --pretty=format:%ct')) { + $output = drush_shell_exec_output(); + $datestamp = $output[0]; + } + else { + $datestamp = time(); + } + drush_pm_inject_info_file_metadata($project['full_project_path'], $project['name'], $full_version, $datestamp); + } + +} + +/** + * Helper function to compute the rebulid version string for a project. + * + * This does some magic in Git to find the latest release tag along + * the branch we're packaging from, count the number of commits since + * then, and use that to construct this fancy alternate version string + * which is useful for the version-specific dependency support in Drupal + * 7 and higher. + * + * NOTE: A similar function lives in git_deploy and in the drupal.org + * packaging script (see DrupalorgProjectPackageRelease.class.php inside + * drupalorg/drupalorg_project/plugins/release_packager). Any changes to the + * actual logic in here should probably be reflected in the other places. + * + * @param string $project_dir + * The full path to the root directory of the project to operate on. + * @param string $branch + * The branch that we're using for -dev. This should only include the + * core version, the dash, and the branch's major version (eg. '7.x-2'). + * + * @return string + * The full 'rebuild version string' in the given Git checkout. + */ +function drush_pm_git_drupalorg_compute_rebuild_version($project_dir, $branch) { + $rebuild_version = ''; + $branch_preg = preg_quote($branch); + + if (drush_shell_cd_and_exec($project_dir, 'git describe --tags')) { + $shell_output = drush_shell_exec_output(); + $last_tag = $shell_output[0]; + // Make sure the tag starts as Drupal formatted (for eg. + // 7.x-1.0-alpha1) and if we are on a proper branch (ie. not master) + // then it's on that branch. + if (preg_match('/^(?' . $branch_preg . '\.\d+(?:-[^-]+)?)(?-(?\d+-)g[0-9a-f]{7})?$/', $last_tag, $matches)) { + // If we found additional git metadata (in particular, number of commits) + // then use that info to build the version string. + if (isset($matches['gitextra'])) { + $rebuild_version = $matches['drupalversion'] . '+' . $matches['numberofcommits'] . 'dev'; + } + // Otherwise, the branch tip is pointing to the same commit as the + // last tag on the branch, in which case we use the prior tag and + // add '+0-dev' to indicate we're still on a -dev branch. + else { + $rebuild_version = $last_tag . '+0-dev'; + } + } + } + return $rebuild_version; +} diff --git a/vendor/drush/drush/commands/pm/package_handler/wget.inc b/vendor/drush/drush/commands/pm/package_handler/wget.inc new file mode 100644 index 0000000000..e263940c5b --- /dev/null +++ b/vendor/drush/drush/commands/pm/package_handler/wget.inc @@ -0,0 +1,116 @@ + to download link, so it is part of the cache key. Dev snapshots can then be cached forever. + $download_link = $release['download_link']; + if (strpos($release['download_link'], '-dev') !== FALSE) { + $download_link .= '?date=' . $release['date']; + } + // Cache for a year by default. + $cache_duration = (drush_get_option('cache', TRUE)) ? 86400*365 : 0; + + // Prepare download path. On Windows file name cannot contain '?'. + // See http://drupal.org/node/1782444 + $filename = str_replace('?', '_', basename($download_link)); + $download_path = drush_tempdir() . '/' . $filename; + + // Download the tarball. + $download_path = drush_download_file($download_link, $download_path, $cache_duration); + if ($download_path || drush_get_context('DRUSH_SIMULATE')) { + drush_log(dt('Downloading !filename was successful.', array('!filename' => $filename))); + } + else { + return drush_set_error('DRUSH_PM_DOWNLOAD_FAILED', dt('Unable to download !project to !path from !url.', array('!project' => $request['name'], '!path' => $download_path, '!url' => $download_link))); + } + + // Check Md5 hash. + if (!drush_get_option('no-md5')) { + if (drush_op('md5_file', $download_path) !== $release['mdhash'] && !drush_get_context('DRUSH_SIMULATE')) { + drush_delete_dir(drush_download_file_name($download_link, TRUE)); + return drush_set_error('DRUSH_PM_FILE_CORRUPT', dt('File !filename is corrupt (wrong md5 checksum).', array('!filename' => $filename))); + } + else { + drush_log(dt('Md5 checksum of !filename verified.', array('!filename' => $filename))); + } + } + + // Extract the tarball in place and return the full path to the untarred directory. + $download_base = dirname($download_path); + if (!$tar_file_list = drush_tarball_extract($download_path, $download_base, TRUE)) { + // An error has been logged. + return FALSE; + } + $tar_directory = drush_trim_path($tar_file_list[0]); + + return $download_base . '/' . $tar_directory; +} + +/** + * Update a project. + * + * @return bool + * Success or failure. An error message will be logged. + */ +function package_handler_update_project(&$request, $release) { + $download_path = package_handler_download_project($request, $release); + if ($download_path) { + return drush_move_dir($download_path, $request['full_project_path']); + } + else { + return FALSE; + } +} + +/** + * Post download action. + * + * This action take place once the project is placed in its final location. + */ +function package_handler_post_download($project) { +} diff --git a/vendor/drush/drush/commands/pm/pm.drush.inc b/vendor/drush/drush/commands/pm/pm.drush.inc new file mode 100644 index 0000000000..fb4e78e5ab --- /dev/null +++ b/vendor/drush/drush/commands/pm/pm.drush.inc @@ -0,0 +1,2318 @@ + drush_get_context('DRUSH_SITE_WIDE_COMMANDFILES'))); + } +} + +/** + * Implementation of hook_drush_command(). + */ +function pm_drush_command() { + $update_options = array( + 'lock' => array( + 'description' => 'Add a persistent lock to remove the specified projects from consideration during updates. Locks may be removed with the --unlock parameter, or overridden by specifically naming the project as a parameter to pm-update or pm-updatecode. The lock does not affect pm-download. See also the update_advanced project for similar and improved functionality.', + 'example-value' => 'foo,bar', + ), + ); + $update_suboptions = array( + 'lock' => array( + 'lock-message' => array( + 'description' => 'A brief message explaining why a project is being locked; displayed during pm-updatecode. Optional.', + 'example-value' => 'message', + ), + 'unlock' => array( + 'description' => 'Remove the persistent lock from the specified projects so that they may be updated again.', + 'example-value' => 'foo,bar', + ), + ), + ); + + $items['pm-enable'] = array( + 'description' => 'Enable one or more extensions (modules or themes).', + 'arguments' => array( + 'extensions' => 'A list of modules or themes. You can use the * wildcard at the end of extension names to enable all matches.', + ), + 'options' => array( + 'resolve-dependencies' => 'Attempt to download any missing dependencies. At the moment, only works when the module name is the same as the project name.', + 'skip' => 'Skip automatic downloading of libraries (c.f. devel).', + ), + 'aliases' => array('en', 'pm:enable'), + 'engines' => array( + 'release_info' => array( + 'add-options-to-command' => FALSE, + ), + ), + ); + $items['pm-disable'] = array( + 'description' => 'Disable one or more extensions (modules or themes).', + 'arguments' => array( + 'extensions' => 'A list of modules or themes. You can use the * wildcard at the end of extension names to disable multiple matches.', + ), + 'aliases' => array('dis', 'pm:disable'), + 'engines' => array( + 'version_control', + 'package_handler', + 'release_info' => array( + 'add-options-to-command' => FALSE, + ), + ), + ); + $items['pm-info'] = array( + 'description' => 'Show detailed info for one or more extensions (modules or themes).', + 'arguments' => array( + 'extensions' => 'A list of modules or themes. You can use the * wildcard at the end of extension names to show info for multiple matches. If no argument is provided it will show info for all available extensions.', + ), + 'aliases' => array('pmi', 'pm:info'), + 'outputformat' => array( + 'default' => 'key-value-list', + 'pipe-format' => 'json', + 'formatted-filter' => '_drush_pm_info_format_table_data', + 'field-labels' => array( + 'extension' => 'Extension', + 'project' => 'Project', + 'type' => 'Type', + 'title' => 'Title', + 'description' => 'Description', + 'version' => 'Version', + 'date' => 'Date', + 'package' => 'Package', + 'core' => 'Core', + 'php' => 'PHP', + 'status' => 'Status', + 'path' => 'Path', + 'schema_version' => 'Schema version', + 'files' => 'Files', + 'requires' => 'Requires', + 'required_by' => 'Required by', + 'permissions' => 'Permissions', + 'config' => 'Configure', + 'engine' => 'Engine', + 'base_theme' => 'Base theme', + 'regions' => 'Regions', + 'features' => 'Features', + 'stylesheets' => 'Stylesheets', + // 'media_' . $media => 'Media '. $media for each $info->info['stylesheets'] as $media => $files + 'scripts' => 'Scripts', + ), + 'output-data-type' => 'format-table', + ), + ); + + $items['pm-projectinfo'] = array( + 'description' => 'Show a report of available projects and their extensions.', + 'arguments' => array( + 'projects' => 'Optional. A list of installed projects to show.', + ), + 'options' => array( + 'drush' => 'Optional. Only incude projects that have one or more Drush commands.', + 'status' => array( + 'description' => 'Filter by project status. Choices: enabled, disabled. A project is considered enabled when at least one of its extensions is enabled.', + 'example-value' => 'enabled', + ), + ), + 'outputformat' => array( + 'default' => 'key-value-list', + 'pipe-format' => 'json', + 'field-labels' => array( + 'label' => 'Name', + 'type' => 'Type', + 'version' => 'Version', + 'status' => 'Status', + 'extensions' => 'Extensions', + 'drush' => 'Drush Commands', + 'datestamp' => 'Datestamp', + 'path' => 'Path', + ), + 'fields-default' => array('label', 'type', 'version', 'status', 'extensions', 'drush', 'datestamp', 'path'), + 'fields-pipe' => array('label'), + 'output-data-type' => 'format-table', + ), + 'aliases' => array('pmpi', 'pm:projectinfo'), + ); + + // Install command is reserved for the download and enable of projects including dependencies. + // @see http://drupal.org/node/112692 for more information. + // $items['install'] = array( + // 'description' => 'Download and enable one or more modules', + // ); + $items['pm-uninstall'] = array( + 'description' => 'Uninstall one or more modules and their dependent modules.', + 'arguments' => array( + 'modules' => 'A list of modules.', + ), + 'aliases' => array('pmu', 'pm:uninstall'), + ); + $items['pm-list'] = array( + 'description' => 'Show a list of available extensions (modules and themes).', + 'callback arguments' => array(array(), FALSE), + 'options' => array( + 'type' => array( + 'description' => 'Filter by extension type. Choices: module, theme.', + 'example-value' => 'module', + ), + 'status' => array( + 'description' => 'Filter by extension status. Choices: enabled, disabled and/or \'not installed\'. You can use multiple comma separated values. (i.e. --status="disabled,not installed").', + 'example-value' => 'disabled', + ), + 'package' => 'Filter by project packages. You can use multiple comma separated values. (i.e. --package="Core - required,Other").', + 'core' => 'Filter out extensions that are not in drupal core.', + 'no-core' => 'Filter out extensions that are provided by drupal core.', + ), + 'outputformat' => array( + 'default' => 'table', + 'pipe-format' => 'list', + 'fields-default' => array('package', 'name', 'type', 'status', 'version'), + 'field-labels' => array('package' => 'Package', 'project' => 'Project', 'name' => 'Name', 'display_name' => 'Display Name', 'type' => 'Type', 'status' => 'Status', 'version' => 'Version', 'path' => 'Path'), + 'output-data-type' => 'format-table', + ), + 'aliases' => array('pml', 'pm:list'), + ); + $items['pm-refresh'] = array( + 'description' => 'Refresh update status information.', + 'engines' => array( + 'update_status' => array( + 'add-options-to-command' => FALSE, + ), + ), + 'aliases' => array('rf', 'pm:refresh'), + ); + $items['pm-updatestatus'] = array( + 'description' => 'Show a report of available minor updates to Drupal core and contrib projects.', + 'arguments' => array( + 'projects' => 'Optional. A list of installed projects to show.', + ), + 'options' => array( + 'pipe' => 'Return a list of the projects with any extensions enabled that need updating, one project per line.', + ) + $update_options, + 'sub-options' => $update_suboptions, + 'engines' => array( + 'update_status', + ), + 'outputformat' => array( + 'default' => 'table', + 'pipe-format' => 'list', + 'field-labels' => array('name' => 'Short Name', 'label' => 'Name', 'existing_version' => 'Installed Version', 'status' => 'Status', 'status_msg' => 'Message', 'candidate_version' => 'Proposed version'), + 'fields-default' => array('label', 'existing_version', 'candidate_version', 'status_msg' ), + 'fields-pipe' => array('name', 'existing_version', 'candidate_version', 'status_msg'), + 'output-data-type' => 'format-table', + ), + 'aliases' => array('ups', 'pm:updatestatus'), + ); + $items['pm-updatecode'] = array( + 'description' => 'Update Drupal core and contrib projects to latest recommended releases.', + 'examples' => array( + 'drush pm-updatecode --no-core' => 'Update contrib projects, but skip core.', + 'drush pm-updatestatus --format=csv --list-separator=" " --fields="name,existing_version,candidate_version,status_msg"' => 'To show a list of projects with their update status, use pm-updatestatus instead of pm-updatecode.', + ), + 'arguments' => array( + 'projects' => 'Optional. A list of installed projects to update.', + ), + 'options' => array( + 'notes' => 'Show release notes for each project to be updated.', + 'no-core' => 'Only update modules and skip the core update.', + 'check-updatedb' => 'Check to see if an updatedb is needed after updating the code. Default is on; use --check-updatedb=0 to disable.', + ) + $update_options, + 'sub-options' => $update_suboptions, + 'aliases' => array('upc', 'pm:updatecode'), + 'topics' => array('docs-policy'), + 'engines' => array( + 'version_control', + 'package_handler', + 'release_info' => array( + 'add-options-to-command' => FALSE, + ), + 'update_status', + ), + ); + // Merge all items from above. + $items['pm-update'] = array( + 'description' => 'Update Drupal core and contrib projects and apply any pending database updates (Same as pm-updatecode + updatedb).', + 'aliases' => array('up', 'pm:update'), + 'allow-additional-options' => array('pm-updatecode', 'updatedb'), + ); + $items['pm-updatecode-postupdate'] = array( + 'description' => 'Notify of pending db updates.', + 'hidden' => TRUE, + 'aliases' => array('pm:updatecode:postupdate'), + ); + $items['pm-releasenotes'] = array( + 'description' => 'Print release notes for given projects.', + 'arguments' => array( + 'projects' => 'A list of project names, with optional version. Defaults to \'drupal\'', + ), + 'options' => array( + 'html' => dt('Display release notes in HTML rather than plain text.'), + ), + 'examples' => array( + 'drush rln cck' => 'Prints the release notes for the recommended version of CCK project.', + 'drush rln token-1.13' => 'View release notes of a specfic version of the Token project for my version of Drupal.', + 'drush rln pathauto zen' => 'View release notes for the recommended version of Pathauto and Zen projects.', + ), + 'aliases' => array('rln', 'pm:releasenotes'), + 'bootstrap' => DRUSH_BOOTSTRAP_MAX, + 'engines' => array( + 'release_info', + ), + ); + $items['pm-releases'] = array( + 'description' => 'Print release information for given projects.', + 'arguments' => array( + 'projects' => 'A list of drupal.org project names. Defaults to \'drupal\'', + ), + 'examples' => array( + 'drush pm-releases cck zen' => 'View releases for cck and Zen projects for your Drupal version.', + ), + 'options' => array( + 'default-major' => 'Show releases compatible with the specified major version of Drupal.', + ), + 'aliases' => array('rl', 'pm:releases'), + 'bootstrap' => DRUSH_BOOTSTRAP_MAX, + 'outputformat' => array( + 'default' => 'table', + 'pipe-format' => 'csv', + 'field-labels' => array( + 'project' => 'Project', + 'version' => 'Release', + 'date' => 'Date', + 'status' => 'Status', + 'release_link' => 'Release link', + 'download_link' => 'Download link', + ), + 'fields-default' => array('project', 'version', 'date', 'status'), + 'fields-pipe' => array('project', 'version', 'date', 'status'), + 'output-data-type' => 'format-table', + ), + 'engines' => array( + 'release_info', + ), + ); + $items['pm-download'] = array( + 'description' => 'Download projects from drupal.org or other sources.', + 'examples' => array( + 'drush dl drupal' => 'Download latest recommended release of Drupal core.', + 'drush dl drupal-7.x' => 'Download latest 7.x development version of Drupal core.', + 'drush dl drupal-6' => 'Download latest recommended release of Drupal 6.x.', + 'drush dl cck zen' => 'Download latest versions of CCK and Zen projects.', + 'drush dl og-1.3' => 'Download a specfic version of Organic groups module for my version of Drupal.', + 'drush dl diff-6.x-2.x' => 'Download a specific development branch of diff module for a specific Drupal version.', + 'drush dl views --select' => 'Show a list of recent releases of the views project, prompt for which one to download.', + 'drush dl webform --dev' => 'Download the latest dev release of webform.', + 'drush dl webform --cache' => 'Download webform. Fetch and populate the download cache as needed.', + ), + 'arguments' => array( + 'projects' => 'A comma delimited list of drupal.org project names, with optional version. Defaults to \'drupal\'', + ), + 'options' => array( + 'destination' => array( + 'description' => 'Path to which the project will be copied. If you\'re providing a relative path, note it is relative to the drupal root (if bootstrapped).', + 'example-value' => 'path', + ), + 'use-site-dir' => 'Force to use the site specific directory. It will create the directory if it doesn\'t exist. If --destination is also present this option will be ignored.', + 'notes' => 'Show release notes after each project is downloaded.', + 'variant' => array( + 'description' => "Only useful for install profiles. Possible values: 'full', 'projects', 'profile-only'.", + 'example-value' => 'full', + ), + 'select' => "Select the version to download interactively from a list of available releases.", + 'drupal-project-rename' => 'Alternate name for "drupal-x.y" directory when downloading Drupal project. Defaults to "drupal".', + 'default-major' => array( + 'description' => 'Specify the default major version of modules to download when there is no bootstrapped Drupal site. Defaults to "8".', + 'example-value' => '7', + ), + 'skip' => 'Skip automatic downloading of libraries (c.f. devel).', + 'pipe' => 'Returns a list of the names of the extensions (modules and themes) contained in the downloaded projects.', + ), + 'bootstrap' => DRUSH_BOOTSTRAP_MAX, + 'aliases' => array('dl', 'pm:download'), + 'engines' => array( + 'version_control', + 'package_handler', + 'release_info', + ), + ); + return $items; +} + +/** + * @defgroup extensions Extensions management. + * @{ + * Functions to manage extensions. + */ + +/** + * Command argument complete callback. + */ +function pm_pm_enable_complete() { + return pm_complete_extensions(); +} + +/** + * Command argument complete callback. + */ +function pm_pm_disable_complete() { + return pm_complete_extensions(); +} + +/** + * Command argument complete callback. + */ +function pm_pm_uninstall_complete() { + return pm_complete_extensions(); +} + +/** + * Command argument complete callback. + */ +function pm_pm_info_complete() { + return pm_complete_extensions(); +} + +/** + * Command argument complete callback. + */ +function pm_pm_releasenotes_complete() { + return pm_complete_projects(); +} + +/** + * Command argument complete callback. + */ +function pm_pm_releases_complete() { + return pm_complete_projects(); +} + +/** + * Command argument complete callback. + */ +function pm_pm_updatecode_complete() { + return pm_complete_projects(); +} + +/** + * Command argument complete callback. + */ +function pm_pm_update_complete() { + return pm_complete_projects(); +} + +/** + * List extensions for completion. + * + * @return + * Array of available extensions. + */ +function pm_complete_extensions() { + if (drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_FULL)) { + $extension_info = drush_get_extensions(FALSE); + return array('values' => array_keys($extension_info)); + } +} + +/** + * List projects for completion. + * + * @return + * Array of installed projects. + */ +function pm_complete_projects() { + if (drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_FULL)) { + return array('values' => array_keys(drush_get_projects())); + } +} + +/** + * Sort callback function for sorting extensions. + * + * It will sort first by type, second by package and third by name. + */ +function _drush_pm_sort_extensions($a, $b) { + $a_type = drush_extension_get_type($a); + $b_type = drush_extension_get_type($b); + if ($a_type == 'module' && $b_type == 'theme') { + return -1; + } + if ($a_type == 'theme' && $b_type == 'module') { + return 1; + } + $cmp = strcasecmp($a->info['package'], $b->info['package']); + if ($cmp == 0) { + $cmp = strcasecmp($a->info['name'], $b->info['name']); + } + return $cmp; +} + +/** + * Calculate an extension status based on current status and schema version. + * + * @param $extension + * Object of a single extension info. + * + * @return + * String describing extension status. Values: enabled|disabled|not installed + */ +function drush_get_extension_status($extension) { + if ((drush_extension_get_type($extension) == 'module') && ($extension->schema_version == -1)) { + $status = "not installed"; + } + else { + $status = ($extension->status == 1)?'enabled':'disabled'; + } + + return $status; +} + +/** + * Classify extensions as modules, themes or unknown. + * + * @param $extensions + * Array of extension names, by reference. + * @param $modules + * Empty array to be filled with modules in the provided extension list. + * @param $themes + * Empty array to be filled with themes in the provided extension list. + */ +function drush_pm_classify_extensions(&$extensions, &$modules, &$themes, $extension_info) { + _drush_pm_expand_extensions($extensions, $extension_info); + foreach ($extensions as $extension) { + if (!isset($extension_info[$extension])) { + continue; + } + $type = drush_extension_get_type($extension_info[$extension]); + if ($type == 'module') { + $modules[$extension] = $extension; + } + else if ($type == 'theme') { + $themes[$extension] = $extension; + } + } +} + +/** + * Obtain an array of installed projects off the extensions available. + * + * A project is considered to be 'enabled' when any of its extensions is + * enabled. + * If any extension lacks project information and it is found that the + * extension was obtained from drupal.org's cvs or git repositories, a new + * 'vcs' attribute will be set on the extension. Example: + * $extensions[name]->vcs = 'cvs'; + * + * @param array $extensions + * Array of extensions as returned by drush_get_extensions(). + * + * @return + * Array of installed projects with info of version, status and provided + * extensions. + */ +function drush_get_projects(&$extensions = NULL) { + if (!isset($extensions)) { + $extensions = drush_get_extensions(); + } + $projects = array( + 'drupal' => array( + 'label' => 'Drupal', + 'version' => drush_drupal_version(), + 'type' => 'core', + 'extensions' => array(), + ) + ); + if (isset($extensions['system']->info['datestamp'])) { + $projects['drupal']['datestamp'] = $extensions['system']->info['datestamp']; + } + foreach ($extensions as $extension) { + $extension_name = drush_extension_get_name($extension); + $extension_path = drush_extension_get_path($extension); + + // Obtain the project name. It is not available in this cases: + // 1. the extension is part of drupal core. + // 2. the project was checked out from CVS/git and cvs_deploy/git_deploy + // is not installed. + // 3. it is not a project hosted in drupal.org. + if (empty($extension->info['project'])) { + if (isset($extension->info['version']) && ($extension->info['version'] == drush_drupal_version())) { + $project = 'drupal'; + } + else { + if (is_dir($extension_path . '/CVS') && (!drush_module_exists('cvs_deploy'))) { + $extension->vcs = 'cvs'; + drush_log(dt('Extension !extension is fetched from cvs. Ignoring.', array('!extension' => $extension_name)), LogLevel::DEBUG); + } + elseif (is_dir($extension_path . '/.git') && (!drush_module_exists('git_deploy'))) { + $extension->vcs = 'git'; + drush_log(dt('Extension !extension is fetched from git. Ignoring.', array('!extension' => $extension_name)), LogLevel::DEBUG); + } + continue; + } + } + else { + $project = $extension->info['project']; + } + + // Create/update the project in $projects with the project data. + if (!isset($projects[$project])) { + $projects[$project] = array( + // If there's an extension with matching name, pick its label. + // Otherwise use just the project name. We avoid $extension->label + // for the project label because the extension's label may have + // no direct relation with the project name. For example, + // "Text (text)" or "Number (number)" for the CCK project. + 'label' => isset($extensions[$project]) ? $extensions[$project]->label : $project, + 'type' => drush_extension_get_type($extension), + 'version' => $extension->info['version'], + 'status' => $extension->status, + 'extensions' => array(), + ); + if (isset($extension->info['datestamp'])) { + $projects[$project]['datestamp'] = $extension->info['datestamp']; + } + if (isset($extension->info['project status url'])) { + $projects[$project]['status url'] = $extension->info['project status url']; + } + } + else { + // If any of the extensions is enabled, consider the project is enabled. + if ($extension->status != 0) { + $projects[$project]['status'] = $extension->status; + } + } + $projects[$project]['extensions'][] = drush_extension_get_name($extension); + } + + // Obtain each project's path and try to provide a better label for ones + // with machine name. + $reserved = array('modules', 'sites', 'themes'); + foreach ($projects as $name => $project) { + if ($name == 'drupal') { + continue; + } + + // If this project has no human label, see if we can find + // one "main" extension whose label we could use. + if ($project['label'] == $name) { + // If there is only one extension, construct a label based on + // the extension name. + if (count($project['extensions']) == 1) { + $extension = $extensions[$project['extensions'][0]]; + $projects[$name]['label'] = $extension->info['name'] . ' (' . $name . ')'; + } + else { + // Make a list of all of the extensions in this project + // that do not depend on any other extension in this + // project. + $candidates = array(); + foreach ($project['extensions'] as $e) { + $has_project_dependency = FALSE; + if (isset($extensions[$e]->info['dependencies']) && is_array($extensions[$e]->info['dependencies'])) { + foreach ($extensions[$e]->info['dependencies'] as $dependent) { + if (in_array($dependent, $project['extensions'])) { + $has_project_dependency = TRUE; + } + } + } + if ($has_project_dependency === FALSE) { + $candidates[] = $extensions[$e]->info['name']; + } + } + // If only one of the modules is a candidate, use its name in the label + if (count($candidates) == 1) { + $projects[$name]['label'] = reset($candidates) . ' (' . $name . ')'; + } + } + } + + drush_log(dt('Obtaining !project project path.', array('!project' => $name)), LogLevel::DEBUG); + $path = _drush_pm_find_common_path($project['type'], $project['extensions']); + // Prevent from setting a reserved path. For example it may happen in a case + // where a module and a theme are declared as part of a same project. + // There's a special case, a project called "sites", this is the reason for + // the second condition here. + if ($path == '.' || (in_array(basename($path), $reserved) && !in_array($name, $reserved))) { + drush_log(dt('Error while trying to find the common path for enabled extensions of project !project. Extensions are: !extensions.', array('!project' => $name, '!extensions' => implode(', ', $project['extensions']))), LogLevel::ERROR); + } + else { + $projects[$name]['path'] = $path; + } + } + + return $projects; +} + +/** + * Helper function to find the common path for a list of extensions in the aim to obtain the project name. + * + * @param $project_type + * Type of project we're trying to find. Valid values: module, theme. + * @param $extensions + * Array of extension names. + */ +function _drush_pm_find_common_path($project_type, $extensions) { + // Select the first path as the candidate to be the common prefix. + $extension = array_pop($extensions); + while (!($path = drupal_get_path($project_type, $extension))) { + drush_log(dt('Unknown path for !extension !type.', array('!extension' => $extension, '!type' => $project_type)), LogLevel::WARNING); + $extension = array_pop($extensions); + } + + // If there's only one extension we are done. Otherwise, we need to find + // the common prefix for all of them. + if (count($extensions) > 0) { + // Iterate over the other projects. + while($extension = array_pop($extensions)) { + $path2 = drupal_get_path($project_type, $extension); + if (!$path2) { + drush_log(dt('Unknown path for !extension !type.', array('!extension' => $extension, '!type' => $project_type)), LogLevel::DEBUG); + continue; + } + // Option 1: same path. + if ($path == $path2) { + continue; + } + // Option 2: $path is a prefix of $path2. + if (strpos($path2, $path) === 0) { + continue; + } + // Option 3: $path2 is a prefix of $path. + if (strpos($path, $path2) === 0) { + $path = $path2; + continue; + } + // Option 4: no one is a prefix of the other. Find the common + // prefix by iteratively strip the rigthtmost piece of $path. + // We will iterate until a prefix is found or path = '.', that on the + // other hand is a condition theorically impossible to reach. + do { + $path = dirname($path); + if (strpos($path2, $path) === 0) { + break; + } + } while ($path != '.'); + } + } + + return $path; +} + +/** + * @} End of "defgroup extensions". + */ + +/** + * Command callback. Show a list of extensions with type and status. + */ +function drush_pm_list() { + // Rebuild the cache if needed (Drupal 9 requirement) + if (drush_drupal_major_version() >= 9) { + drupal_flush_all_caches(); + } + + //--package + $package_filter = array(); + $package = strtolower(drush_get_option('package')); + if (!empty($package)) { + $package_filter = explode(',', $package); + } + if (!empty($package_filter) && (count($package_filter) == 1)) { + drush_hide_output_fields('package'); + } + + //--type + $all_types = array('module', 'theme'); + $type_filter = strtolower(drush_get_option('type')); + if (!empty($type_filter)) { + $type_filter = explode(',', $type_filter); + } + else { + $type_filter = $all_types; + } + + if (count($type_filter) == 1) { + drush_hide_output_fields('type'); + } + foreach ($type_filter as $type) { + if (!in_array($type, $all_types)) { //TODO: this kind of check can be implemented drush-wide + return drush_set_error('DRUSH_PM_INVALID_PROJECT_TYPE', dt('!type is not a valid project type.', array('!type' => $type))); + } + } + + //--status + $all_status = array('enabled', 'disabled', 'not installed'); + $status_filter = strtolower(drush_get_option('status')); + if (!empty($status_filter)) { + $status_filter = explode(',', $status_filter); + } + else { + $status_filter = $all_status; + } + if (count($status_filter) == 1) { + drush_hide_output_fields('status'); + } + + foreach ($status_filter as $status) { + if (!in_array($status, $all_status)) { //TODO: this kind of check can be implemented drush-wide + return drush_set_error('DRUSH_PM_INVALID_PROJECT_STATUS', dt('!status is not a valid project status.', array('!status' => $status))); + } + } + + $result = array(); + $extension_info = drush_get_extensions(FALSE); + uasort($extension_info, '_drush_pm_sort_extensions'); + + $major_version = drush_drupal_major_version(); + foreach ($extension_info as $key => $extension) { + if (!in_array(drush_extension_get_type($extension), $type_filter)) { + unset($extension_info[$key]); + continue; + } + $status = drush_get_extension_status($extension); + if (!in_array($status, $status_filter)) { + unset($extension_info[$key]); + continue; + } + + // Filter out core if --no-core specified. + if (drush_get_option('no-core', FALSE)) { + if ((($major_version >= 8) && ($extension->origin == 'core')) || (($major_version <= 7) && (strpos($extension->info['package'], 'Core') === 0))) { + unset($extension_info[$key]); + continue; + } + } + + // Filter out non-core if --core specified. + if (drush_get_option('core', FALSE)) { + if ((($major_version >= 8) && ($extension->origin != 'core')) || (($major_version <= 7) && (strpos($extension->info['package'], 'Core') !== 0))) { + unset($extension_info[$key]); + continue; + } + } + + // Filter by package. + if (!empty($package_filter)) { + if (!in_array(strtolower($extension->info['package']), $package_filter)) { + unset($extension_info[$key]); + continue; + } + } + + $row['package'] = $extension->info['package']; + $row['project'] = isset($extension->info['project']) ? $extension->info['project'] : ''; + $row['display_name'] = $extension->label; + $row['name'] = $extension->label; + $row['type'] = ucfirst(drush_extension_get_type($extension)); + $row['status'] = ucfirst($status); + $row['path'] = drush_extension_get_path($extension); + // Suppress notice when version is not present. + $row['version'] = @$extension->info['version']; + + $result[$key] = $row; + unset($row); + } + // In Drush-5, we used to return $extension_info here. + return $result; +} + +/** + * Helper function for pm-enable. + */ +function drush_pm_enable_find_project_from_extension($extension) { + $result = drush_pm_lookup_extension_in_cache($extension); + + if (!isset($result)) { + $release_info = drush_get_engine('release_info'); + + // If we can find info on a project that has the same name + // as the requested extension, then we'll call that a match. + $request = pm_parse_request($extension); + if ($release_info->checkProject($request)) { + $result = $extension; + } + } + + return $result; +} + +/** + * Validate callback. Determine the modules and themes that the user would like enabled. + */ +function drush_pm_enable_validate() { + // Rebuild the cache if needed (Drupal 9 requirement) + if (drush_drupal_major_version() >= 9) { + drupal_flush_all_caches(); + } + + $args = pm_parse_arguments(func_get_args()); + + $extension_info = drush_get_extensions(); + + $recheck = TRUE; + $last_download = NULL; + while ($recheck) { + $recheck = FALSE; + + // Classify $args in themes, modules or unknown. + $modules = array(); + $themes = array(); + $download = array(); + drush_pm_classify_extensions($args, $modules, $themes, $extension_info); + $extensions = array_merge($modules, $themes); + $unknown = array_diff($args, $extensions); + + // If there're unknown extensions, try and download projects + // with matching names. + if (!empty($unknown)) { + $found = array(); + foreach ($unknown as $name) { + drush_log(dt('!extension was not found.', array('!extension' => $name)), LogLevel::WARNING); + $project = drush_pm_enable_find_project_from_extension($name); + if (!empty($project)) { + $found[] = $project; + } + } + if (!empty($found)) { + // Prevent from looping if last download failed. + if ($found === $last_download) { + drush_log(dt("Unable to download some or all of the extensions."), LogLevel::WARNING); + break; + } + drush_log(dt("The following projects provide some or all of the extensions not found:\n@list", array('@list' => implode("\n", $found))), LogLevel::OK); + if (drush_get_option('resolve-dependencies')) { + drush_log(dt("They are being downloaded."), LogLevel::OK); + } + if ((drush_get_option('resolve-dependencies')) || (drush_confirm("Would you like to download them?"))) { + $download = $found; + } + } + } + + // Discard already enabled and incompatible extensions. + foreach ($extensions as $name) { + if ($extension_info[$name]->status) { + drush_log(dt('!extension is already enabled.', array('!extension' => $name)), LogLevel::OK); + } + // Check if the extension is compatible with Drupal core and php version. + if ($component = drush_extension_check_incompatibility($extension_info[$name])) { + drush_set_error('DRUSH_PM_ENABLE_MODULE_INCOMPATIBLE', dt('!name is incompatible with the !component version.', array('!name' => $name, '!component' => $component))); + if (drush_extension_get_type($extension_info[$name]) == 'module') { + unset($modules[$name]); + } + else { + unset($themes[$name]); + } + } + } + + if (!empty($modules)) { + // Check module dependencies. + $dependencies = drush_check_module_dependencies($modules, $extension_info); + $unmet_dependencies = array(); + foreach ($dependencies as $module => $info) { + if (!empty($info['unmet-dependencies'])) { + foreach ($info['unmet-dependencies'] as $unmet) { + $unmet_project = (!empty($info['dependencies'][$unmet]['project'])) ? $info['dependencies'][$unmet]['project'] : drush_pm_enable_find_project_from_extension($unmet); + if (!empty($unmet_project)) { + $unmet_dependencies[$module][$unmet_project] = $unmet_project; + } + } + } + } + if (!empty($unmet_dependencies)) { + $msgs = array(); + $unmet_project_list = array(); + foreach ($unmet_dependencies as $module => $unmet_projects) { + $unmet_project_list = array_merge($unmet_project_list, $unmet_projects); + $msgs[] = dt("!module requires !unmet-projects", array('!unmet-projects' => implode(', ', $unmet_projects), '!module' => $module)); + } + $found = array_merge($download, $unmet_project_list); + // Prevent from looping if last download failed. + if ($found === $last_download) { + drush_log(dt("Unable to download some or all of the extensions."), LogLevel::WARNING); + break; + } + drush_log(dt("The following projects have unmet dependencies:\n!list", array('!list' => implode("\n", $msgs))), LogLevel::OK); + if (drush_get_option('resolve-dependencies')) { + drush_log(dt("They are being downloaded."), LogLevel::OK); + } + if (drush_get_option('resolve-dependencies') || drush_confirm(dt("Would you like to download them?"))) { + $download = $found; + } + } + } + + if (!empty($download)) { + // Disable DRUSH_AFFIRMATIVE context temporarily. + $drush_affirmative = drush_get_context('DRUSH_AFFIRMATIVE'); + drush_set_context('DRUSH_AFFIRMATIVE', FALSE); + // Invoke a new process to download dependencies. + $result = drush_invoke_process('@self', 'pm-download', $download, array(), array('interactive' => TRUE)); + // Restore DRUSH_AFFIRMATIVE context. + drush_set_context('DRUSH_AFFIRMATIVE', $drush_affirmative); + // Refresh module cache after downloading the new modules. + if (drush_drupal_major_version() >= 8) { + \Drush\Drupal\ExtensionDiscovery::reset(); + system_list_reset(); + } + $extension_info = drush_get_extensions(); + $last_download = $download; + $recheck = TRUE; + } + } + + if (!empty($modules)) { + $all_dependencies = array(); + $dependencies_ok = TRUE; + foreach ($dependencies as $key => $info) { + if (isset($info['error'])) { + unset($modules[$key]); + $dependencies_ok = drush_set_error($info['error']['code'], $info['error']['message']); + } + elseif (!empty($info['dependencies'])) { + // Make sure we have an assoc array. + $dependencies_list = array_keys($info['dependencies']); + $assoc = array_combine($dependencies_list, $dependencies_list); + $all_dependencies = array_merge($all_dependencies, $assoc); + } + } + if (!$dependencies_ok) { + return FALSE; + } + $modules = array_diff(array_merge($modules, $all_dependencies), drush_module_list()); + // Discard modules which doesn't meet requirements. + require_once DRUSH_DRUPAL_CORE . '/includes/install.inc'; + foreach ($modules as $key => $module) { + // Check to see if the module can be installed/enabled (hook_requirements). + // See @system_modules_submit + if (!drupal_check_module($module)) { + unset($modules[$key]); + drush_set_error('DRUSH_PM_ENABLE_MODULE_UNMEET_REQUIREMENTS', dt('Module !module doesn\'t meet the requirements to be enabled.', array('!module' => $module))); + _drush_log_drupal_messages(); + return FALSE; + } + } + } + + $searchpath = array(); + foreach (array_merge($modules, $themes) as $name) { + $searchpath[] = drush_extension_get_path($extension_info[$name]); + } + // Add all modules that passed validation to the drush + // list of commandfiles (if they have any). This + // will allow these newly-enabled modules to participate + // in the pre-pm_enable and post-pm_enable hooks. + if (!empty($searchpath)) { + _drush_add_commandfiles($searchpath); + } + + drush_set_context('PM_ENABLE_EXTENSION_INFO', $extension_info); + drush_set_context('PM_ENABLE_MODULES', $modules); + drush_set_context('PM_ENABLE_THEMES', $themes); + + return TRUE; +} + +/** + * Command callback. Enable one or more extensions from downloaded projects. + * Note that the modules and themes to be enabled were evaluated during the + * pm-enable validate hook, above. + */ +function drush_pm_enable() { + // Get the data built during the validate phase + $extension_info = drush_get_context('PM_ENABLE_EXTENSION_INFO'); + $modules = drush_get_context('PM_ENABLE_MODULES'); + $themes = drush_get_context('PM_ENABLE_THEMES'); + + // Inform the user which extensions will finally be enabled. + $extensions = array_merge($modules, $themes); + if (empty($extensions)) { + return drush_log(dt('There were no extensions that could be enabled.'), LogLevel::OK); + } + else { + drush_print(dt('The following extensions will be enabled: !extensions', array('!extensions' => implode(', ', $extensions)))); + if(!drush_confirm(dt('Do you really want to continue?'))) { + return drush_user_abort(); + } + } + + // Enable themes. + if (!empty($themes)) { + drush_theme_enable($themes); + } + + // Enable modules and pass dependency validation in form submit. + if (!empty($modules)) { + drush_include_engine('drupal', 'environment'); + drush_module_enable($modules); + } + + // Inform the user of final status. + $result_extensions = drush_get_named_extensions_list($extensions); + $problem_extensions = array(); + $role = drush_role_get_class(); + foreach ($result_extensions as $name => $extension) { + if ($extension->status) { + drush_log(dt('!extension was enabled successfully.', array('!extension' => $name)), LogLevel::OK); + $perms = $role->getModulePerms($name); + if (!empty($perms)) { + drush_print(dt('!extension defines the following permissions: !perms', array('!extension' => $name, '!perms' => implode(', ', $perms)))); + } + } + else { + $problem_extensions[] = $name; + } + } + if (!empty($problem_extensions)) { + return drush_set_error('DRUSH_PM_ENABLE_EXTENSION_ISSUE', dt('There was a problem enabling !extension.', array('!extension' => implode(',', $problem_extensions)))); + } + // Return the list of extensions enabled + return $extensions; +} + +/** + * Command callback. Disable one or more extensions. + */ +function drush_pm_disable() { + $args = pm_parse_arguments(func_get_args()); + drush_include_engine('drupal', 'pm'); + _drush_pm_disable($args); +} + +/** + * Add extensions that match extension_name*. + * + * A helper function for commands that take a space separated list of extension + * names. It will identify extensions that have been passed in with a + * trailing * and add all matching extensions to the array that is returned. + * + * @param $extensions + * An array of extensions, by reference. + * @param $extension_info + * Optional. An array of extension info as returned by drush_get_extensions(). + */ +function _drush_pm_expand_extensions(&$extensions, $extension_info = array()) { + if (empty($extension_info)) { + $extension_info = drush_get_extensions(); + } + foreach ($extensions as $key => $extension) { + if (($wildcard = rtrim($extension, '*')) !== $extension) { + foreach (array_keys($extension_info) as $extension_name) { + if (substr($extension_name, 0, strlen($wildcard)) == $wildcard) { + $extensions[] = $extension_name; + } + } + unset($extensions[$key]); + continue; + } + } +} + +/** + * Command callback. Uninstall one or more modules. + */ +function drush_pm_uninstall() { + $args = pm_parse_arguments(func_get_args()); + drush_include_engine('drupal', 'pm'); + _drush_pm_uninstall($args); +} + +/** + * Command callback. Show available releases for given project(s). + */ +function drush_pm_releases() { + $release_info = drush_get_engine('release_info'); + + // Obtain requests. + $requests = pm_parse_arguments(func_get_args(), FALSE); + if (!$requests) { + $requests = array('drupal'); + } + + // Get installed projects. + if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_FULL) { + $projects = drush_get_projects(); + } + else { + $projects = array(); + } + + // Select the filter to apply based on cli options. + if (drush_get_option('dev', FALSE)) { + $filter = 'dev'; + } + elseif (drush_get_option('all', FALSE)) { + $filter = 'all'; + } + else { + $filter = ''; + } + + $status_url = drush_get_option('source'); + + $output = array(); + foreach ($requests as $request) { + $request = pm_parse_request($request, $status_url, $projects); + $project_name = $request['name']; + $project_release_info = $release_info->get($request); + if ($project_release_info) { + $version = isset($projects[$project_name]) ? $projects[$project_name]['version'] : NULL; + $releases = $project_release_info->filterReleases($filter, $version); + foreach ($releases as $key => $release) { + $output["${project_name}-${key}"] = array( + 'project' => $project_name, + 'version' => $release['version'], + 'date' => gmdate('Y-M-d', $release['date']), + 'status' => implode(', ', $release['release_status']), + ) + $release; + } + } + } + if (empty($output)) { + return drush_log(dt('No valid projects given.'), LogLevel::OK); + } + + return $output; +} + +/** + * Command callback. Show release notes for given project(s). + */ +function drush_pm_releasenotes() { + $release_info = drush_get_engine('release_info'); + + // Obtain requests. + if (!$requests = pm_parse_arguments(func_get_args(), FALSE)) { + $requests = array('drupal'); + } + + // Get installed projects. + if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_FULL) { + $projects = drush_get_projects(); + } + else { + $projects = array(); + } + + $status_url = drush_get_option('source'); + + $output = ''; + foreach($requests as $request) { + $request = pm_parse_request($request, $status_url, $projects); + $project_release_info = $release_info->get($request); + if ($project_release_info) { + $version = empty($request['version']) ? NULL : $request['version']; + $output .= $project_release_info->getReleaseNotes($version); + } + } + return $output; +} + +/** + * Command callback. Refresh update status information. + */ +function drush_pm_refresh() { + $update_status = drush_get_engine('update_status'); + drush_print(dt("Refreshing update status information ...")); + $update_status->refresh(); + drush_print(dt("Done.")); +} + +/** + * Command callback. Execute pm-update. + */ +function drush_pm_update() { + // Call pm-updatecode. updatedb will be called in the post-update process. + $args = pm_parse_arguments(func_get_args(), FALSE); + drush_set_option('check-updatedb', FALSE); + return drush_invoke('pm-updatecode', $args); +} + +/** + * Post-command callback. + * Execute updatedb command after an updatecode - user requested `update`. + */ +function drush_pm_post_pm_update() { + // Use drush_invoke_process to start a subprocess. Cleaner that way. + if (drush_get_context('DRUSH_PM_UPDATED', FALSE) !== FALSE) { + drush_invoke_process('@self', 'updatedb'); + } +} + +/** + * Validate callback for updatecode command. Abort if 'backup' directory exists. + */ +function drush_pm_updatecode_validate() { + $path = drush_get_context('DRUSH_DRUPAL_ROOT') . '/backup'; + if (is_dir($path) && (realpath(drush_get_option('backup-dir', FALSE)) != $path)) { + return drush_set_error('', dt('Backup directory !path found. It\'s a security risk to store backups inside the Drupal tree. Drush now uses by default ~/drush-backups. You need to move !path out of the Drupal tree to proceed. Note: if you know what you\'re doing you can explicitly set --backup-dir to !path and continue.', array('!path' => $path))); + } +} + +/** + * Post-command callback for updatecode. + * + * Execute pm-updatecode-postupdate in a backend process to not conflict with + * old code already in memory. + */ +function drush_pm_post_pm_updatecode() { + // Skip if updatecode was invoked by pm-update. + // This way we avoid being noisy, as updatedb is to be executed. + if (drush_get_option('check-updatedb', TRUE)) { + if (drush_get_context('DRUSH_PM_UPDATED', FALSE)) { + drush_invoke_process('@self', 'pm-updatecode-postupdate'); + } + } +} + +/** + * Command callback. Execute updatecode-postupdate. + */ +function drush_pm_updatecode_postupdate() { + // Clear the cache, since some projects could have moved around. + drush_drupal_cache_clear_all(); + + // Notify of pending database updates. + // Make sure the installation API is available + require_once DRUSH_DRUPAL_CORE . '/includes/install.inc'; + + // Load all .install files. + drupal_load_updates(); + + // @see system_requirements(). + foreach (drush_module_list() as $module) { + $updates = drupal_get_schema_versions($module); + if ($updates !== FALSE) { + $default = drupal_get_installed_schema_version($module); + if (max($updates) > $default) { + drush_log(dt("You have pending database updates. Run `drush updatedb` or visit update.php in your browser."), LogLevel::WARNING); + break; + } + } + } +} + +/** + * Sanitize user provided arguments to several pm commands. + * + * Return an array of arguments off a space and/or comma separated values. + */ +function pm_parse_arguments($args, $dashes_to_underscores = TRUE) { + $arguments = _convert_csv_to_array($args); + foreach ($arguments as $key => $argument) { + $argument = ($dashes_to_underscores) ? strtr($argument, '-', '_') : $argument; + } + return $arguments; +} + +/** + * Decompound a version string and returns major, minor, patch and extra parts. + * + * @see _pm_parse_version_compound() + * @see pm_parse_version() + * + * @param string $version + * A version string like X.Y-Z, X.Y.Z-W or a subset. + * + * @return array + * Array with major, patch and extra keys. + */ +function _pm_parse_version_decompound($version) { + $pattern = '/^(\d+)(?:.(\d+))?(?:\.(x|\d+))?(?:-([a-z0-9\.-]*))?(?:\+(\d+)-dev)?$/'; + + $matches = array(); + preg_match($pattern, $version, $matches); + + $parts = array( + 'major' => '', + 'minor' => '', + 'patch' => '', + 'extra' => '', + 'offset' => '', + ); + if (isset($matches[1])) { + $parts['major'] = $matches[1]; + if (isset($matches[2])) { + if (isset($matches[3]) && $matches[3] != '') { + $parts['minor'] = $matches[2]; + $parts['patch'] = $matches[3]; + } + else { + $parts['patch'] = $matches[2]; + } + } + if (!empty($matches[4])) { + $parts['extra'] = $matches[4]; + } + if (!empty($matches[5])) { + $parts['offset'] = $matches[5]; + } + } + + return $parts; +} + +/** + * Build a version string from an array of major, minor and extra parts. + * + * @see _pm_parse_version_decompound() + * @see pm_parse_version() + * + * @param array $parts + * Array of parts. + * + * @return string + * A Version string. + */ +function _pm_parse_version_compound($parts) { + $project_version = ''; + if ($parts['patch'] != '') { + $project_version = $parts['major']; + if ($parts['minor'] != '') { + $project_version = $project_version . '.' . $parts['minor']; + } + if ($parts['patch'] == 'x') { + $project_version = $project_version . '.x-dev'; + } + else { + $project_version = $project_version . '.' . $parts['patch']; + if ($parts['extra'] != '') { + $project_version = $project_version . '-' . $parts['extra']; + } + } + if ($parts['offset'] != '') { + $project_version = $project_version . '+' . $parts['offset'] . '-dev'; + } + } + + return $project_version; +} + +/** + * Parses a version string and returns its components. + * + * It parses both core and contrib version strings. + * + * Core (semantic versioning): + * - 8.0.0-beta3+252-dev + * - 8.0.0-beta2 + * - 8.0.x-dev + * - 8.1.x + * - 8.0.1 + * - 8 + * + * Core (classic drupal scheme): + * - 7.x-dev + * - 7.x + * - 7.33 + * - 7.34+3-dev + * - 7 + * + * Contrib: + * - 7.x-1.0-beta1+30-dev + * - 7.x-1.0-beta1 + * - 7.x-1.0+30-dev + * - 7.x-1.0 + * - 1.0-beta1 + * - 1.0 + * - 7.x-1.x + * - 7.x-1.x-dev + * - 1.x + * + * TODO: Currently does not support specific version releases of + * contrib modules that use SEMVER release versions, e.g. devel-4.0.0. + * + * @see pm_parse_request() + * + * @param string $version + * A core or project version string. + * + * @param bool $is_core + * Whether this is a core version or a project version. + * + * @return array + * Version string in parts. + * Example for a contrib version (ex: 7.x-3.2-beta1): + * - version : Fully qualified version string. + * - drupal_version : Core compatibility version (ex: 7.x). + * - version_major : Major version (ex: 3). + * - version_minor : Minor version. Not applicable. Always empty. + * - version_patch : Patch version (ex: 2). + * - version_extra : Extra version (ex: beta1). + * - project_version : Project specific part of the version (ex: 3.2-beta1). + * + * Example for a core version (ex: 8.1.2-beta2 or 7.0-beta2): + * - version : Fully qualified version string. + * - drupal_version : Core compatibility version (ex: 8.x). + * - version_major : Major version (ex: 8). + * - version_minor : Minor version (ex: 1). Empty if not a semver. + * - version_patch : Patch version (ex: 2). + * - version_extra : Extra version (ex: beta2). + * - project_version : Same as 'version'. + */ +function pm_parse_version($version, $is_core = FALSE) { + $core_parts = _pm_parse_version_decompound($version); + + // If no major version, we have no version at all. Pick a default. + $drupal_version_default = drush_drupal_major_version(); + // Make 9 act like 8, because release_xml is weird. + if ($drupal_version_default == 9) { + $drupal_version_default = 8; + } + if ($core_parts['major'] == '') { + $core_parts['major'] = ($drupal_version_default) ? $drupal_version_default : drush_get_option('default-major', 7); + } + + if ($is_core) { + $project_version = _pm_parse_version_compound($core_parts); + $version_parts = array( + 'version' => $project_version, + 'drupal_version' => $core_parts['major'] . '.x', + 'project_version' => $project_version, + 'version_major' => $core_parts['major'], + 'version_minor' => $core_parts['minor'], + 'version_patch' => ($core_parts['patch'] == 'x') ? '' : $core_parts['patch'], + 'version_extra' => ($core_parts['patch'] == 'x') ? 'dev' : $core_parts['extra'], + 'version_offset' => $core_parts['offset'], + ); + } + else { + // If something as 7.x-1.0-beta1, the project specific version is + // in $version['extra'] and we need to parse it. + if (strpbrk($core_parts['extra'], '.-')) { + $nocore_parts = _pm_parse_version_decompound($core_parts['extra']); + $nocore_parts['offset'] = $core_parts['offset']; + $project_version = _pm_parse_version_compound($nocore_parts); + $version_parts = array( + 'version' => $core_parts['major'] . '.x-' . $project_version, + 'drupal_version' => $core_parts['major'] . '.x', + 'project_version' => $project_version, + 'version_major' => $nocore_parts['major'], + 'version_minor' => $core_parts['minor'], + 'version_patch' => ($nocore_parts['patch'] == 'x') ? '' : $nocore_parts['patch'], + 'version_extra' => ($nocore_parts['patch'] == 'x') ? 'dev' : $nocore_parts['extra'], + 'version_offset' => $core_parts['offset'], + ); + } + // At this point we have half a version and must decide if this is a drupal major or a project. + else { + // If working on a bootstrapped site, core_parts has the project version. + if ($drupal_version_default) { + $project_version = _pm_parse_version_compound($core_parts); + $version = ($project_version) ? $drupal_version_default . '.x-' . $project_version : ''; + $version_parts = array( + 'version' => $version, + 'drupal_version' => $drupal_version_default . '.x', + 'project_version' => $project_version, + 'version_major' => $core_parts['major'], + 'version_minor' => $core_parts['minor'], + 'version_patch' => ($core_parts['patch'] == 'x') ? '' : $core_parts['patch'], + 'version_extra' => ($core_parts['patch'] == 'x') ? 'dev' : $core_parts['extra'], + 'version_offset' => $core_parts['offset'], + ); + } + // Not working on a bootstrapped site, core_parts is core version. + else { + $version_parts = array( + 'version' => '', + 'drupal_version' => $core_parts['major'] . '.x', + 'project_version' => '', + 'version_major' => '', + 'version_minor' => '', + 'version_patch' => '', + 'version_extra' => '', + 'version_offset' => '', + ); + } + } + } + + return $version_parts; +} + +/** + * Parse out the project name and version and return as a structured array. + * + * @see pm_parse_version() + * + * @param string $request_string + * Project name with optional version. Examples: 'ctools-7.x-1.0-beta1' + * + * @return array + * Array with all parts of the request info. + */ +function pm_parse_request($request_string, $status_url = NULL, &$projects = array()) { + // Split $request_string in project name and version. Note that hyphens (-) + // are permitted in project names (ex: field-conditional-state). + // We use a regex to split the string. The pattern used matches a string + // starting with hyphen, followed by one or more numbers, any of the valid + // symbols in version strings (.x-) and a catchall for the rest of the + // version string. + $parts = preg_split('/-(?:([\d+\.x].*))?$/', $request_string, NULL, PREG_SPLIT_DELIM_CAPTURE); + + if (count($parts) == 1) { + // No version in the request string. + $project = $request_string; + $version = ''; + } + else { + $project = $parts[0]; + $version = $parts[1]; + } + + $is_core = ($project == 'drupal'); + $request = array( + 'name' => $project, + ) + pm_parse_version($version, $is_core); + + // Set the status url if provided or available in project's info file. + if ($status_url) { + $request['status url'] = $status_url; + } + elseif (!empty($projects[$project]['status url'])) { + $request['status url'] = $projects[$project]['status url']; + } + + return $request; +} + +/** + * @defgroup engines Engine types + * @{ + */ + +/** + * Implementation of hook_drush_engine_type_info(). + */ +function pm_drush_engine_type_info() { + return array( + 'package_handler' => array( + 'option' => 'package-handler', + 'description' => 'Determine how to fetch projects from update service.', + 'default' => 'wget', + 'options' => array( + 'cache' => 'Cache release XML and tarballs or git clones. Git clones use git\'s --reference option. Defaults to 1 for downloads, and 0 for git.', + ), + ), + 'release_info' => array( + 'add-options-to-command' => TRUE, + ), + 'update_status' => array( + 'option' => 'update-backend', + 'description' => 'Determine how to fetch update status information.', + 'default' => 'drush', + 'add-options-to-command' => TRUE, + 'options' => array( + 'update-backend' => 'Backend to obtain available updates.', + 'check-disabled' => 'Check for updates of disabled modules and themes.', + 'security-only' => 'Only update modules that have security updates available.', + ), + 'combine-help' => TRUE, + ), + 'version_control' => array( + 'option' => 'version-control', + 'default' => 'backup', + 'description' => 'Integrate with version control systems.', + ), + ); +} + +/** + * Implements hook_drush_engine_ENGINE_TYPE(). + * + * Package handler engine is used by pm-download and + * pm-updatecode commands to determine how to download/checkout + * new projects and acquire updates to projects. + */ +function pm_drush_engine_package_handler() { + return array( + 'wget' => array( + 'description' => 'Download project packages using wget or curl.', + 'options' => array( + 'no-md5' => 'Skip md5 validation of downloads.', + ), + ), + 'git_drupalorg' => array( + 'description' => 'Use git.drupalcode.org to checkout and update projects.', + 'options' => array( + 'gitusername' => 'Your git username as shown on user/[uid]/edit/git. Typically, this is set this in drushrc.php. Omitting this prevents users from pushing changes back to git.drupalcode.org.', + 'gitsubmodule' => 'Use git submodules for checking out new projects. Existing git checkouts are unaffected, and will continue to (not) use submodules regardless of this setting.', + 'gitcheckoutparams' => 'Add options to the `git checkout` command.', + 'gitcloneparams' => 'Add options to the `git clone` command.', + 'gitfetchparams' => 'Add options to the `git fetch` command.', + 'gitpullparams' => 'Add options to the `git pull` command.', + 'gitinfofile' => 'Inject version info into each .info file.', + ), + 'sub-options' => array( + 'gitsubmodule' => array( + 'gitsubmoduleaddparams' => 'Add options to the `git submodule add` command.', + ), + ), + ), + ); +} + +/** + * Implements hook_drush_engine_ENGINE_TYPE(). + * + * Release info engine is used by several pm commands to obtain + * releases info from Drupal's update service or external sources. + */ +function pm_drush_engine_release_info() { + return array( + 'updatexml' => array( + 'description' => 'Drush release info engine for update.drupal.org and compatible services.', + 'options' => array( + 'source' => 'The base URL which provides project release history in XML. Defaults to http://updates.drupal.org/release-history.', + 'dev' => 'Work with development releases solely.', + ), + 'sub-options' => array( + 'cache' => array( + 'cache-duration-releasexml' => 'Expire duration (in seconds) for release XML. Defaults to 86400 (24 hours).', + ), + 'select' => array( + 'all' => 'Shows all available releases instead of a short list of recent releases.', + ), + ), + 'class' => 'Drush\UpdateService\ReleaseInfo', + ), + ); +} + +/** + * Implements hook_drush_engine_ENGINE_TYPE(). + * + * Update status engine is used to check available updates for + * the projects in a Drupal site. + */ +function pm_drush_engine_update_status() { + return array( + 'drupal' => array( + 'description' => 'Check available updates with update.module.', + 'drupal dependencies' => array('update'), + 'class' => 'Drush\UpdateService\StatusInfoDrupal', + ), + 'drush' => array( + 'description' => 'Check available updates without update.module.', + 'class' => 'Drush\UpdateService\StatusInfoDrush', + ), + ); +} + +/** + * Implements hook_drush_engine_ENGINE_TYPE(). + * + * Integration with VCS in order to easily commit your changes to projects. + */ +function pm_drush_engine_version_control() { + return array( + 'backup' => array( + 'description' => 'Backup all project files before updates.', + 'options' => array( + 'no-backup' => 'Do not perform backups. WARNING: Will result in non-core files/dirs being deleted (e.g. .git)', + 'backup-dir' => 'Specify a directory to backup projects into. Defaults to drush-backups within the home directory of the user running the command. It is forbidden to specify a directory inside your drupal root.', + ), + ), + 'bzr' => array( + 'signature' => 'bzr root %s', + 'description' => 'Quickly add/remove/commit your project changes to Bazaar.', + 'options' => array( + 'bzrsync' => 'Automatically add new files to the Bazaar repository and remove deleted files. Caution.', + 'bzrcommit' => 'Automatically commit changes to Bazaar repository. You must also use the --bzrsync option.', + ), + 'sub-options' => array( + 'bzrcommit' => array( + 'bzrmessage' => 'Override default commit message which is: Drush automatic commit. Project Command: ', + ), + ), + 'examples' => array( + 'drush dl cck --version-control=bzr --bzrsync --bzrcommit' => 'Download the cck project and then add it and commit it to Bazaar.' + ), + ), + 'svn' => array( + 'signature' => 'svn info %s', + 'description' => 'Quickly add/remove/commit your project changes to Subversion.', + 'options' => array( + 'svnsync' => 'Automatically add new files to the SVN repository and remove deleted files. Caution.', + 'svncommit' => 'Automatically commit changes to SVN repository. You must also using the --svnsync option.', + 'svnstatusparams' => "Add options to the 'svn status' command", + 'svnaddparams' => 'Add options to the `svn add` command', + 'svnremoveparams' => 'Add options to the `svn remove` command', + 'svnrevertparams' => 'Add options to the `svn revert` command', + 'svncommitparams' => 'Add options to the `svn commit` command', + ), + 'sub-options' => array( + 'svncommit' => array( + 'svnmessage' => 'Override default commit message which is: Drush automatic commit: ', + ), + ), + 'examples' => array( + 'drush [command] cck --svncommitparams=\"--username joe\"' => 'Commit changes as the user \'joe\' (Quotes are required).' + ), + ), + ); +} + +/** + * @} End of "Engine types". + */ + +/** + * Interface for version control systems. + * We use a simple object layer because we conceivably need more than one + * loaded at a time. + */ +interface drush_version_control { + function pre_update(&$project); + function rollback($project); + function post_update($project); + function post_download($project); + static function reserved_files(); +} + +/** + * A simple factory function that tests for version control systems, in a user + * specified order, and returns the one that appears to be appropriate for a + * specific directory. + */ +function drush_pm_include_version_control($directory = '.') { + $engine_info = drush_get_engines('version_control'); + $version_controls = drush_get_option('version-control', FALSE); + // If no version control was given, use a list of defaults. + if (!$version_controls) { + // Backup engine is the last option. + $version_controls = array_reverse(array_keys($engine_info['engines'])); + } + else { + $version_controls = array($version_controls); + } + + // Find the first valid engine in the list, checking signatures if needed. + $engine = FALSE; + while (!$engine && count($version_controls)) { + $version_control = array_shift($version_controls); + if (isset($engine_info['engines'][$version_control])) { + if (!empty($engine_info['engines'][$version_control]['signature'])) { + drush_log(dt('Verifying signature for !vcs version control engine.', array('!vcs' => $version_control)), LogLevel::DEBUG); + if (drush_shell_exec($engine_info['engines'][$version_control]['signature'], $directory)) { + $engine = $version_control; + } + } + else { + $engine = $version_control; + } + } + } + if (!$engine) { + return drush_set_error('DRUSH_PM_NO_VERSION_CONTROL', dt('No valid version control or backup engine found (the --version-control option was set to "!version-control").', array('!version-control' => $version_control))); + } + + $instance = drush_include_engine('version_control', $engine); + return $instance; +} + +/** + * Update the locked status of all of the candidate projects + * to be updated. + * + * @param array &$projects + * The projects array from pm_updatecode. $project['locked'] will + * be set for every file where a persistent lockfile can be found. + * The 'lock' and 'unlock' operations are processed first. + * @param array $projects_to_lock + * A list of projects to create peristent lock files for + * @param array $projects_to_unlock + * A list of projects to clear the persistent lock on + * @param string $lock_message + * The reason the project is being locked; stored in the lockfile. + * + * @return array + * A list of projects that are locked. + */ +function drush_pm_update_lock(&$projects, $projects_to_lock, $projects_to_unlock, $lock_message = NULL) { + $locked_result = array(); + + // Warn about ambiguous lock / unlock values + if ($projects_to_lock == array('1')) { + $projects_to_lock = array(); + drush_log(dt('Ignoring --lock with no value.'), LogLevel::WARNING); + } + if ($projects_to_unlock == array('1')) { + $projects_to_unlock = array(); + drush_log(dt('Ignoring --unlock with no value.'), LogLevel::WARNING); + } + + // Log if we are going to lock or unlock anything + if (!empty($projects_to_unlock)) { + drush_log(dt('Unlocking !projects', array('!projects' => implode(',', $projects_to_unlock))), LogLevel::OK); + } + if (!empty($projects_to_lock)) { + drush_log(dt('Locking !projects', array('!projects' => implode(',', $projects_to_lock))), LogLevel::OK); + } + + $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT'); + foreach ($projects as $name => $project) { + $message = NULL; + if (isset($project['path'])) { + if ($name == 'drupal') { + $lockfile = $drupal_root . '/.drush-lock-update'; + } + else { + $lockfile = $drupal_root . '/' . $project['path'] . '/.drush-lock-update'; + } + + // Remove the lock file if the --unlock option was specified + if (((in_array($name, $projects_to_unlock)) || (in_array('all', $projects_to_unlock))) && (file_exists($lockfile))) { + drush_op('unlink', $lockfile); + } + + // Create the lock file if the --lock option was specified + if ((in_array($name, $projects_to_lock)) || (in_array('all', $projects_to_lock))) { + drush_op('file_put_contents', $lockfile, $lock_message != NULL ? $lock_message : "Locked via drush."); + // Note that the project is locked. This will work even if we are simulated, + // or if we get permission denied from the file_put_contents. + // If the lock is -not- simulated or transient, then the lock message will be + // read from the lock file below. + $message = drush_get_context('DRUSH_SIMULATE') ? 'Simulated lock.' : 'Transient lock.'; + } + + // If the persistent lock file exists, then mark the project as locked. + if (file_exists($lockfile)) { + $message = trim(file_get_contents($lockfile)); + } + } + + // If there is a message set, then mark the project as locked. + if (isset($message)) { + $projects[$name]['locked'] = !empty($message) ? $message : "Locked."; + $locked_result[$name] = $project; + } + } + + return $locked_result; +} + +/** + * Returns the path to the extensions cache file. + */ +function _drush_pm_extension_cache_file() { + return drush_get_context('DRUSH_PER_USER_CONFIGURATION') . "/drush-extension-cache.inc"; +} + +/** + * Load the extensions cache. + */ +function _drush_pm_get_extension_cache() { + $extension_cache = array(); + $cache_file = _drush_pm_extension_cache_file(); + + if (file_exists($cache_file)) { + include $cache_file; + } + if (!array_key_exists('extension-map', $extension_cache)) { + $extension_cache['extension-map'] = array(); + } + return $extension_cache; +} + +/** + * Lookup an extension in the extensions cache. + */ +function drush_pm_lookup_extension_in_cache($extension) { + $result = NULL; + $extension_cache = _drush_pm_get_extension_cache(); + if (!empty($extension_cache) && array_key_exists($extension, $extension_cache)) { + $result = $extension_cache[$extension]; + } + return $result; +} + +/** + * Persists extensions cache. + * + * #TODO# not implemented. + */ +function drush_pm_put_extension_cache($extension_cache) { +} + +/** + * Store extensions founds within a project in extensions cache. + */ +function drush_pm_cache_project_extensions($project, $found) { + $extension_cache = _drush_pm_get_extension_cache(); + foreach($found as $extension) { + // Simple cache does not handle conflicts + // We could keep an array of projects, and count + // how many times each one has been seen... + $extension_cache[$extension] = $project['name']; + } + drush_pm_put_extension_cache($extension_cache); +} + +/** + * Print out all extensions (modules/themes/profiles) found in specified project. + * + * Find .info.yml files in the project path and identify modules, themes and + * profiles. It handles two kind of projects: drupal core/profiles and + * modules/themes. + * It does nothing with theme engine projects. + */ +function drush_pm_extensions_in_project($project) { + // Mask for drush_scan_directory, to match .info.yml files. + $mask = $project['drupal_version'][0] >= 8 ? '/(.*)\.info\.yml$/' : '/(.*)\.info$/'; + + // Mask for drush_scan_directory, to avoid tests directories. + $nomask = array('.', '..', 'CVS', 'tests'); + + // Drupal core and profiles can contain modules, themes and profiles. + if (in_array($project['project_type'], array('core', 'profile'))) { + $found = array('profile' => array(), 'theme' => array(), 'module' => array()); + // Find all of the .info files + foreach (drush_scan_directory($project['full_project_path'], $mask, $nomask) as $filename => $info) { + // Extract extension name from filename. + $matches = array(); + preg_match($mask, $info->basename, $matches); + $name = $matches[1]; + + // Find the project type corresponding the .info file. + // (Only drupal >=7.x has .info for .profile) + $base = dirname($filename) . '/' . $name; + if (is_file($base . '.module')) { + $found['module'][] = $name; + } + else if (is_file($base . '.profile')) { + $found['profile'][] = $name; + } + else { + $found['theme'][] = $name; + } + } + // Special case: find profiles for drupal < 7.x (no .info) + if ($project['drupal_version'][0] < 7) { + foreach (drush_find_profiles($project['full_project_path']) as $filename => $info) { + $found['profile'][] = $info->name; + } + } + // Log results. + $msg = "Project !project contains:\n"; + $args = array('!project' => $project['name']); + foreach (array_keys($found) as $type) { + if ($count = count($found[$type])) { + $msg .= " - !count_$type !type_$type: !found_$type\n"; + $args += array("!count_$type" => $count, "!type_$type" => $type, "!found_$type" => implode(', ', $found[$type])); + if ($count > 1) { + $args["!type_$type"] = $type.'s'; + } + } + } + drush_log(dt($msg, $args), LogLevel::SUCCESS); + drush_print_pipe(call_user_func_array('array_merge', array_values($found))); + } + // Modules and themes can only contain other extensions of the same type. + elseif (in_array($project['project_type'], array('module', 'theme'))) { + $found = array(); + foreach (drush_scan_directory($project['full_project_path'], $mask, $nomask) as $filename => $info) { + // Extract extension name from filename. + $matches = array(); + preg_match($mask, $info->basename, $matches); + $found[] = $matches[1]; + } + // If there is only one module / theme in the project, only print out + // the message if is different than the project name. + if (count($found) == 1) { + if ($found[0] != $project['name']) { + $msg = "Project !project contains a !type named !found."; + } + } + // If there are multiple modules or themes in the project, list them all. + else { + $msg = "Project !project contains !count !types: !found."; + } + if (isset($msg)) { + drush_print(dt($msg, array('!project' => $project['name'], '!count' => count($found), '!type' => $project['project_type'], '!found' => implode(', ', $found)))); + } + drush_print_pipe($found); + // Cache results. + drush_pm_cache_project_extensions($project, $found); + } +} + +/** + * Return an array of empty directories. + * + * Walk a directory and return an array of subdirectories that are empty. Will + * return the given directory if it's empty. + * If a list of items to exclude is provided, subdirectories will be condidered + * empty even if they include any of the items in the list. + * + * @param string $dir + * Path to the directory to work in. + * @param array $exclude + * Array of files or directory to exclude in the check. + * + * @return array + * A list of directory paths that are empty. A directory is deemed to be empty + * if it only contains excluded files or directories. + */ +function drush_find_empty_directories($dir, $exclude = array()) { + // Skip files. + if (!is_dir($dir)) { + return array(); + } + $to_exclude = array_merge(array('.', '..'), $exclude); + $empty_dirs = array(); + $dir_is_empty = TRUE; + foreach (scandir($dir) as $file) { + // Skip excluded directories. + if (in_array($file, $to_exclude)) { + continue; + } + // Recurse into sub-directories to find potentially empty ones. + $subdir = $dir . '/' . $file; + $empty_dirs += drush_find_empty_directories($subdir, $exclude); + // $empty_dir will not contain $subdir, if it is a file or if the + // sub-directory is not empty. $subdir is only set if it is empty. + if (!isset($empty_dirs[$subdir])) { + $dir_is_empty = FALSE; + } + } + + if ($dir_is_empty) { + $empty_dirs[$dir] = $dir; + } + return $empty_dirs; +} + +/** + * Inject metadata into all .info files for a given project. + * + * @param string $project_dir + * The full path to the root directory of the project to operate on. + * @param string $project_name + * The project machine name (AKA shortname). + * @param string $version + * The version string to inject into the .info file(s). + * @param int $datestamp + * The datestamp of the last commit. + * + * @return boolean + * TRUE on success, FALSE on any failures appending data to .info files. + */ +function drush_pm_inject_info_file_metadata($project_dir, $project_name, $version, $datestamp) { + // `drush_drupal_major_version()` cannot be used here because this may be running + // outside of a Drupal context. + $regex = '/((\d*)\.\d*\.[\dx]*-?.*)|([89]\.x-\d*\.[\dx]*.*)/'; + $yaml_format = preg_match($regex, $version); + $pattern = preg_quote($yaml_format ? '.info.yml' : '.info'); + $info_files = drush_scan_directory($project_dir, '/.*' . $pattern . '$/'); + if (!empty($info_files)) { + // Construct the string of metadata to append to all the .info files. + if ($yaml_format) { + $info = _drush_pm_generate_info_yaml_metadata($version, $project_name, $datestamp); + } + else { + $info = _drush_pm_generate_info_ini_metadata($version, $project_name, $datestamp); + } + foreach ($info_files as $info_file) { + if (!drush_file_append_data($info_file->filename, $info)) { + return FALSE; + } + } + } + return TRUE; +} + +/** + * Generate version information for `.info` files in ini format. + * + * Taken with some modifications from: + * http://drupalcode.org/project/drupalorg.git/blob/refs/heads/6.x-3.x:/drupalorg_project/plugins/release_packager/DrupalorgProjectPackageRelease.class.php#l192 + */ +function _drush_pm_generate_info_ini_metadata($version, $project_name, $datestamp) { + $matches = array(); + $extra = ''; + if (preg_match('/^((\d+)\.x)-.*/', $version, $matches) && $matches[2] >= 6) { + $extra .= "\ncore = \"$matches[1]\""; + } + if (!drush_get_option('no-gitprojectinfo', FALSE)) { + $extra = "\nproject = \"$project_name\""; + } + $date = date('Y-m-d', $datestamp); + $info = <<= 6) { + $extra .= "\ncore: '$matches[1]'"; + } + if (!drush_get_option('no-gitprojectinfo', FALSE)) { + $extra = "\nproject: '$project_name'"; + } + $date = date('Y-m-d', $datestamp); + $info = << $status))); + } + } +} + +/** + * Implementation of drush_hook_COMMAND(). + */ +function drush_pm_projectinfo() { + // Get specific requests. + $requests = pm_parse_arguments(func_get_args(), FALSE); + + // Get installed extensions and projects. + $extensions = drush_get_extensions(); + $projects = drush_get_projects($extensions); + + // If user did not specify any projects, return them all + if (empty($requests)) { + $result = $projects; + } + else { + $result = array(); + foreach ($requests as $name) { + if (array_key_exists($name, $projects)) { + $result[$name] = $projects[$name]; + } + else { + drush_log(dt('!project was not found.', array('!project' => $name)), LogLevel::WARNING); + continue; + } + } + } + + // Find the Drush commands that belong with each project. + foreach ($result as $name => $project) { + $drush_commands = pm_projectinfo_commands_in_project($project); + if (!empty($drush_commands)) { + $result[$name]['drush'] = $drush_commands; + } + } + + // If user specified --drush, remove projects with no drush extensions + if (drush_get_option('drush')) { + foreach ($result as $name => $project) { + if (!array_key_exists('drush', $project)) { + unset($result[$name]); + } + } + } + + // If user specified --status=1|0, remove projects with a distinct status. + if (($status = drush_get_option('status', FALSE)) !== FALSE) { + $status_code = ($status == 'enabled') ? 1 : 0; + foreach ($result as $name => $project) { + if ($project['status'] != $status_code) { + unset($result[$name]); + } + } + } + + return $result; +} + +function pm_projectinfo_commands_in_project($project) { + $drush_commands = array(); + if (array_key_exists('path', $project)) { + $commands = drush_get_commands(); + foreach ($commands as $commandname => $command) { + if (!array_key_exists("is_alias", $command) && ($command['path'] == $project['path'])) { + $drush_commands[] = $commandname; + } + } + } + return $drush_commands; +} + diff --git a/vendor/drush/drush/commands/pm/updatecode.pm.inc b/vendor/drush/drush/commands/pm/updatecode.pm.inc new file mode 100644 index 0000000000..a89fab7987 --- /dev/null +++ b/vendor/drush/drush/commands/pm/updatecode.pm.inc @@ -0,0 +1,409 @@ + FALSE, + ); + $values = drush_invoke_process("@self", 'pm-updatestatus', func_get_args(), $updatestatus_options, $backend_options); + if (!is_array($values) || $values['error_status']) { + return drush_set_error('pm-updatestatus failed.'); + } + $last = $update_status->lastCheck(); + drush_print(dt('Update information last refreshed: ') . ($last ? drush_format_date($last) : dt('Never'))); + drush_print($values['output']); + + $update_info = $values['object']; + + // Prevent update of core if --no-core was specified. + if (isset($update_info['drupal']) && drush_get_option('no-core', FALSE)) { + unset($update_info['drupal']); + drush_print(dt('Skipping core update (--no-core specified).')); + } + + // Remove locked and non-updateable projects. + foreach ($update_info as $name => $project) { + if ((isset($project['locked']) && !isset($requests[$name])) || (!isset($project['updateable']) || !$project['updateable'])) { + unset($update_info[$name]); + } + } + + // Do no updates in simulated mode. + if (drush_get_context('DRUSH_SIMULATE')) { + return drush_log(dt('No action taken in simulated mode.'), LogLevel::OK); + return TRUE; + } + + $tmpfile = drush_tempnam('pm-updatecode.'); + + $core_update_available = FALSE; + if (isset($update_info['drupal'])) { + $drupal_project = $update_info['drupal']; + unset($update_info['drupal']); + + // At present we need to update drupal core after non-core projects + // are updated. + if (empty($update_info)) { + return _pm_update_core($drupal_project, $tmpfile); + } + // If there are modules other than drupal core enabled, then update them + // first. + else { + $core_update_available = TRUE; + if ($drupal_project['status'] == DRUSH_UPDATESTATUS_NOT_SECURE) { + drush_print(dt("NOTE: A security update for the Drupal core is available.")); + } + else { + drush_print(dt("NOTE: A code update for the Drupal core is available.")); + } + drush_print(dt("Drupal core will be updated after all of the non-core projects are updated.\n")); + } + } + + // If there are no releases to update, then print a final + // exit message. + if (empty($update_info)) { + if (drush_get_option('security-only')) { + return drush_log(dt('No security updates available.'), LogLevel::OK); + } + else { + return drush_log(dt('No code updates available.'), LogLevel::OK); + } + } + + // Offer to update to the identified releases. + if (!pm_update_packages($update_info, $tmpfile)) { + return FALSE; + } + + // After projects are updated we can update core. + if ($core_update_available) { + drush_print(); + return _pm_update_core($drupal_project, $tmpfile); + } +} + +/** + * Update drupal core, following interactive confirmation from the user. + * + * @param $project + * The drupal project information from the drupal.org update service, + * copied from $update_info['drupal']. @see drush_pm_updatecode. + * + * @return bool + * Success or failure. An error message will be logged. + */ +function _pm_update_core(&$project, $tmpfile) { + $release_info = drush_get_engine('release_info'); + + drush_print(dt('Code updates will be made to drupal core.')); + drush_print(dt("WARNING: Updating core will discard any modifications made to Drupal core files, most noteworthy among these are .htaccess and robots.txt. If you have made any modifications to these files, please back them up before updating so that you can re-create your modifications in the updated version of the file.")); + drush_print(dt("Note: Updating core can potentially break your site. It is NOT recommended to update production sites without prior testing.")); + drush_print(); + if (drush_get_option('notes', FALSE)) { + drush_print('Obtaining release notes for above projects...'); + #TODO# Build the $request array from info in $project. + $request = pm_parse_request('drupal'); + $release_info->get($request)->getReleaseNotes(NULL, TRUE, $tmpfile); + } + if(!drush_confirm(dt('Do you really want to continue?'))) { + drush_print(dt('Rolling back all changes. Run again with --no-core to update modules only.')); + return drush_user_abort(); + } + + $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT'); + + // We need write permission on $drupal_root. + if (!is_writable($drupal_root)) { + return drush_set_error('DRUSH_PATH_NO_WRITABLE', dt('Drupal root path is not writable.')); + } + + // Create a directory 'core' if it does not already exist. + $project['path'] = 'drupal-' . $project['candidate_version']; + $project['full_project_path'] = $drupal_root . '/' . $project['path']; + if (!is_dir($project['full_project_path'])) { + drush_mkdir($project['full_project_path']); + } + + // Create a list of directories to exclude from the update process. + // On Drupal >=8 skip also directories in the document root. + if (drush_drupal_major_version() >= 8) { + $skip_list = array('sites', $project['path'], 'modules', 'profiles', 'themes'); + } + else { + $skip_list = array('sites', $project['path']); + } + // Add non-writable directories: we can't move them around. + // We will also use $items_to_test later for $version_control check. + $items_to_test = drush_scan_directory($drupal_root, '/.*/', array_merge(array('.', '..'), $skip_list), 0, FALSE, 'basename', 0, TRUE); + foreach (array_keys($items_to_test) as $item) { + if (is_dir($item) && !is_writable($item)) { + $skip_list[] = $item; + unset($items_to_test[$item]); + } + elseif (is_link($item)) { + $skip_list[] = $item; + unset($items_to_test[$item]); + } + } + $project['skip_list'] = $skip_list; + + // Move all files and folders in $drupal_root to the new 'core' directory + // except for the items in the skip list + _pm_update_move_files($drupal_root, $project['full_project_path'], $project['skip_list']); + + // Set a context variable to indicate that rollback should reverse + // the _pm_update_move_files above. + drush_set_context('DRUSH_PM_DRUPAL_CORE', $project); + + if (!$version_control = drush_pm_include_version_control($project['full_project_path'])) { + return FALSE; + } + + // Check we have a version control system, and it clears its pre-flight. + if (!$version_control->pre_update($project, $items_to_test)) { + return FALSE; + } + + // Update core. + if (pm_update_project($project, $version_control) === FALSE) { + return FALSE; + } + + // Take the updated files in the 'core' directory that have been updated, + // and move all except for the items in the skip list back to + // the drupal root. + _pm_update_move_files($project['full_project_path'], $drupal_root, $project['skip_list']); + drush_delete_dir($project['full_project_path']); + $project['full_project_path'] = $drupal_root; + + // If there is a backup target, then find items + // in the backup target that do not exist at the + // drupal root. These are to be moved back. + if (array_key_exists('backup_target', $project)) { + _pm_update_move_files($project['backup_target'], $drupal_root, $project['skip_list'], FALSE); + _pm_update_move_files($project['backup_target'] . '/profiles', $drupal_root . '/profiles', array('default'), FALSE); + } + + pm_update_finish($project, $version_control); + + return TRUE; +} + +/** + * Move some files from one location to another. + */ +function _pm_update_move_files($src_dir, $dest_dir, $skip_list, $remove_conflicts = TRUE) { + $items_to_move = drush_scan_directory($src_dir, '/.*/', array_merge(array('.', '..'), $skip_list), 0, FALSE, 'filename', 0, TRUE); + foreach ($items_to_move as $filename => $info) { + if ($remove_conflicts) { + drush_delete_dir($dest_dir . '/' . basename($filename)); + } + if (!file_exists($dest_dir . '/' . basename($filename))) { + $move_result = drush_move_dir($filename, $dest_dir . '/' . basename($filename)); + } + } + return TRUE; +} + +/** + * Update projects according to an array of releases and print the release notes + * for each project, following interactive confirmation from the user. + * + * @param $update_info + * An array of projects from the drupal.org update service, with an additional + * array key candidate_version that specifies the version to be installed. + */ +function pm_update_packages($update_info, $tmpfile) { + $release_info = drush_get_engine('release_info'); + + $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT'); + + $print = ''; + $status = array(); + foreach($update_info as $project) { + $print .= $project['title'] . " [" . $project['name'] . '-' . $project['candidate_version'] . "], "; + $status[$project['status']] = $project['status']; + } + // We print the list of the projects that need to be updated. + if (isset($status[DRUSH_UPDATESTATUS_NOT_SECURE])) { + if (isset($status[DRUSH_UPDATESTATUS_NOT_CURRENT])) { + $title = (dt('Security and code updates will be made to the following projects:')); + } + else { + $title = (dt('Security updates will be made to the following projects:')); + } + } + else { + $title = (dt('Code updates will be made to the following projects:')); + } + $print = "$title " . (substr($print, 0, strlen($print)-2)); + drush_print($print); + file_put_contents($tmpfile, "\n\n$print\n\n", FILE_APPEND); + + // Print the release notes for projects to be updated. + if (drush_get_option('notes', FALSE)) { + drush_print('Obtaining release notes for above projects...'); + #TODO# Build the $request array from info in $project. + foreach (array_keys($update_info) as $project_name) { + $request = pm_parse_request($project_name); + $release_info->get($request)->getReleaseNotes(NULL, TRUE, $tmpfile); + } + } + + // We print some warnings before the user confirms the update. + drush_print(); + if (drush_get_option('no-backup', FALSE)) { + drush_print(dt("Note: You have selected to not store backups.")); + } + else { + drush_print(dt("Note: A backup of your project will be stored to backups directory if it is not managed by a supported version control system.")); + drush_print(dt('Note: If you have made any modifications to any file that belongs to one of these projects, you will have to migrate those modifications after updating.')); + } + if(!drush_confirm(dt('Do you really want to continue with the update process?'))) { + return drush_user_abort(); + } + + // Now we start the actual updating. + foreach($update_info as $project) { + if (empty($project['path'])) { + return drush_set_error('DRUSH_PM_UPDATING_NO_PROJECT_PATH', dt('The !project project path is not available, perhaps the !type is enabled but has been deleted from disk.', array('!project' => $project['name'], '!type' => $project['project_type']))); + } + drush_log(dt('Starting to update !project code at !dir...', array('!project' => $project['title'], '!dir' => $project['path']))); + + // Define and check the full path to project directory and base (parent) directory. + $project['full_project_path'] = $drupal_root . '/' . $project['path']; + if (stripos($project['path'], $project['project_type']) === FALSE || !is_dir($project['full_project_path'])) { + return drush_set_error('DRUSH_PM_UPDATING_PATH_NOT_FOUND', dt('The !project directory could not be found within the !types directory at !full_project_path, perhaps the project is enabled but has been deleted from disk.', array('!project' => $project['name'], '!type' => $project['project_type'], '!full_project_path' => $project['full_project_path']))); + } + if (!$version_control = drush_pm_include_version_control($project['full_project_path'])) { + return FALSE; + } + + // Check we have a version control system, and it clears its pre-flight. + if (!$version_control->pre_update($project)) { + return FALSE; + } + + // Run update on one project. + if (pm_update_project($project, $version_control) === FALSE) { + return FALSE; + } + pm_update_finish($project, $version_control); + } + + return TRUE; +} + +/** + * Update one project -- a module, theme or Drupal core. + * + * @param $project + * The project to upgrade. $project['full_project_path'] must be set + * to the location where this project is stored. + * @return bool + * Success or failure. An error message will be logged. + */ +function pm_update_project($project, $version_control) { + // 1. If the version control engine is a proper vcs we need to remove project + // files in order to not have orphan files after update. + // 2. If the package-handler is cvs or git, it will remove upstream removed + // files and no orphans will exist after update. + // So, we must remove all files previous update if the directory is not a + // working copy of cvs or git but we don't need to remove them if the version + // control engine is backup, as it did already move the project out to the + // backup directory. + if (($version_control->engine != 'backup') && (drush_get_option('package-handler', 'wget') == 'wget')) { + // Find and unlink all files but the ones in the vcs control directories. + $skip_list = array('.', '..'); + $skip_list = array_merge($skip_list, drush_version_control_reserved_files()); + drush_scan_directory($project['full_project_path'], '/.*/', $skip_list, 'unlink', TRUE, 'filename', 0, TRUE); + } + + // Add the project to a context so we can roll back if needed. + $updated = drush_get_context('DRUSH_PM_UPDATED'); + $updated[] = $project; + drush_set_context('DRUSH_PM_UPDATED', $updated); + + if (!package_handler_update_project($project, $project['releases'][$project['candidate_version']])) { + return drush_set_error('DRUSH_PM_UPDATING_FAILED', dt('Updating project !project failed. Attempting to roll back to previously installed version.', array('!project' => $project['name']))); + } + + // If the version control engine is a proper vcs we also need to remove + // orphan directories. + if (($version_control->engine != 'backup') && (drush_get_option('package-handler', 'wget') == 'wget')) { + $files = drush_find_empty_directories($project['full_project_path'], $version_control->reserved_files()); + array_map('drush_delete_dir', $files); + } + + return TRUE; +} + +/** + * Run the post-update hooks after updatecode is finished for one project. + */ +function pm_update_finish($project, $version_control) { + drush_print(dt('Project !project was updated successfully. Installed version is now !version.', array('!project' => $project['name'], '!version' => $project['candidate_version']))); + drush_command_invoke_all('pm_post_update', $project['name'], $project['releases'][$project['candidate_version']], $project); + $version_control->post_update($project); +} + +/** + * Rollback the update process. + */ +function drush_pm_updatecode_rollback() { + $projects = array_reverse(drush_get_context('DRUSH_PM_UPDATED', array())); + foreach($projects as $project) { + drush_log(dt('Rolling back update of !project code ...', array('!project' => $project['title']))); + + // Check we have a version control system, and it clears it's pre-flight. + if (!$version_control = drush_pm_include_version_control($project['path'])) { + return FALSE; + } + $version_control->rollback($project); + } + + // Post rollback, we will do additional repair if the project is drupal core. + $drupal_core = drush_get_context('DRUSH_PM_DRUPAL_CORE', FALSE); + if ($drupal_core) { + $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT'); + _pm_update_move_files($drupal_core['full_project_path'], $drupal_root, $drupal_core['skip_list']); + drush_delete_dir($drupal_core['full_project_path']); + } +} + diff --git a/vendor/drush/drush/commands/pm/updatestatus.pm.inc b/vendor/drush/drush/commands/pm/updatestatus.pm.inc new file mode 100644 index 0000000000..535d4dc141 --- /dev/null +++ b/vendor/drush/drush/commands/pm/updatestatus.pm.inc @@ -0,0 +1,246 @@ +engine == 'drupal') ? NULL : FALSE; + $check_disabled = drush_get_option('check-disabled', $check_disabled_default); + + $update_info = $update_status->getStatus($projects, $check_disabled); + + foreach ($extensions as $name => $extension) { + // Add an item to $update_info for each enabled extension which was obtained + // from cvs or git and its project is unknown (because of cvs_deploy or + // git_deploy is not enabled). + if (!isset($extension->info['project'])) { + if ((isset($extension->vcs)) && ($extension->status)) { + $update_info[$name] = array( + 'name' => $name, + 'label' => $extension->label, + 'existing_version' => 'Unknown', + 'status' => DRUSH_UPDATESTATUS_PROJECT_NOT_PACKAGED, + 'status_msg' => dt('Project was not packaged by drupal.org but obtained from !vcs. You need to enable !vcs_deploy module', array('!vcs' => $extension->vcs)), + ); + // The user may have requested to update a project matching this + // extension. If it was by coincidence or error we don't mind as we've + // already added an item to $update_info. Just clean up $requests. + if (isset($requests[$name])) { + unset($requests[$name]); + } + } + } + // Additionally if the extension name is distinct to the project name and + // the user asked to update the extension, fix the request. + elseif ((isset($requests[$name])) && ($name != $extension->info['project'])) { + $requests[$extension->info['project']] = $requests[$name]; + unset($requests[$name]); + } + } + // If specific project updates were requested then remove releases for all + // others. + $requested = func_get_args(); + if (!empty($requested)) { + foreach ($update_info as $name => $project) { + if (!isset($requests[$name])) { + unset($update_info[$name]); + } + } + } + // Add an item to $update_info for each request not present in $update_info. + foreach ($requests as $name => $request) { + if (!isset($update_info[$name])) { + // Disabled projects. + if ((isset($projects[$name])) && ($projects[$name]['status'] == 0)) { + $update_info[$name] = array( + 'name' => $name, + 'label' => $projects[$name]['label'], + 'existing_version' => $projects[$name]['version'], + 'status' => DRUSH_UPDATESTATUS_REQUESTED_PROJECT_NOT_UPDATEABLE, + ); + unset($requests[$name]); + } + // At this point we are unable to find matching installed project. + // It does not exist at all or it is misspelled,... + else { + $update_info[$name] = array( + 'name' => $name, + 'label' => $name, + 'existing_version' => 'Unknown', + 'status'=> DRUSH_UPDATESTATUS_REQUESTED_PROJECT_NOT_FOUND, + ); + } + } + } + + // If specific versions were requested, match the requested release. + foreach ($requests as $name => $request) { + if (!empty($request['version'])) { + if (empty($update_info[$name]['releases'][$request['version']])) { + $update_info[$name]['status'] = DRUSH_UPDATESTATUS_REQUESTED_VERSION_NOT_FOUND; + } + elseif ($request['version'] == $update_info[$name]['existing_version']) { + $update_info[$name]['status'] = DRUSH_UPDATESTATUS_REQUESTED_VERSION_CURRENT; + } + // TODO: should we warn/reject if this is a downgrade? + else { + $update_info[$name]['status'] = DRUSH_UPDATESTATUS_REQUESTED_VERSION_NOT_CURRENT; + $update_info[$name]['candidate_version'] = $request['version']; + } + } + } + // Process locks specified on the command line. + $locked_list = drush_pm_update_lock($update_info, drush_get_option_list('lock'), drush_get_option_list('unlock'), drush_get_option('lock-message')); + + // Build project updatable messages, set candidate version and mark + // 'updateable' in the project. + foreach ($update_info as $key => $project) { + switch($project['status']) { + case DRUSH_UPDATESTATUS_NOT_SECURE: + $status = dt('SECURITY UPDATE available'); + pm_release_recommended($project); + break; + case DRUSH_UPDATESTATUS_REVOKED: + $status = dt('Installed version REVOKED'); + pm_release_recommended($project); + break; + case DRUSH_UPDATESTATUS_NOT_SUPPORTED: + $status = dt('Installed version not supported'); + pm_release_recommended($project); + break; + case DRUSH_UPDATESTATUS_NOT_CURRENT: + $status = dt('Update available'); + pm_release_recommended($project); + break; + case DRUSH_UPDATESTATUS_CURRENT: + $status = dt('Up to date'); + pm_release_recommended($project); + $project['updateable'] = FALSE; + break; + case DRUSH_UPDATESTATUS_NOT_CHECKED: + case DRUSH_UPDATESTATUS_NOT_FETCHED: + case DRUSH_UPDATESTATUS_FETCH_PENDING: + $status = dt('Unable to check status'); + break; + case DRUSH_UPDATESTATUS_PROJECT_NOT_PACKAGED: + $status = $project['status_msg']; + break; + case DRUSH_UPDATESTATUS_REQUESTED_PROJECT_NOT_UPDATEABLE: + $status = dt('Project has no enabled extensions and can\'t be updated'); + break; + case DRUSH_UPDATESTATUS_REQUESTED_PROJECT_NOT_FOUND: + $status = dt('Specified project not found'); + break; + case DRUSH_UPDATESTATUS_REQUESTED_VERSION_NOT_FOUND: + $status = dt('Specified version not found'); + break; + case DRUSH_UPDATESTATUS_REQUESTED_VERSION_CURRENT: + $status = dt('Specified version already installed'); + break; + case DRUSH_UPDATESTATUS_REQUESTED_VERSION_NOT_CURRENT: + $status = dt('Specified version available'); + $project['updateable'] = TRUE; + break; + default: + $status = dt('Unknown'); + break; + } + + if (isset($project['locked'])) { + $status = $project['locked'] . " ($status)"; + } + // Persist candidate_version in $update_info (plural). + if (empty($project['candidate_version'])) { + $update_info[$key]['candidate_version'] = $project['existing_version']; // Default to no change + } + else { + $update_info[$key]['candidate_version'] = $project['candidate_version']; + } + $update_info[$key]['status_msg'] = $status; + if (isset($project['updateable'])) { + $update_info[$key]['updateable'] = $project['updateable']; + } + } + + // Filter projects to show. + return pm_project_filter($update_info, drush_get_option('security-only')); +} + +/** + * Filter projects based on verbosity level and $security_only flag. + * + * @param array $update_info + * Update info for projects. + * @param bool $security_only + * Whether to select only projects with security updates. + * + * @return + * Array of projects matching filter criteria. + */ +function pm_project_filter($update_info, $security_only) { + $eligible = array(); + foreach ($update_info as $key => $project) { + if ($security_only) { + if ($project['status'] == DRUSH_UPDATESTATUS_NOT_SECURE) { + $eligible[$key] = $project; + } + } + elseif (drush_get_context('DRUSH_VERBOSE')) { + $eligible[$key] = $project; + } + elseif ($project['status'] != DRUSH_UPDATESTATUS_CURRENT) { + $eligible[$key] = $project; + } + } + return $eligible; +} + +/** + * Set a release to a recommended version (if available), and set as updateable. + */ +function pm_release_recommended(&$project) { + if (isset($project['recommended'])) { + $project['candidate_version'] = $project['recommended']; + $project['updateable'] = TRUE; + } + // If installed version is dev and the candidate version is older, choose + // latest dev as candidate. + if (($project['install_type'] == 'dev') && isset($project['candidate_version'])) { + if ($project['releases'][$project['candidate_version']]['date'] < $project['datestamp']) { + $project['candidate_version'] = $project['latest_dev']; + if ($project['releases'][$project['candidate_version']]['date'] <= $project['datestamp']) { + $project['candidate_version'] = $project['existing_version']; + $project['updateable'] = FALSE; + } + } + } +} + diff --git a/vendor/drush/drush/commands/pm/version_control/backup.inc b/vendor/drush/drush/commands/pm/version_control/backup.inc new file mode 100644 index 0000000000..8c17352a03 --- /dev/null +++ b/vendor/drush/drush/commands/pm/version_control/backup.inc @@ -0,0 +1,86 @@ +prepare_backup_dir()) { + if ($project['project_type'] != 'core') { + $backup_target .= '/' . $project['project_type'] . 's'; + drush_mkdir($backup_target); + } + $backup_target .= '/'. $project['name']; + // Save for rollback or notifications. + $project['backup_target'] = $backup_target; + + // Move or copy to backup target based in package-handler. + if (drush_get_option('package-handler', 'wget') == 'wget') { + if (drush_move_dir($project['full_project_path'], $backup_target)) { + return TRUE; + } + } + // cvs or git. + elseif (drush_copy_dir($project['full_project_path'], $backup_target)) { + return TRUE; + } + return drush_set_error('DRUSH_PM_BACKUP_FAILED', dt('Failed to backup project directory !project to !backup_target', array('!project' => $project['full_project_path'], '!backup_target' => $backup_target))); + } + } + + /** + * Implementation of rollback(). + */ + public function rollback($project) { + if (drush_get_option('no-backup', FALSE)) { + return; + } + if (drush_move_dir($project['backup_target'], $project['full_project_path'], TRUE)) { + return drush_log(dt("Backups were restored successfully."), LogLevel::OK); + } + return drush_set_error('DRUSH_PM_BACKUP_ROLLBACK_FAILED', dt('Could not restore backup and rollback from failed upgrade. You will need to resolve manually.')); + } + + /** + * Implementation of post_update(). + */ + public function post_update($project) { + if (drush_get_option('no-backup', FALSE)) { + return; + } + if ($project['backup_target']) { + drush_log(dt("Backups were saved into the directory !backup_target.", array('!backup_target' => $project['backup_target'])), LogLevel::OK); + } + } + + /** + * Implementation of post_download(). + */ + public function post_download($project) { + // NOOP + } + + // Helper for pre_update. + public function prepare_backup_dir($subdir = NULL) { + return drush_prepare_backup_dir($subdir); + } + + public static function reserved_files() { + return array(); + } +} diff --git a/vendor/drush/drush/commands/pm/version_control/bzr.inc b/vendor/drush/drush/commands/pm/version_control/bzr.inc new file mode 100644 index 0000000000..22bfb7a412 --- /dev/null +++ b/vendor/drush/drush/commands/pm/version_control/bzr.inc @@ -0,0 +1,137 @@ + '.'); + } + $args = array_keys($items_to_test); + array_unshift($args, 'bzr status --short' . str_repeat(' %s', count($args))); + array_unshift($args, $project['full_project_path']); + if (call_user_func_array('drush_shell_cd_and_exec', $args)) { + $output = preg_grep('/^[\sRCP][\sNDKM][\s\*]/', drush_shell_exec_output()); + if (!empty($output)) { + return drush_set_error('DRUSH_PM_BZR_LOCAL_CHANGES', dt("The Bazaar working copy at !path appears to have uncommitted changes (see below). Please commit or revert these changes before continuing:\n!output", array('!path' => $project['full_project_path'], '!output' => implode("\n", $output)))); + } + } + else { + return drush_set_error('DRUSH_PM_BZR_NOT_FOUND', dt("Drush was unable to get the bzr status on !path. Check that you have Bazaar \ninstalled and that this directory is a Bazaar working copy.\nThe specific errors are below:\n!errors", array('!path' => $project['full_project_path'], '!errors' => implode("\n", drush_shell_exec_output())))); + } + return TRUE; + } + + /** + * Implementation of rollback(). + */ + public function rollback($project) { + if (drush_shell_exec('bzr revert %s', $project['full_project_path'])) { + $output = drush_shell_exec_output(); + if (!empty($output)) { + return drush_set_error('DRUSH_PM_BZR_LOCAL_CHANGES', dt("The Bazaar working copy at !path appears to have uncommitted changes (see below). Please commit or revert these changes before continuing:\n!output", array('!path' => $project['full_project_path'], '!output' => implode("\n", $output)))); + } + } + else { + return drush_set_error('DRUSH_PM_BZR_NOT_FOUND', dt("Drush was unable to get the Bazaar status on !path. Check that you have Bazaar \ninstalled and that this directory is a Bazaar working copy.\nThe specific errors are below:\n!errors", array('!path' => $project['full_project_path'], '!errors' => implode("\n", drush_shell_exec_output())))); + } + } + + /** + * Implementation of post_update(). + */ + public function post_update($project) { + if ($this->sync($project)) { + // Only attempt commit on a sucessful sync + $this->commit($project); + } + } + + /** + * Implementation of post_download(). + */ + public function post_download($project) { + if ($this->sync($project)) { + // Only attempt commit on a sucessful sync + $this->commit($project); + } + } + + /** + * Automatically add any unversioned files to Bazaar and remove any files + * that have been deleted on the file system + */ + private function sync($project) { + if (drush_get_option('bzrsync')) { + $errors = ''; + $root = array(); + if (drush_shell_exec('bzr status --short %s', $project['full_project_path'])) { + $output = drush_shell_exec_output(); + // All paths returned by bzr status are relative to the repository root. + if (drush_shell_exec('bzr root %s', $project['full_project_path'])) { + $root = drush_shell_exec_output(); + } + foreach ($output as $line) { + if (preg_match('/^\?\s+(.*)/', $line, $matches)) { + $path = $root[0] .'/'. $matches[1]; + if (!drush_shell_exec('bzr add --no-recurse %s', $path)) { + $errors .= implode("\n", drush_shell_exec_output()); + } + } + else if (preg_match('/^\s+D\s+(.*)/', $line, $matches)) { + $path = $root[0] .'/'. $matches[1]; + if (!drush_shell_exec('bzr remove %s', $path)) { + $errors .= implode("\n", drush_shell_exec_output()); + } + } + } + if (!empty($errors)) { + return drush_set_error('DRUSH_PM_BZR_SYNC_PROBLEMS', dt("Problems were encountered adding or removing files to/from Bazaar.\nThe specific errors are below:\n!errors", array('!errors' => $errors))); + } + } + else { + return drush_set_error('DRUSH_PM_BZR_NOT_FOUND', dt("Drush was unable to get the bzr status. Check that you have Bazaar \ninstalled and that the site is a Bazaar working copy.\nThe specific errors are below:\n!errors", array('!errors' => implode("\n", drush_shell_exec_output())))); + } + return TRUE; + } + } + + /** + * Automatically commit changes to the repository + */ + private function commit($project) { + if (drush_get_option('bzrcommit')) { + $message = drush_get_option('bzrmessage'); + if (empty($message)) { + $message = dt("Drush automatic commit.\nProject: @name @type\nCommand: @arguments", array('@name' => $project['name'], '@type' => $project['project_type'], '@arguments' => implode(' ', $_SERVER['argv']))); + } + if (drush_shell_exec('bzr commit --message=%s %s', $message, $project['full_project_path'])) { + drush_log(dt('Project committed to Bazaar successfully'), LogLevel::OK); + } + else { + drush_set_error('DRUSH_PM_BZR_COMMIT_PROBLEMS', dt("Problems were encountered committing your changes to Bazaar.\nThe specific errors are below:\n!errors", array('!errors' => implode("\n", drush_shell_exec_output())))); + } + } + else { + drush_print(dt("You should consider committing the new code to your Bazaar repository.\nIf this version becomes undesireable, use Bazaar to roll back.")); + } + } + + public static function reserved_files() { + return array('.bzr', '.bzrignore', '.bzrtags'); + } +} diff --git a/vendor/drush/drush/commands/pm/version_control/svn.inc b/vendor/drush/drush/commands/pm/version_control/svn.inc new file mode 100644 index 0000000000..42aafa9a43 --- /dev/null +++ b/vendor/drush/drush/commands/pm/version_control/svn.inc @@ -0,0 +1,138 @@ + $project['full_project_path'], '!output' => implode("\n", $output)))); + } + } + else { + return drush_set_error('DRUSH_PM_SVN_NOT_FOUND', dt("Drush was unable to get the svn status on !path.\nThe specific errors are below:\n!errors", array('!path' => $project['full_project_path'], '!errors' => implode("\n", drush_shell_exec_output())))); + } + + // Check for incoming updates + $args = array_keys($items_to_test); + array_unshift($args, 'svn status -u '. drush_get_option('svnstatusparams') . str_repeat('%s ', count($args))); + array_unshift($args, $project['full_project_path']); + if (call_user_func_array('drush_shell_cd_and_exec', $args)) { + $output = preg_grep('/\*/', drush_shell_exec_output()); + if (!empty($output)) { + return drush_set_error('DRUSH_PM_SVN_REMOTE_CHANGES', dt("The SVN working copy at !path appears to be out of date with the repository (see below). Please run 'svn update' to pull down changes before continuing:\n!output", array('!path' => $project['full_project_path'], '!output' => implode("\n", $output)))); + } + } + else { + return drush_set_error('DRUSH_PM_SVN_NOT_FOUND', dt("Drush was unable to get the svn remote status on !path. Check that you have connectivity to the repository.\nThe specific errors are below:\n!errors", array('!path' => $project['full_project_path'], '!errors' => implode("\n", drush_shell_exec_output())))); + } + return TRUE; + } + + /** + * Implementation of rollback(). + */ + public function rollback($project) { + if (drush_shell_exec('svn revert '. drush_get_option('svnrevertparams') .' '. $project['full_project_path'])) { + $output = drush_shell_exec_output(); + if (!empty($output)) { + return drush_set_error('DRUSH_PM_SVN_LOCAL_CHANGES', dt("The SVN working copy at !path appears to have uncommitted changes (see below). Please commit or revert these changes before continuing:\n!output", array('!path' => $project['full_project_path'], '!output' => implode("\n", $output)))); + } + } + else { + return drush_set_error('DRUSH_PM_SVN_NOT_FOUND', dt("Drush was unable to get the svn status on !path. Check that you have Subversion \ninstalled and that this directory is a subversion working copy.\nThe specific errors are below:\n!errors", array('!path' => $project['full_project_path'], '!errors' => implode("\n", drush_shell_exec_output())))); + } + } + + /** + * Implementation of post_update(). + */ + public function post_update($project) { + if ($this->sync($project)) { + // Only attempt commit on a sucessful sync + $this->commit($project); + } + } + + /** + * Implementation of post_download(). + */ + public function post_download($project) { + if ($this->sync($project)) { + // Only attempt commit on a sucessful sync + $this->commit($project); + } + } + + /** + * Automatically add any unversioned files to Subversion and remove any files + * that have been deleted on the file system + */ + private function sync($project) { + if (drush_get_option('svnsync')) { + $errors = ''; + if (drush_shell_exec('svn status '. drush_get_option('svnstatusparams') .' '. $project['full_project_path'])) { + $output = drush_shell_exec_output(); + foreach ($output as $line) { + if (preg_match('/^\? *(.*)/', $line, $matches)) { + if (!drush_shell_exec('svn add '. drush_get_option('svnaddparams') .' '. $matches[1])) { + $errors .= implode("\n", drush_shell_exec_output()); + } + } + if (preg_match('/^\! *(.*)/', $line, $matches)) { + if (!drush_shell_exec('svn remove '. drush_get_option('svnremoveparams') .' '. $matches[1])) { + $errors .= implode("\n", drush_shell_exec_output()); + } + } + } + if (!empty($errors)) { + return drush_set_error('DRUSH_PM_SVN_SYNC_PROBLEMS', dt("Problems were encountered adding or removing files to/from this SVN working copy.\nThe specific errors are below:\n!errors", array('!errors' => $errors))); + } + } + else { + return drush_set_error('DRUSH_PM_SVN_NOT_FOUND', dt("Drush was unable to get the svn status on !path. Check that you have Subversion \ninstalled and that this directory is a subversion working copy.\nThe specific errors are below:\n!errors", array('!path' => $project['full_project_path'], '!errors' => implode("\n", drush_shell_exec_output())))); + } + return TRUE; + } + } + + /** + * Automatically commit changes to the repository + */ + private function commit($project) { + if (drush_get_option('svncommit')) { + $message = drush_get_option('svnmessage'); + if (empty($message)) { + $message = dt("Drush automatic commit: \n") . implode(' ', $_SERVER['argv']); + } + if (drush_shell_exec('svn commit '. drush_get_option('svncommitparams') .' -m "'. $message .'" '. $project['full_project_path'])) { + drush_log(dt('Project committed to Subversion successfully'), LogLevel::OK); + } + else { + drush_set_error('DRUSH_PM_SVN_COMMIT_PROBLEMS', dt("Problems were encountered committing your changes to Subversion.\nThe specific errors are below:\n!errors", array('!errors' => implode("\n", drush_shell_exec_output())))); + } + } + else { + drush_print(dt("You should consider committing the new code to your Subversion repository.\nIf this version becomes undesireable, use Subversion to roll back.")); + } + } + + public static function reserved_files() { + return array('.svn'); + } +} diff --git a/vendor/drush/drush/commands/runserver/d7-rs-router.php b/vendor/drush/drush/commands/runserver/d7-rs-router.php new file mode 100644 index 0000000000..c1e467f35a --- /dev/null +++ b/vendor/drush/drush/commands/runserver/d7-rs-router.php @@ -0,0 +1,70 @@ +uid; + $message = strtr('Watchdog: !message | severity: !severity | type: !type | uid: !uid | !ip | !request_uri | !referer | !link', array( + '!message' => strip_tags(!isset($log_entry['variables']) ? $log_entry['message'] : strtr($log_entry['message'], $log_entry['variables'])), + '!severity' => $log_entry['severity'], + '!type' => $log_entry['type'], + '!ip' => $log_entry['ip'], + '!request_uri' => $log_entry['request_uri'], + '!referer' => $log_entry['referer'], + '!uid' => $uid, + '!link' => strip_tags($log_entry['link']), + )); + error_log($message); + } +} + +// Get a $_SERVER key, or equivalent environment variable +// if it is not set in $_SERVER. +function runserver_env($key) { + if (isset($_SERVER[$key])) { + return $_SERVER[$key]; + } + else { + return getenv($key); + } +} + +$url = parse_url($_SERVER["REQUEST_URI"]); +if (file_exists('.' . urldecode($url['path']))) { + // Serve the requested resource as-is. + return FALSE; +} + +// Populate the "q" query key with the path, skip the leading slash. +$_GET['q'] = $_REQUEST['q'] = urldecode(substr($url['path'], 1)); + +// We set the base_url so that Drupal generates correct URLs for runserver +// (e.g. http://127.0.0.1:8888/...), but can still select and serve a specific +// site in a multisite configuration (e.g. http://mysite.com/...). +$base_url = runserver_env('RUNSERVER_BASE_URL'); + +// The built in webserver incorrectly sets $_SERVER['SCRIPT_NAME'] when URLs +// contain multiple dots (such as config entity IDs) in the path. Since this is +// a virtual resource, served by index.php set the script name explicitly. +// See https://github.com/drush-ops/drush/issues/2033 for more information. +$_SERVER['SCRIPT_NAME'] = '/index.php'; + +// Include the main index.php and let Drupal take over. +// n.b. Drush sets the cwd to the Drupal root during bootstrap. +include 'index.php'; diff --git a/vendor/drush/drush/commands/runserver/d8-rs-router.php b/vendor/drush/drush/commands/runserver/d8-rs-router.php new file mode 100644 index 0000000000..dc47a9a52e --- /dev/null +++ b/vendor/drush/drush/commands/runserver/d8-rs-router.php @@ -0,0 +1,96 @@ +id(); + $message = strtr('Watchdog: !message | severity: !severity | type: !type | uid: !uid | !ip | !request_uri | !referer | !link', array( + '!message' => strip_tags(!isset($log_entry['variables']) ? $log_entry['message'] : strtr($log_entry['message'], $log_entry['variables'])), + '!severity' => $log_entry['severity'], + '!type' => $log_entry['type'], + '!ip' => $log_entry['ip'], + '!request_uri' => $log_entry['request_uri'], + '!referer' => $log_entry['referer'], + '!uid' => $uid, + '!link' => strip_tags($log_entry['link']), + )); + error_log($message); + } +} + +// Get a $_SERVER key, or equivalent environment variable +// if it is not set in $_SERVER. +function runserver_env($key) { + if (isset($_SERVER[$key])) { + return $_SERVER[$key]; + } + else { + return getenv($key); + } +} + +$url = parse_url($_SERVER["REQUEST_URI"]); +if (file_exists('.' . urldecode($url['path']))) { + // Serve the requested resource as-is. + return FALSE; +} + +// We set the base_url so that Drupal generates correct URLs for runserver +// (e.g. http://127.0.0.1:8888/...), but can still select and serve a specific +// site in a multisite configuration (e.g. http://mysite.com/...). +$base_url = runserver_env('RUNSERVER_BASE_URL'); + +// The built in webserver incorrectly sets $_SERVER['SCRIPT_NAME'] when URLs +// contain multiple dots (such as config entity IDs) in the path. Since this is +// a virtual resource, served by index.php set the script name explicitly. +// See https://github.com/drush-ops/drush/issues/2033 for more information. +// Work around the PHP bug. Update $_SERVER variables to point to the correct +// index-file. +$path = $url['path']; +$script = 'index.php'; +if (strpos($path, '.php') !== FALSE) { + // Work backwards through the path to check if a script exists. Otherwise + // fallback to index.php. + do { + $path = dirname($path); + if (preg_match('/\.php$/', $path) && is_file('.' . $path)) { + // Discovered that the path contains an existing PHP file. Use that as the + // script to include. + $script = ltrim($path, '/'); + break; + } + } while ($path !== '/' && $path !== '.'); +} + +// Update $_SERVER variables to point to the correct index-file. +$index_file_absolute = $_SERVER['DOCUMENT_ROOT'] . DIRECTORY_SEPARATOR . $script; +$index_file_relative = DIRECTORY_SEPARATOR . $script; + +// SCRIPT_FILENAME will point to the router script itself, it should point to +// the full path of index.php. +$_SERVER['SCRIPT_FILENAME'] = $index_file_absolute; + +// SCRIPT_NAME and PHP_SELF will either point to index.php or contain the full +// virtual path being requested depending on the URL being requested. They +// should always point to index.php relative to document root. +$_SERVER['SCRIPT_NAME'] = $index_file_relative; +$_SERVER['PHP_SELF'] = $index_file_relative; + +// Require the script and let core take over. +require $_SERVER['SCRIPT_FILENAME']; \ No newline at end of file diff --git a/vendor/drush/drush/commands/runserver/runserver-prepend.php b/vendor/drush/drush/commands/runserver/runserver-prepend.php new file mode 100644 index 0000000000..48802ee232 --- /dev/null +++ b/vendor/drush/drush/commands/runserver/runserver-prepend.php @@ -0,0 +1,68 @@ +uid; + } + else { + $uid = $log_entry['user']->id(); + } + $message = strtr('Watchdog: !message | severity: !severity | type: !type | uid: !uid | !ip | !request_uri | !referer | !link', array( + '!message' => strip_tags(!isset($log_entry['variables']) ? $log_entry['message'] : strtr($log_entry['message'], $log_entry['variables'])), + '!severity' => $log_entry['severity'], + '!type' => $log_entry['type'], + '!ip' => $log_entry['ip'], + '!request_uri' => $log_entry['request_uri'], + '!referer' => $log_entry['referer'], + '!uid' => $uid, + '!link' => strip_tags($log_entry['link']), + )); + error_log($message); + } +} + +// Get a $_SERVER key, or equivalent environment variable +// if it is not set in $_SERVER. +function runserver_env($key) { + if (isset($_SERVER[$key])) { + return $_SERVER[$key]; + } + else { + return getenv($key); + } +} diff --git a/vendor/drush/drush/commands/runserver/runserver.drush.inc b/vendor/drush/drush/commands/runserver/runserver.drush.inc new file mode 100644 index 0000000000..13103f2acb --- /dev/null +++ b/vendor/drush/drush/commands/runserver/runserver.drush.inc @@ -0,0 +1,220 @@ + 'Runs PHP\'s built-in http server for development.', + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL, + 'arguments' => array( + 'addr:port/path' => 'Host IP address and port number to bind to and path to open in web browser. Format is addr:port/path, default 127.0.0.1:8888, all elements optional. See examples for shorthand. Only opens a browser if a path is specified.', + ), + 'options' => array( + 'variables' => 'Key-value array of variables to override in the $conf array for the running site. By default disables drupal_http_request_fails to avoid errors on Windows (which supports only one connection at a time). Comma delimited list of name=value pairs (or array in drushrc).', + 'default-server' => 'A default addr:port/path to use for any values not specified as an argument.', + 'user' => 'If opening a web browser, automatically log in as this user (user ID or username). Default is to log in as uid 1.', + 'browser' => 'If opening a web browser, which browser to user (defaults to operating system default). Use --no-browser to avoid opening a browser.', + 'dns' => 'Resolve hostnames/IPs using DNS/rDNS (if possible) to determine binding IPs and/or human friendly hostnames for URLs and browser.', + ), + 'aliases' => array('rs'), + 'examples' => array( + 'drush rs 8080' => 'Start runserver on 127.0.0.1, port 8080.', + 'drush rs 10.0.0.28:80' => 'Start runserver on 10.0.0.28, port 80.', + 'drush rs [::1]:80' => 'Start runserver on IPv6 localhost ::1, port 80.', + 'drush rs --dns localhost:8888/user' => 'Start runserver on localhost (using rDNS to determine binding IP), port 8888, and open /user in browser.', + 'drush rs /' => 'Start runserver on default IP/port (127.0.0.1, port 8888), and open / in browser.', + 'drush rs --default-server=127.0.0.1:8080/ -' => 'Use a default (would be specified in your drushrc) that starts runserver on port 8080, and opens a browser to the front page. Set path to a single hyphen path in argument to prevent opening browser for this session.', + 'drush rs :9000/admin' => 'Start runserver on 127.0.0.1, port 9000, and open /admin in browser. Note that you need a colon when you specify port and path, but no IP.', + ), + ); + return $items; +} + +/** + * Callback for runserver command. + */ +function drush_core_runserver($uri = NULL) { + global $user, $base_url; + + // Determine active configuration. + $uri = runserver_uri($uri); + if (!$uri) { + return FALSE; + } + + // Remove any leading slashes from the path, since that is what url() expects. + $path = ltrim($uri['path'], '/'); + + // $uri['addr'] is a special field set by runserver_uri() + $hostname = $uri['host']; + $addr = $uri['addr']; + + drush_set_context('DRUSH_URI', 'http://' . $hostname . ':' . $uri['port']); + + // We pass in the currently logged in user (if set via the --user option), + // which will automatically log this user in the browser during the first + // request. + if (drush_get_option('user', FALSE) === FALSE) { + drush_set_option('user', 1); + } + drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_LOGIN); + + // We delete any registered files here, since they are not caught by Ctrl-C. + _drush_delete_registered_files(); + + // We set the effective base_url, since we have now detected the current site, + // and need to ensure generated URLs point to our runserver host. + // We also pass in the effective base_url to our auto_prepend_script via the + // CGI environment. This allows Drupal to generate working URLs to this http + // server, whilst finding the correct multisite from the HTTP_HOST header. + $base_url = 'http://' . $addr . ':' . $uri['port']; + $env['RUNSERVER_BASE_URL'] = $base_url; + + // We pass in an array of $conf overrides using the same approach. + // This is available as an option for developers to pass in their own + // favorite $conf overrides (e.g. disabling css aggregation). + $current_override = drush_get_option_list('variables', array()); + $override = array(); + foreach ($current_override as $name => $value) { + if (is_numeric($name) && (strpos($value, '=') !== FALSE)) { + list($name, $value) = explode('=', $value, 2); + } + $override[$name] = $value; + } + $env['RUNSERVER_CONF'] = urlencode(serialize($override)); + + // We log in with the specified user ID (if set) via the password reset URL. + $user_message = ''; + $usersingle = drush_user_get_class()->getCurrentUserAsSingle(); + if ($usersingle->id()) { + $browse = $usersingle->passResetUrl($path); + $user_message = ', logged in as ' . $usersingle->getUsername(); + } + else { + $browse = drush_url($path); + } + + drush_print(dt('HTTP server listening on !addr, port !port (see http://!hostname:!port/!path), serving site !site!user...', array('!addr' => $addr, '!hostname' => $hostname, '!port' => $uri['port'], '!path' => $path, '!site' => drush_get_context('DRUSH_DRUPAL_SITE', 'default'), '!user' => $user_message))); + // Start php 5.4 builtin server. + // Store data used by runserver-prepend.php in the shell environment. + foreach ($env as $key => $value) { + putenv($key . '=' . $value); + } + if (!empty($uri['path'])) { + // Start a browser if desired. Include a 2 second delay to allow the + // server to come up. + drush_start_browser($browse, 2); + } + // Start the server using 'php -S'. + if (drush_drupal_major_version() >= 8) { + $extra = ' "' . __DIR__ . '/d8-rs-router.php"'; + } + elseif (drush_drupal_major_version() == 7) { + $extra = ' "' . __DIR__ . '/d7-rs-router.php"'; + } + else { + $extra = ' --define auto_prepend_file="' . __DIR__ . '/runserver-prepend.php"'; + } + $root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT'); + drush_shell_exec_interactive('cd %s && %s -S ' . $addr . ':' . $uri['port']. $extra, $root, drush_get_option('php', 'php')); +} + +/** + * Determine the URI to use for this server. + */ +function runserver_uri($uri) { + $drush_default = array( + 'host' => '127.0.0.1', + 'port' => '8888', + 'path' => '', + ); + $user_default = runserver_parse_uri(drush_get_option('default-server', '')); + $site_default = runserver_parse_uri(drush_get_option('uri', '')); + $uri = runserver_parse_uri($uri); + if (is_array($uri)) { + // Populate defaults. + $uri = $uri + $user_default + $site_default + $drush_default; + if (ltrim($uri['path'], '/') == '-') { + // Allow a path of a single hyphen to clear a default path. + $uri['path'] = ''; + } + // Determine and set the new URI. + $uri['addr'] = $uri['host']; + if (drush_get_option('dns', FALSE)) { + if (ip2long($uri['host'])) { + $uri['host'] = gethostbyaddr($uri['host']); + } + else { + $uri['addr'] = gethostbyname($uri['host']); + } + } + } + return $uri; +} + +/** + * Parse a URI or partial URI (including just a port, host IP or path). + * + * @param string $uri + * String that can contain partial URI. + * + * @return array + * URI array as returned by parse_url. + */ +function runserver_parse_uri($uri) { + if (empty($uri)) { + return array(); + } + if ($uri[0] == ':') { + // ':port/path' shorthand, insert a placeholder hostname to allow parsing. + $uri = 'placeholder-hostname' . $uri; + } + // FILTER_VALIDATE_IP expects '[' and ']' to be removed from IPv6 addresses. + // We check for colon from the right, since IPv6 addresses contain colons. + $to_path = trim(substr($uri, 0, strpos($uri, '/')), '[]'); + $to_port = trim(substr($uri, 0, strrpos($uri, ':')), '[]'); + if (filter_var(trim($uri, '[]'), FILTER_VALIDATE_IP) || filter_var($to_path, FILTER_VALIDATE_IP) || filter_var($to_port, FILTER_VALIDATE_IP)) { + // 'IP', 'IP/path' or 'IP:port' shorthand, insert a schema to allow parsing. + $uri = 'http://' . $uri; + } + $uri = parse_url($uri); + if (empty($uri)) { + return drush_set_error('RUNSERVER_INVALID_ADDRPORT', dt('Invalid argument - should be in the "host:port/path" format, numeric (port only) or non-numeric (path only).')); + } + if (count($uri) == 1 && isset($uri['path'])) { + if (is_numeric($uri['path'])) { + // Port only shorthand. + $uri['port'] = $uri['path']; + unset($uri['path']); + } + } + if (isset($uri['host']) && $uri['host'] == 'placeholder-hostname') { + unset($uri['host']); + } + return $uri; +} diff --git a/vendor/drush/drush/commands/sql/sql.drush.inc b/vendor/drush/drush/commands/sql/sql.drush.inc new file mode 100644 index 0000000000..99f71ecb69 --- /dev/null +++ b/vendor/drush/drush/commands/sql/sql.drush.inc @@ -0,0 +1,685 @@ + 'The DB connection key if using multiple connections in settings.php.', + 'example-value' => 'key', + ); + $db_url['db-url'] = array( + 'description' => 'A Drupal 6 style database URL.', + 'example-value' => 'mysql://root:pass@127.0.0.1/db', + ); + $options['target'] = array( + 'description' => 'The name of a target within the specified database connection. Defaults to \'default\'.', + 'example-value' => 'key', + // Gets unhidden in help_alter(). We only want to show this to D7 users but have to + // declare it here since some commands do not bootstrap fully. + 'hidden' => TRUE, + ); + + $items['sql-drop'] = array( + 'description' => 'Drop all tables in a given database.', + 'arguments' => array( + ), + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'options' => array( + 'yes' => 'Skip confirmation and proceed.', + 'result-file' => array( + 'description' => 'Save to a file. The file should be relative to Drupal root. Recommended.', + 'example-value' => '/path/to/file', + ), + ) + $options + $db_url, + 'topics' => array('docs-policy'), + 'aliases' => array('sql:drop'), + ); + $items['sql-conf'] = array( + 'description' => 'Print database connection details using print_r().', + 'hidden' => TRUE, + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'options' => array( + 'all' => 'Show all database connections, instead of just one.', + 'show-passwords' => 'Show database password.', + ) + $options, + 'outputformat' => array( + 'default' => 'print-r', + 'pipe-format' => 'var_export', + 'private-fields' => 'password', + ), + 'aliases' => array('sql:conf'), + ); + $items['sql-connect'] = array( + 'description' => 'A string for connecting to the DB.', + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'options' => $options + $db_url + array( + 'extra' => array( + 'description' => 'Add custom options to the connect string.', + 'example-value' => '--skip-column-names', + ), + ), + 'examples' => array( + '`drush sql-connect` < example.sql' => 'Bash: Import SQL statements from a file into the current database.', + 'eval (drush sql-connect) < example.sql' => 'Fish: Import SQL statements from a file into the current database.', + ), + 'aliases' => array('sql:connect'), + ); + $items['sql-create'] = array( + 'description' => 'Create a database.', + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'examples' => array( + 'drush sql-create' => 'Create the database for the current site.', + 'drush @site.test sql-create' => 'Create the database as specified for @site.test.', + 'drush sql-create --db-su=root --db-su-pw=rootpassword --db-url="mysql://drupal_db_user:drupal_db_password@127.0.0.1/drupal_db"' => + 'Create the database as specified in the db-url option.' + ), + 'options' => array( + 'db-su' => 'Account to use when creating a new database. Optional.', + 'db-su-pw' => 'Password for the "db-su" account. Optional.', + ) + $options + $db_url, + 'aliases' => array('sql:create'), + ); + $items['sql-dump'] = array( + 'description' => 'Exports the Drupal DB as SQL using mysqldump or equivalent.', + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'examples' => array( + 'drush sql-dump --result-file=../18.sql' => 'Save SQL dump to the directory above Drupal root.', + 'drush sql-dump --skip-tables-key=common' => 'Skip standard tables. @see example.drushrc.php', + 'drush sql-dump --extra=--no-data' => 'Pass extra option to dump command.', + ), + 'options' => drush_sql_get_table_selection_options() + array( + 'result-file' => array( + 'description' => 'Save to a file. The file should be relative to Drupal root. If --result-file is provided with no value, then date based filename will be created under ~/drush-backups directory.', + 'example-value' => '/path/to/file', + 'value' => 'optional', + ), + 'create-db' => array('hidden' => TRUE, 'description' => 'Omit DROP TABLE statements. Postgres and Oracle only. Used by sql-sync, since including the DROP TABLE statements interfere with the import when the database is created.'), + 'data-only' => 'Dump data without statements to create any of the schema.', + 'ordered-dump' => 'Order by primary key and add line breaks for efficient diff in revision control. Slows down the dump. Mysql only.', + 'gzip' => 'Compress the dump using the gzip program which must be in your $PATH.', + 'extra' => 'Add custom options to the dump command.', + ) + $options + $db_url, + 'aliases' => array('sql:dump'), + ); + $items['sql-query'] = array( + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'description' => 'Execute a query against a database.', + 'examples' => array( + 'drush sql-query "SELECT * FROM users WHERE uid=1"' => 'Browse user record. Table prefixes, if used, must be added to table names by hand.', + 'drush sql-query --db-prefix "SELECT * FROM {users} WHERE uid=1"' => 'Browse user record. Table prefixes are honored. Caution: curly-braces will be stripped from all portions of the query.', + '`drush sql-connect` < example.sql' => 'Import sql statements from a file into the current database.', + 'drush sql-query --file=example.sql' => 'Alternate way to import sql statements from a file.', + ), + 'arguments' => array( + 'query' => 'An SQL query. Ignored if \'file\' is provided.', + ), + 'options' => array( + 'result-file' => array( + 'description' => 'Save to a file. The file should be relative to Drupal root. Optional.', + 'example-value' => '/path/to/file', + ), + 'file' => 'Path to a file containing the SQL to be run. Gzip files are accepted.', + 'extra' => array( + 'description' => 'Add custom options to the database connection command.', + 'example-value' => '--skip-column-names', + ), + 'db-prefix' => 'Enable replacement of braces in your query.', + 'db-spec' => array( + 'description' => 'A database specification', + 'hidden' => TRUE, // Hide since this is only used with --backend calls. + ) + ) + $options + $db_url, + 'aliases' => array('sqlq', 'sql:query'), + ); + $items['sql-cli'] = array( + 'description' => "Open a SQL command-line interface using Drupal's credentials.", + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + // 'options' => $options + $db_url, + 'allow-additional-options' => array('sql-connect'), + 'aliases' => array('sqlc', 'sql:cli'), + 'examples' => array( + 'drush sql-cli' => "Open a SQL command-line interface using Drupal's credentials.", + 'drush sql-cli --extra=-A' => "Open a SQL CLI and skip reading table information.", + ), + 'remote-tty' => TRUE, + ); + return $items; +} + +/** + * Implements hook_drush_help_alter(). + */ +function sql_drush_help_alter(&$command) { + // Drupal 7+ only options. + if (drush_drupal_major_version() >= 7) { + if ($command['commandfile'] == 'sql') { + unset($command['options']['target']['hidden']); + } + } +} + +/** + * Safely bootstrap Drupal to the point where we can + * access the database configuration. + */ +function drush_sql_bootstrap_database_configuration() { + // Under Drupal 7, if the database is configured but empty, then + // DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION will throw an exception. + // If this happens, we'll just catch it and continue. + // TODO: Fix this in the bootstrap, per http://drupal.org/node/1996004 + try { + drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION); + } + catch (Exception $e) { + } +} + +/** + * Check whether further bootstrap is needed. If so, do it. + */ +function drush_sql_bootstrap_further() { + if (!drush_get_option(array('db-url', 'db-spec'))) { + drush_sql_bootstrap_database_configuration(); + } +} + +/** + * Command callback. Displays the Drupal site's database connection string. + */ +function drush_sql_conf() { + drush_sql_bootstrap_database_configuration(); + if (drush_get_option('all')) { + $sqlVersion = drush_sql_get_version(); + return $sqlVersion->getAll(); + } + else { + $sql = drush_sql_get_class(); + return $sql->db_spec(); + } +} + +/** + * Command callback. Emits a connect string. + */ +function drush_sql_connect() { + drush_sql_bootstrap_further(); + $sql = drush_sql_get_class(); + return $sql->connect(FALSE); +} + +/** + * Command callback. Create a database. + */ +function drush_sql_create() { + drush_sql_bootstrap_further(); + $sql = drush_sql_get_class(); + $db_spec = $sql->db_spec(); + // Prompt for confirmation. + if (!drush_get_context('DRUSH_SIMULATE')) { + // @todo odd - maybe for sql-sync. + $txt_destination = (isset($db_spec['remote-host']) ? $db_spec['remote-host'] . '/' : '') . $db_spec['database']; + drush_print(dt("Creating database !target. Any possible existing database will be dropped!", array('!target' => $txt_destination))); + + if (!drush_confirm(dt('Do you really want to continue?'))) { + return drush_user_abort(); + } + } + + $result = $sql->createdb(TRUE); + if (!$result) { + drush_set_error('DRUSH_SQL_CREATE_ERROR', dt('SQL create database error occurred.')); + } + return $result; +} + + +/** + * Command callback. Outputs the entire Drupal database in SQL format using mysqldump or equivalent. + */ +function drush_sql_dump() { + drush_sql_bootstrap_further(); + $sql = drush_sql_get_class(); + return $sql->dump(drush_get_option('result-file', FALSE)); +} + +/** + * Construct an array that places table names in appropriate + * buckets based on whether the table is to be skipped, included + * for structure only, or have structure and data dumped. + * The keys of the array are: + * - skip: tables to be skipped completed in the dump + * - structure: tables to only have their structure i.e. DDL dumped + * - tables: tables to have structure and data dumped + * + * @return array + * An array of table names with each table name in the appropriate + * element of the array. + */ +function drush_sql_get_table_selection() { + // Skip large core tables if instructed. Used by 'sql-drop/sql-dump/sql-sync' commands. + $skip_tables = _drush_sql_get_raw_table_list('skip-tables'); + // Skip any structure-tables as well. + $structure_tables = _drush_sql_get_raw_table_list('structure-tables'); + // Dump only the specified tables. Takes precedence over skip-tables and structure-tables. + $tables = _drush_sql_get_raw_table_list('tables'); + + return array('skip' => $skip_tables, 'structure' => $structure_tables, 'tables' => $tables); +} + +function drush_sql_get_table_selection_options() { + return array( + 'skip-tables-key' => 'A key in the $skip_tables array. @see example.drushrc.php. Optional.', + 'structure-tables-key' => 'A key in the $structure_tables array. @see example.drushrc.php. Optional.', + 'tables-key' => 'A key in the $tables array. Optional.', + 'skip-tables-list' => 'A comma-separated list of tables to exclude completely. Optional.', + 'structure-tables-list' => 'A comma-separated list of tables to include for structure, but not data. Optional.', + 'tables-list' => 'A comma-separated list of tables to transfer. Optional.', + ); +} + +/** + * Expand wildcard tables. + * + * @param array $tables + * An array of table names, some of which may contain wildcards (`*`). + * @param array $db_tables + * An array with all the existing table names in the current database. + * @return + * $tables array with wildcards resolved to real table names. + */ +function drush_sql_expand_wildcard_tables($tables, $db_tables) { + // Table name expansion based on `*` wildcard. + $expanded_db_tables = array(); + foreach ($tables as $k => $table) { + // Only deal with table names containing a wildcard. + if (strpos($table, '*') !== FALSE) { + $pattern = '/^' . str_replace('*', '.*', $table) . '$/i'; + // Merge those existing tables which match the pattern with the rest of + // the expanded table names. + $expanded_db_tables += preg_grep($pattern, $db_tables); + } + } + return $expanded_db_tables; +} + +/** + * Filters tables. + * + * @param array $tables + * An array of table names to filter. + * @param array $db_tables + * An array with all the existing table names in the current database. + * @return + * An array with only valid table names (i.e. all of which actually exist in + * the database). + */ +function drush_sql_filter_tables($tables, $db_tables) { + // Ensure all the tables actually exist in the database. + foreach ($tables as $k => $table) { + if (!in_array($table, $db_tables)) { + unset($tables[$k]); + } + } + + return $tables; +} + +/** + * Given the table names in the input array that may contain wildcards (`*`), + * expand the table names so that the array returned only contains table names + * that exist in the database. + * + * @param array $tables + * An array of table names where the table names may contain the + * `*` wildcard character. + * @param array $db_tables + * The list of tables present in a database. + * @return array + * An array of tables with non-existant tables removed. + */ +function _drush_sql_expand_and_filter_tables($tables, $db_tables) { + $expanded_tables = drush_sql_expand_wildcard_tables($tables, $db_tables); + $tables = drush_sql_filter_tables(array_merge($tables, $expanded_tables), $db_tables); + $tables = array_unique($tables); + sort($tables); + return $tables; +} + +/** + * Consult the specified options and return the list of tables + * specified. + * + * @param option_name + * The option name to check: skip-tables, structure-tables + * or tables. This function will check both *-key and *-list, + * and, in the case of sql-sync, will also check target-* + * and source-*, to see if an alias set one of these options. + * @returns array + * Returns an array of tables based on the first option + * found, or an empty array if there were no matches. + */ +function _drush_sql_get_raw_table_list($option_name) { + foreach(array('' => 'cli', 'target-,,source-' => NULL) as $prefix_list => $context) { + foreach(explode(',',$prefix_list) as $prefix) { + $key_list = drush_get_option($prefix . $option_name . '-key', NULL, $context); + foreach(explode(',', $key_list) as $key) { + $all_tables = drush_get_option($option_name, array()); + if (array_key_exists($key, $all_tables)) { + return $all_tables[$key]; + } + if ($option_name != 'tables') { + $all_tables = drush_get_option('tables', array()); + if (array_key_exists($key, $all_tables)) { + return $all_tables[$key]; + } + } + } + $table_list = drush_get_option($prefix . $option_name . '-list', NULL, $context); + if (isset($table_list)) { + return empty($table_list) ? array() : explode(',', $table_list); + } + } + } + + return array(); +} + +/** + * Command callback. Executes the given SQL query on the Drupal database. + */ +function drush_sql_query($query = NULL) { + drush_sql_bootstrap_further(); + $filename = drush_get_option('file', NULL); + // Enable prefix processing when db-prefix option is used. + if (drush_get_option('db-prefix')) { + drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_DATABASE); + } + if (drush_get_context('DRUSH_SIMULATE')) { + if ($query) { + drush_print(dt('Simulating sql-query: !q', array('!q' => $query))); + } + else { + drush_print(dt('Simulating sql-import from !f', array('!f' => drush_get_option('file')))); + } + } + else { + $sql = drush_sql_get_class(drush_get_option('db-spec')); + $result = $sql->query($query, $filename, drush_get_option('result-file')); + if (!$result) { + return drush_set_error('DRUSH_SQL_NO_QUERY', dt('Query failed.')); + } + drush_print(implode("\n", drush_shell_exec_output())); + } + return TRUE; +} + +/** + * Command callback. Drops all tables in the database. + */ +function drush_sql_drop() { + drush_sql_bootstrap_further(); + $sql = drush_sql_get_class(); + $db_spec = $sql->db_spec(); + if (!drush_confirm(dt('Do you really want to drop all tables in the database !db?', array('!db' => $db_spec['database'])))) { + return drush_user_abort(); + } + $tables = $sql->listTablesQuoted(); + return $sql->drop($tables); +} + +/** + * Command callback. Launches console a DB backend. + */ +function drush_sql_cli() { + drush_sql_bootstrap_further(); + $sql = drush_sql_get_class(); + $result = !drush_shell_proc_open($sql->connect()); + if (!$result) { + drush_set_error('DRUSH_SQL_CLI_ERROR', dt('SQL client error occurred.')); + } + return $result; +} + +/** + * Call from a pre-sql-sync hook to register an sql + * query to be executed in the post-sql-sync hook. + * @see drush_sql_pre_sql_sync() and @see drush_sql_post_sql_sync(). + * + * @param $id + * String containing an identifier representing this + * operation. This id is not actually used at the + * moment, it is just used to fufill the contract + * of drush contexts. + * @param $message + * String with the confirmation message that describes + * to the user what the post-sync operation is going + * to do. This confirmation message is printed out + * just before the user is asked whether or not the + * sql-sync operation should be continued. + * @param $query + * String containing the sql query to execute. If no + * query is provided, then the confirmation message will + * be displayed to the user, but no action will be taken + * in the post-sync hook. This is useful for drush modules + * that wish to provide their own post-sync hooks to fix + * up the target database in other ways (e.g. through + * Drupal APIs). + */ +function drush_sql_register_post_sync_op($id, $message, $query = NULL) { + $options = drush_get_context('post-sync-ops'); + + $options[$id] = array('message' => $message, 'query' => $query); + + drush_set_context('post-sync-ops', $options); +} + +/** + * Builds a confirmation message for all post-sync operations. + * + * @return string + * All post-sync operation messages concatenated together. + */ +function _drush_sql_get_post_sync_messages() { + $messages = ''; + $operations = drush_get_context('post-sync-ops'); + if (!empty($operations)) { + $messages = dt('The following operations will be done on the target database:') . "\n"; + + $bullets = array_column($operations, 'message'); + $messages .= " * " . implode("\n * ", $bullets) . "\n"; + } + return $messages; +} + +/** + * Wrapper for drush_get_class; instantiates an driver-specific instance + * of SqlBase class. + * + * @param array $db_spec + * If known, specify a $db_spec that the class can operate with. + * + * @throws \Drush\Sql\SqlException + * + * @return Drush\Sql\SqlBase + */ +function drush_sql_get_class($db_spec = NULL) { + $database = drush_get_option('database', 'default'); + $target = drush_get_option('target', 'default'); + + // Try a few times to quickly get $db_spec. + if (!empty($db_spec)) { + if (!empty($db_spec['driver'])) { + $class_name = [ + 'Drush\Sql\Sql', + 'Drupal\Driver\Database\\' . $db_spec['driver'] . '\Drush', + ]; + try { + $class = drush_get_class($class_name, [$db_spec], [$db_spec['driver']]); + return $class; + } + catch (ArgumentCountError $e) { + /** @var \Drush\Log\Logger $logger */ + $logger = drush_get_context('DRUSH_LOGGER'); + $logger->error(dt('Could not load SQL class. There is a mismatch between your Drupal root and your PHP autoloading.')); + $logger->error(dt('This can happen if you mix Drupal 8 and Drupal 7.')); + $logger->error(dt('Please check that the location of Drush matches the Drupal core version you want Drush to talk to.')); + if (drush_get_context('DRUSH_DEBUG')) { + $message = sprintf( + dt('drush_sql_get_class threw a %s exception: %s'), + get_class($e), + $e->getMessage()); + $logger->log(LogLevel::DEBUG, $message); + } + } + } + } + elseif ($url = drush_get_option('db-url')) { + $url = is_array($url) ? $url[$database] : $url; + $db_spec = drush_convert_db_from_db_url($url); + $db_spec['db_prefix'] = drush_get_option('db-prefix'); + return drush_sql_get_class($db_spec); + } + elseif (($databases = drush_get_option('databases')) && (array_key_exists($database, $databases)) && (array_key_exists($target, $databases[$database]))) { + $db_spec = $databases[$database][$target]; + return drush_sql_get_class($db_spec); + } + else { + // No parameter or options provided. Determine $db_spec ourselves. + if ($sqlVersion = drush_sql_get_version()) { + if ($db_spec = $sqlVersion->get_db_spec()) { + return drush_sql_get_class($db_spec); + } + } + } + + throw new \Drush\Sql\SqlException('Unable to find a matching SQL Class. Drush cannot find your database connection details.'); +} + +/** + * Wrapper for drush_get_class; instantiates a Drupal version-specific instance + * of SqlVersion class. + * + * @return Drush\Sql\SqlVersion + */ +function drush_sql_get_version() { + return drush_get_class('Drush\Sql\Sql', array(), array(drush_drupal_major_version())) ?: NULL; +} + +/** + * Implements hook_drush_sql_sync_sanitize(). + * + * Sanitize usernames, passwords, and sessions when the --sanitize option is used. + * It is also an example of how to write a database sanitizer for sql sync. + * + * To write your own sync hook function, define mymodule_drush_sql_sync_sanitize() + * in mymodule.drush.inc and follow the form of this function to add your own + * database sanitization operations via the register post-sync op function; + * @see drush_sql_register_post_sync_op(). This is the only thing that the + * sync hook function needs to do; sql-sync takes care of the rest. + * + * The function below has a lot of logic to process user preferences and + * generate the correct SQL regardless of whether Postgres, Mysql, + * Drupal 6/7/8 is in use. A simpler sanitize function that + * always used default values and only worked with Drupal 6 + mysql + * appears in the drush.api.php. @see hook_drush_sql_sync_sanitize(). + */ +function sql_drush_sql_sync_sanitize($site) { + $site_settings = drush_sitealias_get_record($site); + $databases = sitealias_get_databases_from_record($site_settings); + $major_version = drush_drupal_major_version(); + $wrap_table_name = (bool) drush_get_option('db-prefix'); + $user_table_updates = array(); + $message_list = array(); + + // Sanitize passwords. + $newpassword = drush_get_option(array('sanitize-password', 'destination-sanitize-password'), drush_generate_password()); + if ($newpassword != 'no' && $newpassword !== 0) { + $pw_op = ""; + + // In Drupal 6, passwords are hashed via the MD5 algorithm. + if ($major_version == 6) { + $pw_op = "MD5('$newpassword')"; + } + // In Drupal 7, passwords are hashed via a more complex algorithm, + // available via the user_hash_password function. + elseif ($major_version == 7) { + $core = DRUSH_DRUPAL_CORE; + include_once $core . '/includes/password.inc'; + include_once $core . '/includes/bootstrap.inc'; + $hash = user_hash_password($newpassword); + $pw_op = "'$hash'"; + } + else { + // D8+. Mimic Drupal's /scripts/password-hash.sh + drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_FULL); + $password_hasher = \Drupal::service('password'); + $hash = $password_hasher->hash($newpassword); + $pw_op = "'$hash'"; + } + if (!empty($pw_op)) { + $user_table_updates[] = "pass = $pw_op"; + $message_list[] = "passwords"; + } + } + + // Sanitize email addresses. + $newemail = drush_get_option(array('sanitize-email', 'destination-sanitize-email'), 'user+%uid@localhost.localdomain'); + if ($newemail != 'no' && $newemail !== 0) { + if (strpos($newemail, '%') !== FALSE) { + // We need a different sanitization query for Postgres and Mysql. + + $db_driver = $databases['default']['default']['driver']; + if ($db_driver == 'pgsql') { + $email_map = array('%uid' => "' || uid || '", '%mail' => "' || replace(mail, '@', '_') || '", '%name' => "' || replace(name, ' ', '_') || '"); + $newmail = "'" . str_replace(array_keys($email_map), array_values($email_map), $newemail) . "'"; + } + elseif ($db_driver == 'mssql') { + $email_map = array('%uid' => "' + uid + '", '%mail' => "' + replace(mail, '@', '_') + '", '%name' => "' + replace(name, ' ', '_') + '"); + $newmail = "'" . str_replace(array_keys($email_map), array_values($email_map), $newemail) . "'"; + } + else { + $email_map = array('%uid' => "', uid, '", '%mail' => "', replace(mail, '@', '_'), '", '%name' => "', replace(name, ' ', '_'), '"); + $newmail = "concat('" . str_replace(array_keys($email_map), array_values($email_map), $newemail) . "')"; + } + $user_table_updates[] = "mail = $newmail, init = $newmail"; + } + else { + $user_table_updates[] = "mail = '$newemail', init = '$newemail'"; + } + $message_list[] = 'email addresses'; + } + + if (!empty($user_table_updates)) { + $table = $major_version >= 8 ? 'users_field_data' : 'users'; + if ($wrap_table_name) { + $table = "{{$table}}"; + } + $sanitize_query = "UPDATE {$table} SET " . implode(', ', $user_table_updates) . " WHERE uid > 0;"; + drush_sql_register_post_sync_op('user-email', dt('Reset !message in !table table', array('!message' => implode(' and ', $message_list), '!table' => $table)), $sanitize_query); + } + + $sanitizer = new \Drush\Commands\core\SanitizeCommands(); + $sanitizer->doSanitize($major_version); +} diff --git a/vendor/drush/drush/commands/sql/sqlsync.drush.inc b/vendor/drush/drush/commands/sql/sqlsync.drush.inc new file mode 100644 index 0000000000..8cc1a687f1 --- /dev/null +++ b/vendor/drush/drush/commands/sql/sqlsync.drush.inc @@ -0,0 +1,283 @@ + 'Copies the database contents from a source site to a target site. Transfers the database dump via rsync.', + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'drush dependencies' => array('sql', 'core'), // core-rsync. + 'package' => 'sql', + 'examples' => array( + 'drush sql-sync @source @target' => 'Copy the database from the site with the alias "source" to the site with the alias "target".', + 'drush sql-sync prod dev' => 'Copy the database from the site in /sites/prod to the site in /sites/dev (multisite installation).', + ), + 'arguments' => array( + 'source' => 'A site-alias or the name of a subdirectory within /sites whose database you want to copy from.', + 'target' => 'A site-alias or the name of a subdirectory within /sites whose database you want to replace.', + ), + 'required-arguments' => TRUE, + 'options' => drush_sql_get_table_selection_options() + array( + // 'cache' => 'Skip dump if result file exists and is less than "cache" hours old. Optional; default is 24 hours.', + // 'no-cache' => 'Do not cache the sql-dump file.', + 'no-dump' => 'Do not dump the sql database; always use an existing dump file.', + 'no-sync' => 'Do not rsync the database dump file from source to target.', + 'runner' => 'Where to run the rsync command; defaults to the local site. Can also be "source" or "destination".', + 'source-db-url' => 'Database specification for source system to dump from.', + 'source-remote-port' => 'Override sql database port number in source-db-url. Optional.', + 'source-remote-host' => 'Remote machine to run sql-dump file on. Optional; default is local machine.', + 'source-dump' => array( + 'description' => 'The destination for the dump file, or the path to the dump file when --no-dump is specified.', + 'example-value' => '/dumpdir/db.sql', + ), + 'source-database' => 'A key in the $db_url (D6) or $databases (D7+) array which provides the data.', + 'source-target' => array( + 'description' => 'A key within the SOURCE database identifying a particular server in the database group.', + 'example-value' => 'key', + // Gets unhidden in help_alter(). We only want to show to D7+ users but have to + // declare it here since this command does not bootstrap fully. + 'hidden' => TRUE, + ), + 'target-db-url' => '', + 'target-remote-port' => '', + 'target-remote-host' => '', + 'target-dump' => array( + 'description' => 'A path for saving the dump file on target. Mandatory when using --no-sync.', + 'example-value' => '/dumpdir/db.sql.gz', + ), + 'target-database' => 'A key in the $db_url (D6) or $databases (D7+) array which shall receive the data.', + 'target-target' => array( + 'description' => 'Oy. A key within the TARGET database identifying a particular server in the database group.', + 'example-value' => 'key', + // Gets unhidden in help_alter(). We only want to show to D7+ users but have to + // declare it here since this command does not bootstrap fully. + 'hidden' => TRUE, + ), + 'create-db' => 'Create a new database before importing the database dump on the target machine.', + 'db-su' => array( + 'description' => 'Account to use when creating a new database. Optional.', + 'example-value' => 'root', + ), + 'db-su-pw' => array( + 'description' => 'Password for the "db-su" account. Optional.', + 'example-value' => 'pass', + ), + // 'no-ordered-dump' => 'Do not pass --ordered-dump to sql-dump. sql-sync orders the dumpfile by default in order to increase the efficiency of rsync.', + 'sanitize' => 'Obscure email addresses and reset passwords in the user table post-sync.', + ), + 'sub-options' => array( + 'sanitize' => drupal_sanitize_options() + array( + 'confirm-sanitizations' => 'Prompt yes/no after importing the database, but before running the sanitizations', + ), + ), + 'topics' => array('docs-aliases', 'docs-policy', 'docs-example-sync-via-http', 'docs-example-sync-extension'), + ); + return $items; +} + +/** + * Implements hook_drush_help_alter(). + */ +function sqlsync_drush_help_alter(&$command) { + // Drupal 7+ only options. + if (drush_drupal_major_version() >= 7) { + if ($command['command'] == 'sql-sync') { + unset($command['options']['source-target']['hidden'], $command['options']['target-target']['hidden']); + } + } +} + +/** + * Command argument complete callback. + * + * @return + * Array of available site aliases. + */ +function sql_sql_sync_complete() { + return array('values' => array_keys(_drush_sitealias_all_list())); +} + +/* + * Implements COMMAND hook init. + */ +function drush_sql_sync_init($source, $destination) { + // Try to get @self defined when --uri was not provided. + drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_SITE); + + // Preflight destination in case it defines the alias used by the source + _drush_sitealias_get_record($destination); + + // After preflight, get source and destination settings + $source_settings = drush_sitealias_get_record($source); + $destination_settings = drush_sitealias_get_record($destination); + + // Apply command-specific options. + drush_sitealias_command_default_options($source_settings, 'source-'); + drush_sitealias_command_default_options($destination_settings, 'target-'); +} + +/* + * A command validate callback. + */ +function drush_sqlsync_sql_sync_validate($source, $destination) { + // Get destination info for confirmation prompt. + $source_settings = drush_sitealias_overlay_options(drush_sitealias_get_record($source), 'source-'); + $destination_settings = drush_sitealias_overlay_options(drush_sitealias_get_record($destination), 'target-'); + $source_db_spec = drush_sitealias_get_db_spec($source_settings, FALSE, 'source-'); + $target_db_spec = drush_sitealias_get_db_spec($destination_settings, FALSE, 'target-'); + $txt_source = (isset($source_db_spec['remote-host']) ? $source_db_spec['remote-host'] . '/' : '') . $source_db_spec['database']; + $txt_destination = (isset($target_db_spec['remote-host']) ? $target_db_spec['remote-host'] . '/' : '') . $target_db_spec['database']; + + // Validate. + if (empty($source_db_spec)) { + if (empty($source_settings)) { + return drush_set_error('DRUSH_ALIAS_NOT_FOUND', dt('Error: no alias record could be found for source !source', array('!source' => $source))); + } + return drush_set_error('DRUSH_DATABASE_NOT_FOUND', dt('Error: no database record could be found for source !source', array('!source' => $source))); + } + if (empty($target_db_spec)) { + if (empty($destination_settings)) { + return drush_set_error('DRUSH_ALIAS_NOT_FOUND', dt('Error: no alias record could be found for target !destination', array('!destination' => $destination))); + } + return drush_set_error('DRUSH_DATABASE_NOT_FOUND', dt('Error: no database record could be found for target !destination', array('!destination' => $destination))); + } + + if (drush_get_option('no-dump') && !drush_get_option('source-dump')) { + return drush_set_error('DRUSH_SOURCE_DUMP_MISSING', dt('The --source-dump option must be supplied when --no-dump is specified.')); + } + + if (drush_get_option('no-sync') && !drush_get_option('target-dump')) { + return drush_set_error('DRUSH_TARGET_DUMP_MISSING', dt('The --target-dump option must be supplied when --no-sync is specified.')); + } + + if (!drush_get_context('DRUSH_SIMULATE')) { + drush_print(dt("You will destroy data in !target and replace with data from !source.", array('!source' => $txt_source, '!target' => $txt_destination))); + // @todo Move sanitization prompts to here. They currently show much later. + if (!drush_confirm(dt('Do you really want to continue?'))) { + return drush_user_abort(); + } + } +} + +/* + * A command callback. + */ +function drush_sqlsync_sql_sync($source, $destination) { + $source_settings = drush_sitealias_overlay_options(drush_sitealias_get_record($source), 'source-'); + $destination_settings = drush_sitealias_overlay_options(drush_sitealias_get_record($destination), 'target-'); + $source_is_local = !array_key_exists('remote-host', $source_settings) || drush_is_local_host($source_settings); + $destination_is_local = !array_key_exists('remote-host', $destination_settings) || drush_is_local_host($destination_settings); + + // These options are passed along to subcommands like sql-create, sql-dump, sql-query, sql-sanitize, ... + $source_options = drush_get_merged_prefixed_options('source-'); + $target_options = drush_get_merged_prefixed_options('target-'); + + $backend_options = array(); + // @todo drush_redispatch_get_options() assumes you will execute same command. Not good. + $global_options = drush_redispatch_get_options() + array( + 'strict' => 0, + ); + // We do not want to include root or uri here. If the user + // provided -r or -l, their key has already been remapped to + // 'root' or 'uri' by the time we get here. + unset($global_options['root']); + unset($global_options['uri']); + + if (drush_get_context('DRUSH_SIMULATE')) { + $backend_options['backend-simulate'] = TRUE; + } + + // Create destination DB if needed. + if (drush_get_option('create-db')) { + drush_log(dt('Starting to create database on Destination.'), LogLevel::OK); + $return = drush_invoke_process($destination, 'sql-create', array(), $global_options + $target_options, $backend_options); + if ($return['error_status']) { + return drush_set_error('DRUSH_SQL_CREATE_FAILED', dt('sql-create failed.')); + } + } + + // Perform sql-dump on source unless told otherwise. + $options = $global_options + $source_options + array( + 'gzip' => TRUE, + 'result-file' => drush_get_option('source-dump', TRUE), + // 'structure-tables-list' => 'cache*', // Do we want to default to this? + ); + if (!drush_get_option('no-dump')) { + drush_log(dt('Starting to dump database on Source.'), LogLevel::OK); + $return = drush_invoke_process($source, 'sql-dump', array(), $options, $backend_options); + if ($return['error_status']) { + return drush_set_error('DRUSH_SQL_DUMP_FAILED', dt('sql-dump failed.')); + } + else { + $source_dump_path = $return['object']; + if (!is_string($source_dump_path)) { + return drush_set_error('DRUSH_SQL_DUMP_FILE_NOT_REPORTED', dt('The Drush sql-dump command did not report the path to the dump file produced. Try upgrading the version of Drush you are using on the source machine.')); + } + } + } + else { + $source_dump_path = drush_get_option('source-dump'); + } + + $do_rsync = !drush_get_option('no-sync'); + // Determine path/to/dump on destination. + if (drush_get_option('target-dump')) { + $destination_dump_path = drush_get_option('target-dump'); + $rsync_options['yes'] = TRUE; // @temporary: See https://github.com/drush-ops/drush/pull/555 + } + elseif ($source_is_local && $destination_is_local) { + $destination_dump_path = $source_dump_path; + $do_rsync = FALSE; + } + else { + $tmp = '/tmp'; // Our fallback plan. + drush_log(dt('Starting to discover temporary files directory on Destination.'), LogLevel::OK); + $return = drush_invoke_process($destination, 'core-status', array(), array(), array('integrate' => FALSE, 'override-simulated' => TRUE)); + if (!$return['error_status'] && isset($return['object']['drush-temp'])) { + $tmp = $return['object']['drush-temp']; + } + $destination_dump_path = Path::join($tmp, basename($source_dump_path)); + $rsync_options['yes'] = TRUE; // No need to prompt as destination is a tmp file. + } + + if ($do_rsync) { + if (!drush_get_option('no-dump')) { + // Cleanup if this command created the dump file. + $rsync_options['remove-source-files'] = TRUE; + } + $runner = drush_get_runner($source_settings, $destination_settings, drush_get_option('runner', FALSE)); + // Since core-rsync is a strict-handling command and drush_invoke_process() puts options at end, we can't send along cli options to rsync. + // Alternatively, add options like --ssh-options to a site alias (usually on the machine that initiates the sql-sync). + $return = drush_invoke_process($runner, 'core-rsync', array("$source:$source_dump_path", "$destination:$destination_dump_path"), $rsync_options); + drush_log(dt('Copying dump file from Source to Destination.'), LogLevel::OK); + if ($return['error_status']) { + return drush_set_error('DRUSH_RSYNC_FAILED', dt('core-rsync failed.')); + } + } + + // Import file into destination. + drush_log(dt('Starting to import dump file onto Destination database.'), LogLevel::OK); + $options = $global_options + $target_options + array( + 'file' => $destination_dump_path, + 'file-delete' => TRUE, + ); + $return = drush_invoke_process($destination, 'sql-query', array(), $options, $backend_options); + if ($return['error_status']) { + // An error was already logged. + return FALSE; + } + + // Run Sanitize if needed. + $options = $global_options + $target_options; + if (drush_get_option('sanitize')) { + drush_log(dt('Starting to sanitize target database on Destination.'), LogLevel::OK); + $return = drush_invoke_process($destination, 'sql-sanitize', array(), $options, $backend_options); + if ($return['error_status']) { + return drush_set_error('DRUSH_SQL_SANITIZE_FAILED', dt('sql-sanitize failed.')); + } + } +} diff --git a/vendor/drush/drush/commands/user/user.drush.inc b/vendor/drush/drush/commands/user/user.drush.inc new file mode 100644 index 0000000000..10e727f6b4 --- /dev/null +++ b/vendor/drush/drush/commands/user/user.drush.inc @@ -0,0 +1,421 @@ + array( + 'description' => 'A comma delimited list of uids of users to operate on.', + 'example-value' => '3,5', + 'value' => 'required', + ), + 'name' => array( + 'description' => 'A comma delimited list of user names of users to operate on.', + 'example-value' => 'foo', + 'value' => 'required', + ), + 'mail' => array( + 'description' => 'A comma delimited list of user mail addresses of users to operate on.', + 'example-value' => 'me@example.com', + 'value' => 'required', + ) + ); + + $items['user-information'] = array( + 'description' => 'Print information about the specified user(s).', + 'aliases' => array('uinf', 'user:information'), + 'examples' => array( + 'drush user-information 2,3,someguy,somegal,billgates@microsoft.com' => + 'Display information about the listed users.', + ), + 'arguments' => array( + 'users' => 'A comma delimited list of uids, user names, or email addresses.', + ), + 'required-arguments' => TRUE, + 'outputformat' => array( + 'default' => 'key-value-list', + 'pipe-format' => 'csv', + 'field-labels' => array( + 'uid' => 'User ID', + 'name' => 'User name', + 'pass' => 'Password', + 'mail' => 'User mail', + 'theme' => 'User theme', + 'signature' => 'Signature', + 'signature_format' => 'Signature format', + 'user_created' => 'User created', + 'created' => 'Created', + 'user_access' => 'User last access', + 'access' => 'Last access', + 'user_login' => 'User last login', + 'login' => 'Last login', + 'user_status' => 'User status', + 'status' => 'Status', + 'timezone' => 'Time zone', + 'picture' => 'User picture', + 'init' => 'Initial user mail', + 'roles' => 'User roles', + 'group_audience' => 'Group Audience', + 'langcode' => 'Language code', + 'uuid' => 'Uuid', + ), + 'format-cell' => 'csv', + 'fields-default' => array('uid', 'name', 'mail', 'roles', 'user_status'), + 'fields-pipe' => array('name', 'uid', 'mail', 'status', 'roles'), + 'fields-full' => array('uid', 'name', 'pass', 'mail', 'theme', 'signature', 'user_created', 'user_access', 'user_login', 'user_status', 'timezone', 'roles', 'group_audience', 'langcode', 'uuid'), + 'output-data-type' => 'format-table', + ), + ); + $items['user-block'] = array( + 'description' => 'Block the specified user(s).', + 'aliases' => array('ublk', 'user:block'), + 'arguments' => array( + 'users' => 'A comma delimited list of uids, user names, or email addresses.', + ), + 'examples' => array( + 'drush user-block 5,user3 --uid=2,3 --name=someguy,somegal --mail=billgates@microsoft.com' => + 'Block the users with name, id, or email 5 or user3, uids 2 and 3, names someguy and somegal, and email address of billgates@microsoft.com', + ), + 'options' => $options_common, + ); + $items['user-unblock'] = array( + 'description' => 'Unblock the specified user(s).', + 'aliases' => array('uublk', 'user:unblock'), + 'arguments' => array( + 'users' => 'A comma delimited list of uids, user names, or email addresses.', + ), + 'examples' => array( + 'drush user-unblock 5,user3 --uid=2,3 --name=someguy,somegal --mail=billgates@microsoft.com' => + 'Unblock the users with name, id, or email 5 or user3, uids 2 and 3, names someguy and somegal, and email address of billgates@microsoft.com', + ), + 'options' => $options_common, + ); + $items['user-add-role'] = array( + 'description' => 'Add a role to the specified user accounts.', + 'aliases' => array('urol', 'user:add:role'), + 'arguments' => array( + 'role' => 'The name of the role to add', + 'users' => '(optional) A comma delimited list of uids, user names, or email addresses.', + ), + 'required-arguments' => 1, + 'examples' => array( + 'drush user-add-role "power user" 5,user3 --uid=2,3 --name=someguy,somegal --mail=billgates@microsoft.com' => + 'Add the "power user" role to the accounts with name, id, or email 5 or user3, uids 2 and 3, names someguy and somegal, and email address of billgates@microsoft.com', + ), + 'options' => $options_common, + ); + $items['user-remove-role'] = array( + 'description' => 'Remove a role from the specified user accounts.', + 'aliases' => array('urrol', 'user:remove:role'), + 'arguments' => array( + 'role' => 'The name of the role to remove', + 'users' => '(optional) A comma delimited list of uids, user names, or email addresses.', + ), + 'required-arguments' => 1, + 'examples' => array( + 'drush user-remove-role "power user" 5,user3 --uid=2,3 --name=someguy,somegal --mail=billgates@microsoft.com' => + 'Remove the "power user" role from the accounts with name, id, or email 5 or user3, uids 2 and 3, names someguy and somegal, and email address of billgates@microsoft.com', + ), + 'options' => $options_common, + ); + $items['user-create'] = array( + 'description' => 'Create a user account with the specified name.', + 'aliases' => array('ucrt', 'user:create'), + 'arguments' => array( + 'name' => 'The name of the account to add' + ), + 'required-arguments' => TRUE, + 'examples' => array( + 'drush user-create newuser --mail="person@example.com" --password="letmein"' => + 'Create a new user account with the name newuser, the email address person@example.com, and the password letmein', + ), + 'options' => array( + 'password' => 'The password for the new account', + 'mail' => 'The email address for the new account', + ), + 'outputformat' => $items['user-information']['outputformat'], + ); + $items['user-cancel'] = array( + 'description' => 'Cancel a user account with the specified name.', + 'aliases' => array('ucan', 'user:cancel'), + 'arguments' => array( + 'name' => 'The name of the account to cancel', + ), + // The `_user_cancel` method still references global $user. + // @todo remove once https://www.drupal.org/node/2163205 is in. + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_LOGIN, + 'required-arguments' => TRUE, + 'examples' => array( + 'drush user-cancel username' => + 'Cancel the user account with the name username and anonymize all content created by that user.', + ), + ); + $items['user-password'] = array( + 'description' => '(Re)Set the password for the user account with the specified name.', + 'aliases' => array('upwd', 'user:password'), + 'arguments' => array( + 'name' => 'The name of the account to modify.' + ), + 'required-arguments' => TRUE, + 'options' => array( + 'password' => array( + 'description' => 'The new password for the account.', + 'required' => TRUE, + 'example-value' => 'foo', + ), + ), + 'examples' => array( + 'drush user-password someuser --password="correct horse battery staple"' => + 'Set the password for the username someuser. @see xkcd.com/936', + ), + ); + $items['user-login'] = array( + 'description' => 'Display a one time login link for the given user account (defaults to uid 1).', + 'aliases' => array('uli', 'user:login'), + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'handle-remote-commands' => TRUE, + 'arguments' => array( + 'user' => 'An optional uid, user name, or email address for the user to log in as. Default is to log in as uid 1. The uid/name/mail options take priority if specified.', + 'path' => 'Optional path to redirect to after logging in.', + ), + 'options' => array( + 'browser' => 'Optional value denotes which browser to use (defaults to operating system default). Use --no-browser to suppress opening a browser.', + 'uid' => 'A uid to log in as.', + 'redirect-port' => 'A custom port for redirecting to (e.g. when running within a Vagrant environment)', + 'name' => 'A user name to log in as.', + 'mail' => 'A user mail address to log in as.', + ), + 'examples' => array( + 'drush user-login ryan node/add/blog' => 'Displays and opens default web browser (if configured or detected) for a one-time login link for the user with the username ryan and redirect to the path node/add/blog.', + 'drush user-login --browser=firefox --mail=drush@example.org admin/settings/performance' => 'Open firefox web browser, login as the user with the e-mail address drush@example.org and redirect to the path admin/settings/performance.', + ), + ); + return $items; +} + +/** + * Implements hook_drush_help_alter(). + */ +function user_drush_help_alter(&$command) { + // Drupal 7+ only options. + if ($command['command'] == 'user-cancel' && drush_drupal_major_version() >= 7) { + $command['options']['delete-content'] = 'Delete all content created by the user'; + $command['examples']['drush user-cancel --delete-content username'] = + 'Cancel the user account with the name username and delete all content created by that user.'; + } +} + +/** + * Get a version specific UserSingle class. + * + * @param $account + * @return \Drush\User\UserSingleBase + * + * @see drush_get_class(). + */ +function drush_usersingle_get_class($account) { + return drush_get_class('Drush\User\UserSingle', array($account)); +} + +/** + * Get a version specific User class. + * + * @return \Drush\User\UserVersion + * + * @see drush_get_class(). + */ +function drush_user_get_class() { + return drush_get_class('Drush\User\User'); +} + +/** + * Command callback. Prints information about the specified user(s). + */ +function drush_user_information($users) { + $userlist = new UserList($users); + $info = $userlist->each('info'); + return $info; +} + +/** + * Block the specified user(s). + */ +function drush_user_block($users = '') { + $userlist = new UserList($users); + $userlist->each('block'); + drush_log(dt('Blocked user(s): !users', array('!users' => $userlist->names())), LogLevel::SUCCESS); +} + +/** + * Unblock the specified user(s). + */ +function drush_user_unblock($users = '') { + $userlist = new UserList($users); + $userlist->each('unblock'); + drush_log(dt('Unblocked user(s): !users', array('!users' => $userlist->names())), LogLevel::SUCCESS); +} + +/** + * Add a role to the specified user accounts. + */ +function drush_user_add_role($role, $users = '') { + // If role is not found, an exception gets thrown and handled by command invoke. + $role_object = drush_role_get_class($role); + $userlist = new UserList($users); + $userlist->each('addRole', array($role_object->rid)); + drush_log(dt('Added role !role role to !users', array('!role' => $role, '!users' => $userlist->names())),LogLevel::SUCCESS); +} + +/** + * Remove a role from the specified user accounts. + */ +function drush_user_remove_role($role, $users = '') { + // If role is not found, an exception gets thrown and handled by command invoke. + $role_object = drush_role_get_class($role); + $userlist = new UserList($users); + $userlist->each('removeRole', array($role_object->rid)); + drush_log(dt('Removed !role role from !users', array('!role' => $role, '!users' => $userlist->names())),LogLevel::SUCCESS); +} + +/** + * Creates a new user account. + */ +function drush_user_create($name) { + $userversion = drush_user_get_class(); + $mail = drush_get_option('mail'); + $pass = drush_get_option('password'); + $new_user = array( + 'name' => $name, + 'pass' => $pass, + 'mail' => $mail, + 'access' => '0', + 'status' => 1, + ); + if (!drush_get_context('DRUSH_SIMULATE')) { + if ($account = $userversion->create($new_user)) { + return array($account->id() => $account->info()); + } + else { + return drush_set_error("Could not create a new user account with the name " . $name . "."); + } + } +} + +function drush_user_create_validate($name) { + $userversion = drush_user_get_class(); + if ($mail = drush_get_option('mail')) { + if ($userversion->load_by_mail($mail)) { + return drush_set_error(dt('There is already a user account with the email !mail', array('!mail' => $mail))); + } + } + if ($userversion->load_by_name($name)) { + return drush_set_error(dt('There is already a user account with the name !name', array('!name' => $name))); + } +} + +/** + * Cancels a user account. + */ +function drush_user_cancel($inputs) { + $userlist = new UserList($inputs); + foreach ($userlist->accounts as $account) { + if (drush_get_option('delete-content') && drush_drupal_major_version() >= 7) { + drush_print(dt('All content created by !name will be deleted.', array('!name' => $account->getUsername()))); + } + if (drush_confirm('Cancel user account?: ')) { + $account->cancel(); + } + } + drush_log(dt('Cancelled user(s): !users', array('!users' => $userlist->names())),LogLevel::SUCCESS); +} + +/** + * Sets the password for the account with the given username + */ +function drush_user_password($inputs) { + $userlist = new UserList($inputs); + if (!drush_get_context('DRUSH_SIMULATE')) { + $pass = drush_get_option('password'); + // If no password has been provided, prompt for one. + if (empty($pass)) { + $pass = drush_prompt(dt('Password'), NULL, TRUE, TRUE); + } + foreach ($userlist->accounts as $account) { + $userlist->each('password', array($pass)); + } + drush_log(dt('Changed password for !users', array('!users' => $userlist->names())), LogLevel::SUCCESS); + } +} + +/** + * Displays a one time login link for the given user. + */ +function drush_user_login($inputs = '', $path = NULL) { + $args = func_get_args(); + + // Redispatch if called against a remote-host so a browser is started on the + // the *local* machine. + $alias = drush_get_context('DRUSH_TARGET_SITE_ALIAS'); + if (drush_sitealias_is_remote_site($alias)) { + $return = drush_invoke_process($alias, 'user-login', $args, drush_redispatch_get_options(), array('integrate' => FALSE, 'convert-stdout-to-backend' => TRUE)); + + if ($return['error_status']) { + return drush_set_error('Unable to execute user login.'); + } + else { + // Prior versions of Drupal returned a string so cast to an array if needed. + $links = !isset($return['object']) ? [ $return['output'] ] : (is_string($return['object']) ? array($return['object']) : $return['object']); + } + } + else { + if (!drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_FULL)) { + // Fail gracefully if unable to bootstrap Drupal. + // drush_bootstrap() has already logged an error. + return FALSE; + } + + if (drush_get_option('uid', FALSE) || drush_get_option('name', FALSE) || drush_get_option('mail', FALSE)) { + // If we only have a single argument and one of the user options is passed, + // then we assume the argument is the path to open. + if (count($args) == 1) { + $path = $args[0]; + } + } + + // Try to load a user from provided options and arguments. + try { + $userlist = new UserList($inputs); + } + catch (UserListException $e) { + // No user option or argument was passed, so we default to uid 1. + $userlist = new UserList(1); + } + $links = $userlist->each('passResetUrl', array($path)); + } + $port = drush_get_option('redirect-port', FALSE); + // There is almost always only one link so pick the first one for display and browser. + // The full array is sent on backend calls. + $first = current($links); + drush_start_browser($first, FALSE, $port); + drush_backend_set_result($links); + return $first; +} diff --git a/vendor/drush/drush/commands/xh.drush.inc b/vendor/drush/drush/commands/xh.drush.inc new file mode 100644 index 0000000000..7a3cfef53f --- /dev/null +++ b/vendor/drush/drush/commands/xh.drush.inc @@ -0,0 +1,103 @@ + 'Enable profiling via XHProf', + ); + $command['sub-options']['xh']['xh-link'] = 'URL to your XHProf report site.'; + $command['sub-options']['xh']['xh-path'] = 'Absolute path to the xhprof project.'; + $command['sub-options']['xh']['xh-profile-builtins'] = 'Profile built-in PHP functions (defaults to TRUE).'; + $command['sub-options']['xh']['xh-profile-cpu'] = 'Profile CPU (defaults to FALSE).'; + $command['sub-options']['xh']['xh-profile-memory'] = 'Profile Memory (defaults to FALSE).'; + } + } +} + +function xh_enabled() { + if (drush_get_option('xh')) { + if (!extension_loaded('xhprof')) { + return drush_set_error('You must enable the xhprof PHP extension in your CLI PHP in order to profile.'); + } + if (!drush_get_option('xh-path', '')) { + return drush_set_error('You must provide the xh-path option in your drushrc or on the CLI in order to profile.'); + } + return TRUE; + } +} + +/** + * Determines flags for xhprof_enable based on drush options. + */ +function xh_flags() { + $flags = 0; + if (!drush_get_option('xh-profile-builtins', XH_PROFILE_BUILTINS)) { + $flags |= XHPROF_FLAGS_NO_BUILTINS; + } + if (drush_get_option('xh-profile-cpu', XH_PROFILE_CPU)) { + $flags |= XHPROF_FLAGS_CPU; + } + if (drush_get_option('xh-profile-memory', XH_PROFILE_MEMORY)) { + $flags |= XHPROF_FLAGS_MEMORY; + } + return $flags; +} + +/** + * Implements hook_drush_init(). + */ +function xh_drush_init() { + if (xh_enabled()) { + if ($path = drush_get_option('xh-path', '')) { + include_once $path . '/xhprof_lib/utils/xhprof_lib.php'; + include_once $path . '/xhprof_lib/utils/xhprof_runs.php'; + xhprof_enable(xh_flags()); + } + } +} + +/** + * Implements hook_drush_exit(). + */ +function xh_drush_exit() { + if (xh_enabled()) { + $args = func_get_args(); + $namespace = 'Drush'; // variable_get('site_name', ''); + $xhprof_data = xhprof_disable(); + $xhprof_runs = new XHProfRuns_Default(); + $run_id = $xhprof_runs->save_run($xhprof_data, $namespace); + if ($url = xh_link($run_id)) { + drush_log(dt('XHProf run saved. View report at !url', array('!url' => $url)), LogLevel::OK); + } + } +} + +/** + * Returns the XHProf link. + */ +function xh_link($run_id) { + if ($xhprof_url = trim(drush_get_option('xh-link'))) { + $namespace = 'Drush'; //variable_get('site_name', ''); + return $xhprof_url . '/index.php?run=' . urlencode($run_id) . '&source=' . urlencode($namespace); + } + else { + drush_log('Configure xh_link in order to see a link to the XHProf report for this request instead of this message.'); + } +} diff --git a/vendor/drush/drush/composer.json b/vendor/drush/drush/composer.json new file mode 100644 index 0000000000..eed82db663 --- /dev/null +++ b/vendor/drush/drush/composer.json @@ -0,0 +1,93 @@ +{ + "name": "drush/drush", + "description": "Drush is a command line shell and scripting interface for Drupal, a veritable Swiss Army knife designed to make life easier for those of us who spend some of our working hours hacking away at the command prompt.", + "homepage": "http://www.drush.org", + "license": "GPL-2.0-or-later", + "minimum-stability": "stable", + "prefer-stable": true, + "authors": [ + { "name": "Moshe Weitzman", "email": "weitzman@tejasa.com" }, + { "name": "Owen Barton", "email": "drupal@owenbarton.com" }, + { "name": "Mark Sonnabaum", "email": "marksonnabaum@gmail.com" }, + { "name": "Antoine Beaupré", "email": "anarcat@koumbit.org" }, + { "name": "Greg Anderson", "email": "greg.1.anderson@greenknowe.org" }, + { "name": "Jonathan Araña Cruz", "email": "jonhattan@faita.net" }, + { "name": "Jonathan Hedstrom", "email": "jhedstrom@gmail.com" }, + { "name": "Christopher Gervais", "email": "chris@ergonlogic.com" }, + { "name": "Dave Reid", "email": "dave@davereid.net" }, + { "name": "Damian Lee", "email": "damiankloip@googlemail.com" } + ], + "support": { + "forum": "http://drupal.stackexchange.com/questions/tagged/drush", + "irc": "irc://irc.freenode.org/drush" + }, + "bin": [ + "drush", + "drush.launcher", + "drush.php", + "drush.complete.sh" + ], + "config": { + "platform": { + "php": "5.4.5" + } + }, + "require": { + "php": ">=5.4.5", + "psr/log": "~1.0", + "psy/psysh": "~0.6", + "consolidation/annotated-command": "^2.12.0", + "consolidation/output-formatters": "~3", + "symfony/yaml": "~2.3|^3|^4.4", + "symfony/var-dumper": "~2.7|^3|^4.4|^5", + "symfony/console": "~2.7|^3|^4.4", + "symfony/event-dispatcher": "~2.7|^3|^4.4", + "symfony/finder": "~2.7|^3|^4.4", + "symfony/process": "~2.7|^3|^4.4", + "pear/console_table": "~1.3.1", + "webflo/drupal-finder": "^1.1.0", + "webmozart/path-util": "~2" + }, + "require-dev": { + "symfony/var-dumper": "~2.7", + "symfony/console": "~2.7", + "symfony/event-dispatcher": "~2.7", + "symfony/finder": "~2.7", + "symfony/yaml": "~2.3", + "symfony/process": "2.7.*", + "phpunit/phpunit": "^4 || ^7.5.20 || ^9", + "squizlabs/php_codesniffer": "^3", + "yoast/phpunit-polyfills": "^1" + }, + "suggest": { + "ext-pcntl": "*", + "drush/config-extra": "Provides configuration workflow commands, such as config-merge." + }, + "autoload": { + "psr-0": { + "Drush\\": "lib/", + "Consolidation\\": "lib/" + }, + "psr-4": { + "Drush\\": "src/" + } + }, + "autoload-dev": { + "psr-0": { + "Unish": "tests/" + } + }, + "scripts": { + "phar:install-tools": "curl -L https://github.com/box-project/box/releases/download/3.14.0/box.phar --output box.phar", + "phar:build": "php box.phar compile", + "lint": [ + "find includes -name '*.inc' -print0 | xargs -0 -n1 php -l", + "find lib/Drush -name '*.php' -print0 | xargs -0 -n1 php -l" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "8.3.x-dev" + } + } +} diff --git a/vendor/drush/drush/composer.lock b/vendor/drush/drush/composer.lock new file mode 100644 index 0000000000..f73ad33eb6 --- /dev/null +++ b/vendor/drush/drush/composer.lock @@ -0,0 +1,2516 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "391ffd2d6eb42f3bc99ab49863bc7fd2", + "packages": [ + { + "name": "consolidation/annotated-command", + "version": "2.12.2", + "source": { + "type": "git", + "url": "https://github.com/consolidation/annotated-command.git", + "reference": "2472a23610cba1d86dcb783a81a21259473b059e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/2472a23610cba1d86dcb783a81a21259473b059e", + "reference": "2472a23610cba1d86dcb783a81a21259473b059e", + "shasum": "" + }, + "require": { + "consolidation/output-formatters": "^3.5.1", + "php": ">=5.4.5", + "psr/log": "^1", + "symfony/console": "^2.8|^3|^4", + "symfony/event-dispatcher": "^2.5|^3|^4", + "symfony/finder": "^2.5|^3|^4|^5" + }, + "require-dev": { + "g1a/composer-test-scenarios": "^3", + "php-coveralls/php-coveralls": "^1", + "phpunit/phpunit": "^6", + "squizlabs/php_codesniffer": "^2.7" + }, + "type": "library", + "extra": { + "scenarios": { + "finder5": { + "require": { + "symfony/finder": "^5" + }, + "config": { + "platform": { + "php": "7.2.5" + } + } + }, + "symfony4": { + "require": { + "symfony/console": "^4.0" + }, + "config": { + "platform": { + "php": "7.1.3" + } + } + }, + "symfony2": { + "require": { + "symfony/console": "^2.8" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36" + }, + "remove": [ + "php-coveralls/php-coveralls" + ], + "config": { + "platform": { + "php": "5.4.8" + } + }, + "scenario-options": { + "create-lockfile": "false" + } + }, + "phpunit4": { + "require-dev": { + "phpunit/phpunit": "^4.8.36" + }, + "remove": [ + "php-coveralls/php-coveralls" + ], + "config": { + "platform": { + "php": "5.4.8" + } + } + } + }, + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Consolidation\\AnnotatedCommand\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Initialize Symfony Console commands from annotated command class methods.", + "support": { + "issues": "https://github.com/consolidation/annotated-command/issues", + "source": "https://github.com/consolidation/annotated-command/tree/2.12.2" + }, + "time": "2022-01-03T00:23:44+00:00" + }, + { + "name": "consolidation/output-formatters", + "version": "3.5.1", + "source": { + "type": "git", + "url": "https://github.com/consolidation/output-formatters.git", + "reference": "0d38f13051ef05c223a2bb8e962d668e24785196" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/0d38f13051ef05c223a2bb8e962d668e24785196", + "reference": "0d38f13051ef05c223a2bb8e962d668e24785196", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^1.1.0", + "php": ">=5.4.0", + "symfony/console": "^2.8|^3|^4", + "symfony/finder": "^2.5|^3|^4|^5" + }, + "require-dev": { + "g1a/composer-test-scenarios": "^3", + "php-coveralls/php-coveralls": "^1", + "phpunit/phpunit": "^5.7.27", + "squizlabs/php_codesniffer": "^2.7", + "symfony/var-dumper": "^2.8|^3|^4", + "victorjonsson/markdowndocs": "^1.3" + }, + "suggest": { + "symfony/var-dumper": "For using the var_dump formatter" + }, + "type": "library", + "extra": { + "scenarios": { + "finder5": { + "require": { + "symfony/finder": "^5" + }, + "config": { + "platform": { + "php": "7.2.5" + } + } + }, + "symfony4": { + "require": { + "symfony/console": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^6" + }, + "config": { + "platform": { + "php": "7.1.3" + } + } + }, + "symfony3": { + "require": { + "symfony/console": "^3.4", + "symfony/finder": "^3.4", + "symfony/var-dumper": "^3.4" + }, + "config": { + "platform": { + "php": "5.6.32" + } + } + }, + "symfony2": { + "require": { + "symfony/console": "^2.8" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36" + }, + "remove": [ + "php-coveralls/php-coveralls" + ], + "config": { + "platform": { + "php": "5.4.8" + } + }, + "scenario-options": { + "create-lockfile": "false" + } + } + }, + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Consolidation\\OutputFormatters\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Format text by applying transformations provided by plug-in formatters.", + "support": { + "issues": "https://github.com/consolidation/output-formatters/issues", + "source": "https://github.com/consolidation/output-formatters/tree/3.5.1" + }, + "time": "2020-10-11T04:15:32+00:00" + }, + { + "name": "dflydev/dot-access-data", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/3fbd874921ab2c041e899d044585a2ab9795df8a", + "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "Dflydev\\DotAccessData": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/master" + }, + "time": "2017-01-20T21:14:22+00:00" + }, + { + "name": "dnoegel/php-xdg-base-dir", + "version": "v0.1.1", + "source": { + "type": "git", + "url": "https://github.com/dnoegel/php-xdg-base-dir.git", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "XdgBaseDir\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "implementation of xdg base directory specification for php", + "support": { + "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues", + "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1" + }, + "time": "2019-12-04T15:06:13+00:00" + }, + { + "name": "jakub-onderka/php-console-color", + "version": "v0.2", + "source": { + "type": "git", + "url": "https://github.com/JakubOnderka/PHP-Console-Color.git", + "reference": "d5deaecff52a0d61ccb613bb3804088da0307191" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Color/zipball/d5deaecff52a0d61ccb613bb3804088da0307191", + "reference": "d5deaecff52a0d61ccb613bb3804088da0307191", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "jakub-onderka/php-code-style": "1.0", + "jakub-onderka/php-parallel-lint": "1.0", + "jakub-onderka/php-var-dump-check": "0.*", + "phpunit/phpunit": "~4.3", + "squizlabs/php_codesniffer": "1.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "JakubOnderka\\PhpConsoleColor\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Jakub Onderka", + "email": "jakub.onderka@gmail.com" + } + ], + "support": { + "issues": "https://github.com/JakubOnderka/PHP-Console-Color/issues", + "source": "https://github.com/JakubOnderka/PHP-Console-Color/tree/master" + }, + "abandoned": "php-parallel-lint/php-console-color", + "time": "2018-09-29T17:23:10+00:00" + }, + { + "name": "jakub-onderka/php-console-highlighter", + "version": "v0.4", + "source": { + "type": "git", + "url": "https://github.com/JakubOnderka/PHP-Console-Highlighter.git", + "reference": "9f7a229a69d52506914b4bc61bfdb199d90c5547" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Highlighter/zipball/9f7a229a69d52506914b4bc61bfdb199d90c5547", + "reference": "9f7a229a69d52506914b4bc61bfdb199d90c5547", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "jakub-onderka/php-console-color": "~0.2", + "php": ">=5.4.0" + }, + "require-dev": { + "jakub-onderka/php-code-style": "~1.0", + "jakub-onderka/php-parallel-lint": "~1.0", + "jakub-onderka/php-var-dump-check": "~0.1", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~1.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "JakubOnderka\\PhpConsoleHighlighter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jakub Onderka", + "email": "acci@acci.cz", + "homepage": "http://www.acci.cz/" + } + ], + "description": "Highlight PHP code in terminal", + "support": { + "issues": "https://github.com/JakubOnderka/PHP-Console-Highlighter/issues", + "source": "https://github.com/JakubOnderka/PHP-Console-Highlighter/tree/master" + }, + "abandoned": "php-parallel-lint/php-console-highlighter", + "time": "2018-09-29T18:48:56+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "4dd659edadffdc2143e4753df655d866dbfeedf0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4dd659edadffdc2143e4753df655d866dbfeedf0", + "reference": "4dd659edadffdc2143e4753df655d866dbfeedf0", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.4" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/2.x" + }, + "time": "2016-09-16T12:04:44+00:00" + }, + { + "name": "pear/console_table", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/pear/Console_Table.git", + "reference": "1930c11897ca61fd24b95f2f785e99e0f36dcdea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pear/Console_Table/zipball/1930c11897ca61fd24b95f2f785e99e0f36dcdea", + "reference": "1930c11897ca61fd24b95f2f785e99e0f36dcdea", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "suggest": { + "pear/Console_Color2": ">=0.1.2" + }, + "type": "library", + "autoload": { + "classmap": [ + "Table.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Jan Schneider", + "homepage": "http://pear.php.net/user/yunosh" + }, + { + "name": "Tal Peer", + "homepage": "http://pear.php.net/user/tal" + }, + { + "name": "Xavier Noguer", + "homepage": "http://pear.php.net/user/xnoguer" + }, + { + "name": "Richard Heyes", + "homepage": "http://pear.php.net/user/richard" + } + ], + "description": "Library that makes it easy to build console style tables.", + "homepage": "http://pear.php.net/package/Console_Table/", + "keywords": [ + "console" + ], + "support": { + "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Console_Table", + "source": "https://github.com/pear/Console_Table" + }, + "time": "2018-01-25T20:47:17+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" + }, + { + "name": "psy/psysh", + "version": "v0.9.12", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/psysh.git", + "reference": "90da7f37568aee36b116a030c5f99c915267edd4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/90da7f37568aee36b116a030c5f99c915267edd4", + "reference": "90da7f37568aee36b116a030c5f99c915267edd4", + "shasum": "" + }, + "require": { + "dnoegel/php-xdg-base-dir": "0.1.*", + "ext-json": "*", + "ext-tokenizer": "*", + "jakub-onderka/php-console-highlighter": "0.3.*|0.4.*", + "nikic/php-parser": "~1.3|~2.0|~3.0|~4.0", + "php": ">=5.4.0", + "symfony/console": "~2.3.10|^2.4.2|~3.0|~4.0|~5.0", + "symfony/var-dumper": "~2.7|~3.0|~4.0|~5.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.2", + "hoa/console": "~2.15|~3.16", + "phpunit/phpunit": "~4.8.35|~5.0|~6.0|~7.0" + }, + "suggest": { + "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", + "ext-pdo-sqlite": "The doc command requires SQLite to work.", + "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well.", + "ext-readline": "Enables support for arrow-key history navigation, and showing and manipulating command history.", + "hoa/console": "A pure PHP readline implementation. You'll want this if your PHP install doesn't already support readline or libedit." + }, + "bin": [ + "bin/psysh" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-develop": "0.9.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Psy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "An interactive shell for modern PHP.", + "homepage": "http://psysh.org", + "keywords": [ + "REPL", + "console", + "interactive", + "shell" + ], + "support": { + "issues": "https://github.com/bobthecow/psysh/issues", + "source": "https://github.com/bobthecow/psysh/tree/v0.9.12" + }, + "time": "2019-12-06T14:19:43+00:00" + }, + { + "name": "symfony/console", + "version": "v2.8.52", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "cbcf4b5e233af15cd2bbd50dee1ccc9b7927dc12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/cbcf4b5e233af15cd2bbd50dee1ccc9b7927dc12", + "reference": "cbcf4b5e233af15cd2bbd50dee1ccc9b7927dc12", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/debug": "^2.7.2|~3.0.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/event-dispatcher": "~2.1|~3.0.0", + "symfony/process": "~2.1|~3.0.0" + }, + "suggest": { + "psr/log-implementation": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/console/tree/v2.8.52" + }, + "time": "2018-11-20T15:55:20+00:00" + }, + { + "name": "symfony/debug", + "version": "v2.8.52", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "74251c8d50dd3be7c4ce0c7b862497cdc641a5d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/74251c8d50dd3be7c4ce0c7b862497cdc641a5d0", + "reference": "74251c8d50dd3be7c4ce0c7b862497cdc641a5d0", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/class-loader": "~2.2|~3.0.0", + "symfony/http-kernel": "~2.3.24|~2.5.9|^2.6.2|~3.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Debug Component", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/debug/tree/v2.8.50" + }, + "time": "2018-11-11T11:18:13+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v2.8.52", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a77e974a5fecb4398833b0709210e3d5e334ffb0", + "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^2.0.5|~3.0.0", + "symfony/dependency-injection": "~2.6|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/stopwatch": "~2.3|~3.0.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v2.8.50" + }, + "time": "2018-11-21T14:20:20+00:00" + }, + { + "name": "symfony/finder", + "version": "v2.8.52", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "1444eac52273e345d9b95129bf914639305a9ba4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/1444eac52273e345d9b95129bf914639305a9ba4", + "reference": "1444eac52273e345d9b95129bf914639305a9ba4", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v2.8.50" + }, + "time": "2018-11-11T11:18:13+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.19.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "aed596913b70fae57be53d86faa2e9ef85a2297b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/aed596913b70fae57be53d86faa2e9ef85a2297b", + "reference": "aed596913b70fae57be53d86faa2e9ef85a2297b", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.19-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.19.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-10-23T09:01:57+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.19.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "b5f7b932ee6fa802fc792eabd77c4c88084517ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b5f7b932ee6fa802fc792eabd77c4c88084517ce", + "reference": "b5f7b932ee6fa802fc792eabd77c4c88084517ce", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.19-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.19.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-10-23T09:01:57+00:00" + }, + { + "name": "symfony/process", + "version": "v2.7.51", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "eda637e05670e2afeec3842dcd646dce94262f6b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/eda637e05670e2afeec3842dcd646dce94262f6b", + "reference": "eda637e05670e2afeec3842dcd646dce94262f6b", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v2.7.51" + }, + "time": "2018-08-03T11:24:48+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v2.8.52", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "91abb1e39d14fb7773d25de9c711949ea8502ac1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/91abb1e39d14fb7773d25de9c711949ea8502ac1", + "reference": "91abb1e39d14fb7773d25de9c711949ea8502ac1", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0" + }, + "require-dev": { + "ext-iconv": "*", + "twig/twig": "~1.34|~2.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-symfony_debug": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony mechanism for exploring and dumping PHP variables", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v2.8.50" + }, + "time": "2018-11-11T11:18:13+00:00" + }, + { + "name": "symfony/yaml", + "version": "v2.8.52", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "02c1859112aa779d9ab394ae4f3381911d84052b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/02c1859112aa779d9ab394ae4f3381911d84052b", + "reference": "02c1859112aa779d9ab394ae4f3381911d84052b", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/polyfill-ctype": "~1.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v2.8.52" + }, + "time": "2018-11-11T11:18:13+00:00" + }, + { + "name": "webflo/drupal-finder", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/webflo/drupal-finder.git", + "reference": "c8e5dbe65caef285fec8057a4c718a0d4138d1ee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webflo/drupal-finder/zipball/c8e5dbe65caef285fec8057a4c718a0d4138d1ee", + "reference": "c8e5dbe65caef285fec8057a4c718a0d4138d1ee", + "shasum": "" + }, + "require": { + "ext-json": "*" + }, + "require-dev": { + "mikey179/vfsstream": "^1.6", + "phpunit/phpunit": "^4.8" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/DrupalFinder.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Florian Weber", + "email": "florian@webflo.org" + } + ], + "description": "Helper class to locate a Drupal installation from a given path.", + "support": { + "issues": "https://github.com/webflo/drupal-finder/issues", + "source": "https://github.com/webflo/drupal-finder/tree/1.2.2" + }, + "time": "2020-10-27T09:42:17+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.9.1", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<3.9.1" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^7.5.13" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.9.1" + }, + "time": "2020-07-08T17:02:28+00:00" + }, + { + "name": "webmozart/path-util", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/path-util.git", + "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/path-util/zipball/d939f7edc24c9a1bb9c0dee5cb05d8e859490725", + "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "webmozart/assert": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\PathUtil\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "A robust cross-platform utility for normalizing, comparing and modifying file paths.", + "support": { + "issues": "https://github.com/webmozart/path-util/issues", + "source": "https://github.com/webmozart/path-util/tree/2.3.0" + }, + "abandoned": "symfony/filesystem", + "time": "2015-12-17T08:42:14+00:00" + } + ], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/master" + }, + "time": "2015-06-14T21:17:01+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "2.0.5", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "e6a969a640b00d8daa3c66518b0405fb41ae0c4b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/e6a969a640b00d8daa3c66518b0405fb41ae0c4b", + "reference": "e6a969a640b00d8daa3c66518b0405fb41ae0c4b", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "dflydev/markdown": "~1.0", + "erusev/parsedown": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "phpDocumentor": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "mike.vanriel@naenius.com" + } + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/release/2.x" + }, + "time": "2016-01-25T08:17:30+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "v1.10.3", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "451c3cd1418cf640de218914901e51b064abb093" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", + "reference": "451c3cd1418cf640de218914901e51b064abb093", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", + "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5 || ^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "support": { + "issues": "https://github.com/phpspec/prophecy/issues", + "source": "https://github.com/phpspec/prophecy/tree/v1.10.3" + }, + "time": "2020-03-05T15:02:03+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "2.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-file-iterator": "~1.3", + "phpunit/php-text-template": "~1.2", + "phpunit/php-token-stream": "~1.3", + "sebastian/environment": "^1.3.2", + "sebastian/version": "~1.0" + }, + "require-dev": { + "ext-xdebug": ">=2.1.4", + "phpunit/phpunit": "~4" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.2.1", + "ext-xmlwriter": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "irc": "irc://irc.freenode.net/phpunit", + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/2.2" + }, + "time": "2015-10-06T15:47:00+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "irc": "irc://irc.freenode.net/phpunit", + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/1.4.5" + }, + "time": "2017-11-27T13:52:08+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1" + }, + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/master" + }, + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.4.12", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1ce90ba27c42e4e44e6d8458241466380b51fa16", + "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-token-stream/issues", + "source": "https://github.com/sebastianbergmann/php-token-stream/tree/1.4" + }, + "abandoned": true, + "time": "2017-12-04T08:55:13+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "4.8.36", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "46023de9a91eec7dfb06cc56cb4e260017298517" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/46023de9a91eec7dfb06cc56cb4e260017298517", + "reference": "46023de9a91eec7dfb06cc56cb4e260017298517", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=5.3.3", + "phpspec/prophecy": "^1.3.1", + "phpunit/php-code-coverage": "~2.1", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "~2.3", + "sebastian/comparator": "~1.2.2", + "sebastian/diff": "~1.2", + "sebastian/environment": "~1.3", + "sebastian/exporter": "~1.2", + "sebastian/global-state": "~1.0", + "sebastian/version": "~1.0", + "symfony/yaml": "~2.1|~3.0" + }, + "suggest": { + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.8.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "source": "https://github.com/sebastianbergmann/phpunit/tree/4.8.36" + }, + "time": "2017-06-21T08:07:12+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "2.3.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": ">=5.3.3", + "phpunit/php-text-template": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "support": { + "irc": "irc://irc.freenode.net/phpunit", + "issues": "https://github.com/sebastianbergmann/phpunit-mock-objects/issues", + "source": "https://github.com/sebastianbergmann/phpunit-mock-objects/tree/2.3" + }, + "abandoned": true, + "time": "2015-10-02T06:51:40+00:00" + }, + { + "name": "sebastian/comparator", + "version": "1.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2 || ~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/1.2" + }, + "time": "2017-01-29T09:50:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/1.4" + }, + "time": "2017-05-22T07:24:03+00:00" + }, + { + "name": "sebastian/environment", + "version": "1.3.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea", + "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8 || ^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/1.3" + }, + "time": "2016-08-18T05:49:44+00:00" + }, + { + "name": "sebastian/exporter", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~1.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/master" + }, + "time": "2016-06-17T09:04:28+00:00" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/1.1.1" + }, + "time": "2015-10-12T03:26:01+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b19cc3298482a335a95f3016d2f8a6950f0fbcd7", + "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/master" + }, + "time": "2016-10-03T07:41:43+00:00" + }, + { + "name": "sebastian/version", + "version": "1.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "shasum": "" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/1.0.6" + }, + "time": "2015-06-21T13:59:46+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + }, + { + "name": "yoast/phpunit-polyfills", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/Yoast/PHPUnit-Polyfills.git", + "reference": "5ea3536428944955f969bc764bbe09738e151ada" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/5ea3536428944955f969bc764bbe09738e151ada", + "reference": "5ea3536428944955f969bc764bbe09738e151ada", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "require-dev": { + "yoast/yoastcs": "^2.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev", + "dev-develop": "1.x-dev" + } + }, + "autoload": { + "files": [ + "phpunitpolyfills-autoload.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Team Yoast", + "email": "support@yoast.com", + "homepage": "https://yoast.com" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Yoast/PHPUnit-Polyfills/graphs/contributors" + } + ], + "description": "Set of polyfills for changed PHPUnit functionality to allow for creating PHPUnit cross-version compatible tests", + "homepage": "https://github.com/Yoast/PHPUnit-Polyfills", + "keywords": [ + "phpunit", + "polyfill", + "testing" + ], + "support": { + "issues": "https://github.com/Yoast/PHPUnit-Polyfills/issues", + "source": "https://github.com/Yoast/PHPUnit-Polyfills" + }, + "time": "2021-11-23T01:37:03+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": ">=5.4.5" + }, + "platform-dev": [], + "platform-overrides": { + "php": "5.4.5" + }, + "plugin-api-version": "2.1.0" +} diff --git a/vendor/drush/drush/docs/bastion.md b/vendor/drush/drush/docs/bastion.md new file mode 100644 index 0000000000..491e691a00 --- /dev/null +++ b/vendor/drush/drush/docs/bastion.md @@ -0,0 +1,64 @@ +# Remote Operations on Drupal Sites via a Bastion Server + +Wikipedia defines a [bastion server](http://en.wikipedia.org/wiki/Bastion_host) as "a special purpose computer on a network specifically designed and configured to withstand attacks." For the purposes of this documentation, though, any server that you can ssh through to reach other servers will do. Using standard ssh and Drush techniques, it is possible to make a two-hop remote command look and act as if the destination machine is on the same network as the source machine. + +## Recap of Remote Site Aliases + +Site aliases can refer to Drupal sites that are running on remote machines simply including 'remote-host' and 'remote-user' attributes: + + $aliases['internal'] = array( + 'remote-host' => 'internal.company.com', + 'remote-user' => 'wwwadmin', + 'uri' => 'http://internal.company.com', + 'root' => '/path/to/remote/drupal/root', + ); + +With this alias defintion, you may use commands such as `drush @internal status`, `drush ssh @internal` and `drush rsync @internal @dev` to operate remotely on the internal machine. What if you cannot reach the server that site is on from your current network? Enter the bastion server. + +## Setting up a Bastion server in .ssh/config + +If you have access to a server, bastion.company.com, which you can ssh to from the open internet, and if the bastion server can in turn reach the internal server, then it is possible to configure ssh to route all traffic to the internal server through the bastion. The .ssh configuration file would look something like this: + +In **.ssh/config:** + + Host internal.company.com + ProxyCommand ssh user@bastion.company.com nc %h %p + +That is all that is necessary; however, if the dev machine you are configuring is a laptop that might sometimes be inside the company intranet, you might want to optimize this setup so that the bastion is not used when the internal server can be reached directly. You could do this by changing the contents of your .ssh/config file when your network settings change -- or you could use Drush. + +# Setting up a Bastion server via Drush configuration + +First, make sure that you do not have any configuration options for the internal machine in your .ssh/config file. The next step after that is to identify when you are connected to your company intranet. I like to determine this by using the `route` command to find my network gateway address, since this is always the same on my intranet, and unlikely to be encountered in other places. + +In **drushrc.php:** + + # Figure out if we are inside our company intranet by testing our gateway address against a known value + exec("route -n | grep '^0\.0\.0\.0' | awk '{ print $2; }' 2> /dev/null", $output); + if ($output[0] == '172.30.10.1') { + drush_set_context('MY_INTRANET', TRUE); + } + +After this code runs, the 'MY\_INTRANET' context will be set if our gateway IP address matches the expected value, and unset otherwise. We can make use of this in our alias files. + +In **aliases.drushrc.php:** + + if (drush_get_context('MY_INTRANET', FALSE) === FALSE) { + $aliases['intranet-proxy'] = array( + 'ssh-options' => ' -o "ProxyCommand ssh user@bastion.company.com nc %h %p"', + ); + } + $aliases['internal-server'] = array( + 'parent' => '@intranet-proxy', + 'remote-host' => 'internal.company.com', + 'remote-user' => 'wwwadmin', + ); + $aliases['internal'] = array( + 'parent' => '@internal-server', + 'uri' => 'http://internal.company.com', + 'root' => '/path/to/remote/drupal/root', + ); + +The 'parent' term of the internal-server alias record is ignored if the alias it references ('@intranet-proxy') is not defined; the result is that 'ssh-options' will only be defined when outside of the intranet, and the ssh ProxyCommand to the bastion server will only be included when it is needed. + +With this setup, you will be able to use your site alias '@internal' to remotely operate on your internal intranet Drupal site seemlessly, regardless of your location -- a handy trick indeed. + diff --git a/vendor/drush/drush/docs/bootstrap.md b/vendor/drush/drush/docs/bootstrap.md new file mode 100644 index 0000000000..c20b48e442 --- /dev/null +++ b/vendor/drush/drush/docs/bootstrap.md @@ -0,0 +1,38 @@ +The Drush Bootstrap Process +=========================== +When preparing to run a command, Drush works by "bootstrapping" the Drupal environment in very much the same way that is done during a normal page request from the web server, so most Drush commands run in the context of a fully-initialized website. + +For efficiency and convenience, some Drush commands can work without first bootstrapping a Drupal site, or by only partially bootstrapping a site. This is faster than a full bootstrap. It is also a matter of convenience, because some commands are useful even when you don't have a working Drupal site. For example, you can use Drush to download Drupal with `drush dl drupal`. This obviously does not require any bootstrapping to work. + +DRUSH\_BOOTSTRAP\_NONE +----------------------- +Only run Drush _preflight_, without considering Drupal at all. Any code that operates on the Drush installation, and not specifically any Drupal directory, should bootstrap to this phase. + +DRUSH\_BOOTSTRAP\_DRUPAL\_ROOT +------------------------------ +Set up and test for a valid Drupal root, either through the --root options, or evaluated based on the current working directory. Any code that interacts with an entire Drupal installation, and not a specific site on the Drupal installation should use this bootstrap phase. + +DRUSH\_BOOTSTRAP\_DRUPAL\_SITE +------------------------------ +Set up a Drupal site directory and the correct environment variables to allow Drupal to find the configuration file. If no site is specified with the --uri options, Drush will assume the site is 'default', which mimics Drupal's behaviour. Note that it is necessary to specify a full URI, e.g. --uri=http://example.com, in order for certain Drush commands and Drupal modules to behave correctly. See the [example Config file](../examples/example.drushrc.php) for more information. Any code that needs to modify or interact with a specific Drupal site's settings.php file should bootstrap to this phase. + +DRUSH\_BOOTSTRAP\_DRUPAL\_CONFIGURATION +--------------------------------------- +Load the settings from the Drupal sites directory. This phase is analagous to the DRUPAL\_BOOTSTRAP\_CONFIGURATION bootstrap phase in Drupal itself, and this is also the first step where Drupal specific code is included. This phase is commonly used for code that interacts with the Drupal install API, as both install.php and update.php start at this phase. + +DRUSH\_BOOTSTRAP\_DRUPAL\_DATABASE +---------------------------------- +Connect to the Drupal database using the database credentials loaded during the previous bootstrap phase. This phase is analogous to the DRUPAL\_BOOTSTRAP\_DATABASE bootstrap phase in Drupal. Any code that needs to interact with the Drupal database API needs to be bootstrapped to at least this phase. + +DRUSH\_BOOTSTRAP\_DRUPAL\_FULL +------------------------------ +Fully initialize Drupal. This is analogous to the DRUPAL\_BOOTSTRAP\_FULL bootstrap phase in Drupal. Any code that interacts with the general Drupal API should be bootstrapped to this phase. + +DRUSH\_BOOTSTRAP\_DRUPAL\_LOGIN +------------------------------- +Log in to the initialiazed Drupal site. This bootstrap phase is used after the site has been fully bootstrapped. This is the default bootstrap phase all commands will try to reach, unless otherwise specified. This phase will log you in to the drupal site with the username or user ID specified by the --user/ -u option(defaults to 0, anonymous). Use this bootstrap phase for your command if you need to have access to information for a specific user, such as listing nodes that might be different based on who is logged in. + +DRUSH\_BOOTSTRAP\_MAX +--------------------- +This is not an actual bootstrap phase. Commands that use DRUSH\_BOOTSTRAP\_MAX will cause Drush to bootstrap as far as possible, and then run the command regardless of the bootstrap phase that was reached. This is useful for Drush commands that work without a bootstrapped site, but that provide additional information or capabilities in the presence of a bootstrapped site. For example, `drush pm-releases modulename` works without a bootstrapped Drupal site, but will include the version number for the installed module if a Drupal site has been bootstrapped. + diff --git a/vendor/drush/drush/docs/commands.md b/vendor/drush/drush/docs/commands.md new file mode 100644 index 0000000000..3b73ab2403 --- /dev/null +++ b/vendor/drush/drush/docs/commands.md @@ -0,0 +1,132 @@ +Creating Custom Drush Commands +============================== + +Creating a new Drush command is very easy. Follow these simple steps: + +1. Create a command file called COMMANDFILE.drush.inc +1. Implement the function COMMANDFILE\_drush\_command() +1. Implement the functions that your commands will call. These will usually be named drush\_COMMANDFILE\_COMMANDNAME(). + +For an example Drush command, see examples/sandwich.drush.inc. The steps for implementing your command are explained in more detail below. + +Create COMMANDFILE.drush.inc +---------------------------- + +The name of your Drush command is very important. It must end in ".drush.inc" to be recognized as a Drush command. The part of the filename that comes before the ".drush.inc" becomes the name of the commandfile. Optionally, the commandfile may be restricted to a particular version of Drupal by adding a ".dVERSION" after the name of the commandfile (e.g. ".d8.drush.inc") Your commandfile name is used by Drush to compose the names of the functions it will call, so choose wisely. + +The example Drush command, 'make-me-a-sandwich', is stored in the 'sandwich' commandfile, 'sandwich.Drush.inc'. You can find this file in the 'examples' directory in the Drush distribution. + +Drush searches for commandfiles in the following locations: + +- Folders listed in the 'include' option (see `drush topic docs-configuration`). +- The system-wide Drush commands folder, e.g. /usr/share/drush/commands +- The ".drush" folder in the user's HOME folder. +- /drush and /sites/all/drush in the current Drupal installation +- All enabled modules in the current Drupal installation +- Folders and files containing other versions of Drush in their names will be \*skipped\* (e.g. devel.drush4.inc or drush4/devel.drush.inc). Names containing the current version of Drush (e.g. devel.drush5.inc) will be loaded. + +Note that modules in the current Drupal installation will only be considered if Drush has bootstrapped to at least the DRUSH\_BOOSTRAP\_SITE level. Usually, when working with a Drupal site, Drush will bootstrap to DRUSH\_BOOTSTRAP\_FULL; in this case, only the Drush commandfiles in enabled modules will be considered eligible for loading. If Drush only bootstraps to DRUSH\_BOOTSTRAP\_SITE, though, then all Drush commandfiles will be considered, whether the module is enabled or not. See `drush topic docs-bootstrap` for more information on bootstrapping. + +Implement COMMANDFILE\_drush\_command() +--------------------------------------- + +The drush\_command hook is the most important part of the commandfile. It returns an array of items that define how your commands should be called, and how they work. Drush commands are very similar to the Drupal menu system. The elements that can appear in a Drush command definition are shown below. + +- **aliases**: Provides a list of shorter names for the command. For example, pm-download may also be called via `drush dl`. If the alias is used, Drush will substitute back in the primary command name, so pm-download will still be used to generate the command hook, etc. +- **command-hook**: Change the name of the function Drush will call to execute the command from drush\_COMMANDFILE\_COMMANDNAME() to drush\_COMMANDFILE\_COMMANDHOOK(), where COMMANDNAME is the original name of the command, and COMMANDHOOK is the value of the 'command-hook' item. +- **callback**: Name of function to invoke for this command. The callback function name \_must\_ begin with "drush\_commandfile\_", where commandfile is from the file "commandfile.drush.inc", which contains the commandfile\_drush\_command() function that returned this command. Note that the callback entry is optional; it is preferable to omit it, in which case drush\_invoke() will generate the hook function name. +- **callback arguments**: An array of arguments to pass to the callback. The command line arguments, if any, will appear after the callback arguments in the function parameters. +- **description**: Description of the command. +- **arguments**: An array of arguments that are understood by the command. Used by `drush help` only. +- **required-arguments**: Defaults to FALSE; TRUE if all of the arguments are required. Set to an integer count of required arguments if only some are required. +- **options**: An array of options that are understood by the command. Any option that the command expects to be able to query via drush\_get\_option \_must\_ be listed in the options array. If it is not, users will get an error about an "Unknown option" when they try to specify the option on the command line. + + The value of each option may be either a simple string containing the option description, or an array containing the following information: + + - **description**: A description of the option. + - **example-value**: An example value to show in help. + - **value**: optional|required. + - **required**: Indicates that an option must be provided. + - **hidden**: The option is not shown in the help output (rare). + +- **allow-additional-options**: If TRUE, then the strict validation to see if options exist is skipped. Examples of where this is done includes the core-rsync command, which passes options along to the rsync shell command. This item may also contain a list of other commands that are invoked as subcommands (e.g. the pm-update command calls pm-updatecode and updatedb commands). When this is done, the options from the subcommand may be used on the commandline, and are also listed in the command's `help` output. Defaults to FALSE. +- **examples**: An array of examples that are understood by the command. Used by `drush help` only. +- **scope**: One of 'system', 'project', 'site'. Not currently used. +- **bootstrap**: Drupal bootstrap level. More info at `drush topic docs-bootstrap`. Valid values are: + - DRUSH\_BOOTSTRAP\_NONE + - DRUSH\_BOOTSTRAP\_DRUPAL\_ROOT + - DRUSH\_BOOTSTRAP\_DRUPAL\_SITE + - DRUSH\_BOOTSTRAP\_DRUPAL\_CONFIGURATION + - DRUSH\_BOOTSTRAP\_DRUPAL\_DATABASE + - DRUSH\_BOOTSTRAP\_DRUPAL\_FULL + - DRUSH\_BOOTSTRAP\_DRUPAL\_LOGIN (default) + - DRUSH\_BOOTSTRAP\_MAX +- **core**: Drupal major version required. Append a '+' to indicate 'and later versions.' +- **drupal dependencies**: Drupal modules required for this command. +- **drush dependencies**: Other Drush commandfiles required for this command. +- **engines**: Provides a list of Drush engines to load with this command. The set of appropriate engines varies by command. + - **outputformat**: One important engine is the 'outputformat' engine. This engine is responsible for formatting the structured data (usually an associative array) that a Drush command returns as its function result into a human-readable or machine-parsable string. Some of the options that may be used with output format engines are listed below; however, each specific output format type can take additional option items that control the way that the output is rendered. See the comment in the output format's implementation for information. The Drush core output format engines can be found in commands/core/outputformat. + - **default**: The default type to render output as. If declared, the command should not print any output on its own, but instead should return a data structure (usually an associative array) that can be rendered by the output type selected. + - **pipe-format**: When the command is executed in --pipe mode, the command output will be rendered by the format specified by the pipe-format item instead of the default format. Note that in either event, the user may specify the format to use via the --format command-line option. + - **formatted-filter** and **pipe-filter**: Specifies a function callback that will be used to filter the command result. The filter is selected based on the type of output format object selected. Most output formatters are 'pipe' formatters, that produce machine-parsable output. A few formatters, such as 'table' and 'key-value' are 'formatted' filter types, that produce human-readable output. +- **topics**: Provides a list of topic commands that are related in some way to this command. Used by `drush help`. +- **topic**: Set to TRUE if this command is a topic, callable from the `drush docs-topics` command. +- **category**: Set this to override the category in which your command is listed in help. + +The 'sandwich' drush\_command hook looks like this: + + function sandwich_drush_command() { + $items = array(); + + $items['make-me-a-sandwich'] = array( + 'description' => "Makes a delicious sandwich.", + 'arguments' => array( + 'filling' => 'The type of the sandwich (turkey, cheese, etc.)', + ), + 'options' => array( + 'spreads' => 'Comma delimited list of spreads (e.g. mayonnaise, mustard)', + ), + 'examples' => array( + 'drush mmas turkey --spreads=ketchup,mustard' => 'Make a terrible-tasting sandwich that is lacking in pickles.', + ), + 'aliases' => array('mmas'), + 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, // No bootstrap at all. + ); + + return $items; + } + +Most of the items in the 'make-me-a-sandwich' command definition have no effect on execution, and are used only by `drush help`. The exceptions are 'aliases' (described above) and 'bootstrap'. As previously mentioned, `drush topic docs-bootstrap` explains the Drush bootstrapping process in detail. + +Implement drush\_COMMANDFILE\_COMMANDNAME() +------------------------------------------- + +The 'make-me-a-sandwich' command in sandwich.drush.inc is defined as follows: + + function drush_sandwich_make_me_a_sandwich($filling = 'ascii') { + // implementation here ... + } + +If a user runs `drush make-me-a-sandwich` with no command line arguments, then Drush will call drush\_sandwich\_make\_me\_a\_sandwich() with no function parameters; in this case, $filling will take on the provided default value, 'ascii'. (If there is no default value provided, then the variable will be NULL, and a warning will be printed.) Running `drush make-me-a-sandwich ham` will cause Drush to call drush\_sandwich\_make\_me\_a\_sandwich('ham'). In the same way, commands that take two command line arguments can simply define two functional parameters, and a command that takes a variable number of command line arguments can use the standard php function func\_get\_args() to get them all in an array for easy processing. + +It is also very easy to query the command options using the function drush\_get\_option(). For example, in the drush\_sandwich\_make\_me\_a\_sandwich() function, the --spreads option is retrieved as follows: + + $str_spreads = ''; + if ($spreads = drush_get_option('spreads')) { + $list = implode(' and ', explode(',', $spreads)); + $str_spreads = ' with just a dash of ' . $list; + } + +Note that Drush will actually call a sequence of functions before and after your Drush command function. One of these hooks is the "validate" hook. The 'sandwich' commandfile provides a validate hook for the 'make-me-a-sandwich' command: + + function drush_sandwich_make_me_a_sandwich_validate() { + $name = posix_getpwuid(posix_geteuid()); + if ($name['name'] !== 'root') { + return drush_set_error('MAKE_IT_YOUSELF', dt('What? Make your own sandwich.')); + } + } + +The validate function should call drush\_set\_error() and return its result if the command cannot be validated for some reason. See `drush topic docs-policy` for more information on defining policy functions with validate hooks, and `drush topic docs-api` for information on how the command hook process works. Also, the list of defined drush error codes can be found in `drush topic docs-errorcodes`. + +To see the full implementation of the sample 'make-me-a-sandwich' command, see `drush topic docs-examplecommand`. + diff --git a/vendor/drush/drush/docs/config-exporting.md b/vendor/drush/drush/docs/config-exporting.md new file mode 100644 index 0000000000..a31428c85d --- /dev/null +++ b/vendor/drush/drush/docs/config-exporting.md @@ -0,0 +1,37 @@ +# Exporting and Importing Configuration + +Drush provides commands to export, transfer, and import configuration files +to and from a Drupal 8 site. Configuration can be altered by different +methods in order to provide different behaviors in different environments; +for example, a development server might be configured slightly differently +than the production server. + +This document describes how to make simple value changes to configuration +based on the environment, how to have a different set of enabled modules +in different configurations without affecting your exported configuration +values, and how to make more complex changes. + +## Simple Value Changes + +It is not necessary to alter the configuration system values to +make simple value changes to configuration variables, as this may be +done by the [configuration override system](https://www.drupal.org/node/1928898). + +The configuration override system allows you to change configuration +values for a given instance of a site (e.g. the development server) by +setting configuration variables in the site's settings.php file. +For example, to change the name of a local development site: +``` +$config['system.site']['name'] = 'Local Install of Awesome Widgets, Inc.'; +``` +Note that the configuration override system is a Drupal feature, not +a Drush feature. It should be the preferred method for changing +configuration values on a per-environment basis; however, it does not +work for some things, such as enabling and disabling modules. For +configuration changes not handled by the configuration override system, +you can use configuration filters of the Config Filter module. + +## Ignoring Development Modules + +Use the [Config Split](https://www.drupal.org/project/config_split) module to +split off development configuration in a dedicated config directory. \ No newline at end of file diff --git a/vendor/drush/drush/docs/context.md b/vendor/drush/drush/docs/context.md new file mode 100644 index 0000000000..6308c765c8 --- /dev/null +++ b/vendor/drush/drush/docs/context.md @@ -0,0 +1,55 @@ +Drush Contexts +============== + +The drush contexts API acts as a storage mechanism for all options, arguments and configuration settings that are loaded into drush. + +This API also acts as an IPC mechanism between the different drush commands, and provides protection from accidentally overriding settings that are needed by other parts of the system. + +It also avoids the necessity to pass references through the command chain and allows the scripts to keep track of whether any settings have changed since the previous execution. + +This API defines several contexts that are used by default. + +Argument contexts +----------------- + +These contexts are used by Drush to store information on the command. They have their own access functions in the forms of drush\_set\_arguments(), drush\_get\_arguments(), drush\_set\_command(), drush\_get\_command(). + +- command : The drush command being executed. +- arguments : Any additional arguments that were specified. + +Setting contexts +---------------- + +These contexts store options that have been passed to the drush.php script, either through the use of any of the config files, directly from the command line through --option='value' or through a JSON encoded string passed through the STDIN pipe. + +These contexts are accessible through the drush\_get\_option() and drush\_set\_option() functions. See drush\_context\_names() for a description of all of the contexts. + +Drush commands may also choose to save settings for a specific context to the matching configuration file through the drush\_save\_config() function. + +Available Setting contexts +-------------------------- + +These contexts are evaluated in a certain order, and the highest priority value is returned by default from drush\_get\_option. This allows scripts to check whether an option was different before the current execution. + +Specified by the script itself : + +- process : Generated in the current process. +- cli : Passed as --option=value to the command line. +- stdin : Passed as a JSON encoded string through stdin. +- alias : Defined in an alias record, and set in the alias context whenever that alias is used. +- specific : Defined in a command-specific option record, and set in the command context whenever that command is used. + +Specified by config files : + +- custom : Loaded from the config file specified by --config or -c +- site : Loaded from the drushrc.php file in the Drupal site directory. +- drupal : Loaded from the drushrc.php file in the Drupal root directory. +- user : Loaded from the drushrc.php file in the user's home directory. +- drush : Loaded from the drushrc.php file in the $HOME/.drush directory. +- system : Loaded from the drushrc.php file in the system's $PREFIX/etc/drush directory. +- drush : Loaded from the drushrc.php file in the same directory as drush.php. + +Specified by the script, but has the lowest priority : + +- default : The script might provide some sensible defaults during init. + diff --git a/vendor/drush/drush/docs/cron.md b/vendor/drush/drush/docs/cron.md new file mode 100644 index 0000000000..04ab36d024 --- /dev/null +++ b/vendor/drush/drush/docs/cron.md @@ -0,0 +1,49 @@ +Running Drupal cron tasks from Drush +==================================== + +Drupal cron tasks are often set up to be run via a wget call to cron.php; this same task can also be accomplished via the `drush cron` command, which circumvents the need to provide a webserver interface to cron. + +Quick start +---------- + +If you just want to get started quickly, here is a crontab entry that will run cron once every hour at ten minutes after the hour: + + 10 * * * * /usr/bin/env PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin COLUMNS=72 /usr/local/bin/drush --root=/path/to/your/drupalroot --uri=your.drupalsite.org --quiet cron + +You should set up crontab to run your cron tasks as the same user that runs the web server; for example, if you run your webserver as the user www-data: + + sudo crontab -u www-data -e + +You might need to edit the crontab entry shown above slightly for your particular setup; for example, if you have installed Drush to some directory other than /usr/local/drush, then you will need to adjust the path to drush appropriately. We'll break down the meaning of each section of the crontab entry in the documentation that continues below. + +Setting the schedule +-------------------- + +See `man 5 crontab` for information on how to format the information in a crontab entry. In the example above, the schedule for the crontab is set by the string `10 * * * *`. These fields are the minute, hour, day of month, month and day of week; `*` means essentially 'all values', so `10 * * * *` will run any time the minute == 10 (once every hour). + +Setting the PATH +---------------- + +We use /usr/bin/env to run Drush so that we can set up some necessary environment variables that Drush needs to execute. By default, cron will run each command with an empty PATH, which would not work well with Drush. To find out what your PATH needs to be, just type: + + echo $PATH + +Take the value that is output and place it into your crontab entry in the place of the one shown above. You can remove any entry that is known to not be of interest to Drush (e.g. /usr/games), or is only useful in a graphic environment (e.g. /usr/X11/bin). + +Setting COLUMNS +--------------- + +When running Drush in a terminal, the number of columns will be automatically determined by Drush by way of the tput command, which queries the active terminal to determine what the width of the screen is. When running Drush from cron, there will not be any terminal set, and the call to tput will produce an error message. Spurrious error messages are undesirable, as cron is often configured to send email whenever any output is produced, so it is important to make an effort to insure that successful runs of cron complete with no output. + +In some cases, Drush is smart enough to recognize that there is no terminal -- if the terminal value is empty or "dumb", for example. However, there are some "non-terminal" values that Drush does not recognize, such as "unknown." If you manually set `COLUMNS`, then Drush will repect your setting and will not attempt to call tput. + +Using --quiet +------------- + +By default, Drush will print a success message when the run of cron is completed. The --quiet flag will suppress these and other progress messages, again avoiding an unnecessary email message. + +Specifying the Drupal site to run +--------------------------------- + +There are many ways to tell Drush which Drupal site to select for the active command, and any may be used here. The example uses the --root and --uri flags, but you could also use an alias record. + diff --git a/vendor/drush/drush/docs/examples.md b/vendor/drush/drush/docs/examples.md new file mode 100644 index 0000000000..7eca54f53c --- /dev/null +++ b/vendor/drush/drush/docs/examples.md @@ -0,0 +1,18 @@ +The _examples_ folder contains example files which you may copy and edit as needed. Read the documentation right in the file. If you see an opportunity to improve the file, please submit a pull request. + +* [drush.wrapper](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/drush.wrapper). A handy launcher script which calls the Drush located in vendor/bin/drush and can add options like --local, --root, etc. +* [example.aliases.drushrc.php](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/example.aliases.drushrc.php). Example site alias definitions. +* [example.bashrc](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/example.bashrc). Enhance your shell with lots of Drush niceties including bash completion. +* [example.drush.ini](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/example.drush.ini). Configure your PHP just for Drush requests. +* [example.drushrc.php](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/example.drushrc.php). A Drush configuration file. +* [example.make](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/example.make). An ini style make file. +* [example.make.yml](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/example.make.yml). A YML make file. +* [example.prompt.sh](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/example.prompt.sh). Displays Git repository and Drush alias status in your prompt. +* [git-bisect.example.sh](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/git-bisect.example.sh). Spelunking through Drush's git history with bisect. +* [helloworld.script](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/helloworld.script). An example Drush script. +* [pm_update.drush.inc](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/pm_update.drush.inc). Restore sqlsrv driver after core update. +* [policy.drush.inc](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/policy.drush.inc). A policy file can disallow prohibited commands/options etc. +* [sandwich.drush.inc](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/sandwich.drush.inc). A fun example command inspired by a famous XKCD comic. +* [sync_via_http.drush.inc](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/sync_via_http.drush.inc). sql-sync modification that transfers via http instead of rsync. +* [sync_enable.drush.inc](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/sync_enable.drush.inc). Automatically enable modules after a sql-sync. +* [xkcd.drush.inc](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/xkcd.drush.inc). A fun example command that browses XKCD comics. diff --git a/vendor/drush/drush/docs/index.md b/vendor/drush/drush/docs/index.md new file mode 100644 index 0000000000..1e256107d7 --- /dev/null +++ b/vendor/drush/drush/docs/index.md @@ -0,0 +1,53 @@ +#### _These docs are for Drush 8. [You may want the docs for Drush 10](https://www.drush.org), the current version._ + +----------- + +Drush is a command line shell and Unix scripting interface for Drupal. Drush core ships with lots of useful commands for interacting with code like modules/themes/profiles. Similarly, it runs update.php, executes sql queries and DB migrations, and misc utilities like run cron or clear cache. Drush can be extended by [3rd party commandfiles](https://www.drupal.org/project/project_module?f[2]=im_vid_3%3A4654). + +[![Latest Stable Version](https://poser.pugx.org/drush/drush/v/stable.png)](https://packagist.org/packages/drush/drush) [![Total Downloads](https://poser.pugx.org/drush/drush/downloads.png)](https://packagist.org/packages/drush/drush) [![Latest Unstable Version](https://poser.pugx.org/drush/drush/v/unstable.png)](https://packagist.org/packages/drush/drush) [![License](https://poser.pugx.org/drush/drush/license.png)](https://packagist.org/packages/drush/drush) + +Resources +----------- +* [Install documentation](http://docs.drush.org/en/8.x/install/) +* [General documentation](http://docs.drush.org) +* [API Documentation](http://api.drush.org) +* [Drush Commands](http://drushcommands.com) +* Subscribe [this atom feed](https://github.com/drush-ops/drush/releases.atom) to receive notification on new releases. Also, [Version eye](https://www.versioneye.com/). +* [Drush packages available via Composer](http://packages.drush.org) +* [A list of modules that include Drush integration](https://www.drupal.org/project/project_module?f[2]=im_vid_3%3A4654&solrsort=ds_project_latest_release+desc) +* Drush comes with a [full test suite](https://github.com/drush-ops/drush/blob/8.x/tests/README.md) powered by [PHPUnit](https://github.com/sebastianbergmann/phpunit). Each commit gets tested by the awesome [Travis.ci continuous integration service](https://travis-ci.org/drush-ops/drush). + +Support +----------- + +Please take a moment to review the rest of the information in this file before +pursuing one of the support options below. + +* Post support requests to [Drupal Answers](http://drupal.stackexchange.com/questions/tagged/drush). +* Bug reports and feature requests should be reported in the [GitHub Drush Issue Queue](https://github.com/drush-ops/drush/issues). +* Use pull requests (PRs) to contribute to Drush. +* It is still possible to search the old issue queue on Drupal.org for [fixed bugs](https://drupal.org/project/issues/search/drush?status%5B%5D=7&categories%5B%5D=bug), [unmigrated issues](https://drupal.org/project/issues/search/drush?status%5B%5D=5&issue_tags=needs+migration), [unmigrated bugs](https://drupal.org/project/issues/search/drush?status%5B%5D=5&categories%5B%5D=bug&issue_tags=needs+migration), and so on. + +FAQ +------ + +##### What does the name Drush mean? +The Drupal Shell. + +##### How do I pronounce Drush? + +Some people pronounce the dru with a long u like Drupal. Fidelity points go to them, but they are in the minority. Most pronounce Drush so that it rhymes with hush, rush, flush, etc. This is the preferred pronunciation. + +##### Does Drush have unit tests? + +Drush has an excellent suite of unit tests. See [tests/README.md](https://github.com/drush-ops/drush/blob/8.x/tests/README.md) for more information. + + +Credits +----------- + +* Originally developed by [Arto Bendiken](http://bendiken.net) for Drupal 4.7. +* Redesigned by [Franz Heinzmann](http://unbiskant.org) in May 2007 for Drupal 5. +* Maintained by [Moshe Weitzman](http://drupal.org/moshe) with much help from + Owen Barton, greg.1.anderson, jonhattan, Mark Sonnabaum, Jonathan Hedstrom and + [Christopher Gervais](http://drupal.org/u/ergonlogic). diff --git a/vendor/drush/drush/docs/install-alternative.md b/vendor/drush/drush/docs/install-alternative.md new file mode 100644 index 0000000000..683e2f1427 --- /dev/null +++ b/vendor/drush/drush/docs/install-alternative.md @@ -0,0 +1,38 @@ +Install a global Drush via Composer +------------------ +Follow the instructions below: + +1. [Install Composer globally](https://getcomposer.org/doc/00-intro.md#globally). +1. Install the [cgr tool](https://github.com/consolidation/cgr) following the [instructions in that project](https://github.com/consolidation/cgr#installation-and-usage). +1. Add composer's `bin` directory to the system path by placing `export PATH="$HOME/.composer/vendor/bin:$PATH"` into your ~/.bash_profile (Mac OS users) or into your ~/.bashrc (Linux users). +1. Install latest stable Drush: `cgr drush/drush`. +1. Verify that Drush works: `drush status` + +Please do not install Drush using `composer global require`. See [Fixing the Composer Global command](https://pantheon.io/blog/fixing-composer-global-command) for more information. + +#### Notes +* Update to latest release (per your specification in ~/.composer/composer.json): `cgr update drush/drush` +* Install a specific version of Drush: + + # Install a specific version of Drush, e.g. Drush 7.1.0 + cgr update drush/drush:7.1.0 + + # Install 8.x branch as a git clone. Great for contributing back to Drush project. + cgr drush/drush:8.x-dev --prefer-source + + +* Alternate way to install for all users via Composer: + + COMPOSER_HOME=/opt/drush COMPOSER_BIN_DIR=/usr/local/bin COMPOSER_VENDOR_DIR=/opt/drush/8 composer require drush/drush:^8 + +* [Documentation for composer's require command.](http://getcomposer.org/doc/03-cli.md#require) +* Uninstall with : `cgr remove drush/drush` + +Windows +------------ +Drush on Windows is experimental, since Drush's test suite is not running there ([help wanted](https://github.com/drush-ops/drush/issues/1612)). + +* [Acquia Dev Desktop](https://www.acquia.com/downloads) is excellent, and includes Drush. See the terminal icon after setting up a web site. +* Or consider running Linux/OSX via Virtualbox. [Drupal VM](http://www.drupalvm.com/) and [Vlad](https://github.com/hashbangcode/vlad) are popular.* These Windows packages include Drush and its dependencies (including MSys). * [7.0.0 (stable)](https://github.com/drush-ops/drush/releases/download/7.0.0/windows-7.0.0.zip). * [6.6.0](https://github.com/drush-ops/drush/releases/download/6.6.0/windows-6.6.0.zip). * [6.0](https://github.com/drush-ops/drush/releases/download/6.0.0/Drush-6.0-2013-08-28-Installer-v1.0.21.msi). +* Or install LAMP on your own, and run Drush via [Git's shell](https://git-for-windows.github.io/), in order to insure that [all depedencies](https://github.com/acquia/DevDesktopCommon/tree/8.x/bintools-win/msys/bin) are available. +* When creating site aliases for Windows remote machines, pay particular attention to information presented in the example.aliases.drushrc.php file, especially when setting values for 'remote-host' and 'os', as these are very important when running Drush rsync and Drush sql-sync commands. diff --git a/vendor/drush/drush/docs/install.md b/vendor/drush/drush/docs/install.md new file mode 100644 index 0000000000..ec56e33f2f --- /dev/null +++ b/vendor/drush/drush/docs/install.md @@ -0,0 +1,95 @@ +Install/Upgrade a global Drush +--------------- +```bash +# Browse to https://github.com/drush-ops/drush/releases and download the drush.phar attached to the latest 8.x release. + +# Test your install. +php drush.phar core-status + +# Rename to `drush` instead of `php drush.phar`. Destination can be anywhere on $PATH. +chmod +x drush.phar +sudo mv drush.phar /usr/local/bin/drush + +# Optional. Enrich the bash startup file with completion and aliases. +drush init +``` + +* MAMP users, and anyone wishing to launch a non-default PHP, needs to [edit ~/.bashrc so that the right PHP is in your $PATH](http://stackoverflow.com/questions/4145667/how-to-override-the-path-of-php-to-use-the-mamp-path/10653443#10653443). +* We have documented [alternative ways to install](http://docs.drush.org/en/8.x/install-alternative/), including [Windows](http://docs.drush.org/en/8.x/install-alternative/#windows). +* If you need to pass custom php.ini values, run `php -d foo=bar drush.phar --php-options=foo=bar` +* Your shell now has [useful bash aliases and tab completion for command names, site aliases, options, and arguments](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/example.bashrc). +* A [drushrc.php](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/example.drushrc.php) has been copied to ~/.drush above. Customize it to save typing and standardize options for commands. +* Upgrade using this same procedure. + +Install a site-local Drush +----------------- +In addition to the global Drush, it is recommended that Drupal 8 sites be [built using Composer, with Drush listed as a dependency](https://github.com/drupal-composer/drupal-project). + +1. When you run `drush`, the global Drush is called first and then hands execution to the site-local Drush. This gives you the convenience of running `drush` without specifying the full path to the executable, without sacrificing the safety provided by a site-local Drush. +2. Optional: Copy the [examples/drush.wrapper](https://github.com/drush-ops/drush/blob/8.x/examples/drush.wrapper) file to your project root and modify to taste. This is a handy launcher script; add --local here to turn off all global configuration locations, and maintain consistency over configuration/aliases/commandfiles for your team. +3. Note that if you have multiple Drupal sites on your system, it is possible to use a different version of Drush with each one. + +Drupal Compatibility +----------------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Drush Version Drush Branch PHP Compatible Drupal versions Code Style Isolation Tests Functional Tests
Drush 9 master 5.6+ D8.4+ + + + + + +
Drush 8 8.x 5.4.5+ D6, D7, D8.3- + + + - + + +
Drush 7 7.x 5.3.0+ D6, D7 Unsupported
Drush 6 6.x 5.3.0+ D6, D7 Unsupported
Drush 5 5.x 5.2.0+ D6, D7 Unsupported
diff --git a/vendor/drush/drush/docs/make.md b/vendor/drush/drush/docs/make.md new file mode 100644 index 0000000000..844be6a29b --- /dev/null +++ b/vendor/drush/drush/docs/make.md @@ -0,0 +1,611 @@ + +Drush make +---------- +**Drush make is no longer maintained ([#3946](https://github.com/drush-ops/drush/issues/3946#issuecomment-467861007)), use [drupal-composer/drupal-project](https://github.com/drupal-composer/drupal-project) template for Drupal projects!** + +Drush make is an extension to drush that can create a ready-to-use drupal site, +pulling sources from various locations. It does this by parsing a flat text file +(similar to a drupal `.info` file) and downloading the sources it describes. In +practical terms, this means that it is possible to distribute a complicated +Drupal distribution as a single text file. + +Among Drush make's capabilities are: + +- Downloading Drupal core, as well as contrib modules from drupal.org. +- Checking code out from SVN, git, and bzr repositories. +- Getting plain `.tar.gz` and `.zip` files (particularly useful for libraries + that can not be distributed directly with drupal core or modules). +- Fetching and applying patches. +- Fetching modules, themes, and installation profiles, but also external + libraries. + + +Usage +----- +The `drush make` command can be executed from a path within a Drupal codebase or +independent of any Drupal sites entirely. See the examples below for instances +where `drush make` can be used within an existing Drupal site. + + drush make [-options] [filename.make] [build path] + +The `.make` file format +----------------------- +Each makefile is a plain text file that adheres to YAML syntax. See +the included `examples/example.make.yml` for an example of a working +makefile. + +The older Drupal `.info` INI format is also supported. See +`examples/example.make` for a working example. + +### Core version + +The make file always begins by specifying the core version of Drupal +for which each package must be compatible. Example: + + core: 7.x + +### API version + +The make file must specify which Drush Make API version it uses. This version +of Drush Make uses API version `2` + + api: 2 + + +### Projects + +An array of the projects (e.g. modules, themes, libraries, and drupal) to be +retrieved. Each project name can be specified as a single string value. If +further options need to be provided for a project, the project should be +specified as the key. + +**Project with no further options:** + + projects: + - drupal + +**Project using options (see below):** + + projects: + drupal: + version: "7.33" + +Do not use both types of declarations for a single project in your makefile. + + +### Project options + +- `version` + + Specifies the version of the project to retrieve. + This can be as loose as the major branch number or + as specific as a particular point release. + + projects: + views: + # Picks the latest release. + version: ~ + + projects: + views: + version: "2.8" + + projects: + views: + version: "3.0-alpha2" + + # Shorthand syntax for versions if no other options are to be specified + projects: + views: "3.0-alpha2" + + Note that version numbers should be enclosed in + quotes to ensure they are interpreted correctly + by the YAML parser. + +- `patch` + + One or more patches to apply to this project. An array of patches, + each specified as a URL or local path relative to the makefile. + + projects: + calendar: + patch: + rfc-fixes: + url: "http://drupal.org/files/issues/cal-760316-rfc-fixes-2.diff" + md5: "e4876228f449cb0c37ffa0f2142" + adminrole: + # shorthand syntax if no md5 checksum is specified + patch: + - "http://drupal.org/files/issues/adminrole_exceptions.patch" + - "http://drupal.org/files/issues/adminrole-213212-01.patch" + - "adminrole-customizations.patch" + +- `subdir` + + Place a project within a subdirectory of the `--contrib-destination` + specified. In the example below, `cck` will be placed in + `sites/all/modules/contrib` instead of the default `sites/all/modules`. + + projects: + cck: + subdir: "contrib" + +- `location` + + URL of an alternate project update XML server to use. Allows project XML data + to be retrieved from sites other than `updates.drupal.org`. + + projects: + tao: + location: "http://code.developmentseed.com/fserver" + +- `type` + + The project type. Must be provided if an update XML source is not specified + and/or using version control or direct retrieval for a project. May be one of + the following values: core, module, profile, theme. + + projects: + mytheme: + type: "theme" + +- `directory_name` + + Provide an alternative directory name for this project. By default, the + project name is used. + + projects: + mytheme: + directory_name: "yourtheme" + +- `l10n_path` + + Specific URL (can include tokens) to a translation. Allows translations to be + retrieved from l10n servers other than `localize.drupal.org`. + + projects: + mytheme: + l10n_path: "http://myl10nserver.com/files/translations/%project-%core-%version-%language.po" + +- `l10n_url` + + URL to an l10n server XML info file. Allows translations to be retrieved from + l10n servers other than `localize.drupal.org`. + + projects: + mytheme: + l10n_url: "http://myl10nserver.com/l10n_server.xml" + +- `overwrite` + + Allows the project to be installed in a directory that is not empty. + If not specified this is treated as FALSE, Drush make sets an error when the directory is not empty. + If specified TRUE, Drush make will continue and use the existing directory. + Useful when adding extra files and folders to existing folders in libraries or module extensions. + + projects: + myproject: + overwrite: TRUE + +- `translations` + + Retrieve translations for the specified language, if available, for all projects. + + translations: + - es + - fr + +- `do_recursion` + + Recursively build an included makefile. Defaults to 'true'. + + do_recursion: false + +- `variant` + + Which type of tarball to download for profiles. Valid options include: + - 'full': complete distro including Drupal core, e.g. `distro_name-core.tar.gz` + - 'projects': the fully built profile, projects defined drupal-org.make, etc., e.g. `distro_name-no-core.tar.gz` + - 'profile-only' (just the bare profile, e.g. `distro_name.tar.gz`). + Defaults to 'profile-only'. When using 'projects', `do_recursion: false` will be necessary to avoid recursively making any makefiles included in the profile. + + variant: projects + + + +### Project download options + + Use an alternative download method instead of retrieval through update XML. + + If no download type is specified, make defaults the type to + `git`. Additionally, if no url is specified, make defaults to use + Drupal.org. + + The following methods are available: + +- `download[type] = file` + + Retrieve a project as a direct download. Options: + + `url` - the URL of the file. Required. + The URL can also be a path to a local file either using the bare path or + the file:// protocol. The path may be absolute or relative to the makefile. + + `md5`, `sha1`, `sha256`, or `sha512` - one or more checksums for the file. Optional. + + `request_type` - the request type - get or post. post depends on + http://drupal.org/project/make_post. Optional. + + `data` - The post data to be submitted with the request. Should be a + valid URL query string. Requires http://drupal.org/project/make_post. Optional. + + `filename` - What to name the file, if it's not an archive. Optional. + + `subtree` - if the download is an archive, only this subtree within the + archive will be copied to the target destination. Optional. + +- `download[type] = copy` + + Copies a project from a local folder. Options: + + `url` - the URL of the folder. Required. + The URL must be a path to a local folder either using the bare path or + the file:// protocol. The path may be absolute or relative to the makefile. + + projects[example][type] = "profile" + projects[example][download][type] = "copy" + projects[example][download][url] = "file://./example" + +- `download[type] = bzr` + + Use a bazaar repository as the source for this project. Options: + + `url` - the URL of the repository. Required. + + `working-copy` - If true, the checked out source will be kept as a working copy rather than exported as standalone files + +- `download[type] = git` + + Use a git repository as the source for this project. Options: + + `url` - the URL of the repository. Required. + + `branch` - the branch to be checked out. Optional. + + `revision` - a specific revision identified by commit to check + out. Optional. Note that it is recommended on use `branch` in + combination with `revision` if relying on the .info file rewriting. + + `tag` - the tag to be checked out. Optional. + + projects[mytheme][download][type] = "git" + projects[mytheme][download][url] = "git://github.com/jane_doe/mytheme.git" + + `refspec` - the git reference to fetch and checkout. Optional. + + If this is set, it will have priority over tag, revision and branch options. + + `working-copy` - If true, the checked out source will be kept as a working copy rather than exported as standalone files + +- `download[type] = svn` + + Use an SVN repository as the source for this project. Options: + + `url` - the URL of the repository. Required. + + `interactive` - whether to prompt the user for authentication credentials + when using a private repository. Allows username and/or password options to + be omitted. Optional. + + `username` - the username to use when retrieving an SVN project as a working + copy or from a private repository. Optional. + + `password` - the password to use when retrieving an SVN project as a working + copy or from a private repository. Optional. + + projects: + mytheme: + download: + type: "svn" + url: "http://example.com/svnrepo/cool-theme/" + + `working-copy` - If true, the checked out source will be kept as a working copy rather than exported as standalone files + + Shorthand for `download[url]` available for all download types: + + projects: + mytheme: + download: "git://github.com/jane_doe/mytheme.git" + + is equivalent to: + + projects: + mytheme: + download: + url: "git://github.com/jane_doe/mytheme.git" + +### Libraries + +An array of non-Drupal-specific libraries to be retrieved (e.g. js, PHP or other +Drupal-agnostic components). Each library should be specified as the key of an +array of options in the libraries array. + +**Example:** + + libraries: + jquery_ui: + download: + type: "file" + url: "http://jquery- ui.googlecode.com/files/jquery.ui-1.6.zip" + md5: "c177d38bc7af59d696b2efd7dda5c605" + + +### Library options + +Libraries share the `download`, `subdir`, and `directory_name` options with +projects. Additionally, they may specify a destination: + +- `destination` + + The target path to which this library should be moved. The path is relative to + that specified by the `--contrib-destination` option. By default, libraries + are placed in the `libraries` directory. + + libraries: + jquery_ui: + destination: "modules/contrib/jquery_ui" + + +### Includes + +An array of makefiles to include. Each include may be a local relative path to +the include makefile directory, a direct URL to the makefile, or from a git +repository. Includes are appended in order with the source makefile appended +last. As a result, values in the source makefile take precedence over those in +includes. Use `overrides` for the reverse order of precedence. + +**Example:** + + includes: + # Includes a file in the same directory. + - "example.make" + # Includes a file with a relative path. + - "../example_relative/example_relative.make" + # A remote-hosted file. + - "http://www.example.com/remote.make" + # A file on a git repository. + - makefile: "example_dir/example.make" + download: + type: "git" + url: "git@github.com:organisation/repository.git" + # Branch could be tag or revision, it relies on the standard Drush git download feature. + branch: "master" + +The `--includes` option is available for most make commands, and allows +makefiles to be included at build-time. + +**Example:** + + # Build from a production makefile, but add development and test projects. + $ drush make production.make --includes=dev.make,test.make + + +### Overrides + +Similar to `includes`, `overrides` will include content from other makefiles. +However, the order of precedence is reversed. That is, they override the +keys/values of the source makefile. + +The `--overrides` option is available for most make commands, and allows +overrides to be included at build-time. + +**Example:** + + #production.make.yml: + api: 2 + core: 8.x + includes: + - core.make + - contrib.make + projects: + custom_feature_A: + type: module + download: + branch: production + type: git + url: http://github.com/example/custom_feature_A.git + custom_feature_B: + type: module + download: + branch: production + type: git + url: http://github.com/example/custom_feature_B.git + + # Build production code-base. + $ drush make production.make.yml + + #testing.make + projects: + custom_feature_A: + download: + branch: dev/bug_fix + custom_feature_B: + download: + branch: feature/new_feature + + # Build production code-base using development/feature branches for custom code. + $ drush make /path/to/production.make --overrides=http://url/of/testing.make + + +### Defaults + +If all projects or libraries have identical settings for a given +attribute, the `defaults` array can be used to specify these, +rather than specifying the attribute for each project. + +**Example:** + + # Specify common subdir of "contrib" + defaults: + projects: + subdir: "contrib" + # Projects that don't specify subdir will go to the 'contrib' directory. + projects: + views: + version: "3.3" + # Override a default value. + devel: + subdir: "development" + +### Overriding properties + +Makefiles which include others may override the included makefiles properties. +Properties in the includer takes precedence over the includee. + +**Example:** + +`base.make` + + core: "6.x" + views: + subdir: "contrib" + cck: + subdir: "contrib" + +`extender.make` + + includes: + - "base.make" + projects: + views: + # This line overrides the included makefile's 'subdir' option + subdir: "patched" + + # These lines overrides the included makefile, switching the download type + # to a git clone. + type: "module" + download: + type: "git" + url: "https://git.drupalcode.org/project/views.git" + +A project or library entry of an included makefile can be removed entirely by +setting the corresponding key to NULL: + + # This line removes CCK entirely which was defined in base.make + cck: ~ + + +Recursion +--------- + +If a project that is part of a build contains a `.make.yml` itself, Drush make will +automatically parse it and recurse into a derivative build. + +For example, a full build tree may look something like this: + + Drush make distro.make distro + + distro.make FOUND + - Drupal core + - Foo bar install profile + + foobar.make.yml FOUND + - CCK + - Token + - Module x + + x.make FOUND + - External library x.js + - Views + - etc. + +Recursion can be used to nest an install profile build in a Drupal site, easily +build multiple install profiles on the same site, fetch library dependencies +for a given module, or bundle a set of module and its dependencies together. +For Drush Make to recognize a makefile embedded within a project, the makefile +itself must have the same name as the project. For instance, the makefile +embedded within the managingnews profile must be called "managingnews.make". If +no makefile matching the project's name is found, Drush Make will look for a +"drupal-org.make.yml" makefile instead. The file must be in the project's root +directory. Subdirectories will be ignored. + +**Build a full Drupal site with the Managing News install profile:** + + core: 6.x + api: 2 + projects: + - drupal + - managingnews + +** Use a distribution as core ** + + core: 7.x + api: 2 + projects: + commerce_kickstart: + type: "core" + version: "7.x-1.19" + +This behavior can be overridden globally using the `--no-recursion` option, or on a project-by-project basis by setting the `do_recursion` project parameter to 'false' in a makefile: + + core: 7.x + api: 2 + projects: + drupal: + type: core + hostmaster: + type: profile + do_recursion: false + + +Testing +------- +Drush make also comes with testing capabilities, designed to test Drush make +itself. Writing a new test is extremely simple. The process is as follows: + +1. Figure out what you want to test. Write a makefile that will test + this out. You can refer to existing test makefiles for + examples. These are located in `DRUSH/tests/makefiles`. +2. Drush make your makefile, and use the --md5 option. You may also use other + options, but be sure to take note of which ones for step 4. +3. Verify that the result you got was in fact what you expected. If so, + continue. If not, tweak it and re-run step 2 until it's what you expected. +4. Using the md5 hash that was spit out from step 2, make a new entry in the + tests clase (DRUSH/tests/makeTest.php), following the example below. + 'machine-readable-name' => array( + 'name' => 'Human readable name', + 'makefile' => 'tests/yourtest.make', + 'messages' => array( + 'Build hash: f68e6510-your-hash-e04fbb4ed', + ), + 'options' => array('any' => TRUE, 'other' => TRUE, 'options' => TRUE), + ), +5. Test! Run Drush test suite (see DRUSH/tests/README.md). To just + run the make tests: + + `./unish.sh --filter=makeMake .` + + +You can check for any messages you want in the message array, but the most +basic tests would just check the build hash. + +Generate +-------- + +Drush make has a primitive makefile generation capability. To use it, simply +change your directory to the Drupal installation from which you would like to +generate the file, and run the following command: + +`drush generate-makefile /path/to/make-file.make` + +This will generate a basic makefile. If you have code from other repositories, +the makefile will not complete - you'll have to fill in some information before +it is fully functional. + +Maintainers +----------- +- Jonathan Hedstrom ([jhedstrom](https://www.drupal.org/u/jhedstrom)) +- Christopher Gervais ([ergonlogic](http://drupal.org/u/ergonlogic)) +- [The rest of the Drush maintainers](https://github.com/drush-ops/drush/graphs/contributors) + +Original Author +--------------- +[Dmitri Gaskin (dmitrig01)](https://twitter.com/dmitrig01) diff --git a/vendor/drush/drush/docs/output-formats.md b/vendor/drush/drush/docs/output-formats.md new file mode 100644 index 0000000000..1dc37285c8 --- /dev/null +++ b/vendor/drush/drush/docs/output-formats.md @@ -0,0 +1,20 @@ +Drush Output Formats +==================== + +Many Drush commands produce output that may be rendered in a variety of different ways using a pluggable formatting system. Drush commands that support output formats will show a --format option in their help text. The available formats are also listed in the help text, along with the default value for the format option. The list of formats shown is abbreviated; to see the complete list of available formats, run the help command with the --verbose option. + +The --pipe option is a quick, consistent way to get machine readable output from a command, in whatever way the command author thought was helpful. The --pipe option is equivalent to using --format=`` The pipe format will be shown in the options section of the command help, under the --pipe option. For historic reasons, --pipe also hides all log messages. + +To best understand how the various Drush output formatters work, it is best to first look at the output of the command using the 'var\_export' format. This will show the result of the command using the exact structure that was built by the command, without any reformatting. This is the standard format for the Drush command. Different formatters will take this information and present it in different ways. + +Global Options +-------------- + +- --list-separator: Specify how elements in a list should be separated. In lists of lists, this applies to the elements in the inner lists. +- --line-separator: In nested lists of lists, specify how the outer lists ("lines") should be separated. + +Output Formats +-------------- + +A list of available formats, and their affect on the output of certain Drush commands, is shown below. + diff --git a/vendor/drush/drush/docs/repl.md b/vendor/drush/drush/docs/repl.md new file mode 100644 index 0000000000..09322380d7 --- /dev/null +++ b/vendor/drush/drush/docs/repl.md @@ -0,0 +1 @@ +You can then use an interactive PHP REPL with your bootstrapped site (remote or local). It’s a Drupal code playground. You can do quick code experimentation, grab some data, or run Drush commands. This can also help with debugging certain issues. See [this blog post](http://blog.damiankloip.net/2015/drush-php) for an introduction. Run `help` for a list of commands. diff --git a/vendor/drush/drush/docs/shellaliases.md b/vendor/drush/drush/docs/shellaliases.md new file mode 100644 index 0000000000..18ab0c41f8 --- /dev/null +++ b/vendor/drush/drush/docs/shellaliases.md @@ -0,0 +1,33 @@ +Drush Shell Aliases +=================== + +A Drush shell alias is a shortcut to any Drush command or any shell command. Drush shell aliases are very similar to [git aliases](https://git.wiki.kernel.org/index.php/Aliases\#Advanced). + +A shell alias is defined in a Drush configuration file called drushrc.php. See `drush topic docs-configuration`. There are two kinds of shell aliases: an alias whose value begins with a '!' will execute the rest of the line as bash commands. Aliases that do not start with a '!' will be interpreted as Drush commands. + + $options['shell-aliases']['pull'] = '!git pull'; + $options['shell-aliases']['noncore'] = 'pm-list --no-core'; + +With the above two aliases defined, `drush pull` will then be equivalent to `git pull`, and `drush noncore` will be equivalent to `drush pm-list --no-core`. + +Shell Alias Replacements +------------------------ + +Shell aliases are even more powerful when combined with shell alias replacements and site aliases. Shell alias replacements take the form of {{sitealias-item}} or {{%pathalias-item}}, and also the special {{@target}}, which is replaced with the name of the site alias used, or '@none' if none was used. + +For example, given the following site alias: + + $aliases['dev'] = array ( + 'root' => '/path/to/drupal', + 'uri' => 'http://example.com', + '#live' => '@acme.live', + ); + +The alias below can be used for all your projects to fetch the database and files from the client's live site via `drush @dev pull-data`. Note that these aliases assume that the alias used defines an item named '\#live' (as shown in the above alias). + + $options['shell-aliases'] = array( + 'pull-data' => '!drush sql-sync {{#live}} {{@target}} && drush rsync {{#live}}:%files {{@target}}:%files' + ); + +If the user does not use these shell aliases with any site alias, then an error will be returned and the script will not run. These aliases with replacements can be used to quickly run combinations of drush sql-sync and rsync commands on the "standard" source or target site, reducing the risk of typos that might send information in the wrong direction or to the wrong site. + diff --git a/vendor/drush/drush/docs/shellscripts.md b/vendor/drush/drush/docs/shellscripts.md new file mode 100644 index 0000000000..184ddb751c --- /dev/null +++ b/vendor/drush/drush/docs/shellscripts.md @@ -0,0 +1,50 @@ +Drush Shell Scripts +=================== + +A drush shell script is any Unix shell script file that has its "execute" bit set (i.e., via `chmod +x myscript.drush`) and that begins with a specific line: + + #!/usr/bin/env drush + +or + + #!/full/path/to/drush + +The former is the usual form, and is more convenient in that it will allow you to run the script regardless of where drush has been installed on your system, as long as it appears in your PATH. The later form allows you to specify the drush command add options to use, as in: + + #!/full/path/to/drush php-script --some-option + +Adding specific options is important only in certain cases, described later; it is usually not necessary. + +Drush scripts do not need to be named "\*.drush" or "\*.script"; they can be named anything at all. To run them, make sure they are executable (`chmod +x helloworld.script`) and then run them from the shell like any other script. + +There are two big advantages to drush scripts over bash scripts: + +- They are written in php +- drush can bootstrap your Drupal site before running your script. + +To bootstrap a Drupal site, provide an alias to the site to bootstrap as the first commandline argument. + +For example: + + $ helloworld.script @dev a b c + +If the first argument is a valid site alias, drush will remove it from the argument list and bootstrap that site, then run your script. The script itself will not see @dev on its argument list. If you do not want drush to remove the first site alias from your scripts argument list (e.g. if your script wishes to syncronise two sites, specified by the first two arguments, and does not want to bootstrap either of those two sites), then fully specify the drush command (php-script) and options to use, as shown above. By default, if the drush command is not specified, drush will provide the following default line: + + #!/full/path/to/drush php-script --bootstrap-to-first-arg + +It is the option --bootstrap-to-first-arg that causes drush to pull off the first argument and bootstrap it. The way to get rid of that option is to specify the php-script line to run, and leave it off, like so: + + #!/full/path/to/drush php-script + +Note that 'php-script' is the only built-in drush command that makes sense to put on the "shebang" ("\#!" is pronounced "shebang") line. However, if you wanted to, you could implement your own custom version of php-script (e.g. to preprocess the script input, perhaps), and specify that command on the shebang line. + +Drush scripts can access their arguments via the drush\_shift() function: + + while ($arg = drush_shift()) { + drush_print($arg); + } + +Options are available via drush\_get\_option('option-name'). The directory where the script was launched is available via drush_cwd() + +See the example drush script in `drush topic docs-examplescript`, and the list of drush error codes in `drush topic docs-errorcodes`. + diff --git a/vendor/drush/drush/docs/strict-options.md b/vendor/drush/drush/docs/strict-options.md new file mode 100644 index 0000000000..327aff7065 --- /dev/null +++ b/vendor/drush/drush/docs/strict-options.md @@ -0,0 +1,15 @@ +Strict Option Handling +====================== + +Some Drush commands use strict option handling; these commands require that all Drush global option appear on the command line before the Drush command name. + +One example of this is the core-rsync command: + + drush --simulate core-rsync -v @site1 @site2 + +The --simulate option is a Drush global option that causes Drush to print out what it would do if the command is executed, without actually taking any action. Commands such as core-rsync that use strict option handling require that --simulate, if used, must appear before the command name. Most Drush commands allow the --simulate to be placed anywhere, such as at the end of the command line. + +The -v option above is an rsync option. In this usage, it will cause the rsync command to run in verbose mode. It will not cause Drush to run in verbose mode, though, because it appears after the core-rsync command name. Most Drush commands would be run in verbose mode if a -v option appeared in the same location. + +The advantage of strict option handling is that it allows Drush to pass options and arguments through to a shell command. Some shell commands, such as rsync and ssh, either have options that cannot be represented in Drush. For example, rsync allows the --exclude option to appear multiple times on the command line, but Drush only allows one instance of an option at a time for most Drush commands. Strict option handling overcomes this limitation, plus possible conflict between Drush options and shell command options with the same name, at the cost of greater restriction on where global options can be placed. + diff --git a/vendor/drush/drush/docs/usage.md b/vendor/drush/drush/docs/usage.md new file mode 100644 index 0000000000..bd996c9a4b --- /dev/null +++ b/vendor/drush/drush/docs/usage.md @@ -0,0 +1,50 @@ +Usage +----------- + +Drush can be run in your shell by typing "drush" from within any Drupal root directory. + + $ drush [options] [argument1] [argument2] + +Use the 'help' command to get a list of available options and commands: + + $ drush help + +For even more documentation, use the 'topic' command: + + $ drush topic + +Options +----------- + +For multisite installations, use the --uri option to target a particular site. If +you are outside the Drupal web root, you might need to use the --root, --uri or other +command line options just for Drush to work. + + $ drush --uri=http://example.com pm-updatecode + +If you wish to be able to select your Drupal site implicitly from the +current working directory without using the --uri option, but you need your +base_url to be set correctly, you may force it by setting the uri in +a drushrc.php file located in the same directory as your settings.php file. + +``` +$options['uri'] = "http://example.com"; +``` + +Site Aliases +------------ + +Drush lets you run commands on a remote server, or even on a set of remote +servers. Once defined, aliases can be referenced with the @ nomenclature, i.e. + +```bash +# Run pending updates on staging site. +$ drush @staging updatedb +# Synchronize staging files to production +$ drush rsync @staging:%files/ @live:%files +# Synchronize database from production to dev, excluding the cache table +$ drush sql-sync --structure-tables-key=custom @live @dev +``` + +See [example.aliases.drushrc.php](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/example.aliases.drushrc.php) for more information. + diff --git a/vendor/drush/drush/dr.bat b/vendor/drush/drush/dr.bat new file mode 100644 index 0000000000..6087c717b8 --- /dev/null +++ b/vendor/drush/drush/dr.bat @@ -0,0 +1,5 @@ +@ECHO OFF +REM Running this file is equivalent to running `php drush` +setlocal DISABLEDELAYEDEXPANSION +SET BIN_TARGET=%~dp0drush +php "%BIN_TARGET%" %* diff --git a/vendor/drush/drush/drush b/vendor/drush/drush/drush new file mode 100755 index 0000000000..902e66c6bf --- /dev/null +++ b/vendor/drush/drush/drush @@ -0,0 +1,115 @@ +#!/usr/bin/env php + "drush wrapper" -> "drush launcher". + * + * Brief description of each: + * + * - Drush finder: Finds the right Drush script and calls it. + * - Drush wrapper: Contains user customizations to options. + * - Drush launcher: Excutes drush.php. + * + * A full explanation of each script follows. + * + * + * DRUSH FINDER + * + * - The "drush" script on the user's global $PATH + * - It's goal is to find the correct site-local Drush to run. + * + * The Drush finder will locate the correct site-local Drush to use + * by examining: + * + * a) The --root option + * b) The site set via `drush site set` in the current terminal + * c) The cwd + * + * If no site-local Drush is found, then the global Drush will be + * used. The Drush finder assumes that the global Drush is the + * "Drush launcher" found in the same directory as the Drush finder itself. + * + * If a site-local Drush is found, then the Drush finder will call + * either the "Drush wrapper", if it exists, or the "Drush launcher" if + * there is no wrapper script. + * + * + * DRUSH WRAPPER + * + * - The drush.wrapper script that the user optionally copies and edits. + * - Its goal is to allow the user to add options when --local is in use + * + * The Drush "wrapper" is found at examples/drush.wrapper, and may optionally + * be copied to __ROOT__ by the user. It may be named either + * "drush" or "drush.wrapper". It will call the "Drush launcher" + * for the same site that it is located in. It adds the --local flag; the + * user is encouraged to add other options to the "Drush wrapper", e.g. to set + * the location where aliases and global commandfiles can be found. + * The Drush "finder" script always calls the "Drush wrapper" if it exists; + * however, if the user does not want to customize the early options of + * the site-local Drush (site-alias locations, etc.), then the wrapper does not + * need to be used. + * + * + * DRUSH LAUNCHER + * + * - The "drush.launcher" script in vendor/bin + * - The bash script formerly called "drush" + * + * The "Drush launcher" is the traditional script that identifies PHP and + * sets up to call drush.php. It is called by the "Drush wrapper", or + * directly by the "Drush launcher" if there is no "Drush wrapper" in use. + * + * + * LOCATIONS FOR THESE SCRIPTS + * + * "Drush finder" : __ROOT__/vendor/bin/drush (composer install) + * __DRUSH__/drush (source) + * + * "Drush wrapper" : __ROOT__/drush (copied by user) + * __DRUSH__/examples/drush.wrapper (source) + * + * "Drush launcher" : __ROOT__/vendor/bin/drush.launcher (composer install) + * __DRUSH__/drush.launcher (source) + * + * + * BACKEND CALL DISPATCHING + * + * Backend calls are typically set up to call the "drush" script in the $PATH, + * or perhaps some might call __ROOT__/vendor/bin/drush directly, by way + * of the "drush-script" element in a site alias. In either event, this is + * the "drush finder" script. + * + * The backend call will always set --root. The "Drush finder" script + * always favors the site-local Drush stored with the site indicated by the + * --root option, if it exists. If there is no site-local Drush, then the + * "Drush finder" will behave as usual (i.e., it will end up calling the + * "Drush launcher" located next to it). + * + * This should always get you the correct "Drush" for local and remote calls. + * Note that it is also okay for aliases to specify a path directly to + * drush.launcher, in instances where it is known that a recent version of + * Drush is installed on the remote end. + */ + +if (!function_exists("drush_startup")) { + include __DIR__ . '/includes/startup.inc'; +} +drush_startup($argv); diff --git a/vendor/drush/drush/drush.api.php b/vendor/drush/drush/drush.api.php new file mode 100644 index 0000000000..6b99d9ddb0 --- /dev/null +++ b/vendor/drush/drush/drush.api.php @@ -0,0 +1,425 @@ + 0;"); +} + +/** + * Add help components to a command. + */ +function hook_drush_help_alter(&$command) { + if ($command['command'] == 'sql-sync') { + $command['options']['myoption'] = "Description of modification of sql-sync done by hook"; + $command['sub-options']['sanitize']['my-sanitize-option'] = "Description of sanitization option added by hook (grouped with --sanitize option)"; + } + if ($command['command'] == 'global-options') { + // Recommended: don't show global hook options in brief global options help. + if ($command['#brief'] === FALSE) { + $command['options']['myglobaloption'] = 'Description of option used globally in all commands (e.g. in a commandfile init hook)'; + } + } +} + +/** + * Add/edit options to cache-clear command. + * + * @param array $types + * Adjust types as needed. Is passed by reference. + * + * @param bool $include_bootstrapped_types + * If FALSE, omit types which require a FULL bootstrap. + */ +function hook_drush_cache_clear(&$types, $include_bootstrapped_types) { + $types['views'] = 'views_invalidate_cache'; +} + +/** + * Inform drush about one or more engine types. + * + * This hook allow to declare available engine types, the cli option to select + * between engine implementatins, which one to use by default, global options + * and other parameters. Commands may override this info when declaring the + * engines they use. + * + * @return array + * An array whose keys are engine type names and whose values describe + * the characteristics of the engine type in relation to command definitions: + * + * - description: The engine type description. + * - topic: If specified, the name of the topic command that will + * display the automatically generated topic for this engine. + * - topic-file: If specified, the path to the file that will be + * displayed at the head of the automatically generated topic for + * this engine. This path is relative to the Drush root directory; + * non-core commandfiles should therefore use: + * 'topic-file' => dirname(__FILE__) . '/mytopic.html'; + * - topics: If set, contains a list of topics that should be added to + * the "Topics" section of any command that uses this engine. Note + * that if 'topic' is set, it will automatically be added to the topics + * list, and therefore does not need to also be listed here. + * - option: The command line option to choose an implementation for + * this engine type. + * FALSE means there's no option. That is, the engine type is for internal + * usage of the command and thus an implementation is not selectable. + * - default: The default implementation to use by the engine type. + * - options: Engine options common to all implementations. + * - add-options-to-command: If there's a single implementation for this + * engine type, add its options as command level options. + * - combine-help: If there are multiple implementations for this engine + * type, then instead of adding multiple help items in the form of + * --engine-option=engine-type [description], instead combine all help + * options into a single --engine-option that lists the different possible + * values that can be used. + * + * @see drush_get_engine_types_info() + * @see pm_drush_engine_type_info() + */ +function hook_drush_engine_type_info() { + return array( + 'dessert' => array( + 'description' => 'Choose a dessert while the sandwich is baked.', + 'option' => 'dessert', + 'default' => 'ice-cream', + 'options' => 'sweetness', + 'add-options-to-command' => FALSE, + ), + ); +} + +/** + * Inform drush about one or more engines implementing a given engine type. + * + * - description: The engine implementation's description. + * - implemented-by: The engine that actually implements this engine. + * This is useful to allow the implementation of similar engines + * in the reference one. + * Defaults to the engine type key (e.g. 'ice-cream'). + * - verbose-only: The engine implementation will only appear in help + * output in --verbose mode. + * + * This hook allow to declare implementations for an engine type. + * + * @see pm_drush_engine_package_handler() + * @see pm_drush_engine_version_control() + */ +function hook_drush_engine_ENGINE_TYPE() { + return array( + 'ice-cream' => array( + 'description' => 'Feature rich ice-cream with all kind of additives.', + 'options' => array( + 'flavour' => 'Choose your favorite flavour', + ), + ), + 'frozen-yogurt' => array( + 'description' => 'Frozen dairy dessert made with yogurt instead of milk and cream.', + 'implemented-by' => 'ice-cream', + ), + ); +} + +/** + * Alter the order that hooks are invoked. + * + * When implementing a given hook we may need to ensure it is invoked before + * or after another implementation of the same hook. For example, let's say + * you want to implement a hook that would be called after drush_make. You'd + * write a drush_MY_MODULE_post_make() function. But if you need your hook to + * be called before drush_make_post_make(), you can ensure this by implemen- + * ting MY_MODULE_drush_invoke_alter(). + * + * @see drush_command_invoke_all_ref() + */ +function hook_drush_invoke_alter($modules, $hook) { + if ($hook == 'some_hook') { + // Take the module who's hooks would normally be called last. + $module = array_pop($modules); + // Ensure it'll be called first for 'some_hook'. + array_unshift($modules, $module); + } +} + +/** + * @} End of "addtogroup hooks". + */ diff --git a/vendor/drush/drush/drush.complete.sh b/vendor/drush/drush/drush.complete.sh new file mode 100755 index 0000000000..62ad507433 --- /dev/null +++ b/vendor/drush/drush/drush.complete.sh @@ -0,0 +1,50 @@ +# BASH completion script for Drush. +# +# Place this in your /etc/bash_completion.d/ directory or source it from your +# ~/.bash_completion or ~/.bash_profile files. Alternatively, source +# examples/example.bashrc instead, as it will automatically find and source +# this file. +# +# If you're using ZSH instead of BASH, add the following to your ~/.zshrc file +# and source it. +# +# autoload bashcompinit +# bashcompinit +# source /path/to/your/drush.complete.sh + +# Ensure drush is available. +command -v drush >/dev/null || alias drush &> /dev/null || return + +__drush_ps1() { + f="${TMPDIR:-/tmp/}/drush-env-${USER}/drush-drupal-site-$$" + if [ -f $f ] + then + __DRUPAL_SITE=$(cat "$f") + else + __DRUPAL_SITE="$DRUPAL_SITE" + fi + + # Set DRUSH_PS1_SHOWCOLORHINTS to a non-empty value and define a + # __drush_ps1_colorize_alias() function for color hints in your Drush PS1 + # prompt. See example.prompt.sh for an example implementation. + if [ -n "${__DRUPAL_SITE-}" ] && [ -n "${DRUSH_PS1_SHOWCOLORHINTS-}" ]; then + __drush_ps1_colorize_alias + fi + + [[ -n "$__DRUPAL_SITE" ]] && printf "${1:- (%s)}" "$__DRUPAL_SITE" +} + +# Completion function, uses the "drush complete" command to retrieve +# completions for a specific command line COMP_WORDS. +_drush_completion() { + # Set IFS to newline (locally), since we only use newline separators, and + # need to retain spaces (or not) after completions. + local IFS=$'\n' + # The '< /dev/null' is a work around for a bug in php libedit stdin handling. + # Note that libedit in place of libreadline in some distributions. See: + # https://bugs.launchpad.net/ubuntu/+source/php5/+bug/322214 + COMPREPLY=( $(drush --early=includes/complete.inc "${COMP_WORDS[@]}" < /dev/null 2> /dev/null) ) +} + +# Register our completion function. We include common short aliases for Drush. +complete -o bashdefault -o default -o nospace -F _drush_completion d dr drush drush5 drush6 drush7 drush8 drush.php diff --git a/vendor/drush/drush/drush.info b/vendor/drush/drush/drush.info new file mode 100644 index 0000000000..15c6c5fecd --- /dev/null +++ b/vendor/drush/drush/drush.info @@ -0,0 +1 @@ +drush_version=8.4.11 diff --git a/vendor/drush/drush/drush.launcher b/vendor/drush/drush/drush.launcher new file mode 100755 index 0000000000..0136c0df90 --- /dev/null +++ b/vendor/drush/drush/drush.launcher @@ -0,0 +1,132 @@ +#!/usr/bin/env sh +# +# This script is a simple launcher that will run Drush with the most appropriate +# php executable it can find. In most cases, the 'drush' script should be +# called first; it will in turn launch this script. +# +# Solaris users: Add /usr/xpg4/bin to the head of your PATH +# + +# Get the absolute path of this executable +SELF_DIRNAME="`dirname -- "$0"`" +SELF_PATH="`cd -P -- "$SELF_DIRNAME" && pwd -P`/`basename -- "$0"`" + +# Decide if we are running a Unix shell on Windows +if `which uname > /dev/null 2>&1`; then + case "`uname -a`" in + CYGWIN*) + CYGWIN=1 ;; + MINGW*) + MINGW=1 ;; + esac +fi + +# Resolve symlinks - this is the equivalent of "readlink -f", but also works with non-standard OS X readlink. +while [ -h "$SELF_PATH" ]; do + # 1) cd to directory of the symlink + # 2) cd to the directory of where the symlink points + # 3) Get the pwd + # 4) Append the basename + DIR="`dirname -- "$SELF_PATH"`" + SYM="`readlink "$SELF_PATH"`" + SYM_DIRNAME="`dirname -- "$SYM"`" + SELF_PATH="`cd "$DIR" && cd "$SYM_DIRNAME" && pwd`/`basename -- "$SYM"`" +done + +# If not exported, try to determine and export the number of columns. +# We do not want to run `tput cols` if $TERM is empty, "unknown", or "dumb", because +# if we do, tput will output an undesirable error message to stderr. If +# we redirect stderr in any way, e.g. `tput cols 2>/dev/null`, then the +# error message is suppressed, but tput cols becomes confused about the +# terminal and prints out the default value (80). +if [ -z $COLUMNS ] && [ -n "$TERM" ] && [ "$TERM" != dumb ] && [ "$TERM" != unknown ] && [ -n "`which tput`" ] ; then + # Note to cygwin/mingw/msys users: install the ncurses package to get tput command. + # Note to mingw/msys users: there is no precompiled ncurses package. + if COLUMNS="`tput cols`"; then + export COLUMNS + fi +fi + +if [ -n "$DRUSH_PHP" ] ; then + # Use the DRUSH_PHP environment variable if it is available. + php="$DRUSH_PHP" +else + # On MSYSGIT, we need to use "php", not the full path to php + if [ -n "$MINGW" ] ; then + php="php" + else + # Default to using the php that we find on the PATH. + # We check for a command line (cli) version of php, and if found use that. + # Note that we need the full path to php here for Dreamhost, which behaves oddly. See http://drupal.org/node/662926 + php="`which php-cli 2>/dev/null`" + + if [ ! -x "$php" ]; then + php="`which php 2>/dev/null`" + fi + + if [ ! -x "$php" ]; then + echo "ERROR: can't find php."; exit 1 + fi + fi +fi + +# Build the path to drush.php. +SCRIPT_PATH="`dirname "$SELF_PATH"`/drush.php" +if [ -n "$CYGWIN" ] ; then + # try to determine if we are running cygwin port php or Windows native php: + if [ -n "`"$php" -i | grep -E '^System => Windows'`" ]; then + SCRIPT_PATH="`cygpath -w -a -- "$SCRIPT_PATH"`" + else + SCRIPT_PATH="`cygpath -u -a -- "$SCRIPT_PATH"`" + fi +fi + +# Check to see if the user has provided a php.ini file or drush.ini file in any conf dir +# Last found wins, so search in reverse priority order +for conf_dir in "`dirname "$SELF_PATH"`" /etc/drush "$HOME/.drush" ; do + if [ ! -d "$conf_dir" ] ; then + continue + fi + # Handle paths that don't start with a drive letter on MinGW shell. Equivalent to cygpath on Cygwin. + if [ -n "$MINGW" ] ; then + conf_dir=`sh -c "cd \"$conf_dir\"; pwd -W"` + fi + if [ -f "$conf_dir/php.ini" ] ; then + drush_php_ini="$conf_dir/php.ini" + fi + if [ -f "$conf_dir/drush.ini" ] ; then + drush_php_override="$conf_dir/drush.ini" + fi +done +# If the PHP_INI environment variable is specified, then tell +# php to use the php.ini file that it specifies. +if [ -n "$PHP_INI" ] ; then + drush_php_ini="$PHP_INI" +fi +# If the DRUSH_INI environment variable is specified, then +# extract all ini variable assignments from it and convert +# them into php '-d' options. These will override similarly-named +# options in the php.ini file +if [ -n "$DRUSH_INI" ] ; then + drush_php_override="$DRUSH_INI" +fi + +# Add in the php file location and/or the php override variables as appropriate +if [ -n "$drush_php_ini" ] ; then + php_options="--php-ini $drush_php_ini" +fi +if [ -n "$drush_php_override" ] ; then + php_options=`grep '^[a-z_A-Z0-9.]\+ *=' $drush_php_override | sed -e 's|\([^ =]*\) *= *\(.*\)|\1="\2"|' -e 's| ||g' -e 's|^|-d |' | tr '\n\r' ' '` +fi +# If the PHP_OPTIONS environment variable is specified, then +# its contents will be passed to php on the command line as +# additional options to use. +if [ -n "$PHP_OPTIONS" ] ; then + php_options="$php_options $PHP_OPTIONS" +fi + +# Pass in the path to php so that drush knows which one to use if it +# re-launches itself to run subcommands. We will also pass in the php options. +# Important note: Any options added here must be removed when Drush processes +# a #! (shebang) script. @see drush_adjust_args_if_shebang_script() +exec "$php" $php_options "$SCRIPT_PATH" --php="$php" --php-options="$php_options" "$@" diff --git a/vendor/drush/drush/drush.php b/vendor/drush/drush/drush.php new file mode 100755 index 0000000000..f7053d11e5 --- /dev/null +++ b/vendor/drush/drush/drush.php @@ -0,0 +1,12 @@ +#!/usr/bin/env php + '/path/to/drupal', + * 'uri' => 'http://example.com', + * ); + * @endcode + * + * With this alias definition, then the following commands + * are equivalent: + * + * $ drush @dev status + * $ drush --root=/path/to/drupal --uri=http://example.com status + * + * See the --uri option documentation below for hints on setting its value. + * + * Any option that can be placed on the drush commandline + * can also appear in an alias definition. + * + * There are several ways to create alias files. + * + * + Put each alias in a separate file called ALIASNAME.alias.drushrc.php + * + Put multiple aliases in a single file called aliases.drushrc.php + * + Put groups of aliases into files called GROUPNAME.aliases.drushrc.php + * + * Drush will search for aliases in any of these files using + * the alias search path. The following locations are examined + * for alias files: + * + * 1. In any path set in $options['alias-path'] in drushrc.php, + * or (equivalently) any path passed in via --alias-path=... + * on the command line. + * 2. In one of the default locations: + * a. /etc/drush + * b. $HOME/.drush + * 3. In one of the site-specific locations: + * a. The /drush and /sites/all/drush folders for the current Drupal site + * b. The /drush folder in the directory above the current Drupal site + * + * These locations are searched recursively. If there is a folder called + * 'site-aliases' in any search path, then Drush will search for site aliases + * only inside that directory. + * + * The preferred locations for alias files, then, are: + * + * /etc/drush/site-aliases + * $HOME/.drush/site-aliases + * $ROOT/drush/site-aliases + * $ROOT/sites/all/drush/site-aliases + * $ROOT/../drush/site-aliases + * + * Or any path set in $options['alias-path'] or via --alias-path. + * + * Folders and files containing other versions of drush in their names will + * be *skipped* (e.g. mysite.aliases.drush4rc.php or + * drush4/mysite.aliases.drushrc.php). Names containing the current version of + * Drush (e.g. mysite.aliases.drush5rc.php) will be loaded. + * + * Files stored in these locations can be used to create aliases + * to local and remote Drupal installations. These aliases can be + * used in place of a site specification on the command line, and + * may also be used in arguments to certain commands such as + * "drush rsync" and "drush sql-sync". + * + * Alias files that are named after the single alias they contain + * may use the syntax for the canonical alias shown at the top of + * this file, or they may set values in $options, just + * like a drushrc.php configuration file: + * + * @code + * $options['uri'] = 'http://example.com'; + * $options['root'] = '/path/to/drupal'; + * @endcode + * + * When alias files use this form, then the name of the alias + * is taken from the first part of the alias filename. + * + * Alias groups (aliases stored together in files called + * GROUPNAME.aliases.drushrc.php, as mentioned above) also + * create an implicit namespace that is named after the group + * name. + * + * For example: + * + * @code + * # File: mysite.aliases.drushrc.php + * $aliases['dev'] = array( + * 'root' => '/path/to/drupal', + * 'uri' => 'http://example.com', + * ); + * $aliases['live'] = array( + * 'root' => '/other/path/to/drupal', + * 'uri' => 'http://example.com', + * ); + * @endcode + * + * Then the following special aliases are defined: + * - @mysite: An alias named after the groupname may be used to reference all of + * the aliases in the group (e.g., `drush @mydrupalsite status`). + * - @mysite.dev: A copy of @dev. + * - @mysite.live: A copy of @live. + * + * Thus, aliases defined in an alias group file may be referred to + * either by their simple (short) name, or by their full namespace-qualified + * name. + * + * To see an example alias definition for the current bootstrapped + * site, use the "site-alias" command with the built-in alias "@self": + * + * $ drush site-alias @self + * + * TIP: If you would like to have drush include a 'databases' record + * in the output, include the options --with-db and --show-passwords: + * + * $ drush site-alias @self --with-db --show-passwords + * + * Drush also supports *remote* site aliases. When a site alias is + * defined for a remote site, Drush will use the ssh command to run + * the requested command on the remote machine. The simplest remote + * alias looks like this: + * + * @code + * $aliases['live'] = array( + * 'remote-host' => 'server.domain.com', + * 'remote-user' => 'www-admin', + * ); + * @endcode + * + * The form above requires that Drush be installed on the remote machine, + * and that there also be an alias of the same name defined on that + * machine. The remote alias should define the 'root' and 'uri' elements, + * as shown in the initial example at the top of this file. + * + * If you do not wish to maintain site aliases on the remote machine, + * then you may define an alias that contains all of the elements + * 'remote-host', 'remote-user', 'root' and 'uri'. If you do this, then + * Drush will make the remote call using the --root and --uri options + * to identify the site, so no site alias is required on the remote server. + * + * @code + * $aliases['live'] = array( + * 'remote-host' => 'server.domain.com', + * 'remote-user' => 'www-admin', + * 'root' => '/other/path/to/drupal', + * 'uri' => 'http://example.com', + * ); + * @endcode + * + * If you would like to see all of the Drupal sites at a specified + * root directory, use the built-in alias "@sites": + * + * $ drush -r /path/to/drupal site-alias @sites + * + * It is also possible to define explicit lists of sites using a special + * alias list definition. Alias lists contain a list of alias names in + * the group, and no other information. For example: + * + * @code + * $aliases['mydevsites'] = array( + * 'site-list' => array('@mysite.dev', '@otherside.dev') + * ); + * @endcode + * + * The built-in alias "@none" represents the state of no Drupal site; + * to ignore the site at the cwd and just see default drush status: + * + * $ drush @none status + * + * Wildcard Aliases for Service Providers + * + * Some service providers that manage Drupal sites allow customers to create + * multiple "environments" for a site. It is common for these providers to + * also have a feature to automatically create Drush aliases for all of a + * user's sites. Rather than write one record for every environment in that + * site, it is also possible to write a single "wildcard" alias that represents + * all possible environments. This is possible if the contents of each + * environment alias are identical save for the name of the environment in + * one or more values. The variable `${env-name}` will be substituted with the + * environment name wherever it appears. + * + * Example wildcard record: + * + * @code + * $aliases['remote-example.*'] = array( + * 'remote-host' => '${env-name}.server.domain.com', + * 'remote-user' => 'www-admin', + * 'root' => '/path/to/${env-name}', + * 'uri' => '${env-name}.example.com', + * ); + * @endcode + * + * With a wildcard record, any environment name may be used, and will always + * match. This is not desirable in instances where the specified environment + * does not exist (e.g. if the user made a typo). An alias alter hook in a + * policy file may be used to catch these mistakes and report an error. + * @see policy.drush.inc for an example on how to do this. + + * + * See `drush help site-alias` for more options for displaying site + * aliases. See `drush topic docs-bastion` for instructions on configuring + * remote access to a Drupal site behind a firewall via a bastion server. + * + * Although most aliases will contain only a few options, a number + * of settings that are commonly used appear below: + * + * - 'uri': In Drupal 7 and 8, the value of --uri should always be the same as + * when the site is being accessed from a web browser (e.g. http://example.com) + * In Drupal 6, the value of --uri should always be the same as the site's folder + * name in the 'sites' folder (e.g. default); it is best if the site folder name + * matches the URI from the browser, and is consistent on every instance of the + * same site (e.g. also use sites/example.com for http://example.com). + * - 'root': The Drupal root; must not be specified as a relative path. + * - 'remote-host': The fully-qualified domain name of the remote system + * hosting the Drupal instance. **Important Note: The remote-host option + * must be omitted for local sites, as this option controls various + * operations, such as whether or not rsync parameters are for local or + * remote machines, and so on. @see hook_drush_sitealias_alter() in drush.api.php + * - 'remote-user': The username to log in as when using ssh or rsync. + * - 'os': The operating system of the remote server. Valid values + * are 'Windows' and 'Linux'. Be sure to set this value for all remote + * aliases because the default value is PHP_OS if 'remote-host' + * is not set, and 'Linux' (or $options['remote-os']) if it is. Therefore, + * if you set a 'remote-host' value, and your remote OS is Windows, if you + * do not set the 'OS' value, it will default to 'Linux' and could cause + * unintended consequences, particularly when running 'drush sql-sync'. + * - 'ssh-options': If the target requires special options, such as a non- + * standard port, alternative identity file, or alternative + * authentication method, ssh-options can contain a string of extra + * options that are used with the ssh command, eg "-p 100" + * - 'parent': Deprecated. See "altering aliases", below. + * - 'path-aliases': An array of aliases for common rsync targets. + * Relative aliases are always taken from the Drupal root. + * - '%drush-script': The path to the 'drush' script, or to 'drush.php'. + * This is used by backend invoke when drush + * runs a drush command. The default is 'drush' on remote machines, or + * the full path to drush.php on the local machine. + * - '%drush': A read-only property: points to the folder that the drush + * script is stored in. + * - '%files': Path to 'files' directory. This will be looked up if not + * specified. + * - '%root': A reference to the Drupal root defined in the 'root' item in the + * site alias record. + * - 'php': path to custom php interpreter. Windows support limited to Cygwin. + * - 'php-options': commandline options for php interpreter, you may + * want to set this to '-d error_reporting="E_ALL^E_DEPRECATED"' + * - 'variables' : An array of name/value pairs which override Drupal + * variables/config. These values take precedence even over settings.php + * overrides. + * - 'command-specific': These options will only be set if the alias + * is used with the specified command. In the example below, the option + * `--no-dump` will be selected whenever the @stage alias + * is used in any of the following ways: + * - `drush @stage sql-sync @self @live` + * - `drush sql-sync @stage @live` + * - `drush sql-sync @live @stage` + * In case of conflicting options, command-specific options in targets + * (source and destination) take precedence over command-specific options + * in the bootstrapped site, and command-specific options in a destination + * alias will take precedence over those in a source alias. + * - 'source-command-specific' and 'target-command-specific': Behaves exactly + * like the 'command-specific' option, but is applied only if the alias + * is used as the source or target, respectively, of an rsync or sql-sync + * command. In the example below, `--skip-tables-list=comments` whenever + * the alias @live is the target of an sql-sync command, but comments will + * be included if @live is the source for the sql-sync command. + * - '#peer': Settings that begin with a '#' are not used directly by Drush, and + * in fact are removed before making a backend invoke call (for example). + * These kinds of values are useful in conjunction with shell aliases. See + * `drush topic docs-shell-aliases` for more information on this. + * - '#env-vars': An associative array of keys and values that should be set on + * the remote side before invoking drush. + * - rsync command options have specific requirements in order to + * be passed through by Drush. See the comments on the sample below: + * + * @code + * 'command-specific' => array ( + * 'core-rsync' => array ( + * + * // single-letter rsync options are placed in the 'mode' key + * // instead of adding '--mode=rultvz' to drush rsync command. + * 'mode' => 'rultvz', + * + * // multi-letter rsync options without values must be set to + * // TRUE or NULL to work (i.e. setting $VALUE to 1, 0, or '' + * // will not work). + * 'delete' => TRUE, + * + * // if you need multiple excludes, use an rsync exclude file + * 'exclude-from' => "'/etc/rsync/exclude.rules'", + * + * // filter options with white space must be wrapped in "" to preserve + * // the inner ''. + * 'filter' => "'exclude *.sql'", + * + * // if you need multple filter options, see rsync merge-file options + * 'filter' => "'merge /etc/rsync/default.rules'", + * ), + * ), + * @endcode + * + * Altering aliases: + * + * Alias records are written in php, so you may use php code to alter + * alias records if you wish. For example: + * + * @code + * $common_live = array( + * 'remote-host' => 'myserver.isp.com', + * 'remote-user' => 'www-admin', + * ); + * + * $aliases['live'] = array( + * 'uri' => 'http://example.com', + * 'root' => '/path.to/root', + * ) + $common_live; + * @endcode + * + * If you wish, you might want to put $common_live in a separate file, + * and include it at the top of each alias file that uses it. + * + * You may also use a policy file to alter aliases in code as they are + * loaded by Drush. See policy_drush_sitealias_alter in + * `drush topic docs-policy` for details. + * + * Some examples appear below. Remove the leading hash signs to enable. + */ + +#$aliases['stage'] = array( +# 'uri' => 'http://stage.example.com', +# 'root' => '/path/to/remote/drupal/root', +# 'remote-host' => 'mystagingserver.myisp.com', +# 'remote-user' => 'publisher', +# 'os' => 'Linux', +# 'path-aliases' => array( +# '%drush' => '/path/to/drush', +# '%drush-script' => '/path/to/drush/drush', +# '%files' => 'sites/mydrupalsite.com/files', +# '%custom' => '/my/custom/path', +# ), +# 'variables' => array( +# 'site_name' => 'My Drupal site', +# ), +# 'command-specific' => array ( +# 'sql-sync' => array ( +# 'no-dump' => TRUE, +# ), +# ), +# # This shell alias will run `mycommand` when executed via +# # `drush @stage site-specific-alias` +# 'shell-aliases' => array ( +# 'site-specific-alias' => '!mycommand', +# ), +# ); +#$aliases['dev'] = array( +# 'uri' => 'http://dev.example.com', +# 'root' => '/path/to/drupal/root', +# 'variables' => array( +# 'mail_system' => array('default-system' => 'DevelMailLog'), +# ), +# ); +#$aliases['server'] = array( +# 'remote-host' => 'mystagingserver.myisp.com', +# 'remote-user' => 'publisher', +# 'os' => 'Linux', +# ); +#$aliases['live'] = array( +# 'uri' => 'http://example.com', +# 'root' => $aliases['dev']['root'], +# ) + $aliases['server']; diff --git a/vendor/drush/drush/examples/example.bashrc b/vendor/drush/drush/examples/example.bashrc new file mode 100644 index 0000000000..8f7069f971 --- /dev/null +++ b/vendor/drush/drush/examples/example.bashrc @@ -0,0 +1,263 @@ +# -*- mode: shell-script; mode: flyspell-prog; ispell-local-dictionary: "american" -*- +# +# Example bash aliases to improve your Drush experience with bash. +# Use `drush init` to copy this file to your home directory, rename and +# customize it to suit, and source it from your ~/.bashrc file. +# +# Creates aliases to common Drush commands that work in a global context: +# +# dr - drush +# ddd - drush drupal-directory +# dl - drush pm-download +# ev - drush php-eval +# sa - drush site-alias +# sa - drush site-alias --local-only (show local site aliases) +# st - drush core-status +# use - drush site-set +# +# Aliases for Drush commands that work on the current drupal site: +# +# cc - drush cache-clear +# cr - drush cache-rebuild +# cca - drush cache-clear all +# dis - drush pm-disable +# en - drush pm-enable +# i - drush pm-info +# pml - drush pm-list +# rf - drush pm-refresh +# unin - drush pm-uninstall +# up - drush pm-update +# upc - drush pm-updatecode +# updb - drush updatedb +# q - drush sql-query +# +# Provides several common shell commands to work better with Drush: +# +# ddd @dev - print the path to the root directory of @dev +# cdd @dev - change the current working directory to @dev +# lsd @dev - ls root folder of @dev +# lsd %files - ls "files" directory of current site +# lsd @dev:%devel - ls devel module directory in @dev +# @dev st - drush @dev core-status +# dssh @live - ssh to the remote server @live points at +# gitd @live pull - run `git pull` on the drupal root of @live +# +# Drush site alias expansion is also done for the cpd command: +# +# cpd -R @site1:%files @site2:%files +# +# Note that the 'cpd' alias only works for local sites. Use +# `drush rsync` or gitd` to move files between remote sites. +# +# Aliases are also possible for the following standard +# commands. Uncomment their definitions below as desired. +# +# cd - cddl [*] +# ls - lsd +# cp - cpd +# ssh - dssh +# git - gitd +# +# These standard commands behave exactly the same as they always +# do, unless a Drush site specification such as @dev or @live:%files +# is used in one of the arguments. + +# Aliases for common Drush commands that work in a global context. +alias dr='drush' +alias ddd='drush drupal-directory' +alias dl='drush pm-download' +alias ev='drush php-eval' +alias sa='drush site-alias' +alias lsa='drush site-alias --local-only' +alias st='drush core-status' +alias use='drush site-set' + +# Aliases for Drush commands that work on the current drupal site +alias cc='drush cache-clear' +alias cr='drush cache-rebuild' +alias cca='drush cache-clear all' +alias dis='drush pm-disable' +alias en='drush pm-enable' +alias pmi='drush pm-info' +alias pml='drush pm-list' +alias rf='drush pm-refresh' +alias unin='drush pm-uninstall' +alias up='drush pm-update' +alias upc='drush pm-updatecode' +alias updb='drush updatedb' +alias q='drush sql-query' + +# Overrides for standard shell commands. Uncomment to enable. Alias +# cd='cdd' if you want to be able to use cd @remote to ssh to a +# remote site. + +# alias cd='cddl' +# alias ls='lsd' +# alias cp='cpd' +# alias ssh='dssh' +# alias git='gitd' + +# We extend the cd command to allow convenient +# shorthand notations, such as: +# cd @site1 +# cd %modules +# cd %devel +# cd @site2:%files +# You must use 'cddl' instead of 'cd' if you are not using +# the optional 'cd' alias from above. +# This is the "local-only" version of the function; +# see the cdd function, below, for an expanded implementation +# that will ssh to the remote server when a remote site +# specification is used. +function cddl() { + fastcddl "$1" + use @self +} + +# Use this function instead of 'cddl' if you have a very large number +# of alias files, and the 'cddl' function is getting too slow as a result. +# This function does not automatically set your prompt to the site that +# you 'cd' to, as 'cddl' does. +function fastcddl() { + s="$1" + if [ -z "$s" ] + then + builtin cd + elif [ "${s:0:1}" == "@" ] || [ "${s:0:1}" == "%" ] + then + d="$(drush drupal-directory $1 --local-only 2>/dev/null)" + if [ $? == 0 ] + then + echo "cd $d"; + builtin cd "$d"; + else + t="$(drush site-alias $1 >/dev/null 2>/dev/null)" + if [ $? == 0 ] + then + echo "Cannot cd to remote site $s" + else + echo "Cannot cd to $s" + fi + fi + else + builtin cd "$s"; + fi +} + +# Works just like the `cddl` shell alias above, with one additional +# feature: `cdd @remote-site` works like `ssh @remote-site`, +# whereas cd above will fail unless the site alias is local. If +# you prefer this behavior, you can add `alias cd='cdd'` to your .bashrc +function cdd() { + s="$1" + if [ -z "$s" ] + then + builtin cd + elif [ "${s:0:1}" == "@" ] || [ "${s:0:1}" == "%" ] + then + d="$(drush drupal-directory $s 2>/dev/null)" + rh="$(drush sa ${s%%:*} --fields=remote-host --format=list)" + if [ -z "$rh" ] + then + echo "cd $d" + builtin cd "$d" + else + if [ -n "$d" ] + then + c="cd \"$d\" \; bash" + drush -s ${s%%:*} ssh --tty + drush ${s%%:*} ssh --tty + else + drush ssh ${s%%:*} + fi + fi + else + builtin cd "$s" + fi +} + +# Allow `git @site gitcommand` as a shortcut for `cd @site; git gitcommand`. +# Also works on remote sites, though. +function gitd() { + s="$1" + if [ -n "$s" ] && [ ${s:0:1} == "@" ] || [ ${s:0:1} == "%" ] + then + d="$(drush drupal-directory $s 2>/dev/null)" + rh="$(drush sa ${s%%:*} --fields=remote-host --format=list)" + if [ -n "$rh" ] + then + drush ${s%%:*} ssh "cd '$d' ; git ${@:2}" + else + echo cd "$d" \; git "${@:2}" + ( + cd "$d" + "git" "${@:2}" + ) + fi + else + "git" "$@" + fi +} + +# Get a directory listing on @site or @site:%files, etc, for local or remote sites. +function lsd() { + p=() + r= + for a in "$@" ; do + if [ ${a:0:1} == "@" ] || [ ${a:0:1} == "%" ] + then + p[${#p[@]}]="$(drush drupal-directory $a 2>/dev/null)" + if [ ${a:0:1} == "@" ] + then + rh="$(drush sa ${a%:*} --fields=remote-host --format=list)" + if [ -n "$rh" ] + then + r=${a%:*} + fi + fi + elif [ -n "$a" ] + then + p[${#p[@]}]="$a" + fi + done + if [ -n "$r" ] + then + drush $r ssh 'ls "${p[@]}"' + else + "ls" "${p[@]}" + fi +} + +# Copy files from or to @site or @site:%files, etc; local sites only. +function cpd() { + p=() + for a in "$@" ; do + if [ ${a:0:1} == "@" ] || [ ${a:0:1} == "%" ] + then + p[${#p[@]}]="$(drush drupal-directory $a --local-only 2>/dev/null)" + elif [ -n "$a" ] + then + p[${#p[@]}]="$a" + fi + done + "cp" "${p[@]}" +} + +# This alias allows `dssh @site` to work like `drush @site ssh`. +# Ssh commands, such as `dssh @site ls /tmp`, are also supported. +function dssh() { + d="$1" + if [ ${d:0:1} == "@" ] + then + drush "$d" ssh "${@:2}" + else + "ssh" "$@" + fi +} + +# Drush checks the current PHP version to ensure compatibility, and fails with +# an error if less than the supported minimum (currently 5.4.5). If you would +# like to try to run Drush on a lower version of PHP, you can un-comment the +# line below to skip this check. Note, however, that this is un-supported. + +# DRUSH_NO_MIN_PHP=TRUE diff --git a/vendor/drush/drush/examples/example.drush.ini b/vendor/drush/drush/examples/example.drush.ini new file mode 100644 index 0000000000..96652d7d9a --- /dev/null +++ b/vendor/drush/drush/examples/example.drush.ini @@ -0,0 +1,85 @@ +; +; Example of a drush php settings override file. +; +; IMPORTANT: This file has no effect when using +; drush.phar. It is only effective when used +; a Composer built Drush is used. When a drush.phar +; hands off execution to a Composer built Drush, +; this file is effective. +; +; IMPORTANT: Before following the instructions in +; this file, first check to see that the cli version +; of php is installed on your system. (e.g. On +; debian systems, `sudo apt-get install php5-cli`.) +; +; Use this file in instances when your system is +; -not- configured to use separate php.ini files for +; webserver and cli use. You can determine which +; php.ini file drush is using by running "drush status". +; If the php.ini file shown is your webserver ini +; file, then rename this file, example.drush.ini, +; to drush.ini and copy it to one of the following +; locations: +; +; 1. Drush installation folder +; 2. User's .drush folder (i.e. ~/.drush/drush.ini) +; 3. System wide configuration folder (i.e. /etc/drush/drush.ini) +; +; If the environment variable DRUSH_INI is defined, +; then the file it specified will be used as drush.ini. +; +; export DRUSH_INI='/path/to/drush.ini' +; +; When in use, the variables defined in this file +; will override the setting values that appear in +; your php.ini file. See the examples below for +; some values that may need to be set in order for +; drush to work. +; +; NOTE: There is a certain amount of overhead +; required for each override, so drush.ini should +; only be used for a relatively small number +; of variables. Comment out any variable that +; has the same value as the webserver php.ini +; to keep the size of the override list small. +; +; To fully specify the value of all php.ini variables, +; copy your webserver php.ini file to one of the +; locations mentioned above (e.g. /etc/drush/php.ini) +; and edit it to suit. Alternately, you may use +; the environment variable PHP_INI to point at +; the file that Drush should use. +; +; export PHP_INI='/path/to/php.ini' +; +; The options listed below are particularly relevant +; to drush. +; + +; +; drush needs as much memory as Drupal in order +; to run; make the memory limit setting match +; what you have in your webserver's php.ini. +; +memory_limit = 128M + +; +; Show all errors and direct them to stderr +; when running drush. +; +error_reporting = E_ALL | E_NOTICE | E_STRICT +display_errors = stderr + +; +; If your php.ini for your webserver is too +; restrictive, you can re-enable functionality +; for drush by adjusting values in this file. +; +; Here are some examples of settings that are +; sometimes set to restrictive values in a +; webserver's php.ini: +; +;safe_mode = +;open_basedir = +;disable_functions = +;disable_classes = diff --git a/vendor/drush/drush/examples/example.drushrc.php b/vendor/drush/drush/examples/example.drushrc.php new file mode 100644 index 0000000000..7384e196f6 --- /dev/null +++ b/vendor/drush/drush/examples/example.drushrc.php @@ -0,0 +1,311 @@ + TRUE); + +// Prevent drush ssh command from adding a cd to Drupal root before provided command. +# $command_specific['ssh'] = array('cd' => FALSE); + +// Additional folders to search for scripts. +// Separate by : (Unix-based systems) or ; (Windows). +# $command_specific['script']['script-path'] = 'sites/all/scripts:profiles/myprofile/scripts'; + +// Always show release notes when running pm-update or pm-updatecode. +# $command_specific['pm-update'] = array('notes' => TRUE); +# $command_specific['pm-updatecode'] = array('notes' => TRUE); + +// Set a predetermined username and password when using site-install. +# $command_specific['site-install'] = array('account-name' => 'alice', 'account-pass' => 'secret'); + +// Use Drupal version specific CLI history instead of per site. +# $command_specific['core-cli'] = array('version-history' => TRUE); diff --git a/vendor/drush/drush/examples/example.make b/vendor/drush/drush/examples/example.make new file mode 100644 index 0000000000..f18bb06b83 --- /dev/null +++ b/vendor/drush/drush/examples/example.make @@ -0,0 +1,103 @@ +; Example makefile +; ---------------- +; This is an example makefile to introduce new users of drush_make to the +; syntax and options available to drush_make. For a full description of all +; options available, see README.txt. + +; This make file is a working makefile - try it! Any line starting with a `;` +; is a comment. + +; Core version +; ------------ +; Each makefile should begin by declaring the core version of Drupal that all +; projects should be compatible with. + +core = 7.x + +; API version +; ------------ +; Every makefile needs to declare it's Drush Make API version. This version of +; drush make uses API version `2`. + +api = 2 + +; Core project +; ------------ +; In order for your makefile to generate a full Drupal site, you must include +; a core project. This is usually Drupal core, but you can also specify +; alternative core projects like Pressflow. Note that makefiles included with +; install profiles *should not* include a core project. + +; Use Pressflow instead of Drupal core: +; projects[pressflow][type] = "core" +; projects[pressflow][download][type] = "file" +; projects[pressflow][download][url] = "http://launchpad.net/pressflow/6.x/6.15.73/+download/pressflow-6.15.73.tar.gz" + +; Git clone of Drupal 7.x. Requires the `core` property to be set to 7.x. +; projects[drupal][type] = "core" +; projects[drupal][download][type] = git +; projects[drupal][download][url] = https://git.drupalcode.org/project/drupal.git + +projects[] = drupal + +; Projects +; -------- +; Each project that you would like to include in the makefile should be +; declared under the `projects` key. The simplest declaration of a project +; looks like this: + +; To include the most recent views module: + +projects[] = views + +; This will, by default, retrieve the latest recommended version of the project +; using its update XML feed on Drupal.org. If any of those defaults are not +; desirable for a project, you will want to use the keyed syntax combined with +; some options. + +; If you want to retrieve a specific version of a project: + +; projects[views] = 2.16 + +; Or an alternative, extended syntax: + +projects[ctools][version] = 1.3 + +; Check out the latest version of a project from Git. Note that when using a +; repository as your project source, you must explicitly declare the project +; type so that drush_make knows where to put your project. + +projects[data][type] = module +projects[data][download][type] = git +projects[data][download][url] = https://git.drupalcode.org/project/views.git +projects[data][download][revision] = DRUPAL-6--3 + +; For projects on drupal.org, some shorthand is available. If any +; download parameters are specified, but not type, the default is git. +projects[cck_signup][download][revision] = "2fe932c" +; It is recommended to also specify the corresponding branch so that +; the .info file rewriting can obtain a version string that works with +; the core update module +projects[cck_signup][download][branch] = "7.x-1.x" + +; Clone a project from github. + +projects[tao][type] = theme +projects[tao][download][type] = git +projects[tao][download][url] = git://github.com/developmentseed/tao.git + +; If you want to install a module into a sub-directory, you can use the +; `subdir` attribute. + +projects[admin_menu][subdir] = custom + +; To apply a patch to a project, use the `patch` attribute and pass in the URL +; of the patch. + +projects[admin_menu][patch][687750] = "http://drupal.org/files/issues/admin_menu.long_.31.patch" + +; If all projects or libraries share common attributes, the `defaults` +; array can be used to specify these globally, rather than +; per-project. + +defaults[projects][subdir] = "contrib" diff --git a/vendor/drush/drush/examples/example.make.yml b/vendor/drush/drush/examples/example.make.yml new file mode 100644 index 0000000000..1abd910995 --- /dev/null +++ b/vendor/drush/drush/examples/example.make.yml @@ -0,0 +1,121 @@ +# Example makefile +# ---------------- +# This is an example makefile to introduce new users of drush make to the +# syntax and options available to drush make. + +# This make file is a working makefile - try it! Any line starting with a `#` +# is a comment. + +# Core version +# ------------ +# Each makefile should begin by declaring the core version of Drupal that all +# projects should be compatible with. + +core: "7.x" + +# API version +# ------------ +# Every makefile needs to declare it's Drush Make API version. This version of +# drush make uses API version `2`. + +api: 2 + +# Core project +# ------------ +# In order for your makefile to generate a full Drupal site, you must include +# a core project. This is usually Drupal core, but you can also specify +# alternative core projects like Pressflow. Note that makefiles included with +# install profiles *should not* include a core project. + +# Use Pressflow instead of Drupal core: +# projects: +# pressflow: +# type: "core" +# download: +# type: "file" +# url: "http://launchpad.net/pressflow/6.x/6.15.73/+download/pressflow-6.15.73.tar.gz" +# +# Git clone of Drupal 7.x. Requires the `core` property to be set to 7.x. +# projects +# drupal: +# type: "core" +# download: +# url: "https://git.drupalcode.org/project/drupal.git" + +projects: + drupal: + version: ~ + + # Projects + # -------- + # Each project that you would like to include in the makefile should be + # declared under the `projects` key. The simplest declaration of a project + # looks like this: + + # To include the most recent views module: + + views: + version: ~ + + # This will, by default, retrieve the latest recommended version of the + # project using its update XML feed on Drupal.org. If any of those defaults + # are not desirable for a project, you will want to use the keyed syntax + # combined with some options. + + # If you want to retrieve a specific version of a project: + + # projects: + # views: "2.16" + + # Or an alternative, extended syntax: + + ctools: + version: "1.3" + + # Check out the latest version of a project from Git. Note that when using a + # repository as your project source, you must explicitly declare the project + # type so that drush_make knows where to put your project. + + data: + type: "module" + download: + type: "git" # Note, 'git' is the default, no need to specify. + url: "https://git.drupalcode.org/project/views.git" + revision: "7.x-3.x" + + # For projects on drupal.org, some shorthand is available. If any + # download parameters are specified, but not type, the default is git. + cck_signup: + download: + revision: "2fe932c" + # It is recommended to also specify the corresponding branch so that + # the .info file rewriting can obtain a version string that works with + # the core update module + branch: "7.x-1.x" + + # Clone a project from github. + + tao: + type: theme + download: + url: "git://github.com/developmentseed/tao.git" + + # If you want to install a module into a sub-directory, you can use the + # `subdir` attribute. + + admin_menu: + subdir: custom + + # To apply patches to a project, use the `patch` attribute and pass in the URL + # of the patch, one per line prefaced with `- `. + + patch: + - "http://drupal.org/files/issues/admin_menu.long_.31.patch" + +# If all projects or libraries share common attributes, the `defaults` +# array can be used to specify these globally, rather than +# per-project. + +defaults: + projects: + subdir: "contrib" diff --git a/vendor/drush/drush/examples/example.prompt.sh b/vendor/drush/drush/examples/example.prompt.sh new file mode 100644 index 0000000000..fd8fee14dc --- /dev/null +++ b/vendor/drush/drush/examples/example.prompt.sh @@ -0,0 +1,74 @@ +# -*- mode: shell-script; mode: flyspell-prog; ispell-local-dictionary: "american" -*- +# +# Example PS1 prompt. +# +# Use `drush init` to copy this to ~/.drush/drush.prompt.sh, and source it in ~/.bashrc +# +# Features: +# +# Displays Git repository and Drush alias status in your prompt. +if [ -n "$(type -t __git_ps1)" ] && [ "$(type -t __git_ps1)" = function ] && [ "$(type -t __drush_ps1)" ] && [ "$(type -t __drush_ps1)" = function ]; then + + # This line enables color hints in your Drush prompt. Modify the below + # __drush_ps1_colorize_alias() to customize your color theme. + DRUSH_PS1_SHOWCOLORHINTS=true + + # Git offers various prompt customization options as well as seen in + # https://github.com/git/git/blob/master/contrib/completion/git-prompt.sh. + # Adjust the following lines to enable the corresponding features: + # + GIT_PS1_SHOWDIRTYSTATE=true + GIT_PS1_SHOWUPSTREAM=auto + # GIT_PS1_SHOWSTASHSTATE=true + # GIT_PS1_SHOWUNTRACKEDFILES=true + GIT_PS1_SHOWCOLORHINTS=true + + # The following line sets your bash prompt according to this example: + # + # username@hostname ~/working-directory (git-branch)[@drush-alias] $ + # + # See http://ss64.com/bash/syntax-prompt.html for customization options. + export PROMPT_COMMAND='__git_ps1 "\u@\h \w" "$(__drush_ps1 "[%s]") \\\$ "' + + # PROMPT_COMMAND is used in the example above rather than PS1 because neither + # Git nor Drush color hints are compatible with PS1. If you don't want color + # hints, however, and prefer to use PS1, you can still do so by commenting out + # the PROMPT_COMMAND line above and uncommenting the PS1 line below: + # + # export PS1='\u@\h \w$(__git_ps1 " (%s)")$(__drush_ps1 "[%s]")\$ ' + + __drush_ps1_colorize_alias() { + if [[ -n ${ZSH_VERSION-} ]]; then + local COLOR_BLUE='%F{blue}' + local COLOR_CYAN='%F{cyan}' + local COLOR_GREEN='%F{green}' + local COLOR_MAGENTA='%F{magenta}' + local COLOR_RED='%F{red}' + local COLOR_WHITE='%F{white}' + local COLOR_YELLOW='%F{yellow}' + local COLOR_NONE='%f' + else + # Using \[ and \] around colors is necessary to prevent issues with + # command line editing/browsing/completion. + local COLOR_BLUE='\[\e[94m\]' + local COLOR_CYAN='\[\e[36m\]' + local COLOR_GREEN='\[\e[32m\]' + local COLOR_MAGENTA='\[\e[35m\]' + local COLOR_RED='\[\e[91m\]' + local COLOR_WHITE='\[\e[37m\]' + local COLOR_YELLOW='\[\e[93m\]' + local COLOR_NONE='\[\e[0m\]' + fi + + # Customize your color theme below. + case "$__DRUPAL_SITE" in + *.live|*.prod) local ENV_COLOR="$COLOR_RED" ;; + *.stage|*.test) local ENV_COLOR="$COLOR_YELLOW" ;; + *.local) local ENV_COLOR="$COLOR_GREEN" ;; + *) local ENV_COLOR="$COLOR_BLUE" ;; + esac + + __DRUPAL_SITE="${ENV_COLOR}${__DRUPAL_SITE}${COLOR_NONE}" + } + +fi diff --git a/vendor/drush/drush/examples/git-bisect.example.sh b/vendor/drush/drush/examples/git-bisect.example.sh new file mode 100755 index 0000000000..e5347ffdcb --- /dev/null +++ b/vendor/drush/drush/examples/git-bisect.example.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env sh + +# +# Git bisect is a helpful way to discover which commit an error +# occurred in. This example file gives simple instructions for +# using git bisect with Drush to quickly find erroneous commits +# in Drush commands or Drupal modules, presuming that you can +# trigger the error condition via Drush (e.g. using `drush php-eval`). +# +# Follow these simple steps: +# +# $ git bisect start +# $ git bisect bad # Tell git that the current commit does not work +# $ git bisect good bcadd5a # Tell drush that the commithash 12345 worked fine +# $ git bisect run mytestscript.sh +# +# 'git bisect run' will continue to call 'git bisect good' and 'git bisect bad', +# based on whether the script's exit code was 0 or 1, respectively. +# +# Replace 'mytestscript.sh' in the example above with a custom script that you +# write yourself. Use the example script at the end of this document as a +# guide. Replace the example command with one that calls the Drush command +# that you would like to test, and replace the 'grep' string with a value +# that appears when the error exists in the commit, but does not appear when +# commit is okay. +# +# If you are using Drush to test Drupal or an external Drush module, use: +# +# $ git bisect run drush mycommand --strict=2 +# +# This presumes that there is one or more '[warning]' or '[error]' +# messages emitted when there is a problem, and no warnings or errors +# when the commit is okay. Omit '--strict=2' to ignore warnings, and +# signal failure only when 'error' messages are emitted. +# +# If you need to test for an error condition explicitly, to find errors +# that do not return any warning or error log messages on their own, you +# can use the Drush php-eval command to force an error when `myfunction()` +# returns FALSE. Replace 'myfunction()' with the name of an appropriate +# function in your module that can be used to detect the error condition +# you are looking for. +# +# $ git bisect run drush ev 'if(!myfunction()) { return drush_set_error("ERR"); }' +# +drush mycommand --myoption 2>&1 | grep -q 'string that indicates there was a problem' +if [ $? == 0 ] ; then + exit 1 +else + exit 0 +fi diff --git a/vendor/drush/drush/examples/helloworld.script b/vendor/drush/drush/examples/helloworld.script new file mode 100755 index 0000000000..07d079c98b --- /dev/null +++ b/vendor/drush/drush/examples/helloworld.script @@ -0,0 +1,50 @@ +#!/usr/bin/env drush + +// +// This example demonstrates how to write a drush +// "shebang" script. These scripts start with the +// line "#!/usr/bin/env drush" or "#!/full/path/to/drush". +// +// See `drush topic docs-scripts` for more information. +// +drush_print("Hello world!"); +drush_print(); +drush_print("The arguments to this command were:"); + +// +// If called with --everything, use drush_get_arguments +// to print the commandline arguments. Note that this +// call will include 'php-script' (the drush command) +// and the path to this script. +// +if (drush_get_option('everything')) { + drush_print(" " . implode("\n ", drush_get_arguments())); +} +// +// If --everything is not included, then use +// drush_shift to pull off the arguments one at +// a time. drush_shift only returns the user +// commandline arguments, and does not include +// the drush command or the path to this script. +// +else { + while ($arg = drush_shift()) { + drush_print(' ' . $arg); + } +} + +drush_print(); + +// +// We can check which site was bootstrapped via +// the '@self' alias, which is defined only if +// there is a bootstrapped site. +// +$self_record = drush_sitealias_get_record('@self'); +if (empty($self_record)) { + drush_print('No bootstrapped site.'); +} +else { + drush_print('The following site is bootstrapped:'); + _drush_sitealias_print_record($self_record); +} diff --git a/vendor/drush/drush/examples/pm_update.drush.inc b/vendor/drush/drush/examples/pm_update.drush.inc new file mode 100644 index 0000000000..3ba610e195 --- /dev/null +++ b/vendor/drush/drush/examples/pm_update.drush.inc @@ -0,0 +1,19 @@ + "Makes a delicious sandwich.", + 'arguments' => array( + 'filling' => 'The type of the sandwich (turkey, cheese, etc.). Defaults to ascii.', + ), + 'options' => array( + 'spreads' => array( + 'description' => 'Comma delimited list of spreads.', + 'example-value' => 'mayonnaise,mustard', + ), + ), + 'examples' => array( + 'drush mmas turkey --spreads=ketchup,mustard' => 'Make a terrible-tasting sandwich that is lacking in pickles.', + ), + 'aliases' => array('mmas'), + // No bootstrap at all. + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + ); + + // The 'sandwiches-served' command. Informs how many 'mmas' commands + // completed. + $items['sandwiches-served'] = array( + 'description' => "Report how many sandwiches we have made.", + 'examples' => array( + 'drush sandwiches-served' => 'Show how many sandwiches we have served.', + ), + 'aliases' => array('sws'), + // Example output engine data: command returns a single keyed + // data item (e.g. array("served" => 1)) that can either be + // printed with a label (e.g. "served: 1"), or output raw with + // --pipe (e.g. "1"). + 'engines' => array( + 'outputformat' => array( + 'default' => 'key-value', + 'pipe-format' => 'string', + 'label' => 'Sandwiches Served', + 'require-engine-capability' => array('format-single'), + ), + ), + // No bootstrap at all. + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + ); + + // The 'spreads-status' command. Prints a table about available spreads. + $items['spreads-status'] = array( + 'description' => "Show a table of information about available spreads.", + 'examples' => array( + 'drush spreads-status' => 'Show a table of spreads.', + ), + 'aliases' => array('sps'), + // Example output engine data: command returns a deep array + // that can either be printed in table format or as a json array. + 'engines' => array( + 'outputformat' => array( + 'default' => 'table', + 'pipe-format' => 'json', + // Commands that return deep arrays will usually use + // machine-ids for the column data. A 'field-labels' + // item maps from the machine-id to a human-readable label. + 'field-labels' => array( + 'name' => 'Name', + 'description' => 'Description', + 'available' => 'Num', + 'taste' => 'Taste', + ), + // In table format, the 'column-widths' item is consulted + // to determine the default weights for any named column. + 'column-widths' => array( + 'name' => 10, + 'available' => 3, + ), + 'require-engine-capability' => array('format-table'), + ), + ), + // No bootstrap at all. + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + ); + + // Commandfiles may also add topics. These will appear in + // the list of topics when `drush topic` is executed. + // To view this topic, run `drush --include=/full/path/to/examples topic` + $items['sandwich-exposition'] = array( + 'description' => 'Ruminations on the true meaning and philosophy of sandwiches.', + 'hidden' => TRUE, + 'topic' => TRUE, + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'callback' => 'drush_print_file', + 'callback arguments' => array(dirname(__FILE__) . '/sandwich-topic.txt'), + ); + + return $items; +} + +/** + * Implements hook_drush_help(). + * + * This function is called whenever a drush user calls + * 'drush help '. This hook is optional. If a command + * does not implement this hook, the command's description is used instead. + * + * This hook is also used to look up help metadata, such as help + * category title and summary. See the comments below for a description. + */ +function sandwich_drush_help($section) { + switch ($section) { + case 'drush:make-me-a-sandwich': + return dt("This command will make you a delicious sandwich, just how you like it."); + + // The 'title' meta item is used to name a group of + // commands in `drush help`. If a title is not defined, + // the default is "All commands in ___", with the + // specific name of the commandfile (e.g. sandwich). + // Command files with less than four commands will + // be placed in the "Other commands" section, _unless_ + // they define a title. It is therefore preferable + // to not define a title unless the file defines a lot + // of commands. + case 'meta:sandwich:title': + return dt("Sandwich commands"); + + // The 'summary' meta item is displayed in `drush help --filter`, + // and is used to give a general idea what the commands in this + // command file do, and what they have in common. + case 'meta:sandwich:summary': + return dt("Automates your sandwich-making business workflows."); + } +} + +/** + * Implements drush_hook_COMMAND_validate(). + * + * The validate command should exit with + * `return drush_set_error(...)` to stop execution of + * the command. In practice, calling drush_set_error + * OR returning FALSE is sufficient. See drush.api.php + * for more details. + */ +function drush_sandwich_make_me_a_sandwich_validate() { + if (drush_is_windows()) { + // $name = drush_get_username(); + // @todo Implement check for elevated process using w32api + // as sudo is not available for Windows + // @see http://php.net/manual/en/book.w32api.php + // @see http://social.msdn.microsoft.com/Forums/en/clr/thread/0957c58c-b30b-4972-a319-015df11b427d + } + else { + $name = posix_getpwuid(posix_geteuid()); + if ($name['name'] !== 'root') { + return drush_set_error('MAKE_IT_YOUSELF', dt('What? Make your own sandwich.')); + } + } +} + +/** + * Implements drush_hook_COMMAND(). + * + * The command callback is where the action takes place. + * + * The function name should be same as command name but with dashes turned to + * underscores and 'drush_commandfile_' prepended, where 'commandfile' is + * taken from the file 'commandfile.drush.inc', which in this case is + * 'sandwich'. Note also that a simplification step is also done in instances + * where the commandfile name is the same as the beginning of the command name, + * "drush_example_example_foo" is simplified to just "drush_example_foo". + * To also implement a hook that is called before your command, implement + * "drush_hook_pre_example_foo". For a list of all available hooks for a + * given command, run drush in --debug mode. + * + * If for some reason you do not want your hook function to be named + * after your command, you may define a 'callback' item in your command + * object that specifies the exact name of the function that should be + * called. + * + * In this function, all of Drupal's API is (usually) available, including + * any functions you have added in your own modules/themes. + * + * @see drush_invoke() + * @see drush.api.php + */ +function drush_sandwich_make_me_a_sandwich($filling = 'ascii') { + $str_spreads = ''; + // Read options with drush_get_option. Note that the options _must_ + // be documented in the $items structure for this command in the 'command' + // hook. See `drush topic docs-commands` for more information. + if ($spreads = drush_get_option('spreads')) { + $list = implode(' and ', explode(',', $spreads)); + $str_spreads = ' with just a dash of ' . $list; + } + $msg = dt('Okay. Enjoy this !filling sandwich!str_spreads.', + array('!filling' => $filling, '!str_spreads' => $str_spreads) + ); + drush_print("\n" . $msg . "\n"); + + if (drush_get_context('DRUSH_NOCOLOR')) { + $filename = dirname(__FILE__) . '/sandwich-nocolor.txt'; + } + else { + $filename = dirname(__FILE__) . '/sandwich.txt'; + } + drush_print(file_get_contents($filename)); + // Find out how many sandwiches have been served, and set + // the cached value to one greater. + $served = drush_sandwich_sandwiches_served(); + drush_cache_set(drush_get_cid('sandwiches-served'), $served + 1); +} + +/** + * Implements drush_hook_COMMAND(). + * + * Demonstrates how to return a simple value that is transformed by + * the selected formatter to display either with a label (using the + * key-value formatter) or as the raw value itself (using the string formatter). + */ +function drush_sandwich_sandwiches_served() { + $served = 0; + $served_object = drush_cache_get(drush_get_cid('sandwiches-served')); + if ($served_object) { + $served = $served_object->data; + } + // In the default format, key-value, this return value + // will print " Sandwiches Served : 1". In the default pipe + // format, only the array value ("1") is returned. + return $served; +} + +/** + * Implements drush_hook_COMMAND(). + * + * This ficticious command shows how a deep array can be constructed + * and used as a command return value that can be output by different + * output formatters. + */ +function drush_sandwich_spreads_status() { + return array( + 'ketchup' => array( + 'name' => 'Ketchup', + 'description' => 'Some say its a vegetable, but we know its a sweet spread.', + 'available' => '7', + 'taste' => 'sweet', + ), + 'mayonnaise' => array( + 'name' => 'Mayonnaise', + 'description' => 'A nice dairy-free spead.', + 'available' => '12', + 'taste' => 'creamy', + ), + 'mustard' => array( + 'name' => 'Mustard', + 'description' => 'Pardon me, but could you please pass that plastic yellow bottle?', + 'available' => '8', + 'taste' => 'tangy', + ), + 'pickles' => array( + 'name' => 'Pickles', + 'description' => 'A necessary part of any sandwich that does not taste terrible.', + 'available' => '63', + 'taste' => 'tasty', + ), + ); +} + +/** + * Command argument complete callback. + * + * Provides argument values for shell completion. + * + * @return array + * Array of popular fillings. + */ +function sandwich_make_me_a_sandwich_complete() { + return array('values' => array('turkey', 'cheese', 'jelly', 'butter')); +} diff --git a/vendor/drush/drush/examples/sandwich.txt b/vendor/drush/drush/examples/sandwich.txt new file mode 100644 index 0000000000..6c4c3097bf --- /dev/null +++ b/vendor/drush/drush/examples/sandwich.txt @@ -0,0 +1,24 @@ + . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + . . . . . . . . . . .:8 ;t;;t;;;;:..:;%SX888@X%t;.. . . .  + . . .. . . . . . .%t%;%@%%%%%%%%%%X@8888XS%t;...:;ttt%X. . + . . . . . . . . .X:8%X%%%XS%%%%%%%XS%%%%%@%%%%X%%%@%S88 .  + . . . . . . . . X@ @%%%X8X%%%88%%%8X%%%%%%%%%XXt@8@88@. . + . . . . . . . .t@tS;%%8XSX%@XSX%@XSS%8@@88X@888X8SS S;S.  + . . . . . . .@%XS%%%%%S8@X%@8%XXSSXX%S@SSSX888.;@ 888@ . .  + . . . . . :.8:S%%%XS8X@@X%S@SSSS8SXSXXX%X88X:;@8@:S  88S. . + . . . .8%S8%%%%%%8@SSSXXXSXSXSXSXSSS8S888 :@%:%XX:%8%:X:  + . . .:8 %%%%@%%8@S%%XXSXSSSS8S@X%XSXX88 ;@X;SX88X8;%X88t.  + . . 88S%S%%%%8XSSXSX@@S@%XS8@SS%@S%888 88@S:8. .;.@%X:@8;.  + . .  88.8888888@XX 888888%X%@XX 88SS8@@;S@8.%;8@S%%:8  . + . .  S%:8 @SSSS8 @@8@8 8 88888888@%S:8:S8 @..%S SXX8888;. . + . . %:8S8888@88SXS S S::X@.8.8 X%S%8X:X88..% @@.S.%% .;. .  + . .XX8@8;;%%t;;;;:@X@888888@888888.88S;8:8  ... . . . + . . 8.;;@8@8:%%%%%t.8@%ttX@8@@@S8%8 X8S;X:@; :... . . . . . + . tS:8@;88.;:8888X8S:.tX88888X88  S8tStS88 :.. . . . . . .  + .:X;;:t%;tt%888S@8XS888@8.:tt@;88.tXXX8:::... . . . . . . . + .:X8St:8SXS XS8@X 8.8%888%X8@@X88tXS8t; . . . . . . . . .  + . :8888.88888888X@@X @ X X%S%;88;8t .. . . . . . . . . + ... ..: . .@@888%St @ @ 8SS 8:; . . . . . . . . . . . + . . . ..::. ..:;;::::. ... . . . . . . . . . . . . + .. . . . . .. . . . . .. . . . . . . . . . . . . .  diff --git a/vendor/drush/drush/examples/sync_enable.drush.inc b/vendor/drush/drush/examples/sync_enable.drush.inc new file mode 100644 index 0000000000..6e3fabe570 --- /dev/null +++ b/vendor/drush/drush/examples/sync_enable.drush.inc @@ -0,0 +1,125 @@ + '/srv/www/drupal', + * 'uri' => 'site.com', + * 'target-command-specific' => array( + * 'sql-sync' => array( + * 'enable' => array('devel', 'hacked'), + * 'disable' => array('securepages'), + * 'permission' => array( + * 'authenticated user' => array( + * 'add' => array('access devel information', 'access environment indicator'), + * 'remove' => 'change own password', + * ), + * 'anonymous user' => array( + * 'add' => 'access environment indicator', + * ), + * ), + * ), + * ), + * ); + * @endcode + * + * To use this feature, copy the 'target-command-specific' + * item from the example alias above, place it in your development + * site aliases, and customize the development module list + * to suit. You must also copy the sync_enable.drush.inc + * file to a location where Drush will find it, such as + * $HOME/.drush. See `drush topic docs-commands` for more + * information. + * + * To set variables on a development site: + * + * Instead of calling variable_set and variable_delete in a post-sync + * hook, consider adding $conf variables to settings.php. + * + * For example: + * + * $conf['error_level'] = 2; + * error_reporting(E_ALL); + * ini_set('display_errors', TRUE); + * ini_set('display_startup_errors', TRUE); + * $conf['preprocess_css'] = 0; + * $conf['cache'] = 0; + * $conf['googleanalytics_account'] = ''; + */ + +/** + * Implements hook_drush_help_alter(). + * + * When a hook extends a command with additional options, it must + * implement help alter and declare the option(s). Doing so will add + * the option to the help text for the modified command, and will also + * allow the new option to be specified on the command line. Without + * this, Drush will fail with an error when a user attempts to use + * the option. + */ +function sync_enable_drush_help_alter(&$command) { + if ($command['command'] == 'sql-sync') { + $command['options']['updb'] = "Apply database updates on the target database after the sync operation has completed."; + $command['options']['enable'] = "Enable the specified modules in the target database after the sync operation has completed."; + $command['options']['disable'] = "Disable the specified modules in the target database after the sync operation has completed."; + $command['options']['permission'] = "Add or remove permissions from a role in the target database after the sync operation has completed. The value of this option must be an array, so it may only be specified in a site alias record or drush configuration file. See `drush topic docs-example-sync-extension`."; + } +} + +/** + * Implements drush_hook_post_COMMAND(). + * + * The post hook is only called if the sql-sync operation completes + * without an error. When called, we check to see if the user specified + * any modules to enable/disable. If so, we will call pm-enable/pm-disable on + * each module. + */ +function drush_sync_enable_post_sql_sync($source = NULL, $destination = NULL) { + $updb = drush_get_option('updb', FALSE); + if ($updb) { + drush_log('Run database updates', 'ok'); + drush_invoke_process($destination, 'updatedb', array(), array('yes' => TRUE)); + } + $modules_to_enable = drush_get_option_list('enable'); + if (!empty($modules_to_enable)) { + drush_log(dt("Enable !modules post-sql-sync", array('!modules' => implode(',', $modules_to_enable))), 'ok'); + drush_invoke_process($destination, 'pm-enable', $modules_to_enable, array('yes' => TRUE)); + } + $modules_to_disable = drush_get_option_list('disable'); + if (!empty($modules_to_disable)) { + drush_log(dt("Disable !modules post-sql-sync", array('!modules' => implode(',', $modules_to_disable))), 'ok'); + drush_invoke_process($destination, 'pm-disable', $modules_to_disable, array('yes' => TRUE)); + } + $permissions_table = drush_get_option('permission'); + if (!empty($permissions_table)) { + foreach ($permissions_table as $role_name => $actions) { + if (array_key_exists('add', $actions)) { + $permissions_to_add = is_array($actions['add']) ? $actions['add'] : explode(', ', $actions['add']); + foreach ($permissions_to_add as $permission) { + $values = drush_invoke_process($destination, 'role-add-perm', array($role_name, $permission), array(), array('integrate' => TRUE)); + } + } + if (array_key_exists('remove', $actions)) { + $permissions_to_remove = is_array($actions['remove']) ? $actions['remove'] : explode(', ', $actions['remove']); + foreach ($permissions_to_remove as $permission) { + $values = drush_invoke_process($destination, 'role-remove-perm', array($role_name, $permission), array(), array('integrate' => TRUE)); + } + } + } + } +} diff --git a/vendor/drush/drush/examples/sync_via_http.drush.inc b/vendor/drush/drush/examples/sync_via_http.drush.inc new file mode 100644 index 0000000000..cf44d2132d --- /dev/null +++ b/vendor/drush/drush/examples/sync_via_http.drush.inc @@ -0,0 +1,126 @@ + '/srv/www/drupal', + * 'uri' => 'staging.site.com', + * 'source-command-specific' => array( + * 'sql-sync' => array( + * 'http-sync' => 'https://staging.site.com/protected-directory/site-database-dump.sql', + * 'http-sync-user' => 'wwwadmin', + * 'http-sync-password' => 'secretsecret', + * ), + * ), + * ); + * @endcode + * + * To use this feature, copy the 'source-command-specific' + * item from the example alias above, place it in your staging + * site aliases, and custom the access credentials as + * necessary. You must also copy the sync_via_http.drush.inc + * file to a location where Drush will find it, such as + * $HOME/.drush. See `drush topic docs-commands` for more + * information. + * + * IMPORTANT NOTE: This example does not cause the sql dump + * to be performed; it is presumed that the dump file already + * exists at the provided URL. For a full solution, a web page + * that initiated an sql-dump (or perhaps a local sql-sync followed + * by an sql-sanitize and then an sql-dump) would be necessary. + */ + +/** + * Implements hook_drush_help_alter(). + * + * When a hook extends a command with additional options, it must + * implement help alter and declare the option(s). Doing so will add + * the option to the help text for the modified command, and will also + * allow the new option to be specified on the command line. Without + * this, Drush will fail with an error when a user attempts to use + * the option. + */ +function sync_via_http_drush_help_alter(&$command) { + if ($command['command'] == 'sql-sync') { + $command['options']['http-sync'] = "Copy the database via http instead of rsync. Value is the url that the existing database dump can be found at."; + $command['sub-options']['http-sync']['http-sync-user'] = "Username for the protected directory containing the sql dump."; + $command['sub-options']['http-sync']['http-sync-password'] = "Password for the same directory."; + } +} + +/** + * Implements drush_hook_pre_COMMAND(). + * + * During the pre hook, determine if the http-sync option has been + * specified. If it has been, then disable the normal ssh + rsync + * dump-and-transfer that sql-sync usually does, and transfer the + * database dump via an http download. + */ +function drush_sync_via_http_pre_sql_sync($source = NULL, $destination = NULL) { + $sql_dump_download_url = drush_get_option('http-sync'); + if (!empty($sql_dump_download_url)) { + $user = drush_get_option('http-sync-user', FALSE); + $password = drush_get_option('http-sync-password', FALSE); + $source_dump_file = _drush_sync_via_http_download_file($sql_dump_download_url, $user, $password); + if ($source_dump_file === FALSE) { + return drush_set_error('DRUSH_CANNOT_DOWNLOAD', dt("The URL !url could not be downloaded.", array('!url' => $sql_dump_download_url))); + } + drush_set_option('target-dump', $source_dump_file); + drush_set_option('no-dump', TRUE); + drush_set_option('no-sync', TRUE); + } +} + +/** + * Downloads a files. + * + * Optionaly uses user authentication, using either wget or curl, as available. + */ +function _drush_sync_via_http_download_file($url, $user = FALSE, $password = FALSE, $destination = FALSE, $overwrite = TRUE) { + static $use_wget; + if ($use_wget === NULL) { + $use_wget = drush_shell_exec('which wget'); + } + + $destination_tmp = drush_tempnam('download_file'); + if ($use_wget) { + if ($user && $password) { + drush_shell_exec("wget -q --timeout=30 --user=%s --password=%s -O %s %s", $user, $password, $destination_tmp, $url); + } + else { + drush_shell_exec("wget -q --timeout=30 -O %s %s", $destination_tmp, $url); + } + } + else { + if ($user && $password) { + drush_shell_exec("curl -s -L --connect-timeout 30 --user %s:%s -o %s %s", $user, $password, $destination_tmp, $url); + } + else { + drush_shell_exec("curl -s -L --connect-timeout 30 -o %s %s", $destination_tmp, $url); + } + } + if (!drush_get_context('DRUSH_SIMULATE')) { + if (!drush_file_not_empty($destination_tmp) && $file = @file_get_contents($url)) { + @file_put_contents($destination_tmp, $file); + } + if (!drush_file_not_empty($destination_tmp)) { + // Download failed. + return FALSE; + } + } + if ($destination) { + drush_move_dir($destination_tmp, $destination, $overwrite); + return $destination; + } + return $destination_tmp; +} diff --git a/vendor/drush/drush/examples/xkcd.drush.inc b/vendor/drush/drush/examples/xkcd.drush.inc new file mode 100644 index 0000000000..bb53ff92c4 --- /dev/null +++ b/vendor/drush/drush/examples/xkcd.drush.inc @@ -0,0 +1,161 @@ + "Retrieve and display xkcd cartoons.", + 'arguments' => array( + 'search' => 'Optional argument to retrive the cartoons matching an index number, keyword search or "random". If omitted the latest cartoon will be retrieved.', + ), + 'options' => array( + 'image-viewer' => 'Command to use to view images (e.g. xv, firefox). Defaults to "display" (from ImageMagick).', + 'google-custom-search-api-key' => 'Google Custom Search API Key, available from https://code.google.com/apis/console/. Default key limited to 100 queries/day globally.', + ), + 'examples' => array( + 'drush xkcd' => 'Retrieve and display the latest cartoon.', + 'drush xkcd sandwich' => 'Retrieve and display cartoons about sandwiches.', + 'drush xkcd 123 --image-viewer=eog' => 'Retrieve and display cartoon #123 in eog.', + 'drush xkcd random --image-viewer=firefox' => 'Retrieve and display a random cartoon in Firefox.', + ), + 'aliases' => array('xkcd'), + // No bootstrap at all. + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + ); + + return $items; +} + +/** + * Implements hook_drush_help(). + * + * This function is called whenever a drush user calls + * 'drush help '. This hook is optional. If a command + * does not implement this hook, the command's description is used instead. + * + * This hook is also used to look up help metadata, such as help + * category title and summary. See the comments below for a description. + */ +function xkcd_drush_help($section) { + switch ($section) { + case 'drush:xkcd-fetch': + return dt("A command line tool (1) for a web site tool (2), that emulates +(badly) a web based tool (3) that emulates (badly) a command line tool (4) to +access a web site (5) with awesome geek humor.\n +(1) Drush +(2) Drupal +(3) http://uni.xkcd.com/ +(4) BASH +(5) http://xkcd.com/"); + } +} + +/** + * Implements drush_hook_COMMAND(). + * + * The command callback is where the action takes place. + * + * The function name should be same as command name but with dashes turned to + * underscores and 'drush_commandfile_' prepended, where 'commandfile' is + * taken from the file 'commandfile.drush.inc', which in this case is + * 'sandwich'. Note also that a simplification step is also done in instances + * where the commandfile name is the same as the beginning of the command name, + * "drush_example_example_foo" is simplified to just "drush_example_foo". + * To also implement a hook that is called before your command, implement + * "drush_hook_pre_example_foo". For a list of all available hooks for a + * given command, run drush in --debug mode. + * + * If for some reason you do not want your hook function to be named + * after your command, you may define a 'callback' item in your command + * object that specifies the exact name of the function that should be + * called. + * + * In this function, all of Drupal's API is (usually) available, including + * any functions you have added in your own modules/themes. + * + * @see drush_invoke() + * @see drush.api.php + * + * @param string $search + * An optional string with search keyworks, cartoon ID or "random". + */ +function drush_xkcd_fetch($search = '') { + if (empty($search)) { + drush_xkcd_display('http://xkcd.com'); + } + elseif (is_numeric($search)) { + drush_xkcd_display('http://xkcd.com/' . $search); + } + elseif ($search == 'random') { + $xkcd_response = @json_decode(file_get_contents('http://xkcd.com/info.0.json')); + if (!empty($xkcd_response->num)) { + drush_xkcd_display('http://xkcd.com/' . rand(1, $xkcd_response->num)); + } + } + else { + // This uses an API key with a limited number of searches per. + $search_response = @json_decode(file_get_contents('https://www.googleapis.com/customsearch/v1?key=' . drush_get_option('google-custom-search-api-key', 'AIzaSyDpE01VDNNT73s6CEeJRdSg5jukoG244ek') . '&cx=012652707207066138651:zudjtuwe28q&q=' . $search)); + if (!empty($search_response->items)) { + foreach ($search_response->items as $item) { + drush_xkcd_display($item->link); + } + } + else { + drush_set_error('DRUSH_XKCD_SEARCH_FAIL', dt('The search failed or produced no results.')); + } + } +} + +/** + * Display a given XKCD cartoon. + * + * Retrieve and display a table of metadata for an XKCD cartoon, then retrieve + * and display the cartoon using a specified image viewer. + * + * @param string $url + * A string with the URL of the cartoon to display. + */ +function drush_xkcd_display($url) { + $xkcd_response = @json_decode(file_get_contents($url . '/info.0.json')); + if (!empty($xkcd_response->num)) { + $data = (array) $xkcd_response; + $data['date'] = $data['year'] . '/' . $data['month'] . '/' . $data['day']; + unset($data['safe_title'], $data['news'], $data['link'], $data['year'], $data['month'], $data['day']); + drush_print_table(drush_key_value_to_array_table($data)); + $img = drush_download_file($data['img']); + drush_register_file_for_deletion($img); + drush_shell_exec(drush_get_option('image-viewer', 'display') . ' ' . $img); + } + else { + drush_set_error('DRUSH_XKCD_METADATA_FAIL', dt('Unable to retrieve cartoon metadata.')); + } +} diff --git a/vendor/drush/drush/includes/annotationcommand_adapter.inc b/vendor/drush/drush/includes/annotationcommand_adapter.inc new file mode 100644 index 0000000000..07ab489517 --- /dev/null +++ b/vendor/drush/drush/includes/annotationcommand_adapter.inc @@ -0,0 +1,879 @@ +setIncludeFilesAtBase(false) + ->setSearchDepth(3) + ->ignoreNamespacePart('contrib', 'Commands') + ->ignoreNamespacePart('custom', 'Commands') + ->ignoreNamespacePart('src') + ->setSearchLocations(['Commands']) + ->setSearchPattern('#.*Commands.php$#'); + } + return $discovery; +} + +/** + * Initialize and cache the command factory. Drush 9 uses dependency injection. + * + * @return AnnotatedCommandFactory + */ +function annotationcommand_adapter_get_factory() { + static $factory; + if (!isset($factory)) { + $factory = new AnnotatedCommandFactory(); + $factory->commandProcessor()->hookManager()->add('annotatedcomand_adapter_backend_result', HookManager::EXTRACT_OUTPUT); + $formatter = new FormatterManager(); + $formatter->addDefaultFormatters(); + $formatter->addDefaultSimplifiers(); + $factory->commandProcessor()->setFormatterManager($formatter); + } + return $factory; +} + +/** + * Fetch the command processor from the factory. + * + * @return AnnotatedCommandFactory + */ +function annotationcommand_adapter_get_processor() { + $factory = annotationcommand_adapter_get_factory(); + return $factory->commandProcessor(); +} + +/** + * Fetch the formatter manager from the command processor + * + * @return FormatterManager + */ +function annotatedcomand_adapter_get_formatter() { + $commandProcessor = annotationcommand_adapter_get_processor(); + return $commandProcessor->formatterManager(); +} + +/** + * Callback function called by HookManager::EXTRACT_OUTPUT to set + * the backend result. + */ +function annotatedcomand_adapter_backend_result($structured_data) { + $return = drush_backend_get_result(); + if (empty($return)) { + drush_backend_set_result($structured_data); + } +} + +/** + * Return the cached commands built by annotationcommand_adapter_discover. + * @see drush_get_commands() + */ +function annotationcommand_adapter_commands() { + $annotation_commandfiles = drush_get_context('DRUSH_ANNOTATED_COMMANDFILES'); + // Remove any entry in the commandfiles list from an ignored module. + $ignored = implode('|', drush_get_option_list('ignored-modules')); + $regex = "#/(modules|themes|profiles)(/|/.*/)($ignored)/#"; + foreach ($annotation_commandfiles as $key => $path) { + if (preg_match($regex, $path)) { + unset($annotation_commandfiles[$key]); + } + } + $site_wide_commands = annotationcommand_adapter_get_commands($annotation_commandfiles); + $module_service_commands = drush_get_context('DRUSH_MODULE_SERVICE_COMMANDS'); + $commands = array_merge($site_wide_commands, $module_service_commands); + + return $commands; +} + +/** + * Search for annotation commands at the provided search path. + * @see _drush_find_commandfiles() + */ +function annotationcommand_adapter_discover($searchpath, $phase = false, $phase_max = false) { + if (empty($searchpath)) { + return; + } + if (($phase >= DRUSH_BOOTSTRAP_DRUPAL_SITE) && (drush_drupal_major_version() >= 8)) { + return; + } + $annotation_commandfiles = []; + // Assemble a cid specific to the bootstrap phase and searchpaths. + // Bump $cf_version when making a change to a dev version of Drush + // that invalidates the commandfile cache. + $cf_version = 1; + $cid = drush_get_cid('annotationfiles-' . $phase, array(), array_merge($searchpath, array($cf_version))); + $command_cache = drush_cache_get($cid); + if (isset($command_cache->data)) { + $annotation_commandfiles = $command_cache->data; + } + else { + // Check to see if this is the Drush searchpath for instances where we are + // NOT going to do a full bootstrap (e.g. when running a help command) + if (($phase == DRUSH_BOOTSTRAP_DRUPAL_SITE) && ($phase_max < DRUSH_BOOTSTRAP_DRUPAL_FULL)) { + $searchpath = annotationcommand_adapter_refine_searchpaths($searchpath); + } + $discovery = annotationcommand_adapter_get_discovery(); + $annotation_commandfiles = $discovery->discoverNamespaced($searchpath, '\Drupal'); + drush_cache_set($cid, $annotation_commandfiles); + } + drush_set_context( + 'DRUSH_ANNOTATED_COMMANDFILES', + array_merge( + drush_get_context('DRUSH_ANNOTATED_COMMANDFILES'), + $annotation_commandfiles + ) + ); +} + +/** + * This function is set as the $command['callback'] for Symfony Console commands + * e.g. those provided by Drupal 8 modules. Note that Drush 9 calls these with + * Symfony's Application::run() method, but Drush 8 continues to use the + * legacy Drush command dispatcher for all commands. + * + * @return bolean false if command failed (expect drush_set_error was called in this case) + */ +function annotationcommand_adapter_run_console_command() { + $args = func_get_args(); + $command = drush_get_command(); + + $console_command = $command['drush-console-command']; + // TODO: Build an appropriate input object + $input = annotationcommand_adapter_build_input($console_command, $args); + $output = new ConsoleOutput(); + $result = $console_command->run($input, $output); + + return $result; +} + +/** + * TODO: This could probably just be a DrushInputAdapter now. + */ +function annotationcommand_adapter_build_input($console_command, $userArgs) { + $args = []; + $defaultOptions = []; + $definition = $console_command->getDefinition(); + $inputArguments = $definition->getArguments(); + foreach ($inputArguments as $key => $inputOption) { + $value = array_shift($userArgs); + if (!isset($value)) { + $value = $inputOption->getDefault(); + } + $args[$key] = $value; + } + $inputOptions = $definition->getOptions(); + foreach ($inputOptions as $option => $inputOption) { + $defaultOptions[$option] = $inputOption->getDefault(); + } + foreach ($defaultOptions as $option => $value) { + $args["--$option"] = drush_get_option($option, $value); + } + // TODO: Need to add global options. Note that ArrayInput is validated. + $input = new ArrayInput($args, $definition); + return $input; +} + +/** + * Collect all of the options defined in every relevant context, and + * merge them together to form the options array. + * + * @return array + */ +function annotationcommand_adapter_get_options($command) { + $default_options = isset($command['consolidation-option-defaults']) ? $command['consolidation-option-defaults'] : []; + $options = drush_redispatch_get_options() + $default_options; + + $options += drush_get_merged_options(); + + return $options; +} + +/** + * This function is set as the $command['callback'] for commands that have + * been converted to annotated commands. Note that Drush 9 calls these with + * Symfony's Application::run() method, but Drush 8 continues to use the + * legacy Drush command dispatcher for all commands. + * + * @return bolean false if command failed (expect drush_set_error was called in this case) + */ +function annotationcommand_adapter_process_command() { + $userArgs = func_get_args(); + $commandprocessor = annotationcommand_adapter_get_processor(); + $command = drush_get_command(); + annotationcommand_adapter_add_hook_options($command); + $args = []; + foreach ($command['consolidation-arg-defaults'] as $key => $default) { + $value = array_shift($userArgs); + if (!isset($value)) { + $value = $default; + } + $args[$key] = $value; + } + + $input = new DrushInputAdapter($args, annotationcommand_adapter_get_options($command), $command['command']); + $output = new DrushOutputAdapter(); + annotationcommand_adapter_input($input); + $annotationData = $command['annotations']; + $commandData = new CommandData( + $annotationData, + $input, + $output + ); + $commandData->setIncludeOptionsInArgs($command['add-options-to-arguments']); + $names = annotationcommand_adapter_command_names($command); + + // For now, the only thing that can be injected is the InputInterface. + foreach ($command['injected-classes'] as $injected) { + switch ($injected) { + case 'Symfony\Component\Console\Input\InputInterface': + $commandData->injectInstance($input); + break; + } + } + + // n.b.: backend result is set by a post-alter hook. + $result = $commandprocessor->process( + $output, + $names, + $command['annotated-command-callback'], + $commandData + ); + + return $result; +} + +function annotationcommand_adapter_input($setInput = null) { + static $cacheInput = null; + if ($setInput != null) { + $cacheInput = $setInput; + } + return $cacheInput; +} + +/** + * Internal function called by annotationcommand_adapter_commands, which + * is called by drush_get_commands(). + * + * @param array $annotation_commandfiles path => class mapping + * + * @return object[] + */ +function annotationcommand_adapter_get_commands($annotation_commandfiles) { + $commands = []; + // This will give us a list containing something akin to: + // 'modules/default_content/src/CliTools/DefaultContentCommands.php' => + // '\\Drupal\\default_content\\CliTools\\DefaultContentCommands', + foreach ($annotation_commandfiles as $commandfile_path => $commandfile_class) { + try { + if (file_exists($commandfile_path) && ($commandfile_class != '\Drush\Commands\DrushCommands')) { + $commandhandler = annotationcommand_adapter_create_commandfile_instance($commandfile_path, $commandfile_class); + $commands_for_this_commandhandler = annotationcommand_adapter_get_commands_for_commandhandler($commandhandler, $commandfile_path); + $commands = array_merge($commands, $commands_for_this_commandhandler); + } + } + catch (\Exception $e) {} + } + return $commands; +} + +use Psr\Log\LoggerAwareInterface; + + +/** + * Create and cache a commandfile instance. + * + * @param string $commandfile_path Path to the commandfile implementation + * @param string $commandfile_class Namespace and class of the commandfile object + * + * @return object + */ +function annotationcommand_adapter_create_commandfile_instance($commandfile_path, $commandfile_class) { + $cache =& drush_get_context('DRUSH_ANNOTATION_COMMANDFILE_INSTANCES'); + if (!isset($cache[$commandfile_path])) { + include_once $commandfile_path; + $commandhandler = new $commandfile_class; + + // Inject logger if requested + if ($commandhandler instanceof LoggerAwareInterface) { + $commandhandler->setLogger(Drush::logger()); + } + + if (class_exists('\Consolidation\SiteAlias\SiteAliasManager')) { + $alias_manager_injector = new AliasManagerAdapterInjector(); + $alias_manager_injector->inflect($commandhandler); + } + + if (class_exists('\Consolidation\SiteProcess\ProcessManager')) { + $process_manager_injector = new ProcessManagerInjector(); + $process_manager_injector->inflect($commandhandler); + } + + $cache[$commandfile_path] = $commandhandler; + } + return $cache[$commandfile_path]; +} + +/** + * TODO: document + */ +function annotationcommand_adapter_cache_module_console_commands($console_command, $commandfile_path = null) { + if (!isset($commandfile_path)) { + $class = new \ReflectionClass($console_command); + $commandfile_path = $class->getFileName(); + } + $module_service_commands = drush_get_context('DRUSH_MODULE_SERVICE_COMMANDS'); + $commands = annotationcommand_adapter_get_command_for_console_command($console_command, $commandfile_path); + drush_set_context('DRUSH_MODULE_SERVICE_COMMANDS', array_merge($commands, $module_service_commands)); +} + +/** + * TODO: document + */ +function annotationcommand_adapter_cache_module_service_commands($commandhandler, $commandfile_path = null) { + if (!isset($commandfile_path)) { + $class = new \ReflectionClass($commandhandler); + $commandfile_path = $class->getFileName(); + } + $module_service_commands = drush_get_context('DRUSH_MODULE_SERVICE_COMMANDS'); + $commands = annotationcommand_adapter_get_commands_for_commandhandler($commandhandler, $commandfile_path, false); + drush_set_context('DRUSH_MODULE_SERVICE_COMMANDS', array_merge($commands, $module_service_commands)); +} + +/** + * Convert a Symfony Console command into a Drush $command record + * + * @param Symfony\Component\Console\Command\Command $console_command The Symfony Console command to convert + * @param string $commandfile_path Path to console command file + * + * @return array Drush $command record + */ +function annotationcommand_adapter_get_command_for_console_command($console_command, $commandfile_path) { + $commands = []; + $commandfile = basename($commandfile_path, '.php'); + $factory = annotationcommand_adapter_get_factory(); + $inputDefinition = $console_command->getDefinition(); + $inputArguments = $inputDefinition->getArguments(); + $inputOptions = $inputDefinition->getOptions(); + $aliases = $console_command->getAliases(); + $command_name = strtolower($console_command->getName()); + $standard_alias = str_replace(':', '-', $command_name); + if ($command_name != $standard_alias) { + $aliases[] = $standard_alias; + } + $command = [ + 'name' => $command_name, + 'callback' => 'annotationcommand_adapter_run_console_command', + 'drush-console-command' => $console_command, + 'commandfile' => $commandfile, + 'category' => $commandfile, + 'options' => [], + 'arguments' => [], + 'description' => $console_command->getDescription(), + 'examples' => $console_command->getUsages(), + 'aliases' => $aliases, + ]; + foreach ($inputArguments as $arg => $inputArg) { + $command['arguments'][$arg] = $inputArg->getDescription(); + } + $command['required-arguments'] = $inputDefinition->getArgumentRequiredCount(); + foreach ($inputOptions as $option => $inputOption) { + $description = $inputOption->getDescription(); + $default = $inputOption->getDefault(); + $command['options'][$option] = ['description' => $description]; + if (!empty($default)) { + $command['options'][$option]['example-value'] = $default; + } + } + $command += drush_command_defaults($command_name, $commandfile, $commandfile_path); + $commands[$command_name] = $command; + return $commands; +} + +function annotationcommand_adapter_bootstrap_phase_index($phase) +{ + $phaseMap = annotationcommand_adapter_bootstrap_phase_map(); + if (isset($phaseMap[$phase])) { + return $phaseMap[$phase]; + } + + if ((substr($phase, 0, 16) != 'DRUSH_BOOTSTRAP_') || (!defined($phase))) { + return; + } + return constant($phase); +} + +function annotationcommand_adapter_bootstrap_phase_map() +{ + return [ + 'none' => DRUSH_BOOTSTRAP_NONE, + 'root' => DRUSH_BOOTSTRAP_DRUPAL_ROOT, + 'site' => DRUSH_BOOTSTRAP_DRUPAL_SITE, + 'config' => DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION, + 'configuration' => DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION, + 'db' => DRUSH_BOOTSTRAP_DRUPAL_DATABASE, + 'database' => DRUSH_BOOTSTRAP_DRUPAL_DATABASE, + 'full' => DRUSH_BOOTSTRAP_DRUPAL_FULL, + ]; +} + +/** + * Convert an annotated command command handler object into a Drush $command record. + * + * @param object $commandhandler Command handler object + * @param string $commandfile_path + * @param boolean $includeAllPublicMethods TODO remove this, make it 'false' always + * + * @return array Drush $command record + */ +function annotationcommand_adapter_get_commands_for_commandhandler($commandhandler, $commandfile_path, $includeAllPublicMethods = true) { + if (!$commandhandler) { + return []; + } + $cache =& drush_get_context('DRUSH_ANNOTATION_COMMANDS_FOR_COMMANDFILE'); + if (isset($cache[$commandfile_path])) { + return $cache[$commandfile_path]; + } + $factory = annotationcommand_adapter_get_factory(); + $commands = []; + $commandfile = basename($commandfile_path, '.php'); + + $commandinfo_list = $factory->getCommandInfoListFromClass($commandhandler); + + foreach ($commandinfo_list as $commandinfo) { + // Hooks are automatically registered when the commandhandler is + // created via registerCommandClass(), so we don't need to do it again here. + // $factory->registerCommandHook($commandinfo, $commandhandler); + $aliases = $commandinfo->getAliases(); + $command_name = strtolower($commandinfo->getName()); + $standard_alias = str_replace(':', '-', $command_name); + if ($command_name != $standard_alias) { + $aliases[] = $standard_alias; + } + $handle_remote_commands = $commandinfo->getAnnotation('handle-remote-commands') == 'true'; + // TODO: if there is no 'bootstrap' annotation, maybe we should default to NONE instead of FULL? + if ($bootstrap = $commandinfo->getAnnotation('bootstrap')) { + // Convert from the bootstrap string to the appropriate bootstrap phase index + $bootstrap = annotationcommand_adapter_bootstrap_phase_index($bootstrap); + } + $command = [ + 'name' => $command_name, + //'callback' => [$commandhandler, $commandinfo->getMethodName()], + 'callback' => 'annotationcommand_adapter_process_command', + 'annotated-command-callback' => [$commandhandler, $commandinfo->getMethodName()], + 'commandfile' => $commandfile, + 'category' => $commandfile, + 'options' => [], + 'arguments' => [], + 'description' => $commandinfo->getDescription(), + 'examples' => $commandinfo->getExampleUsages(), + 'bootstrap' => $bootstrap, + 'handle-remote-commands' => $handle_remote_commands, + 'aliases' => $aliases, + 'add-options-to-arguments' => TRUE, + 'consolidation-output-formatters' => TRUE, + 'consolidation-option-defaults' => $commandinfo->options()->getValues(), + 'consolidation-arg-defaults' => $commandinfo->arguments()->getValues(), + ]; + $required_arguments = 0; + foreach ($commandinfo->arguments()->getValues() as $arg => $default) { + $command['arguments'][$arg] = $commandinfo->arguments()->getDescription($arg); + if (!isset($default)) { + ++$required_arguments; + } + } + $command['required-arguments'] = $required_arguments; + foreach ($commandinfo->options()->getValues() as $option => $default) { + $description = $commandinfo->options()->getDescription($option); + $command['options'][$option] = ['description' => $description]; + if (!empty($default)) { + $command['options'][$option]['example-value'] = $default; + } + $fn = 'annotationcommand_adapter_alter_option_description_' . $option; + if (function_exists($fn)) { + $command['options'][$option] = $fn($command['options'][$option], $commandinfo, $default); + } + } + $command['annotations'] = $commandinfo->getAnnotations(); + $command['injected-classes'] = $commandinfo->getInjectedClasses(); + // If the command has a '@return' annotation, then + // remember information we will need to use the output formatter. + $returnType = $commandinfo->getReturnType(); + if (isset($returnType)) { + $command['return-type'] = $returnType; + } + $command += drush_command_defaults($command_name, $commandfile, $commandfile_path); + $commands[$command_name] = $command; + } + $cache[$commandfile_path] = $commands; + return $commands; +} + +/** + * Modify a $command record, adding option definitions defined by any + * command hook. + * + * @param array $command Drush command record to modify + */ +function annotationcommand_adapter_add_hook_options(&$command) +{ + // Get options added by hooks. We postpone doing this until the + // last minute rather than doing it when processing commandfiles + // so that we do not need to worry about what order we process the + // commandfiles in -- we can load extensions late, and still have + // the extension hook a core command, or have an early-loaded global + // extension hook a late-loaded extension (e.g. attached to a module). + $names = annotationcommand_adapter_command_names($command); + $names[] = '*'; // we are missing annotations here; maybe we just don't support that? (TODO later, maybe) + $factory = annotationcommand_adapter_get_factory(); + $extraOptions = $factory->hookManager()->getHookOptions($names); + foreach ($extraOptions as $commandinfo) { + if (!isset($command['consolidation-option-defaults'])) { + $command['consolidation-option-defaults'] = array(); + } + $command['consolidation-option-defaults'] += $commandinfo->options()->getValues(); + foreach ($commandinfo->options()->getValues() as $option => $default) { + $description = $commandinfo->options()->getDescription($option); + $command['options'][$option] = ['description' => $description]; + if (!empty($default)) { + $command['options'][$option]['example-value'] = $default; + } + $fn = 'annotationcommand_adapter_alter_option_description_' . $option; + if (function_exists($fn)) { + $command['options'][$option] = $fn($command['options'][$option], $commandinfo, $default); + } + } + } +} + +/** + * Build all of the name variants for a Drush $command record + * + * @param array $command Drush command record + * + * @return string[] + */ +function annotationcommand_adapter_command_names($command) +{ + $names = array_merge( + [$command['command']], + $command['aliases'] + ); + if (!empty($command['annotated-command-callback'])) { + $commandHandler = $command['annotated-command-callback'][0]; + $reflectionClass = new \ReflectionClass($commandHandler); + $commandFileClass = $reflectionClass->getName(); + $names[] = $commandFileClass; + } + return $names; +} + +/** + * Convert from an old-style Drush initialize hook into annotated-command hooks. + * @see _drush_invoke_hooks(). + * + * @param string[] $names All of the applicable names for the command being hooked + * @param CommandData $commandData All of the parameter data associated with the + * current command invokation, including the InputInterface, OutputInterface + * and AnnotationData + */ +function annotationcommand_adapter_call_initialize($names, CommandData $commandData) +{ + $factory = annotationcommand_adapter_get_factory(); + $hookManager = $factory->hookManager(); + + $hooks = $hookManager->getHooks( + $names, + [ + HookManager::PRE_INITIALIZE, + HookManager::INITIALIZE, + HookManager::POST_INITIALIZE, + ], + $commandData->annotationData() + ); + + foreach ((array)$hooks as $hook) { + $hook($commandData->input(), $commandData->annotationData()); + } +} + +/** + * Convert from an old-style Drush pre-validate hook into annotated-command hooks. + * @see _drush_invoke_hooks(). + * + * @param string[] $names All of the applicable names for the command being hooked + * @param CommandData $commandData All of the parameter data associated with the + * current command invokation, including the InputInterface, OutputInterface + * and AnnotationData + * + * @return boolean|object + */ +function annotationcommand_adapter_call_hook_pre_validate($names, CommandData $commandData) +{ + return annotationcommand_adapter_call_validate_interface( + $names, + [ + HookManager::PRE_ARGUMENT_VALIDATOR, + ], + $commandData + ); +} + +/** + * Convert from an old-style Drush validate hook into annotated-command hooks. + * @see _drush_invoke_hooks(). + * + * @param string[] $names All of the applicable names for the command being hooked + * @param CommandData $commandData All of the parameter data associated with the + * current command invokation, including the InputInterface, OutputInterface + * and AnnotationData + * + * @return boolean|object + */ +function annotationcommand_adapter_call_hook_validate($names, CommandData $commandData) +{ + return annotationcommand_adapter_call_validate_interface( + $names, + [ + HookManager::ARGUMENT_VALIDATOR, + ], + $commandData + ); +} + +/** + * Convert from an old-style Drush pre-command hook into annotated-command hooks. + * @see _drush_invoke_hooks(). + * + * @param string[] $names All of the applicable names for the command being hooked + * @param CommandData $commandData All of the parameter data associated with the + * current command invokation, including the InputInterface, OutputInterface + * and AnnotationData + * + * @return boolean|object + */ +function annotationcommand_adapter_call_hook_pre_command($names, CommandData $commandData) +{ + return annotationcommand_adapter_call_validate_interface( + $names, + [ + HookManager::PRE_COMMAND_HOOK, + ], + $commandData + ); +} + +/** + * Convert from an old-style Drush 'command' hook into annotated-command hooks. + * @see _drush_invoke_hooks(). + * + * @param string[] $names All of the applicable names for the command being hooked + * @param CommandData $commandData All of the parameter data associated with the + * current command invokation, including the InputInterface, OutputInterface + * and AnnotationData + * + * @return boolean|object + */ +function annotationcommand_adapter_call_hook_command($names, CommandData $commandData) +{ + return annotationcommand_adapter_call_validate_interface( + $names, + [ + HookManager::COMMAND_HOOK, + ], + $commandData + ); +} + +/** + * Convert from an old-style Drush post-command hook into annotated-command hooks. + * @see _drush_invoke_hooks(). + * + * @param string[] $names All of the applicable names for the command being hooked + * @param CommandData $commandData All of the parameter data associated with the + * current command invokation, including the InputInterface, OutputInterface + * and AnnotationData + * @param mixed $return The return value of the command being executed + * + * @return mixed The altered command return value + */ +function annotationcommand_adapter_call_hook_post_command($names, CommandData $commandData, $return) +{ + return annotationcommand_adapter_call_process_interface( + $names, + [ + HookManager::POST_COMMAND_HOOK, + ], + $commandData, + $return + ); +} + +/** + * After the primary Drush command hook is called, call all of the annotated-command + * process and alter hooks. + * @see _drush_invoke_hooks(). + * + * @param string[] $names All of the applicable names for the command being hooked + * @param CommandData $commandData All of the parameter data associated with the + * current command invokation, including the InputInterface, OutputInterface + * and AnnotationData + * @param mixed $return The return value of the command being executed + * + * @return mixed The altered command return value + */ +function annotationcommand_adapter_call_hook_process_and_alter($names, $commandData, $return) +{ + return annotationcommand_adapter_call_process_interface( + $names, + [ + HookManager::PRE_PROCESS_RESULT, + HookManager::PROCESS_RESULT, + HookManager::POST_PROCESS_RESULT, + HookManager::PRE_ALTER_RESULT, + HookManager::ALTER_RESULT, + HookManager::POST_ALTER_RESULT, + ], + $commandData, + $return + ); +} + +/** + * Given a list of hooks that conform to the interface ProcessResultInterface, + * call them and return the result. + * + * @param string[] $names All of the applicable names for the command being hooked + * @param string[] $hooks All of the HookManager hooks that should be called + * @param CommandData $commandData All of the parameter data associated with the + * current command invokation, including the InputInterface, OutputInterface + * and AnnotationData + * @param mixed $return The return value of the command being executed + * + * @return mixed The altered command return value + */ +function annotationcommand_adapter_call_process_interface($names, $hooks, CommandData $commandData, $return) +{ + $factory = annotationcommand_adapter_get_factory(); + $hookManager = $factory->hookManager(); + + $hooks = $hookManager->getHooks($names, $hooks, $commandData->annotationData()); + + foreach ((array)$hooks as $hook) { + $result = $hook($return, $commandData); + if (isset($result)) { + $return = $result; + } + } + return $return; +} + +/** + * Given a list of hooks that conform to the interface ValidatorInterface, + * call them and return the result. + * + * @param string[] $names All of the applicable names for the command being hooked + * @param string[] $hooks All of the HookManager hooks that should be called + * @param CommandData $commandData All of the parameter data associated with the + * current command invokation, including the InputInterface, OutputInterface + * and AnnotationData + * + * @return boolean|object + */ +function annotationcommand_adapter_call_validate_interface($names, $hooks, CommandData $commandData) +{ + $factory = annotationcommand_adapter_get_factory(); + $hookManager = $factory->hookManager(); + + $annotationData = $commandData->annotationData(); + $hooks = $hookManager->getHooks($names, $hooks, $annotationData); + + foreach ((array)$hooks as $hook) { + $validated = $hook($commandData); + // TODO: if $validated is a CommandError, maybe the best thing to do is 'return drush_set_error()'? + if (is_object($validated) || ($validated === false)) { + return $validated; + } + } + return true; +} + +/** + * TODO: Document + */ +function annotationcommand_adapter_alter_option_description_format($option_help, $commandinfo, $default) { + $formatterManager = annotatedcomand_adapter_get_formatter(); + $return_type = $commandinfo->getReturnType(); + if (!empty($return_type)) { + $available_formats = $formatterManager->validFormats($return_type); + $option_help['description'] = dt('Select output format. Available: !formats.', array('!formats' => implode(', ', $available_formats))); + if (!empty($default)) { + $option_help['description'] .= dt(' Default is !default.', array('!default' => $default)); + } + } + return $option_help; +} + +/** + * TODO: Document + */ +function annotationcommand_adapter_alter_option_description_fields($option_help, $commandinfo, $default) { + $formatOptions = new FormatterOptions($commandinfo->getAnnotations()->getArrayCopy()); + $field_labels = $formatOptions->get(FormatterOptions::FIELD_LABELS); + $default_fields = $formatOptions->get(FormatterOptions::DEFAULT_FIELDS, [], array_keys($field_labels)); + $available_fields = array_keys($field_labels); + $option_help['example-value'] = implode(', ', $default_fields); + $option_help['description'] = dt('Fields to output. All available fields are: !available.', array('!available' => implode(', ', $available_fields))); + return $option_help; +} + +/** + * In some circumstances, Drush just does a deep search for any *.drush.inc + * file, so that it can find all commands, in enabled and disabled modules alike, + * for the purpose of displaying the help text for that command. + */ +function annotationcommand_adapter_refine_searchpaths($searchpath) { + $result = []; + foreach ($searchpath as $path) { + $max_depth = TRUE; + $pattern = '/.*\.info$/'; + if (drush_drupal_major_version() > 7) { + $pattern = '/.*\.info.yml$/'; + } + $locations = drush_scan_directory($path, $pattern, ['.', '..'], false, $max_depth); + + // Search for any directory that might be a module or theme (contains + // a *.info or a *.info.yml file) + foreach ($locations as $key => $info) { + $result[dirname($key)] = true; + } + } + return array_keys($result); +} diff --git a/vendor/drush/drush/includes/array_column.inc b/vendor/drush/drush/includes/array_column.inc new file mode 100644 index 0000000000..e7551173ad --- /dev/null +++ b/vendor/drush/drush/includes/array_column.inc @@ -0,0 +1,117 @@ +>>'); +define('DRUSH_BACKEND_OUTPUT_DELIMITER', DRUSH_BACKEND_OUTPUT_START . '%s<< "\\0")); + $packet_regex = str_replace("\n", "", $packet_regex); + $data['output'] = preg_replace("/$packet_regex/s", '', drush_backend_output_collect(NULL)); + } + + if (drush_get_context('DRUSH_QUIET', FALSE)) { + ob_end_clean(); + } + + $result_object = drush_backend_get_result(); + if (isset($result_object)) { + $data['object'] = $result_object; + } + + $error = drush_get_error(); + $data['error_status'] = ($error) ? $error : DRUSH_SUCCESS; + + $data['log'] = drush_get_log(); // Append logging information + // The error log is a more specific version of the log, and may be used by calling + // scripts to check for specific errors that have occurred. + $data['error_log'] = drush_get_error_log(); + // If there is a @self record, then include it in the result + $self_record = drush_sitealias_get_record('@self'); + if (!empty($self_record)) { + $site_context = drush_get_context('site', array()); + unset($site_context['config-file']); + unset($site_context['context-path']); + unset($self_record['loaded-config']); + unset($self_record['#name']); + $data['self'] = array_merge($site_context, $self_record); + } + + // Return the options that were set at the end of the process. + $data['context'] = drush_get_merged_options(); + printf("\0" . DRUSH_BACKEND_OUTPUT_DELIMITER, json_encode($data)); +} + +/** + * Callback to collect backend command output. + */ +function drush_backend_output_collect($string) { + static $output = ''; + if (!isset($string)) { + return $output; + } + + $output .= $string; + return $string; +} + +/** + * Output buffer functions that discards all output but backend packets. + */ +function drush_backend_output_discard($string) { + $packet_regex = strtr(sprintf(DRUSH_BACKEND_PACKET_PATTERN, "([^\0]*)"), array("\0" => "\\0")); + $packet_regex = str_replace("\n", "", $packet_regex); + if (preg_match_all("/$packet_regex/s", $string, $matches)) { + return implode('', $matches[0]); + } +} + +/** + * Output a backend packet if we're running as backend. + * + * @param packet + * The packet to send. + * @param data + * Data for the command. + * + * @return + * A boolean indicating whether the command was output. + */ +function drush_backend_packet($packet, $data) { + if (drush_get_context('DRUSH_BACKEND')) { + $data['packet'] = $packet; + $data = json_encode($data); + // We use 'fwrite' instead of 'drush_print' here because + // this backend packet is out-of-band data. + fwrite(STDERR, sprintf(DRUSH_BACKEND_PACKET_PATTERN, $data)); + return TRUE; + } + + return FALSE; +} + +/** + * Parse output returned from a Drush command. + * + * @param string + * The output of a drush command + * @param integrate + * Integrate the errors and log messages from the command into the current process. + * @param outputted + * Whether output has already been handled. + * + * @return + * An associative array containing the data from the external command, or the string parameter if it + * could not be parsed successfully. + */ +function drush_backend_parse_output($string, $backend_options = array(), $outputted = FALSE) { + $regex = sprintf(DRUSH_BACKEND_OUTPUT_DELIMITER, '(.*)'); + + preg_match("/$regex/s", $string, $match); + + if (!empty($match) && $match[1]) { + // we have our JSON encoded string + $output = $match[1]; + // remove the match we just made and any non printing characters + $string = trim(str_replace(sprintf(DRUSH_BACKEND_OUTPUT_DELIMITER, $match[1]), '', $string)); + } + + if (!empty($output)) { + $data = json_decode($output, TRUE); + if (is_array($data)) { + _drush_backend_integrate($data, $backend_options, $outputted); + return $data; + } + } + return $string; +} + +/** + * Integrate log messages and error statuses into the current + * process. + * + * Output produced by the called script will be printed if we didn't print it + * on the fly, errors will be set, and log messages will be logged locally, if + * not already logged. + * + * @param data + * The associative array returned from the external command. + * @param outputted + * Whether output has already been handled. + */ +function _drush_backend_integrate($data, $backend_options, $outputted) { + // In 'integrate' mode, logs and errors have already been handled + // by drush_backend_packet (sender) drush_backend_parse_packets (receiver - us) + // during incremental output. We therefore do not need to call drush_set_error + // or drush_log here. The exception is if the sender is an older version of + // Drush (version 4.x) that does not send backend packets, then we will + // not have processed the log entries yet, and must print them here. + $received_packets = drush_get_context('DRUSH_RECEIVED_BACKEND_PACKETS', FALSE); + if (is_array($data['log']) && $backend_options['log'] && (!$received_packets)) { + foreach($data['log'] as $log) { + $message = is_array($log['message']) ? implode("\n", $log['message']) : $log['message']; + if (isset($backend_options['#output-label'])) { + $message = $backend_options['#output-label'] . $message; + } + if (isset($log['error']) && $backend_options['integrate']) { + drush_set_error($log['error'], $message); + } + elseif ($backend_options['integrate']) { + drush_log($message, $log['type']); + } + } + } + // Output will either be printed, or buffered to the drush_backend_output command. + // If the output has already been printed, then we do not need to show it again on a failure. + if (!$outputted) { + if (drush_cmp_error('DRUSH_APPLICATION_ERROR') && !empty($data['output'])) { + drush_set_error("DRUSH_APPLICATION_ERROR", dt("Output from failed command :\n !output", array('!output' => $data['output']))); + } + elseif ($backend_options['output']) { + _drush_backend_print_output($data['output'], $backend_options); + } + } +} + +/** + * Supress log message output during backend integrate. + */ +function _drush_backend_integrate_log($entry) { +} + +/** + * Call an external command using proc_open. + * + * @param cmds + * An array of records containing the following elements: + * 'cmd' - The command to execute, already properly escaped + * 'post-options' - An associative array that will be JSON encoded + * and passed to the script being called. Objects are not allowed, + * as they do not json_decode gracefully. + * 'backend-options' - Options that control the operation of the backend invoke + * - OR - + * An array of commands to execute. These commands already need to be properly escaped. + * In this case, post-options will default to empty, and a default output label will + * be generated. + * @param data + * An associative array that will be JSON encoded and passed to the script being called. + * Objects are not allowed, as they do not json_decode gracefully. + * + * @return + * False if the command could not be executed, or did not return any output. + * If it executed successfully, it returns an associative array containing the command + * called, the output of the command, and the error code of the command. + */ +function _drush_backend_proc_open($cmds, $process_limit, $context = NULL) { + $descriptorspec = array( + 0 => array("pipe", "r"), // stdin is a pipe that the child will read from + 1 => array("pipe", "w"), // stdout is a pipe that the child will write to + ); + + $open_processes = array(); + $bucket = array(); + $process_limit = max($process_limit, 1); + $is_windows = drush_is_windows(); + // Loop through processes until they all close, having a nap as needed. + $nap_time = 0; + while (count($open_processes) || count($cmds)) { + $nap_time++; + if (count($cmds) && (count($open_processes) < $process_limit)) { + // Pop the site and command (key / value) from the cmds array + end($cmds); + $cmd = current($cmds); + $site = key($cmds); + unset($cmds[$site]); + + if (is_array($cmd)) { + $c = $cmd['cmd']; + $post_options = $cmd['post-options']; + $backend_options = $cmd['backend-options']; + } + else { + $c = $cmd; + $post_options = array(); + $backend_options = array(); + } + $backend_options += array( + '#output-label' => '', + '#process-read-size' => 4096, + ); + $process = array(); + drush_log($backend_options['#output-label'] . $c); + $process['process'] = proc_open($c, $descriptorspec, $process['pipes'], null, null, array('context' => $context)); + if (is_resource($process['process'])) { + if ($post_options) { + fwrite($process['pipes'][0], json_encode($post_options)); // pass the data array in a JSON encoded string + } + // If we do not close stdin here, then we cause a deadlock; + // see: http://drupal.org/node/766080#comment-4309936 + // If we reimplement interactive commands to also use + // _drush_proc_open, then clearly we would need to keep + // this open longer. + fclose($process['pipes'][0]); + + $process['info'] = stream_get_meta_data($process['pipes'][1]); + stream_set_blocking($process['pipes'][1], FALSE); + stream_set_timeout($process['pipes'][1], 1); + $bucket[$site]['cmd'] = $c; + $bucket[$site]['output'] = ''; + $bucket[$site]['remainder'] = ''; + $bucket[$site]['backend-options'] = $backend_options; + $bucket[$site]['end_of_output'] = FALSE; + $bucket[$site]['outputted'] = FALSE; + $open_processes[$site] = $process; + } + // Reset the $nap_time variable as there might be output to process next + // time around: + $nap_time = 0; + } + // Set up to call stream_select(). See: + // http://php.net/manual/en/function.stream-select.php + // We can't use stream_select on Windows, because it doesn't work for + // streams returned by proc_open. + if (!$is_windows) { + $ss_result = 0; + $read_streams = array(); + $write_streams = array(); + $except_streams = array(); + foreach ($open_processes as $site => &$current_process) { + if (isset($current_process['pipes'][1])) { + $read_streams[] = $current_process['pipes'][1]; + } + } + // Wait up to 2s for data to become ready on one of the read streams. + if (count($read_streams)) { + $ss_result = stream_select($read_streams, $write_streams, $except_streams, 2); + // If stream_select returns a error, then fallback to using $nap_time. + if ($ss_result !== FALSE) { + $nap_time = 0; + } + } + } + + foreach ($open_processes as $site => &$current_process) { + if (isset($current_process['pipes'][1])) { + // Collect output from stdout + $bucket[$site][1] = ''; + $info = stream_get_meta_data($current_process['pipes'][1]); + + if (!feof($current_process['pipes'][1]) && !$info['timed_out']) { + $string = $bucket[$site]['remainder'] . fread($current_process['pipes'][1], $backend_options['#process-read-size']); + $bucket[$site]['remainder'] = ''; + $output_end_pos = strpos($string, DRUSH_BACKEND_OUTPUT_START); + if ($output_end_pos !== FALSE) { + $trailing_string = substr($string, 0, $output_end_pos); + $trailing_remainder = ''; + // If there is any data in the trailing string (characters prior + // to the backend output start), then process any backend packets + // embedded inside. + if (strlen($trailing_string) > 0) { + drush_backend_parse_packets($trailing_string, $trailing_remainder, $bucket[$site]['backend-options']); + } + // If there is any data remaining in the trailing string after + // the backend packets are removed, then print it. + if (strlen($trailing_string) > 0) { + _drush_backend_print_output($trailing_string . $trailing_remainder, $bucket[$site]['backend-options']); + $bucket[$site]['outputted'] = TRUE; + } + $bucket[$site]['end_of_output'] = TRUE; + } + if (!$bucket[$site]['end_of_output']) { + drush_backend_parse_packets($string, $bucket[$site]['remainder'], $bucket[$site]['backend-options']); + // Pass output through. + _drush_backend_print_output($string, $bucket[$site]['backend-options']); + if (strlen($string) > 0) { + $bucket[$site]['outputted'] = TRUE; + } + } + $bucket[$site][1] .= $string; + $bucket[$site]['output'] .= $string; + $info = stream_get_meta_data($current_process['pipes'][1]); + flush(); + + // Reset the $nap_time variable as there might be output to process + // next time around: + if (strlen($string) > 0) { + $nap_time = 0; + } + } + else { + fclose($current_process['pipes'][1]); + unset($current_process['pipes'][1]); + // close the pipe , set a marker + + // Reset the $nap_time variable as there might be output to process + // next time around: + $nap_time = 0; + } + } + else { + // if both pipes are closed for the process, remove it from active loop and add a new process to open. + $bucket[$site]['code'] = proc_close($current_process['process']); + unset($open_processes[$site]); + + // Reset the $nap_time variable as there might be output to process next + // time around: + $nap_time = 0; + } + } + + // We should sleep for a bit if we need to, up to a maximum of 1/10 of a + // second. + if ($nap_time > 0) { + usleep(max($nap_time * 500, 100000)); + } + } + return $bucket; + // TODO: Handle bad proc handles + //} + //return FALSE; +} + + + +/** + * Print the output received from a call to backend invoke, + * adding the label to the head of each line if necessary. + */ +function _drush_backend_print_output($output_string, $backend_options) { + if ($backend_options['output'] && !empty($output_string)) { + $output_label = array_key_exists('#output-label', $backend_options) ? $backend_options['#output-label'] : FALSE; + if (!empty($output_label)) { + // Remove one, and only one newline from the end of the + // string. Else we'll get an extra 'empty' line. + foreach (explode("\n", preg_replace('/\\n$/', '', $output_string)) as $line) { + fwrite(STDOUT, $output_label . rtrim($line) . "\n"); + } + } + else { + fwrite(STDOUT, $output_string); + } + } +} + +/** + * Parse out and remove backend packet from the supplied string and + * invoke the commands. + */ +function drush_backend_parse_packets(&$string, &$remainder, $backend_options) { + $remainder = ''; + $packet_regex = strtr(sprintf(DRUSH_BACKEND_PACKET_PATTERN, "([^\0]*)"), array("\0" => "\\0")); + $packet_regex = str_replace("\n", "", $packet_regex); + if (preg_match_all("/$packet_regex/s", $string, $match, PREG_PATTERN_ORDER)) { + drush_set_context('DRUSH_RECEIVED_BACKEND_PACKETS', TRUE); + foreach ($match[1] as $packet_data) { + $entry = (array) json_decode($packet_data); + if (is_array($entry) && isset($entry['packet'])) { + $function = 'drush_backend_packet_' . $entry['packet']; + if (function_exists($function)) { + $function($entry, $backend_options); + } + else { + drush_log(dt("Unknown backend packet @packet", array('@packet' => $entry['packet'])), LogLevel::NOTICE); + } + } + else { + drush_log(dt("Malformed backend packet"), LogLevel::ERROR); + drush_log(dt("Bad packet: @packet", array('@packet' => print_r($entry, TRUE))), LogLevel::DEBUG); + drush_log(dt("String is: @str", array('@str' => $packet_data), LogLevel::DEBUG)); + } + } + $string = preg_replace("/$packet_regex/s", '', $string); + } + // Check to see if there is potentially a partial packet remaining. + // We only care about the last null; if there are any nulls prior + // to the last one, they would have been removed above if they were + // valid drush packets. + $embedded_null = strrpos($string, "\0"); + if ($embedded_null !== FALSE) { + // We will consider everything after $embedded_null to be part of + // the $remainder string if: + // - the embedded null is less than strlen(DRUSH_BACKEND_OUTPUT_START) + // from the end of $string (that is, there might be a truncated + // backend packet header, or the truncated backend output start + // after the null) + // OR + // - the embedded null is followed by DRUSH_BACKEND_PACKET_START + // (that is, the terminating null for that packet has not been + // read into our buffer yet) + if (($embedded_null + strlen(DRUSH_BACKEND_OUTPUT_START) >= strlen($string)) || (substr($string, $embedded_null + 1, strlen(DRUSH_BACKEND_PACKET_START)) == DRUSH_BACKEND_PACKET_START)) { + $remainder = substr($string, $embedded_null); + $string = substr($string, 0, $embedded_null); + } + } +} + +/** + * Backend command for setting errors. + */ +function drush_backend_packet_set_error($data, $backend_options) { + if (!$backend_options['integrate']) { + return; + } + $output_label = ""; + if (array_key_exists('#output-label', $backend_options)) { + $output_label = $backend_options['#output-label']; + } + drush_set_error($data['error'], $data['message'], $output_label); +} + +/** + * Default options for backend_invoke commands. + */ +function _drush_backend_adjust_options($site_record, $command, $command_options, $backend_options) { + // By default, if the caller does not specify a value for 'output', but does + // specify 'integrate' === FALSE, then we will set output to FALSE. Otherwise we + // will allow it to default to TRUE. + if ((array_key_exists('integrate', $backend_options)) && ($backend_options['integrate'] === FALSE) && (!array_key_exists('output', $backend_options))) { + $backend_options['output'] = FALSE; + } + $has_site_specification = array_key_exists('root', $site_record) || array_key_exists('uri', $site_record); + $result = $backend_options + array( + 'method' => 'GET', + 'output' => TRUE, + 'log' => TRUE, + 'integrate' => TRUE, + 'backend' => TRUE, + 'dispatch-using-alias' => !$has_site_specification, + ); + // Convert '#integrate' et. al. into backend options + foreach ($command_options as $key => $value) { + if (substr($key,0,1) === '#') { + $result[substr($key,1)] = $value; + } + } + return $result; +} + +/** + * Execute a new local or remote command in a new process. + * + * n.b. Prefer drush_invoke_process() to this function. + * + * @param invocations + * An array of command records to exacute. Each record should contain: + * 'site': + * An array containing information used to generate the command. + * 'remote-host' + * Optional. A remote host to execute the drush command on. + * 'remote-user' + * Optional. Defaults to the current user. If you specify this, you can choose which module to send. + * 'ssh-options' + * Optional. Defaults to "-o PasswordAuthentication=no" + * '#env-vars' + * Optional. An associative array of environmental variables to prefix the Drush command with. + * 'path-aliases' + * Optional; contains paths to folders and executables useful to the command. + * '%drush-script' + * Optional. Defaults to the current drush.php file on the local machine, and + * to simply 'drush' (the drush script in the current PATH) on remote servers. + * You may also specify a different drush.php script explicitly. You will need + * to set this when calling drush on a remote server if 'drush' is not in the + * PATH on that machine. + * 'command': + * A defined drush command such as 'cron', 'status' or any of the available ones such as 'drush pm'. + * 'args': + * An array of arguments for the command. + * 'options' + * Optional. An array containing options to pass to the remote script. + * Array items with a numeric key are treated as optional arguments to the + * command. + * 'backend-options': + * Optional. Additional parameters that control the operation of the invoke. + * 'method' + * Optional. Defaults to 'GET'. + * If this parameter is set to 'POST', the $data array will be passed + * to the script being called as a JSON encoded string over the STDIN + * pipe of that process. This is preferable if you have to pass + * sensitive data such as passwords and the like. + * For any other value, the $data array will be collapsed down into a + * set of command line options to the script. + * 'integrate' + * Optional. Defaults to TRUE. + * If TRUE, any error statuses will be integrated into the current + * process. This might not be what you want, if you are writing a + * command that operates on multiple sites. + * 'log' + * Optional. Defaults to TRUE. + * If TRUE, any log messages will be integrated into the current + * process. + * 'output' + * Optional. Defaults to TRUE. + * If TRUE, output from the command will be synchronously printed to + * stdout. + * 'drush-script' + * Optional. Defaults to the current drush.php file on the local + * machine, and to simply 'drush' (the drush script in the current + * PATH) on remote servers. You may also specify a different drush.php + * script explicitly. You will need to set this when calling drush on + * a remote server if 'drush' is not in the PATH on that machine. + * 'dispatch-using-alias' + * Optional. Defaults to FALSE. + * If specified as a non-empty value the drush command will be + * dispatched using the alias name on the command line, instead of + * the options from the alias being added to the command line + * automatically. + * @param common_options + * Optional. Merged in with the options for each invocation. + * @param backend_options + * Optional. Merged in with the backend options for each invocation. + * @param default_command + * Optional. Used as the 'command' for any invocation that does not + * define a command explicitly. + * @param default_site + * Optional. Used as the 'site' for any invocation that does not + * define a site explicitly. + * @param context + * Optional. Passed in to proc_open if provided. + * + * @return + * If the command could not be completed successfully, FALSE. + * If the command was completed, this will return an associative array containing the data from drush_backend_output(). + */ +function drush_backend_invoke_concurrent($invocations, $common_options = array(), $common_backend_options = array(), $default_command = NULL, $default_site = NULL, $context = NULL) { + $index = 0; + + // Slice and dice our options in preparation to build a command string + $invocation_options = array(); + foreach ($invocations as $invocation) { + $site_record = isset($invocation['site']) ? $invocation['site'] : $default_site; + // NULL is a synonym to '@self', although the latter is preferred. + if (!isset($site_record)) { + $site_record = '@self'; + } + // If the first parameter is not a site alias record, + // then presume it is an alias name, and try to look up + // the alias record. + if (!is_array($site_record)) { + $site_record = drush_sitealias_get_record($site_record); + } + $command = isset($invocation['command']) ? $invocation['command'] : $default_command; + $args = isset($invocation['args']) ? $invocation['args'] : array(); + $command_options = isset($invocation['options']) ? $invocation['options'] : array(); + $backend_options = isset($invocation['backend-options']) ? $invocation['backend-options'] : array(); + // If $backend_options is passed in as a bool, interpret that as the value for 'integrate' + if (!is_array($common_backend_options)) { + $integrate = (bool)$common_backend_options; + $common_backend_options = array('integrate' => $integrate); + } + + $command_options += $common_options; + $backend_options += $common_backend_options; + + $backend_options = _drush_backend_adjust_options($site_record, $command, $command_options, $backend_options); + $backend_options += array( + 'drush-script' => NULL, + ); + + // Insure that contexts such as DRUSH_SIMULATE and NO_COLOR are included. + $command_options += _drush_backend_get_global_contexts($site_record); + + // Add in command-specific options as well + $command_options += drush_command_get_command_specific_options($site_record, $command); + + // If the caller has requested it, don't pull the options from the alias + // into the command line, but use the alias name for dispatching. + if (!empty($backend_options['dispatch-using-alias']) && isset($site_record['#name'])) { + list($post_options, $commandline_options, $drush_global_options) = _drush_backend_classify_options(array(), $command_options, $backend_options); + $site_record_to_dispatch = '@' . ltrim($site_record['#name'], '@'); + } + else { + list($post_options, $commandline_options, $drush_global_options) = _drush_backend_classify_options($site_record, $command_options, $backend_options); + $site_record_to_dispatch = ''; + } + if (array_key_exists('backend-simulate', $backend_options)) { + $drush_global_options['simulate'] = TRUE; + } + $site_record += array('path-aliases' => array(), '#env-vars' => array()); + $site_record['path-aliases'] += array( + '%drush-script' => $backend_options['drush-script'], + ); + + $site = (array_key_exists('#name', $site_record) && !array_key_exists($site_record['#name'], $invocation_options)) ? $site_record['#name'] : $index++; + $invocation_options[$site] = array( + 'site-record' => $site_record, + 'site-record-to-dispatch' => $site_record_to_dispatch, + 'command' => $command, + 'args' => $args, + 'post-options' => $post_options, + 'drush-global-options' => $drush_global_options, + 'commandline-options' => $commandline_options, + 'command-options' => $command_options, + 'backend-options' => $backend_options, + ); + } + + // Calculate the length of the longest output label + $max_name_length = 0; + $label_separator = ''; + if (!array_key_exists('no-label', $common_options) && (count($invocation_options) > 1)) { + $label_separator = array_key_exists('label-separator', $common_options) ? $common_options['label-separator'] : ' >> '; + foreach ($invocation_options as $site => $item) { + $backend_options = $item['backend-options']; + if (!array_key_exists('#output-label', $backend_options)) { + if (is_numeric($site)) { + $backend_options['#output-label'] = ' * [@self.' . $site; + $label_separator = '] '; + } + else { + $backend_options['#output-label'] = $site; + } + $invocation_options[$site]['backend-options']['#output-label'] = $backend_options['#output-label']; + } + $name_len = strlen($backend_options['#output-label']); + if ($name_len > $max_name_length) { + $max_name_length = $name_len; + } + if (array_key_exists('#label-separator', $backend_options)) { + $label_separator = $backend_options['#label-separator']; + } + } + } + // Now pad out the output labels and add the label separator. + $reserve_margin = $max_name_length + strlen($label_separator); + foreach ($invocation_options as $site => $item) { + $backend_options = $item['backend-options'] + array('#output-label' => ''); + $invocation_options[$site]['backend-options']['#output-label'] = str_pad($backend_options['#output-label'], $max_name_length, " ") . $label_separator; + if ($reserve_margin) { + $invocation_options[$site]['drush-global-options']['reserve-margin'] = $reserve_margin; + } + } + + // Now take our prepared options and generate the command strings + $cmds = array(); + foreach ($invocation_options as $site => $item) { + $site_record = $item['site-record']; + $site_record_to_dispatch = $item['site-record-to-dispatch']; + $command = $item['command']; + $args = $item['args']; + $post_options = $item['post-options']; + $commandline_options = $item['commandline-options']; + $command_options = $item['command-options']; + $drush_global_options = $item['drush-global-options']; + $backend_options = $item['backend-options']; + $is_remote = array_key_exists('remote-host', $site_record); + $is_different_site = + $is_remote || + (isset($site_record['root']) && ($site_record['root'] != drush_get_context('DRUSH_DRUPAL_ROOT'))) || + (isset($site_record['uri']) && ($site_record['uri'] != drush_get_context('DRUSH_SELECTED_URI'))); + $os = drush_os($site_record); + // If the caller did not pass in a specific path to drush, then we will + // use a default value. For commands that are being executed on the same + // machine, we will use DRUSH_COMMAND, which is the path to the drush.php + // that is running right now. For remote commands, we will run a wrapper + // script instead of drush.php called drush. + $drush_path = $site_record['path-aliases']['%drush-script']; + if (!$drush_path && !$is_remote && $is_different_site) { + $drush_path = find_wrapper_or_launcher($site_record['root']); + } + $env_vars = $site_record['#env-vars']; + $php = array_key_exists('php', $site_record) ? $site_record['php'] : (array_key_exists('php', $command_options) ? $command_options['php'] : NULL); + $drush_command_path = drush_build_drush_command($drush_path, $php, $os, $is_remote, $env_vars); + $cmd = _drush_backend_generate_command($site_record, $drush_command_path . " " . _drush_backend_argument_string($drush_global_options, $os) . " " . $site_record_to_dispatch . " " . $command, $args, $commandline_options, $backend_options) . ' 2>&1'; + $cmds[$site] = array( + 'cmd' => $cmd, + 'post-options' => $post_options, + 'backend-options' => $backend_options, + ); + } + + return _drush_backend_invoke($cmds, $common_backend_options, $context); +} + +/** + * Find all of the drush contexts that are used to cache global values and + * return them in an associative array. + */ +function _drush_backend_get_global_contexts($site_record) { + $result = array(); + $global_option_list = drush_get_global_options(FALSE); + foreach ($global_option_list as $global_key => $global_metadata) { + if (is_array($global_metadata)) { + $value = ''; + if (!array_key_exists('never-propagate', $global_metadata)) { + if ((array_key_exists('propagate', $global_metadata))) { + $value = drush_get_option($global_key); + } + elseif ((array_key_exists('propagate-cli-value', $global_metadata))) { + $value = drush_get_option($global_key, '', 'cli'); + } + elseif ((array_key_exists('context', $global_metadata))) { + // If the context is declared to be a 'local-context-only', + // then only put it in if this is a local dispatch. + if (!array_key_exists('local-context-only', $global_metadata) || !array_key_exists('remote-host', $site_record)) { + $value = drush_get_context($global_metadata['context'], array()); + } + } + if (!empty($value) || ($value === '0')) { + $result[$global_key] = $value; + } + } + } + } + return $result; +} + +/** + * Take all of the values in the $command_options array, and place each of + * them into one of the following result arrays: + * + * - $post_options: options to be encoded as JSON and written to the + * standard input of the drush subprocess being executed. + * - $commandline_options: options to be placed on the command line of the drush + * subprocess. + * - $drush_global_options: the drush global options also go on the command + * line, but appear before the drush command name rather than after it. + * + * Also, this function may modify $backend_options. + */ +function _drush_backend_classify_options($site_record, $command_options, &$backend_options) { + // In 'POST' mode (the default, remove everything (except the items marked 'never-post' + // in the global option list) from the commandline options and put them into the post options. + // The post options will be json-encoded and sent to the command via stdin + $global_option_list = drush_get_global_options(FALSE); // These should be in the command line. + $additional_global_options = array(); + if (array_key_exists('additional-global-options', $backend_options)) { + $additional_global_options = $backend_options['additional-global-options']; + $command_options += $additional_global_options; + } + $method_post = ((!array_key_exists('method', $backend_options)) || ($backend_options['method'] == 'POST')); + $post_options = array(); + $commandline_options = array(); + $drush_global_options = array(); + $drush_local_options = array(); + $additional_backend_options = array(); + foreach ($site_record as $key => $value) { + if (!in_array($key, drush_sitealias_site_selection_keys())) { + if ($key[0] == '#') { + $backend_options[$key] = $value; + } + if (!isset($command_options[$key])) { + if (array_key_exists($key, $global_option_list)) { + $command_options[$key] = $value; + } + } + } + } + if (array_key_exists('drush-local-options', $backend_options)) { + $drush_local_options = $backend_options['drush-local-options']; + $command_options += $drush_local_options; + } + if (!empty($backend_options['backend']) && empty($backend_options['interactive']) && empty($backend_options['fork'])) { + $drush_global_options['backend'] = '2'; + } + foreach ($command_options as $key => $value) { + $global = array_key_exists($key, $global_option_list); + $propagate = TRUE; + $special = FALSE; + if ($global) { + $propagate = (!array_key_exists('never-propagate', $global_option_list[$key])); + $special = (array_key_exists('never-post', $global_option_list[$key])); + if ($propagate) { + // We will allow 'merge-pathlist' contexts to be propogated. Right now + // these are all 'local-context-only' options; if we allowed them to + // propogate remotely, then we would need to get the right path separator + // for the remote machine. + if (is_array($value) && array_key_exists('merge-pathlist', $global_option_list[$key])) { + $value = implode(PATH_SEPARATOR, $value); + } + } + } + // Just remove options that are designated as non-propagating + if ($propagate === TRUE) { + // In METHOD POST, move command options to post options + if ($method_post && ($special === FALSE)) { + $post_options[$key] = $value; + } + // In METHOD GET, ignore options with array values + elseif (!is_array($value)) { + if ($global || array_key_exists($key, $additional_global_options)) { + $drush_global_options[$key] = $value; + } + else { + $commandline_options[$key] = $value; + } + } + } + } + return array($post_options, $commandline_options, $drush_global_options, $additional_backend_options); +} + +/** + * Create a new pipe with proc_open, and attempt to parse the output. + * + * We use proc_open instead of exec or others because proc_open is best + * for doing bi-directional pipes, and we need to pass data over STDIN + * to the remote script. + * + * Exec also seems to exhibit some strangeness in keeping the returned + * data intact, in that it modifies the newline characters. + * + * @param cmd + * The complete command line call to use. + * @param post_options + * An associative array to json-encode and pass to the remote script on stdin. + * @param backend_options + * Options for the invocation. + * + * @return + * If no commands were executed, FALSE. + * + * If one command was executed, this will return an associative array containing + * the data from drush_backend_output(). The result code is stored + * in $result['error_status'] (0 == no error). + * + * If multiple commands were executed, this will return an associative array + * containing one item, 'concurrent', which will contain a list of the different + * backend invoke results from each concurrent command. + */ +function _drush_backend_invoke($cmds, $common_backend_options = array(), $context = NULL) { + if (drush_get_context('DRUSH_SIMULATE') && !array_key_exists('override-simulated', $common_backend_options) && !array_key_exists('backend-simulate', $common_backend_options)) { + foreach ($cmds as $cmd) { + drush_print(dt('Simulating backend invoke: !cmd', array('!cmd' => $cmd['cmd']))); + } + return FALSE; + } + foreach ($cmds as $cmd) { + drush_log(dt('Backend invoke: !cmd', array('!cmd' => $cmd['cmd'])), 'command'); + } + if (!empty($common_backend_options['interactive']) || !empty($common_backend_options['fork'])) { + foreach ($cmds as $cmd) { + $exec_cmd = $cmd['cmd']; + if (array_key_exists('fork', $common_backend_options)) { + $exec_cmd .= ' --quiet &'; + } + + $result_code = drush_shell_proc_open($exec_cmd); + $ret = array('error_status' => $result_code); + } + } + else { + $process_limit = drush_get_option_override($common_backend_options, 'concurrency', 1); + $procs = _drush_backend_proc_open($cmds, $process_limit, $context); + $procs = is_array($procs) ? $procs : array($procs); + + $ret = array(); + foreach ($procs as $site => $proc) { + if (($proc['code'] == DRUSH_APPLICATION_ERROR) && isset($common_backend_options['integrate'])) { + drush_set_error('DRUSH_APPLICATION_ERROR', dt("The external command could not be executed due to an application error.")); + } + + if ($proc['output']) { + $values = drush_backend_parse_output($proc['output'], $proc['backend-options'], $proc['outputted']); + if (is_array($values)) { + $values['site'] = $site; + } + elseif (!empty($common_backend_options['convert-stdout-to-backend'])) { + $values = [ + 'site' => $site, + 'output' => $proc['output'], + 'error_status' => $proc['code'], + 'log' => [], + ]; + } + else { + return drush_set_error('DRUSH_FRAMEWORK_ERROR', dt("The command could not be executed successfully (returned: !return, code: !code)", array("!return" => $proc['output'], "!code" => $proc['code']))); + } + if (empty($ret)) { + $ret = $values; + } + elseif (!array_key_exists('concurrent', $ret)) { + $ret = array('concurrent' => array($ret, $values)); + } + else { + $ret['concurrent'][] = $values; + } + } + } + } + return empty($ret) ? FALSE : $ret; +} + +/** + * Helper function that generates an anonymous site alias specification for + * the given parameters. + */ +function drush_backend_generate_sitealias($backend_options) { + // Ensure default values. + $backend_options += array( + 'remote-host' => NULL, + 'remote-user' => NULL, + 'ssh-options' => NULL, + 'drush-script' => NULL, + 'env-vars' => NULL + ); + return array( + 'remote-host' => $backend_options['remote-host'], + 'remote-user' => $backend_options['remote-user'], + 'ssh-options' => $backend_options['ssh-options'], + '#env-vars' => $backend_options['env-vars'], + 'path-aliases' => array( + '%drush-script' => $backend_options['drush-script'], + ), + ); +} + +/** + * Generate a command to execute. + * + * @param site_record + * An array containing information used to generate the command. + * 'remote-host' + * Optional. A remote host to execute the drush command on. + * 'remote-user' + * Optional. Defaults to the current user. If you specify this, you can choose which module to send. + * 'ssh-options' + * Optional. Defaults to "-o PasswordAuthentication=no" + * '#env-vars' + * Optional. An associative array of environmental variables to prefix the Drush command with. + * 'path-aliases' + * Optional; contains paths to folders and executables useful to the command. + * '%drush-script' + * Optional. Defaults to the current drush.php file on the local machine, and + * to simply 'drush' (the drush script in the current PATH) on remote servers. + * You may also specify a different drush.php script explicitly. You will need + * to set this when calling drush on a remote server if 'drush' is not in the + * PATH on that machine. + * @param command + * A defined drush command such as 'cron', 'status' or any of the available ones such as 'drush pm'. + * @param args + * An array of arguments for the command. + * @param command_options + * Optional. An array containing options to pass to the remote script. + * Array items with a numeric key are treated as optional arguments to the + * command. This parameter is a reference, as any options that have been + * represented as either an option, or an argument will be removed. This + * allows you to pass the left over options as a JSON encoded string, + * without duplicating data. + * @param backend_options + * Optional. An array of options for the invocation. + * @see drush_backend_invoke for documentation. + * + * @return + * A text string representing a fully escaped command. + */ +function _drush_backend_generate_command($site_record, $command, $args = array(), $command_options = array(), $backend_options = array()) { + $site_record += array( + 'remote-host' => NULL, + 'remote-user' => NULL, + 'ssh-options' => NULL, + 'path-aliases' => array(), + ); + $backend_options += array( + '#tty' => FALSE, + ); + + $hostname = $site_record['remote-host']; + $username = $site_record['remote-user']; + $ssh_options = $site_record['ssh-options']; + $os = drush_os($site_record); + + if (drush_is_local_host($hostname)) { + $hostname = null; + } + + foreach ($command_options as $key => $arg) { + if (is_numeric($key)) { + $args[] = $arg; + unset($command_options[$key]); + } + } + + $cmd[] = $command; + foreach ($args as $arg) { + $cmd[] = drush_escapeshellarg($arg, $os); + } + $option_str = _drush_backend_argument_string($command_options, $os); + if (!empty($option_str)) { + $cmd[] = " " . $option_str; + } + $command = implode(' ', array_filter($cmd, 'strlen')); + if (isset($hostname)) { + $username = (isset($username)) ? drush_escapeshellarg($username, "LOCAL") . "@" : ''; + $ssh_options = $site_record['ssh-options']; + $ssh_options = (isset($ssh_options)) ? $ssh_options : drush_get_option('ssh-options', "-o PasswordAuthentication=no"); + + $ssh_cmd[] = "ssh"; + $ssh_cmd[] = $ssh_options; + if ($backend_options['#tty']) { + $ssh_cmd[] = '-t'; + } + $ssh_cmd[] = $username . drush_escapeshellarg($hostname, "LOCAL"); + $ssh_cmd[] = drush_escapeshellarg($command . ' 2>&1', "LOCAL"); + + // Remove NULLs and separate with spaces + $command = implode(' ', array_filter($ssh_cmd, 'strlen')); + } + + return $command; +} + +/** + * Map the options to a string containing all the possible arguments and options. + * + * @param data + * Optional. An array containing options to pass to the remote script. + * Array items with a numeric key are treated as optional arguments to the command. + * This parameter is a reference, as any options that have been represented as either an option, or an argument will be removed. + * This allows you to pass the left over options as a JSON encoded string, without duplicating data. + * @param method + * Optional. Defaults to 'GET'. + * If this parameter is set to 'POST', the $data array will be passed to the script being called as a JSON encoded string over + * the STDIN pipe of that process. This is preferable if you have to pass sensitive data such as passwords and the like. + * For any other value, the $data array will be collapsed down into a set of command line options to the script. + * @return + * A properly formatted and escaped set of arguments and options to append to the drush.php shell command. + */ +function _drush_backend_argument_string($data, $os = NULL) { + $options = array(); + + foreach ($data as $key => $value) { + if (!is_array($value) && !is_object($value) && isset($value)) { + if (substr($key,0,1) != '#') { + $options[$key] = $value; + } + } + } + + $option_str = ''; + foreach ($options as $key => $value) { + $option_str .= _drush_escape_option($key, $value, $os); + } + + return $option_str; +} + +/** + * Return a properly formatted and escaped command line option + * + * @param key + * The name of the option. + * @param value + * The value of the option. + * + * @return + * If the value is set to TRUE, this function will return " --key" + * In other cases it will return " --key='value'" + */ +function _drush_escape_option($key, $value = TRUE, $os = NULL) { + if ($value !== TRUE) { + $option_str = " --$key=" . drush_escapeshellarg($value, $os); + } + else { + $option_str = " --$key"; + } + return $option_str; +} + +/** + * Read options fron STDIN during POST requests. + * + * This function will read any text from the STDIN pipe, + * and attempts to generate an associative array if valid + * JSON was received. + * + * @return + * An associative array of options, if successfull. Otherwise FALSE. + */ +function _drush_backend_get_stdin() { + $fp = fopen('php://stdin', 'r'); + // Windows workaround: we cannot count on stream_get_contents to + // return if STDIN is reading from the keyboard. We will therefore + // check to see if there are already characters waiting on the + // stream (as there always should be, if this is a backend call), + // and if there are not, then we will exit. + // This code prevents drush from hanging forever when called with + // --backend from the commandline; however, overall it is still + // a futile effort, as it does not seem that backend invoke can + // successfully write data to that this function can read, + // so the argument list and command always come out empty. :( + // Perhaps stream_get_contents is the problem, and we should use + // the technique described here: + // http://bugs.php.net/bug.php?id=30154 + // n.b. the code in that issue passes '0' for the timeout in stream_select + // in a loop, which is not recommended. + // Note that the following DOES work: + // drush ev 'print(json_encode(array("test" => "XYZZY")));' | drush status --backend + // So, redirecting input is okay, it is just the proc_open that is a problem. + if (drush_is_windows()) { + // Note that stream_select uses reference parameters, so we need variables (can't pass a constant NULL) + $read = array($fp); + $write = NULL; + $except = NULL; + // Question: might we need to wait a bit for STDIN to be ready, + // even if the process that called us immediately writes our parameters? + // Passing '100' for the timeout here causes us to hang indefinitely + // when called from the shell. + $changed_streams = stream_select($read, $write, $except, 0); + // Return on error (FALSE) or no changed streams (0). + // Oh, according to http://php.net/manual/en/function.stream-select.php, + // stream_select will return FALSE for streams returned by proc_open. + // That is not applicable to us, is it? Our stream is connected to a stream + // created by proc_open, but is not a stream returned by proc_open. + if ($changed_streams < 1) { + return FALSE; + } + } + stream_set_blocking($fp, FALSE); + $string = stream_get_contents($fp); + fclose($fp); + if (trim($string)) { + return json_decode($string, TRUE); + } + return FALSE; +} diff --git a/vendor/drush/drush/includes/batch.inc b/vendor/drush/drush/includes/batch.inc new file mode 100644 index 0000000000..62dd67365f --- /dev/null +++ b/vendor/drush/drush/includes/batch.inc @@ -0,0 +1,100 @@ +getCurrentUserAsSingle()->id(); + + drush_include_engine('drupal', 'batch'); + _drush_backend_batch_process($command, $args, $options); +} + +/** + * Process sets from the specified batch. + * + * This function is called by the worker process that is spawned by the + * drush_backend_batch_process function. + * + * The command called needs to call this function after it's special bootstrap + * requirements have been taken care of. + * + * @param int $id + * The batch ID of the batch being processed. + */ +function drush_batch_command($id) { + include_once(DRUSH_DRUPAL_CORE . '/includes/batch.inc'); + drush_include_engine('drupal', 'batch'); + _drush_batch_command($id); +} diff --git a/vendor/drush/drush/includes/bootstrap.inc b/vendor/drush/drush/includes/bootstrap.inc new file mode 100644 index 0000000000..9b08db8acb --- /dev/null +++ b/vendor/drush/drush/includes/bootstrap.inc @@ -0,0 +1,614 @@ +valid_root($path)) { + return $candidate; + } + } + return NULL; +} + +/** + * Check to see if there is a bootstrap class available + * at the specified location; if there is, load it. + */ +function drush_load_bootstrap_commandfile_at_path($path) { + static $paths = array(); + + if (!empty($path) && (!array_key_exists($path, $paths))) { + $paths[$path] = TRUE; + // Check to see if we have any bootstrap classes in this location. + $bootstrap_class_dir = $path . '/drush/bootstrap'; + if (is_dir($bootstrap_class_dir)) { + _drush_add_commandfiles(array($bootstrap_class_dir), DRUSH_BOOTSTRAP_NONE); + } + } +} + +/** + * Select the bootstrap class to use. If this is called multiple + * times, the bootstrap class returned might change on subsequent + * calls, if the root directory changes. Once the bootstrap object + * starts changing the state of the system, however, it will + * be 'latched', and further calls to drush_select_bootstrap_class() + * will always return the same object. + */ +function drush_select_bootstrap_class() { + $root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT'); + + // Once we have selected a Drupal root, we will reduce our bootstrap + // candidates down to just the one used to select this site root. + $bootstrap = drush_bootstrap_class_for_root($root); + // If we have not found a bootstrap class by this point, + // then take the last one and use it. This should be our + // default bootstrap class. The default bootstrap class + // should pass through all calls without doing anything that + // changes state in a CMS-specific way. + if ($bootstrap == NULL) { + $candidates = drush_get_bootstrap_candidates(); + $bootstrap = array_pop($candidates); + } + + return $bootstrap; +} + +/** + * Don't allow the bootstrap object to change once we start bootstrapping + */ +function drush_latch_bootstrap_object($bootstrap) { + drush_set_context('DRUSH_BOOTSTRAP_OBJECT', $bootstrap); +} + +/** + * Get the appropriate bootstrap object. We'll search for a new + * bootstrap object every time someone asks for one until we start + * bootstrapping; then we'll returned the same cached one every time. + * + * @return \Drush\Boot\Boot + */ +function drush_get_bootstrap_object() { + $bootstrap = drush_get_context('DRUSH_BOOTSTRAP_OBJECT', FALSE); + if (!$bootstrap) { + $bootstrap = drush_select_bootstrap_class(); + } + return $bootstrap; +} + +/** + * Find the URI that has been selected by the cwd + * if it was not previously set via the --uri / -l option + */ +function _drush_bootstrap_selected_uri() { + $uri = drush_get_context('DRUSH_SELECTED_URI'); + if (empty($uri)) { + $site_path = drush_site_path(); + $elements = explode('/', $site_path); + $current = array_pop($elements); + if (!$current) { + $current = 'default'; + } + $uri = 'http://'. $current; + $uri = drush_set_context('DRUSH_SELECTED_URI', $uri); + drush_sitealias_create_self_alias(); + } + + return $uri; +} + +/** + * Helper function to store any context settings that are being validated. + */ +function drush_bootstrap_value($context, $value = null) { + $values =& drush_get_context('DRUSH_BOOTSTRAP_VALUES', array()); + + if (isset($value)) { + $values[$context] = $value; + } + + if (array_key_exists($context, $values)) { + return $values[$context]; + } + + return null; +} + +/** + * Returns an array that determines what bootstrap phases + * are necessary to bootstrap the CMS. + * + * @param bool $function_names + * (optional) If TRUE, return an array of method names index by their + * corresponding phase values. Otherwise return an array of phase values. + * + * @return array + * + * @see \Drush\Boot\Boot::bootstrap_phases() + */ +function _drush_bootstrap_phases($function_names = FALSE) { + $result = array(); + + if ($bootstrap = drush_get_bootstrap_object()) { + $result = $bootstrap->bootstrap_phases(); + if (!$function_names) { + $result = array_keys($result); + } + } + return $result; +} + +/** + * Bootstrap Drush to the desired phase. + * + * This function will sequentially bootstrap each + * lower phase up to the phase that has been requested. + * + * @param int $phase + * The bootstrap phase to bootstrap to. + * @param int $phase_max + * (optional) The maximum level to boot to. This does not have a use in this + * function itself but can be useful for other code called from within this + * function, to know if e.g. a caller is in the process of booting to the + * specified level. If specified, it should never be lower than $phase. + * + * @return bool + * TRUE if the specified bootstrap phase has completed. + * + * @see \Drush\Boot\Boot::bootstrap_phases() + */ +function drush_bootstrap($phase, $phase_max = FALSE) { + $bootstrap = drush_get_bootstrap_object(); + $phases = _drush_bootstrap_phases(TRUE); + $result = TRUE; + + // If the requested phase does not exist in the list of available + // phases, it means that the command requires bootstrap to a certain + // level, but no site root could be found. + if (!isset($phases[$phase])) { + $result = drush_bootstrap_error('DRUSH_NO_SITE', dt("We could not find an applicable site for that command.")); + } + + // Once we start bootstrapping past the DRUSH_BOOTSTRAP_DRUSH phase, we + // will latch the bootstrap object, and prevent it from changing. + if ($phase > DRUSH_BOOTSTRAP_DRUSH) { + drush_latch_bootstrap_object($bootstrap); + } + + drush_set_context('DRUSH_BOOTSTRAPPING', TRUE); + foreach ($phases as $phase_index => $current_phase) { + $bootstrapped_phase = drush_get_context('DRUSH_BOOTSTRAP_PHASE', -1); + if ($phase_index > $phase) { + break; + } + if ($phase_index > $bootstrapped_phase) { + if ($result = drush_bootstrap_validate($phase_index)) { + if (method_exists($bootstrap, $current_phase) && !drush_get_error()) { + drush_log(dt("Drush bootstrap phase : !function()", array('!function' => $current_phase)), LogLevel::BOOTSTRAP); + $bootstrap->{$current_phase}(); + + // Reset commandfile cache and find any new command files that are available during this bootstrap phase. + drush_get_commands(TRUE); + _drush_find_commandfiles($phase_index, $phase_max); + } + drush_set_context('DRUSH_BOOTSTRAP_PHASE', $phase_index); + } + } + } + drush_set_context('DRUSH_BOOTSTRAPPING', FALSE); + if (!$result || drush_get_error()) { + $errors = drush_get_context('DRUSH_BOOTSTRAP_ERRORS', array()); + foreach ($errors as $code => $message) { + drush_set_error($code, $message); + } + } + return !drush_get_error(); +} + +/** + * Determine whether a given bootstrap phase has been completed + * + * This function name has a typo which makes me laugh so we choose not to + * fix it. Take a deep breath, and smile. See + * http://en.wikipedia.org/wiki/HTTP_referer + * + * + * @param int $phase + * The bootstrap phase to test + * + * @return bool + * TRUE if the specified bootstrap phase has completed. + */ +function drush_has_boostrapped($phase) { + $phase_index = drush_get_context('DRUSH_BOOTSTRAP_PHASE'); + + return isset($phase_index) && ($phase_index >= $phase); +} + +/** + * Validate whether a bootstrap phase can be reached. + * + * This function will validate the settings that will be used + * during the actual bootstrap process, and allow commands to + * progressively bootstrap to the highest level that can be reached. + * + * This function will only run the validation function once, and + * store the result from that execution in a local static. This avoids + * validating phases multiple times. + * + * @param int $phase + * The bootstrap phase to validate to. + * + * @return bool + * TRUE if bootstrap is possible, FALSE if the validation failed. + * + * @see \Drush\Boot\Boot::bootstrap_phases() + */ +function drush_bootstrap_validate($phase) { + $bootstrap = drush_get_bootstrap_object(); + $phases = _drush_bootstrap_phases(TRUE); + static $result_cache = array(); + + if (!array_key_exists($phase, $result_cache)) { + drush_set_context('DRUSH_BOOTSTRAP_ERRORS', array()); + drush_set_context('DRUSH_BOOTSTRAP_VALUES', array()); + + foreach ($phases as $phase_index => $current_phase) { + $validated_phase = drush_get_context('DRUSH_BOOTSTRAP_VALIDATION_PHASE', -1); + if ($phase_index > $phase) { + break; + } + if ($phase_index > $validated_phase) { + $current_phase .= '_validate'; + if (method_exists($bootstrap, $current_phase)) { + $result_cache[$phase_index] = $bootstrap->{$current_phase}(); + } + else { + $result_cache[$phase_index] = TRUE; + } + drush_set_context('DRUSH_BOOTSTRAP_VALIDATION_PHASE', $phase_index); + } + } + } + return $result_cache[$phase]; +} + +/** + * Bootstrap to the specified phase. + * + * @param int $max_phase_index + * Only attempt bootstrap to the specified level. + * + * @return bool + * TRUE if the specified bootstrap phase has completed. + */ +function drush_bootstrap_to_phase($max_phase_index) { + if ($max_phase_index == DRUSH_BOOTSTRAP_MAX) { + // Bootstrap as far as we can without throwing an error, but log for + // debugging purposes. + drush_log(dt("Trying to bootstrap as far as we can."), 'debug'); + drush_bootstrap_max(); + return TRUE; + } + + drush_log(dt("Bootstrap to phase !phase.", array('!phase' => $max_phase_index)), LogLevel::BOOTSTRAP); + $phases = _drush_bootstrap_phases(); + $result = TRUE; + + // Try to bootstrap to the maximum possible level, without generating errors + foreach ($phases as $phase_index) { + if ($phase_index > $max_phase_index) { + // Stop trying, since we achieved what was specified. + break; + } + + if (drush_bootstrap_validate($phase_index)) { + if ($phase_index > drush_get_context('DRUSH_BOOTSTRAP_PHASE', DRUSH_BOOTSTRAP_NONE)) { + $result = drush_bootstrap($phase_index, $max_phase_index); + } + } + else { + $result = FALSE; + break; + } + } + + return $result; +} + +/** + * Bootstrap to the highest level possible, without triggering any errors. + * + * @param int $max_phase_index + * (optional) Only attempt bootstrap to the specified level. + * + * @return int + * The maximum phase to which we bootstrapped. + */ +function drush_bootstrap_max($max_phase_index = FALSE) { + $phases = _drush_bootstrap_phases(TRUE); + if (!$max_phase_index) { + $max_phase_index = count($phases); + } + + // Try to bootstrap to the maximum possible level, without generating errors. + foreach ($phases as $phase_index => $current_phase) { + if ($phase_index > $max_phase_index) { + // Stop trying, since we achieved what was specified. + break; + } + + if (drush_bootstrap_validate($phase_index)) { + if ($phase_index > drush_get_context('DRUSH_BOOTSTRAP_PHASE')) { + drush_bootstrap($phase_index, $max_phase_index); + } + } + else { + // drush_bootstrap_validate() only logs successful validations. For us, + // knowing what failed can also be important. + $previous = drush_get_context('DRUSH_BOOTSTRAP_PHASE'); + drush_log(dt("Bootstrap phase !function() failed to validate; continuing at !current().", array('!function' => $current_phase, '!current' => $phases[$previous])), 'debug'); + break; + } + } + + return drush_get_context('DRUSH_BOOTSTRAP_PHASE'); +} + +/** + * Bootstrap the specified site alias. The site alias must + * be a valid alias to a local site. + * + * @param $site_record + * The alias record for the given site alias. + * @see drush_sitealias_get_record(). + * @param $max_phase_index + * Only attempt bootstrap to the specified level. + * @returns TRUE if attempted to bootstrap, or FALSE + * if no bootstrap attempt was made. + */ +function drush_bootstrap_max_to_sitealias($site_record, $max_phase_index = NULL) { + if ((array_key_exists('root', $site_record) && !array_key_exists('remote-host', $site_record))) { + drush_sitealias_set_alias_context($site_record); + drush_bootstrap_max($max_phase_index); + return TRUE; + } + return FALSE; +} + +/** + * Helper function to collect any errors that occur during the bootstrap process. + * Always returns FALSE, for convenience. + */ +function drush_bootstrap_error($code, $message = null) { + $errors = drush_get_context('DRUSH_BOOTSTRAP_ERRORS'); + $errors[$code] = $message; + drush_set_context('DRUSH_BOOTSTRAP_ERRORS', $errors); + return FALSE; +} + +function _drush_bootstrap_output_prepare() { + // Note that as soon as we set the DRUSH_BACKEND context, we change + // the behavior of drush_log(). It is therefore important that we + // should not set this context until immediately before we call ob_start + // (i.e., in this function). + $backend = drush_set_context('DRUSH_BACKEND', drush_get_option('backend')); + $quiet = drush_get_context('DRUSH_QUIET'); + + if ($backend) { + // Load options passed as a JSON encoded string through STDIN. + $stdin_options = _drush_backend_get_stdin(); + if (is_array($stdin_options)) { + drush_set_context('stdin', $stdin_options); + } + // Add an output buffer handler to collect output/pass through backend + // packets. Using a chunksize of 2 ensures that each line is flushed + // straight away. + if ($quiet) { + // Pass through of backend packets, discard regular output. + ob_start('drush_backend_output_discard', 2); + } + else { + // Collect output. + ob_start('drush_backend_output_collect', 2); + } + } + + // In non-backend quiet mode we start buffering and discards it on command + // completion. + if ($quiet && !$backend) { + ob_start(); + } +} + +/** + * Used by a Drush extension to request that its Composer autoload + * files be loaded by Drush, if they have not already been. + * + * Usage: + * + * function mycommandfile_drush_init() { + * drush_autoload(__FILE__) + * } + * + */ +function drush_autoload($commandfile) { + $already_added = commandfiles_cache()->add($commandfile); + + $dir = dirname($commandfile); + $candidates = array("vendor/autoload.php", "../../../vendor/autoload.php"); + $drush_autoload_file = drush_get_context('DRUSH_VENDOR_PATH', ''); + + foreach ($candidates as $candidate) { + $autoload = $dir . '/' . $candidate; + if (file_exists($autoload) && (realpath($autoload) != $drush_autoload_file)) { + include $autoload; + } + } +} diff --git a/vendor/drush/drush/includes/cache.inc b/vendor/drush/drush/includes/cache.inc new file mode 100644 index 0000000000..8f62193832 --- /dev/null +++ b/vendor/drush/drush/includes/cache.inc @@ -0,0 +1,201 @@ +get($cid); + $mess = $ret ? "HIT" : "MISS"; + drush_log(dt("Cache !mess cid: !cid", array('!mess' => $mess, '!cid' => $cid)), LogLevel::DEBUG); + return $ret; +} + +/** + * Return data from the persistent cache when given an array of cache IDs. + * + * @param array $cids + * An array of cache IDs for the data to retrieve. This is passed by + * reference, and will have the IDs successfully returned from cache removed. + * @param string $bin + * The cache bin where the data is stored. + * + * @return + * An array of the items successfully returned from cache indexed by cid. + */ +function drush_cache_get_multiple(array &$cids, $bin = 'default') { + return _drush_cache_get_object($bin)->getMultiple($cids); +} + +/** + * Store data in the persistent cache. + * + * @param string $cid + * The cache ID of the data to store. + * + * @param $data + * The data to store in the cache. + * @param string $bin + * The cache bin to store the data in. + * @param $expire + * One of the following values: + * - DRUSH_CACHE_PERMANENT: Indicates that the item should never be removed + * unless explicitly told to using drush_cache_clear_all() with a cache ID. + * - DRUSH_CACHE_TEMPORARY: Indicates that the item should be removed at + * the next general cache wipe. + * - A Unix timestamp: Indicates that the item should be kept at least until + * the given time, after which it behaves like DRUSH_CACHE_TEMPORARY. + * + * @return bool + */ +function drush_cache_set($cid, $data, $bin = 'default', $expire = DRUSH_CACHE_PERMANENT) { + $ret = _drush_cache_get_object($bin)->set($cid, $data, $expire); + if ($ret) drush_log(dt("Cache SET cid: !cid", array('!cid' => $cid)), LogLevel::DEBUG); + return $ret; +} + +/** + * Expire data from the cache. + * + * If called without arguments, expirable entries will be cleared from all known + * cache bins. + * + * @param string $cid + * If set, the cache ID to delete. Otherwise, all cache entries that can + * expire are deleted. + * @param string $bin + * If set, the bin $bin to delete from. Mandatory + * argument if $cid is set. + * @param bool $wildcard + * If $wildcard is TRUE, cache IDs starting with $cid are deleted in + * addition to the exact cache ID specified by $cid. If $wildcard is + * TRUE and $cid is '*' then the entire bin $bin is emptied. + */ +function drush_cache_clear_all($cid = NULL, $bin = 'default', $wildcard = FALSE) { + if (!isset($cid) && !isset($bin)) { + foreach (drush_cache_get_bins() as $bin) { + _drush_cache_get_object($bin)->clear(); + } + return; + } + return _drush_cache_get_object($bin)->clear($cid, $wildcard); +} + +/** + * Check if a cache bin is empty. + * + * A cache bin is considered empty if it does not contain any valid data for any + * cache ID. + * + * @param $bin + * The cache bin to check. + * + * @return + * TRUE if the cache bin specified is empty. + */ +function _drush_cache_is_empty($bin) { + return _drush_cache_get_object($bin)->isEmpty(); +} + +/** + * Return drush cache bins and any bins added by hook_drush_flush_caches(). + */ +function drush_cache_get_bins() { + $drush = array('default'); + return array_merge(drush_command_invoke_all('drush_flush_caches'), $drush); +} + +/** + * Create a cache id from a given prefix, contexts, and additional parameters. + * + * @param prefix + * A human readable cid prefix that will not be hashed. + * @param contexts + * Array of drush contexts that will be used to build a unique hash. + * @param params + * Array of any addition parameters to be hashed. + * + * @return + * A cache id string. + */ +function drush_get_cid($prefix, $contexts = array(), $params = array()) { + $cid = array(); + + foreach ($contexts as $context) { + $c = drush_get_context($context); + if (!empty($c)) { + $cid[] = is_scalar($c) ? $c : serialize($c); + } + } + + foreach ($params as $param) { + $cid[] = $param; + } + + return DRUSH_VERSION . '-' . $prefix . '-' . md5(implode("", $cid)); +} diff --git a/vendor/drush/drush/includes/command.inc b/vendor/drush/drush/includes/command.inc new file mode 100644 index 0000000000..6440b94b99 --- /dev/null +++ b/vendor/drush/drush/includes/command.inc @@ -0,0 +1,2017 @@ + $command))); + } +} + +/** + * Invoke a command in a new process, targeting the site specified by + * the provided site alias record. + * + * @param array $site_alias_record + * The site record to execute the command on. Use '@self' to run on the current site. + * @param string $command_name + * The command to invoke. + * @param array $commandline_args + * The arguments to pass to the command. + * @param array $commandline_options + * The options (e.g. --select) to provide to the command. + * @param mixed $backend_options + * TRUE - integrate errors + * FALSE - do not integrate errors + * array - @see drush_backend_invoke_concurrent + * There are also several options that _only_ work when set in + * this parameter. They include: + * 'invoke-multiple' + * If $site_alias_record represents a single site, then 'invoke-multiple' + * will cause the _same_ command with the _same_ arguments and options + * to be invoked concurrently (e.g. for running concurrent batch processes). + * 'concurrency' + * Limits the number of concurrent processes that will run at the same time. + * Defaults to '4'. + * 'override-simulated' + * Forces the command to run, even in 'simulated' mode. Useful for + * commands that do not change any state on the machine, e.g. to fetch + * database information for sql-sync via sql-conf. + * 'interactive' + * Overrides the backend invoke process to run commands interactively. + * 'fork' + * Overrides the backend invoke process to run non blocking commands in + * the background. Forks a new process by adding a '&' at the end of the + * command. The calling process does not receive any output from the child + * process. The fork option is used to spawn a process that outlives its + * parent. + * + * @return + * If the command could not be completed successfully, FALSE. + * If the command was completed, this will return an associative + * array containing the results of the API call. + * @see drush_backend_get_result() + * + * Do not change the signature of this function! drush_invoke_process + * is one of the key Drush APIs. See http://drupal.org/node/1152908 + */ +function drush_invoke_process($site_alias_record, $command_name, $commandline_args = array(), $commandline_options = array(), $backend_options = TRUE) { + if (is_array($site_alias_record) && array_key_exists('site-list', $site_alias_record)) { + list($site_alias_records, $not_found) = drush_sitealias_resolve_sitespecs($site_alias_record['site-list']); + if (!empty($not_found)) { + drush_log(dt("Not found: @list", array("@list" => implode(', ', $not_found))), LogLevel::WARNING); + return FALSE; + } + $site_alias_records = drush_sitealias_simplify_names($site_alias_records); + foreach ($site_alias_records as $alias_name => $alias_record) { + $invocations[] = array( + 'site' => $alias_record, + 'command' => $command_name, + 'args' => $commandline_args, + ); + } + } + else { + $invocations[] = array( + 'site' => $site_alias_record, + 'command' => $command_name, + 'args' => $commandline_args); + $invoke_multiple = drush_get_option_override($backend_options, 'invoke-multiple', 0); + if ($invoke_multiple) { + $invocations = array_fill(0, $invoke_multiple, $invocations[0]); + } + } + return drush_backend_invoke_concurrent($invocations, $commandline_options, $backend_options); +} + +/** + * Given a command record, dispatch it as if it were + * the original command. Executes in the currently + * bootstrapped site using the current option contexts. + * Note that drush_dispatch will not bootstrap any further than the + * current command has already bootstrapped; therefore, you should only invoke + * commands that have the same (or lower) bootstrap requirements. + * + * @param command + * A full $command such as returned by drush_get_commands(), + * or a string containing the name of the command record from + * drush_get_commands() to call. + * @param arguments + * An array of argument values. + * + * @see drush_topic_docs_topic(). + */ +function drush_dispatch($command, $arguments = array()) { + drush_set_command($command); + $return = FALSE; + + if ($command) { + // Add arguments, if this has not already been done. + // (If the command was fetched from drush_parse_command, + // then you cannot provide arguments to drush_dispatch.) + if (empty($command['arguments'])) { + _drush_prepare_command($command, $arguments); + } + + // Merge in the options added by hooks. We need this + // for validation, but this $command is just going to + // get thrown away, so we'll have to do this again later. + annotationcommand_adapter_add_hook_options($command); + + // Add command-specific options, if applicable. + drush_command_default_options($command); + + // Test to see if any of the options in the 'cli' context + // are not represented in the command structure. + if ((_drush_verify_cli_options($command) === FALSE) || (_drush_verify_cli_arguments($command) === FALSE)) { + return FALSE; + } + + // Give command files an opportunity to alter the command record + drush_command_invoke_all_ref('drush_command_alter', $command); + + // Include and validate command engines. + if (drush_load_command_engines($command) === FALSE) { + return FALSE; + } + + // Do tilde expansion immediately prior to execution, + // so that tildes are passed through unchanged for + // remote commands and other redispatches. + drush_preflight_tilde_expansion($command); + + // Get the arguments for this command. Add the options + // on the end if this is that kind of command. + $args = $command['arguments']; + + // Call the callback function of the active command. + $return = call_user_func_array($command['callback'], $args); + } + + // Add a final log entry, just so a timestamp appears. + drush_log(dt('Command dispatch complete'), LogLevel::NOTICE); + + return $return; +} + +/** + * Entry point for commands into the drush_invoke() API + * + * If a command does not have a callback specified, this function will be called. + * + * This function will trigger $hook_drush_init, then if no errors occur, + * it will call drush_invoke() with the command that was dispatch. + * + * If no errors have occured, it will run $hook_drush_exit. + */ +function drush_command() { + $args = func_get_args(); + $command = drush_get_command(); + foreach (drush_command_implements("drush_init") as $name) { + $func = $name . '_drush_init'; + if (drush_get_option('show-invoke')) { + drush_log(dt("Calling global init hook: !func", array('!name' => $name, '!func' => $func . '()')), LogLevel::BOOTSTRAP); + } + call_user_func_array($func, $args); + _drush_log_drupal_messages(); + } + + if (!drush_get_error()) { + $result = _drush_invoke_hooks($command, $args); + } + + if (!drush_get_error()) { + foreach (drush_command_implements('drush_exit') as $name) { + $func = $name . '_drush_exit'; + if (drush_get_option('show-invoke')) { + drush_log(dt("Calling global exit hook: !func", array('!name' => $name, '!func' => $func . '()')), LogLevel::BOOTSTRAP); + } + call_user_func_array($func, $args); + _drush_log_drupal_messages(); + } + } +} + +/** + * Invoke Drush API calls, including all hooks. + * + * This is an internal function; it is called from drush_dispatch via + * drush_command, but only if the command does not specify a 'callback' + * function. If a callback function is specified, it will be called + * instead of drush_command + _drush_invoke_hooks. + * + * Executes the specified command with the specified arguments on the + * currently bootstrapped site using the current option contexts. + * Note that _drush_invoke_hooks will not bootstrap any further than the + * current command has already bootstrapped; therefore, you should only invoke + * commands that have the same (or lower) bootstrap requirements. + * + * Call the correct hook for all the modules that implement it. + * Additionally, the ability to rollback when an error has been encountered is also provided. + * If at any point during execution, the drush_get_error() function returns anything but 0, + * drush_invoke() will trigger $hook_rollback for each of the hooks that implement it, + * in reverse order from how they were executed. Rollbacks are also triggered any + * time a hook function returns FALSE. + * + * This function will also trigger pre_$hook and post_$hook variants of the hook + * and its rollbacks automatically. + * + * HOW DRUSH HOOK FUNCTIONS ARE NAMED: + * + * The name of the hook is composed from the name of the command and the name of + * the command file that the command definition is declared in. The general + * form for the hook filename is: + * + * drush_COMMANDFILE_COMMANDNAME + * + * In many cases, drush commands that are functionally part of a common collection + * of similar commands will all be declared in the same file, and every command + * defined in that file will start with the same command prefix. For example, the + * command file "pm.drush.inc" defines commands such as "pm-enable" and "pm-disable". + * In the case of "pm-enable", the command file is "pm", and and command name is + * "pm-enable". When the command name starts with the same sequence of characters + * as the command file, then the repeated sequence is dropped; thus, the command + * hook for "pm-enable" is "drush_pm_enable", not "drush_pm_pm_enable". + * + * There is also a special Drupal-version-specific naming convention that may + * be used. To hide a commandfile from all versions of Drupal except for the + * specific one named, add a ".dVERSION" after the command prefix. For example, + * the file "views.d8.drush.inc" defines a "views" commandfile that will only + * load with Drupal 8. This feature is not necessary and should not be used + * in contrib modules (any extension with a ".module" file), since these modules + * are already version-specific. + * + * @param command + * The drush command to execute. + * @param args + * An array of arguments to the command OR a single non-array argument. + * @return + * The return value will be passed along to the caller if --backend option is + * present. A boolean FALSE indicates failure and rollback will be intitated. + * + * This function should not be called directly. + * @see drush_invoke() and @see drush_invoke_process() + */ +function _drush_invoke_hooks($command, $args) { + $return = null; + // If someone passed a standalone arg, convert it to a single-element array + if (!is_array($args)) { + $args = array($args); + } + // Include the external command file used by this command, if there is one. + drush_command_include($command['command-hook']); + // Generate the base name for the hook by converting all + // dashes in the command name to underscores. + $hook = str_replace("-", "_", $command['command-hook']); + + // Call the hook init function, if it exists. + // If a command needs to bootstrap, it is advisable + // to do so in _init; otherwise, new commandfiles + // will miss out on participating in any stage that + // has passed or started at the time it was discovered. + $func = 'drush_' . $hook . '_init'; + if (function_exists($func)) { + drush_log(dt("Calling drush command init function: !func", array('!func' => $func)), LogLevel::BOOTSTRAP); + call_user_func_array($func, $args); + _drush_log_drupal_messages(); + if (drush_get_error()) { + drush_log(dt('The command @command could not be initialized.', array('@command' => $command['command-hook'])), LogLevel::ERROR); + return FALSE; + } + } + + // We will adapt and call as many of the annotated command hooks as we can. + // The following command hooks are not supported in Drush 8.x: + // - Command Event: not called (requires CommandEvent object) + // - Option: Equivalent functionality supported in annotationcommand_adapter.inc + // - Interact: not called (We don't use SymfonyStyle in 8.x at the moment) + // - Status: not called - probably not needed? + // - Extract not called - probably not needed? + // The hooks that are called include: + // - Pre-initialize, initialize and post-initialize + // - Pre-validate and validate + // - Pre-command, command and post-command + // - Pre-process, process and post-process + // - Pre-alter, alter and post-alter + + $names = annotationcommand_adapter_command_names($command); + // Merge in the options added by hooks (again) + annotationcommand_adapter_add_hook_options($command); + $annotationData = $command['annotations']; + + $input = new DrushInputAdapter($args, annotationcommand_adapter_get_options($command), $command['command']); + $output = new DrushOutputAdapter(); + $commandData = new CommandData( + $annotationData, + $input, + $output, + false, + false + ); + + annotationcommand_adapter_call_initialize($names, $commandData); + + $rollback = FALSE; + $completed = array(); + $available_rollbacks = array(); + $all_available_hooks = array(); + + // Iterate through the different hook variations + $variations = array( + 'pre_validate' => $hook . "_pre_validate", + 'validate' => $hook . "_validate", + 'pre_command' => "pre_$hook", + 'command' => $hook, + 'post_command' => "post_$hook" + ); + foreach ($variations as $hook_phase => $var_hook) { + + $adapterHookFunction = 'annotationcommand_adapter_call_hook_' . $hook_phase; + $adapterHookFunction($names, $commandData, $return); + + // Get the list of command files. + // We re-fetch the list every time through + // the loop in case one of the hook function + // does something that will add additional + // commandfiles to the list (i.e. bootstrapping + // to a higher phase will do this). + $list = drush_commandfile_list(); + + // Make a list of function callbacks to call. If + // there is a 'primary function' mentioned, make sure + // that it appears first in the list, but only if + // we are running the main hook ("$hook"). After that, + // make sure that any callback associated with this commandfile + // executes before any other hooks defined in any other + // commandfiles. + $callback_list = array(); + if (($var_hook == $hook) && ($command['primary function'])) { + $callback_list[$command['primary function']] = $list[$command['commandfile']]; + } + else { + $primary_func = ($command['commandfile'] . "_" == substr($var_hook . "_",0,strlen($command['commandfile']) + 1)) ? sprintf("drush_%s", $var_hook) : sprintf("drush_%s_%s", $command['commandfile'], $var_hook); + $callback_list[$primary_func] = $list[$command['commandfile']]; + } + // We've got the callback for the primary function in the + // callback list; now add all of the other callback functions. + unset($list[$command['commandfile']]); + foreach ($list as $commandfile => $filename) { + $func = sprintf("drush_%s_%s", $commandfile, $var_hook); + $callback_list[$func] = $filename; + } + // Run all of the functions available for this variation + $accumulated_result = NULL; + foreach ($callback_list as $func => $filename) { + if (function_exists($func)) { + $all_available_hooks[] = $func . ' [* Defined in ' . $filename . ']'; + $available_rollbacks[] = $func . '_rollback'; + $completed[] = $func; + drush_log(dt("Calling hook !hook", array('!hook' => $func)), LogLevel::DEBUG); + try { + $result = call_user_func_array($func, $args); + drush_log(dt("Returned from hook !hook", array('!hook' => $func)), LogLevel::DEBUG); + } + catch (Exception $e) { + drush_set_error('DRUSH_EXECUTION_EXCEPTION', (string) $e); + } + // If there is an error, break out of the foreach + // $variations and foreach $callback_list + if (drush_get_error() || ($result === FALSE)) { + $rollback = TRUE; + break 2; + } + // If result values are arrays, then combine them all together. + // Later results overwrite earlier results. + if (isset($result) && is_array($accumulated_result) && is_array($result)) { + $accumulated_result = array_merge($accumulated_result, $result); + } + else { + $accumulated_result = $result; + } + _drush_log_drupal_messages(); + } + else { + $all_available_hooks[] = $func; + } + } + // Process the result value from the 'main' callback hook only. + if ($var_hook == $hook) { + $return = $accumulated_result; + if (isset($return)) { + annotationcommand_adapter_call_hook_process_and_alter($names, $commandData, $return); + drush_handle_command_output($command, $return); + } + } + } + + // If no hook functions were found, print a warning. + if (empty($completed)) { + $default_command_hook = sprintf("drush_%s_%s", $command['commandfile'], $hook); + if (($command['commandfile'] . "_" == substr($hook . "_",0,strlen($command['commandfile'])+ 1))) { + $default_command_hook = sprintf("drush_%s", $hook); + } + $dt_args = array( + '!command' => $command['command-hook'], + '!default_func' => $default_command_hook, + ); + $message = "No hook functions were found for !command. The primary hook function is !default_func(). Please implement this function. Run with --show-invoke to see all available hooks."; + $return = drush_set_error('DRUSH_FUNCTION_NOT_FOUND', dt($message, $dt_args)); + } + if (drush_get_option('show-invoke')) { + // We show all available hooks up to and including the one that failed (or all, if there were no failures) + drush_log(dt("Available drush_invoke() hooks for !command: !available", array('!command' => $command['command-hook'], '!available' => "\n" . implode("\n", $all_available_hooks))), LogLevel::OK); + } + if (drush_get_option('show-invoke') && !empty($available_rollbacks)) { + drush_log(dt("Available rollback hooks for !command: !rollback", array('!command' => $command['command-hook'], '!rollback' => "\n" . implode("\n", $available_rollbacks))), LogLevel::OK); + } + + // Something went wrong, we need to undo. + if ($rollback) { + if (drush_get_option('confirm-rollback', FALSE)) { + // Optionally ask for confirmation, --yes and --no are ignored from here on as we are about to finish this process. + drush_set_context('DRUSH_AFFIRMATIVE', FALSE); + drush_set_context('DRUSH_NEGATIVE', FALSE); + $rollback = drush_confirm(dt('Do you want to rollback? (manual cleanup might be required otherwise)')); + } + + if ($rollback) { + foreach (array_reverse($completed) as $func) { + $rb_func = $func . '_rollback'; + if (function_exists($rb_func)) { + call_user_func_array($rb_func, $args); + _drush_log_drupal_messages(); + drush_log(dt("Changes made in !func have been rolled back.", array('!func' => $func)), LogLevel::DEBUG); + } + } + } + $return = FALSE; + } + + if (isset($return)) { + return $return; + } +} + +/** + * Convert the structured output array provided from the Drush + * command into formatted output. Output is only printed for commands + * that define 'default-format' &/or 'default-pipe-format'; all + * other commands are expected to do their own output. + */ +function drush_handle_command_output($command, $structured_output) { + // If the hook already called drush_backend_set_result, + // then return that value. If it did not, then the return + // value from the hook will be the value returned from + // this routine. + $return = drush_backend_get_result(); + if (empty($return)) { + drush_backend_set_result($structured_output); + } + // We skip empty strings and empty arrays, but note that 'empty' + // returns TRUE for the integer value '0', but we do want to print that. + // Only handle output here if the command defined an output format + // engine. If no engine was declared, then we presume that the command + // handled its own output. + if ((!empty($structured_output) || ($structured_output === 0))) { + // If the command specifies a default pipe format and + // returned a result, then output the formatted output when + // in --pipe mode. + $formatter = drush_get_outputformat(); + if (!$formatter && is_string($structured_output)) { + $formatter = drush_load_engine('outputformat', 'string'); + } + if ($formatter) { + if ($formatter === TRUE) { + return drush_set_error(dt('No outputformat class defined for !format', array('!format' => $format))); + } + if ((!empty($command['engines']['outputformat'])) && (!in_array($formatter->engine, $command['engines']['outputformat']['usable']))) { + return $formatter->format_error(dt("The command '!command' does not produce output in a structure usable by this output format.", array('!command' => $command['command']))); + } + // Add any user-specified options to the metadata passed to the formatter. + $metadata = array(); + $metadata['strict'] = drush_get_option('strict', FALSE); + if (isset($formatter->engine_config['options'])) { + $machine_parsable = $formatter->engine_config['engine-info']['machine-parsable']; + if (drush_get_option('full', FALSE)) { + if (isset($formatter->engine_config['fields-full'])) { + $formatter->engine_config['fields-default'] = $formatter->engine_config['fields-full']; + } + else { + $formatter->engine_config['fields-default'] = array_keys($formatter->engine_config['field-labels']); + } + } + elseif ((drush_get_context('DRUSH_PIPE') || $machine_parsable) && isset($formatter->engine_config['fields-pipe'])) { + $formatter->engine_config['fields-default'] = $formatter->engine_config['fields-pipe']; + } + + // Determine the --format, and options relevant for that format. + foreach ($formatter->engine_config['options'] as $option => $option_info) { + $default_value = isset($formatter->engine_config[$option . '-default']) ? $formatter->engine_config[$option . '-default'] : FALSE; + if (($default_value === FALSE) && array_key_exists('default', $option_info)) { + $default_value = $option_info['default']; + } + if (isset($option_info['list'])) { + $user_specified_value = drush_get_option_list($option, $default_value); + } + else { + $user_specified_value = drush_get_option($option, $default_value); + } + if ($user_specified_value !== FALSE) { + if (array_key_exists('key', $option_info)) { + $option = $option_info['key']; + } + $metadata[$option] =$user_specified_value; + } + } + } + if (isset($metadata['fields']) && !empty($metadata['fields'])) { + if (isset($formatter->engine_config['field-labels'])) { + $formatter->engine_config['field-labels'] = drush_select_fields($formatter->engine_config['field-labels'], $metadata['fields'], $metadata['strict']); + } + } + $output = $formatter->process($structured_output, $metadata); + if (drush_get_context('DRUSH_PIPE')) { + drush_print_pipe($output); + } + else { + drush_print($output); + } + } + } +} + +/** + * Fail with an error if the user specified options on the + * command line that are not documented in the current command + * record. Also verify that required options are present. + */ +function _drush_verify_cli_options($command) { + + // Start out with just the options in the current command record. + $options = _drush_get_command_options($command); + // Skip all tests if the command is marked to allow anything. + // Also skip backend commands, which may have options on the commandline + // that were inherited from the calling command. + if (($command['allow-additional-options'] === TRUE)) { + return TRUE; + } + // If 'allow-additional-options' contains a list of command names, + // then union together all of the options from all of the commands. + if (is_array($command['allow-additional-options'])) { + $implemented = drush_get_commands(); + foreach ($command['allow-additional-options'] as $subcommand_name) { + if (array_key_exists($subcommand_name, $implemented)) { + $options = array_merge($options, _drush_get_command_options($implemented[$subcommand_name])); + } + } + } + // Also add in global options + $options = array_merge($options, drush_get_global_options()); + + // Add a placeholder option so that backend requests originating from prior versions of Drush are valid. + $options += array('invoke' => ''); + + // Now we will figure out which options in the cli context + // are not represented in our options list. + $cli_options = array_keys(drush_get_context('cli')); + $allowed_options = _drush_flatten_options($options); + $allowed_options = drush_append_negation_options($allowed_options); + $disallowed_options = array_diff($cli_options, $allowed_options); + if (!empty($disallowed_options)) { + $unknown = count($disallowed_options) > 1 ? dt('Unknown options') : dt('Unknown option'); + if (drush_get_option('strict', TRUE)) { + $msg = dt("@unknown: --@options. See `drush help @command` for available options. To suppress this error, add the option --strict=0.", array('@unknown' => $unknown, '@options' => implode(', --', $disallowed_options), '@command' => $command['command'])); + return drush_set_error('DRUSH_UNKNOWN_OPTION', $msg); + } + } + + // Next check to see if all required options were specified, + // and if all specified options with required values have values. + $missing_required_options = array(); + $options_missing_required_values = array(); + foreach ($command['options'] as $option_name => $option) { + if (is_array($option) && !empty($option['required']) && drush_get_option($option_name, NULL) === NULL) { + $missing_required_options[] = $option_name; + } + // Note that drush_get_option() will return TRUE if an option + // was specified without a value (--option), as opposed to + // the string "1" is --option=1 was used. + elseif (is_array($option) && !empty($option['value']) && ($option['value'] == 'required') && drush_get_option($option_name, NULL) === TRUE) { + $options_missing_required_values[] = $option_name; + } + } + if (!empty($missing_required_options) || !empty($options_missing_required_values)) { + $missing_message = ''; + if (!empty($missing_required_options)) { + $missing = count($missing_required_options) > 1 ? dt('Missing required options') : dt('Missing required option'); + $missing_message = dt("@missing: --@options.", array('@missing' => $missing, '@options' => implode(', --', $missing_required_options))); + } + if (!empty($options_missing_required_values)) { + if (!empty($missing_message)) { + $missing_message .= " "; + } + $missing = count($options_missing_required_values) > 1 ? dt('Options used without providing required values') : dt('Option used without a value where one was required'); + $missing_message .= dt("@missing: --@options.", array('@missing' => $missing, '@options' => implode(', --', $options_missing_required_values))); + } + return drush_set_error(dt("!message See `drush help @command` for information on usage.", array('!message' => $missing_message, '@command' => $command['command']))); + } + return TRUE; +} + +function drush_append_negation_options($allowed_options) { + $new_allowed = $allowed_options; + foreach ($allowed_options as $option) { + $new_allowed[] = 'no-' . $option; + } + return $new_allowed; +} + +function _drush_verify_cli_arguments($command) { + // Check to see if all of the required arguments + // are specified. + if ($command['required-arguments']) { + $required_arg_count = $command['required-arguments']; + if ($required_arg_count === TRUE) { + $required_arg_count = count($command['argument-description']); + } + + if (count($command['arguments']) < $required_arg_count) { + $missing = $required_arg_count > 1 ? dt('Missing required arguments') : dt('Missing required argument'); + $required = array_slice(array_keys($command['argument-description']), 0, $required_arg_count); + + return drush_set_error(dt("@missing: @required. See `drush help @command` for information on usage.", array( + '@missing' => $missing, + '@required' => implode(", ", $required), + '@command' => $command['command'], + ))); + } + } + return TRUE; +} + +/** + * Return the list of all of the options for the given + * command record by merging the 'options' and 'sub-options' + * records. + */ +function _drush_get_command_options($command) { + drush_command_invoke_all_ref('drush_help_alter', $command); + $options = $command['options']; + foreach ($command['sub-options'] as $group => $suboptions) { + $options = array_merge($options, $suboptions); + } + return $options; +} + +/** + * Return the list of all of the options for the given + * command record including options provided by engines and additional-options. + */ +function drush_get_command_options_extended($command) { + drush_merge_engine_data($command); + + // Start out with just the options in the current command record. + $options = _drush_get_command_options($command); + // If 'allow-additional-options' contains a list of command names, + // then union together all of the options from all of the commands. + if (is_array($command['allow-additional-options'])) { + $implemented = drush_get_commands(); + foreach ($command['allow-additional-options'] as $subcommand_name) { + if (array_key_exists($subcommand_name, $implemented)) { + $options = array_merge($options, _drush_get_command_options($implemented[$subcommand_name])); + } + } + } + return $options; +} + +/** + * Return the array keys of $options, plus any 'short-form' + * representations that may appear in the option's value. + */ +function _drush_flatten_options($options) { + $flattened_options = array(); + + foreach($options as $key => $value) { + // engine sections start with 'package-handler=git_drupalorg', + // or something similar. Get rid of everything from the = onward. + if (($eq_pos = strpos($key, '=')) !== FALSE) { + $key = substr($key, 0, $eq_pos); + } + $flattened_options[] = $key; + if (is_array($value)) { + if (array_key_exists('short-form', $value)) { + $flattened_options[] = $value['short-form']; + } + } + } + return $flattened_options; +} + +/** + * Get the options that were passed to the current command. + * + * This function returns an array that contains all of the options + * that are appropriate for forwarding along to drush_invoke_process. + * + * @return + * An associative array of option key => value pairs. + */ +function drush_redispatch_get_options() { + $options = array(); + + // Add in command-specific and alias options, but for global options only. + $options_soup = drush_get_context('specific') + drush_get_context('alias'); + $global_option_list = drush_get_global_options(FALSE); + foreach ($options_soup as $key => $value) { + if (array_key_exists($key, $global_option_list)) { + $options[$key] = $value; + } + } + + // Local php settings should not override sitealias settings. + $cli_context = drush_get_context('cli'); + unset($cli_context['php'], $cli_context['php-options']); + // Pass along CLI parameters, as higher priority. + $options = $cli_context + $options; + + $options = array_diff_key($options, array_flip(drush_sitealias_site_selection_keys())); + unset($options['command-specific']); + unset($options['path-aliases']); + // If we can parse the current command, then examine all contexts + // in order for any option that is directly related to the current command + $command = drush_parse_command(); + if (is_array($command)) { + foreach (drush_get_command_options_extended($command) as $key => $value) { + $value = drush_get_option($key); + if (isset($value)) { + $options[$key] = $value; + } + } + } + // If --bootstrap-to-first-arg is specified, do not + // pass it along to remote commands. + unset($options['bootstrap-to-first-arg']); + + return $options; +} + +/** + * @} End of "defgroup dispatching". + */ + +/** + * @file + * The drush command engine. + * + * Since drush can be invoked independently of a proper Drupal + * installation and commands may operate across sites, a distinct + * command engine is needed. + * + * It mimics the Drupal module engine in order to economize on + * concepts and to make developing commands as familiar as possible + * to traditional Drupal module developers. + */ + +/** + * Parse console arguments. + */ +function drush_parse_args() { + $args = drush_get_context('argv'); + $command_args = NULL; + $global_options = array(); + $target_alias_name = NULL; + // It would be nice if commandfiles could somehow extend this list, + // but it is not possible. We need to parse args before we find commandfiles, + // because the specified options may affect how commandfiles are located. + // Therefore, commandfiles are loaded too late to affect arg parsing. + // There are only a limited number of short options anyway; drush reserves + // all for use by drush core. + static $arg_opts = array('c', 'u', 'r', 'l', 'i'); + + // Check to see if we were executed via a "#!/usr/bin/env drush" script + drush_adjust_args_if_shebang_script($args); + + // Now process the command line arguments. We will divide them + // into options (starting with a '-') and arguments. + $arguments = $options = array(); + + $verbosity = 0; + for ($i = 1; $i < count($args); $i++) { + $opt = $args[$i]; + // We set $command_args to NULL until the first argument that is not + // an alias is found (the command); we put everything that follows + // into $command_args. + if (is_array($command_args)) { + $command_args[] = $opt; + } + // Is the arg an option (starting with '-')? + if (!empty($opt) && $opt[0] == "-" && strlen($opt) != 1) { + // Do we have multiple options behind one '-'? + if (strlen($opt) > 2 && $opt[1] != "-") { + // Each char becomes a key of its own. + for ($j = 1; $j < strlen($opt); $j++) { + $opt_char = substr($opt, $j, 1); + $options[$opt_char] = TRUE; + if ($opt_char == 'v') { + ++$verbosity; + } + } + } + // Do we have a longopt (starting with '--')? + elseif ($opt[1] == "-") { + if ($pos = strpos($opt, '=')) { + $options[substr($opt, 2, $pos - 2)] = substr($opt, $pos + 1); + } + else { + $options[substr($opt, 2)] = TRUE; + } + } + else { + $opt = substr($opt, 1); + // Check if the current opt is in $arg_opts (= has to be followed by an argument). + if ((in_array($opt, $arg_opts))) { + // Raising errors for missing option values should be handled by the + // bootstrap or specific command, so we no longer do this here. + $options[$opt] = $args[$i + 1]; + $i++; + } + else { + $options[$opt] = TRUE; + } + } + } + // If it's not an option, it's a command. + else { + $arguments[] = $opt; + // Once we find the first argument, record the command args and global options + if (!is_array($command_args)) { + // Remember whether we set $target_alias_name on a previous iteration, + // then record the $target_alias_name iff this arguement references a valid site alias. + $already_set_target = is_string($target_alias_name); + if (!$already_set_target && drush_sitealias_valid_alias_format($opt)) { + $target_alias_name = $opt; + } + // If an alias record was set on a previous iteration, then this + // argument must be the command name. If we set the target alias + // record on this iteration, then this is not the command name. + // If we've found the command name, then save $options in $global_options + // (all options that came before the command name), and initialize + // $command_args to an array so that we will begin storing all args + // and options that follow the command name in $command_args. + if ($already_set_target || (!is_string($target_alias_name))) { + $command_args = array(); + $global_options = $options; + } + } + } + } + if ($verbosity >= 3) { + $options['debug'] = TRUE; + } + // If no arguments are specified, then the command will + // be either 'help' or 'version' (the latter if --version is specified) + // @todo: it would be handy if one could do `drush @remote st --help` and + // have that show help for st. Today, that shows --help for help command! + if (!count($arguments)) { + if (array_key_exists('version', $options)) { + $arguments = array('version'); + } + else { + $arguments = array('help'); + } + } + if (is_array($command_args)) { + drush_set_context('DRUSH_COMMAND_ARGS', $command_args); + } + drush_set_context('DRUSH_GLOBAL_CLI_OPTIONS', $global_options); + + // Handle the "@shift" alias, if present + drush_process_bootstrap_to_first_arg($arguments); + + drush_set_arguments($arguments); + drush_set_config_special_contexts($options); + drush_set_context('cli', $options); + return $arguments; +} + +/** + * Pop an argument off of drush's argument list + */ +function drush_shift() { + $arguments = drush_get_arguments(); + $result = NULL; + if (!empty($arguments)) { + // The php-script command uses the DRUSH_SHIFT_SKIP + // context to cause drush_shift to skip the 'php-script' + // command and the script path argument when it is + // called from the user script. + $skip_count = drush_get_context('DRUSH_SHIFT_SKIP'); + if (is_numeric($skip_count)) { + for ($i = 0; $i < $skip_count; $i++) { + array_shift($arguments); + } + $skip_count = drush_set_context('DRUSH_SHIFT_SKIP', 0); + } + $result = array_shift($arguments); + drush_set_arguments($arguments); + } + return $result; +} + +/** + * Special checking for "shebang" script handling. + * + * If there is a file 'script.php' that begins like so: + * #!/path/to/drush + * Then $args will be: + * /path/to/drush /path/to/script userArg1 userArg2 ... + * If it instead starts like this: + * #!/path/to/drush --flag php-script + * Then $args will be: + * /path/to/drush "--flag php-script" /path/to/script userArg1 userArg2 ... + * (Note that execve does not split the parameters from + * the shebang line on whitespace; see http://en.wikipedia.org/wiki/Shebang_%28Unix%29) + * When drush is called via one of the "shebang" lines above, + * the first or second parameter will be the full path + * to the "shebang" script file -- and if the path to the + * script is in the second position, then we will expect that + * the argument in the first position must begin with a + * '@' (alias) or '-' (flag). Under ordinary circumstances, + * we do not expect that the drush command must come before + * any argument that is the full path to a file. We use + * this assumption to detect "shebang" script execution. + */ +function drush_adjust_args_if_shebang_script(&$args) { + if (drush_has_bash()) { + // The drush.launcher script may add --php or --php-options at the + // head of the argument list; skip past those. + $base_arg_number = 1; + while (isset($args[$base_arg_number]) && substr($args[$base_arg_number], 0, 5) == '--php') { + ++$base_arg_number; + } + if (!isset($args[$base_arg_number])) { + // No arguments passed. + return; + } + elseif (_drush_is_drush_shebang_script($args[$base_arg_number])) { + // If $args[1] is a drush "shebang" script, we will insert + // the option "--bootstrap-to-first-arg" and the command + // "php-script" at the beginning of @args, so the command + // line args become: + // /path/to/drush --bootstrap-to-first-arg php-script /path/to/script userArg1 userArg2 ... + drush_set_option('bootstrap-to-first-arg', TRUE); + array_splice($args, $base_arg_number, 0, array('php-script')); + drush_set_context('DRUSH_SHEBANG_SCRIPT', TRUE); + } + elseif (((strpos($args[$base_arg_number], ' ') !== FALSE) || (!ctype_alnum($args[$base_arg_number][0]))) && (($base_arg_number + 1 < count($args)) && (_drush_is_drush_shebang_script($args[$base_arg_number + 1])))) { + // If $args[2] is a drush "shebang" script, we will insert + // the space-exploded $arg[1] in place of $arg[1], so the + // command line args become: + // /path/to/drush scriptArg1 scriptArg2 ... /path/to/script userArg1 userArg2 ... + // If none of the script arguments look like a drush command, + // then we will insert "php-script" as the default command to + // execute. + $script_args = explode(' ', $args[$base_arg_number]); + $has_command = FALSE; + foreach ($script_args as $script_arg) { + if (preg_match("/^[a-z][a-z0-9-]*$/",$script_arg)) { + $has_command = TRUE; + } + } + if (!$has_command) { + $script_args[] = 'php-script'; + } + array_splice($args, 1, $base_arg_number, $script_args); + drush_set_context('DRUSH_SHEBANG_SCRIPT', TRUE); + } + } +} + +/** + * Process the --bootstrap-to-first-arg option, if it is present. + * + * This option checks to see if the first user-provided argument is an alias + * or site specification; if it is, it will be shifted into the first argument + * position, where it will specify the site to bootstrap. The result of this + * is that if your shebang line looks like this: + * + * #!/path/to/drush --bootstrap-to-first-arg php-script + * + * Then when you run that script, you can optionally provide an alias such + * as @dev as the first argument (e.g. $ ./mydrushscript.php @dev scriptarg1 + * scriptarg2). Since this is the behavior that one would usually want, + * it is default behavior for a canonical script. That is, a script + * with a simple shebang line, like so: + * + * #!/path/to/drush + * + * will implicitly have "--bootstrap-to-first-arg" and "php-script" prepended, and will therefore + * behave exactly like the first example. To write a script that does not + * use --bootstrap-to-first-arg, then the drush command or at least one flag must be explicitly + * included, like so: + * + * #!/path/to/drush php-script + */ +function drush_process_bootstrap_to_first_arg(&$arguments) { + if (drush_get_option('bootstrap-to-first-arg', FALSE)) { + $shift_alias_pos = 1 + (drush_get_context('DRUSH_SHEBANG_SCRIPT') === TRUE); + if (count($arguments) >= $shift_alias_pos) { + $shifted_alias = $arguments[$shift_alias_pos]; + $alias_record = drush_sitealias_get_record($shifted_alias); + if (!empty($alias_record)) { + // Move the alias we shifted from its current position + // in the argument list to the front of the list + array_splice($arguments, $shift_alias_pos, 1); + array_unshift($arguments, $shifted_alias); + } + } + } +} + +/** + * Get a list of all implemented commands. + * This invokes hook_drush_command(). + * + * @return + * Associative array of currently active command descriptors. + * + */ +function drush_get_commands($reset = FALSE) { + static $commands = array(); + + if ($reset) { + $commands = array(); + return; + } + elseif ($commands) { + return $commands; + } + + $list = drush_commandfile_list(); + foreach ($list as $commandfile => $path) { + if (drush_command_hook($commandfile, 'drush_command')) { + $function = $commandfile . '_drush_command'; + $result = $function(); + foreach ((array)$result as $key => $command) { + // Add some defaults and normalize the command descriptor. + $command += drush_command_defaults($key, $commandfile, $path); + + // Add engine data. + drush_merge_engine_data($command); + + // Translate command. + drush_command_translate($command); + + // If the command callback is not 'drush_command', then + // copy the callback function to an alternate element + // of the command array that will be called when Drush + // calls the command function hooks. Then, set the + // callback to drush_command so that the function hooks + // will be called. + if (($command['callback'] != 'drush_command') && $command['invoke hooks']) { + $command['primary function'] = $command['callback']; + $command['callback'] = 'drush_command'; + } + + $commands[$key] = $command; + } + } + } + $commands = array_merge($commands, annotationcommand_adapter_commands()); + foreach ($commands as $command) { + // For every alias, make a copy of the command and store it in the command list + // using the alias as a key + if (isset($command['aliases']) && count($command['aliases'])) { + foreach ($command['aliases'] as $alias) { + $commands[$alias] = $command; + $commands[$alias]['is_alias'] = TRUE; + } + } + } + return $commands; +} + +/** + * Organize commands into categories. Used by help listing and core-cli. + * + * @param array $commands + * A commands array as per drush_get_commands(). + * + * @return array $command_categories + * A categorized associative array of commands. + */ +function drush_commands_categorize($commands) { + $command_categories = array(); + $category_map = array(); + foreach ($commands as $key => $candidate) { + if ((!array_key_exists('is_alias', $candidate) || !$candidate['is_alias']) && !$candidate['hidden']) { + $category = $candidate['category']; + // If we have decided to remap a category, remap every command + if (array_key_exists($category, $category_map)) { + $category = $category_map[$category]; + } + if (!array_key_exists($category, $command_categories)) { + $title = drush_command_invoke_all('drush_help', "meta:$category:title"); + $alternate_title = ''; + if (!$title) { + // If there is no title, then check to see if the + // command file is stored in a folder with the same + // name as some other command file (e.g. 'core') that + // defines a title. + $alternate = basename($candidate['path']); + $alternate_title = drush_command_invoke_all('drush_help', "meta:$alternate:title"); + } + if (!empty($alternate_title)) { + $category_map[$category] = $alternate; + $category = $alternate; + $title = $alternate_title; + } + $command_categories[$category]['title'] = empty($title) ? '' : $title[0]; + $summary = drush_command_invoke_all('drush_help', "meta:$category:summary"); + if ($summary) { + $command_categories[$category]['summary'] = $summary[0]; + } + } + $candidate['category'] = $category; + $command_categories[$category]['commands'][$key] = $candidate; + } + } + + // Make sure that 'core' is always first in the list + $core_category = array('core' => $command_categories['core']); + unset($command_categories['core']); + + // Post-process the categories that have no title. + // Any that have fewer than 4 commands go into a section called "other". + $processed_categories = array(); + $misc_categories = array(); + $other_commands = array(); + $other_categories = array(); + foreach ($command_categories as $key => $info) { + if (empty($info['title'])) { + $one_category = $key; + if (count($info['commands']) < 4) { + $other_commands = array_merge($other_commands, $info['commands']); + $other_categories[] = $one_category; + } + else { + $info['title'] = dt("All commands in !category", array('!category' => $key)); + $misc_categories[$one_category] = $info; + } + } + else { + $processed_categories[$key] = $info; + } + } + $other_category = array(); + if (!empty($other_categories)) { + $other_category[implode(',', $other_categories)] = array('title' => dt("Other commands"), 'commands' => $other_commands); + } + asort($processed_categories); + asort($misc_categories); + $command_categories = array_merge($core_category, $processed_categories, $misc_categories, $other_category); + + // If the user specified --sort, then merge all of the remaining + // categories together + if (drush_get_option('sort', FALSE)) { + $combined_commands = array(); + foreach ($command_categories as $key => $info) { + $combined_commands = array_merge($combined_commands, $info['commands']); + } + $command_categories = array('all' => array('commands' => $combined_commands, 'title' => dt("Commands:"))); + } + + return $command_categories; +} + +function drush_command_defaults($key, $commandfile, $path) { + $defaults = array( + 'command' => $key, + 'command-hook' => $key, + 'invoke hooks' => TRUE, + 'callback arguments' => array(), + 'commandfile' => $commandfile, + 'path' => dirname($path), + 'engines' => array(), // Helpful for drush_show_help(). + 'callback' => 'drush_command', + 'primary function' => FALSE, + 'description' => NULL, + 'sections' => array( + 'examples' => 'Examples', + 'arguments' => 'Arguments', + 'options' => 'Options', + ), + 'arguments' => array(), + 'required-arguments' => FALSE, + 'options' => array(), + 'sub-options' => array(), + 'allow-additional-options' => FALSE, + 'global-options' => array(), + 'examples' => array(), + 'aliases' => array(), + 'core' => array(), + 'scope' => 'site', + 'drush dependencies' => array(), + 'handle-remote-commands' => FALSE, + 'remote-tty' => FALSE, + 'strict-option-handling' => FALSE, + 'tilde-expansion' => TRUE, + 'bootstrap_errors' => array(), + 'topics' => array(), + 'hidden' => FALSE, + 'category' => $commandfile, + 'add-options-to-arguments' => FALSE, + 'consolidation-output-formatters' => FALSE, + 'annotated-command-callback' => '', + 'annotations' => new AnnotationData(['command' => $key]), + ); + // We end up here, setting the defaults for a command, when + // called from drush_get_global_options(). Early in the Drush + // bootstrap, there will be no bootstrap object, because we + // need to get the list of global options when loading config + // files, and config files are loaded before the bootstrap object + // is created. In this early stage, we just use the core global + // options list. Later, the bootstrap object can also provide + // additional defaults if needed. The bootstrap command defaults + // will be merged into the command object again just before + // running it in bootstrap_and_dispatch(). + if ($bootstrap = drush_get_bootstrap_object()) { + $defaults = array_merge($defaults, $bootstrap->command_defaults()); + } + return $defaults; +} + +/** + * Translates description and other keys of a command definition. + * + * @param $command + * A command definition. + */ +function drush_command_translate(&$command) { + $command['description'] = _drush_command_translate($command['description']); + $keys = array('arguments', 'options', 'examples', 'sections'); + foreach ($keys as $key) { + foreach ($command[$key] as $k => $v) { + if (is_array($v)) { + $v['description'] = _drush_command_translate($v['description']); + } + else { + $v = _drush_command_translate($v); + } + $command[$key][$k] = $v; + } + } +} + +/** + * Helper function for drush_command_translate(). + * + * @param $source + * String or array. + */ +function _drush_command_translate($source) { + return is_array($source) ? call_user_func_array('dt', $source) : dt($source); +} + +/** + * Matches a commands array, as returned by drush_get_arguments, with the + * current command table. + * + * Note that not all commands may be discoverable at the point-of-call, + * since Drupal modules can ship commands as well, and they are + * not available until after bootstrapping. + * + * drush_parse_command returns a normalized command descriptor, which + * is an associative array. Some of its entries are: + * - callback arguments: an array of arguments to pass to the calback. + * - callback: the function to run. Usually, this is 'drush_command', which + * will determine the primary hook for the function automatically. Only + * specify a callback function if you need many commands to call the same + * function (e.g. drush_print_file). + * - invoke hooks: If TRUE (the default), Drush will invoke all of the pre and + * post hooks for this command. Set to FALSE to suppress hooks. This setting + * is ignored unless the command 'callback' is also set. + * - primary function: Drush will copy the 'callback' parameter here if + * necessary. This value should not be set explicitly; use 'callback' instead. + * - description: description of the command. + * - arguments: an array of arguments that are understood by the command. for help texts. + * - required-arguments: The minimum number of arguments that are required, or TRUE if all are required. + * - options: an array of options that are understood by the command. for help texts. + * - global-options: a list of options from the set of Drush global options (@see: + * drush_get_global_options()) that relate to this command. The help for these + * options will be included in the help output for this command. + * - examples: an array of examples that are understood by the command. for help texts. + * - scope: one of 'system', 'project', 'site'. + * - bootstrap: drupal bootstrap level (depends on Drupal major version). -1=no_bootstrap. + * - core: Drupal major version required. + * - drupal dependencies: drupal modules required for this command. + * - drush dependencies: other drush command files required for this command. + * - handle-remote-commands: set to TRUE if `drush @remote mycommand` should be executed + * locally rather than remotely dispatched. When this mode is set, the target site + * can be obtained via: + * drush_get_context('DRUSH_TARGET_SITE_ALIAS') + * - remote-tty: set to TRUE if Drush should force ssh to allocate a pseudo-tty + * when this command is being called remotely. Important for interactive commands. + * Remote commands that allocate a psedo-tty always print "Connection closed..." when done. + * - strict-option-handling: set to TRUE if drush should strictly separate local command + * cli options from the global options. Usually, drush allows global cli options and + * command cli options to be interspersed freely on the commandline. For commands where + * this flag is set, options are separated, with global options comming before the + * command names, and command options coming after, like so: + * drush --global-options command --command-options + * In this mode, the command options are no longer available via drush_get_option(); + * instead, they can be retrieved via: + * $args = drush_get_original_cli_args_and_options(); + * $args = drush_get_context('DRUSH_COMMAND_ARGS', array()); + * In this case, $args will contain the command args and options literally, exactly as they + * were entered on the command line, and in the same order as they appeared. + * - 'outputformat': declares the data format to be used to render the + * command result. In addition to the output format engine options + * listed below, each output format type can take additional metadata + * items that control the way that the output is rendered. See the + * comment in each particular output format class for information. The + * Drush core output format engines can be found in commands/core/outputformat. + * - 'default': The default type to render output as. If declared, the + * command should not print any output on its own, but instead should + * return a data structure (usually an associative array) that can + * be rendered by the output type selected. + * - 'pipe-format': When the command is executed in --pipe mode, the + * command output will be rendered by the format specified by the + * pipe-format item instead of the default format. Note that in + * either event, the user may specify the format to use via the + * --format command-line option. + * - 'formatted-filter': specifies a function callback that will be + * used to filter the command result if the selected output formatter + * is NOT declared to be machine-parsable. "table" is an example of + * an output format that is not machine-parsable. + * - 'parsable-filter': function callback that will be used to filter the + * command result if the selected output formatter is declared to be + * machine-parsable. "var_export" is an example of an output format that + * is machine-parsable. + * - 'output-data-type': An identifier representing the data structure that + * the command returns. @see outputformat_drush_engine_outputformat() for + * a description of the supported values. + * - 'field-labels': A mapping from machine name to human-readable name + * for all of the fields in a table-format command result. All + * possible field names should appear in this list. + * - 'fields-default': A list of the machine names of the fields that + * should be displayed by default in tables. + * - 'private-fields': A list of any fields that contain sensitive + * information, such as passwords. By default, Drush will hide private + * fields before printing the results to the console, but will include + * them in backend invoke results. Use --show-passwords to display. + * - 'column-widths': A mapping from field machine name to the column width + * that should be used in table output. Drush will automatically + * calculate the width of any field not listed here based on the length + * of the data items in it. + * - engines: declares information on Drush engines the command will load. + * Available engines can vary by command type. + * + * @return bool|array + * A command definition. + */ +function drush_parse_command() { + $args = drush_get_arguments(); + $command = FALSE; + + // Get a list of all implemented commands. + $implemented = drush_get_commands(); + if (!empty($args) && isset($implemented[$args[0]])) { + $command = $implemented[$args[0]]; + $arguments = array_slice($args, 1); + } + + // We have found a command that matches. Set the appropriate values. + if ($command) { + // Special case. Force help command if --help option was specified. + if (drush_get_option('help')) { + $arguments = array($command['command']); + $command = $implemented['helpsingle']; + $command['arguments'] = $arguments; + $command['allow-additional-options'] = TRUE; + } + else { + _drush_prepare_command($command, $arguments); + } + drush_set_command($command); + } + return $command; +} + +/** + * Called by drush_parse_command(). If a command is dispatched + * directly by drush_dispatch(), then drush_dispatch() will call + * this function. + */ +function _drush_prepare_command(&$command, $arguments = array()) { + // Drush overloads $command['arguments']; save the argument description + if (!isset($command['argument-description'])) { + $command['argument-description'] = $command['arguments']; + } + // Merge specified callback arguments, which precede the arguments passed on the command line. + if (isset($command['callback arguments']) && is_array($command['callback arguments'])) { + $arguments = array_merge($command['callback arguments'], $arguments); + } + $command['arguments'] = $arguments; +} + +/** + * Invoke a hook in all available command files that implement it. + * + * @see drush_command_invoke_all_ref() + * + * @param $hook + * The name of the hook to invoke. + * @param ... + * Arguments to pass to the hook. + * @return + * An array of return values of the hook implementations. If commands return + * arrays from their implementations, those are merged into one array. + */ +function drush_command_invoke_all() { + $args = func_get_args(); + if (count($args) == 1) { + $args[] = NULL; + } + $reference_value = $args[1]; + $args[1] = &$reference_value; + + return call_user_func_array('drush_command_invoke_all_ref', $args); +} + +/** + * A drush_command_invoke_all() that wants the first parameter to be passed by reference. + * + * @see drush_command_invoke_all() + */ +function drush_command_invoke_all_ref($hook, &$reference_parameter) { + $args = func_get_args(); + array_shift($args); + // Insure that call_user_func_array can alter first parameter + $args[0] = &$reference_parameter; + $return = array(); + $modules = drush_command_implements($hook); + if ($hook != 'drush_invoke_alter') { + // Allow modules to control the order of hook invocations + drush_command_invoke_all_ref('drush_invoke_alter', $modules, $hook); + } + foreach ($modules as $module) { + $function = $module .'_'. $hook; + $result = call_user_func_array($function, $args); + if (isset($result) && is_array($result)) { + $return = array_merge_recursive($return, $result); + } + else if (isset($result)) { + $return[] = $result; + } + } + return $return; +} + +/** + * Determine which command files are implementing a hook. + * + * @param $hook + * The name of the hook (e.g. "drush_help" or "drush_command"). + * + * @return + * An array with the names of the command files which are implementing this hook. + */ +function drush_command_implements($hook) { + $implementations[$hook] = array(); + $list = drush_commandfile_list(); + foreach ($list as $commandfile => $file) { + if (drush_command_hook($commandfile, $hook)) { + $implementations[$hook][] = $commandfile; + } + } + return (array)$implementations[$hook]; +} + +/** + * @param string + * name of command to check. + * + * @return boolean + * TRUE if the given command has an implementation. + */ +function drush_is_command($command) { + $commands = drush_get_commands(); + return isset($commands[$command]); +} + +/** + * @param string + * name of command or command alias. + * + * @return string + * Primary name of command. + */ +function drush_command_normalize_name($command_name) { + $commands = drush_get_commands(); + return isset($commands[$command_name]) ? $commands[$command_name]['command'] : $command_name; +} + +/** + * Collect a list of all available drush command files. + * + * Scans the following paths for drush command files: + * + * - The "/path/to/drush/commands" folder. + * - Folders listed in the 'include' option (see example.drushrc.php). + * - The system-wide drush commands folder, e.g. /usr/share/drush/commands + * - The ".drush" folder in the user's HOME folder. + * - /drush and sites/all/drush in current Drupal site. + * - Folders belonging to enabled modules in the current Drupal site. + * + * A Drush command file is a file that matches "*.drush.inc". + * + * @see drush_scan_directory() + * + * @return + * An associative array whose keys and values are the names of all available + * command files. + */ +function drush_commandfile_list() { + return commandfiles_cache()->get(); +} + +function _drush_find_commandfiles($phase, $phase_max = FALSE) { + drush_log(dt("Find command files for phase !phase (max=!max)", array('!phase' => $phase, '!max' => (string)$phase_max)), LogLevel::DEBUG); + if ($bootstrap = drush_get_bootstrap_object()) { + $searchpath = $bootstrap->commandfile_searchpaths($phase, $phase_max); + _drush_add_commandfiles($searchpath, $phase); + annotationcommand_adapter_discover($searchpath, $phase, $phase_max); + } +} + +function _drush_add_commandfiles($searchpath, $phase = NULL, $reset = FALSE) { + static $evaluated = array(); + $needs_sort = FALSE; + + if (count($searchpath)) { + if (!$reset) { + // Assemble a cid specific to the bootstrap phase and searchpaths. + // Bump $cf_version when making a change to a dev version of Drush + // that invalidates the commandfile cache. + $cf_version = 8; + $cid = drush_get_cid('commandfiles-' . $phase, array(), array_merge($searchpath, array($cf_version))); + $command_cache = drush_cache_get($cid); + if (isset($command_cache->data)) { + $cached_list = $command_cache->data; + // If we want to temporarily ignore modules via 'ignored-modules', + // then we need to take these out of the cache as well. + foreach (drush_get_option_list('ignored-modules') as $ignored) { + unset($cached_list[$ignored]); + } + } + } + + // Build a list of all of the modules to attempt to load. + // Start with any modules deferred from a previous phase. + $list = commandfiles_cache()->deferred(); + if (isset($cached_list)) { + $list = array_merge($list, $cached_list); + } + else { + // Scan for drush command files; add to list for consideration if found. + foreach (array_unique($searchpath) as $path) { + if (is_dir($path)) { + $nomask = array_merge(drush_filename_blacklist(), drush_get_option_list('ignored-modules')); + $dmv = DRUSH_MAJOR_VERSION; + $files = drush_scan_directory($path, "/\.drush($dmv|)\.inc$/", $nomask); + foreach ($files as $filename => $info) { + $module = basename($filename); + $module = preg_replace('/\.*drush[0-9]*\.inc/', '', $module); + // Only try to bootstrap modules that we have never seen before. + if (!array_key_exists($module, $evaluated) && file_exists($filename)) { + $evaluated[$module] = TRUE; + $list[$module] = Path::canonicalize($filename); + } + } + } + } + if (isset($cid)) { + drush_cache_set($cid, $list); + } + } + // Check to see if the commandfile is valid for this version of Drupal + // and is still present on filesystem (in case of cached commandfile list). + foreach ($list as $module => $filename) { + // Only try to require if the file exists. If not, a file from the + // command file cache may not be available anymore, in which case + // we rebuild the cache for this phase. + if (file_exists($filename)) { + // Avoid realpath() here as Drush commandfiles can have phar:// locations. + $load_command = commandfiles_cache()->add($filename); + if ($load_command) { + $needs_sort = TRUE; + } + } + elseif (!$reset) { + _drush_add_commandfiles($searchpath, $phase, TRUE); + $needs_sort = FALSE; + } + } + + if ($needs_sort) { + commandfiles_cache()->sort(); + } + } +} + +/** + * Substrings to ignore during commandfile and site alias searching. + */ +function drush_filename_blacklist() { + $blacklist = array('.', '..', 'drush_make', 'examples', 'tests', 'disabled', 'gitcache', 'cache'); + for ($v=4; $v<=(DRUSH_MAJOR_VERSION)+3; ++$v) { + if ($v != DRUSH_MAJOR_VERSION) { + $blacklist[] = 'drush' . $v; + } + } + $blacklist = array_merge($blacklist, drush_get_option_list('exclude')); + return $blacklist; +} + +/** + * Conditionally include files based on the command used. + * + * Steps through each of the currently loaded commandfiles and + * loads an optional commandfile based on the key. + * + * When a command such as 'pm-enable' is called, this + * function will find all 'enable.pm.inc' files that + * are present in each of the commandfile directories. + */ +function drush_command_include($command) { + $include_files = drush_command_get_includes($command); + foreach($include_files as $filename => $commandfile) { + drush_log(dt('Including !filename', array('!filename' => $filename)), LogLevel::BOOTSTRAP); + include_once($filename); + } +} + +function drush_command_get_includes($command) { + $include_files = array(); + $parts = explode('-', $command); + $command = implode(".", array_reverse($parts)); + + $commandfiles = drush_commandfile_list(); + $options = array(); + foreach ($commandfiles as $commandfile => $file) { + $filename = sprintf("%s/%s.inc", dirname($file), $command); + if (file_exists($filename)) { + $include_files[$filename] = $commandfile; + } + } + return $include_files; +} + +/** + * Conditionally include default options based on the command used. + */ +function drush_command_default_options($command = NULL) { + $command_default_options = drush_get_context('command-specific'); + drush_command_set_command_specific($command_default_options, $command); +} + +function drush_sitealias_command_default_options($site_record, $prefix, $command = NULL) { + if (isset($site_record) && array_key_exists($prefix . 'command-specific', $site_record)) { + drush_command_set_command_specific($site_record[$prefix . 'command-specific'], $command); + } + return FALSE; +} + +function drush_command_set_command_specific_options($prefix, $command = NULL) { + $command_default_options = drush_get_option($prefix . 'command-specific', array()); + drush_command_set_command_specific($command_default_options, $command); +} + +function drush_command_set_command_specific($command_default_options, $command = NULL) { + if (!$command) { + $command = drush_get_command(); + } + if ($command) { + // Look for command-specific options for this command + // keyed both on the command's primary name, and on each + // of its aliases. + $options_were_set = _drush_command_set_default_options($command_default_options, $command['command']); + if (isset($command['aliases']) && count($command['aliases'])) { + foreach ($command['aliases'] as $alias) { + $options_were_set += _drush_command_set_default_options($command_default_options, $alias); + } + } + // If we set or cleared any options, go back and re-bootstrap any global + // options such as -y and -v. + if (!empty($options_were_set)) { + _drush_preflight_global_options(); + } + // If the command uses strict option handling, back out any global + // options that were set. + if ($command['strict-option-handling']) { + $global_options = drush_get_global_options(); + foreach ($options_were_set as $key) { + if (array_key_exists($key, $global_options)) { + if (!array_key_exists('context', $global_options[$key])) { + $strict_options_warning =& drush_get_context('DRUSH_STRICT_OPTIONS_WARNING', array()); + if (!array_key_exists($key, $strict_options_warning)) { + drush_log(dt("Global option --!option not supported in command-specific options for command !command due to a limitation in strict option handling.", array('!option' => $key, '!command' => $command['command'])), LogLevel::WARNING); + $strict_options_warning[$key] = TRUE; + } + } + drush_unset_option($key, 'specific'); + } + } + } + } +} + +function _drush_command_set_default_options($command_default_options, $command) { + $options_were_set = array(); + if (array_key_exists($command, $command_default_options)) { + foreach ($command_default_options[$command] as $key => $value) { + // We set command-specific options in their own context + // that is higher precedence than the various config file + // context, but lower than command-line options. + if (!drush_get_option('no-' . $key, FALSE)) { + drush_set_option($key, $value, 'specific'); + $options_were_set[] = $key; + } + } + } + return $options_were_set; +} + +/** + * Return all of the command-specific options defined in the given + * options set for the specified command name. Note that it is valid + * to use the command name alias rather than the primary command name, + * both in the parameter to this function, and in the options set. + */ +function drush_command_get_command_specific_options($options, $command_name, $prefix = '') { + $result = array(); + $command_name = drush_command_normalize_name($command_name); + if (isset($options[$prefix . 'command-specific'])) { + foreach ($options[$prefix . 'command-specific'] as $options_for_command => $values) { + if ($command_name == drush_command_normalize_name($options_for_command)) { + $result = array_merge($result, $values); + } + } + } + return $result; +} + +/** + * Return the original cli args and options, exactly as they + * appeared on the command line, and in the same order. + * Any command-specific options that were set will also + * appear in this list, appended at the very end. + * + * The args and options returned are raw, and must be + * escaped as necessary before use. + */ +function drush_get_original_cli_args_and_options($command = NULL) { + $args = drush_get_context('DRUSH_COMMAND_ARGS', array()); + $command_specific_options = drush_get_context('specific'); + if ($command == NULL) { + $command = drush_get_command(); + } + $command_options = ($command == NULL) ? array() : _drush_get_command_options($command); + foreach ($command_specific_options as $key => $value) { + if (!array_key_exists($key, $command_options)) { + if (($value === TRUE) || (!isset($value))) { + $args[] = "--$key"; + } + else { + $args[] = "--$key=$value"; + } + } + } + return $args; +} + +/** + * Determine whether a command file implements a hook. + * + * @param $module + * The name of the module (without the .module extension). + * @param $hook + * The name of the hook (e.g. "help" or "menu"). + * @return + * TRUE if the the hook is implemented. + */ +function drush_command_hook($commandfile, $hook) { + return function_exists($commandfile . '_' . $hook); +} + +/** + * Check that a command is valid for the current bootstrap phase. + * + * @param $command + * Command to check. Any errors will be added to the 'bootstrap_errors' element. + * + * @return + * TRUE if command is valid. + */ +function drush_enforce_requirement_bootstrap_phase(&$command) { + $valid = array(); + $current_phase = drush_get_context('DRUSH_BOOTSTRAP_PHASE'); + if ($command['bootstrap'] <= $current_phase) { + return TRUE; + } + // TODO: provide description text for each bootstrap level so we can give + // the user something more helpful and specific here. + $command['bootstrap_errors']['DRUSH_COMMAND_INSUFFICIENT_BOOTSTRAP'] = dt('Command !command needs a higher bootstrap level to run - you will need to invoke drush from a more functional Drupal environment to run this command.', array('!command' => $command['command'])); +} + +/** + * Check that a command has its declared drush dependencies available or have no + * dependencies. Drush dependencies are helpful when a command is invoking + * another command, or implementing its API. + * + * @param $command + * Command to check. Any errors will be added to the 'bootstrap_errors' element. + * @return + * TRUE if dependencies are met. + */ +function drush_enforce_requirement_drush_dependencies(&$command) { + // If there are no drush dependencies, then do nothing. + if (!empty($command['drush dependencies'])) { + $commandfiles = drush_commandfile_list(); + foreach ($command['drush dependencies'] as $dependency) { + if (!isset($commandfiles[$dependency])) { + $dt_args = array( + '!command' => $command['command'], + '!dependency' => "$dependency.drush.inc", + ); + $command['bootstrap_errors']['DRUSH_COMMANDFILE_DEPENDENCY_ERROR'] = dt('Command !command needs the following drush command file to run: !dependency.', $dt_args); + return FALSE; + } + } + } + return TRUE; +} + +/** + * Check that a command is valid for the current major version of core. Handles + * explicit version numbers and 'plus' numbers like 7+ (compatible with 7,8 ...). + * + * @param $command + * Command to check. Any errors will be added to the 'bootstrap_errors' element. + * + * @return + * TRUE if command is valid. + */ +function drush_enforce_requirement_core(&$command) { + $major = drush_drupal_major_version(); + if (!$core = $command['core']) { + return TRUE; + } + foreach ($core as $compat) { + if ($compat == $major) { + return TRUE; + } + elseif (substr($compat, -1) == '+' && $major >= substr($compat, 0, strlen($compat)-1)) { + return TRUE; + } + } + $versions = array_pop($core); + if (!empty($core)) { + $versions = implode(', ', $core) . dt(' or ') . $versions; + } + $command['bootstrap_errors']['DRUSH_COMMAND_CORE_VERSION_ERROR'] = dt('Command !command requires Drupal core version !versions to run.', array('!command' => $command['command'], '!versions' => $versions)); +} + +/** + * Check if a shell alias exists for current request. If so, re-route to + * core-execute and pass alias value along with rest of CLI arguments. + */ +function drush_shell_alias_replace($target_site_alias) { + $escape = TRUE; + $args = drush_get_arguments(); + $argv = drush_get_context('argv'); + $first = current($args); + // @todo drush_get_option is awkward here. + $shell_aliases = drush_get_context('shell-aliases', array()); + if (isset($shell_aliases[$first])) { + // Shell alias found for first argument in the request. + $alias_value = $shell_aliases[$first]; + if (!is_array($alias_value)) { + // Shell aliases can have embedded variables such as {{@target}} and {{%root}} + // that are replaced with the name of the target site alias, or the value of a + // path alias defined in the target site alias record. We only support replacements + // when the alias value is a string; if it is already broken out into an array, + // then the values therein are used literally. + $alias_variables = array( '{{@target}}' => '@none' ); + if ($target_site_alias) { + $alias_variables = array( '{{@target}}' => $target_site_alias ); + $target = drush_sitealias_get_record($target_site_alias); + foreach ($target as $key => $value) { + if (!is_array($value)) { + $alias_variables['{{' . $key . '}}'] = $value; + } + } + if (array_key_exists('path-aliases', $target)) { + foreach ($target['path-aliases'] as $key => $value) { + // n.b. $key will contain something like "%root" or "%files". + $alias_variables['{{' . $key . '}}'] = $value; + } + } + } + $alias_value = str_replace(array_keys($alias_variables), array_values($alias_variables), $alias_value); + // Check for unmatched replacements + $matches = array(); + $match_result = preg_match('/{{[%@#]*[a-z0-9.]*}}/', $alias_value, $matches); + if ($match_result) { + $unmatched_replacements = implode(', ', $matches); + $unmatched_replacements = preg_replace('/[{}]/', '', $unmatched_replacements); + return drush_set_error('DRUSH_SHELL_ALIAS_UNMATCHED_REPLACEMENTS', dt('The shell alias @alias-name uses replacements "@unmatched". You must use this command with a site alias (e.g. `drush @myalias @alias-name ...`) that defines all of these variables.', array('@alias-name' => $first, '@unmatched' => $unmatched_replacements))); + } + if (substr($alias_value, 0, 1) == '!') { + $alias_value = ltrim($alias_value, '!'); + $alias_value = array('core-execute', $alias_value); + $escape = FALSE; + } + else { + // Respect quoting. See http://stackoverflow.com/questions/2202435/php-ex + $alias_value = str_getcsv($alias_value, ' '); + } + } + drush_log(dt('Shell alias found: !key => !value', array('!key' => $first, '!value' => implode(' ', $alias_value))), LogLevel::DEBUG); + $pos = array_search($first, $argv); + $number = 1; + if ($target_site_alias && ($argv[$pos - 1] == $target_site_alias)) { + --$pos; + ++$number; + } + array_splice($argv, $pos, $number, $alias_value); + if (!$escape) { + drush_set_option('escape', FALSE); + } + drush_set_context('argv', $argv); + drush_parse_args(); + _drush_preflight_global_options(); + } +} + +function commandfiles_cache() { + static $commandfiles_cache = NULL; + + if (!isset($commandfiles_cache)) { + $commandfiles_cache = new Drush\Command\Commandfiles(); + } + return $commandfiles_cache; +} diff --git a/vendor/drush/drush/includes/complete.inc b/vendor/drush/drush/includes/complete.inc new file mode 100644 index 0000000000..ba36b501ea --- /dev/null +++ b/vendor/drush/drush/includes/complete.inc @@ -0,0 +1,586 @@ +. The shell completion scripts should call + * "drush complete ", where is the full command line, which we take + * as input and use to produce a list of possible completions for the + * current/next word, separated by newlines. Typically, when multiple + * completions are returned the shell will display them to the user in a concise + * format - but when a single completion is returned it will autocomplete. + * + * We provide completion for site aliases, commands, shell aliases, options, + * engines and arguments. Displaying all of these when the last word has no + * characters yet is not useful, as there are too many items. Instead we filter + * the possible completions based on position, in a similar way to git. + * For example: + * - We only display site aliases and commands if one is not already present. + * - We only display options if the user has already entered a hyphen. + * - We only display global options before a command is entered, and we only + * display command specific options after the command (Drush itself does not + * care about option placement, but this approach keeps things more concise). + * + * Below is typical output of complete in different situations. Tokens in square + * brackets are optional, and [word] will filter available options that start + * with the same characters, or display all listed options if empty. + * drush --[word] : Output global options + * drush [word] : Output site aliases, sites, commands and shell aliases + * drush [@alias] [word] : Output commands + * drush [@alias] command [word] : Output command specific arguments + * drush [@alias] command --[word] : Output command specific options + * + * Because the purpose of autocompletion is to make the command line more + * efficient for users we need to respond quickly with the list of completions. + * To do this, we call drush_complete() early in the Drush bootstrap, and + * implement a simple caching system. + * + * To generate the list of completions, we set up the Drush environment as if + * the command was called on it's own, parse the command using the standard + * Drush functions, bootstrap the site (if any) and collect available + * completions from various sources. Because this can be somewhat slow, we cache + * the results. The cache strategy aims to balance accuracy and responsiveness: + * - We cache per site, if a site is available. + * - We generate (and cache) everything except arguments at the same time, so + * subsequent completions on the site don't need any bootstrap. + * - We generate and cache arguments on-demand, since these can often be + * expensive to generate. Arguments are also cached per-site. + * + * For argument completions, commandfiles can implement + * COMMANDFILE_COMMAND_complete() returning an array containing a key 'values' + * containing an array of all possible argument completions for that command. + * For example, return array('values' => array('aardvark', 'aardwolf')) offers + * the words 'aardvark' and 'aardwolf', or will complete to 'aardwolf' if the + * letters 'aardw' are already present. Since command arguments are cached, + * commandfiles can bootstrap a site or perform other somewhat time consuming + * activities to retrieve the list of possible arguments. Commands can also + * clear the cache (or just the "arguments" cache for their command) when the + * completion results have likely changed - see drush_complete_cache_clear(). + * + * Commandfiles can also return a special optional element in their array with + * the key 'files' that contains an array of patterns/flags for the glob() + * function. These are used to produce file and directory completions (the + * results of these are not cached, since this is a fast operation). + * See http://php.net/glob for details of valid patterns and flags. + * For example the following will complete the command arguments on all + * directories, as well as files ending in tar.gz: + * return array( + * 'files' => array( + * 'directories' => array( + * 'pattern' => '*', + * 'flags' => GLOB_ONLYDIR, + * ), + * 'tar' => array( + * 'pattern' => '*.tar.gz', + * ), + * ), + * ); + * + * To check completion results without needing to actually trigger shell + * completion, you can call this manually using a command like: + * + * drush --early=includes/complete.inc [--complete-debug] drush [@alias] [command]... + * + * If you want to simulate the results of pressing tab after a space (i.e. + * and empty last word, include '' on the end of your command: + * + * drush --early=includes/complete.inc [--complete-debug] drush '' + */ + +/** + * Produce autocomplete output. + * + * Determine position (is there a site-alias or command set, and are we trying + * to complete an option). Then produce a list of completions for the last word + * and output them separated by newlines. + */ +function drush_early_complete() { + // We use a distinct --complete-debug option to avoid unwanted debug messages + // being printed when users use this option for other purposes in the command + // they are trying to complete. + drush_set_option(LogLevel::DEBUG, FALSE); + if (drush_get_option('complete-debug', FALSE)) { + drush_set_context('DRUSH_DEBUG', TRUE); + } + // Set up as if we were running the command, and attempt to parse. + $argv = drush_complete_process_argv(); + if ($alias = drush_get_context('DRUSH_TARGET_SITE_ALIAS')) { + $set_sitealias_name = $alias; + $set_sitealias = drush_sitealias_get_record($alias); + } + + // Arguments have now had site-aliases and options removed, so we take the + // first item as our command. We need to know if the command is valid, so that + // we know if we are supposed to complete an in-progress command name, or + // arguments for a command. We do this by checking against our per-site cache + // of command names (which will only bootstrap if the cache needs to be + // regenerated), rather than drush_parse_command() which always requires a + // site bootstrap. + $arguments = drush_get_arguments(); + $set_command_name = NULL; + if (isset($arguments[0]) && in_array($arguments[0] . ' ', drush_complete_get('command-names'))) { + $set_command_name = $arguments[0]; + } + // We unset the command if it is "help" but that is not explicitly found in + // args, since Drush sets the command to "help" if no command is specified, + // which prevents completion of global options. + if ($set_command_name == 'help' && !array_search('help', $argv)) { + $set_command_name = NULL; + } + + // Determine the word we are trying to complete, and if it is an option. + $last_word = end($argv); + $word_is_option = FALSE; + if (!empty($last_word) && $last_word[0] == '-') { + $word_is_option = TRUE; + $last_word = ltrim($last_word, '-'); + } + + $completions = array(); + if (!$set_command_name) { + // We have no command yet. + if ($word_is_option) { + // Include global option completions. + $completions += drush_hyphenate_options(drush_complete_match($last_word, drush_complete_get('options'))); + } + else { + if (empty($set_sitealias_name)) { + // Include site alias completions. + $completions += drush_complete_match($last_word, drush_complete_get('site-aliases')); + } + // Include command completions. + $completions += drush_complete_match($last_word, drush_complete_get('command-names')); + } + } + else { + if ($last_word == $set_command_name) { + // The user just typed a valid command name, but we still do command + // completion, as there may be other commands that start with the detected + // command (e.g. "make" is a valid command, but so is "make-test"). + // If there is only the single matching command, this will include in the + // completion list so they get a space inserted, confirming it is valid. + $completions += drush_complete_match($last_word, drush_complete_get('command-names')); + } + else if ($word_is_option) { + // Include command option completions. + $completions += drush_hyphenate_options(drush_complete_match($last_word, drush_complete_get('options', $set_command_name))); + } + else { + // Include command argument completions. + $argument_completion = drush_complete_get('arguments', $set_command_name); + if (isset($argument_completion['values'])) { + $completions += drush_complete_match($last_word, $argument_completion['values']); + } + if (isset($argument_completion['files'])) { + $completions += drush_complete_match_file($last_word, $argument_completion['files']); + } + } + } + + if (!empty($completions)) { + sort($completions); + return implode("\n", $completions); + } + return TRUE; +} + +/** + * This function resets the raw arguments so that Drush can parse the command as + * if it was run directly. The shell complete command passes the + * full command line as an argument, and the --early and --complete-debug + * options have to come before that, and the "drush" bash script will add a + * --php option on the end, so we end up with something like this: + * + * /path/to/drush.php --early=includes/complete.inc [--complete-debug] drush [@alias] [command]... --php=/usr/bin/php + * + * Note that "drush" occurs twice, and also that the second occurrence could be + * an alias, so we can't easily use it as to detect the start of the actual + * command. Hence our approach is to remove the initial "drush" and then any + * options directly following that - what remains is then the command we need + * to complete - i.e.: + * + * drush [@alias] [command]... + * + * Note that if completion is initiated following a space an empty argument is + * added to argv. So in that case argv looks something like this: + * array ( + * '0' => '/path/to/drush.php', + * '1' => '--early=includes/complete.inc', + * '2' => 'drush', + * '3' => 'topic', + * '4' => '', + * '5' => '--php=/usr/bin/php', + * ); + * + * @return $args + * Array of arguments (argv), excluding the initial command and options + * associated with the complete call. + * array ( + * '0' => 'drush', + * '1' => 'topic', + * '2' => '', + * ); + */ +function drush_complete_process_argv() { + $argv = drush_get_context('argv'); + // Remove the first argument, which will be the "drush" command. + array_shift($argv); + while (substr($arg = array_shift($argv), 0, 2) == '--') { + // We remove all options, until we get to a non option, which + // marks the start of the actual command we are trying to complete. + } + // Replace the initial argument. + array_unshift($argv, $arg); + // Remove the --php option at the end if exists (added by the "drush" shell + // script that is called when completion is requested). + if (substr(end($argv), 0, 6) == '--php=') { + array_pop($argv); + } + drush_set_context('argv', $argv); + drush_set_command(NULL); + // Reparse arguments, site alias, and command. + drush_parse_args(); + // Ensure the base environment is configured, so tests look in the correct + // places. + _drush_preflight_base_environment(); + // Check for and record any site alias. + drush_sitealias_check_arg(); + drush_sitealias_check_site_env(); + // We might have just changed our root--run drush_select_bootstrap_class() again. + $bootstrap = drush_select_bootstrap_class(); + + // Return the new argv for easy reference. + return $argv; +} + +/** + * Retrieves the appropriate list of candidate completions, then filters this + * list using the last word that we are trying to complete. + * + * @param string $last_word + * The last word in the argument list (i.e. the subject of completion). + * @param array $values + * Array of possible completion values to filter. + * + * @return array + * Array of candidate completions that start with the same characters as the + * last word. If the last word is empty, return all candidates. + */ +function drush_complete_match($last_word, $values) { + // Using preg_grep appears to be faster that strpos with array_filter/loop. + return preg_grep('/^' . preg_quote($last_word, '/') . '/', $values); +} + +/** + * Retrieves the appropriate list of candidate file/directory completions, + * filtered by the last word that we are trying to complete. + * + * @param string $last_word + * The last word in the argument list (i.e. the subject of completion). + * @param array $files + * Array of file specs, each with a pattern and flags subarray. + * + * @return array + * Array of candidate file/directory completions that start with the same + * characters as the last word. If the last word is empty, return all + * candidates. + */ +function drush_complete_match_file($last_word, $files) { + $return = array(); + if ($last_word[0] == '~') { + // Complete does not do tilde expansion, so we do it here. + // We shell out (unquoted) to expand the tilde. + drush_shell_exec('echo ' . $last_word); + return drush_shell_exec_output(); + } + + $dir = ''; + if (substr($last_word, -1) == '/' && is_dir($last_word)) { + // If we exactly match a trailing directory, then we use that as the base + // for the listing. We only do this if a trailing slash is present, since at + // this stage it is still possible there are other directories that start + // with this string. + $dir = $last_word; + } + else { + // Otherwise we discard the last part of the path (this is matched against + // the list later), and use that as our base. + $dir = dirname($last_word); + if (empty($dir) || $dir == '.' && $last_word != '.' && substr($last_word, 0, 2) != './') { + // We are looking at the current working directory, so unless the user is + // actually specifying a leading dot we leave the path empty. + $dir = ''; + } + else { + // In all other cases we need to add a trailing slash. + $dir .= '/'; + } + } + + foreach ($files as $spec) { + // We always include GLOB_MARK, as an easy way to detect directories. + $flags = GLOB_MARK; + if (isset($spec['flags'])) { + $flags = $spec['flags'] | GLOB_MARK; + } + $listing = glob($dir . $spec['pattern'], $flags); + $return = array_merge($return, drush_complete_match($last_word, $listing)); + } + // If we are returning a single item (which will become part of the final + // command), we need to use the full path, and we need to escape it + // appropriately. + if (count($return) == 1) { + // Escape common shell metacharacters (we don't use escapeshellarg as it + // single quotes everything, even when unnecessary). + $item = array_pop($return); + $item = preg_replace('/[ |&;()<>]/', "\\\\$0", $item); + if (substr($item, -1) !== '/') { + // Insert a space after files, since the argument is complete. + $item = $item . ' '; + } + $return = array($item); + } + else { + $firstchar = TRUE; + if ($last_word[0] == '/') { + // If we are working with absolute paths, we need to check if the first + // character of all the completions matches. If it does, then we pass a + // full path for each match, so the shell completes as far as it can, + // matching the behaviour with relative paths. + $pos = strlen($last_word); + foreach ($return as $id => $item) { + if ($item[$pos] !== $return[0][$pos]) { + $firstchar = FALSE; + continue; + } + } + } + foreach ($return as $id => $item) { + // For directories we leave the path alone. + $slash_pos = strpos($last_word, '/'); + if ($slash_pos === 0 && $firstchar) { + // With absolute paths where completions share initial characters, we + // pass in a resolved path. + $return[$id] = realpath($item); + } + else if ($slash_pos !== FALSE && $dir != './') { + // For files, we pass only the file name, ignoring the false match when + // the user is using a single dot relative path. + $return[$id] = basename($item); + } + } + } + return $return; +} + +/** + * Simple helper function to ensure options are properly hyphenated before we + * return them to the user (we match against the non-hyphenated versions + * internally). + * + * @param array $options + * Array of unhyphenated option names. + * + * @return array + * Array of hyphenated option names. + */ +function drush_hyphenate_options($options) { + foreach ($options as $key => $option) { + $options[$key] = '--' . ltrim($option, '--'); + } + return $options; +} + +/** + * Retrieves from cache, or generates a listing of completion candidates of a + * specific type (and optionally, command). + * + * @param string $type + * String indicating type of completions to return. + * See drush_complete_rebuild() for possible keys. + * @param string $command + * An optional command name if command specific completion is needed. + * + * @return array + * List of candidate completions. + */ +function drush_complete_get($type, $command = NULL) { + static $complete; + if (empty($command)) { + // Quick return if we already have a complete static cache. + if (!empty($complete[$type])) { + return $complete[$type]; + } + // Retrieve global items from a non-command specific cache, or rebuild cache + // if needed. + $cache = drush_cache_get(drush_complete_cache_cid($type), 'complete'); + if (isset($cache->data)) { + return $cache->data; + } + $complete = drush_complete_rebuild(); + return $complete[$type]; + } + // Retrieve items from a command specific cache. + $cache = drush_cache_get(drush_complete_cache_cid($type, $command), 'complete'); + if (isset($cache->data)) { + return $cache->data; + } + // Build argument cache - built only on demand. + if ($type == 'arguments') { + return drush_complete_rebuild_arguments($command); + } + // Rebuild cache of general command specific items. + if (empty($complete)) { + $complete = drush_complete_rebuild(); + } + if (!empty($complete['commands'][$command][$type])) { + return $complete['commands'][$command][$type]; + } + return array(); +} + +/** + * Rebuild and cache completions for everything except command arguments. + * + * @return array + * Structured array of completion types, commands and candidate completions. + */ +function drush_complete_rebuild() { + $complete = array(); + // Bootstrap to the site level (if possible) - commands may need to check + // the bootstrap level, and perhaps bootstrap higher in extraordinary cases. + drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION); + $commands = drush_get_commands(); + foreach ($commands as $command_name => $command) { + // Add command options and suboptions. + $options = array_keys($command['options']); + foreach ($command['sub-options'] as $option => $sub_options) { + $options = array_merge($options, array_keys($sub_options)); + } + $complete['commands'][$command_name]['options'] = $options; + } + // We treat shell aliases as commands for the purposes of completion. + $complete['command-names'] = array_merge(array_keys($commands), array_keys(drush_get_context('shell-aliases', array()))); + $site_aliases = _drush_sitealias_all_list(); + // TODO: Figure out where this dummy @0 alias is introduced. + unset($site_aliases['@0']); + $complete['site-aliases'] = array_keys($site_aliases); + $complete['options'] = array_keys(drush_get_global_options()); + + // We add a space following all completes. Eventually there may be some + // items (e.g. options that we know need values) where we don't add a space. + array_walk_recursive($complete, 'drush_complete_trailing_space'); + drush_complete_cache_set($complete); + return $complete; +} + +/** + * Helper callback function that adds a trailing space to completes in an array. + */ +function drush_complete_trailing_space(&$item, $key) { + if (!is_array($item)) { + $item = (string)$item . ' '; + } +} + +/** + * Rebuild and cache completions for command arguments. + * + * @param string $command + * A specific command to retrieve and cache arguments for. + * + * @return array + * Structured array of candidate completion arguments, keyed by the command. + */ +function drush_complete_rebuild_arguments($command) { + // Bootstrap to the site level (if possible) - commands may need to check + // the bootstrap level, and perhaps bootstrap higher in extraordinary cases. + drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_SITE); + $commands = drush_get_commands(); + $command_info = $commands[$command]; + if ($callback = $command_info['annotated-command-callback']) { + list($classname, $method) = $callback; + $commandInfo = new CommandInfo($classname, $method); + if ($callable = $commandInfo->getAnnotation('complete')) { + $result = call_user_func($callable); + } + } + else { + $hook = str_replace("-", "_", $command_info['command-hook']); + $result = drush_command_invoke_all($hook . '_complete'); + } + if (isset($result['values'])) { + // We add a space following all completes. Eventually there may be some + // items (e.g. comma separated arguments) where we don't add a space. + array_walk($result['values'], 'drush_complete_trailing_space'); + } + + $complete = array( + 'commands' => array( + $command => array( + 'arguments' => $result, + ) + ) + ); + drush_complete_cache_set($complete); + return $complete['commands'][$command]['arguments']; +} + +/** + * Stores caches for completions. + * + * @param $complete + * A structured array of completions, keyed by type, including a 'commands' + * type that contains all commands with command specific completions keyed by + * type. The array does not need to include all types - used by + * drush_complete_rebuild_arguments(). + */ +function drush_complete_cache_set($complete) { + foreach ($complete as $type => $values) { + if ($type == 'commands') { + foreach ($values as $command_name => $command) { + foreach ($command as $command_type => $command_values) { + drush_cache_set(drush_complete_cache_cid($command_type, $command_name), $command_values, 'complete', DRUSH_CACHE_TEMPORARY); + } + } + } + else { + drush_cache_set(drush_complete_cache_cid($type), $values, 'complete', DRUSH_CACHE_TEMPORARY); + } + } +} + +/** + * Generate a cache id. + * + * @param $type + * The completion type. + * @param $command + * The command name (optional), if completions are command specific. + * + * @return string + * Cache id. + */ +function drush_complete_cache_cid($type, $command = NULL) { + // For per-site caches, we include the site root and uri/path in the cache id + // hash. These are quick to determine, and prevents a bootstrap to site just + // to get a validated root and URI. Because these are not validated, there is + // the possibility of cache misses/ but they should be rare, since sites are + // normally referred to the same way (e.g. a site alias, or using the current + // directory), at least within a single command completion session. + // We also static cache them, since we may get differing results after + // bootstrap, which prevents the caches from being found on the next call. + static $root, $site; + if (empty($root)) { + $root = drush_get_option(array('r', 'root'), drush_locate_root()); + $site = drush_get_option(array('l', 'uri'), drush_site_path()); + } + return drush_get_cid('complete', array(), array($type, $command, $root, $site)); +} diff --git a/vendor/drush/drush/includes/context.inc b/vendor/drush/drush/includes/context.inc new file mode 100644 index 0000000000..a10996d486 --- /dev/null +++ b/vendor/drush/drush/includes/context.inc @@ -0,0 +1,669 @@ + realpath($config), '!context' => $context)), LogLevel::BOOTSTRAP); + $ret = @include_once($config); + if ($ret === FALSE) { + drush_log(dt('Cannot open drushrc "!config", ignoring.', array('!config' => realpath($config))), LogLevel::WARNING); + return FALSE; + } + if (!empty($options) || !empty($aliases) || !empty($command_specific) || !empty($override)) { + $options = array_merge(drush_get_context($context), $options); + $options['config-file'] = realpath($config); + + unset($options['site-aliases']); + $options['command-specific'] = array_merge(isset($command_specific) ? $command_specific : array(), isset($options['command-specific']) ? $options['command-specific'] : array()); + + drush_set_config_options($context, $options, $override); + } + } + } +} + +function drush_set_config_options($context, $options, $override = array()) { + // Copy 'config-file' into 'context-path', converting to an array to hold multiple values if necessary + if (isset($options['config-file'])) { + if (isset($options['context-path'])) { + $options['context-path'] = array_merge(array($options['config-file']), is_array($options['context-path']) ? $options['context-path'] : array($options['context-path'])); + } + else { + $options['context-path'] = $options['config-file']; + } + } + + // Take out $aliases and $command_specific options + drush_set_config_special_contexts($options); + + drush_set_context($context, $options); +} + +/** + * For all global options with a short form, convert all options in the option + * array that use the short form into the long form. + */ +function drush_expand_short_form_options(&$options) { + foreach (drush_get_global_options() as $name => $info) { + if (is_array($info)) { + // For any option with a short form, check to see if the short form was set in the + // options. If it was, then rename it to its long form. + if (array_key_exists('short-form', $info) && array_key_exists($info['short-form'], $options)) { + if (!array_key_exists($name, $options) || !array_key_exists('merge-pathlist', $info)) { + $options[$name] = $options[$info['short-form']]; + } + else { + $options[$name] = array_merge((array)$options[$name], (array)$options[$info['short-form']]); + } + unset($options[$info['short-form']]); + } + } + } +} + +/** + * There are certain options such as 'site-aliases' and 'command-specific' + * that must be merged together if defined in multiple drush configuration + * files. If we did not do this merge, then the last configuration file + * that defined any of these properties would overwrite all of the options + * that came before in previously-loaded configuration files. We place + * all of them into their own context so that this does not happen. + */ +function drush_set_config_special_contexts(&$options) { + if (isset($options) && is_array($options)) { + $has_command_specific = array_key_exists('command-specific', $options); + // Change the keys of the site aliases from 'alias' to '@alias' + if (array_key_exists('site-aliases', $options)) { + $user_aliases = $options['site-aliases']; + $options['site-aliases'] = array(); + foreach ($user_aliases as $alias_name => $alias_value) { + if (substr($alias_name,0,1) != '@') { + $alias_name = "@$alias_name"; + } + $options['site-aliases'][$alias_name] = $alias_value; + } + } + // Expand -s into --simulate, etc. + drush_expand_short_form_options($options); + + foreach (drush_get_global_options() as $name => $info) { + if (is_array($info)) { + // For any global option with the 'merge-pathlist' or 'merge-associative' flag, set its + // value in the specified context. The option is 'merged' because we + // load $options with the value from the context prior to including the + // configuration file. If the configuration file sets $option['special'][] = 'value', + // then the configuration will be merged. $option['special'] = array(...), on the + // other hand, will replace rather than merge the values together. + if ((array_key_exists($name, $options)) && (array_key_exists('merge', $info) || (array_key_exists('merge-pathlist', $info) || array_key_exists('merge-associative', $info)))) { + $context = array_key_exists('context', $info) ? $info['context'] : $name; + $cache =& drush_get_context($context); + $value = $options[$name]; + if (!is_array($value) && array_key_exists('merge-pathlist', $info)) { + $value = explode(PATH_SEPARATOR, $value); + } + if (array_key_exists('merge-associative', $info)) { + foreach ($value as $subkey => $subvalue) { + $cache[$subkey] = array_merge(isset($cache[$subkey]) ? $cache[$subkey] : array(), $subvalue); + } + } + else { + $cache = array_unique(array_merge($cache, $value)); + } + // Once we have moved the option to its special context, we + // can remove it from its option context -- unless 'propagate-cli-value' + // is set, in which case we need to let it stick around in options + // in case it is needed in backend invoke. + if (!array_key_exists('propagate-cli-value', $info)) { + unset($options[$name]); + } + } + } + } + + // If command-specific options were set and if we already have + // a command, then apply the command-specific options immediately. + if ($has_command_specific) { + drush_command_default_options(); + } + } +} + +/** + * Set a specific context. + * + * @param context + * Any of the default defined contexts. + * @param value + * The value to store in the context + * + * @return + * An associative array of the settings specified in the request context. + */ +function drush_set_context($context, $value) { + $cache =& drush_get_context($context); + $cache = $value; + return $value; +} + + +/** + * Return a specific context, or the whole context cache + * + * This function provides a storage mechanism for any information + * the currently running process might need to communicate. + * + * This avoids the use of globals, and constants. + * + * Functions that operate on the context cache, can retrieve a reference + * to the context cache using : + * $cache = &drush_get_context($context); + * + * This is a private function, because it is meant as an internal + * generalized API for writing static cache functions, not as a general + * purpose function to be used inside commands. + * + * Code that modifies the reference directly might have unexpected consequences, + * such as modifying the arguments after they have already been parsed and dispatched + * to the callbacks. + * + * @param context + * Optional. Any of the default defined contexts. + * + * @return + * If context is not supplied, the entire context cache will be returned. + * Otherwise only the requested context will be returned. + * If the context does not exist yet, it will be initialized to an empty array. + */ +function &drush_get_context($context = NULL, $default = NULL) { + static $cache = array(); + if (isset($context)) { + if (!isset($cache[$context])) { + $default = !isset($default) ? array() : $default; + $cache[$context] = $default; + } + return $cache[$context]; + } + return $cache; +} + +/** + * Set the arguments passed to the drush.php script. + * + * This function will set the 'arguments' context of the current running script. + * + * When initially called by drush_parse_args, the entire list of arguments will + * be populated. Once the command is dispatched, this will be set to only the remaining + * arguments to the command (i.e. the command name is removed). + * + * @param arguments + * Command line arguments, as an array. + */ +function drush_set_arguments($arguments) { + drush_set_context('arguments', $arguments); +} + +/** + * Gets the command line arguments passed to Drush. + * + * @return array + * An indexed array of arguments. Until Drush has dispatched the command, the + * array will include the command name as the first element. After that point + * the array will not include the command name. + * + * @see drush_set_arguments() + */ +function drush_get_arguments() { + return drush_get_context('arguments'); +} + +/** + * Set the command being executed. + * + * Drush_dispatch will set the correct command based on it's + * matching of the script arguments retrieved from drush_get_arguments + * to the implemented commands specified by drush_get_commands. + * + * @param + * A numerically indexed array of command components. + */ +function drush_set_command($command) { + drush_set_context('command', $command); +} + +/** + * Return the command being executed. + */ +function drush_get_command() { + return drush_get_context('command'); +} + +/** + * Get the value for an option. + * + * If the first argument is an array, then it checks whether one of the options + * exists and return the value of the first one found. Useful for allowing both + * -h and --host-name + * + * @param option + * The name of the option to get + * @param default + * Optional. The value to return if the option has not been set + * @param context + * Optional. The context to check for the option. If this is set, only this context will be searched. + */ +function drush_get_option($option, $default = NULL, $context = NULL) { + $value = NULL; + + if ($context) { + // We have a definite context to check for the presence of an option. + $value = _drush_get_option($option, drush_get_context($context)); + } + else { + // We are not checking a specific context, so check them in a predefined order of precedence. + $contexts = drush_context_names(); + + foreach ($contexts as $context) { + $value = _drush_get_option($option, drush_get_context($context)); + + if ($value !== NULL) { + return $value; + } + } + } + + if ($value !== NULL) { + return $value; + } + + return $default; +} + +/** + * Get the value for an option and return it as a list. If the + * option in question is passed on the command line, its value should + * be a comma-separated list (e.g. --flag=1,2,3). If the option + * was set in a drushrc.php file, then its value may be either a + * comma-separated list or an array of values (e.g. $option['flag'] = array('1', '2', '3')). + * + * @param option + * The name of the option to get + * @param default + * Optional. The value to return if the option has not been set + * @param context + * Optional. The context to check for the option. If this is set, only this context will be searched. + */ +function drush_get_option_list($option, $default = array(), $context = NULL) { + $result = drush_get_option($option, $default, $context); + + if (!is_array($result)) { + $result = array_map('trim', array_filter(explode(',', $result))); + } + + return $result; +} + +/** + * Get the value for an option, but first checks the provided option overrides. + * + * The feature of drush_get_option that allows a list of option names + * to be passed in an array is NOT supported. + * + * @param option_overrides + * An array to check for values before calling drush_get_option. + * @param option + * The name of the option to get. + * @param default + * Optional. The value to return if the option has not been set. + * @param context + * Optional. The context to check for the option. If this is set, only this context will be searched. + * + */ +function drush_get_option_override($option_overrides, $option, $default = NULL, $context = NULL) { + return drush_sitealias_get_option($option_overrides, $option, $default, '', $context); +} + +/** + * Get an option out of the specified alias. If it has not been + * set in the alias, then get it via drush_get_option. + * + * @param site_alias_record + * An array of options for an alias record. + * @param option + * The name of the option to get. + * @param default + * Optional. The value to return if the option does not exist in the site record and has not been set in a context. + * @param context + * Optional. The context to check for the option. If this is set, only this context will be searched. + */ +function drush_sitealias_get_option($site_alias_record, $option, $default = NULL, $prefix = '', $context = NULL) { + if (is_array($site_alias_record) && array_key_exists($option, $site_alias_record)) { + return $site_alias_record[$option]; + } + else { + return drush_get_option($prefix . $option, $default, $context); + } +} + +/** + * Get all of the values for an option in every context. + * + * @param option + * The name of the option to get + * @return + * An array whose key is the context name and value is + * the specific value for the option in that context. + */ +function drush_get_context_options($option, $flatten = FALSE) { + $result = array(); + + $contexts = drush_context_names(); + foreach ($contexts as $context) { + $value = _drush_get_option($option, drush_get_context($context)); + + if ($value !== NULL) { + if ($flatten && is_array($value)) { + $result = array_merge($value, $result); + } + else { + $result[$context] = $value; + } + } + } + + return $result; +} + +/** + * Retrieves a collapsed list of all options. + */ +function drush_get_merged_options() { + $contexts = drush_context_names(); + $cache = drush_get_context(); + $result = array(); + foreach (array_reverse($contexts) as $context) { + if (array_key_exists($context, $cache)) { + $result = array_merge($result, $cache[$context]); + } + } + + return $result; +} + +/** + * Retrieves a collapsed list of all options + * with a specified prefix. + */ +function drush_get_merged_prefixed_options($prefix) { + $merged_options = drush_get_merged_options(); + $result = array(); + foreach ($merged_options as $key => $value) { + if ($prefix == substr($key, 0, strlen($prefix))) { + $result[substr($key, strlen($prefix))] = $value; + } + } + + return $result; +} + +/** + * Helper function to recurse through possible option names + */ +function _drush_get_option($option, $context) { + if (is_array($option)) { + foreach ($option as $current) { + $current_value = _drush_get_option($current, $context); + if (isset($current_value)) { + return $current_value; + } + } + } + elseif (array_key_exists('no-' . $option, $context)) { + return FALSE; + } + elseif (array_key_exists($option, $context)) { + return $context[$option]; + } + + return NULL; +} + +/** + * Set an option in one of the option contexts. + * + * @param option + * The option to set. + * @param value + * The value to set it to. + * @param context + * Optional. Which context to set it in. + * @return + * The value parameter. This allows for neater code such as + * $myvalue = drush_set_option('http_host', $_SERVER['HTTP_HOST']); + * Without having to constantly type out the value parameter. + */ +function drush_set_option($option, $value, $context = 'process') { + $cache =& drush_get_context($context); + $cache[$option] = $value; + return $value; +} + +/** + * A small helper function to set the value in the default context + */ +function drush_set_default($option, $value) { + return drush_set_option($option, $value, 'default'); +} + +/** + * Remove a setting from a specific context. + * + * @param + * Option to be unset + * @param + * Context in which to unset the value in. + */ +function drush_unset_option($option, $context = NULL) { + if ($context != NULL) { + $cache =& drush_get_context($context); + if (array_key_exists($option, $cache)) { + unset($cache[$option]); + } + } + else { + $contexts = drush_context_names(); + + foreach ($contexts as $context) { + drush_unset_option($option, $context); + } + } +} + +/** + * Save the settings in a specific context to the applicable configuration file + * This is useful is you want certain settings to be available automatically the next time a command is executed. + * + * @param $context + * The context to save + */ +function drush_save_config($context) { + $filename = _drush_config_file($context); + if (is_array($filename)) { + $filename = $filename[0]; + } + + if ($filename) { + $cache = drush_get_context($context); + + $fp = fopen($filename, "w+"); + if (!$fp) { + return drush_set_error('DRUSH_PERM_ERROR', dt('Drushrc (!filename) could not be written', array('!filename' => $filename))); + } + else { + fwrite($fp, " $value) { + $line = "\n\$options['$key'] = ". var_export($value, TRUE) .';'; + fwrite($fp, $line); + } + fwrite($fp, "\n"); + fclose($fp); + drush_log(dt('Drushrc file (!filename) was written successfully', array('!filename' => $filename))); + return TRUE; + } + + } + return FALSE; +} diff --git a/vendor/drush/drush/includes/dbtng.inc b/vendor/drush/drush/includes/dbtng.inc new file mode 100644 index 0000000000..86f3fd07c1 --- /dev/null +++ b/vendor/drush/drush/includes/dbtng.inc @@ -0,0 +1,203 @@ + $data) { + if (is_array($data)) { + $new_keys = array(); + // $data can't have keys that are a prefix of other keys to + // prevent a corrupted result in the below calls to str_replace(). + // To avoid this we will use a zero padded indexed array of the values of $data. + $pad_length = strlen((string)count(array_values($data))); + foreach (array_values($data) as $i => $value) { + if (!is_numeric($value)) { + $value = "'".$value."'"; + } + $new_keys[$key . '_' . str_pad($i, $pad_length, '0', STR_PAD_LEFT)] = $value; + } + $where = preg_replace('#' . $key . '\b#', implode(', ', array_keys($new_keys)), $where); + unset($args[$key]); + $args += $new_keys; + } + else if (!is_numeric($data)) { + $args[$key] = "'".$data."'"; + } + } + + foreach ($args as $key => $data) { + $where = str_replace($key, $data, $where); + } + + return $where; +} + +/** + * A db_select() that works for any version of Drupal. + * + * @param $table + * String. The table to operate on. + * @param $fields + * Array or string. Fields affected in this operation. Valid string values are '*' or a single column name. + * @param $where + * String. WHERE snippet for the operation. It uses named placeholders. see @_drush_replace_query_placeholders() + * @param $args + * Array. Arguments for the WHERE snippet. + * @param $start + * Int. Value for OFFSET. + * @param $length + * Int. Value for LIMIT. + * @param $order_by_field + * String. Database column to order by. + * @param $order_by_direction + * ('ASC', 'DESC'). Ordering direction. + * @return + * A database resource. + */ +function drush_db_select($table, $fields = '*', $where = NULL, $args = NULL, $start = NULL, $length = NULL, $order_by_field = NULL, $order_by_direction = 'ASC') { + if (drush_drupal_major_version() >= 7) { + if (!is_array($fields)) { + if ($fields == '*') { + $fields = array(); + } + else { + $fields = array($fields); + } + } + $query = (drush_drupal_major_version() >= 9) ? + Database::getConnection()->select($table, 't') : + db_select($table, 't'); + $query->fields('t', $fields); + if (!empty($where)) { + $query = $query->where($where, $args); + } + if (isset($order_by_field)) { + $query = $query->orderBy($order_by_field, $order_by_direction); + } + if (isset($length)) { + $query = $query->range($start, $length); + } + return $query->execute(); + } + else { + if (is_array($fields)) { + $fields = implode(', ', $fields); + } + $query = "SELECT $fields FROM {{$table}}"; + if (!empty($where)) { + $where = _drush_replace_query_placeholders($where, $args); + $query .= " WHERE ".$where; + } + if (isset($order_by_field)) { + $query .= " ORDER BY $order_by_field $order_by_direction"; + } + if (isset($length)) { + $sql = drush_sql_get_class(); + $db_scheme = $sql->scheme(); + if ($db_scheme == 'oracle') + return db_query_range($query, $start, $length); + else { + $limit = " LIMIT $length"; + if (isset($start)) { + $limit .= " OFFSET $start"; + } + $query .= $limit; + } + } + + return db_query($query, $args); + } +} + +/** + * A db_delete() that works for any version of Drupal. + * + * @param $table + * String. The table to operate on. + * @param $where + * String. WHERE snippet for the operation. It uses named placeholders. see @_drush_replace_query_placeholders() + * @param $args + * Array. Arguments for the WHERE snippet. + * @return + * Affected rows (except on D7+mysql without a WHERE clause - returns TRUE) or FALSE. + */ +function drush_db_delete($table, $where = NULL, $args = NULL) { + if (drush_drupal_major_version() >= 9) { + if (!empty($where)) { + $query = Database::getConnection()->delete($table)->where($where, $args); + return $query->execute(); + } + return Database::getConnection()->truncate($table)->execute(); + } + elseif (drush_drupal_major_version() >= 7) { + if (!empty($where)) { + $query = db_delete($table)->where($where, $args); + return $query->execute(); + } + return db_truncate($table)->execute(); + } + else { + $query = "DELETE FROM {{$table}}"; + if (!empty($where)) { + $where = _drush_replace_query_placeholders($where, $args); + $query .= ' WHERE '.$where; + } + if (!db_query($query, $args)) { + return FALSE; + } + return db_affected_rows(); + } +} + +/** + * A db_result() that works consistently for any version of Drupal. + * + * @param + * A Database result object. + */ +function drush_db_result($result) { + switch (drush_drupal_major_version()) { + case 6: + return db_result($result); + case 7: + default: + return $result->fetchField(); + } +} + +/** + * A db_fetch_object() that works for any version of Drupal. + * + * @param + * A Database result object. + */ +function drush_db_fetch_object($result) { + return drush_drupal_major_version() >= 7 ? $result->fetchObject() : db_fetch_object($result); +} + +/** + * @} End of "defgroup dbfunctions". + */ diff --git a/vendor/drush/drush/includes/drupal.inc b/vendor/drush/drush/includes/drupal.inc new file mode 100644 index 0000000000..e95678a1fa --- /dev/null +++ b/vendor/drush/drush/includes/drupal.inc @@ -0,0 +1,300 @@ +get_version($drupal_root); + } + } + } + return $version; +} + +function drush_drupal_cache_clear_all() { + if (drush_drupal_major_version() >= 8) { + drush_invoke_process('@self', 'cache-rebuild'); + } + else { + drush_invoke_process('@self', 'cache-clear', array('all')); + } +} + +/** + * Returns the Drupal major version number (6, 7, 8 ...) + */ +function drush_drupal_major_version($drupal_root = NULL) { + $major_version = FALSE; + if ($version = drush_drupal_version($drupal_root)) { + $version_parts = explode('.', $version); + if (is_numeric($version_parts[0])) { + $major_version = (integer)$version_parts[0]; + } + } + return $major_version; +} + +/** + * b/c layer for format_date + */ +function drush_format_date($timestamp, $type = 'medium', $format = '', $timezone = NULL, $langcode = NULL) { + if (drush_drupal_major_version() >= 9) { + return \Drupal::service('date.formatter') + ->format($timestamp, $type, $format, $timezone, $langcode); + } + return format_date($timestamp, $type, $format, $timezone, $langcode); +} + +/** + * Returns the path of a configuration directory. + * + * Configuration directories are configured using $config_directories in + * settings.php. + * + * @param string $type + * The type of config directory to return. + * + * @return string + * The configuration directory path. + * + * @throws \Exception + */ +function drush_config_get_config_directory($type = 'sync') { + if (drush_drupal_major_version() >= 9) { + if ($type != 'sync') { + return ''; + } + return Settings::get('config_sync_directory', FALSE); + } + return config_get_config_directory($type); +} + +/** + * Log Drupal watchdog() calls. + * + * A sneaky implementation of hook_watchdog(), for D6/D7. + */ +function system_watchdog($log_entry) { + // Transform non informative severity levels to 'error' for compatibility with _drush_print_log. + // Other severity levels are coincident with the ones we use in drush. + if (drush_drupal_major_version() >= 6 && $log_entry['severity'] <= 2) { + $severity = 'error'; + } + else { + drush_include_engine('drupal', 'environment'); + $levels = drush_watchdog_severity_levels(); + $severity = $levels[$log_entry['severity']]; + } + // Format the message. + if (is_array($log_entry['variables'])) { + $message = strtr($log_entry['message'], $log_entry['variables']); + } + else { + $message = $log_entry['message']; + } + + // decode_entities() only loaded after FULL bootstrap. + if (function_exists('decode_entities')) { + $message = decode_entities($message); + } + $message = strip_tags($message); + + // Log or print or ignore. Just printing saves memory but thats rarely needed. + switch (drush_get_option('watchdog', 'log')) { + case 'log': + drush_log('WD '. $log_entry['type'] . ': ' . $message, $severity); + break; + case 'print': + // Disable in backend mode since it logs output and the goal is to conserve memory. + // @see _drush_bootstrap_drush(). + if (ob_get_length() === FALSE) { + drush_print('WD '. $severity . ' ' . $log_entry['type'] . ': ' . $message); + } + break; + default: + // Do nothing. + } +} + +/** + * Log the return value of Drupal hook_update_n functions. + * + * This is used during install and update to log the output + * of the update process to the logging system. + */ +function _drush_log_update_sql($ret) { + if (is_array($ret) && count($ret)) { + foreach ($ret as $info) { + if (is_array($info)) { + if (!$info['success']) { + drush_set_error('DRUPAL_UPDATE_FAILED', $info['query']); + } + else { + drush_log($info['query'], ($info['success']) ? LogLevel::SUCCESS : LogLevel::ERROR); + } + } + } + } +} + +function drush_drupal_get_profile() { + if (method_exists('Drupal', 'installProfile')) { + return \Drupal::installProfile(); + } + if (function_exists('drupal_get_profile')) { + return \drupal_get_profile(); + } + return variable_get('install_profile', ''); +} + +function drush_find_profiles($drupal_root , $key = 'name') { + return drush_scan_directory($drupal_root . '/profiles', "/.*\.profile$/", array('.', '..', 'CVS', 'tests'), 0, 2, $key); +} + +/** + * Parse Drupal info file format. + * + * Copied with modifications from includes/common.inc. + * + * @see drupal_parse_info_file + */ +function drush_drupal_parse_info_file($filename) { + if (!file_exists($filename)) { + return array(); + } + + $data = file_get_contents($filename); + return _drush_drupal_parse_info_file($data); +} + +/** + * Parse the info file. + */ +function _drush_drupal_parse_info_file($data, $merge_item = NULL) { + if (!$data) { + return FALSE; + } + + if (preg_match_all(' + @^\s* # Start at the beginning of a line, ignoring leading whitespace + ((?: + [^=;\[\]]| # Key names cannot contain equal signs, semi-colons or square brackets, + \[[^\[\]]*\] # unless they are balanced and not nested + )+?) + \s*=\s* # Key/value pairs are separated by equal signs (ignoring white-space) + (?: + ("(?:[^"]|(?<=\\\\)")*")| # Double-quoted string, which may contain slash-escaped quotes/slashes + (\'(?:[^\']|(?<=\\\\)\')*\')| # Single-quoted string, which may contain slash-escaped quotes/slashes + ([^\r\n]*?) # Non-quoted string + )\s*$ # Stop at the next end of a line, ignoring trailing whitespace + @msx', $data, $matches, PREG_SET_ORDER)) { + $info = array(); + foreach ($matches as $match) { + // Fetch the key and value string. + $i = 0; + foreach (array('key', 'value1', 'value2', 'value3') as $var) { + $$var = isset($match[++$i]) ? $match[$i] : ''; + } + $value = stripslashes(substr($value1, 1, -1)) . stripslashes(substr($value2, 1, -1)) . $value3; + + // Parse array syntax. + $keys = preg_split('/\]?\[/', rtrim($key, ']')); + $last = array_pop($keys); + $parent = &$info; + + // Create nested arrays. + foreach ($keys as $key) { + if ($key == '') { + $key = count($parent); + } + if (isset($merge_item) && isset($parent[$key]) && !is_array($parent[$key])) { + $parent[$key] = array($merge_item => $parent[$key]); + } + if (!isset($parent[$key]) || !is_array($parent[$key])) { + $parent[$key] = array(); + } + $parent = &$parent[$key]; + } + + // Handle PHP constants. + if (defined($value)) { + $value = constant($value); + } + + // Insert actual value. + if ($last == '') { + $last = count($parent); + } + if (isset($merge_item) && isset($parent[$last]) && is_array($parent[$last])) { + $parent[$last][$merge_item] = $value; + } + else { + $parent[$last] = $value; + } + } + return $info; + } + return FALSE; +} + +/** + * Build a cache id to store the install_profile for a given site. + */ +function drush_cid_install_profile() { + return drush_get_cid('install_profile', array(), array(drush_get_context('DRUSH_SELECTED_DRUPAL_SITE_CONF_PATH'))); +} + +/* + * An array of options shared by sql-sanitize and sql-sync commands. + */ +function drupal_sanitize_options() { + return array( + 'sanitize-password' => array( + 'description' => 'The password to assign to all accounts in the sanitization operation, or "no" to keep passwords unchanged.', + 'example-value' => 'password', + 'value' => 'required', + ), + 'sanitize-email' => array( + 'description' => 'The pattern for test email addresses in the sanitization operation, or "no" to keep email addresses unchanged. May contain replacement patterns %uid, %mail or %name.', + 'example-value' => 'user+%uid@localhost', + 'value' => 'required', + ), + ); +} diff --git a/vendor/drush/drush/includes/drush.inc b/vendor/drush/drush/includes/drush.inc new file mode 100644 index 0000000000..7c43e77414 --- /dev/null +++ b/vendor/drush/drush/includes/drush.inc @@ -0,0 +1,1855 @@ + $name, '!version' => $version, '!extension' => $extension))); +} + +/** + * Provide a version-specific class instance. + * + * @param $class_name + * The name of the class to instantiate. Appends the Drupal + * major version number to the end of the class name before instantiation. + * @param $constructor_args + * An array of arguments to pass to the class constructor. + * + * Example wrapper class to instantiate a widget, called with the + * arguments for the WIDGET_CLASS constructor: + * + * function drush_WIDGET_CLASS_get_class($widgetName, $widgetStyle) { + * retrun drush_get_class('Widget_Class', func_get_args())); + * } + */ + +function drush_get_class($class_name, $constructor_args = array(), $variations = array()) { + if (empty($variations)) { + $variations[] = drush_drupal_major_version(); + } + $class_names = is_array($class_name) ? $class_name : array($class_name); + foreach ($class_names as $class_name) { + for ($i=count($variations); $i >= 0; $i--) { + $variant_class_name = $class_name . implode('', array_slice($variations, 0, $i)); + if (class_exists($variant_class_name)) { + $reflectionClass = new ReflectionClass($variant_class_name); + return !empty($constructor_args) ? $reflectionClass->newInstanceArgs($constructor_args) : $reflectionClass->newInstanceArgs(); + } + } + } + // Something bad happenned. TODO Exception? + return drush_set_error('DRUSH_GET_CLASS_ERROR', dt('Unable to load class !class', array('!class' => $class_name))); +} + +/** + * Generate an .ini file. used by archive-dump." + * + * @param array $ini + * A two dimensional associative array where top level are sections and + * second level are key => value pairs. + * + * @return string + * .ini formatted text. + */ +function drush_export_ini($ini) { + $output = ''; + foreach ($ini as $section => $pairs) { + if ($section) { + $output .= "[$section]\n"; + } + + foreach ($pairs as $k => $v) { + if ($v) { + $output .= "$k = \"$v\"\n"; + } + } + } + return $output; +} + +/** + * Generate code friendly to the Drupal .info format from a structured array. + * Mostly copied from http://drupalcode.org/viewvc/drupal/contributions/modules/features/features.export.inc. + * + * @param $info + * An array or single value to put in a module's .info file. + * + * @param boolean $integer_keys + * Use integer in keys. + * + * @param $parents + * Array of parent keys (internal use only). + * + * @return + * A code string ready to be written to a module's .info file. + */ +function drush_export_info($info, $integer_keys = FALSE, $parents = array()) { + $output = ''; + if (is_array($info)) { + foreach ($info as $k => $v) { + $child = $parents; + $child[] = $k; + $output .= drush_export_info($v, $integer_keys, $child); + } + } + else if (!empty($info) && count($parents)) { + $line = array_shift($parents); + foreach ($parents as $key) { + $line .= (!$integer_keys && is_numeric($key)) ? "[]" : "[{$key}]"; + } + $line .= " = \"{$info}\"\n"; + return $line; + } + return $output; +} + +/** + * Convert a csv string, or an array of items which + * may contain csv strings, into an array of items. + * + * @param $args + * A simple csv string; e.g. 'a,b,c' + * or a simple list of items; e.g. array('a','b','c') + * or some combination; e.g. array('a,b','c') or array('a,','b,','c,') + * + * @returns array + * A simple list of items (e.g. array('a','b','c') + */ +function _convert_csv_to_array($args) { + // + // Step 1: implode(',',$args) converts from, say, array('a,','b,','c,') to 'a,,b,,c,' + // Step 2: explode(',', ...) converts to array('a','','b','','c','') + // Step 3: array_filter(...) removes the empty items + // Step 4: array_map(...) trims extra whitespace from each item + // (handles csv strings with extra whitespace, e.g. 'a, b, c') + // + return array_map('trim', array_filter(explode(',', is_array($args) ? implode(',',$args) : $args))); +} + +/** + * Convert a nested array into a flat array. Thows away + * the array keys, returning only the values. + * + * @param $args + * An array that may potentially be nested. + * e.g. array('a', array('b', 'c')) + * + * @returns array + * A simple list of items (e.g. array('a','b','c') + */ +function drush_flatten_array($a) { + $result = array(); + if (!is_array($a)) { + return array($a); + } + foreach ($a as $value) { + $result = array_merge($result, drush_flatten_array($value)); + } + return $result; +} + +/** + * Get the available global options. Used by help command. Command files may + * modify this list using hook_drush_help_alter(). + * + * @param boolean $brief + * Return a reduced set of important options. Used by help command. + * + * @return + * An associative array containing the option definition as the key, + * and a descriptive array for each of the available options. The array + * elements for each item are: + * + * - short-form: The shortcut form for specifying the key on the commandline. + * - propagate: backend invoke will use drush_get_option to propagate this + * option, when set, to any other Drush command that is called. + * - context: The drush context where the value of this item is cached. Used + * by backend invoke to propagate values set in code. + * - never-post: If TRUE, backend invoke will never POST this item's value + * on STDIN; it will always be sent as a commandline option. + * - never-propagate: If TRUE, backend invoke will never pass this item on + * to the subcommand being executed. + * - local-context-only: Backend invoke will only pass this value on for local calls. + * - merge: For options such as $options['shell-aliases'] that consist of an array + * of items, make a merged array that contains all of the values specified for + * all of the contexts (config files) where the option is defined. The value is stored in + * the specified 'context', or in a context named after the option itself if the + * context flag is not specified. + * IMPORTANT: When the merge flag is used, the option value must be obtained via + * drush_get_context('option') rather than drush_get_option('option'). + * - merge-pathlist: For options such as --include and --config, make a merged list + * of options from all contexts; works like the 'merge' flag, but also handles string + * values separated by the PATH_SEPARATOR. + * - merge-associative: Like 'merge-pathlist', but key values are preserved. + * - propagate-cli-value: Used to tell backend invoke to include the value for + * this item as specified on the cli. This can either override 'context' + * (e.g., propagate --include from cli value instead of DRUSH_INCLUDE context), + * or for an independent global setting (e.g. --user) + * - description: The help text for this item. displayed by `drush help`. + */ +function drush_get_global_options($brief = FALSE) { + $options['root'] = array('short-form' => 'r', 'short-has-arg' => TRUE, 'never-post' => TRUE, 'description' => "Drupal root directory to use (default: current directory).", 'example-value' => 'path'); + $options['uri'] = array('short-form' => 'l', 'short-has-arg' => TRUE, 'never-post' => TRUE, 'description' => 'URI of the drupal site to use (only needed in multisite environments or when running on an alternate port).', 'example-value' => 'http://example.com:8888'); + $options['verbose'] = array('short-form' => 'v', 'context' => 'DRUSH_VERBOSE', 'description' => 'Display extra information about the command.'); + $options['debug'] = array('short-form' => 'd', 'context' => 'DRUSH_DEBUG', 'description' => 'Display even more information, including internal messages.'); + $options['yes'] = array('short-form' => 'y', 'context' => 'DRUSH_AFFIRMATIVE', 'description' => "Assume 'yes' as answer to all prompts."); + $options['no'] = array('short-form' => 'n', 'context' => 'DRUSH_NEGATIVE', 'description' => "Assume 'no' as answer to all prompts."); + $options['simulate'] = array('short-form' => 's', 'context' => 'DRUSH_SIMULATE', 'never-propagate' => TRUE, 'description' => "Simulate all relevant actions (don't actually change the system)."); + $options['pipe'] = array('short-form' => 'p', 'hidden' => TRUE, 'description' => "Emit a compact representation of the command for scripting."); + $options['help'] = array('short-form' => 'h', 'description' => "This help system."); + + if (!$brief) { + $options['version'] = array('description' => "Show drush version."); + $options['php'] = array('description' => "The absolute path to your PHP interpreter, if not 'php' in the path.", 'example-value' => '/path/to/file', 'never-propagate' => TRUE); + $options['interactive'] = array('short-form' => 'ia', 'description' => "Force interactive mode for commands run on multiple targets (e.g. `drush @site1,@site2 cc --ia`).", 'never-propagate' => TRUE); + $options['tty'] = array('hidden' => TRUE, 'description' => "Force allocation of tty for remote commands", 'never-propagate' => TRUE); + $options['quiet'] = array('short-form' => 'q', 'description' => 'Suppress non-error messages.'); + $options['include'] = array('short-form' => 'i', 'short-has-arg' => TRUE, 'context' => 'DRUSH_INCLUDE', 'never-post' => TRUE, 'propagate-cli-value' => TRUE, 'merge-pathlist' => TRUE, 'description' => "A list of additional directory paths to search for drush commands.", 'example-value' => '/path/dir'); + $options['exclude'] = array('propagate-cli-value' => TRUE, 'never-post' => TRUE, 'merge-pathlist' => TRUE, 'description' => "A list of files and directory paths to exclude from consideration when searching for drush commandfiles.", 'example-value' => '/path/dir'); + $options['config'] = array('short-form' => 'c', 'short-has-arg' => TRUE, 'context' => 'DRUSH_CONFIG', 'never-post' => TRUE, 'propagate-cli-value' => TRUE, 'merge-pathlist' => TRUE, 'description' => "Specify an additional config file to load. See example.drushrc.php.", 'example-value' => '/path/file'); + $options['user'] = array('short-form' => 'u', 'short-has-arg' => TRUE, 'propagate-cli-value' => TRUE, 'description' => "Specify a Drupal user to login with. May be a name or a number.", 'example-value' => 'name_or_number'); + $options['backend'] = array('short-form' => 'b', 'never-propagate' => TRUE, 'description' => "Hide all output and return structured data."); + $options['choice'] = array('description' => "Provide an answer to a multiple-choice prompt.", 'example-value' => 'number'); + $options['variables'] = array('description' => "Comma delimited list of name=value pairs. These values take precedence even over settings.php variable overrides.", 'example-value' => 'foo=bar,baz=yaz'); + $options['search-depth'] = array('description' => "Control the depth that drush will search for alias files.", 'example-value' => 'number'); + $options['ignored-modules'] = array('description' => "Exclude some modules from consideration when searching for drush command files.", 'example-value' => 'token,views'); + $options['no-label'] = array('description' => "Remove the site label that drush includes in multi-site command output (e.g. `drush @site1,@site2 status`)."); + $options['label-separator'] = array('description' => "Specify the separator to use in multi-site command output (e.g. `drush @sites pm-list --label-separator=',' --format=csv`)."); + $options['nocolor'] = array('context' => 'DRUSH_NOCOLOR', 'propagate-cli-value' => TRUE, 'description' => "Suppress color highlighting on log messages."); + $options['show-passwords'] = array('description' => "Show database passwords in commands that display connection information."); + $options['show-invoke'] = array('description' => "Show all function names which could have been called for the current command. See drush_invoke()."); + $options['watchdog'] = array('description' => "Control logging of Drupal's watchdog() to drush log. Recognized values are 'log', 'print', 'disabled'. Defaults to log. 'print' shows calls to admin but does not add them to the log.", 'example-value' => 'print'); + $options['cache-default-class'] = array('description' => "A cache backend class that implements CacheInterface. Defaults to JSONCache.", 'example-value' => 'JSONCache'); + $options['cache-class-'] = array('description' => "A cache backend class that implements CacheInterface to use for a specific cache bin.", 'example-value' => 'className'); + $options['early'] = array('description' => "Include a file (with relative or full path) and call the drush_early_hook() function (where 'hook' is the filename). The function is called pre-bootstrap and offers an opportunity to alter the drush bootstrap environment or process (returning FALSE from the function will continue the bootstrap), or return output very rapidly (e.g. from caches). See includes/complete.inc for an example."); + $options['alias-path'] = array('context' => 'ALIAS_PATH', 'local-context-only' => TRUE, 'merge-pathlist' => TRUE, 'propagate-cli-value' => TRUE, 'description' => "Specifies the list of paths where drush will search for alias files.", 'example-value' => '/path/alias1:/path/alias2'); + $options['backup-location'] = array('description' => "Specifies the directory where drush will store backups.", 'example-value' => '/path/to/dir'); + $options['confirm-rollback'] = array('description' => 'Wait for confirmation before doing a rollback when something goes wrong.'); + $options['complete-debug'] = array('hidden' => TRUE, 'description' => "Turn on debug mode forf completion code"); + $options['php-options'] = array('description' => "Options to pass to `php` when running drush. Only effective when using the drush.launcher script.", 'never-propagate' => TRUE, 'example-value' => '-d error_reporting="E_ALL"'); + $options['halt-on-error'] = array('propagate-cli-value' => TRUE, 'description' => "Controls error handling of recoverable errors (E_RECOVERABLE_ERROR). --halt-on-error=1: Execution is halted. --halt-on-error=0: Drush execution continues"); + $options['deferred-sanitization'] = array('hidden' => TRUE, 'description' => "Defer calculating the sanitization operations until after the database has been copied. This is done automatically if the source database is remote."); + $options['remote-host'] = array('hidden' => TRUE, 'description' => 'Remote site to execute drush command on. Managed by site alias.'); + $options['remote-user'] = array('hidden' => TRUE, 'description' => 'User account to use with a remote drush command. Managed by site alias.'); + $options['remote-os'] = array('hidden' => TRUE, 'description' => 'The operating system used on the remote host. Managed by site alias.'); + $options['site-list'] = array('hidden' => TRUE, 'description' => 'List of sites to run commands on. Managed by site alias.'); + $options['reserve-margin'] = array('hidden' => TRUE, 'description' => 'Remove columns from formatted opions. Managed by multi-site command handling.'); + $options['strict'] = array('propagate' => TRUE, 'description' => 'Return an error on unrecognized options. --strict=0: Allow unrecognized options. --strict=2: Also return an error on any "warning" log messages. Optional. Default is 1.'); + $options['command-specific'] = array('hidden' => TRUE, 'merge-associative' => TRUE, 'description' => 'Command-specific options.'); + $options['site-aliases'] = array('hidden' => TRUE, 'merge-associative' => TRUE, 'description' => 'List of site aliases.'); + $options['shell-aliases'] = array('hidden' => TRUE, 'merge' => TRUE, 'never-propagate' => TRUE, 'description' => 'List of shell aliases.'); + $options['path-aliases'] = array('hidden' => TRUE, 'never-propagate' => TRUE, 'description' => 'Path aliases from site alias.'); + $options['ssh-options'] = array('never-propagate' => TRUE, 'description' => 'A string of extra options that will be passed to the ssh command', 'example-value' => '-p 100'); + $options['editor'] = array('never-propagate' => TRUE, 'description' => 'A string of bash which launches user\'s preferred text editor. Defaults to ${VISUAL-${EDITOR-vi}}.', 'example-value' => 'vi'); + $options['bg'] = array('never-propagate' => TRUE, 'description' => 'Run editor in the background. Does not work with editors such as `vi` that run in the terminal. Supresses config-import at the end.'); + $options['db-url'] = array('hidden' => TRUE, 'description' => 'A Drupal 6 style database URL. Used by various commands.', 'example-value' => 'mysql://root:pass@127.0.0.1/db'); + $options['drush-coverage'] = array('hidden' => TRUE, 'never-post' => TRUE, 'propagate-cli-value' => TRUE, 'description' => 'File to save code coverage data into.'); + $options['redirect-port'] = array('hidden' => TRUE, 'never-propagate' => TRUE, 'description' => 'Used by the user-login command to specify the redirect port on the local machine; it therefore would not do to pass this to the remote machines.'); + $options['cache-clear'] = array('propagate' => TRUE, 'description' => 'If 0, Drush skips normal cache clearing; the caller should then clear if needed.', 'example-value' => '0', ); + $options['local'] = array('propagate' => TRUE, 'description' => 'Don\'t look in global locations for commandfiles, config, and site aliases'); + $options['no-interaction'] = array('hidden' => TRUE, 'description' => 'Synonym for --yes; provided for light forward-compatibility with future Drush versions.'); + $options['ignored-directories'] = array('propagate' => TRUE, 'description' => "Exclude directories when searching for files in drush_scan_directory().", 'example-value' => 'node_modules,bower_components'); + + $command = array( + 'options' => $options, + '#brief' => FALSE, + ) + drush_command_defaults('global-options', 'global_options', __FILE__); + drush_command_invoke_all_ref('drush_help_alter', $command); + + $options = $command['options']; + } + return $options; +} + +/** + * Exits with a message. In general, you should use drush_set_error() instead of + * this function. That lets drush proceed with other tasks. + * TODO: Exit with a correct status code. + */ +function drush_die($msg = NULL, $status = NULL) { + die($msg ? "drush: $msg\n" : ''); +} + +/** + * Check to see if the provided line is a "#!/usr/bin/env drush" + * "shebang" script line. + */ +function _drush_is_drush_shebang_line($line) { + return ((substr($line,0,2) == '#!') && (strstr($line, 'drush') !== FALSE)); +} + +/** + * Check to see if the provided script file is a "#!/usr/bin/env drush" + * "shebang" script line. + */ +function _drush_is_drush_shebang_script($script_filename) { + $result = FALSE; + + if (file_exists($script_filename)) { + $fp = fopen($script_filename, "r"); + if ($fp !== FALSE) { + $line = fgets($fp); + $result = _drush_is_drush_shebang_line($line); + fclose($fp); + } + } + + return $result; +} + +/** + * @defgroup userinput Get input from the user. + * @{ + */ + +/** + * Asks the user a basic yes/no question. + * + * @param string $msg + * The question to ask. + * @param int $indent + * The number of spaces to indent the message. + * + * @return bool + * TRUE if the user enters "y" or FALSE if "n". + */ +function drush_confirm($msg, $indent = 0) { + drush_print_prompt((string)$msg . " (y/n): ", $indent); + + // Automatically accept confirmations if the --yes argument was supplied. + if (drush_get_context('DRUSH_AFFIRMATIVE')) { + drush_print("y"); + return TRUE; + } + // Automatically cancel confirmations if the --no argument was supplied. + elseif (drush_get_context('DRUSH_NEGATIVE')) { + drush_print("n"); + return FALSE; + } + // See http://drupal.org/node/499758 before changing this. + $stdin = fopen("php://stdin","r"); + + while ($line = fgets($stdin)) { + $line = trim($line); + if ($line == 'y') { + return TRUE; + } + if ($line == 'n') { + return FALSE; + } + drush_print_prompt((string)$msg . " (y/n): ", $indent); + } +} + +/** + * Ask the user to select an item from a list. + * From a provided associative array, drush_choice will + * display all of the questions, numbered from 1 to N, + * and return the item the user selected. "0" is always + * cancel; entering a blank line is also interpreted + * as cancelling. + * + * @param $options + * A list of questions to display to the user. The + * KEYS of the array are the result codes to return to the + * caller; the VALUES are the messages to display on + * each line. Special keys of the form '-- something --' can be + * provided as separator between choices groups. Separator keys + * don't alter the numbering. + * @param $prompt + * The message to display to the user prompting for input. + * @param $label + * Controls the display of each line. Defaults to + * '!value', which displays the value of each item + * in the $options array to the user. Use '!key' to + * display the key instead. In some instances, it may + * be useful to display both the key and the value; for + * example, if the key is a user id and the value is the + * user name, use '!value (uid=!key)'. + */ +function drush_choice($options, $prompt = 'Enter a number.', $label = '!value', $widths = array()) { + drush_print(dt($prompt)); + + // Preflight so that all rows will be padded out to the same number of columns + $array_pad = 0; + foreach ($options as $key => $option) { + if (is_array($option) && (count($option) > $array_pad)) { + $array_pad = count($option); + } + } + + $rows[] = array_pad(array('[0]', ':', 'Cancel'), $array_pad + 2, ''); + $selection_number = 0; + foreach ($options as $key => $option) { + if ((substr($key, 0, 3) == '-- ') && (substr($key, -3) == ' --')) { + $rows[] = array_pad(array('', '', $option), $array_pad + 2, ''); + } + else { + $selection_number++; + $row = array("[$selection_number]", ':'); + if (is_array($option)) { + $row = array_merge($row, $option); + } + else { + $row[] = dt($label, array('!number' => $selection_number, '!key' => $key, '!value' => $option)); + } + $rows[] = $row; + $selection_list[$selection_number] = $key; + } + } + drush_print_table($rows, FALSE, $widths); + drush_print_pipe(array_keys($options)); + + // If the user specified --choice, then make an + // automatic selection. Cancel if the choice is + // not an available option. + if (($choice = drush_get_option('choice', FALSE)) !== FALSE) { + // First check to see if $choice is one of the symbolic options + if (array_key_exists($choice, $options)) { + return $choice; + } + // Next handle numeric selections + elseif (array_key_exists($choice, $selection_list)) { + return $selection_list[$choice]; + } + return FALSE; + } + + // If the user specified --no, then cancel; also avoid + // getting hung up waiting for user input in --pipe and + // backend modes. If none of these apply, then wait, + // for user input and return the selected result. + if (!drush_get_context('DRUSH_NEGATIVE') && !drush_get_context('DRUSH_AFFIRMATIVE') && !drush_get_context('DRUSH_PIPE')) { + while ($line = trim(fgets(STDIN))) { + if (array_key_exists($line, $selection_list)) { + return $selection_list[$line]; + } + } + } + // We will allow --yes to confirm input if there is only + // one choice; otherwise, --yes will cancel to avoid ambiguity + if (drush_get_context('DRUSH_AFFIRMATIVE') && (count($options) == 1)) { + return $selection_list[1]; + } + return FALSE; +} + +/** + * Ask the user to select multiple items from a list. + * This is a wrapper around drush_choice, that repeats the selection process, + * allowing users to toggle a number of items in a list. The number of values + * that can be constrained by both min and max: the user will only be allowed + * finalize selection once the minimum number has been selected, and the oldest + * selected value will "drop off" the list, if they exceed the maximum number. + * + * @param $options + * Same as drush_choice() (see above). + * @param $defaults + * This can take 3 forms: + * - FALSE: (Default) All options are unselected by default. + * - TRUE: All options are selected by default. + * - Array of $options keys to be selected by default. + * @param $prompt + * Same as drush_choice() (see above). + * @param $label + * Same as drush_choice() (see above). + * @param $mark + * Controls how selected values are marked. Defaults to '!value (selected)'. + * @param $min + * Constraint on minimum number of selections. Defaults to zero. When fewer + * options than this are selected, no final options will be available. + * @param $max + * Constraint on minimum number of selections. Defaults to NULL (unlimited). + * If the a new selection causes this value to be exceeded, the oldest + * previously selected value is automatically unselected. + * @param $final_options + * An array of additional options in the same format as $options. + * When the minimum number of selections is met, this array is merged into the + * array of options. If the user selects one of these values and the + * selection process will complete (the key for the final option is included + * in the return value). If this is an empty array (default), then a built in + * final option of "Done" will be added to the available options (in this case + * no additional keys are added to the return value). + */ +function drush_choice_multiple($options, $defaults = FALSE, $prompt = 'Select some numbers.', $label = '!value', $mark = '!value (selected)', $min = 0, $max = NULL, $final_options = array()) { + $selections = array(); + // Load default selections. + if (is_array($defaults)) { + $selections = $defaults; + } + elseif ($defaults === TRUE) { + $selections = array_keys($options); + } + $complete = FALSE; + $final_builtin = array(); + if (empty($final_options)) { + $final_builtin['done'] = dt('Done'); + } + $final_options_keys = array_keys($final_options); + while (TRUE) { + $current_options = $options; + // Mark selections. + foreach ($selections as $selection) { + $current_options[$selection] = dt($mark, array('!key' => $selection, '!value' => $options[$selection])); + } + // Add final options, if the minimum number of selections has been reached. + if (count($selections) >= $min) { + $current_options = array_merge($current_options, $final_options, $final_builtin); + } + $toggle = drush_choice($current_options, $prompt, $label); + if ($toggle === FALSE) { + return FALSE; + } + // Don't include the built in final option in the return value. + if (count($selections) >= $min && empty($final_options) && $toggle == 'done') { + return $selections; + } + // Toggle the selected value. + $item = array_search($toggle, $selections); + if ($item === FALSE) { + array_unshift($selections, $toggle); + } + else { + unset($selections[$item]); + } + // If the user selected one of the final options, return. + if (count($selections) >= $min && in_array($toggle, $final_options_keys)) { + return $selections; + } + // If the user selected too many options, drop the oldest selection. + if (isset($max) && count($selections) > $max) { + array_pop($selections); + } + } +} + +/** + * Prompt the user for input + * + * The input can be anything that fits on a single line (not only y/n), + * so we can't use drush_confirm() + * + * @param $prompt + * The text which is displayed to the user. + * @param $default + * The default value of the input. + * @param $required + * If TRUE, user may continue even when no value is in the input. + * @param $password + * If TRUE, surpress printing of the input. + * + * @see drush_confirm() + */ +function drush_prompt($prompt, $default = NULL, $required = TRUE, $password = FALSE) { + if (isset($default)) { + $prompt .= " [" . $default . "]"; + } + $prompt .= ": "; + + drush_print_prompt($prompt); + + if (drush_get_context('DRUSH_AFFIRMATIVE')) { + return $default; + } + + $stdin = fopen('php://stdin', 'r'); + + if ($password) drush_shell_exec("stty -echo"); + + stream_set_blocking($stdin, TRUE); + while (($line = fgets($stdin)) !== FALSE) { + $line = trim($line); + if ($line === "") { + $line = $default; + } + if ($line || !$required) { + break; + } + drush_print_prompt($prompt); + } + fclose($stdin); + if ($password) { + drush_shell_exec("stty echo"); + print "\n"; + } + return $line; +} + +/** + * @} End of "defgroup userinput". + */ + +/** + * Calls a given function, passing through all arguments unchanged. + * + * This should be used when calling possibly mutative or destructive functions + * (e.g. unlink() and other file system functions) so that can be suppressed + * if the simulation mode is enabled. + * + * Important: Call @see drush_op_system() to execute a shell command, + * or @see drush_shell_exec() to execute a shell command and capture the + * shell output. + * + * @param $callable + * The name of the function. Any additional arguments are passed along. + * @return + * The return value of the function, or TRUE if simulation mode is enabled. + * + */ +function drush_op($callable) { + $args_printed = array(); + $args = func_get_args(); + array_shift($args); // Skip function name + foreach ($args as $arg) { + $args_printed[] = is_scalar($arg) ? $arg : (is_array($arg) ? 'Array' : 'Object'); + } + + if (!is_array($callable)) { + $callable_string = $callable; + } + else { + if (is_object($callable[0])) { + $callable_string = get_class($callable[0]) . '::' . $callable[1]; + } + else { + $callable_string = implode('::', $callable); + } + } + + // Special checking for drush_op('system') + if ($callable == 'system') { + drush_log(dt("Do not call drush_op('system'); use drush_op_system instead"), LogLevel::DEBUG); + } + + if (drush_get_context('DRUSH_VERBOSE') || drush_get_context('DRUSH_SIMULATE')) { + drush_log(sprintf("Calling %s(%s)", $callable_string, implode(", ", $args_printed)), LogLevel::DEBUG); + } + + if (drush_get_context('DRUSH_SIMULATE')) { + return TRUE; + } + + return drush_call_user_func_array($callable, $args); +} + +/** + * Mimic cufa but still call function directly. See http://drupal.org/node/329012#comment-1260752 + */ +function drush_call_user_func_array($function, $args = array() ) { + if (is_array($function)) { + // $callable is a method so always use CUFA. + return call_user_func_array($function, $args); + } + + switch (count($args)) { + case 0: return $function(); break; + case 1: return $function($args[0]); break; + case 2: return $function($args[0], $args[1]); break; + case 3: return $function($args[0], $args[1], $args[2]); break; + case 4: return $function($args[0], $args[1], $args[2], $args[3]); break; + case 5: return $function($args[0], $args[1], $args[2], $args[3], $args[4]); break; + case 6: return $function($args[0], $args[1], $args[2], $args[3], $args[4], $args[5]); break; + case 7: return $function($args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6]); break; + case 8: return $function($args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6], $args[7]); break; + case 9: return $function($args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6], $args[7], $args[8]); break; + default: return call_user_func_array($function,$args); + } +} + +/** + * Download a file using wget, curl or file_get_contents, or via download cache. + * + * @param string $url + * The url of the file to download. + * @param string $destination + * The name of the file to be saved, which may include the full path. + * Optional, if omitted the filename will be extracted from the url and the + * file downloaded to the current working directory (Drupal root if + * bootstrapped). + * @param integer $cache_duration + * The acceptable age of a cached file. If cached file is too old, a fetch + * will occur and cache will be updated. Optional, if ommitted the file will + * be fetched directly. + * + * @return string + * The path to the downloaded file, or FALSE if the file could not be + * downloaded. + */ +function drush_download_file($url, $destination = FALSE, $cache_duration = 0) { + // Generate destination if omitted. + if (!$destination) { + $file = basename(current(explode('?', $url, 2))); + $destination = getcwd() . '/' . basename($file); + } + + // Simply copy local files to the destination + if (!_drush_is_url($url)) { + return copy($url, $destination) ? $destination : FALSE; + } + + if ($cache_duration !== 0 && $cache_file = drush_download_file_name($url)) { + // Check for cached, unexpired file. + if (file_exists($cache_file) && filectime($cache_file) > ($_SERVER['REQUEST_TIME']-$cache_duration)) { + drush_log(dt('!name retrieved from cache.', array('!name' => $cache_file))); + } + else { + if (_drush_download_file($url, $cache_file, TRUE)) { + // Cache was set just by downloading file to right location. + } + elseif (file_exists($cache_file)) { + drush_log(dt('!name retrieved from an expired cache since refresh failed.', array('!name' => $cache_file)), LogLevel::WARNING); + } + else { + $cache_file = FALSE; + } + } + + if ($cache_file && copy($cache_file, $destination)) { + // Copy cached file to the destination + return $destination; + } + } + elseif ($return = _drush_download_file($url, $destination)) { + drush_register_file_for_deletion($return); + return $return; + } + + // Unable to retrieve from cache nor download. + return FALSE; +} + +/** + * Helper function to determine name of cached file. + */ +function drush_download_file_name($url) { + if ($cache_dir = drush_directory_cache('download')) { + $cache_name = str_replace(array(':', '/', '?', '=', '\\'), '-', $url); + return $cache_dir . "/" . $cache_name; + } + else { + return FALSE; + } +} + +/** + * Check whether the given path is just a url or a local path + * @param string $url + * @return boolean + * TRUE if the path does not contain a schema:// part. + */ +function _drush_is_url($url) { + return parse_url($url, PHP_URL_SCHEME) !== NULL; +} + +/** + * Download a file using wget, curl or file_get_contents. Does not use download + * cache. + * + * @param string $url + * The url of the file to download. + * @param string $destination + * The name of the file to be saved, which may include the full path. + * @param boolean $overwrite + * Overwrite any file thats already at the destination. + * @return string + * The path to the downloaded file, or FALSE if the file could not be + * downloaded. + */ +function _drush_download_file($url, $destination, $overwrite = TRUE) { + static $use_wget; + if ($use_wget === NULL) { + $use_wget = drush_shell_exec('wget --version'); + } + + $destination_tmp = drush_tempnam('download_file'); + if ($use_wget) { + drush_shell_exec("wget -q --timeout=30 -O %s %s", $destination_tmp, $url); + } + else { + // Force TLS1+ as per https://github.com/drush-ops/drush/issues/894. + drush_shell_exec("curl --tlsv1 --fail -s -L --connect-timeout 30 -o %s %s", $destination_tmp, $url); + } + if (!drush_file_not_empty($destination_tmp) && $file = @file_get_contents($url)) { + @file_put_contents($destination_tmp, $file); + } + if (!drush_file_not_empty($destination_tmp)) { + // Download failed. + return FALSE; + } + + drush_move_dir($destination_tmp, $destination, $overwrite); + return $destination; +} + +/** + * Determines the MIME content type of the specified file. + * + * The power of this function depends on whether the PHP installation + * has either mime_content_type() or finfo installed -- if not, only tar, + * gz, zip and bzip2 types can be detected. + * + * If mime type can't be obtained, an error will be set. + * + * @return mixed + * The MIME content type of the file or FALSE. + */ +function drush_mime_content_type($filename) { + $content_type = drush_attempt_mime_content_type($filename); + if ($content_type) { + drush_log(dt('Mime type for !file is !mt', array('!file' => $filename, '!mt' => $content_type)), LogLevel::NOTICE); + return $content_type; + } + return drush_set_error('MIME_CONTENT_TYPE_UNKNOWN', dt('Unable to determine mime type for !file.', array('!file' => $filename))); +} + +/** + * Works like drush_mime_content_type, but does not set an error + * if the type is unknown. + */ +function drush_attempt_mime_content_type($filename) { + $content_type = FALSE; + if (class_exists('finfo')) { + $finfo = new finfo(FILEINFO_MIME_TYPE); + $content_type = $finfo->file($filename); + if ($content_type == 'application/octet-stream') { + drush_log(dt('Mime type for !file is application/octet-stream.', array('!file' => $filename)), LogLevel::DEBUG); + $content_type = FALSE; + } + } + // If apache is configured in such a way that all files are considered + // octet-stream (e.g with mod_mime_magic and an http conf that's serving all + // archives as octet-stream for other reasons) we'll detect mime types on our + // own by examing the file's magic header bytes. + if (!$content_type) { + drush_log(dt('Examining !file headers.', array('!file' => $filename)), LogLevel::DEBUG); + if ($file = fopen($filename, 'rb')) { + $first = fread($file, 2); + fclose($file); + + if ($first !== FALSE) { + // Interpret the two bytes as a little endian 16-bit unsigned int. + $data = unpack('v', $first); + switch ($data[1]) { + case 0x8b1f: + // First two bytes of gzip files are 0x1f, 0x8b (little-endian). + // See http://www.gzip.org/zlib/rfc-gzip.html#header-trailer + $content_type = 'application/x-gzip'; + break; + + case 0x4b50: + // First two bytes of zip files are 0x50, 0x4b ('PK') (little-endian). + // See http://en.wikipedia.org/wiki/Zip_(file_format)#File_headers + $content_type = 'application/zip'; + break; + + case 0x5a42: + // First two bytes of bzip2 files are 0x5a, 0x42 ('BZ') (big-endian). + // See http://en.wikipedia.org/wiki/Bzip2#File_format + $content_type = 'application/x-bzip2'; + break; + + default: + drush_log(dt('Unable to determine mime type from header bytes 0x!hex of !file.', array('!hex' => dechex($data[1]), '!file' => $filename,), LogLevel::DEBUG)); + } + } + else { + drush_log(dt('Unable to read !file.', array('!file' => $filename)), LogLevel::WARNING); + } + } + else { + drush_log(dt('Unable to open !file.', array('!file' => $filename)), LogLevel::WARNING); + } + } + + // 3. Lastly if above methods didn't work, try to guess the mime type from + // the file extension. This is useful if the file has no identificable magic + // header bytes (for example tarballs). + if (!$content_type) { + drush_log(dt('Examining !file extension.', array('!file' => $filename)), LogLevel::DEBUG); + + // Remove querystring from the filename, if present. + $filename = basename(current(explode('?', $filename, 2))); + $extension_mimetype = array( + '.tar' => 'application/x-tar', + '.sql' => 'application/octet-stream', + ); + foreach ($extension_mimetype as $extension => $ct) { + if (substr($filename, -strlen($extension)) === $extension) { + $content_type = $ct; + break; + } + } + } + return $content_type; +} + +/** + * Check whether a file is a supported tarball. + * + * @return mixed + * The file content type if it's a tarball. FALSE otherwise. + */ +function drush_file_is_tarball($path) { + $content_type = drush_attempt_mime_content_type($path); + $supported = array( + 'application/x-bzip2', + 'application/x-gzip', + 'application/gzip', + 'application/x-tar', + 'application/x-zip', + 'application/zip', + ); + if (in_array($content_type, $supported)) { + return $content_type; + } + return FALSE; +} + +/** + * Extract a tarball. + * + * @param string $path + * Path to the archive to be extracted. + * @param string $destination + * The destination directory the tarball should be extracted into. + * Optional, if ommitted the tarball directory will be used as destination. + * @param boolean $listing + * If TRUE, a listing of the tar contents will be returned on success. + * @param string $tar_extra_options + * Extra options to be passed to the tar command. + * + * @return mixed + * TRUE on success, FALSE on fail. If $listing is TRUE, a file listing of the + * tarball is returned if the extraction reported success, instead of TRUE. + */ +function drush_tarball_extract($path, $destination = FALSE, $listing = FALSE, $tar_extra_options = '') { + // Check if tarball is supported. + if (!($mimetype = drush_file_is_tarball($path))) { + return drush_set_error('TARBALL_EXTRACT_UNKNOWN_FORMAT', dt('Unable to extract !path. Unknown archive format.', array('!path' => $path))); + } + + // Check if destination is valid. + if (!$destination) { + $destination = dirname($path); + } + if (!drush_mkdir($destination)) { + // drush_mkdir already set an error. + return FALSE; + } + + // Perform the extraction of a zip file. + if (($mimetype == 'application/zip') || ($mimetype == 'application/x-zip')) { + $return = drush_shell_cd_and_exec(dirname($path), "unzip %s -d %s", $path, $destination); + if (!$return) { + return drush_set_error('DRUSH_TARBALL_EXTRACT_ERROR', dt('Unable to unzip !filename.', array('!filename' => $path))); + } + if ($listing) { + // unzip prefixes the file listing output with a header line, + // and prefixes each line with a verb representing the compression type. + $output = drush_shell_exec_output(); + // Remove the header line. + array_shift($output); + // Remove the prefix verb from each line. + $output = array_map( + function ($str) use ($destination) { + return substr($str, strpos($str, ":") + 3 + strlen($destination)); + }, + $output + ); + // Remove any remaining blank lines. + $return = array_filter( + $output, + function ($str) { + return $str != ""; + } + ); + } + } + // Otherwise we have a possibly-compressed Tar file. + // If we are not on Windows, then try to do "tar" in a single operation. + elseif (!drush_is_windows()) { + $tar = drush_get_tar_executable(); + $tar_compression_flag = ''; + if ($mimetype == 'application/x-gzip') { + $tar_compression_flag = 'z'; + } + elseif ($mimetype == 'application/x-bzip2') { + $tar_compression_flag = 'j'; + } + + $return = drush_shell_cd_and_exec(dirname($path), "$tar {$tar_extra_options} -C %s -x%sf %s", $destination, $tar_compression_flag, basename($path)); + if (!$return) { + return drush_set_error('DRUSH_TARBALL_EXTRACT_ERROR', dt('Unable to untar !filename.', array('!filename' => $path))); + } + if ($listing) { + // We use a separate tar -tf instead of -xvf above because + // the output is not the same in Mac. + drush_shell_cd_and_exec(dirname($path), "$tar -t%sf %s", $tar_compression_flag, basename($path)); + $return = drush_shell_exec_output(); + } + } + // In windows, do the extraction by its primitive steps. + else { + // 1. copy the source tarball to the destination directory. Rename to a + // temp name in case the destination directory == dirname($path) + $tmpfile = drush_tempnam(basename($path), $destination); + drush_copy_dir($path, $tmpfile, FILE_EXISTS_OVERWRITE); + + // 2. uncompress the tarball, if compressed. + if (($mimetype == 'application/x-gzip') || ($mimetype == 'application/x-bzip2')) { + if ($mimetype == 'application/x-gzip') { + $compressed = $tmpfile . '.gz'; + // We used to use gzip --decompress in --stdout > out, but the output + // redirection sometimes failed on Windows for some binary output. + $command = 'gzip --decompress %s'; + } + elseif ($mimetype == 'application/x-bzip2') { + $compressed = $tmpfile . '.bz2'; + $command = 'bzip2 --decompress %s'; + } + drush_op('rename', $tmpfile, $compressed); + $return = drush_shell_cd_and_exec(dirname($compressed), $command, $compressed); + if (!$return || !file_exists($tmpfile)) { + return drush_set_error('DRUSH_TARBALL_EXTRACT_ERROR', dt('Unable to decompress !filename.', array('!filename' => $compressed))); + } + } + + // 3. Untar. + $tar = drush_get_tar_executable(); + $return = drush_shell_cd_and_exec(dirname($tmpfile), "$tar {$tar_extra_options} -xvf %s", basename($tmpfile)); + if (!$return) { + return drush_set_error('DRUSH_TARBALL_EXTRACT_ERROR', dt('Unable to untar !filename.', array('!filename' => $tmpfile))); + } + if ($listing) { + $return = drush_shell_exec_output(); + // Cut off the 'x ' prefix for the each line of the tar output + // See http://drupal.org/node/1775520 + foreach($return as &$line) { + if(strpos($line, "x ") === 0) + $line = substr($line, 2); + } + } + + // Remove the temporary file so the md5 hash is accurate. + unlink($tmpfile); + } + + return $return; +} + +/** + * @defgroup commandprocessing Command processing functions. + * @{ + * + * These functions manage command processing by the + * main function in drush.php. + */ + +/** + * Determine whether or not an argument should be removed from the + * DRUSH_COMMAND_ARGS context. This method is used when a Drush + * command has set the 'strict-option-handling' flag indicating + * that it will pass through all commandline arguments and any + * additional options (not known to Drush) to some shell command. + * + * Take as an example the following call to core-rsync: + * + * drush --yes core-rsync -v -az --exclude-paths='.git:.svn' local-files/ @site:%files + * + * In this instance: + * + * --yes is a global Drush option + * + * -v is an rsync option. It will make rsync run in verbose mode, + * but will not make Drush run in verbose mode due to the fact that + * core-rsync sets the 'strict-option-handling' flag. + * + * --exclude-paths is a local Drush option. It will be converted by + * Drush into --exclude='.git' and --exclude='.svn', and then passed + * on to the rsync command. + * + * The parameter $arg passed to this function is one of the elements + * of DRUSH_COMMAND_ARGS. It will have values such as: + * -v + * -az + * --exclude-paths='.git:.svn' + * local-files/ + * @site:%files + * + * Our job in this function is to determine if $arg should be removed + * by virtue of appearing in $removal_list. $removal_list is an array + * that will contain values such as 'exclude-paths'. Both the key and + * the value of $removal_list is the same. + */ +function _drush_should_remove_command_arg($arg, $removal_list) { + foreach ($removal_list as $candidate) { + if (($arg == "-$candidate") || + ($arg == "--$candidate") || + (substr($arg,0,strlen($candidate)+3) == "--$candidate=") ) { + return TRUE; + } + } + return FALSE; +} + +/** + * Redispatch the specified command using the same + * options that were passed to this invocation of drush. + */ +function drush_do_command_redispatch($command, $args = array(), $remote_host = NULL, $remote_user = NULL, $drush_path = NULL, $user_interactive = FALSE, $aditional_options = array()) { + $additional_global_options = array(); + $command_options = drush_redispatch_get_options(); + $command_options = $aditional_options + $command_options; + if (is_array($command)) { + $command_name = $command['command']; + // If we are executing a remote command that uses strict option handling, + // then mark all of the options in the alias context as global, so that they + // will appear before the command name. + if (!empty($command['strict-option-handling'])) { + foreach(drush_get_context('alias') as $alias_key => $alias_value) { + if (array_key_exists($alias_key, $command_options) && !array_key_exists($alias_key, $command['options'])) { + $additional_global_options[$alias_key] = $alias_value; + } + } + } + } + else { + $command_name = $command; + } + // If the path to drush was supplied, then use it to invoke the new command. + if ($drush_path == NULL) { + $drush_path = drush_get_option('drush-script'); + if (!isset($drush_path)) { + $drush_folder = drush_get_option('drush'); + if (isset($drush)) { + $drush_path = $drush_folder . '/drush'; + } + } + } + $backend_options = array('drush-script' => $drush_path, 'remote-host' => $remote_host, 'remote-user' => $remote_user, 'integrate' => TRUE, 'additional-global-options' => $additional_global_options); + // Set the tty if requested, if the command necessitates it, + // or if the user explicitly asks for interactive mode, but + // not if interactive mode is forced. tty implies interactive + if (drush_get_option('tty') || $user_interactive || !empty($command['remote-tty'])) { + $backend_options['#tty'] = TRUE; + $backend_options['interactive'] = TRUE; + } + elseif (drush_get_option('interactive')) { + $backend_options['interactive'] = TRUE; + } + + // Run the command in a new process. + drush_log(dt('Begin redispatch via drush_invoke_process().')); + $values = drush_invoke_process('@self', $command_name, $args, $command_options, $backend_options); + drush_log(dt('End redispatch via drush_invoke_process().')); + + return $values; +} + + +/** + * @} End of "defgroup commandprocessing". + */ + +/** + * @defgroup logging Logging information to be provided as output. + * @{ + * + * These functions are primarily for diagnostic purposes, but also provide an overview of tasks that were taken + * by drush. + */ + +/** + * Add a log message to the log history. + * + * This function calls the callback stored in the 'DRUSH_LOG_CALLBACK' context with + * the resulting entry at the end of execution. + * + * This allows you to replace it with custom logging implementations if needed, + * such as logging to a file or logging to a database (drupal or otherwise). + * + * The default callback is the Drush\Log\Logger class with prints the messages + * to the shell. + * + * @param message + * String containing the message to be logged. + * @param type + * The type of message to be logged. Common types are 'warning', 'error', 'success' and 'notice'. + * A type of 'ok' or 'success' can also be supplied to flag something that worked. + * If you want your log messages to print to screen without the user entering + * a -v or --verbose flag, use type 'ok', this prints log messages out to + * STDERR, which prints to screen (unless you have redirected it). All other + * types of messages will be assumed to be notices. + */ +function drush_log($message, $type = LogLevel::NOTICE, $error = null) { + $entry = array( + 'type' => $type, + 'message' => $message, + 'timestamp' => microtime(TRUE), + 'memory' => memory_get_usage(), + ); + $entry['error'] = $error; + + return _drush_log($entry); +} + +/** + * Future: add some sort of dependency injection to Drush. + */ +function _drush_create_default_logger() { + $drush_logger = new Logger(); + drush_set_context('DRUSH_LOGGER', $drush_logger); + drush_set_context('DRUSH_LOG_CALLBACK', $drush_logger); +} + +/** + * Call the default logger, or the user's log callback, as + * appropriate. + */ +function _drush_log($entry) { + $callback = drush_get_context('DRUSH_LOG_CALLBACK'); + + if ($callback instanceof LoggerInterface) { + _drush_log_to_logger($callback, $entry); + } + elseif ($callback) { + $log =& drush_get_context('DRUSH_LOG', array()); + $log[] = $entry; + drush_backend_packet('log', $entry); + return $callback($entry); + } +} + +// Maintain compatibility with extensions that hook into +// DRUSH_LOG_CALLBACK (e.g. drush_ctex_bonus) +function _drush_print_log($entry) { + $drush_logger = drush_get_context('DRUSH_LOGGER'); + if ($drush_logger) { + _drush_log_to_logger($drush_logger, $entry); + } +} + +function _drush_log_to_logger($logger, $entry) { + $context = $entry; + $log_level = $entry['type']; + $message = $entry['message']; + unset($entry['type']); + unset($entry['message']); + + $logger->log($log_level, $message, $context); +} + +function drush_log_has_errors($types = array(LogLevel::WARNING, LogLevel::ERROR, LogLevel::FAILED)) { + $log =& drush_get_context('DRUSH_LOG', array()); + foreach ($log as $entry) { + if (in_array($entry['type'], $types)) { + return TRUE; + } + } + return FALSE; +} + +/** + * Backend command callback. Add a log message to the log history. + * + * @param entry + * The log entry. + */ +function drush_backend_packet_log($entry, $backend_options) { + if (!$backend_options['log']) { + return; + } + if (!is_string($entry['message'])) { + $entry['message'] = implode("\n", (array)$entry['message']); + } + $entry['message'] = $entry['message']; + if (array_key_exists('#output-label', $backend_options)) { + $entry['message'] = $backend_options['#output-label'] . $entry['message']; + } + + // If integrate is FALSE, then log messages are stored in DRUSH_LOG, + // but are -not- printed to the console. + if ($backend_options['integrate']) { + _drush_log($entry); + } + else { + $log =& drush_get_context('DRUSH_LOG', array()); + $log[] = $entry; + // Yes, this looks odd, but we might in fact be a backend command + // that ran another backend command. + drush_backend_packet('log', $entry); + } +} + +/** + * Retrieve the log messages from the log history + * + * @return + * Entire log history + */ +function drush_get_log() { + return drush_get_context('DRUSH_LOG', array()); +} + +/** + * Run print_r on a variable and log the output. + */ +function dlm($object) { + drush_log(print_r($object, TRUE)); +} + +/** + * Display the pipe output for the current request. + */ +function drush_pipe_output() { + $pipe = drush_get_context('DRUSH_PIPE_BUFFER'); + if (!empty($pipe)) { + drush_print_r($pipe, NULL, FALSE); + } +} + +// Print all timers for the request. +function drush_print_timers() { + global $timers; + $temparray = array(); + foreach ((array)$timers as $name => $timerec) { + // We have to use timer_read() for active timers, and check the record for others + if (isset($timerec['start'])) { + $temparray[$name] = timer_read($name); + } + else { + $temparray[$name] = $timerec['time']; + } + } + // Go no farther if there were no timers + if (count($temparray) > 0) { + // Put the highest cumulative times first + arsort($temparray); + $table = array(); + $table[] = array('Timer', 'Cum (sec)', 'Count', 'Avg (msec)'); + foreach ($temparray as $name => $time) { + $cum = round($time/1000, 3); + $count = $timers[$name]['count']; + if ($count > 0) { + $avg = round($time/$count, 3); + } + else { + $avg = 'N/A'; + } + $table[] = array($name, $cum, $count, $avg); + } + drush_print_table($table, TRUE, array(), STDERR); + } +} + +/** + * Turn drupal_set_message errors into drush_log errors + */ +function _drush_log_drupal_messages() { + if (function_exists('drupal_get_messages')) { + + $messages = drupal_get_messages(NULL, TRUE); + + if (array_key_exists('error', $messages)) { + //Drupal message errors. + foreach ((array) $messages['error'] as $error) { + $error = strip_tags($error); + $header = preg_match('/^warning: Cannot modify header information - headers already sent by /i', $error); + $session = preg_match('/^warning: session_start\(\): Cannot send session /i', $error); + if ($header || $session) { + //These are special cases for an unavoidable warnings + //that are generated by generating output before Drupal is bootstrapped. + //or sending a session cookie (seems to affect d7 only?) + //Simply ignore them. + continue; + } + elseif (preg_match('/^warning:/i', $error)) { + drush_log(preg_replace('/^warning: /i', '', $error), LogLevel::WARNING); + } + elseif (preg_match('/^notice:/i', $error)) { + drush_log(preg_replace('/^notice: /i', '', $error), LogLevel::NOTICE); + } + elseif (preg_match('/^user warning:/i', $error)) { + // This is a special case. PHP logs sql errors as 'User Warnings', not errors. + drush_set_error('DRUSH_DRUPAL_ERROR_MESSAGE', preg_replace('/^user warning: /i', '', $error)); + } + elseif (preg_match('/^deprecated function:/i', $error)) { + drush_log(preg_replace('/^deprecated function: /i', '', $error), LogLevel::WARNING); + } + else { + drush_set_error('DRUSH_DRUPAL_ERROR_MESSAGE', $error); + } + } + } + unset($messages['error']); + + // Log non-error messages. + foreach ($messages as $type => $items) { + foreach ($items as $item) { + drush_log(strip_tags($item), $type); + } + } + } +} + +// Copy of format_size() in Drupal. +function drush_format_size($size) { + if ($size < DRUSH_KILOBYTE) { + // format_plural() not always available. + return dt('@count bytes', array('@count' => $size)); + } + else { + $size = $size / DRUSH_KILOBYTE; // Convert bytes to kilobytes. + $units = array( + dt('@size KB', array()), + dt('@size MB', array()), + dt('@size GB', array()), + dt('@size TB', array()), + dt('@size PB', array()), + dt('@size EB', array()), + dt('@size ZB', array()), + dt('@size YB', array()), + ); + foreach ($units as $unit) { + if (round($size, 2) >= DRUSH_KILOBYTE) { + $size = $size / DRUSH_KILOBYTE; + } + else { + break; + } + } + return str_replace('@size', round($size, 2), $unit); + } +} + +/** + * @} End of "defgroup logging". + */ + +/** + * @defgroup errorhandling Managing errors that occur in the Drush framework. + * @{ + * Functions that manage the current error status of the Drush framework. + * + * These functions operate by maintaining a static variable that is a equal to the constant DRUSH_FRAMEWORK_ERROR if an + * error has occurred. + * This error code is returned at the end of program execution, and provide the shell or calling application with + * more information on how to diagnose any problems that may have occurred. + */ + +/** + * Set an error code for the error handling system. + * + * @param \Drupal\Component\Render\MarkupInterface|string $error + * A text string identifying the type of error. + * @param null|string $message + * Optional. Error message to be logged. If no message is specified, + * hook_drush_help will be consulted, using a key of 'error:MY_ERROR_STRING'. + * @param null|string $output_label + * Optional. Label to prepend to the error message. + * + * @return bool + * Always returns FALSE, to allow returning false in the calling functions, + * such as return drush_set_error('DRUSH_FRAMEWORK_ERROR'). + */ +function drush_set_error($error, $message = null, $output_label = "") { + $error_code =& drush_get_context('DRUSH_ERROR_CODE', DRUSH_SUCCESS); + $error_code = DRUSH_FRAMEWORK_ERROR; + + $error_log =& drush_get_context('DRUSH_ERROR_LOG', array()); + + if (is_numeric($error)) { + $error = 'DRUSH_FRAMEWORK_ERROR'; + } + elseif (!is_string($error)) { + // Typical case: D8 TranslatableMarkup, implementing MarkupInterface. + $error = "$error"; + } + + $message = ($message) ? $message : drush_command_invoke_all('drush_help', 'error:' . $error); + + if (is_array($message)) { + $message = implode("\n", $message); + } + + $error_log[$error][] = $message; + if (!drush_backend_packet('set_error', array('error' => $error, 'message' => $message))) { + drush_log(($message) ? $output_label . $message : $output_label . $error, LogLevel::ERROR, $error); + } + + return FALSE; +} + +/** + * Return the current error handling status + * + * @return + * The current aggregate error status + */ +function drush_get_error() { + return drush_get_context('DRUSH_ERROR_CODE', DRUSH_SUCCESS); +} + +/** + * Return the current list of errors that have occurred. + * + * @return + * An associative array of error messages indexed by the type of message. + */ +function drush_get_error_log() { + return drush_get_context('DRUSH_ERROR_LOG', array()); +} + +/** + * Check if a specific error status has been set. + * + * @param error + * A text string identifying the error that has occurred. + * @return + * TRUE if the specified error has been set, FALSE if not + */ +function drush_cmp_error($error) { + $error_log = drush_get_error_log(); + + if (is_numeric($error)) { + $error = 'DRUSH_FRAMEWORK_ERROR'; + } + + return array_key_exists($error, $error_log); +} + +/** + * Clear error context. + */ +function drush_clear_error() { + drush_set_context('DRUSH_ERROR_CODE', DRUSH_SUCCESS); +} + +/** + * Exit due to user declining a confirmation prompt. + * + * Usage: return drush_user_abort(); + */ +function drush_user_abort($msg = NULL) { + drush_set_context('DRUSH_USER_ABORT', TRUE); + drush_set_context('DRUSH_EXIT_CODE', DRUSH_EXITCODE_USER_ABORT); + drush_log($msg ? $msg : dt('Cancelled.'), LogLevel::CANCEL); + return FALSE; +} + +/** + * Turn PHP error handling off. + * + * This is commonly used while bootstrapping Drupal for install + * or updates. + * + * This also records the previous error_reporting setting, in + * case it wasn't recorded previously. + * + * @see drush_errors_off() + */ +function drush_errors_off() { + drush_get_context('DRUSH_ERROR_REPORTING', error_reporting(0)); + ini_set('display_errors', FALSE); +} + +/** + * Turn PHP error handling on. + * + * We default to error_reporting() here just in + * case drush_errors_on() is called before drush_errors_off() and + * the context is not yet set. + * + * @arg $errors string + * The default error level to set in drush. This error level will be + * carried through further drush_errors_on()/off() calls even if not + * provided in later calls. + * + * @see error_reporting() + * @see drush_errors_off() + */ +function drush_errors_on($errors = null) { + if (!isset($errors)) { + $errors = error_reporting(); + } + else { + drush_set_context('DRUSH_ERROR_REPORTING', $errors); + } + error_reporting(drush_get_context('DRUSH_ERROR_REPORTING', $errors)); + ini_set('display_errors', TRUE); +} + +/** + * @} End of "defgroup errorhandling". + */ + +/** + * Get the PHP memory_limit value in bytes. + */ +function drush_memory_limit() { + $value = trim(ini_get('memory_limit')); + $last = strtolower($value[strlen($value)-1]); + $size = (int) substr($value, 0, -1); + switch ($last) { + case 'g': + $size *= DRUSH_KILOBYTE; + case 'm': + $size *= DRUSH_KILOBYTE; + case 'k': + $size *= DRUSH_KILOBYTE; + } + + return $size; +} + +/** + * Unset the named key anywhere in the provided + * data structure. + */ +function drush_unset_recursive(&$data, $unset_key) { + if (!empty($data) && is_array($data)) { + unset($data[$unset_key]); + foreach ($data as $key => $value) { + if (is_array($value)) { + drush_unset_recursive($data[$key], $unset_key); + } + } + } +} + +/** + * Return a list of VCSs reserved files and directories. + */ +function drush_version_control_reserved_files() { + static $files = FALSE; + + if (!$files) { + // Also support VCSs that are not drush vc engines. + $files = array('.git', '.gitignore', '.hg', '.hgignore', '.hgrags'); + $engine_info = drush_get_engines('version_control'); + $vcs = array_keys($engine_info['engines']); + foreach ($vcs as $name) { + $version_control = drush_include_engine('version_control', $name); + $files = array_merge($files, $version_control->reserved_files()); + } + } + + return $files; +} + +/** + * Generate a random alphanumeric password. Copied from user.module. + */ +function drush_generate_password($length = 10) { + // This variable contains the list of allowable characters for the + // password. Note that the number 0 and the letter 'O' have been + // removed to avoid confusion between the two. The same is true + // of 'I', 1, and 'l'. + $allowable_characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'; + + // Zero-based count of characters in the allowable list: + $len = strlen($allowable_characters) - 1; + + // Declare the password as a blank string. + $pass = ''; + + // Loop the number of times specified by $length. + for ($i = 0; $i < $length; $i++) { + + // Each iteration, pick a random character from the + // allowable string and append it to the password: + $pass .= $allowable_characters[mt_rand(0, $len)]; + } + + return $pass; +} + +/** + * Form an associative array from a linear array. + * + * This function walks through the provided array and constructs an associative + * array out of it. The keys of the resulting array will be the values of the + * input array. The values will be the same as the keys unless a function is + * specified, in which case the output of the function is used for the values + * instead. + * + * @param $array + * A linear array. + * @param $function + * A name of a function to apply to all values before output. + * + * @return + * An associative array. + */ +function drush_map_assoc($array, $function = NULL) { + // array_combine() fails with empty arrays: + // http://bugs.php.net/bug.php?id=34857. + $array = !empty($array) ? array_combine($array, $array) : array(); + if (is_callable($function)) { + $array = array_map($function, $array); + } + return $array; +} +/** + * Clears completion caches. + * + * If called with no parameters the entire complete cache will be cleared. + * If called with just the $type parameter the global cache for that type will + * be cleared (in the site context, if any). If called with both $type and + * $command parameters the command cache of that type will be cleared (in the + * site context, if any). + * + * This is included in drush.inc as complete.inc is only loaded conditionally. + * + * @param $type + * The completion type (optional). + * @param $command + * The command name (optional), if command specific cache is to be cleared. + * If specifying a command, $type is not optional. + */ +function drush_complete_cache_clear($type = NULL, $command = NULL) { + require_once DRUSH_BASE_PATH . '/includes/complete.inc'; + if ($type) { + drush_cache_clear_all(drush_complete_cache_cid($type, $command), 'complete'); + return; + } + // No type or command, so clear the entire complete cache. + drush_cache_clear_all('*', 'complete', TRUE); +} + +/* + * Cast a value according to the provided format + * + * @param mixed $value. + * @param string $format + * Allowed values: auto, integer, float, bool, boolean, json, yaml. + * + * @return $value + * The value, re-typed as needed. + */ +function drush_value_format($value, $format) { + if ($format == 'auto') { + if (is_numeric($value)) { + $value = $value + 0; // http://php.net/manual/en/function.is-numeric.php#107326 + $format = gettype($value); + } + elseif (($value == 'TRUE') || ($value == 'FALSE')) { + $format = 'bool'; + } + } + + // Now, we parse the object. + switch ($format) { + case 'integer': + $value = (integer)$value; + break; + // from: http://php.net/gettype + // for historical reasons "double" is returned in case of a float, and not simply "float" + case 'double': + case 'float': + $value = (float)$value; + break; + case 'bool': + case 'boolean': + if ($value == 'TRUE') { + $value = TRUE; + } + elseif ($value == 'FALSE') { + $value = FALSE; + } + else { + $value = (bool)$value; + } + break; + + case 'json': + $value = drush_json_decode($value); + break; + + case 'yaml': + $value = \Drush\Internal\Symfony\Yaml\Yaml::parse($value, FALSE, TRUE); + break; + } + return $value; +} diff --git a/vendor/drush/drush/includes/engines.inc b/vendor/drush/drush/includes/engines.inc new file mode 100644 index 0000000000..56a2b41f34 --- /dev/null +++ b/vendor/drush/drush/includes/engines.inc @@ -0,0 +1,567 @@ + $data) { + $info[$type] += array( + 'description' => '', + 'option' => FALSE, + 'default' => NULL, + 'options' => array(), + 'sub-options' => array(), + 'config-aliases' => array(), + 'add-options-to-command' => FALSE, + 'combine-help' => FALSE, + ); + } + + return $info; +} + +/** + * Return a structured array of engines of a specific type. + * + * Engines are pluggable subsystems. Each engine of a specific type will + * implement the same set of API functions and perform the same high-level + * task using a different backend or approach. + * + * This function/hook is useful when you have a selection of several mutually + * exclusive options to present to a user to select from. + * + * Other commands are able to extend this list and provide their own engines. + * The hook can return useful information to help users decide which engine + * they need, such as description or list of available engine options. + * + * The engine path element will automatically default to a subdirectory (within + * the directory of the commandfile that implemented the hook) with the name of + * the type of engine - e.g. an engine "wget" of type "handler" provided by + * the "pm" commandfile would automatically be found if the file + * "pm/handler/wget.inc" exists and a specific path is not provided. + * + * @param $engine_type + * The type of engine. + * + * @return + * A structured array of engines. + */ +function drush_get_engines($engine_type) { + $info = drush_get_engine_types_info(); + if (!isset($info[$engine_type])) { + return drush_set_error('DRUSH_UNKNOWN_ENGINE_TYPE', dt('Unknown engine type !engine_type', array('!engine_type' => $engine_type))); + } + + $engines = array( + 'info' => $info[$engine_type], + 'engines' => array(), + ); + $list = drush_commandfile_list(); + $hook = 'drush_engine_' . str_replace('-', '_', $engine_type); + foreach ($list as $commandfile => $path) { + if (drush_command_hook($commandfile, $hook)) { + $function = $commandfile . '_' . $hook; + $result = $function(); + foreach ($result as $engine_name => $engine) { + // Add some defaults. + $engine += array( + 'commandfile' => $commandfile, + 'options' => array(), + 'sub-options' => array(), + 'drupal dependencies' => array(), + ); + + // Legacy engines live in a subdirectory + // of the commandfile that declared them. + $engine_path = sprintf("%s/%s", dirname($path), $engine_type); + if (file_exists($engine_path)) { + $engine['path'] = $engine_path; + } + // Build engine class name, in case the engine doesn't provide it. + // The class name is based on the engine type and name, converted + // from snake_case to CamelCase. + // For example for type 'package_handler' and engine 'git_drupalorg' + // the class is \Drush\PackageHandler\GitDrupalorg + elseif (!isset($engine['class'])) { + $parts = array(); + $parts[] = '\Drush'; + $parts[] = str_replace(' ', '', ucwords(strtr($engine_type, '_', ' '))); + $parts[] = str_replace(' ', '', ucwords(strtr($engine_name, '_', ' '))); + $engine['class'] = implode('\\', $parts); + } + + $engines['engines'][$engine_name] = $engine; + } + } + } + + return $engines; +} + +/** + * Take a look at all of the available engines, + * and create topic commands for each one that + * declares a topic. + */ +function drush_get_engine_topics() { + $items = array(); + $info = drush_get_engine_types_info(); + foreach ($info as $engine => $data) { + if (array_key_exists('topic', $data)) { + $items[$data['topic']] = array( + 'description' => $data['description'], + 'hidden' => TRUE, + 'topic' => TRUE, + 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, + 'callback' => 'drush_engine_topic_command', + 'callback arguments' => array($engine), + ); + } + } + return $items; +} + +/** + * Include, instantiate and validate command engines. + * + * @return FALSE if a engine doesn't validate. + */ +function drush_load_command_engines($command) { + $result = TRUE; + foreach ($command['engines'] as $engine_type => $config) { + $result = drush_load_command_engine($command, $engine_type); + // Stop loading engines if any of them fails. + if ($result === FALSE) { + break; + } + } + return $result; +} + +/** + * Returns engine config supplied in the command definition. + */ +function drush_get_command_engine_config($command, $engine_type, $metadata = array()) { + if (isset($command['engines'][$engine_type])) { + $metadata = array_merge($metadata, $command['engines'][$engine_type]); + } + return $metadata; +} + +/** + * Selects and loads an engine implementing the given type. + * + * Loaded engines are stored as a context. + */ +function drush_load_command_engine($command, $engine_type, $metadata = array()) { + drush_log(dt("Loading !engine engine.", array('!engine' => $engine_type), LogLevel::BOOTSTRAP)); + + $config = drush_get_command_engine_config($command, $engine_type, $metadata); + $engine_info = drush_get_engines($engine_type); + $engine = drush_select_engine($config, $engine_info); + $version = drush_drupal_major_version(); + + $context = $engine_type . '_engine_' . $engine . '_' . $version; + $instance = drush_get_context($context, FALSE); + if ($instance != FALSE) { + drush_set_engine($engine_type, $instance); + } + else { + $instance = drush_load_engine($engine_type, $engine, $config); + if ($instance == FALSE) { + return FALSE; + } + drush_set_context($context, $instance); + } + return $instance; +} + +/** + * Add command structure info from each engine type back into the command. + */ +function drush_merge_engine_data(&$command) { + // First remap engine data from the shortcut location + // ($command['engine_type']) to the standard location + // ($command['engines']['engine_type']) + $info = drush_get_engine_types_info(); + foreach ($info as $engine_type => $info) { + if (isset($command[$engine_type])) { + $config = $command[$engine_type]; + foreach ($info['config-aliases'] as $engine_option_alias_name => $engine_option_standard_name) { + if (array_key_exists($engine_option_alias_name, $config)) { + $config[$engine_option_standard_name] = $config[$engine_option_alias_name]; + unset($config[$engine_option_alias_name]); + } + } + // Convert single string values of 'require-engine-capability' to an array. + if (isset($config['require-engine-capability']) && is_string($config['require-engine-capability'])) { + $config['require-engine-capability'] = array($config['require-engine-capability']); + } + $command['engines'][$engine_type] = $config; + } + } + + foreach ($command['engines'] as $engine_type => $config) { + // Normalize engines structure. + if (!is_array($config)) { + unset($command['engines'][$engine_type]); + $command['engines'][$config] = array(); + $engine_type = $config; + } + + // Get all implementations for this engine type. + $engine_info = drush_get_engines($engine_type); + if ($engine_info === FALSE) { + return FALSE; + } + + // Complete command-declared engine type with default info. + $command['engines'][$engine_type] += $engine_info['info']; + $config = $command['engines'][$engine_type]; + + $engine_data = array(); + + // If there's a single implementation for this engine type, it will be + // loaded by default, and makes no sense to provide a command line option + // to select the only flavor (ie. --release_info=updatexml), so we won't + // add an option in this case. + // Additionally, depending on the command, it may be convenient to extend + // the command with the engine options. + if (count($engine_info['engines']) == 1) { + if ($config['add-options-to-command'] !== FALSE) { + // Add options and suboptions of the engine type and + // the sole implementation. + $engine = key($engine_info['engines']); + $data = $engine_info['engines'][$engine]; + $engine_data += array( + 'options' => $config['options'] + $data['options'], + 'sub-options' => $config['sub-options'] + $data['sub-options'], + ); + } + } + // Otherwise, provide a command option to choose between engines and add + // the engine options and sub-options. + else { + // Add engine type global options and suboptions. + $engine_data += array( + 'options' => $config['options'], + 'sub-options' => $config['sub-options'], + ); + + // If the 'combine-help' flag is set in the engine config, + // then we will combine all of the help items into the help + // text for $config['option']. + $combine_help = $config['combine-help']; + $combine_help_data = array(); + + // Process engines in order. First the default engine, the rest alphabetically. + $default = drush_select_engine($config, $engine_info); + $engines = array_keys($engine_info['engines']); + asort($engines); + array_unshift($engines, $default); + $engines = array_unique($engines); + foreach ($engines as $engine) { + $data = $engine_info['engines'][$engine]; + // Check to see if the command requires any particular + // capabilities. If no capabilities are required, then + // all engines are acceptable. + $engine_is_usable = TRUE; + if (array_key_exists('require-engine-capability', $config)) { + // See if the engine declares that it provides any + // capabilities. If no capabilities are listed, then + // it is assumed that the engine can satisfy all requirements. + if (array_key_exists('engine-capabilities', $data)) { + $engine_is_usable = FALSE; + // If 'require-engine-capability' is TRUE instead of an array, + // then only engines that are universal (do not declare any + // particular capabilities) are usable. + if (is_array($config['require-engine-capability'])) { + foreach ($config['require-engine-capability'] as $required) { + // We need an engine that provides any one of the requirements. + if (in_array($required, $data['engine-capabilities'])) { + $engine_is_usable = TRUE; + } + } + } + } + } + if ($engine_is_usable) { + $command['engines'][$engine_type]['usable'][] = $engine; + if (!isset($data['hidden'])) { + $option = $config['option'] . '=' . $engine; + $engine_data['options'][$option]['description'] = array_key_exists('description', $data) ? $data['description'] : NULL; + if ($combine_help) { + $engine_data['options'][$option]['hidden'] = TRUE; + if (drush_get_context('DRUSH_VERBOSE') || ($default == $engine) || !isset($data['verbose-only'])) { + $combine_help_data[$engine] = $engine . ': ' . $data['description']; + } + } + if (isset($data['options'])) { + $engine_data['sub-options'][$option] = $data['options']; + } + if (isset($data['sub-options'])) { + $engine_data['sub-options'] += $data['sub-options']; + } + } + } + } + if (!empty($combine_help_data)) { + $engine_selection_option = $config['option']; + if (!is_array($engine_data['options'][$engine_selection_option])) { + $engine_data['options'][$engine_selection_option] = array('description' => $config['options'][$engine_selection_option]); + } + if (drush_get_context('DRUSH_VERBOSE')) { + $engine_data['options'][$engine_selection_option]['description'] .= "\n" . dt("All available values are:") . "\n - " . implode("\n - ", $combine_help_data) . "\n"; + } + else { + $engine_data['options'][$engine_selection_option]['description'] .= " " . dt("Available: ") . implode(', ', array_keys($combine_help_data)) . ". "; + } + $engine_data['options'][$engine_selection_option]['description'] .= dt("Default is !default.", array('!default' => $default)); + } + else { + // If the help options are not combined, then extend the + // default engine description. + $desc = $engine_info['engines'][$default]['description']; + $engine_info['engines'][$default]['description'] = dt('Default !type engine.', array('!type' => $engine_type)) . ' ' . $desc; + } + } + // This was simply array_merge_recursive($command, $engine_data), but this + // function has an undesirable behavior when merging primative types. + // If there is a 'key' => 'value' in $command, and the same 'key' => 'value' + // exists in $engine data, then the result will be 'key' => array('value', 'value');. + // This is NOT what we want, so we provide our own 'overlay' function instead. + $command = _drush_array_overlay_recursive($command, $engine_data); + } +} + +// Works like array_merge_recursive(), but does not convert primative +// types into arrays. Ever. +function _drush_array_overlay_recursive($a, $b) { + foreach ($b as $key => $value) { + if (!isset($a[$key]) || !is_array($a[$key])) { + $a[$key] = $b[$key]; + } + else { + $a[$key] = _drush_array_overlay_recursive($a[$key], $b[$key]); + } + } + return $a; +} + +/** + * Implementation of command hook for docs-output-formats + */ +function drush_engine_topic_command($engine) { + $engine_instances = drush_get_engines($engine); + $option = $engine_instances['info']['option']; + + if (isset($engine_instances['info']['topic-file'])) { + // To do: put this file next to the commandfile that defines the + // engine type, not in the core docs directory. + $docs_dir = drush_get_context('DOC_PREFIX', DRUSH_BASE_PATH); + $path = $engine_instances['info']['topic-file']; + $docs_file = (drush_is_absolute_path($path) ? '' : $docs_dir . '/') . $path; + $doc_text = drush_html_to_text(file_get_contents($docs_file)); + } + elseif (isset($engine_instances['info']['topic-text'])) { + $doc_text = $engine_instances['info']['topic-text']; + } + else { + return drush_set_error('DRUSH_BAD_ENGINE_TOPIC', dt("The engine !engine did not define its topic command correctly.", array('!engine' => $engine))); + } + + // Look at each instance of the engine; if it has an html + // file in the the 'topics' folder named after itself, then + // include the file contents in the engine topic text. + $instances = $engine_instances['engines']; + ksort($instances); + foreach ($instances as $instance => $config) { + if (isset($config['description'])) { + $doc_text .= "\n\n::: --$option=$instance :::\n" . $config['description']; + $path = $config['path'] . '/topics/' . $instance . '.html'; + if (file_exists($path)) { + $doc_text .= "\n" . drush_html_to_text(file_get_contents($path)); + } + $additional_topic_text = drush_command_invoke_all('drush_engine_topic_additional_text', $engine, $instance, $config); + if (!empty($additional_topic_text)) { + $doc_text .= "\n\n" . implode("\n\n", $additional_topic_text); + } + } + } + + // Write the topic text to a file so that is can be paged + $file = drush_save_data_to_temp_file($doc_text); + drush_print_file($file); +} + +/** + * Selects an engine between the available ones. + * + * Precedence: + * + * - preferred engine, if available. + * - user supplied engine via cli. + * - default engine from engine type / command declaration. + * - the first engine available. + * + * @param array $config + * Engine type configuration. My be overridden in command declaration. + * @param array $engine_info + * Engine type declaration. + * @param string $default + * Preferred engine. + * + * @return string + * Selected engine. + */ +function drush_select_engine($config, $engine_info, $preferred = NULL) { + $engines = array_keys($engine_info['engines']); + + if (in_array($preferred, $engines)) { + return $preferred; + } + + if (!empty($config['option'])) { + $engine = drush_get_option($config['option'], FALSE); + if ($engine && in_array($engine, $engines)) { + return $engine; + } + } + + if (isset($config['default']) && in_array($config['default'], $engines)) { + return $config['default']; + } + + return current($engines); +} + +/** + * Loads and validate an engine of the given type. + * + * @param string $type + * Engine type. + * @param string $engine + * Engine name. + * @param array $config + * Engine configuration. Tipically it comes from a command declaration. + * + * @return + * TRUE or instanced object of available class on success. FALSE on fail. + */ +function drush_load_engine($type, $engine, $config = array()) { + $engine_info = drush_get_engines($type); + $engine = drush_select_engine($config, $engine_info, $engine); + $config['engine-info'] = $engine_info['engines'][$engine]; + + // Check engine dependency on drupal modules before include. + $dependencies = $config['engine-info']['drupal dependencies']; + foreach ($dependencies as $dependency) { + if (!drush_module_exists($dependency)) { + return drush_set_error('DRUSH_ENGINE_DEPENDENCY_ERROR', dt('!engine_type: !engine engine needs the following modules installed/enabled to run: !dependencies.', array('!engine_type' => $type, '!engine' => $engine, '!dependencies' => implode(', ', $dependencies)))); + } + } + + $result = drush_include_engine($type, $engine, $config); + if (is_object($result)) { + $valid = method_exists($result, 'validate') ? $result->validate() : TRUE; + if ($valid) { + drush_set_engine($type, $result); + } + } + else { + $function = strtr($type, '-', '_') . '_validate'; + $valid = function_exists($function) ? call_user_func($function) : TRUE; + } + if (!$valid) { + return FALSE; + } + return $result; +} + +/** + * Include the engine code for a specific named engine of a certain type. + * + * If the engine type has implemented hook_drush_engine_$type the path to the + * engine specified in the array will be used. + * + * If a class named in the form drush_$type_$engine exists, it will return an + * instance of the class. + * + * @param string $type + * The type of engine. + * @param string $engine + * The name for the engine to include. + * @param array $config + * Parameters for the engine class constructor. + * + * @return + * TRUE or instanced object of available class on success. FALSE on fail. + */ +function drush_include_engine($type, $engine, $config = NULL) { + $engine_info = drush_get_engines($type); + + // Pick the engine name that actually implements the requested engine. + $engine = isset($engine_info['engines'][$engine]['implemented-by']) ? $engine_info['engines'][$engine]['implemented-by'] : $engine; + + // Legacy engines live in a subdirectory of the commandfile + // that declares them. We need to explicitly include the file. + if (isset($engine_info['engines'][$engine]['path'])) { + $path = $engine_info['engines'][$engine]['path']; + if (!drush_include($path, $engine)) { + return drush_set_error('DRUSH_ENGINE_INCLUDE_FAILED', dt('Unable to include the !type engine !engine from !path.' , array('!path' => $path, '!type' => $type, '!engine' => $engine))); + } + // Legacy engines may be implemented in a magic class name. + $class = 'drush_' . $type . '_' . str_replace('-', '_', $engine); + if (class_exists($class)) { + $instance = new $class($config); + $instance->engine_type = $type; + $instance->engine = $engine; + return $instance; + } + + return TRUE; + } + + return drush_get_class($engine_info['engines'][$engine]['class'], array($type, $engine, $config)); +} + +/** + * Return the engine of the specified type that was loaded by the Drush command. + */ +function drush_get_engine($type) { + return drush_get_context($type . '_engine', FALSE); +} + +/** + * Called by the Drush command (@see _drush_load_command_engines()) + * to cache the active engine instance. + */ +function drush_set_engine($type, $instance) { + drush_set_context($type . '_engine', $instance); +} + +/** + * Add engine topics to the command topics, if any. + */ +function drush_engine_add_help_topics(&$command) { + $engine_types = drush_get_engine_types_info(); + foreach ($command['engines'] as $engine_type => $config) { + $info = $engine_types[$engine_type]; + if (isset($info['topics'])) { + $command['topics'] = array_merge($command['topics'], $info['topics']); + } + if (isset($info['topic'])) { + $command['topics'][] = $info['topic']; + } + } +} diff --git a/vendor/drush/drush/includes/environment.inc b/vendor/drush/drush/includes/environment.inc new file mode 100644 index 0000000000..1dda026dff --- /dev/null +++ b/vendor/drush/drush/includes/environment.inc @@ -0,0 +1,880 @@ + php_ini_loaded_file())); + } + else { + return dt('Please check your configuration settings in your php.ini file or in your drush.ini file; see examples/example.drush.ini for details.'); + } +} + +/** + * Evalute the environment after an abnormal termination and + * see if we can determine any configuration settings that the user might + * want to adjust. + */ +function _drush_postmortem() { + // Make sure that the memory limit has been bumped up from the minimum default value of 32M. + $php_memory_limit = drush_memory_limit(); + if (($php_memory_limit > 0) && ($php_memory_limit <= 32*DRUSH_KILOBYTE*DRUSH_KILOBYTE)) { + drush_set_error('DRUSH_MEMORY_LIMIT', dt('Your memory limit is set to !memory_limit; Drush needs as much memory to run as Drupal. !php_ini_msg', array('!memory_limit' => $php_memory_limit / (DRUSH_KILOBYTE*DRUSH_KILOBYTE) . 'M', '!php_ini_msg' => _drush_php_ini_loaded_file_message()))); + } +} + +/** + * Evaluate the environment before command bootstrapping + * begins. If the php environment is too restrictive, then + * notify the user that a setting change is needed and abort. + */ +function _drush_environment_check_php_ini() { + $ini_checks = array('safe_mode' => '', 'open_basedir' => '', 'disable_functions' => array('exec', 'system'), 'disable_classes' => ''); + + // Test to insure that certain php ini restrictions have not been enabled + $prohibited_list = array(); + foreach ($ini_checks as $prohibited_mode => $disallowed_value) { + $ini_value = ini_get($prohibited_mode); + $invalid_value = FALSE; + if (empty($disallowed_value)) { + $invalid_value = !empty($ini_value) && (strcasecmp($ini_value, 'off') != 0); + } + else { + foreach ($disallowed_value as $test_value) { + if (preg_match('/(^|,)' . $test_value . '(,|$)/', $ini_value)) { + $invalid_value = TRUE; + } + } + } + if ($invalid_value) { + $prohibited_list[] = $prohibited_mode; + } + } + if (!empty($prohibited_list)) { + drush_log(dt('The following restricted PHP modes have non-empty values: !prohibited_list. This configuration is incompatible with drush. !php_ini_msg', array('!prohibited_list' => implode(' and ', $prohibited_list), '!php_ini_msg' => _drush_php_ini_loaded_file_message())), LogLevel::ERROR); + } + + return TRUE; +} + +/** + * Returns the current working directory. + * + * This is the directory as it was when drush was started, not the + * directory we are currently in. For that, use getcwd() directly. + */ +function drush_cwd() { + if ($path = drush_get_context('DRUSH_OLDCWD')) { + return $path; + } + // We use PWD if available because getcwd() resolves symlinks, which + // could take us outside of the Drupal root, making it impossible to find. + // $_SERVER['PWD'] isn't set on windows and generates a Notice. + $path = isset($_SERVER['PWD']) ? $_SERVER['PWD'] : ''; + if (empty($path)) { + $path = getcwd(); + } + + // Convert windows paths. + $path = Path::canonicalize($path); + + // Save original working dir case some command wants it. + drush_set_context('DRUSH_OLDCWD', $path); + + // Make a read-only copy of the original cwd at env.cwd + // for forwards-compatibility + drush_set_option('env.cwd', $path); + + return $path; +} + +/** + * Converts a Windows path (dir1\dir2\dir3) into a Unix path (dir1/dir2/dir3). + * Also converts a cygwin "drive emulation" path (/cygdrive/c/dir1) into a + * proper drive path, still with Unix slashes (c:/dir1). + */ +function _drush_convert_path($path) { + $path = str_replace('\\','/', $path); + if (drush_is_windows(_drush_get_os()) && !drush_is_cygwin(_drush_get_os())) { + $path = preg_replace('/^\/cygdrive\/([A-Za-z])(.*)$/', '\1:\2', $path); + } + + return $path; +} + +/** + * Returns parent directory. + * + * @param string + * Path to start from. + * + * @return string + * Parent path of given path. + */ +function _drush_shift_path_up($path) { + if (empty($path)) { + return FALSE; + } + $path = explode(DIRECTORY_SEPARATOR, $path); + // Move one directory up. + array_pop($path); + return implode(DIRECTORY_SEPARATOR, $path); + // return dirname($path); +} + +/** + * Like Drupal conf_path, but searching from beneath. + * Allows proper site uri detection in site sub-directories. + * + * Essentially looks for a settings.php file. Drush uses this + * function to find a usable site based on the user's current + * working directory. + * + * @param string + * Search starting path. Defaults to current working directory. + * + * @return + * Current site path (folder containing settings.php) or FALSE if not found. + */ +function drush_site_path($path = NULL) { + $site_path = FALSE; + + $path = empty($path) ? drush_cwd() : $path; + // Check the current path. + if (file_exists($path . '/settings.php')) { + $site_path = $path; + } + else { + // Move up dir by dir and check each. + // Stop if we get to a Drupal root. We don't care + // if it is DRUSH_SELECTED_DRUPAL_ROOT or some other root. + while (($path = _drush_shift_path_up($path)) && !drush_valid_root($path)) { + if (file_exists($path . '/settings.php')) { + $site_path = $path; + break; + } + } + } + + $site_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT'); + if (file_exists($site_root . '/sites/sites.php')) { + $sites = array(); + // This will overwrite $sites with the desired mappings. + include($site_root . '/sites/sites.php'); + // We do a reverse lookup here to determine the URL given the site key. + if ($match = array_search($site_path, $sites)) { + $site_path = $match; + } + } + + // Last resort: try from site root + if (!$site_path) { + if ($site_root) { + if (file_exists($site_root . '/sites/default/settings.php')) { + $site_path = $site_root . '/sites/default'; + } + } + } + + return $site_path; +} + +/** + * Lookup a site's directory via the sites.php file given a hostname. + * + * @param $hostname + * The hostname of a site. May be converted from URI. + * + * @return $dir + * The directory associated with that hostname or FALSE if not found. + */ +function drush_site_dir_lookup_from_hostname($hostname, $site_root = NULL) { + if (!isset($site_root)) { + $site_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT'); + } + if (!empty($site_root) && file_exists($site_root . '/sites/sites.php')) { + $sites = array(); + // This will overwrite $sites with the desired mappings. + include($site_root . '/sites/sites.php'); + return isset($sites[$hostname]) ? $sites[$hostname] : FALSE; + } + else { + return FALSE; + } +} + +/** + * This is a copy of Drupal's conf_path function, taken from D7 and + * adjusted slightly to search from the selected Drupal Root. + * + * Drush uses this routine to find a usable site based on a URI + * passed in via a site alias record or the --uri commandline option. + * + * Drush uses Drupal itself (specifically, the Drupal conf_path function) + * to bootstrap the site itself. If the implementation of conf_path + * changes, the site should still bootstrap correctly; the only consequence + * of this routine not working is that drush configuration files + * (drushrc.php) stored with the site's drush folders might not be found. + */ +function drush_conf_path($server_uri, $require_settings = TRUE) { + $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT'); + if(empty($drupal_root) || empty($server_uri)) { + return NULL; + } + $parsed_uri = parse_url($server_uri); + if (is_array($parsed_uri) && !array_key_exists('scheme', $parsed_uri)) { + $parsed_uri = parse_url('http://' . $server_uri); + } + if (!is_array($parsed_uri)) { + return NULL; + } + $server_host = $parsed_uri['host']; + if (array_key_exists('path', $parsed_uri)) { + $server_uri = $parsed_uri['path'] . '/index.php'; + } + else { + $server_uri = "/index.php"; + } + $confdir = 'sites'; + + $sites = array(); + if (file_exists($drupal_root . '/' . $confdir . '/sites.php')) { + // This will overwrite $sites with the desired mappings. + include($drupal_root . '/' . $confdir . '/sites.php'); + } + + $uri = explode('/', $server_uri); + $server = explode('.', implode('.', array_reverse(explode(':', rtrim($server_host, '.'))))); + for ($i = count($uri) - 1; $i > 0; $i--) { + for ($j = count($server); $j > 0; $j--) { + $dir = implode('.', array_slice($server, -$j)) . implode('.', array_slice($uri, 0, $i)); + if (isset($sites[$dir]) && file_exists($drupal_root . '/' . $confdir . '/' . $sites[$dir])) { + $dir = $sites[$dir]; + } + if (file_exists($drupal_root . '/' . $confdir . '/' . $dir . '/settings.php') || (!$require_settings && file_exists(DRUPAL_ROOT . '/' . $confdir . '/' . $dir))) { + $conf = "$confdir/$dir"; + return $conf; + } + } + } + $conf = "$confdir/default"; + return $conf; +} + +/** + * Exhaustive depth-first search to try and locate the Drupal root directory. + * This makes it possible to run Drush from a subdirectory of the drupal root. + * + * @param + * Search start path. Defaults to current working directory. + * @return + * A path to drupal root, or FALSE if not found. + */ +function drush_locate_root($start_path = NULL) { + $drupal_root = FALSE; + + $start_path = empty($start_path) ? drush_cwd() : $start_path; + foreach (array(TRUE, FALSE) as $follow_symlinks) { + $path = $start_path; + if ($follow_symlinks && is_link($path)) { + $path = realpath($path); + } + // Check the start path. + if (drush_valid_root($path)) { + $drupal_root = $path; + break; + } + else { + // Move up dir by dir and check each. + while ($path = _drush_shift_path_up($path)) { + if ($follow_symlinks && is_link($path)) { + $path = realpath($path); + } + if (drush_valid_root($path)) { + $drupal_root = $path; + break 2; + } + } + } + } + + return $drupal_root; +} + +/** + * Checks whether given path qualifies as a Drupal root. + * + * @param string + * Path to check. + * + * @return string + * The relative path to common.inc (varies by Drupal version), or FALSE if not + * a Drupal root. + */ +function drush_valid_root($path) { + $bootstrap_class = drush_bootstrap_class_for_root($path); + return $bootstrap_class != NULL; +} + +/** + * Tests the currently loaded database credentials to ensure a database connection can be made. + * + * @param bool $log_errors + * (optional) If TRUE, log error conditions; otherwise be quiet. + * + * @return bool + * TRUE if database credentials are valid. + */ +function drush_valid_db_credentials() { + try { + $sql = drush_sql_get_class(); + if (!$sqlVersion = drush_sql_get_version()) { + drush_log(dt('While checking DB credentials, could not instantiate SQLVersion class.'), 'debug'); + return FALSE; + } + if (!$sqlVersion->valid_credentials($sql->db_spec())) { + drush_log(dt('DB credentials are invalid.'), 'debug'); + return FALSE; + } + return $sql->query('SELECT 1;'); + } + catch (Exception $e) { + drush_log(dt('Checking DB credentials yielded error: @e', array('@e' => $e->getMessage())), 'debug'); + return FALSE; + } +} + +/** + * Determine a proper way to call drush again + * + * This check if we were called directly or as an argument to some + * wrapper command (php and sudo are checked now). + * + * Calling ./drush.php directly yields the following environment: + * + * _SERVER["argv"][0] => ./drush.php + * + * Calling php ./drush.php also yields the following: + * + * _SERVER["argv"][0] => ./drush.php + * + * Note that the $_ global is defined only in bash and therefore cannot + * be relied upon. + * + * The DRUSH_COMMAND constant is initialised to the value of this + * function when environment.inc is loaded. + * + * @see DRUSH_COMMAND + */ +function drush_find_drush() { + if ($drush = realpath($_SERVER['argv']['0'])) { + return Path::canonicalize($drush); + } + return FALSE; +} + +/** + * Verify that we are running PHP through the command line interface. + * + * This function is useful for making sure that code cannot be run via the web server, + * such as a function that needs to write files to which the web server should not have + * access to. + * + * @return + * A boolean value that is true when PHP is being run through the command line, + * and false if being run through cgi or mod_php. + */ +function drush_verify_cli() { + return (php_sapi_name() == 'cli' || (is_numeric($_SERVER['argc']) && $_SERVER['argc'] > 0)); +} + +/** + * Build a drush command suitable for use for Drush to call itself + * e.g. in backend_invoke. + */ +function drush_build_drush_command($drush_path = NULL, $php = NULL, $os = NULL, $remote_command = FALSE, $environment_variables = array()) { + $os = _drush_get_os($os); + $additional_options = ''; + $prefix = ''; + if (!$drush_path) { + if (!$remote_command) { + $drush_path = DRUSH_COMMAND; + } + else { + $drush_path = 'drush'; // drush_is_windows($os) ? 'drush.bat' : 'drush'; + } + } + // If the path to drush points to drush.php, then we will need to + // run it via php rather than direct execution. By default, we + // will use 'php' unless something more specific was passed in + // via the --php flag. + if (substr($drush_path, -4) == ".php") { + if (!isset($php)) { + $php = drush_get_option('php'); + if (!isset($php)) { + $php = 'php'; + } + } + if (isset($php) && ($php != "php")) { + $additional_options .= ' --php=' . drush_escapeshellarg($php, $os); + } + // We will also add in the php options from --php-options + $prefix .= drush_escapeshellarg($php, $os); + $php_options = implode(' ', drush_get_context_options('php-options')); + if (!empty($php_options)) { + $prefix .= ' ' . $php_options; + $additional_options .= ' --php-options=' . drush_escapeshellarg($php_options, $os); + } + } + else { + // Set environment variables to propogate config to redispatched calls. + if (drush_has_bash($os)) { + if ($php) { + $environment_variables['DRUSH_PHP'] = $php; + } + if ($php_options_alias = drush_get_option('php-options', NULL, 'alias')) { + $environment_variables['PHP_OPTIONS'] = $php_options_alias; + } + $columns = drush_get_context('DRUSH_COLUMNS'); + if (($columns) && ($columns != 80)) { + $environment_variables['COLUMNS'] = $columns; + } + } + } + + // Add environmental variables, if present + if (!empty($environment_variables)) { + $env = 'env'; + foreach ($environment_variables as $key=>$value) { + $env .= ' ' . drush_escapeshellarg($key, $os) . '=' . drush_escapeshellarg($value, $os); + } + $prefix = $env . ' ' . $prefix; + } + + return trim($prefix . ' ' . drush_escapeshellarg($drush_path, $os) . $additional_options); +} + +/** + * Check if the operating system is Winodws + * running some variant of cygwin -- either + * Cygwin or the MSYSGIT shell. If you care + * which is which, test mingw first. + */ +function drush_is_cygwin($os = NULL) { + return _drush_test_os($os, array("CYGWIN","CWRSYNC","MINGW")); +} + +function drush_is_mingw($os = NULL) { + return _drush_test_os($os, array("MINGW")); +} + +/** + * Return tar executable name specific for the current OS + */ +function drush_get_tar_executable() { + return drush_is_windows() ? (drush_is_mingw() ? "tar.exe" : "bsdtar.exe") : "tar"; +} + +/** + * Check if the operating system is OS X. + * This will return TRUE for Mac OS X (Darwin). + */ +function drush_is_osx($os = NULL) { + return _drush_test_os($os, array("DARWIN")); +} + +/** + * Checks if the operating system has bash. + * + * MinGW has bash, but PHP isn't part of MinGW and hence doesn't run in bash. + */ +function drush_has_bash($os = NULL) { + return (drush_is_cygwin($os) && !drush_is_mingw($os)) || !drush_is_windows($os); +} + +/** + * Checks operating system and returns + * supported bit bucket folder. + */ +function drush_bit_bucket() { + if (drush_has_bash()) { + return '/dev/null'; + } + else { + return 'nul'; + } +} + +/** + * Return the OS we are running under. + * + * @return string + * Linux + * WIN* (e.g. WINNT) + * CYGWIN + * MINGW* (e.g. MINGW32) + */ +function _drush_get_os($os = NULL) { + // The special os "CWRSYNC" can be used to indicate that we are testing + // a path that will be passed as an argument to cwRsync, which requires + // that the path be converted to /cygdrive/c/path, even on DOS or Powershell. + // The special os "RSYNC" can be used to indicate that we want to assume + // "CWRSYNC" when cwrsync is installed, or default to the local OS otherwise. + if (strtoupper($os) == "RSYNC") { + $os = _drush_get_os("LOCAL"); + // For now we assume that cwrsync is always installed on Windows, and never installed son any other platform. + return drush_is_windows($os) ? "CWRSYNC" : $os; + } + // We allow "LOCAL" to document, in instances where some parameters are being escaped + // for use on a remote machine, that one particular parameter will always be used on + // the local machine (c.f. drush_backend_invoke). + if (isset($os) && ($os != "LOCAL")) { + return $os; + } + if (_drush_test_os(getenv("MSYSTEM"), array("MINGW"))) { + return getenv("MSYSTEM"); + } + // QUESTION: Can we differentiate between DOS and POWERSHELL? They appear to have the same environment. + // At the moment, it does not seem to matter; they behave the same from PHP. + // At this point we will just return PHP_OS. + return PHP_OS; +} + +function _drush_test_os($os, $os_list_to_check) { + $os = _drush_get_os($os); + foreach ($os_list_to_check as $test) { + if (strtoupper(substr($os, 0, strlen($test))) == strtoupper($test)) { + return TRUE; + } + } + return FALSE; +} + +/** + * Read the drush info file. + */ +function drush_read_drush_info() { + return Drush::ReadDrushInfo(); +} + +/** + * Make a determination whether or not the given + * host is local or not. + * + * @param host + * A hostname, 'localhost' or '127.0.0.1'. + * @return + * True if the host is local. + */ +function drush_is_local_host($host) { + // Check to see if the provided host is "local". + // @see hook_drush_sitealias_alter() in drush.api.php. + if ( + ($host == 'localhost') || + ($host == '127.0.0.1') + ) { + return TRUE; + } + + return FALSE; +} + +/** + * Return the user's home directory. + */ +function drush_server_home() { + try { + return Path::getHomeDirectory(); + } catch (Exception $e) { + return NULL; + } +} + +/** + * Return the name of the user running drush. + */ +function drush_get_username() { + $name = NULL; + if (!$name = getenv("username")) { // Windows + if (!$name = getenv("USER")) { + // If USER not defined, use posix + if (function_exists('posix_getpwuid')) { + $processUser = posix_getpwuid(posix_geteuid()); + $name = $processUser['name']; + } + } + } + return $name; +} + +/** + * The path to the global cache directory. + * + * @param subdir + * Return the specified subdirectory inside the global + * cache directory instead. The subdirectory is created. + */ +function drush_directory_cache($subdir = '') { + $cache_locations = array(); + if (getenv('CACHE_PREFIX')) { + $cache_locations[getenv('CACHE_PREFIX')] = 'cache'; + } + $home = drush_server_home(); + if ($home) { + $cache_locations[$home] = '.drush/cache'; + } + $cache_locations[drush_find_tmp()] = 'drush-' . drush_get_username() . '/cache'; + foreach ($cache_locations as $base => $dir) { + if (!empty($base) && is_writable($base)) { + $cache_dir = $base . '/' . $dir; + if (!empty($subdir)) { + $cache_dir .= '/' . $subdir; + } + if (drush_mkdir($cache_dir)) { + return $cache_dir; + } + else { + // If the base directory is writable, but the cache directory + // is not, then we will get an error. The error will be displayed, + // but we will still call drush_clear_error so that we can go + // on and try the next location to see if we can find a cache + // directory somewhere. + drush_clear_error(); + } + } + } + return drush_set_error('DRUSH_NO_WRITABLE_CACHE', dt('Drush must have a writable cache directory; please insure that one of the following locations is writable: @locations', + array('@locations' => implode(',', array_keys($cache_locations))))); +} + +/** + * Get complete information for all available extensions (modules and themes). + * + * @return + * An array containing info for all available extensions. In D8, these are Extension objects. + */ +function drush_get_extensions($include_hidden = TRUE) { + drush_include_engine('drupal', 'environment'); + $extensions = array_merge(drush_get_modules($include_hidden), drush_get_themes($include_hidden)); + foreach ($extensions as $name => $extension) { + if (isset($extension->info['name'])) { + $extensions[$name]->label = $extension->info['name'].' ('.$name.')'; + } + else { + drush_log(dt("Extension !name provides no human-readable name in .info file.", array('!name' => $name), LogLevel::DEBUG)); + $extensions[$name]->label = $name.' ('.$name.')'; + } + if (empty($extension->info['package'])) { + $extensions[$name]->info['package'] = dt('Other'); + } + } + return $extensions; +} + +/** + * Gets the extension name. + * + * @param $info + * The extension info. + * @return string + * The extension name. + */ +function drush_extension_get_name($info) { + drush_include_engine('drupal', 'environment'); + return _drush_extension_get_name($info); +} + +/** + * Gets the extension type. + * + * @param $info + * The extension info. + * @return string + * The extension type. + */ +function drush_extension_get_type($info) { + drush_include_engine('drupal', 'environment'); + return _drush_extension_get_type($info); +} + +/** + * Gets the extension path. + * + * @param $info + * The extension info. + * @return string + * The extension path. + */ +function drush_extension_get_path($info) { + drush_include_engine('drupal', 'environment'); + return _drush_extension_get_path($info); +} + +/** + * Test compatibility of a extension with version of drupal core and php. + * + * @param $file Extension object as returned by system_rebuild_module_data(). + * @return FALSE if the extension is compatible. + */ +function drush_extension_check_incompatibility($file) { + // TODO: what is the right way to detect core modules? + if (isset($file->info['package']) && ($file->info['package'] == 'Core')) { + return FALSE; + } + // TODO: validate core_version_requirement + if (isset($file->info['core_version_requirement'])) { + return FALSE; + } + if (!isset($file->info['core']) || $file->info['core'] != drush_get_drupal_core_compatibility()) { + return 'Drupal'; + } + if (version_compare(phpversion(), $file->info['php']) < 0) { + return 'PHP'; + } + return FALSE; +} + +/** + * + */ +function drush_drupal_required_modules($modules) { + drush_include_engine('drupal', 'environment'); + return _drush_drupal_required_modules($modules); +} + +/** + * Return the default theme. + * + * @return + * Machine name of the default theme. + */ +function drush_theme_get_default() { + drush_include_engine('drupal', 'environment'); + return _drush_theme_default(); +} + +/** + * Return the administration theme. + * + * @return + * Machine name of the administration theme. + */ +function drush_theme_get_admin() { + drush_include_engine('drupal', 'environment'); + return _drush_theme_admin(); +} + +/** + * Return the path to public files directory. + */ +function drush_file_get_public() { + drush_include_engine('drupal', 'environment'); + return _drush_file_public_path(); +} + +/** + * Return the path to private files directory. + */ +function drush_file_get_private() { + drush_include_engine('drupal', 'environment'); + return _drush_file_private_path(); +} + +/** + * Returns the sitewide Drupal directory for extensions. + */ +function drush_drupal_sitewide_directory($major_version = NULL) { + if (!$major_version) { + $major_version = drush_drupal_major_version(); + } + return ($major_version < 8) ? 'sites/all' : ''; +} + +/** + * Helper function to get core compatibility constant. + * + * @return string + * The Drupal core compatibility constant. + */ +function drush_get_drupal_core_compatibility() { + if (defined('DRUPAL_CORE_COMPATIBILITY')) { + return DRUPAL_CORE_COMPATIBILITY; + } + elseif (defined('\Drupal::CORE_COMPATIBILITY')) { + return \Drupal::CORE_COMPATIBILITY; + } +} + +/** + * Set Env. Variables for given site-alias. + */ +function drush_set_environment_vars(array $site_record) { + if (!empty($site_record)) { + $os = drush_os($site_record); + if (isset($site_record['#env-vars'])) { + foreach ($site_record['#env-vars'] as $var => $value) { + $env_var = drush_escapeshellarg($var, $os, TRUE) . '=' . drush_escapeshellarg($value, $os, TRUE); + putenv($env_var); + } + } + } +} diff --git a/vendor/drush/drush/includes/exec.inc b/vendor/drush/drush/includes/exec.inc new file mode 100644 index 0000000000..412cc5f965 --- /dev/null +++ b/vendor/drush/drush/includes/exec.inc @@ -0,0 +1,420 @@ +&1', $output, $result); + _drush_shell_exec_output_set($output); + + if (drush_get_context('DRUSH_DEBUG')) { + foreach ($output as $line) { + drush_print($line, 2); + } + } + + // Exit code 0 means success. + return ($result == 0); + } + } + else { + return TRUE; + } +} + +/** + * Determine whether 'which $command' can find + * a command on this system. + */ +function drush_which($command) { + exec("which $command 2>&1", $output, $result); + return ($result == 0); +} + +/** + * Build an SSH string including an optional fragment of bash. Commands that use + * this should also merge drush_shell_proc_build_options() into their + * command options. @see ssh_drush_command(). + * + * @param array $site + * A site alias record. + * @param string $command + * An optional bash fragment. + * @param string $cd + * An optional directory to change into before executing the $command. Set to + * boolean TRUE to change into $site['root'] if available. + * @param boolean $interactive + * Force creation of a tty + * @return string + * A string suitable for execution with drush_shell_remote_exec(). + */ +function drush_shell_proc_build($site, $command = '', $cd = NULL, $interactive = FALSE) { + // Build up the command. TODO: We maybe refactor this soon. + $hostname = drush_remote_host($site); + $ssh_options = drush_sitealias_get_option($site, 'ssh-options', "-o PasswordAuthentication=no"); + $os = drush_os($site); + if (drush_sitealias_get_option($site, 'tty') || $interactive) { + $ssh_options .= ' -t'; + } + + $cmd = "ssh " . $ssh_options . " " . $hostname; + + if ($cd === TRUE) { + if (array_key_exists('root', $site)) { + $cd = $site['root']; + } + else { + $cd = FALSE; + } + } + if ($cd) { + $command = 'cd ' . drush_escapeshellarg($cd, $os) . ' && ' . $command; + } + + if (!empty($command)) { + if (!drush_get_option('escaped', FALSE)) { + $cmd .= " " . drush_escapeshellarg($command, $os); + } + else { + $cmd .= " $command"; + } + } + + return $cmd; +} + +/** + * Execute bash command using proc_open(). + * + * @returns + * Exit code from launched application + * 0 no error + * 1 general error + * 127 command not found + */ +function drush_shell_proc_open($cmd) { + if (drush_get_context('DRUSH_VERBOSE') || drush_get_context('DRUSH_SIMULATE')) { + drush_print("Calling proc_open($cmd);", 0, STDERR); + } + if (!drush_get_context('DRUSH_SIMULATE')) { + $process = proc_open($cmd, array(0 => STDIN, 1 => STDOUT, 2 => STDERR), $pipes); + $proc_status = proc_get_status($process); + $exit_code = proc_close($process); + return ($proc_status["running"] ? $exit_code : $proc_status["exitcode"] ); + } + return 0; +} + +/** + * Used by definition of ssh and other commands that call into drush_shell_proc_build() + * to declare their options. + */ +function drush_shell_exec_proc_build_options() { + return array( + 'ssh-options' => 'A string of extra options that will be passed to the ssh command (e.g. "-p 100")', + 'tty' => 'Create a tty (e.g. to run an interactive program).', + 'escaped' => 'Command string already escaped; do not add additional quoting.', + ); +} + +/** + * Determine the appropriate os value for the + * specified site record + * + * @returns + * NULL for 'same as local machine', 'Windows' or 'Linux'. + */ +function drush_os($site_record = NULL) { + // Default to $os = NULL, meaning 'same as local machine' + $os = NULL; + // If the site record has an 'os' element, use it + if (isset($site_record) && array_key_exists('os', $site_record)) { + $os = $site_record['os']; + } + // Otherwise, we will assume that all remote machines are Linux + // (or whatever value 'remote-os' is set to in drushrc.php). + elseif (isset($site_record) && array_key_exists('remote-host', $site_record) && !empty($site_record['remote-host'])) { + $os = drush_get_option('remote-os', 'Linux'); + } + + return $os; +} + +/** + * Determine the remote host (username@hostname.tld) for + * the specified site. + */ +function drush_remote_host($site, $prefix = '') { + $hostname = drush_escapeshellarg(drush_sitealias_get_option($site, 'remote-host', '', $prefix), "LOCAL"); + $username = drush_escapeshellarg(drush_sitealias_get_option($site, 'remote-user', '', $prefix), "LOCAL"); + return $username . (empty($username) ? '' : '@') . $hostname; +} + +/** + * Make an attempt to simply wrap the arg with the + * kind of quote characters it does not already contain. + * If it contains both kinds, then this function reverts to drush_escapeshellarg. + */ +function drush_wrap_with_quotes($arg) { + $has_double = strpos($arg, '"') !== FALSE; + $has_single = strpos($arg, "'") !== FALSE; + if ($has_double && $has_single) { + return drush_escapeshellarg($arg); + } + elseif ($has_double) { + return "'" . $arg . "'"; + } + else { + return '"' . $arg . '"'; + } +} + +/** + * Platform-dependent version of escapeshellarg(). + * Given the target platform, return an appropriately-escaped + * string. The target platform may be omitted for args that + * are /known/ to be for the local machine. + * Use raw to get an unquoted version of the escaped arg. + * Notice that you can't add quotes later until you know the platform. + */ + +/** + * Stores output for the most recent shell command. + * This should only be run from drush_shell_exec(). + * + * @param array|bool $output + * The output of the most recent shell command. + * If this is not set the stored value will be returned. + */ +function _drush_shell_exec_output_set($output = FALSE) { + static $stored_output; + if ($output === FALSE) return $stored_output; + $stored_output = $output; +} + +/** + * Returns the output of the most recent shell command as an array of lines. + */ +function drush_shell_exec_output() { + return _drush_shell_exec_output_set(); +} + +/** + * Starts a background browser/tab for the current site or a specified URL. + * + * Uses a non-blocking proc_open call, so Drush execution will continue. + * + * @param $uri + * Optional URI or site path to open in browser. If omitted, or if a site path + * is specified, the current site home page uri will be prepended if the sites + * hostname resolves. + * @return + * TRUE if browser was opened, FALSE if browser was disabled by the user or a, + * default browser could not be found. + */ +function drush_start_browser($uri = NULL, $sleep = FALSE, $port = FALSE) { + if ($browser = drush_get_option('browser', TRUE)) { + // We can only open a browser if we have a DISPLAY environment variable on + // POSIX or are running Windows or OS X. + if (!drush_get_context('DRUSH_SIMULATE') && !getenv('DISPLAY') && !drush_is_windows() && !drush_is_osx()) { + drush_log(dt('No graphical display appears to be available, not starting browser.'), LogLevel::NOTICE); + return FALSE; + } + $host = parse_url($uri, PHP_URL_HOST); + if (!$host) { + // Build a URI for the current site, if we were passed a path. + $site = drush_get_context('DRUSH_URI'); + $host = parse_url($site, PHP_URL_HOST); + $uri = $site . '/' . ltrim($uri, '/'); + } + // Validate that the host part of the URL resolves, so we don't attempt to + // open the browser for http://default or similar invalid hosts. + $hosterror = (gethostbynamel($host) === FALSE); + $iperror = (ip2long($host) && gethostbyaddr($host) == $host); + if (!drush_get_context('DRUSH_SIMULATE') && ($hosterror || $iperror)) { + drush_log(dt('!host does not appear to be a resolvable hostname or IP, not starting browser. You may need to use the --uri option in your command or site alias to indicate the correct URL of this site.', array('!host' => $host)), LogLevel::WARNING); + return FALSE; + } + if ($port) { + $uri = str_replace($host, "localhost:$port", $uri); + } + if ($browser === TRUE) { + // See if we can find an OS helper to open URLs in default browser. + if (drush_shell_exec('which xdg-open')) { + $browser = 'xdg-open'; + } + else if (drush_shell_exec('which open')) { + $browser = 'open'; + } + else if (!drush_has_bash()) { + $browser = 'start'; + } + else { + // Can't find a valid browser. + $browser = FALSE; + } + } + $prefix = ''; + if ($sleep) { + $prefix = 'sleep ' . $sleep . ' && '; + } + if ($browser) { + drush_log(dt('Opening browser !browser at !uri', array('!browser' => $browser, '!uri' => $uri))); + if (!drush_get_context('DRUSH_SIMULATE')) { + $pipes = array(); + proc_close(proc_open($prefix . $browser . ' ' . drush_escapeshellarg($uri) . ' 2> ' . drush_bit_bucket() . ' &', array(), $pipes)); + } + return TRUE; + } + } + return FALSE; +} + +/** + * @} End of "defgroup commandwrappers". + */ diff --git a/vendor/drush/drush/includes/filesystem.inc b/vendor/drush/drush/includes/filesystem.inc new file mode 100644 index 0000000000..0d0f49935a --- /dev/null +++ b/vendor/drush/drush/includes/filesystem.inc @@ -0,0 +1,748 @@ +filename), $sum); + $hashes[] = trim(str_replace(array($dir), array(''), $sum[0])); + } + sort($hashes); + return md5(implode("\n", $hashes)); +} + +/** + * Deletes the specified file or directory and everything inside it. + * + * Usually respects read-only files and folders. To do a forced delete use + * drush_delete_tmp_dir() or set the parameter $forced. + * + * @param string $dir + * The file or directory to delete. + * @param bool $force + * Whether or not to try everything possible to delete the directory, even if + * it's read-only. Defaults to FALSE. + * @param bool $follow_symlinks + * Whether or not to delete symlinked files. Defaults to FALSE--simply + * unlinking symbolic links. + * + * @return bool + * FALSE on failure, TRUE if everything was deleted. + */ +function drush_delete_dir($dir, $force = FALSE, $follow_symlinks = FALSE) { + // Do not delete symlinked files, only unlink symbolic links + if (is_link($dir) && !$follow_symlinks) { + return unlink($dir); + } + // Allow to delete symlinks even if the target doesn't exist. + if (!is_link($dir) && !file_exists($dir)) { + return TRUE; + } + if (!is_dir($dir)) { + if ($force) { + // Force deletion of items with readonly flag. + @chmod($dir, 0777); + } + return unlink($dir); + } + if (drush_delete_dir_contents($dir, $force) === FALSE) { + return FALSE; + } + if ($force) { + // Force deletion of items with readonly flag. + @chmod($dir, 0777); + } + return rmdir($dir); +} + +/** + * Deletes the contents of a directory. + * + * @param string $dir + * The directory to delete. + * @param bool $force + * Whether or not to try everything possible to delete the contents, even if + * they're read-only. Defaults to FALSE. + * + * @return bool + * FALSE on failure, TRUE if everything was deleted. + */ +function drush_delete_dir_contents($dir, $force = FALSE) { + $scandir = @scandir($dir); + if (!is_array($scandir)) { + return FALSE; + } + + foreach ($scandir as $item) { + if ($item == '.' || $item == '..') { + continue; + } + if ($force) { + @chmod($dir, 0777); + } + if (!drush_delete_dir($dir . '/' . $item, $force)) { + return FALSE; + } + } + return TRUE; +} + +/** + * Deletes the provided file or folder and everything inside it. + * This function explicitely tries to delete read-only files / folders. + * + * @param $dir + * The directory to delete + * @return + * FALSE on failure, TRUE if everything was deleted + */ +function drush_delete_tmp_dir($dir) { + return drush_delete_dir($dir, TRUE); +} + +/** + * Copy $src to $dest. + * + * @param $src + * The directory to copy. + * @param $dest + * The destination to copy the source to, including the new name of + * the directory. To copy directory "a" from "/b" to "/c", then + * $src = "/b/a" and $dest = "/c/a". To copy "a" to "/c" and rename + * it to "d", then $dest = "/c/d". + * @param $overwrite + * Action to take if destination already exists. + * - FILE_EXISTS_OVERWRITE - completely removes existing directory. + * - FILE_EXISTS_ABORT - aborts the operation. + * - FILE_EXISTS_MERGE - Leaves existing files and directories in place. + * @return + * TRUE on success, FALSE on failure. + */ +function drush_copy_dir($src, $dest, $overwrite = FILE_EXISTS_ABORT) { + // Preflight based on $overwrite if $dest exists. + if (file_exists($dest)) { + if ($overwrite === FILE_EXISTS_OVERWRITE) { + drush_op('drush_delete_dir', $dest, TRUE); + } + elseif ($overwrite === FILE_EXISTS_ABORT) { + return drush_set_error('DRUSH_DESTINATION_EXISTS', dt('Destination directory !dest already exists.', array('!dest' => $dest))); + } + elseif ($overwrite === FILE_EXISTS_MERGE) { + // $overwrite flag may indicate we should merge instead. + drush_log(dt('Merging existing !dest directory', array('!dest' => $dest))); + } + } + // $src readable? + if (!is_readable($src)) { + return drush_set_error('DRUSH_SOURCE_NOT_EXISTS', dt('Source directory !src is not readable or does not exist.', array('!src' => $src))); + } + // $dest writable? + if (!is_writable(dirname($dest))) { + return drush_set_error('DRUSH_DESTINATION_NOT_WRITABLE', dt('Destination directory !dest is not writable.', array('!dest' => dirname($dest)))); + } + // Try to do a recursive copy. + if (@drush_op('_drush_recursive_copy', $src, $dest)) { + return TRUE; + } + + return drush_set_error('DRUSH_COPY_DIR_FAILURE', dt('Unable to copy !src to !dest.', array('!src' => $src, '!dest' => $dest))); +} + +/** + * Internal function called by drush_copy_dir; do not use directly. + */ +function _drush_recursive_copy($src, $dest) { + // all subdirectories and contents: + if(is_dir($src)) { + if (!drush_mkdir($dest, TRUE)) { + return FALSE; + } + $dir_handle = opendir($src); + while($file = readdir($dir_handle)) { + if ($file != "." && $file != "..") { + if (_drush_recursive_copy("$src/$file", "$dest/$file") !== TRUE) { + return FALSE; + } + } + } + closedir($dir_handle); + } + elseif (is_link($src)) { + symlink(readlink($src), $dest); + } + elseif (!copy($src, $dest)) { + return FALSE; + } + + // Preserve file modification time. + // https://github.com/drush-ops/drush/pull/1146 + touch($dest, filemtime($src)); + + // Preserve execute permission. + if (!is_link($src) && !drush_is_windows()) { + // Get execute bits of $src. + $execperms = fileperms($src) & 0111; + // Apply execute permissions if any. + if ($execperms > 0) { + $perms = fileperms($dest) | $execperms; + chmod($dest, $perms); + } + } + + return TRUE; +} + +/** + * Move $src to $dest. + * + * If the php 'rename' function doesn't work, then we'll do copy & delete. + * + * @param $src + * The directory to move. + * @param $dest + * The destination to move the source to, including the new name of + * the directory. To move directory "a" from "/b" to "/c", then + * $src = "/b/a" and $dest = "/c/a". To move "a" to "/c" and rename + * it to "d", then $dest = "/c/d" (just like php rename function). + * @param $overwrite + * If TRUE, the destination will be deleted if it exists. + * @return + * TRUE on success, FALSE on failure. + */ +function drush_move_dir($src, $dest, $overwrite = FALSE) { + // Preflight based on $overwrite if $dest exists. + if (file_exists($dest)) { + if ($overwrite) { + drush_op('drush_delete_dir', $dest, TRUE); + } + else { + return drush_set_error('DRUSH_DESTINATION_EXISTS', dt('Destination directory !dest already exists.', array('!dest' => $dest))); + } + } + // $src readable? + if (!drush_op('is_readable', $src)) { + return drush_set_error('DRUSH_SOURCE_NOT_EXISTS', dt('Source directory !src is not readable or does not exist.', array('!src' => $src))); + } + // $dest writable? + if (!drush_op('is_writable', dirname($dest))) { + return drush_set_error('DRUSH_DESTINATION_NOT_WRITABLE', dt('Destination directory !dest is not writable.', array('!dest' => dirname($dest)))); + } + // Try rename. It will fail if $src and $dest are not in the same partition. + if (@drush_op('rename', $src, $dest)) { + return TRUE; + } + // Eventually it will create an empty file in $dest. See + // http://www.php.net/manual/es/function.rename.php#90025 + elseif (is_file($dest)) { + drush_op('unlink', $dest); + } + + // If 'rename' fails, then we will use copy followed + // by a delete of the source. + if (drush_copy_dir($src, $dest)) { + drush_op('drush_delete_dir', $src, TRUE); + return TRUE; + } + + return drush_set_error('DRUSH_MOVE_DIR_FAILURE', dt('Unable to move !src to !dest.', array('!src' => $src, '!dest' => $dest))); +} + +/** + * Cross-platform compatible helper function to recursively create a directory tree. + * + * @param path + * Path to directory to create. + * @param required + * If TRUE, then drush_mkdir will call drush_set_error on failure. + * + * Callers should *always* do their own error handling after calling drush_mkdir. + * If $required is FALSE, then a different location should be selected, and a final + * error message should be displayed if no usable locations can be found. + * @see drush_directory_cache(). + * If $required is TRUE, then the execution of the current command should be + * halted if the required directory cannot be created. + */ +function drush_mkdir($path, $required = TRUE) { + if (!is_dir($path)) { + if (drush_mkdir(dirname($path))) { + if (@mkdir($path)) { + return TRUE; + } + elseif (is_dir($path) && is_writable($path)) { + // The directory was created by a concurrent process. + return TRUE; + } + else { + if (!$required) { + return FALSE; + } + if (is_writable(dirname($path))) { + return drush_set_error('DRUSH_CREATE_DIR_FAILURE', dt('Unable to create !dir.', array('!dir' => preg_replace('/\w+\/\.\.\//', '', $path)))); + } + else { + return drush_set_error('DRUSH_PARENT_NOT_WRITABLE', dt('Unable to create !newdir in !dir. Please check directory permissions.', array('!newdir' => basename($path), '!dir' => realpath(dirname($path))))); + } + } + } + return FALSE; + } + else { + if (!is_writable($path)) { + if (!$required) { + return FALSE; + } + return drush_set_error('DRUSH_DESTINATION_NOT_WRITABLE', dt('Directory !dir exists, but is not writable. Please check directory permissions.', array('!dir' => realpath($path)))); + } + return TRUE; + } +} + +/* + * Determine if program exists on user's PATH. + * + * @return bool|null + */ +function drush_program_exists($program) { + if (drush_has_bash()) { + $bucket = drush_bit_bucket(); + return drush_op_system("command -v $program >$bucket 2>&1") === 0 ? TRUE : FALSE; + } +} + +/** + * Save a string to a temporary file. Does not depend on Drupal's API. + * The temporary file will be automatically deleted when drush exits. + * + * @param string $data + * @param string $suffix + * Append string to filename. use of this parameter if is discouraged. @see + * drush_tempnam(). + * @return string + * A path to the file. + */ +function drush_save_data_to_temp_file($data, $suffix = NULL) { + static $fp; + + $file = drush_tempnam('drush_', NULL, $suffix); + $fp = fopen($file, "w"); + fwrite($fp, $data); + $meta_data = stream_get_meta_data($fp); + $file = $meta_data['uri']; + fclose($fp); + + return $file; +} + +/** + * Returns the path to a temporary directory. + * + * This is a custom version of Drupal's file_directory_path(). + * We can't directly rely on sys_get_temp_dir() as this + * path is not valid in some setups for Mac, and we want to honor + * an environment variable (used by tests). + */ +function drush_find_tmp() { + static $temporary_directory; + + if (!isset($temporary_directory)) { + $directories = array(); + + // Get user specific and operating system temp folders from system environment variables. + // See http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/ntcmds_shelloverview.mspx?mfr=true + $tempdir = getenv('TEMP'); + if (!empty($tempdir)) { + $directories[] = $tempdir; + } + $tmpdir = getenv('TMP'); + if (!empty($tmpdir)) { + $directories[] = $tmpdir; + } + // Operating system specific dirs. + if (drush_is_windows()) { + $windir = getenv('WINDIR'); + if (isset($windir)) { + // WINDIR itself is not writable, but it always contains a /Temp dir, + // which is the system-wide temporary directory on older versions. Newer + // versions only allow system processes to use it. + $directories[] = Path::join($windir, 'Temp'); + } + } + else { + $directories[] = Path::canonicalize('/tmp'); + } + $directories[] = Path::canonicalize(sys_get_temp_dir()); + + foreach ($directories as $directory) { + if (is_dir($directory) && is_writable($directory)) { + $temporary_directory = $directory; + break; + } + } + + if (empty($temporary_directory)) { + // If no directory has been found, create one in cwd. + $temporary_directory = Path::join(drush_cwd(), 'tmp'); + drush_mkdir($temporary_directory, TRUE); + if (!is_dir($temporary_directory)) { + return drush_set_error('DRUSH_UNABLE_TO_CREATE_TMP_DIR', dt("Unable to create a temporary directory.")); + } + drush_register_file_for_deletion($temporary_directory); + } + } + + return $temporary_directory; +} + +/** + * Creates a temporary file, and registers it so that + * it will be deleted when drush exits. Whenever possible, + * drush_save_data_to_temp_file() should be used instead + * of this function. + * + * @param string $suffix + * Append this suffix to the filename. Use of this parameter is discouraged as + * it can break the guarantee of tempname(). See http://www.php.net/manual/en/function.tempnam.php#42052. + * Originally added to support Oracle driver. + */ +function drush_tempnam($pattern, $tmp_dir = NULL, $suffix = '') { + if ($tmp_dir == NULL) { + $tmp_dir = drush_find_tmp(); + } + $tmp_file = tempnam($tmp_dir, $pattern); + drush_register_file_for_deletion($tmp_file); + $tmp_file_with_suffix = $tmp_file . $suffix; + drush_register_file_for_deletion($tmp_file_with_suffix); + return $tmp_file_with_suffix; +} + +/** + * Creates a temporary directory and return its path. + */ +function drush_tempdir() { + $tmp_dir = drush_trim_path(drush_find_tmp()); + $tmp_dir .= '/' . 'drush_tmp_' . uniqid(time() . '_'); + + drush_mkdir($tmp_dir); + drush_register_file_for_deletion($tmp_dir); + + return $tmp_dir; +} + +/** + * Any file passed in to this function will be deleted + * when drush exits. + */ +function drush_register_file_for_deletion($file = NULL) { + static $registered_files = array(); + + if (isset($file)) { + if (empty($registered_files)) { + register_shutdown_function('_drush_delete_registered_files'); + } + $registered_files[] = $file; + } + + return $registered_files; +} + +/** + * Delete all of the registered temporary files. + */ +function _drush_delete_registered_files() { + $files_to_delete = drush_register_file_for_deletion(); + + foreach ($files_to_delete as $file) { + // We'll make sure that the file still exists, just + // in case someone came along and deleted it, even + // though they did not need to. + if (file_exists($file)) { + if (is_dir($file)) { + drush_delete_dir($file, TRUE); + } + else { + @chmod($file, 0777); // Make file writeable + unlink($file); + } + } + } +} + +/** + * Decide where our backup directory should go + * + * @param string $subdir + * The name of the desired subdirectory(s) under drush-backups. + * Usually a database name. + */ +function drush_preflight_backup_dir($subdir = NULL) { + $backup_dir = drush_get_context('DRUSH_BACKUP_DIR', drush_get_option('backup-location')); + + if (empty($backup_dir)) { + // Try to use db name as subdir if none was provided. + if (empty($subdir)) { + $subdir = 'unknown'; + if ($sql = drush_sql_get_class()) { + $db_spec = $sql->db_spec(); + $subdir = $db_spec['database']; + } + } + + // Save the date to be used in the backup directory's path name. + $date = gmdate('YmdHis', $_SERVER['REQUEST_TIME']); + + $backup_dir = drush_get_option('backup-dir', Path::join(drush_server_home(), 'drush-backups')); + $backup_dir = Path::join($backup_dir, $subdir, $date); + drush_set_context('DRUSH_BACKUP_DIR', $backup_dir); + } + else { + Path::canonicalize($backup_dir); + } + return $backup_dir; +} + +/** + * Prepare a backup directory + */ +function drush_prepare_backup_dir($subdir = NULL) { + $backup_dir = drush_preflight_backup_dir($subdir); + $backup_parent = Path::getDirectory($backup_dir); + $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT'); + + if ((!empty($drupal_root)) && (strpos($backup_parent, $drupal_root) === 0)) { + return drush_set_error('DRUSH_PM_BACKUP_FAILED', dt('It\'s not allowed to store backups inside the Drupal root directory.')); + } + if (!file_exists($backup_parent)) { + if (!drush_mkdir($backup_parent, TRUE)) { + return drush_set_error('DRUSH_PM_BACKUP_FAILED', dt('Unable to create backup directory !dir.', array('!dir' => $backup_parent))); + } + } + if (!is_writable($backup_parent)) { + return drush_set_error('DRUSH_PM_BACKUP_FAILED', dt('Backup directory !dir is not writable.', array('!dir' => $backup_parent))); + } + + if (!drush_mkdir($backup_dir, TRUE)) { + return FALSE; + } + return $backup_dir; +} + +/** + * Test to see if a file exists and is not empty + */ +function drush_file_not_empty($file_to_test) { + if (file_exists($file_to_test)) { + clearstatcache(); + $stat = stat($file_to_test); + if ($stat['size'] > 0) { + return TRUE; + } + } + return FALSE; +} + +/** + * Finds all files that match a given mask in a given directory. + * Directories and files beginning with a period are excluded; this + * prevents hidden files and directories (such as SVN working directories + * and GIT repositories) from being scanned. + * + * @param $dir + * The base directory for the scan, without trailing slash. + * @param $mask + * The regular expression of the files to find. + * @param $nomask + * An array of files/directories to ignore. + * @param $callback + * The callback function to call for each match. + * @param $recurse_max_depth + * When TRUE, the directory scan will recurse the entire tree + * starting at the provided directory. When FALSE, only files + * in the provided directory are returned. Integer values + * limit the depth of the traversal, with zero being treated + * identically to FALSE, and 1 limiting the traversal to the + * provided directory and its immediate children only, and so on. + * @param $key + * The key to be used for the returned array of files. Possible + * values are "filename", for the path starting with $dir, + * "basename", for the basename of the file, and "name" for the name + * of the file without an extension. + * @param $min_depth + * Minimum depth of directories to return files from. + * @param $include_dot_files + * If TRUE, files that begin with a '.' will be returned if they + * match the provided mask. If FALSE, files that begin with a '.' + * will not be returned, even if they match the provided mask. + * @param $depth + * Current depth of recursion. This parameter is only used internally and should not be passed. + * + * @return + * An associative array (keyed on the provided key) of objects with + * "path", "basename", and "name" members corresponding to the + * matching files. + */ +function drush_scan_directory($dir, $mask, $nomask = array('.', '..', 'CVS'), $callback = 0, $recurse_max_depth = TRUE, $key = 'filename', $min_depth = 0, $include_dot_files = FALSE, $depth = 0) { + $key = (in_array($key, array('filename', 'basename', 'name')) ? $key : 'filename'); + $files = array(); + + // Exclude Bower and Node directories. + $nomask = array_merge($nomask, drush_get_option_list('ignored-directories', array('node_modules', 'bower_components'))); + + if (is_string($dir) && is_dir($dir) && $handle = opendir($dir)) { + while (FALSE !== ($file = readdir($handle))) { + if (!in_array($file, $nomask) && (($include_dot_files && (!preg_match("/\.\+/",$file))) || ($file[0] != '.'))) { + if (is_dir("$dir/$file") && (($recurse_max_depth === TRUE) || ($depth < $recurse_max_depth))) { + // Give priority to files in this folder by merging them in after any subdirectory files. + $files = array_merge(drush_scan_directory("$dir/$file", $mask, $nomask, $callback, $recurse_max_depth, $key, $min_depth, $include_dot_files, $depth + 1), $files); + } + elseif ($depth >= $min_depth && preg_match($mask, $file)) { + // Always use this match over anything already set in $files with the same $$key. + $filename = "$dir/$file"; + $basename = basename($file); + $name = substr($basename, 0, strrpos($basename, '.')); + $files[$$key] = new stdClass(); + $files[$$key]->filename = $filename; + $files[$$key]->basename = $basename; + $files[$$key]->name = $name; + if ($callback) { + drush_op($callback, $filename); + } + } + } + } + + closedir($handle); + } + + return $files; +} + +/** + * Simple helper function to append data to a given file. + * + * @param string $file + * The full path to the file to append the data to. + * @param string $data + * The data to append. + * + * @return boolean + * TRUE on success, FALSE in case of failure to open or write to the file. + */ +function drush_file_append_data($file, $data) { + if (!$fd = fopen($file, 'a+')) { + drush_set_error(dt("ERROR: fopen(@file, 'ab') failed", array('@file' => $file))); + return FALSE; + } + if (!fwrite($fd, $data)) { + drush_set_error(dt("ERROR: fwrite(@file) failed", array('@file' => $file)) . '
' . $data);
+    return FALSE;
+  }
+  return TRUE;
+}
+
+/**
+ * Return 'TRUE' if one directory is located anywhere inside
+ * the other.
+ */
+function drush_is_nested_directory($base_dir, $test_is_nested) {
+  $common = Path::getLongestCommonBasePath([$test_is_nested, $base_dir]);
+  return $common == Path::canonicalize($base_dir);
+}
+
+/**
+ * @} End of "defgroup filesystemfunctions".
+ */
diff --git a/vendor/drush/drush/includes/output.inc b/vendor/drush/drush/includes/output.inc
new file mode 100644
index 0000000000..950953f121
--- /dev/null
+++ b/vendor/drush/drush/includes/output.inc
@@ -0,0 +1,824 @@
+ $metadata);
+  }
+  if (!is_array($metadata)) {
+    $metadata = array();
+  }
+  $metadata += array(
+    'metameta' => array(),
+  );
+  if (isset($metadata['format'])) {
+    // If the format is set in metadata, then it will
+    // override whatever is passed in via the $format parameter.
+    $format = $metadata['format'];
+  }
+  if (!isset($format)) {
+    // TODO: we shouldn't ever call drush_get_option here.
+    // Get rid of this once we confirm that there are no
+    // callers that still need it.
+    $format = drush_get_option('format', 'print-r');
+  }
+
+  $formatter = drush_load_engine('outputformat', $format);
+  if ($formatter) {
+    if ($formatter === TRUE) {
+      return drush_set_error(dt('No outputformat class defined for !format', array('!format' => $format)));
+    }
+    $output = $formatter->process($input, $metadata);
+  }
+
+  return $output;
+}
+
+/**
+ * Rudimentary replacement for Drupal API t() function.
+ *
+ * @param string
+ *   String to process, possibly with replacement item.
+ * @param array
+ *  An associative array of replacement items.
+ *
+ * @return
+ *   The processed string.
+ *
+ * @see t()
+ */
+function dt($string, $args = array()) {
+  $output = NULL;
+  if (function_exists('t') && drush_drupal_major_version() == 7) {
+    $output = t($string, $args);
+  }
+  // The language system requires a working container which has the string
+  // translation service.
+  else if (drush_drupal_major_version() >= 8 && \Drupal::hasService('string_translation')) {
+    // Drupal 8 removes !var replacements, creating a user-level error when
+    // these are used, so we'll pre-replace these before calling translate().
+    list($string, $args) = replace_legacy_dt_args($string, $args);
+    $output = (string) \Drupal::translation()->translate($string, $args);
+  }
+  else if (function_exists('t') && drush_drupal_major_version() <= 7 && function_exists('theme')) {
+    $output = t($string, $args);
+  }
+
+  // If Drupal's t() function unavailable.
+  if (!isset($output)) {
+    if (!empty($args)) {
+      $output = strtr($string, $args);
+    }
+    else {
+      $output = $string;
+    }
+  }
+  return $output;
+}
+
+/**
+ * Replace placeholders that begin with a '!' with '@'.
+ */
+function replace_legacy_dt_args(&$string, &$legacy_args) {
+  $args = array();
+  $replace = array();
+  foreach ($legacy_args as $name => $argument) {
+    if ($name[0] == '!') {
+      $new_arg = '@' . substr($name, 1);
+      $replace[$name] = $new_arg;
+      $args[$new_arg] = Markup::create($argument);
+    }
+    else {
+      $args[$name] = $argument;
+    }
+  }
+  return [
+    strtr($string, $replace),
+    $args
+  ];
+}
+
+/**
+ * Convert html to readable text.  Compatible API to
+ * drupal_html_to_text, but less functional.  Caller
+ * might prefer to call drupal_html_to_text if there
+ * is a bootstrapped Drupal site available.
+ *
+ * @param string $html
+ *   The html text to convert.
+ *
+ * @return string
+ *   The plain-text representation of the input.
+ */
+function drush_html_to_text($html, $allowed_tags = NULL) {
+  $replacements = array(
+    '
' => '------------------------------------------------------------------------------', + '
  • ' => ' * ', + '

    ' => '===== ', + '

    ' => ' =====', + '

    ' => '---- ', + '

    ' => ' ----', + '

    ' => '::: ', + '

    ' => ' :::', + '
    ' => "\n", + ); + $text = str_replace(array_keys($replacements), array_values($replacements), $html); + return html_entity_decode(preg_replace('/ *<[^>]*> */', ' ', $text)); +} + + +/** + * Print a formatted table. + * + * @param $rows + * The rows to print. + * @param $header + * If TRUE, the first line will be treated as table header. + * @param $widths + * An associative array whose keys are column IDs and values are widths of each column (in characters). + * If not specified this will be determined automatically, based on a "best fit" algorithm. + * @param $handle + * File handle to write to. NULL will write + * to standard output, STDERR will write to the standard + * error. See http://php.net/manual/en/features.commandline.io-streams.php + * @return $tbl + * Use $tbl->getTable() to get the output from the return value. + */ +function drush_print_table($rows, $header = FALSE, $widths = array(), $handle = NULL) { + $tbl = _drush_format_table($rows, $header, $widths); + $output = $tbl->getTable(); + if (!stristr(PHP_OS, 'WIN')) { + $output = str_replace("\r\n", PHP_EOL, $output); + } + + drush_print(rtrim($output), 0, $handle); + return $tbl; +} + +/** + * Format a table of data. + * + * @param $rows + * The rows to print. + * @param $header + * If TRUE, the first line will be treated as table header. + * @param $widths + * An associative array whose keys are column IDs and values are widths of each column (in characters). + * If not specified this will be determined automatically, based on a "best fit" algorithm. + * @param array $console_table_options + * An array that is passed along when constructing a Console_Table instance. + * @return $output + * The formatted output. + */ +function drush_format_table($rows, $header = FALSE, $widths = array(), $console_table_options = array()) { + $tbl = _drush_format_table($rows, $header, $widths, $console_table_options); + $output = $tbl->getTable(); + if (!drush_is_windows()) { + $output = str_replace("\r\n", PHP_EOL, $output); + } + return $output; +} + +function _drush_format_table($rows, $header = FALSE, $widths = array(), $console_table_options = array()) { + // Add defaults. + $tbl = new ReflectionClass('Console_Table'); + $console_table_options += array(CONSOLE_TABLE_ALIGN_LEFT , ''); + $tbl = $tbl->newInstanceArgs($console_table_options); + + $auto_widths = drush_table_column_autowidth($rows, $widths); + + // Do wordwrap on all cells. + $newrows = array(); + foreach ($rows as $rowkey => $row) { + foreach ($row as $col_num => $cell) { + $newrows[$rowkey][$col_num] = wordwrap($cell, $auto_widths[$col_num], "\n", TRUE); + if (isset($widths[$col_num])) { + $newrows[$rowkey][$col_num] = str_pad($newrows[$rowkey][$col_num], $widths[$col_num]); + } + } + } + if ($header) { + $headers = array_shift($newrows); + $tbl->setHeaders($headers); + } + + $tbl->addData($newrows); + return $tbl; +} + +/** + * Convert an associative array of key : value pairs into + * a table suitable for processing by drush_print_table. + * + * @param $keyvalue_table + * An associative array of key : value pairs. + * @param $metadata + * 'key-value-item': If the value is an array, then + * the item key determines which item from the value + * will appear in the output. + * @return + * An array of arrays, where the keys from the input + * array are stored in the first column, and the values + * are stored in the third. A second colum is created + * specifically to hold the ':' separator. + */ +function drush_key_value_to_array_table($keyvalue_table, $metadata = array()) { + if (!is_array($keyvalue_table)) { + return drush_set_error('DRUSH_INVALID_FORMAT', dt("Data not compatible with selected key-value output format.")); + } + if (!is_array($metadata)) { + $metadata = array('key-value-item' => $metadata); + } + $item_key = array_key_exists('key-value-item', $metadata) ? $metadata['key-value-item'] : NULL; + $metadata += array( + 'format' => 'list', + 'separator' => ' ', + ); + $table = array(); + foreach ($keyvalue_table as $key => $value) { + if (isset($value)) { + if (is_array($value) && isset($item_key)) { + $value = $value[$item_key]; + } + // We should only have simple values here, but if + // we don't, use drush_format() to flatten as a fallback. + if (is_array($value)) { + $value = drush_format($value, $metadata, 'list'); + } + } + if (isset($metadata['include-field-labels']) && !$metadata['include-field-labels']) { + $table[] = array(isset($value) ? $value : ''); + } + elseif (isset($value)) { + $table[] = array($key, ' :', $value); + } + else { + $table[] = array($key . ':', '', ''); + } + } + return $table; +} + +/** + * Select the fields that should be used. + */ +function drush_select_fields($all_field_labels, $fields, $strict = TRUE) { + $field_labels = array(); + foreach ($fields as $field) { + if (array_key_exists($field, $all_field_labels)) { + $field_labels[$field] = $all_field_labels[$field]; + } + else { + // Allow the user to select fields via their human-readable names. + // This is less convenient than the field name (since the human-readable + // names may contain spaces, and must therefore be quoted), but these are + // the values that the user sees in the command output. n.b. the help + // text lists fields by their more convenient machine names. + $key = array_search(strtolower($field), array_map('strtolower', $all_field_labels)); + if ($key !== FALSE) { + $field_labels[$key] = $all_field_labels[$key]; + } + elseif (!$strict) { + $field_labels[$field] = $field; + } + } + } + return $field_labels; +} + +/** + * Select the fields from the input array that should be output. + * + * @param $input + * An associative array of key:value pairs to be output + * @param $fields + * An associative array that maps FROM a field in $input + * TO the corresponding field name in $output. + * @param $mapping + * An associative array that maps FROM a field in $fields + * TO the actual field in $input to use in the preceeding + * translation described above. + * @return + * The input array, re-ordered and re-keyed per $fields + */ +function drush_select_output_fields($input, $fields, $mapping = array(), $default_value = NULL) { + $result = array(); + if (empty($fields)) { + $result = $input; + } + else { + foreach ($fields as $key => $label) { + $value = drush_lookup_field_by_path($input, $key, $mapping, $default_value); + if (isset($value)) { + $result[$label] = $value; + } + } + } + return $result; +} + +/** + * Return a specific item inside an array, as identified + * by the provided path. + * + * @param $input: + * An array of items, potentially multiple layers deep. + * @param $path: + * A specifier of array keys, either in an array or separated by + * a '/', that list the elements of the array to access. This + * works much like a very simple version of xpath for arrays, with + * all items being treated identically (like elements). + * @param $mapping: + * (optional) An array whose keys may correspond to the $path parameter and + * whose values are the corresponding paths to be used in $input. + * + * Example 1: + * + * $input = array('#name' => 'site.dev', '#id' => '222'); + * $path = '#name'; + * result: 'site.dev'; + * + * Example 2: + * + * $input = array('ca' => array('sf' => array('mission'=>array('1700'=>'woodward')))); + * $path = 'ca/sf/mission/1701'; + * result: 'woodward' + * + * Example 3: + * + * $input = array('#name' => 'site.dev', '#id' => '222'); + * $path = 'name'; + * $mapping = array('name' => '#name'); + * result: 'site.dev'; + */ +function drush_lookup_field_by_path($input, $path, $mapping = array(), $default_value = NULL) { + $result = ''; + if (isset($mapping[$path])) { + $path = $mapping[$path]; + } + if (!is_array($path)) { + $parts = explode('/', $path); + } + if (!empty($parts)) { + $result = $input; + foreach ($parts as $key) { + if ((is_array($result)) && (isset($result[$key]))) { + $result = $result[$key]; + } + else { + return $default_value; + } + } + } + return $result; +} + +/** + * Given a table array (an associative array of associative arrays), + * return an array of all of the values with the specified field name. + */ +function drush_output_get_selected_field($input, $field_name, $default_value = '') { + $result = array(); + foreach ($input as $key => $data) { + if (is_array($data) && isset($data[$field_name])) { + $result[] = $data[$field_name]; + } + else { + $result[] = $default_value; + } + } + return $result; +} + +/** + * Hide any fields that are empty + */ +function drush_hide_empty_fields($input, $fields) { + $has_data = array(); + foreach ($input as $key => $data) { + foreach ($fields as $field => $label) { + if (isset($data[$field]) && !empty($data[$field])) { + $has_data[$field] = TRUE; + } + } + } + foreach ($fields as $field => $label) { + if (!isset($has_data[$field])) { + unset($fields[$field]); + } + } + return $fields; +} + +/** + * Convert an array of data rows, where each row contains an + * associative array of key : value pairs, into + * a table suitable for processing by drush_print_table. + * The provided $header determines the order that the items + * will appear in the output. Only data items listed in the + * header will be placed in the output. + * + * @param $rows_of_keyvalue_table + * array( + * 'row1' => array('col1' => 'data', 'col2' => 'data'), + * 'row2' => array('col1' => 'data', 'col2' => 'data'), + * ) + * @param $header + * array('col1' => 'Column 1 Label', 'col2' => 'Column 2 Label') + * @param $metadata + * (optional) An array of special options, all optional: + * - strip-tags: call the strip_tags function on the data + * before placing it in the table + * - concatenate-columns: an array of: + * - dest-col: array('src-col1', 'src-col2') + * Appends all of the src columns with whatever is + * in the destination column. Appended columns are + * separated by newlines. + * - transform-columns: an array of: + * - dest-col: array('from' => 'to'), + * Transforms any occurance of 'from' in 'dest-col' to 'to'. + * - format-cell: Drush output format name to use to format + * any cell that is an array. + * - process-cell: php function name to use to process + * any cell that is an array. + * - field-mappings: an array whose keys are some or all of the keys in + * $header and whose values are the corresponding keys to use when + * indexing the values of $rows_of_keyvalue_table. + * @return + * An array of arrays + */ +function drush_rows_of_key_value_to_array_table($rows_of_keyvalue_table, $header, $metadata) { + if (isset($metadata['hide-empty-fields'])) { + $header = drush_hide_empty_fields($rows_of_keyvalue_table, $header); + } + if (empty($header)) { + $first = (array)reset($rows_of_keyvalue_table); + $keys = array_keys($first); + $header = array_combine($keys, $keys); + } + $table = array(array_values($header)); + if (isset($rows_of_keyvalue_table) && is_array($rows_of_keyvalue_table) && !empty($rows_of_keyvalue_table)) { + foreach ($rows_of_keyvalue_table as $row_index => $row_data) { + $row_data = (array)$row_data; + $row = array(); + foreach ($header as $column_key => $column_label) { + $data = drush_lookup_field_by_path($row_data, $column_key, $metadata['field-mappings']); + if (array_key_exists('transform-columns', $metadata)) { + foreach ($metadata['transform-columns'] as $dest_col => $transformations) { + if ($dest_col == $column_key) { + $data = str_replace(array_keys($transformations), array_values($transformations), $data); + } + } + } + if (array_key_exists('concatenate-columns', $metadata)) { + foreach ($metadata['concatenate-columns'] as $dest_col => $src_cols) { + if ($dest_col == $column_key) { + $data = ''; + if (!is_array($src_cols)) { + $src_cols = array($src_cols); + } + foreach($src_cols as $src) { + if (array_key_exists($src, $row_data) && !empty($row_data[$src])) { + if (!empty($data)) { + $data .= "\n"; + } + $data .= $row_data[$src]; + } + } + } + } + } + if (array_key_exists('format-cell', $metadata) && is_array($data)) { + $data = drush_format($data, array(), $metadata['format-cell']); + } + if (array_key_exists('process-cell', $metadata) && is_array($data)) { + $data = $metadata['process-cell']($data, $metadata); + } + if (array_key_exists('strip-tags', $metadata)) { + $data = strip_tags($data); + } + $row[] = $data; + } + $table[] = $row; + } + } + return $table; +} + +/** + * Determine the best fit for column widths. + * + * @param $rows + * The rows to use for calculations. + * @param $widths + * Manually specified widths of each column (in characters) - these will be + * left as is. + */ +function drush_table_column_autowidth($rows, $widths) { + $auto_widths = $widths; + + // First we determine the distribution of row lengths in each column. + // This is an array of descending character length keys (i.e. starting at + // the rightmost character column), with the value indicating the number + // of rows where that character column is present. + $col_dist = array(); + foreach ($rows as $rowkey => $row) { + foreach ($row as $col_id => $cell) { + if (empty($widths[$col_id])) { + $length = strlen($cell); + if ($length == 0) { + $col_dist[$col_id][0] = 0; + } + while ($length > 0) { + if (!isset($col_dist[$col_id][$length])) { + $col_dist[$col_id][$length] = 0; + } + $col_dist[$col_id][$length]++; + $length--; + } + } + } + } + foreach ($col_dist as $col_id => $count) { + // Sort the distribution in decending key order. + krsort($col_dist[$col_id]); + // Initially we set all columns to their "ideal" longest width + // - i.e. the width of their longest column. + $auto_widths[$col_id] = max(array_keys($col_dist[$col_id])); + } + + // We determine what width we have available to use, and what width the + // above "ideal" columns take up. + $available_width = drush_get_context('DRUSH_COLUMNS', 80) - (count($auto_widths) * 2); + $auto_width_current = array_sum($auto_widths); + + // If we need to reduce a column so that we can fit the space we use this + // loop to figure out which column will cause the "least wrapping", + // (relative to the other columns) and reduce the width of that column. + while ($auto_width_current > $available_width) { + $count = 0; + $width = 0; + foreach ($col_dist as $col_id => $counts) { + // If we are just starting out, select the first column. + if ($count == 0 || + // OR: if this column would cause less wrapping than the currently + // selected column, then select it. + (current($counts) < $count) || + // OR: if this column would cause the same amount of wrapping, but is + // longer, then we choose to wrap the longer column (proportionally + // less wrapping, and helps avoid triple line wraps). + (current($counts) == $count && key($counts) > $width)) { + // Select the column number, and record the count and current width + // for later comparisons. + $column = $col_id; + $count = current($counts); + $width = key($counts); + } + } + if ($width <= 1) { + // If we have reached a width of 1 then give up, so wordwrap can still progress. + break; + } + // Reduce the width of the selected column. + $auto_widths[$column]--; + // Reduce our overall table width counter. + $auto_width_current--; + // Remove the corresponding data from the disctribution, so next time + // around we use the data for the row to the left. + unset($col_dist[$column][$width]); + } + return $auto_widths; +} + +/** + * Print the contents of a file. + * + * @param string $file + * Full path to a file. + */ +function drush_print_file($file) { + // Don't even bother to print the file in --no mode + if (drush_get_context('DRUSH_NEGATIVE')) { + return; + } + if ((substr($file,-4) == ".htm") || (substr($file,-5) == ".html")) { + $tmp_file = drush_tempnam(basename($file)); + file_put_contents($tmp_file, drush_html_to_text(file_get_contents($file))); + $file = $tmp_file; + } + // Do not wait for user input in --yes or --pipe modes + if (drush_get_context('DRUSH_PIPE')) { + drush_print_pipe(file_get_contents($file)); + } + elseif (drush_get_context('DRUSH_AFFIRMATIVE')) { + drush_print(file_get_contents($file)); + } + elseif (drush_shell_exec_interactive("less %s", $file)) { + return; + } + elseif (drush_shell_exec_interactive("more %s", $file)) { + return; + } + else { + drush_print(file_get_contents($file)); + } +} + + +/** + * Converts a PHP variable into its Javascript equivalent. + * + * We provide a copy of D7's drupal_json_encode since this function is + * unavailable on earlier versions of Drupal. + * + * @see drupal_json_decode() + * @ingroup php_wrappers + */ +function drush_json_encode($var) { + if (version_compare(phpversion(), '5.4.0', '>=')) { + $json = json_encode($var, JSON_PRETTY_PRINT); + } + else { + $json = json_encode($var); + } + // json_encode() does not escape <, > and &, so we do it with str_replace(). + return str_replace(array('<', '>', '&'), array('\u003c', '\u003e', '\u0026'), $json); +} + +/** + * Converts an HTML-safe JSON string into its PHP equivalent. + * + * We provide a copy of D7's drupal_json_decode since this function is + * unavailable on earlier versions of Drupal. + * + * @see drupal_json_encode() + * @ingroup php_wrappers + */ +function drush_json_decode($var) { + return json_decode($var, TRUE); +} + +/** + * Drupal-friendly var_export(). Taken from utility.inc in Drupal 8. + * + * @param $var + * The variable to export. + * @param $prefix + * A prefix that will be added at the beginning of every lines of the output. + * + * @return + * The variable exported in a way compatible to Drupal's coding standards. + */ +function drush_var_export($var, $prefix = '') { + if (is_array($var)) { + if (empty($var)) { + $output = 'array()'; + } + else { + $output = "array(\n"; + // Don't export keys if the array is non associative. + $export_keys = array_values($var) != $var; + foreach ($var as $key => $value) { + $output .= ' ' . ($export_keys ? drush_var_export($key) . ' => ' : '') . drush_var_export($value, ' ', FALSE) . ",\n"; + } + $output .= ')'; + } + } + elseif (is_bool($var)) { + $output = $var ? 'TRUE' : 'FALSE'; + } + elseif (is_string($var)) { + $line_safe_var = str_replace("\n", '\n', $var); + if (strpos($var, "\n") !== FALSE || strpos($var, "'") !== FALSE) { + // If the string contains a line break or a single quote, use the + // double quote export mode. Encode backslash and double quotes and + // transform some common control characters. + $var = str_replace(array('\\', '"', "\n", "\r", "\t"), array('\\\\', '\"', '\n', '\r', '\t'), $var); + $output = '"' . $var . '"'; + } + else { + $output = "'" . $var . "'"; + } + } + elseif (is_object($var) && get_class($var) === 'stdClass') { + // var_export() will export stdClass objects using an undefined + // magic method __set_state() leaving the export broken. This + // workaround avoids this by casting the object as an array for + // export and casting it back to an object when evaluated. + $output = '(object) ' . drush_var_export((array) $var, $prefix); + } + else { + $output = var_export($var, TRUE); + } + + if ($prefix) { + $output = str_replace("\n", "\n$prefix", $output); + } + + return $output; +} + +/** + * @} End of "defgroup outputfunctions". + */ diff --git a/vendor/drush/drush/includes/preflight.inc b/vendor/drush/drush/includes/preflight.inc new file mode 100644 index 0000000000..6d31b7c3dd --- /dev/null +++ b/vendor/drush/drush/includes/preflight.inc @@ -0,0 +1,884 @@ +bootstrap_and_dispatch(); + } + } + } + // TODO: Get rid of global variable access here, and just trust + // the bootstrap object returned from drush_preflight(). This will + // require some adjustments to Drush bootstrapping. + // See: https://github.com/drush-ops/drush/pull/1303 + if ($bootstrap = drush_get_bootstrap_object()) { + $bootstrap->terminate(); + } + drush_postflight(); + if (is_object($return)) { + $return = 0; + } + + // How strict are we? If we are very strict, turn 'ok' into 'error' + // if there are any warnings in the log. + if (($return == 0) && (drush_get_option('strict') > 1) && drush_log_has_errors()) { + $return = 1; + } + + // After this point the drush_shutdown function will run, + // exiting with the correct exit code. + return $return; +} + +/** + * Prepare Drush for preflight. + * + * Runs before drush_main(). + * + * @see drush_main() + * @see drush.php + */ +function drush_preflight_prepare() { + if (!defined('DRUSH_BASE_PATH')) { + define('DRUSH_BASE_PATH', dirname(dirname(__FILE__))); + } + // Local means that autoload.php is inside of Drush. That is, Drush is its own Composer project. + // Global means autoload.php is outside of Drush. That is, Drush is a dependency of a bigger project. + $local_vendor_path = DRUSH_BASE_PATH . '/vendor/autoload.php'; + $global_vendor_path = DRUSH_BASE_PATH . '/../../../vendor/autoload.php'; + + // Check for a local composer install or a global composer install. Vendor dirs are in different spots). + if (file_exists($local_vendor_path)) { + $vendor_path = $local_vendor_path; + } + elseif (file_exists($global_vendor_path)) { + $vendor_path = $global_vendor_path; + } + else { + $msg = "Unable to load autoload.php. Run composer install to fetch dependencies and write this file (http://docs.drush.org/en/master/install-alternative/). Or if you prefer, use the drush.phar which already has dependencies included (http://docs.drush.org/en/master/install).\n"; + fwrite(STDERR, $msg); + return FALSE; + } + + $classloader = require $vendor_path; + + require_once DRUSH_BASE_PATH . '/includes/startup.inc'; + require_once DRUSH_BASE_PATH . '/includes/bootstrap.inc'; + require_once DRUSH_BASE_PATH . '/includes/environment.inc'; + require_once DRUSH_BASE_PATH . '/includes/annotationcommand_adapter.inc'; + require_once DRUSH_BASE_PATH . '/includes/command.inc'; + require_once DRUSH_BASE_PATH . '/includes/drush.inc'; + require_once DRUSH_BASE_PATH . '/includes/engines.inc'; + require_once DRUSH_BASE_PATH . '/includes/backend.inc'; + require_once DRUSH_BASE_PATH . '/includes/batch.inc'; + require_once DRUSH_BASE_PATH . '/includes/context.inc'; + require_once DRUSH_BASE_PATH . '/includes/sitealias.inc'; + require_once DRUSH_BASE_PATH . '/includes/exec.inc'; + require_once DRUSH_BASE_PATH . '/includes/drupal.inc'; + require_once DRUSH_BASE_PATH . '/includes/output.inc'; + require_once DRUSH_BASE_PATH . '/includes/cache.inc'; + require_once DRUSH_BASE_PATH . '/includes/filesystem.inc'; + require_once DRUSH_BASE_PATH . '/includes/dbtng.inc'; + require_once DRUSH_BASE_PATH . '/includes/array_column.inc'; + + // Stash our vendor path and classloader. + drush_set_context('DRUSH_VENDOR_PATH', dirname($vendor_path)); + drush_set_context('DRUSH_CLASSLOADER', $classloader); + + // Make a read-only copy of the vendor path at drush.vendor-dir + // for forwards-compatibility + drush_set_option('drush.vendor-dir', dirname($vendor_path)); + + // Can't log until we have a logger, so we'll create this ASAP. + _drush_create_default_logger(); + + // Terminate immediately unless invoked as a command line script + if (!drush_verify_cli()) { + return drush_set_error('DRUSH_REQUIREMENTS_ERROR', dt('Drush is designed to run via the command line.')); + } + + // Check supported version of PHP. + // Note: If this is adjusted, check other code that compares + // PHP_VERSION, such as drush_json_encode(), runserver/runserver.drush.inc, and also + // adjust _drush_environment_check_php_ini() and the php_prohibited_options + // list in the drush script. See http://drupal.org/node/1748228 + if (!defined('DRUSH_MINIMUM_PHP')) { + define('DRUSH_MINIMUM_PHP', '5.4.5'); + } + if (version_compare(phpversion(), DRUSH_MINIMUM_PHP) < 0 && !getenv('DRUSH_NO_MIN_PHP')) { + return drush_set_error('DRUSH_REQUIREMENTS_ERROR', dt('Your command line PHP installation is too old. Drush requires at least PHP !version. To suppress this check, set the environment variable DRUSH_NO_MIN_PHP=1', array('!version' => DRUSH_MINIMUM_PHP))); + } + + if (!$return = _drush_environment_check_php_ini()) { + return; // An error was logged. + } + + // For backwards compatibility. Prefer the static accessors. + if (!defined('DRUSH_VERSION')) { + define('DRUSH_VERSION', Drush::getVersion()); + define('DRUSH_MAJOR_VERSION', Drush::getMajorVersion()); + define('DRUSH_MINOR_VERSION', Drush::getMinorVersion()); + + define('DRUSH_REQUEST_TIME', microtime(TRUE)); + } + + drush_set_context('argc', $GLOBALS['argc']); + drush_set_context('argv', $GLOBALS['argv']); + + // Make a read-only copy of the arguments at runtime.argv + // for forwards-compatibility + drush_set_option('runtime.argv', $GLOBALS['argv']); + + // Set an error handler and a shutdown function + set_error_handler('drush_error_handler'); + register_shutdown_function('drush_shutdown'); + // We need some global options/arguments processed at this early stage. + drush_parse_args(); + + // Process initial global options such as --debug. + _drush_preflight_global_options(); + + drush_log(dt("Drush preflight prepare loaded autoloader at !autoloader", array('!autoloader' => realpath($vendor_path))), LogLevel::PREFLIGHT); +} + +/** + * During the initialization of Drush, this is the first + * step where we load our configuration and commandfiles, + * and select the site we are going to operate on; however, + * we take no irreversible actions (e.g. site bootstrapping). + * This allows commands that are declared with no bootstrap + * to select a new site root and bootstrap it. + * + * In this step we will register the shutdown function, + * parse the command line arguments and store them in their + * related contexts. + * + * Configuration files (drushrc.php) that are + * a) Specified on the command line + * b) Stored in the root directory of drush.php + * c) Stored in the home directory of the system user. + * + * Additionally the DRUSH_QUIET and DRUSH_BACKEND contexts, + * will be evaluated now, as they need to be set very early in + * the execution flow to be able to take affect. + * + * @return \Drush\Boot\Boot; + */ +function drush_preflight() { + // Create an alias '@none' to represent no Drupal site + _drush_sitealias_cache_alias('@none', array('root' => '', 'uri' => '')); + + // Discover terminal width for pretty output. + _drush_preflight_columns(); + + // Display is tidy now that column width has been handled. + drush_log(dt('Starting Drush preflight.'), LogLevel::PREFLIGHT); + + // Statically define a way to call drush again. + if (!defined('DRUSH_COMMAND')) { + define('DRUSH_COMMAND', drush_find_drush()); + } + + // prime the CWD cache + drush_cwd(); + + // Set up base environment for system-wide file locations. + _drush_preflight_base_environment(); + + // Setup global alias_paths[] in context system. + if (!drush_get_option('local')) { + _drush_preflight_alias_path(); + } + if (!drush_get_option('local')) { + // Load a drushrc.php file in the drush.php's directory. + drush_load_config('drush'); + + // Load a drushrc.php file in the $ETC_PREFIX/etc/drush directory. + drush_load_config('system'); + + // Load a drushrc.php file at ~/.drushrc.php. + drush_load_config('user'); + + // Load a drushrc.php file in the ~/.drush directory. + drush_load_config('home.drush'); + } + + // Load a custom config specified with the --config option. + drush_load_config('custom'); + + _drush_preflight_global_options(); + // Load all the commandfiles findable from any of the + // scopes listed above. + _drush_find_commandfiles_drush(); + + // Look up the alias identifier that the user wants to use, + // either via an argument or via 'site-set'. + $target_alias = drush_sitealias_check_arg_and_site_set(); + + // Process the site alias that specifies which instance + // of Drush (local or remote) this command will operate on. + // We must do this after we load our config files (so that + // site aliases are available), but before the rest of + // Drush preflight and Drupal root bootstrap phase are + // done, since site aliases may set option values that + // affect these phases. + $alias_record = _drush_sitealias_set_context_by_name($target_alias); + + // Find the selected site based on --root, --uri or cwd + drush_preflight_root(); + + // Preflight the selected site, and load any configuration and commandfiles associated with it. + drush_preflight_site(); + + // Check to see if anything changed during the 'site' preflight + // that might allow us to find our alias record now + if (empty($alias_record)) { + $alias_record = _drush_sitealias_set_context_by_name($target_alias); + + // If the site alias settings changed late in the preflight, + // then run the preflight for the root and site contexts again. + if (!empty($alias_record)) { + $remote_host = drush_get_option('remote-host'); + if (!isset($remote_host)) { + drush_preflight_root(); + drush_preflight_site(); + } + } + } + + // Fail if we could not find the selected site alias. + if ($target_alias && empty($alias_record)) { + // We will automatically un-set the site-set alias if it could not be found. + // Otherwise, we'd be stuck -- the user would only be able to execute Drush + // commands again after `drush @none site-set @none`, and most folks would + // have a hard time figuring that out. + $site_env = drush_sitealias_site_get(); + if ($site_env == $target_alias) { + drush_sitealias_site_clear(); + } + return drush_set_error('DRUSH_BOOTSTRAP_NO_ALIAS', dt("Could not find the alias !alias", array('!alias' => $target_alias))); + } + + // If applicable swaps in shell alias values. + drush_shell_alias_replace($target_alias); + + // Copy global options to their respective contexts + _drush_preflight_global_options(); + + // Set environment variables based on #env-vars. + drush_set_environment_vars($alias_record); + + // Select the bootstrap object and return it. + return drush_select_bootstrap_class(); +} + +/** + * If --root is provided, set context. + */ +function drush_preflight_root() { + $root = drush_get_option('root'); + if (!isset($root)) { + $root = drush_locate_root(); + } + // If the old ways do not find a Drupal root, use DrupalFinder. + if (!$root) { + $finder = new \DrupalFinder\DrupalFinder(); + if ($finder->locateRoot(getcwd())) { + $root = $finder->getDrupalRoot(); + } + } + if ($root) { + $root = realpath($root); + } + // @todo This context name should not mention Drupal. + // @todo Drupal code should use DRUSH_DRUPAL_ROOT instead of this constant. + drush_set_context('DRUSH_SELECTED_DRUPAL_ROOT', $root); + + // Load the config options from Drupal's /drush, ../drush, and sites/all/drush directories, + // even prior to bootstrapping the root. + drush_load_config('drupal'); + + // Search for commandfiles in the root locations + $discovery = annotationcommand_adapter_get_discovery(); + $searchpath = [dirname($root) . '/drush', "$root/drush", "$root/sites/all/drush"]; + + $drush_root_extensions = $discovery->discover($searchpath, '\Drush'); + drush_set_context( + 'DRUSH_ANNOTATED_COMMANDFILES', + array_merge( + drush_get_context('DRUSH_ANNOTATED_COMMANDFILES'), + $drush_root_extensions + ) + ); +} + +function drush_preflight_site() { + // Load the Drupal site configuration options upfront. + drush_load_config('site'); + + // Determine URI and set constants/contexts accordingly. Keep this after loading of drupal,site configs. + _drush_preflight_uri(); + + // If someone set 'uri' in the 'site' context, then copy it + // to the 'process' context (to give it a higher priority + // than the 'cli' and 'alias' contexts) and reset our selected + // site and @self alias. + $uri = drush_get_option('uri'); + if ($uri != drush_get_option('uri', $uri, 'site')) { + drush_set_option('uri', drush_get_option('uri', $uri, 'site')); + _drush_preflight_uri(); + } + + // Create a @self site alias record. + drush_sitealias_create_self_alias(); +} + +function _drush_preflight_global_options() { + // Debug implies verbose + $verbose = drush_get_option('verbose', FALSE); + $debug = drush_get_option('debug', FALSE); + drush_set_context('DRUSH_VERBOSE', $verbose || $debug); + drush_set_context('DRUSH_DEBUG', $debug); + drush_set_context('DRUSH_DEBUG_NOTIFY', $verbose && $debug); + drush_set_context('DRUSH_SIMULATE', drush_get_option('simulate', FALSE)); + + // Backend implies affirmative unless negative is explicitly specified + drush_set_context('DRUSH_NEGATIVE', drush_get_option('no', FALSE)); + drush_set_context('DRUSH_AFFIRMATIVE', drush_get_option(array('yes', 'pipe', 'no-interaction'), FALSE) || (drush_get_context('DRUSH_BACKEND') && !drush_get_context('DRUSH_NEGATIVE'))); + + // Pipe implies quiet. + drush_set_context('DRUSH_QUIET', drush_get_option(array('quiet', 'pipe'))); + drush_set_context('DRUSH_PIPE', drush_get_option('pipe')); + + // Suppress colored logging if --nocolor option is explicitly given or if + // terminal does not support it. + $nocolor = (drush_get_option('nocolor', FALSE)); + if (!$nocolor) { + // Check for colorless terminal. If there is no terminal, then + // 'tput colors 2>&1' will return "tput: No value for $TERM and no -T specified", + // which is not numeric and therefore will put us in no-color mode. + $colors = exec('tput colors 2>&1'); + $nocolor = !($colors === FALSE || (is_numeric($colors) && $colors >= 3)); + } + drush_set_context('DRUSH_NOCOLOR', $nocolor); +} + +/** + * Sets up basic environment that controls where Drush looks for files on a + * system-wide basis. Important to call for "early" functions that need to + * work with unit tests. + */ +function _drush_preflight_base_environment() { + // Copy ETC_PREFIX and SHARE_PREFIX from environment variables if available. + // This alters where we check for server-wide config and alias files. + // Used by unit test suite to provide a clean environment. + if (getenv('ETC_PREFIX')) drush_set_context('ETC_PREFIX', getenv('ETC_PREFIX')); + if (getenv('SHARE_PREFIX')) drush_set_context('SHARE_PREFIX', getenv('SHARE_PREFIX')); + + drush_set_context('DOC_PREFIX', DRUSH_BASE_PATH); + if (!file_exists(DRUSH_BASE_PATH . '/README.md') && file_exists(drush_get_context('SHARE_PREFIX', '/usr') . '/share/doc/drush' . '/README.md')) { + drush_set_context('DOC_PREFIX', drush_get_context('SHARE_PREFIX', '/usr') . '/share/doc/drush'); + } + + $default_prefix_configuration = drush_is_windows() ? getenv('ALLUSERSPROFILE') . '/Drush' : ''; + $default_prefix_commandfile = drush_is_windows() ? getenv('ALLUSERSPROFILE') . '/Drush' : '/usr'; + $site_wide_configuration_dir = drush_get_context('ETC_PREFIX', $default_prefix_configuration) . '/etc/drush'; + $site_wide_commandfile_dir = drush_get_context('SHARE_PREFIX', $default_prefix_commandfile) . '/share/drush/commands'; + drush_set_context('DRUSH_SITE_WIDE_CONFIGURATION', $site_wide_configuration_dir); + drush_set_context('DRUSH_SITE_WIDE_COMMANDFILES', $site_wide_commandfile_dir); + + $server_home = drush_server_home(); + if (isset($server_home)) { + drush_set_context('DRUSH_PER_USER_CONFIGURATION', $server_home . '/.drush'); + } +} + +/* + * Set the terminal width, used for wrapping table output. + * Normally this is exported using tput in the drush script. + * If this is not present we do an additional check using stty here. + * On Windows in CMD and PowerShell is this exported using mode con. + */ +function _drush_preflight_columns() { + if (!($columns = getenv('COLUMNS') ?: 0)) { + // Trying to export the columns using stty. + exec('stty size 2>&1', $columns_output, $columns_status); + $matched = FALSE; + if (!$columns_status && $matched = preg_match('/^\d+\s(\d+)$/', $columns_output[0], $matches, 0)) { + $columns = $matches[1]; + } + // If stty fails and Drush us running on Windows are we trying with mode con. + if (($columns_status || !$matched) && drush_is_windows()) { + $columns_output = array(); + exec('mode con', $columns_output, $columns_status); + if (!$columns_status && is_array($columns_output)) { + $columns = (int)preg_replace('/\D/', '', $columns_output[4], -1, $columns_count); + } + else { + drush_log(dt('Drush could not detect the console window width. Set a Windows Environment Variable of COLUMNS to the desired width.'), LogLevel::WARNING); + } + } + + // Failling back to default columns value + if (empty($columns)) { + $columns = 80; + } + } + // If a caller wants to reserve some room to add additional + // information to the drush output via post-processing, the + // --reserve-margin flag can be used to declare how much + // space to leave out. This only affects drush functions + // such as drush_print_table() that wrap the output. + $columns -= drush_get_option('reserve-margin', 0); + drush_set_context('DRUSH_COLUMNS', $columns); +} + +function _drush_preflight_alias_path() { + $alias_path =& drush_get_context('ALIAS_PATH'); + $default_prefix_configuration = drush_is_windows() ? getenv('ALLUSERSPROFILE') . '/Drush' : ''; + $site_wide_configuration_dir = drush_get_context('ETC_PREFIX', $default_prefix_configuration) . '/etc/drush'; + $alias_path[] = drush_sitealias_alias_base_directory($site_wide_configuration_dir); + + $alias_path[] = drush_sitealias_alias_base_directory(dirname(__FILE__) . '/..'); + + $server_home = drush_server_home(); + if (isset($server_home)) { + $alias_path[] = drush_sitealias_alias_base_directory($server_home . '/.drush'); + } +} + +/* + * Set root and uri. + */ +function _drush_preflight_root_uri() { + drush_preflight_root(); + _drush_preflight_uri(); +} + +/** + * If --uri is provided, set context. + */ +function _drush_preflight_uri() { + $uri = drush_get_option('uri', ''); + if (empty($uri) && getenv('DRUSH_OPTIONS_URI')) { + $uri = getenv('DRUSH_OPTIONS_URI'); + } + drush_set_context('DRUSH_SELECTED_URI', $uri); +} + +function _drush_find_commandfiles_drush() { + // Core commands shipping with Drush + $searchpath[] = dirname(__FILE__) . '/../commands/'; + + // User commands, specified by 'include' option + $include = drush_get_context('DRUSH_INCLUDE', array()); + foreach ($include as $path) { + if (is_dir($path)) { + drush_log('Include ' . $path, LogLevel::NOTICE); + $searchpath[] = $path; + } + } + + if (!drush_get_option('local')) { + // System commands, residing in $SHARE_PREFIX/share/drush/commands + $share_path = drush_get_context('DRUSH_SITE_WIDE_COMMANDFILES'); + if (is_dir($share_path)) { + $searchpath[] = $share_path; + } + + // User commands, residing in ~/.drush + $per_user_config_dir = drush_get_context('DRUSH_PER_USER_CONFIGURATION'); + if (!empty($per_user_config_dir)) { + $searchpath[] = $per_user_config_dir; + } + } + + // @todo the zero parameter is a bit weird here. It's $phase. + _drush_add_commandfiles($searchpath, 0); + + // Also discover Drush's own annotation commands. + $discovery = annotationcommand_adapter_get_discovery(); + $annotation_commandfiles = $discovery->discover(DRUSH_BASE_PATH . '/lib/Drush', '\Drush'); + + // And, finally, search for commandfiles in the $searchpath + $searchpath = array_map( + function ($item) { + if (strtolower(basename($item)) == 'commands') { + return dirname($item); + } + return $item; + }, + $searchpath + ); + $global_drush_extensions = $discovery->discover($searchpath, '\Drush'); + $annotation_commandfiles += $global_drush_extensions; + + drush_set_context('DRUSH_ANNOTATED_COMMANDFILES', $annotation_commandfiles); +} + +/** + * Handle any command preprocessing that may need to be done, including + * potentially redispatching the command immediately (e.g. for remote + * commands). + * + * @return + * TRUE if the command was handled remotely. + */ +function drush_preflight_command_dispatch() { + $interactive = drush_get_option('interactive', FALSE); + + // The command will be executed remotely if the --remote-host flag + // is set; note that if a site alias is provided on the command line, + // and the site alias references a remote server, then the --remote-host + // option will be set when the site alias is processed. + // @see drush_sitealias_check_arg_and_site_set and _drush_sitealias_set_context_by_name + $remote_host = drush_get_option('remote-host'); + $site_list = drush_get_option('site-list'); + // Get the command early so that we can allow commands to directly handle remote aliases if they wish + $command = drush_parse_command(); + drush_command_default_options($command); + + // If the command sets the 'strict-option-handling' flag, then we will remove + // any cli options that appear after the command name from the 'cli' context. + // The cli options that appear before the command name are stored in the + // 'DRUSH_GLOBAL_CLI_OPTIONS' context, so we will just overwrite the cli context + // with this, after doing the neccessary fixup from short-form to long-form options. + // After we do that, we put back any local drush options identified by $command['options']. + if (is_array($command) && !empty($command['strict-option-handling'])) { + $cli_options = drush_get_context('DRUSH_GLOBAL_CLI_OPTIONS', array()); + // Now we are going to sort out any options that exist in $command['options']; + // we will remove these from DRUSH_COMMAND_ARGS and put them back into the + // cli options. + $cli_context = drush_get_context('cli'); + $remove_from_command_args = array(); + foreach ($command['options'] as $option => $info) { + if (array_key_exists($option, $cli_context)) { + $cli_options[$option] = $cli_context[$option]; + $remove_from_command_args[$option] = $option; + } + } + if (!empty($remove_from_command_args)) { + $drush_command_args = array(); + foreach (drush_get_context('DRUSH_COMMAND_ARGS') as $arg) { + if (!_drush_should_remove_command_arg($arg, $remove_from_command_args)) { + $drush_command_args[] = $arg; + } + } + drush_set_context('DRUSH_COMMAND_ARGS', $drush_command_args); + } + drush_expand_short_form_options($cli_options); + drush_set_context('cli', $cli_options); + _drush_preflight_global_options(); + } + $args = drush_get_arguments(); + $command_name = array_shift($args); + $root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT'); + $local_drush = drush_get_option('drush-script'); + if (empty($local_drush) && !empty($root)) { + $local_drush = find_wrapper_or_launcher($root); + } + $is_local = drush_get_option('local'); + $must_use_site_local = !empty($root) && !empty($local_drush) && !empty($is_local); + // If the command sets the 'handle-remote-commands' flag, then we will short-circuit + // remote command dispatching and site-list command dispatching, and always let + // the command handler run on the local machine. + if (is_array($command) && !empty($command['handle-remote-commands']) && !$must_use_site_local) { + return FALSE; + } + $values = NULL; + if (!isset($remote_host) && !isset($site_list) && $must_use_site_local) { + if (!drush_is_absolute_path($local_drush)) { + $local_drush = $root . DIRECTORY_SEPARATOR . $local_drush; + } + $local_drush = realpath($local_drush); + $this_drush = drush_find_drush(); + // If there is a local Drush selected, and it is not the + // same Drush that is currently running, redispatch to it. + // We assume that if the current Drush is nested inside + // the current Drupal root (or, more specifically, the + // current Drupal root's parent), then it is a site-local Drush. + // We avoid redispatching in that instance to prevent an + // infinite loop. + if (file_exists($local_drush) && !drush_is_nested_directory(dirname($root), $this_drush)) { + $uri = drush_get_context('DRUSH_SELECTED_URI'); + $aditional_options = array( + 'root' => $root, + ); + if (!empty($uri)) { + $aditional_options['uri'] = $uri; + } + // We need to chdir to the Drupal root here, for the + // benefit of the Drush wrapper. + chdir($root); + $values = drush_do_command_redispatch(is_array($command) ? $command : $command_name, $args, NULL, NULL, $local_drush, TRUE, $aditional_options); + } + } + if (isset($remote_host)) { + $remote_user = drush_get_option('remote-user'); + + // Force interactive mode if there is a single remote target. #interactive is added by drush_do_command_redispatch + $user_interactive = drush_get_option('interactive'); + drush_set_option('interactive', TRUE); + $values = drush_do_command_redispatch(is_array($command) ? $command : $command_name, $args, $remote_host, $remote_user, $user_interactive); + } + // If the --site-list flag is set, then we will execute the specified + // command once for every site listed in the site list. + if (isset($site_list)) { + if (!is_array($site_list)) { + $site_list = explode(',', $site_list); + } + $site_record = array('site-list' => $site_list); + $args = drush_get_arguments(); + + if (!drush_get_context('DRUSH_SIMULATE') && !$interactive && !drush_get_context('DRUSH_AFFIRMATIVE') && !drush_get_context('DRUSH_QUIET')) { + drush_print(dt("You are about to execute '!command' non-interactively (--yes forced) on all of the following targets:", array('!command' => implode(" ", $args)))); + foreach ($site_list as $one_destination) { + drush_print(dt(' !target', array('!target' => $one_destination))); + } + + if (drush_confirm('Continue? ') === FALSE) { + drush_user_abort(); + return TRUE; + } + } + $command_name = array_shift($args); + $multi_options = drush_redispatch_get_options(); + $backend_options = array(); + if (drush_get_option('pipe') || drush_get_option('interactive')) { + $backend_options['interactive'] = TRUE; + } + if (drush_get_option('no-label', FALSE)) { + $backend_options['no-label'] = TRUE; + } + // If the user specified a format, try to look up the + // default list separator for the specified format. + // If the user did not specify a different label separator, + // then pass in the default as an option, so that the + // separator between the items in the list and the site + // name will be consistent. + $format = drush_get_option('format', FALSE); + if ($format && !array_key_exists('label-separator', $multi_options)) { + $formatter = drush_load_engine('outputformat', $format); + if ($formatter) { + $list_separator = $formatter->get_info('list-separator'); + if ($list_separator) { + $multi_options['label-separator'] = $list_separator; + } + } + } + $values = drush_invoke_process($site_record, $command_name, $args, $multi_options, $backend_options); + } + if (isset($values)) { + if (is_array($values) && ($values['error_status'] > 0)) { + // Force an error result code. Note that drush_shutdown() will still run. + drush_set_context('DRUSH_EXECUTION_COMPLETED', TRUE); + exit($values['error_status']); + } + return TRUE; + } + return FALSE; +} + +/** + * Look for instances of arguments or parameters that + * start with "~/". Replace these with "$HOME/". + * + * Note that this function is called _after_ Drush does + * its redispatch checks; tildes are passed through + * unmodified on a redispatch, and are only expanded when + * a command is handled locally. + */ +function drush_preflight_tilde_expansion(&$command) { + // Skip tilde expansion for commands that use + // stict option handling, or those that explicitly + // turn it off via $command['tilde-expansion'] = FALSE. + if ($command['tilde-expansion'] && !$command['strict-option-handling']) { + $cli =& drush_get_context('cli'); + $match = '#^~/#'; + $replacement = drush_server_home() . '/'; + foreach ($cli as $key => $value) { + if (is_string($value) && preg_match($match, $value)) { + $cli[$key] = preg_replace($match, $replacement, $value); + } + } + $command['arguments'] = array_map(function($value) use($match, $replacement) { return is_string($value) ? preg_replace($match, $replacement, $value) : $value; } , $command['arguments']); + } +} + +/** + * We set this context to let the shutdown function know we reached the end of drush_main(). + * + * @see drush_main() + */ +function drush_postflight() { + drush_set_context("DRUSH_EXECUTION_COMPLETED", TRUE); +} + +/** + * Shutdown function for use while Drush and Drupal are bootstrapping and to return any + * registered errors. + * + * The shutdown command checks whether certain options are set to reliably + * detect and log some common Drupal initialization errors. + * + * If the command is being executed with the --backend option, the script + * will return a json string containing the options and log information + * used by the script. + * + * The command will exit with '1' if it was successfully executed, and the + * result of drush_get_error() if it wasn't. + */ +function drush_shutdown() { + // Mysteriously make $user available during sess_write(). Avoids a NOTICE. + global $user; + + if (!drush_get_context('DRUSH_EXECUTION_COMPLETED', FALSE) && !drush_get_context('DRUSH_USER_ABORT', FALSE)) { + $php_error_message = ''; + if ($error = error_get_last()) { + $php_error_message = "\n" . dt('Error: !message in !file, line !line', array('!message' => $error['message'], '!file' => $error['file'], '!line' => $error['line'])); + } + // We did not reach the end of the drush_main function, + // this generally means somewhere in the code a call to exit(), + // was made. We catch this, so that we can trigger an error in + // those cases. + drush_set_error("DRUSH_NOT_COMPLETED", dt("Drush command terminated abnormally due to an unrecoverable error.!message", array('!message' => $php_error_message))); + // Attempt to give the user some advice about how to fix the problem + _drush_postmortem(); + } + + // @todo Ask the bootstrap object (or maybe dispatch) how far we got. + $phase = drush_get_context('DRUSH_BOOTSTRAP_PHASE'); + if (drush_get_context('DRUSH_BOOTSTRAPPING')) { + switch ($phase) { + case DRUSH_BOOTSTRAP_DRUPAL_FULL : + ob_end_clean(); + _drush_log_drupal_messages(); + drush_set_error('DRUSH_DRUPAL_BOOTSTRAP_ERROR'); + break; + } + } + + if (drush_get_context('DRUSH_BACKEND', FALSE)) { + drush_backend_output(); + } + elseif (drush_get_context('DRUSH_QUIET', FALSE)) { + ob_end_clean(); + // If we are in pipe mode, emit the compact representation of the command, if available. + if (drush_get_context('DRUSH_PIPE')) { + drush_pipe_output(); + } + } + + // This way drush_return_status() will always be the last shutdown function (unless other shutdown functions register shutdown functions...) + // and won't prevent other registered shutdown functions (IE from numerous cron methods) from running by calling exit() before they get a chance. + if (!defined('UNISH_DRUSH_TESTS_RUNNING')) { + register_shutdown_function('drush_return_status'); + } +} + +/** + * Shutdown function to save code coverage data. + */ +function drush_coverage_shutdown() { + if ($file_name = drush_get_context('DRUSH_CODE_COVERAGE', FALSE)) { + $data = xdebug_get_code_coverage(); + xdebug_stop_code_coverage(); + + // If coverage dump file contains anything, merge in the old data before + // saving. This happens if the current drush command invoked another drush + // command. + if (file_exists($file_name) && $content = file_get_contents($file_name)) { + $merge_data = unserialize($content); + if (is_array($merge_data)) { + foreach ($merge_data as $file => $lines) { + if (!isset($data[$file])) { + $data[$file] = $lines; + } + else { + foreach ($lines as $num => $executed) { + if (!isset($data[$file][$num])) { + $data[$file][$num] = $executed; + } + else { + $data[$file][$num] = ($executed == 1 ? $executed : $data[$file][$num]); + } + } + } + } + } + } + + file_put_contents($file_name, serialize($data)); + } +} + +function drush_return_status() { + // If a specific exit code was set, then use it. + $exit_code = drush_get_context('DRUSH_EXIT_CODE'); + if (empty($exit_code)) { + $exit_code = (drush_get_error()) ? DRUSH_FRAMEWORK_ERROR : DRUSH_SUCCESS; + } + + exit($exit_code); +} diff --git a/vendor/drush/drush/includes/sitealias.inc b/vendor/drush/drush/includes/sitealias.inc new file mode 100644 index 0000000000..9c03c9372f --- /dev/null +++ b/vendor/drush/drush/includes/sitealias.inc @@ -0,0 +1,2391 @@ + $drupal_root, 'uri' => $uri)); + } + } +} + +/** + * Given a list of alias records, shorten the name used if possible + */ +function drush_sitealias_simplify_names($site_list) { + $result = array(); + foreach ($site_list as $original_name => $alias_record) { + $adjusted_name = $alias_record['#name']; + $hashpos = strpos($original_name, '#'); + if ($hashpos !== FALSE) { + $adjusted_name = substr($original_name, $hashpos); + if (array_key_exists('remote-host', $alias_record)) { + $adjusted_name = $alias_record['remote-host'] . $adjusted_name; + } + } + $result[$adjusted_name] = $alias_record; + } + return $result; +} + +/** + * Given an array of site specifications, resolve each one in turn and + * return an array of alias records. If you only want a single record, + * it is preferable to simply call drush_sitealias_get_record() directly. + * + * @param $site_specifications + * One of: + * A comma-separated list of site specifications: '@site1,@site2' + * An array of site specifications: array('@site1','@site2') + * An array of alias records: + * array( + * 'site1' => array('root' => ...), + * 'site2' => array('root' => ...) + * ) + * An array of site specifications. + * @see drush_sitealias_get_record() for the format of site specifications. + * @return + * An array of alias records + */ +function drush_sitealias_resolve_sitespecs($site_specifications, $alias_path_context = NULL) { + $result_list = array(); + $not_found = array(); + if (!is_array($site_specifications)) { + $site_specifications = explode(',', $site_specifications); + } + if (!empty($site_specifications)) { + foreach ($site_specifications as $site) { + if (is_array($site)) { + $result_list[] = $site; + } + else { + $alias_record = drush_sitealias_get_record($site, $alias_path_context); + if (!$alias_record) { + $not_found[] = $site; + } + else { + $result_list = array_merge($result_list, drush_sitealias_resolve_sitelist($alias_record)); + } + } + } + } + return array($result_list, $not_found); +} + +/** + * Returns TRUE if $alias is a valid format for an alias name. + * + * Mirrors the allowed formats shown below for drush_sitealias_get_record. + */ +function drush_sitealias_valid_alias_format($alias) { + return ( (strpos($alias, ',') !== false) || + ((strpos($alias, '@') === FALSE ? 0 : 1) + (strpos($alias, '/') === FALSE ? 0 : 1) + (strpos($alias, '#') === FALSE ? 0 : 1) >= 2) || + ($alias[0] == '#') || + ($alias[0] == '@') + ); +} + +/** + * Get a site alias record given an alias name or site specification. + * + * If it is the name of a site alias, return the alias record from + * the site aliases array. + * + * If it is the name of a folder in the 'sites' folder, construct + * an alias record from values stored in settings.php. + * + * If it is a site specification, construct an alias record from the + * values in the specification. + * + * Site specifications come in several forms: + * - /path/to/drupal#sitename + * - user@server/path/to/drupal#sitename + * - user@server/path/to/drupal (sitename == server) + * - user@server#sitename (only if $option['r'] set in some drushrc file on server) + * - #sitename (only if $option['r'] already set, and 'sitename' is a folder in $option['r']/sites) + * - sitename (only if $option['r'] already set, and 'sitename' is a folder in $option['r']/sites) + * + * Note that in the case of the first four forms, it is also possible + * to add additional site variable to the specification using uri query + * syntax. For example: + * + * user@server/path/to/drupal?db-url=...#sitename + * + * @param alias + * An alias name or site specification + * @return array + * An alias record, or empty if none found. + */ +function drush_sitealias_get_record($alias, $alias_context = NULL) { + // Check to see if the alias contains commas. If it does, then + // we will go ahead and make a site list record + $alias_record = array(); + if (strpos($alias, ',') !== false) { + // TODO: If the site list contains any site lists, or site + // search paths, then we should expand those and merge them + // into this list longhand. + $alias_record['site-list'] = explode(',', $alias); + } + else { + $alias_record = _drush_sitealias_get_record($alias, $alias_context); + } + if (!empty($alias_record)) { + if (array_key_exists('#name', $alias_record)) { + if ($alias_record['#name'] == 'self') { + $path = drush_sitealias_local_site_path($alias_record); + if ($path) { + $cached_alias_record = drush_sitealias_lookup_alias_by_path($path); + // Don't overrite keys which have already been negotiated. + unset($cached_alias_record['#name'], $cached_alias_record['root'], $cached_alias_record['uri']); + $alias_record = array_merge($alias_record, $cached_alias_record); + } + } + } + else { + $alias_record['#name'] = drush_sitealias_uri_to_site_dir($alias); + } + } + return $alias_record; +} + +/** + * This is a continuation of drush_sitealias_get_record, above. It is + * not intended to be called directly. + */ +function _drush_sitealias_get_record($alias, $alias_context = NULL) { + $alias_record = array(); + // Before we do anything else, load $alias if it needs to be loaded + _drush_sitealias_load_alias($alias, $alias_context); + + // Check to see if the provided parameter is in fact a defined alias. + $all_site_aliases =& drush_get_context('site-aliases'); + if (array_key_exists($alias, $all_site_aliases)) { + $alias_record = $all_site_aliases[$alias]; + } + // If the parameter is not an alias, then it is some form of + // site specification (or it is nothing at all) + else { + if (isset($alias)) { + // Cases 1.) - 4.): + // We will check for a site specification if the alias has at least + // two characters from the set '@', '/', '#'. + if ((strpos($alias, '@') === FALSE ? 0 : 1) + ((strpos($alias, '/') === FALSE && strpos($alias, '\\') === FALSE) ? 0 : 1) + (strpos($alias, '#') === FALSE ? 0 : 1) >= 2) { + if ((substr($alias,0,7) != 'http://') && !drush_is_absolute_path($alias)) { + // Add on a scheme so that "user:pass@server" will always parse correctly + $parsed = parse_url('http://' . $alias); + } + else if (drush_is_windows() && drush_is_absolute_path($alias)) { + // On windows if alias begins with a filesystem path we must add file:// scheme to make it parse correcly + $parsed = parse_url('file:///' . $alias); + } + else { + $parsed = parse_url($alias); + } + // Copy various parts of the parsed URL into the appropriate records of the alias record + foreach (array('user' => 'remote-user', 'pass' => 'remote-pass', 'host' => 'remote-host', 'fragment' => 'uri', 'path' => 'root') as $url_key => $option_key) { + if (array_key_exists($url_key, $parsed)) { + _drush_sitealias_set_record_element($alias_record, $option_key, $parsed[$url_key]); + } + } + // If the site specification has a query, also set the query items + // in the alias record. This allows passing db_url as part of the + // site specification, for example. + if (array_key_exists('query', $parsed)) { + foreach (explode('&', $parsed['query']) as $query_arg) { + $query_components = explode('=', $query_arg); + _drush_sitealias_set_record_element($alias_record, urldecode($query_components[0]), urldecode($query_components[1])); + } + } + + // Case 3.): If the URL contains a 'host' portion but no fragment, then set the uri to the host + // Note: We presume that 'server' is the best default for case 3; without this code, the default would + // be whatever is set in $options['l'] on the target machine's drushrc.php settings file. + if (array_key_exists('host', $parsed) && !array_key_exists('fragment', $parsed)) { + $alias_record['uri'] = $parsed['host']; + } + + // Special checking: relative aliases embedded in a path + $relative_alias_pos = strpos($alias_record['root'], '/@'); + if ($relative_alias_pos !== FALSE) { + // Special checking: /path/@sites + $base = substr($alias_record['root'], 0, $relative_alias_pos); + $relative_alias = substr($alias_record['root'], $relative_alias_pos + 1); + if (drush_valid_root($base) || ($relative_alias == '@sites')) { + drush_sitealias_create_sites_alias($base); + $alias_record = drush_sitealias_get_record($relative_alias); + } + else { + $alias_record = array(); + } + } + } + else { + // Case 5.) and 6.): + // If the alias is the name of a folder in the 'sites' directory, + // then use it as a local site specification. + $alias_record = _drush_sitealias_find_record_for_local_site($alias); + } + } + } + + if (!empty($alias_record)) { + if (!isset($alias_record['remote']) && !isset($alias_record['#loaded-config'])) { + if (array_key_exists('root', $alias_record)) { + drush_sitealias_add_to_alias_path($alias_record['root'] . '/drush'); + drush_sitealias_add_to_alias_path($alias_record['root'] . '/sites/all/drush'); + } + // TODO: We should probably remove this feature, and put it back + // in, but in different places (e.g. site selection, sql-sync + rsync + // parameters, etc.) + $alias_site_dir = drush_sitealias_local_site_path($alias_record); + + if (isset($alias_site_dir)) { + // Add the sites folder of this site to the alias search path list + drush_sitealias_add_to_alias_path($alias_site_dir); + } + if (isset($alias_record['config']) && file_exists($alias_record['config'])) { + drush_load_config_file('site', $alias_record['config']); + $alias_record['#loaded-config'] = TRUE; + } + unset($alias_record['config']); + } + + // Add the static defaults + _drush_sitealias_add_static_defaults($alias_record); + + // Cache the result with all of its calculated values + $all_site_aliases[$alias] = $alias_record; + } + + return $alias_record; +} + +/** + * Add a path to the array of paths where alias files are searched for. + * + * @param $add_path + * A path to add to the search path (or NULL to not add any). + * Once added, the new path will remain available until drush + * exits. + * @return + * An array of paths containing all values added so far + */ +function drush_sitealias_add_to_alias_path($add_path) { + static $site_paths = array(); + + if ($add_path != NULL) { + if (!is_array($add_path)) { + $add_path = explode(PATH_SEPARATOR, $add_path); + } + // Normalize path to make sure we don't add the same path twice on + // windows due to different spelling. e.g. c:\tmp and c:/tmp + foreach($add_path as &$path) { + $path = drush_normalize_path($path); + } + $site_paths = array_unique(array_merge($site_paths, $add_path)); + } + return $site_paths; +} + +/** + * Return the array of paths where alias files are searched for. + * + * @param $alias_path_context + * If the alias being looked up is part of a relative alias, + * the alias path context specifies the context of the primary + * alias the new alias is rooted from. Alias files stored in + * the sites folder of this context, or inside the context itself + * takes priority over any other search path that might define + * a similarly-named alias. In this way, multiple sites can define + * a '@peer' alias. + * @return + * An array of paths + */ +function drush_sitealias_alias_path($alias_path_context = NULL) { + $context_path = array(); + if (isset($alias_path_context)) { + $context_path = array(drush_sitealias_local_site_path($alias_path_context)); + } + // We get the current list of site paths by adding NULL + // (nothing) to the path list, which is a no-op + $site_paths = drush_sitealias_add_to_alias_path(NULL); + + // If the user defined the root of a drupal site, then also + // look for alias files in /drush and /sites/all/drush. + $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT'); + if (!empty($drupal_root)) { + $site_paths[] = drush_sitealias_alias_base_directory($drupal_root . '/../drush'); + $site_paths[] = drush_sitealias_alias_base_directory($drupal_root . '/drush'); + $site_paths[] = drush_sitealias_alias_base_directory($drupal_root . '/sites/all/drush'); + $uri = drush_get_context('DRUSH_SELECTED_URI'); + if (empty($uri)) { + $uri = 'default'; + } + $site_dir = drush_sitealias_uri_to_site_dir($uri, $drupal_root); + if ($site_dir) { + $site_paths[] = drush_sitealias_alias_base_directory("$drupal_root/sites/$site_dir"); + } + } + $alias_path = (array) drush_get_context('ALIAS_PATH', array()); + return array_unique(array_merge($context_path, $alias_path, $site_paths)); +} + +/** + * If there is a directory 'site-aliases' in the specified search location, + * then search ONLY in that directory for aliases. Otherwise, search + * anywhere inside the specified directory for aliases. + */ +function drush_sitealias_alias_base_directory($dir) { + $potential_location = $dir . '/site-aliases'; + if (is_dir($potential_location)) { + return $potential_location; + } + return $dir; +} + +/** + * Return the full path to the site directory of the + * given alias record. + * + * @param $alias_record + * The alias record + * @return + * The path to the site directory of the associated + * alias record, or NULL if the record is not a local site. + */ +function drush_sitealias_local_site_path($alias_record) { + $result = NULL; + + if (isset($alias_record['root']) && !isset($alias_record['remote-host'])) { + if (isset($alias_record['uri'])) { + $uri = $alias_record['uri']; + $uri = preg_replace('#^[^:]*://#', '', $uri); + while (!$result && !empty($uri)) { + if (file_exists($alias_record['root'] . '/sites/sites.php')) { + $sites = array(); + include($alias_record['root'] . '/sites/sites.php'); + if (array_key_exists($uri, $sites)) { + $result = $alias_record['root'] . '/sites/' . $sites[$uri]; + } + } + if (!$result) { + $result = ($alias_record['root'] . '/sites/' . drush_sitealias_uri_to_site_dir($uri, drush_sitealias_get_root($alias_record))); + } + $result = realpath($result); + $uri = preg_replace('#^[^.]*\.*#', '', $uri); + } + } + if (!$result) { + $result = realpath($alias_record['root'] . '/sites/default'); + } + } + + return $result; +} + +/** + * Check and see if an alias definition for $alias is available. + * If it is, load it into the list of aliases cached in the + * 'site-aliases' context. + * + * @param $alias + * The name of the alias to load in ordinary form ('@name') + * @param $alias_path_context + * When looking up a relative alias, the alias path context is + * the primary alias that we will start our search from. + */ +function _drush_sitealias_load_alias($alias, $alias_path_context = NULL) { + $all_site_aliases = drush_get_context('site-aliases'); + $result = array(); + + // Only aliases--those named entities that begin with '@'--can be loaded this way. + // We also skip any alias that has already been loaded. + if ((substr($alias,0,1) == '@') && !array_key_exists($alias,$all_site_aliases)) { + $aliasname = substr($alias,1); + $result = _drush_sitealias_find_and_load_alias($aliasname, $alias_path_context); + if (!empty($result)) { + $alias_options = array('site-aliases' => array($aliasname => $result)); + _drush_sitealias_add_inherited_values($alias_options['site-aliases']); + drush_set_config_special_contexts($alias_options); + if (array_key_exists('#file', $result)) { + drush_log(dt('Loaded alias !alias from file !file', array('!alias' => $alias, '!file' => $result['#file']))); + } + } + } + + return $result; +} + +/** + * Load every alias file that can be found anywhere in the + * alias search path. + */ +function drush_sitealias_load_all($resolve_parent = TRUE) { + $result = _drush_sitealias_find_and_load_all_aliases(); + if (!empty($result) && ($resolve_parent == TRUE)) { + // If any aliases were returned, then check for + // inheritance and then store the aliases into the + // alias cache + _drush_sitealias_add_inherited_values($result); + $alias_options = array('site-aliases' => $result); + drush_set_config_special_contexts($alias_options); + } +} + +/** + * Worker function called by _drush_sitealias_load_alias and + * drush_sitealias_load_all. Traverses the alias search path + * and finds the specified alias record. + * + * @return + * An array of $kay => $value pair of alias names and alias records + * loaded. + */ +function _drush_sitealias_find_and_load_all_aliases() { + $result = array(); + + $drush_alias_files = _drush_sitealias_find_alias_files(); + drush_set_context('drush-alias-files', $drush_alias_files); + + // For every file that matches, check inside it for + // an alias with a matching name. + foreach ($drush_alias_files as $filename) { + if (file_exists($filename)) { + $aliases = $options = array(); + // silently ignore files we can't include + if ((@include $filename) === FALSE) { + drush_log(dt('Cannot open alias file "!alias", ignoring.', array('!alias' => realpath($filename))), LogLevel::BOOTSTRAP); + continue; + } + unset($options['site-aliases']); // maybe unnecessary + + // If $aliases are not set, but $options are, then define one alias named + // after the first word of the file, before '.alias.drushrc.php. + if (empty($aliases) && !empty($options)) { + $this_alias_name = substr(basename($filename),0,strpos(basename($filename),'.')); + $aliases[$this_alias_name] = $options; + $options = array(); + } + // If this is a group alias file, then make an + // implicit alias from the group name that contains + // a site-list of all of the aliases in the file + $group_name = ''; + if (substr($filename, -20) == ".aliases.drushrc.php") { + $group_name = basename($filename,".aliases.drushrc.php"); + if (!empty($aliases) && !array_key_exists($group_name, $aliases)) { + $alias_names = array(); + foreach (array_keys($aliases) as $one_alias) { + $alias_names[] = "@$group_name.$one_alias"; + $aliases["$group_name.$one_alias"] = $aliases[$one_alias]; + unset($aliases[$one_alias]); + } + $aliases[$group_name] = array('site-list' => implode(',', $alias_names)); + } + } + if (!empty($aliases)) { + if (!empty($options)) { + foreach ($aliases as $name => $value) { + $aliases[$name] = array_merge($options, $value); + } + $options = array(); + } + + foreach ($aliases as $name => $value) { + _drush_sitealias_initialize_alias_record($aliases[$name]); + $aliases[$name]['#name'] = $name; + $aliases[$name]['#file'] = $filename; + } + + $result = _sitealias_array_merge($result, $aliases); + // If we found at least one alias from this file + // then record it in the drush-alias-files context. + $drush_alias_files = drush_get_context('drush-alias-files'); + if (!in_array($filename, $drush_alias_files)) { + $drush_alias_files[] = $filename; + } + drush_set_context('drush-alias-files', $drush_alias_files); + } + } + } + + return $result; +} + +/** + * Function to find all alias files that might contain aliases + * that match the requested alias name. + */ +function _drush_sitealias_find_alias_files($aliasname = NULL, $alias_path_context = NULL) { + $alias_files_to_consider = array(); + + // The alias path is a list of folders to search for alias settings files + $alias_path = drush_sitealias_alias_path($alias_path_context); + + // $alias_files contains a list of filename patterns + // to search for. We will find any matching file in + // any folder in the alias path. The directory scan + // is not deep, though; only files immediately in the + // search path are considered. + $alias_files = array('/.*aliases\.drush(' . DRUSH_MAJOR_VERSION . '|)rc\.php$/'); + if ($aliasname == NULL) { + $alias_files[] = '/.*\.alias\.drush(' . DRUSH_MAJOR_VERSION . '|)rc\.php$/'; + } + else { + $alias_files[] = '/' . preg_quote($aliasname, '/') . '\.alias\.drush(' . DRUSH_MAJOR_VERSION . '|)rc\.php$/'; + } + + // Do not scan into the files directory. + $blacklist = array_merge(array('files'), drush_filename_blacklist()); + + // $alias_search_depth specifies max depth to search in the alias paths + $alias_search_depth = drush_get_option('alias-search-depth', TRUE); + + // Search each path in turn. + foreach ($alias_path as $path) { + // Find all of the matching files in this location + foreach ($alias_files as $file_pattern_to_search_for) { + drush_log(dt('Scanning into @path for @pattern', array('@path' => $path, '@pattern' => $file_pattern_to_search_for)), LogLevel::DEBUG_NOTIFY); + $alias_files_to_consider = array_merge($alias_files_to_consider, array_keys(drush_scan_directory($path, $file_pattern_to_search_for, $blacklist, 0, $alias_search_depth))); + } + } + + return $alias_files_to_consider; +} + +/** + * Traverses the alias search path and finds the specified alias record. + * + * @param $aliasname + * The name of the alias without the leading '@' (i.e. '#name') + * or NULL to load every alias found in every alias file. + * @param $alias_path_context + * When looking up a relative alias, the alias path context is + * the primary alias that we will start our search from. + * @return + * An empty array if nothing was loaded. If $aliasname is + * not null, then the array returned is the alias record for + * $aliasname. If $aliasname is NULL, then the array returned + * is a $kay => $value pair of alias names and alias records + * loaded. + */ +function _drush_sitealias_find_and_load_alias($aliasname, $alias_path_context = NULL) { + // Special checking for '@sites' alias + if ($aliasname == 'sites') { + $drupal_root = NULL; + if ($alias_path_context != null) { + if (array_key_exists('root', $alias_path_context) && !array_key_exists('remote-host', $alias_path_context)) { + $drupal_root = $alias_path_context['root']; + } + } + else { + $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT'); + } + if (isset($drupal_root) && !is_array($drupal_root)) { + drush_sitealias_create_sites_alias($drupal_root); + } + } + + $alias_files_to_consider = _drush_sitealias_find_alias_files($aliasname, $alias_path_context); + + return _drush_sitealias_find_and_load_alias_from_file($aliasname, $alias_files_to_consider); +} + +function _drush_sitealias_find_and_load_alias_from_file($aliasname, $alias_files_to_consider) { + $result = array(); + $result_names = array(); + + // For every file that matches, check inside it for + // an alias with a matching name. + $recorded_files = array(); + foreach ($alias_files_to_consider as $filename) { + if (file_exists($filename)) { + $aliases = $options = array(); + // silently ignore files we can't include + if ((@include $filename) === FALSE) { + drush_log(dt('Cannot open alias file "!alias", ignoring.', array('!alias' => realpath($filename))), LogLevel::BOOTSTRAP); + continue; + } + unset($options['site-aliases']); // maybe unnecessary + + // If $aliases are not set, but $options are, then define one alias named + // after the first word of the file, before '.alias.drushrc.php. + if (empty($aliases) && !empty($options)) { + $this_alias_name = substr(basename($filename),0,strpos(basename($filename),'.')); + $aliases[$this_alias_name] = $options; + $options = array(); + } + // If this is a group alias file, then make an + // implicit alias from the group name that contains + // a site-list of all of the aliases in the file + $group_prefix = ''; + if (substr($filename, -20) == ".aliases.drushrc.php") { + $group_name = basename($filename,".aliases.drushrc.php"); + $group_prefix = $group_name . '.'; + if (!empty($aliases) && !array_key_exists($group_name, $aliases)) { + $alias_names = array(); + foreach (array_keys($aliases) as $one_alias) { + $alias_names[] = "@$group_name.$one_alias"; + $aliases[$one_alias]['#name'] = "$group_name.$one_alias"; + $aliases[$one_alias]['#group'] = $group_name; + $aliases["$group_name.$one_alias"] = $aliases[$one_alias]; + $aliases[$one_alias]["#hidden"] = TRUE; + } + $aliases[$group_name] = array('site-list' => implode(',', $alias_names), '#group' => $group_name, '#name' => $group_name); + } + } + // Wildcard check if necessary. This will insert a resolved alias + // record into the aliases list, allowing the existing code to match it. + _drush_sitealias_wildcard_check($aliasname, $aliases); + // Store only the named alias into the alias cache + if ((isset($aliases)) && !empty($aliasname) && array_key_exists($aliasname, $aliases)) { + drush_set_config_special_contexts($options); // maybe unnecessary + $one_result = array_merge($options, $aliases[$aliasname]); + $one_result['#file'] = $filename; + if (!array_key_exists('#name', $one_result)) { + $one_result['#name'] = $aliasname; + } + _drush_sitealias_initialize_alias_record($one_result); + // If the alias name is exactly the same as a previous match, then + // merge the two records together + if (!empty($result) && ($result['#name'] == $one_result['#name'])) { + $result = _sitealias_array_merge($result, $one_result); + } + // Add the name of the found record to the list of results + else { + $result_names[] = "@" . $one_result['#name']; + $result = $one_result; + } + } + } + } + // If there are multiple matches, then return a list of results. + if (count($result_names) > 1) { + $result = array('site-list' => $result_names); + } + + return $result; +} + +/** + * Convert a matching wildcard record into the requested record if needed. + */ +function _drush_sitealias_wildcard_check($aliasname, &$aliases) { + // Don't overwrite a specific alias record with a resolved wildcard record. + if (array_key_exists($aliasname, $aliases) || empty($aliasname)) { + return; + } + // If the alias is 'site.dev', then $alias_env is 'dev' + $alias_env = preg_replace('#^.*\.#', '', $aliasname); + // Check to see if there is a wildcard record. + // e.g. if alias is 'site.dev', then look for a record 'site.*'. + $wildcard_alias_name = preg_replace('#\.[^.]*$#', '.*', $aliasname); + if (($wildcard_alias_name == $aliasname) || empty($alias_env) || !array_key_exists($wildcard_alias_name, $aliases)) { + return; + } + + $aliases[$aliasname] = _drush_sitealias_replace_wildcard_values($aliases[$wildcard_alias_name], $alias_env); + $aliases[$aliasname]['#name'] = $aliasname; +} + +/** + * Make substitutions in a wildcard alias record. + */ +function _drush_sitealias_replace_wildcard_values($alias_record, $alias_env) { + $result = array(); + + foreach ($alias_record as $key => $value) { + if (is_array($value)) { + $result[$key] = _drush_sitealias_replace_wildcard_values($value, $alias_env); + } + elseif (is_string($value)) { + // This substitution looks just like a consolidation/config variable, + // although this is the only one we support. + $result[$key] = str_replace('${env-name}', $alias_env, $value); + } + else { + $result[$key] = $value; + } + } + return $result; +} + +/** + * Merges two site aliases. + * + * array_merge_recursive is too much; we only want to run + * array_merge on the common top-level keys of the array. + * + * @param array $site_alias_a + * A site alias array. + * @param array $site_alias_b + * A site alias array. + * @return + * A site alias array where the keys from $site_alias_a are overwritten by the + * keys from $site_alias_b. + */ +function _sitealias_array_merge($site_alias_a, $site_alias_b) { + $result = $site_alias_a; + + foreach($site_alias_b as $key => $value) { + if (is_array($value) && array_key_exists($key, $result)) { + $result[$key] = array_merge($result[$key], $value); + } + else { + $result[$key] = $value; + } + } + + return $result; +} + +/** + * Check to see if there is a 'parent' item in the alias; if there is, + * then load the parent alias record and overlay the entries in the + * current alias record on top of the items from the parent record. + * + * @param $aliases + * An array of alias records that are modified in-place. + */ +function _drush_sitealias_add_inherited_values(&$aliases) { + foreach ($aliases as $alias_name => $alias_value) { + // Prevent circular references from causing an infinite loop + _drush_sitealias_cache_alias("@$alias_name", array()); + _drush_sitealias_add_inherited_values_to_record($alias_value); + $aliases[$alias_name] = $alias_value; + } +} + +function _drush_sitealias_add_inherited_values_to_record(&$alias_value) { + drush_command_invoke_all_ref('drush_sitealias_alter', $alias_value); + if (isset($alias_value['parent'])) { + drush_log(dt("Using deprecated 'parent' element '!parent' in '!name'.", array('!parent' => $alias_value['parent'], '!name' => $alias_value['#name'])), LogLevel::DEBUG); + // Fetch and merge in each parent + foreach (explode(',', $alias_value['parent']) as $parent) { + $parent_record = drush_sitealias_get_record($parent); + unset($parent_record['#name']); + unset($parent_record['#file']); + unset($parent_record['#hidden']); + $array_based_keys = array_merge(drush_get_special_keys(), array('path-aliases')); + foreach ($array_based_keys as $array_based_key) { + if (isset($alias_value[$array_based_key]) && isset($parent_record[$array_based_key])) { + $alias_value[$array_based_key] = array_merge($parent_record[$array_based_key], $alias_value[$array_based_key]); + } + } + $alias_value = array_merge($parent_record, $alias_value); + } + } + unset($alias_value['parent']); +} + +/** + * Add an empty record for the specified alias name + * + * @param $alias_name + * The name of the alias, including the leading "@" + */ +function _drush_sitealias_cache_alias($alias_name, $alias_record) { + $cache =& drush_get_context('site-aliases'); + // If the alias already exists in the cache, then merge + // the new alias with the existing alias + if (array_key_exists($alias_name, $cache)) { + $alias_record = array_merge($cache[$alias_name], $alias_record); + } + if (!isset($alias_record['#name'])) { + $alias_record['#name'] = trim($alias_name, '@'); + } + $cache[$alias_name] = $alias_record; + + // If the alias record points at a local site, make sure + // that /drush, /sites/all/drush and the site folder for that site + // are added to the alias path, so that other alias files + // stored in those locations become searchable. + if (!array_key_exists('remote-host', $alias_record) && !empty($alias_record['root'])) { + drush_sitealias_add_to_alias_path($alias_record['root'] . '/drush'); + drush_sitealias_add_to_alias_path($alias_record['root'] . '/sites/all/drush'); + $site_dir = drush_sitealias_local_site_path($alias_record); + if (isset($site_dir)) { + drush_sitealias_add_to_alias_path($site_dir); + } + } +} + +/** + * If the alias record does not contain a 'databases' or 'db-url' + * entry, then use backend invoke to look up the settings value + * from the remote or local site. The 'db_url' form is preferred; + * nothing is done if 'db_url' is not available (e.g. on a D7 site) + * + * @param $alias_record + * The full alias record to populate with database settings + */ +function drush_sitealias_add_db_url(&$alias_record) { + if (!isset($alias_record['db-url']) && !isset($alias_record['databases']) && !isset($alias_record['site-list'])) { + drush_sitealias_add_db_settings($alias_record); + } + if (!isset($alias_record['db-url']) && isset($alias_record['databases'])) { + $alias_record['db-url'] = drush_sitealias_convert_databases_to_db_url($alias_record['databases']); + } +} + +/** + * Drush still accepts --db-url format database specifications as + * cli parameters; it is therefore useful to be able to convert + * from a database record back to a db-url sometimes. + */ +function drush_sitealias_convert_db_spec_to_db_url($db_spec) { + $result = urlencode($db_spec["driver"]) . "://"; + if (isset($db_spec["username"])) { + $result .= urlencode($db_spec["username"]); + if (isset($db_spec["password"])) { + $result .= ":" . urlencode($db_spec["password"]); + } + $result .= "@"; + } + // Host is required, unless this is an sqlite db. + if (isset($db_spec["host"])) { + $result .= urlencode($db_spec["host"]); + if (isset($db_spec["port"])) { + $result .= ":" . urlencode($db_spec["port"]); + } + $result .= '/' . urlencode($db_spec["database"]); + } + else { + // URL-encode the database, but convert slashes + // back to their original form for readability. + // This portion is the "path" of the URL, so it may + // contain slashes. This is important for sqlite. + $result .= str_replace("%2F", "/", urlencode(ltrim($db_spec["database"], '/'))); + } + return $result; +} + +/** + * Create a db-url from the databases record. + */ +function drush_sitealias_convert_databases_to_db_url($databases) { + if ((count($databases) == 1) && isset($databases['default'])) { + $result = drush_sitealias_convert_db_spec_to_db_url($databases['default']['default']); + } + else { + foreach ($databases as $key => $db_info) { + $result[$key] = drush_sitealias_convert_db_spec_to_db_url($db_info['default']); + } + } + return $result; +} + +/** + * Return the databases record from the alias record + * + * @param $alias_record + * A record returned from drush_sitealias_get_record + * @returns + * A databases record (always in D7 format) or NULL + * if the databases record could not be found. + */ +function sitealias_get_databases_from_record(&$alias_record) { + $altered_record = drush_sitealias_add_db_settings($alias_record); + + return array_key_exists('databases', $alias_record) ? $alias_record['databases'] : NULL; +} + +/** + * Return the $db_spec record for the database associated with + * the provided alias record. @see drush_sitealias_add_db_settings(), + * which will be used to first add the database information to the + * alias records, invoking sql-conf to look them up if necessary. + * + * The options 'database' and 'target' are used to specify which + * specific database should be fetched from the database record; + * they may appear in the alias definition, or may be taken from the + * command line options. The values 'default' and 'default' are + * used if these options are not specified in either location. + * + * Note that in the context of sql-sync, the site alias record will + * be taken from one of the source or target aliases + * (e.g. `drush sql-sync @source @target`), which will be overlayed with + * any options that begin with 'source-' or 'target-', respectively. + * Therefore, the commandline options 'source-database' and 'source-target' + * (or 'target-database' and 'source-target') may also affect the operation + * of this function. + */ +function drush_sitealias_get_db_spec(&$alias_record, $default_to_self = FALSE, $prefix = '') { + $db_spec = NULL; + $databases = sitealias_get_databases_from_record($alias_record); + if (isset($databases) && !empty($databases)) { + $database = drush_sitealias_get_option($alias_record, 'database', 'default', $prefix); + $target = drush_sitealias_get_option($alias_record, 'target', 'default', $prefix); + if (array_key_exists($database, $databases) && array_key_exists($target, $databases[$database])) { + $db_spec = $databases[$database][$target]; + } + } + elseif ($default_to_self) { + $db_spec = _drush_sql_get_db_spec(); + } + + if (isset($db_spec)) { + $remote_host = drush_sitealias_get_option($alias_record, 'remote-host', NULL, $prefix); + if (!drush_is_local_host($remote_host)) { + $db_spec['remote-host'] = $remote_host; + $db_spec['port'] = drush_sitealias_get_option($alias_record, 'remote-port', (isset($db_spec['port']) ? $db_spec['port'] : NULL), $prefix); + } + } + + return $db_spec; +} + +/** + * If the alias record does not contain a 'databases' or 'db-url' + * entry, then use backend invoke to look up the settings value + * from the remote or local site. The 'databases' form is + * preferred; 'db_url' will be converted to 'databases' if necessary. + * + * @param $alias_record + * The full alias record to populate with database settings + */ +function drush_sitealias_add_db_settings(&$alias_record) { + $altered_record = FALSE; + if (isset($alias_record['root']) || isset($alias_record['remote-host'])) { + // If the alias record does not have a defined 'databases' entry, + // then we'll need to look one up + if (!isset($alias_record['db-url']) && !isset($alias_record['databases']) && !isset($alias_record['site-list'])) { + // If this alias record is remote, and it does NOT have a 'root' element + // explicitly set, and 'root' was not passed on the command line, then + // force the 'root' element in the alias record to NULL to prevent + // backend invoke from adding a --root using the locally-bootstrapped + // Drupal site's 'root' path. + if (isset($alias_record['remote-host']) && !isset($alias_record['root']) && (drush_get_option('root', NULL, 'cli') === NULL)) { + $alias_record['root'] = NULL; + } + $values = drush_invoke_process($alias_record, "sql-conf", array(), array('all' => TRUE), array('integrate' => FALSE, 'override-simulated' => TRUE)); + if (is_array($values) && ($values['error_status'] == 0)) { + $altered_record = TRUE; + // If there are any special settings in the '@self' record returned by drush_invoke_process, + // then add those into our altered record as well + if (array_key_exists('self', $values)) { + $alias_record = array_merge($values['self'], $alias_record); + } + drush_sitealias_cache_db_settings($alias_record, $values['object']); + } + } + } + return $altered_record; +} + +function drush_sitealias_cache_db_settings(&$alias_record, $databases) { + if (!empty($databases)) { + $alias_record['databases'] = $databases; + } + + // If the name is set, then re-cache the record after we fetch the databases + if (array_key_exists('#name', $alias_record)) { + $all_site_aliases =& drush_get_context('site-aliases'); + $all_site_aliases['@' . $alias_record['#name']] = $alias_record; + // Check and see if this record is a copy of 'self' + if (($alias_record['#name'] != 'self') && array_key_exists('@self', $all_site_aliases) && array_key_exists('#name', $all_site_aliases['@self']) && ($all_site_aliases['@self']['#name'] == $alias_record['#name'])) { + $all_site_aliases['@self'] = $alias_record; + } + } +} + +/** + * Check to see if we have already bootstrapped to a site. + */ +function drush_sitealias_is_bootstrapped_site($alias_record) { + if (!isset($alias_record['remote-host']) && array_key_exists('root', $alias_record)) { + $self_record = drush_sitealias_get_record("@self"); + if (empty($self_record) || !array_key_exists('root', $self_record)) { + // TODO: If we have not bootstrapped to a site yet, we could + // perhaps bootstrap to $alias_record here. + return FALSE; + } + elseif(($alias_record['root'] == $self_record['root']) && ($alias_record['uri'] == $self_record['uri'])) { + return TRUE; + } + } + return FALSE; +} + +/** + * Determines whether a given site alias is for a remote site. + * + * @param string $alias + * An alias name or site specification. + * + * @return bool + * Returns TRUE if the alias refers to a remote site, FALSE if it does not, or NULL is unsure. + */ +function drush_sitealias_is_remote_site($alias) { + if (is_array($alias) && !empty($alias['remote-host'])) { + return TRUE; + } + if (!is_string($alias) || !strlen($alias)) { + return NULL; + } + + $site_record = drush_sitealias_get_record($alias); + if ($site_record) { + if (!empty($site_record['remote-host'])) { + return TRUE; + } + else { + return FALSE; + } + } + else { + drush_set_error('Unrecognized site alias.'); + } +} + +/** + * Get the name of the current bootstrapped site + */ +function drush_sitealias_bootstrapped_site_name() { + $site_name = NULL; + $self_record = drush_sitealias_get_record('@self'); + if (array_key_exists('#name', $self_record)) { + $site_name = $self_record['#name']; + } + if (!isset($site_name) || ($site_name == '@self')) { + $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT'); + if (isset($drupal_root)) { + $drupal_uri = drush_get_context('DRUSH_SELECTED_URI', 'default'); + $drupal_uri = str_replace('http://', '', $drupal_uri); + // TODO: Maybe use _drush_sitealias_find_local_alias_name? + $site_name = $drupal_root . '#' . $drupal_uri; + } + } + return $site_name; +} + +/** + * If there are any path aliases (items beginning with "%") in the test + * string, then resolve them as path aliases and add them to the provided + * alias record. + * + * @param $alias_record + * The full alias record to use in path alias expansion + * @param $test_string + * A slash-separated list of path aliases to resolve + * e.g. "%files/%special". + */ +function drush_sitealias_resolve_path_references(&$alias_record, $test_string = '') { + $path_aliases = array_key_exists('path-aliases', $alias_record) ? $alias_record['path-aliases'] : array(); + // Convert the test string into an array of items, and + // from this make a comma-separated list of projects + // that we can pass to 'drush status'. + $test_array = explode('/', $test_string); + $project_array = array(); + foreach($test_array as $one_item) { + if (!empty($one_item) && ($one_item[0] == '%') && (!array_key_exists($one_item,$path_aliases))) { + $project_array[] = substr($one_item,1); + } + } + $project_list = implode(',', $project_array); + + if (!empty($project_array)) { + // Optimization: if we're already bootstrapped to the + // site specified by $alias_record, then we can just + // call _core_site_status_table() rather than use backend invoke. + if (drush_sitealias_is_bootstrapped_site($alias_record) && drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)) { + $status_values = _core_site_status_table($project_list); + } + else { + $values = drush_invoke_process($alias_record, "core-status", array(), empty($project_list) ? array() : array('project' => $project_list), array('integrate' => FALSE, 'override-simulated' => TRUE)); + $status_values = $values['object']; + } + if (isset($status_values['%paths'])) { + foreach ($status_values['%paths'] as $key => $path) { + $alias_record['path-aliases'][$key] = $path; + } + } + // If 'root' is not set in the alias, then fill it in from the status values. + if (!isset($alias_record['root']) && isset($status_values['root'])) { + $alias_record['root'] = $status_values['root']; + } + } +} + +/** + * Given an alias record that is a site list (contains a 'site-list' entry), + * resolve all of the members of the site list and return them + * is an array of alias records. + * + * @param $alias_record + * The site list alias record array + * @return + * An array of individual site alias records + */ +function drush_sitealias_resolve_sitelist($alias_record) { + $result_list = array(); + if (isset($alias_record)) { + if (array_key_exists('site-list', $alias_record)) { + foreach ($alias_record['site-list'] as $sitespec) { + $one_result = drush_sitealias_get_record($sitespec); + $result_list = array_merge($result_list, drush_sitealias_resolve_sitelist($one_result)); + } + } + elseif (array_key_exists('#name', $alias_record)) { + $result_list[$alias_record['#name']] = $alias_record; + } + } + + return $result_list; +} + +function _drush_sitelist_find_in_list($one_source, &$target) { + $result = FALSE; + + foreach ($target as $key => $one_target) { + if(_drush_sitelist_check_site_records($one_source, $one_target)) { + $result = $one_target; + unset($target[$key]); + } + } + + return $result; +} + +function _drush_sitelist_check_site_records($source, $target) { + if ((array_key_exists('uri', $source)) && (array_key_exists('uri', $target)) && ($source['uri'] == $target['uri'])) { + return TRUE; + } + return FALSE; +} + +/** + * Initialize an alias record; called as soon as the alias + * record is loaded from its alias file, before it is stored + * in the cache. + * + * @param alias_record + * The alias record to be initialized; parameter is modified in place. + */ +function _drush_sitealias_initialize_alias_record(&$alias_record) { + // If there is a 'from-list' entry, then build a derived + // list based on the site list with the given name. + if (array_key_exists('from-list', $alias_record)) { + // danger of infinite loops... move to transient defaults? + $from_record = drush_sitealias_get_record($alias_record['from-list']); + $from_list = drush_sitealias_resolve_sitelist($from_record); + $derived_list = array(); + foreach ($from_list as $one_record) { + $derived_record = _drush_sitealias_derive_record($one_record, $alias_record); + $derived_list[] = drush_sitealias_alias_record_to_spec($derived_record); + } + + $alias_record = array(); + if (!empty($derived_list)) { + $alias_record['site-list'] = $derived_list; + } + } + // If there is a 'site-search-path' entry, then build + // a 'site-list' entry from all of the sites that can be + // found in the search path. + if (array_key_exists('site-search-path', $alias_record)) { + // TODO: Is there any point in merging the sites from + // the search path with any sites already listed in the + // 'site-list' entry? For now we'll just overwrite. + $search_path = $alias_record['site-search-path']; + if (!is_array($search_path)) { + $search_path = explode(',', $search_path); + } + $found_sites = _drush_sitealias_find_local_sites($search_path); + $alias_record['site-list'] = $found_sites; + // The 'unordered-list' flag indicates that the order of the items in the site list is not stable. + $alias_record['unordered-list'] = '1'; + // DEBUG: var_export($alias_record, FALSE); + } + if (array_key_exists('site-list', $alias_record)) { + if (!is_array($alias_record['site-list'])) { + $alias_record['site-list'] = explode(',', $alias_record['site-list']); + } + } + else { + if (isset($alias_record['root']) && !isset($alias_recort['uri'])) { + $alias_recort['uri'] = 'default'; + } + } +} + +/** + * Add "static" default values to the given alias record. The + * difference between a static default and a transient default is + * that static defaults -always- exist in the alias record, and + * they are cached, whereas transient defaults are only added + * if the given drush command explicitly adds them. + * + * @param alias_record + * An alias record with most values already filled in + */ +function _drush_sitealias_add_static_defaults(&$alias_record) { + // If there is a 'db-url' entry but not 'databases' entry, then we will + // build 'databases' from 'db-url' so that drush commands that use aliases + // can always count on using a uniform 'databases' array. + if (isset($alias_record['db-url']) && !isset($alias_record['databases'])) { + $alias_record['databases'] = drush_sitealias_convert_db_from_db_url($alias_record['db-url']); + } + + // Canonicalize paths. + if (!empty($alias_record['root'])) { + $alias_record['root'] = Path::canonicalize($alias_record['root']); + } + + // Adjustments for aliases to drupal instances (as opposed to aliases that are site lists) + if (array_key_exists('uri', $alias_record)) { + // Make sure that there is always a 'path-aliases' array + if (!array_key_exists('path-aliases', $alias_record)) { + $alias_record['path-aliases'] = array(); + } + // If there is a 'root' entry, then copy it to the '%root' path alias + if (isset($alias_record['root'])) { + $alias_record['path-aliases']['%root'] = $alias_record['root']; + } + } +} + +function _drush_sitealias_derive_record($from_record, $modifying_record) { + $result = $from_record; + + // If there is a 'remote-user' in the modifying record, copy it. + if (array_key_exists('remote-user', $modifying_record)) { + $result['remote-user'] = $from_record['remote_user']; + } + // If there is a 'remote-host', then: + // If it is empty, clear the remote host in the result record + // If it ends in '.', then prepend it to the remote host in the result record + // Otherwise, copy it to the result record + if (array_key_exists('remote-host', $modifying_record)) { + $remote_host_modifier = $modifying_record['remote-host']; + if(empty($remote_host_modifier)) { + unset($result['remote-host']); + unset($result['remote-user']); + } + elseif ($remote_host_modifier[strlen($remote_host_modifier)-1] == '.') { + $result['remote-host'] = $remote_host_modifier . $result['remote-host']; + } + else { + $result['remote-host'] = $remote_host_modifier; + } + } + // If there is a 'root', then: + // If it begins with '/', copy it to the result record + // Otherwise, append it to the result record + if (array_key_exists('root', $modifying_record)) { + $root_modifier = $modifying_record['root']; + if($root_modifier[0] == '/') { + $result['root'] = $root_modifier; + } + else { + $result['root'] = $result['root'] . '/' . $root_modifier; + } + } + // Poor man's realpath: take out the /../ with preg_replace. + // (realpath fails if the files in the path do not exist) + while(strpos($result['root'], '/../') !== FALSE) { + $result['root'] = preg_replace('/\w+\/\.\.\//', '', $result['root']); + } + + // TODO: Should we allow the uri to be transformed? + // I think that if the uri does not match, then you should + // always build the list by hand, and not rely on '_drush_sitealias_derive_record'. + + return $result; +} + +/** + * Convert from an alias record to a site specification + * + * @param alias_record + * The full alias record to convert + * + * @param with_db + * True if the site specification should include a ?db-url term + * + * @return string + * The site specification + */ +function drush_sitealias_alias_record_to_spec($alias_record, $with_db = false) { + $result = ''; + + // TODO: we should handle 'site-list' records too. + if (array_key_exists('site-list', $alias_record)) { + // TODO: we should actually expand the site list and recompose it + $result = implode(',', $alias_record['site-list']); + } + else { + // There should always be a uri + if (array_key_exists('uri', $alias_record)) { + $result = '#' . drush_sitealias_uri_to_site_dir($alias_record['uri'], drush_sitealias_get_root($alias_record)); + } + // There should always be a root + if (array_key_exists('root', $alias_record)) { + $result = $alias_record['root'] . $result; + } + if (array_key_exists('remote-host', $alias_record)) { + $result = drush_remote_host($alias_record) . $result; + } + + // Add the database info to the specification if desired + if ($with_db) { + // If db-url is not supplied, look it up from the remote + // or local site and add it to the site alias + if (!isset($alias_record['db-url'])) { + drush_sitealias_add_db_url($alias_record); + } + $result = $result . '?db-url=' . urlencode(is_array($alias_record['db-url']) ? $alias_record['db-url']['default'] : $alias_record['db-url']); + } + } + + return $result; +} + +/** + * Search for drupal installations in the search path. + * + * @param search_path + * An array of drupal root folders + * + * @return + * An array of site specifications (/path/to/root#sitename.com) + */ +function _drush_sitealias_find_local_sites($search_path) { + $result = array(); + foreach ($search_path as $a_drupal_root) { + $result = array_merge($result, _drush_find_local_sites_at_root($a_drupal_root)); + } + return $result; +} + +/** + * Return a list of all of the local sites at the specified drupal root. + */ +function _drush_find_local_sites_at_root($a_drupal_root = '', $search_depth = 1) { + $site_list = array(); + $base_path = (empty($a_drupal_root) ? drush_get_context('DRUSH_DRUPAL_ROOT') : $a_drupal_root ); + if (!empty($base_path)) { + if (drush_valid_root($base_path)) { + // If $a_drupal_root is in fact a valid drupal root, then return + // all of the sites found inside the 'sites' folder of this drupal instance. + $site_list = _drush_find_local_sites_in_sites_folder($base_path); + } + else { + $bootstrap_files = drush_scan_directory($base_path, '/' . basename(DRUSH_DRUPAL_SIGNATURE) . '/' , array('.', '..', 'CVS', 'examples'), 0, drush_get_option('search-depth', $search_depth) + 1, 'filename', 1); + foreach ($bootstrap_files as $one_bootstrap => $info) { + $includes_dir = dirname($one_bootstrap); + if (basename($includes_dir) == basename(dirname(DRUSH_DRUPAL_SIGNATURE))) { + $drupal_root = dirname($includes_dir); + $site_list = array_merge(_drush_find_local_sites_in_sites_folder($drupal_root), $site_list); + } + } + } + } + return $site_list; +} + +/** + * Return a list of all of the local sites at the specified 'sites' folder. + */ +function _drush_find_local_sites_in_sites_folder($a_drupal_root) { + $site_list = array(); + + // If anyone searches for sites at a given root, then + // make sure that alias files stored at this root + // directory are included in the alias search path + drush_sitealias_add_to_alias_path($a_drupal_root); + + $base_path = $a_drupal_root . '/sites'; + + // TODO: build a cache keyed off of $base_path (realpath($base_path)?), + // so that it is guarenteed that the lists returned will definitely be + // exactly the same should this routine be called twice with the same path. + + $files = drush_scan_directory($base_path, '/settings\.php/', array('.', '..', 'CVS', 'all'), 0, 1, 'filename', 1); + foreach ($files as $filename => $info) { + if ($info->basename == 'settings.php') { + // First we'll resolve the realpath of the settings.php file, + // so that we get the correct drupal root when symlinks are in use. + $real_sitedir = dirname(realpath($filename)); + $real_root = drush_locate_root($filename); + if ($real_root !== FALSE) { + $a_drupal_site = $real_root . '#' . basename($real_sitedir); + } + // If the symlink points to some folder outside of any drupal + // root, then we'll use the + else { + $uri = drush_sitealias_site_dir_from_filename($filename); + $a_drupal_site = $a_drupal_root . '#' . $uri; + } + // Add the site if it isn't already in the array + if (!in_array($a_drupal_site, $site_list)) { + $site_list[] = $a_drupal_site; + } + } + } + return $site_list; +} + +function drush_sitealias_create_sites_alias($a_drupal_root = '') { + $sites_list = _drush_find_local_sites_at_root($a_drupal_root); + _drush_sitealias_cache_alias('@sites', array('site-list' => $sites_list)); +} + +/** + * Add "transient" default values to the given alias record. The + * difference between a static default and a transient default is + * that static defaults -always- exist in the alias record, + * whereas transient defaults are only added if the given drush + * command explicitly calls this function. The other advantage + * of transient defaults is that it is possible to differentiate + * between a default value and an unspecified value, since the + * transient defaults are not added until requested. + * + * Since transient defaults are not cached, you should avoid doing + * expensive operations here. To be safe, drush commands should + * avoid calling this function more than once. + * + * @param alias_record + * An alias record with most values already filled in + */ +function _drush_sitealias_add_transient_defaults(&$alias_record) { + if (isset($alias_record['path-aliases'])) { + // Add the path to the drush folder to the path aliases as !drush + if (!array_key_exists('%drush', $alias_record['path-aliases'])) { + if (array_key_exists('%drush-script', $alias_record['path-aliases'])) { + $alias_record['path-aliases']['%drush'] = dirname($alias_record['path-aliases']['%drush-script']); + } + else { + $alias_record['path-aliases']['%drush'] = dirname(drush_find_drush()); + } + } + // Add the path to the site folder to the path aliases as !site + if (!array_key_exists('%site', $alias_record['path-aliases']) && array_key_exists('uri', $alias_record)) { + $alias_record['path-aliases']['%site'] = 'sites/' . drush_sitealias_uri_to_site_dir($alias_record['uri'], drush_sitealias_get_root($alias_record)) . '/'; + } + } +} + +/** + * Find the name of a local alias record that has the specified + * root and uri. + */ +function _drush_sitealias_find_local_alias_name($root, $uri) { + $result = ''; + $all_site_aliases =& drush_get_context('site-aliases'); + + foreach ($all_site_aliases as $alias_name => $alias_values) { + if (!array_key_exists('remote-host', $alias_values) && array_key_exists('root', $alias_values) && array_key_exists('uri', $alias_values) && ($alias_name != '@self')) { + if (($root == $alias_values['root']) && ($uri == $alias_values['uri'])) { + $result = $alias_name; + } + } + } + + return $result; +} + +/** + * If '$alias' is the name of a folder in the sites folder of the given drupal + * root, then build an alias record for it + * + * @param alias + * The name of the site in the 'sites' folder to convert + * @return array + * An alias record, or empty if none found. + */ +function _drush_sitealias_find_record_for_local_site($alias, $drupal_root = NULL) { + $alias_record = array(); + + // Clip off the leading '#' if it is there + if (substr($alias,0,1) == '#') { + $alias = substr($alias,1); + } + + if (!isset($drupal_root)) { + $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT'); + } + + if (!empty($drupal_root)) { + $alias_dir = drush_sitealias_uri_to_site_dir($alias, $drupal_root); + $site_settings_file = $drupal_root . '/sites/' . $alias_dir . '/settings.php'; + $alias_record = drush_sitealias_build_record_from_settings_file($site_settings_file, $alias, $drupal_root); + } + + return $alias_record; +} + +function drush_sitealias_build_record_from_settings_file($site_settings_file, $alias = null, $drupal_root = null) { + $alias_record = array(); + + if (file_exists($site_settings_file)) { + if (!isset($drupal_root)) { + $drupal_root = drush_locate_root($site_settings_file); + } + + $alias_record['root'] = $drupal_root; + if (isset($alias)) { + $alias_record['uri'] = $alias; + } + else { + $alias_record['uri'] = _drush_sitealias_site_dir_to_uri(drush_sitealias_site_dir_from_filename($site_settings_file)); + } + } + + return $alias_record; +} + +/** + * Pull the site directory from the path to settings.php + * + * @param site_settings_file + * path to settings.php + * + * @return string + * the site directory component of the path to settings.php + */ +function drush_sitealias_site_dir_from_filename($site_settings_file) { + return basename(dirname($site_settings_file)); +} + +/** + * Convert from a URI to a site directory. + * + * @param uri + * A uri, such as http://domain.com:8080/drupal + * @return string + * A directory, such as domain.com.8080.drupal + */ +function drush_sitealias_uri_to_site_dir($uri, $site_root = NULL) { + $uri = str_replace('http://', '', $uri); + $uri = str_replace('https://', '', $uri); + if (drush_is_windows()) { + // Handle absolute paths on windows + $uri = str_replace(array(':/', ':\\'), array('.', '.'), $uri); + } + + $hostname = str_replace(array('/', ':', '\\'), array('.', '.', '.'), $uri); + + // Check sites.php mappings + $site_dir = drush_site_dir_lookup_from_hostname($hostname, $site_root); + + return $site_dir ? $site_dir : $hostname; +} + +/** + * Convert from an old-style database URL to an array of database settings. + * + * @param db_url + * A Drupal 6 db url string to convert, or an array with a 'default' element. + * @return array + * An array of database values containing only the 'default' element of + * the db url. If the parse fails the array is empty. + */ +function drush_convert_db_from_db_url($db_url) { + $db_spec = array(); + + if (is_array($db_url)) { + $db_url_default = $db_url['default']; + } + else { + $db_url_default = $db_url; + } + + // If it's a sqlite database, pick the database path and we're done. + if (strpos($db_url_default, 'sqlite://') === 0) { + $db_spec = array( + 'driver' => 'sqlite', + 'database' => substr($db_url_default, strlen('sqlite://')), + ); + } + else { + $url = parse_url($db_url_default); + if ($url) { + // Fill in defaults to prevent notices. + $url += array( + 'scheme' => NULL, + 'user' => NULL, + 'pass' => NULL, + 'host' => NULL, + 'port' => NULL, + 'path' => NULL, + ); + $url = (object)array_map('urldecode', $url); + $db_spec = array( + 'driver' => $url->scheme == 'mysqli' && extension_loaded('mysql') ? 'mysql' : $url->scheme, + 'username' => $url->user, + 'password' => $url->pass, + 'host' => $url->host, + 'port' => $url->port, + 'database' => ltrim($url->path, '/'), + ); + } + } + + return $db_spec; +} + +function drush_convert_db_url($db_url) { + if (extension_loaded('mysql') && !extension_loaded('mysqli')) { + return preg_replace('#^mysqli://#', 'mysql://', $db_url);; + } + return preg_replace('#^mysql://#', 'mysqli://', $db_url); +} + +/** + * Convert from an old-style database URL to an array of database settings + * + * @param db_url + * A Drupal 6 db-url string to convert, or an array with multiple db-urls. + * @return array + * An array of database values. + */ +function drush_sitealias_convert_db_from_db_url($db_url) { + $result = array(); + + if (!is_array($db_url)) { + $result = array('default' => array('default' => drush_convert_db_from_db_url($db_url))); + } + else { + foreach ($db_url as $one_name => $one_db_url) { + $result[$one_name] = array('default' => drush_convert_db_from_db_url($one_db_url)); + } + } + + return $result; +} + +/** + * Utility function used by drush_get_alias; keys that start with + * '%' or '!' are path aliases, the rest are entries in the alias record. + */ +function _drush_sitealias_set_record_element(&$alias_record, $key, $value) { + if ((substr($key,0,1) == '%') || (substr($key,0,1) == '!')) { + $alias_record['path-aliases'][$key] = $value; + } + elseif (!empty($key)) { + $alias_record[$key] = $value; + } +} + +/** + * Looks up the specified alias record and calls through to + * drush_sitealias_set_alias_context, below. + * + * @param alias + * The name of the alias record + * @param prefix + * The prefix value to afix to the beginning of every + * key set. + * @return boolean + * TRUE is an alias was found and processed. + */ +function _drush_sitealias_set_context_by_name($alias, $prefix = '') { + if ($alias) { + $site_alias_settings = drush_sitealias_get_record($alias); + if (!empty($site_alias_settings)) { + drush_sitealias_set_alias_context($site_alias_settings, $prefix); + drush_sitealias_cache_alias_by_path($site_alias_settings); + if (empty($prefix)) { + + // Create an alias '@self' + // Allow 'uri' from the commandline to override + $drush_uri = drush_get_option(array('uri', 'l'), FALSE); + if ($drush_uri) { + $site_alias_settings['uri'] = $drush_uri; + } + + _drush_sitealias_cache_alias('@self', $site_alias_settings); + // Change the selected site to match the new --root and --uri, if any were set. + _drush_preflight_root_uri(); + } + return $site_alias_settings; + } + } + return array(); +} + +/** + * Given an alias record, overwrite its values with options + * from the command line and other drush contexts as specified + * by the provided prefix. For example, if the prefix is 'source-', + * then any option 'source-foo' will set the value 'foo' in the + * alias record. + */ +function drush_sitealias_overlay_options($site_alias_record, $prefix) { + return array_merge($site_alias_record, drush_get_merged_prefixed_options($prefix)); +} + +/** + * First return an option set via drush_sitealias_overlay_options, if + * any, then fall back on "%" . $option from the path aliases. + */ +function drush_sitealias_get_path_option($site_alias_record, $option, $default = NULL) { + if (isset($site_alias_record) && array_key_exists($option, $site_alias_record)) { + return $site_alias_record[$option]; + } + if (isset($site_alias_record) && array_key_exists('path-aliases', $site_alias_record) && array_key_exists("%$option", $site_alias_record['path-aliases'])) { + return $site_alias_record['path-aliases']["%$option"]; + } + else { + return drush_get_option($option, $default); + } +} + +/** + * Given a site alias record, copy selected fields from it + * into the drush 'alias' context. The 'alias' context has + * lower precedence than the 'cli' context, so values + * set by an alias record can be overridden by command-line + * parameters. + * + * @param site_alias_settings + * An alias record + * @param prefix + * The prefix value to affix to the beginning of every + * key set. For example, if this function is called once with + * 'source-' and again with 'destination-' prefixes, then the + * source database records will be stored in 'source-databases', + * and the destination database records will be in + * 'destination-databases'. + */ +function drush_sitealias_set_alias_context($site_alias_settings, $prefix = '') { + $options = drush_get_context('alias'); + + // There are some items that we should just skip + $skip_list = drush_get_special_keys(); + // If 'php-options' are set in the alias, then we will force drush + // to redispatch via the remote dispatch mechanism even if the target is localhost. + if ((array_key_exists('php-options', $site_alias_settings) || array_key_exists('php', $site_alias_settings)) && !drush_get_context('DRUSH_BACKEND', FALSE)) { + if (!array_key_exists('remote-host', $site_alias_settings)) { + $site_alias_settings['remote-host'] = 'localhost'; + } + } + // If 'php-options' are not set in the alias, then skip 'remote-host' + // and 'remote-user' if 'remote-host' is actually the local machine. + // This prevents drush from using the remote dispatch mechanism (the command + // is just run directly on the local machine, bootstrapping to the specified alias) + elseif (array_key_exists('remote-host', $site_alias_settings) && drush_is_local_host($site_alias_settings['remote-host'])) { + $skip_list[] = 'remote-host'; + $skip_list[] = 'remote-user'; + } + // If prefix is set, then copy from the 'prefix-' version + // of the drush special keys ('command-specific', 'path-aliases') + // into the ordinary version. This will allow us to set + // 'source-command-specific' options that will only apply when + // the alias is used as the source option for rsync or sql-sync. + if (!empty($prefix)) { + $special_contexts = drush_get_special_keys(); + foreach ($special_contexts as $option_name) { + if (array_key_exists($prefix . $option_name, $site_alias_settings)) { + $site_alias_settings[$option_name] = array_key_exists($option_name, $site_alias_settings) ? array_merge($site_alias_settings[$option_name], $site_alias_settings[$prefix . $option_name]) : $site_alias_settings[$prefix . $option_name]; + } + } + } + // Transfer all options from the site alias to the drush options + // in the 'alias' context. + foreach ($site_alias_settings as $key => $value) { + // Special handling for path aliases: + if ($key == "path-aliases") { + $path_aliases = $value; + foreach (array('%drush-script', '%dump', '%dump-dir', '%include') as $path_key) { + if (array_key_exists($path_key, $path_aliases)) { + // Evaluate the path value, and substitute any path references found. + // ex: '%dump-dir' => '%root/dumps' will store sql-dumps in the folder + // 'dumps' in the Drupal root folder for the site. + $evaluated_path = str_replace(array_keys($path_aliases), array_values($path_aliases), $path_aliases[$path_key]); + $options[$prefix . substr($path_key, 1)] = $evaluated_path; + } + } + } + // Special handling for command-specific + elseif ($key == "command-specific") { + $options[$key] = $value; + } + elseif (!in_array($key, $skip_list)) { + $options[$prefix . $key] = $value; + } + } + drush_set_config_options('alias', $options); +} + +/** + * Call prior to drush_sitealias_evaluate_path to insure + * that any site-specific aliases associated with any + * local site in $path are defined. + */ +function _drush_sitealias_preflight_path($path) { + $alias = NULL; + // Parse site aliases if there is a colon in the path + // We allow: + // @alias:/path + // machine.domain.com:/path + // machine:/path + // Note that paths in the form "c:/path" are converted to + // "/cygdrive/c/path" later; we do not want them to confuse + // us here, so we skip paths that start with a single character + // before the colon if we are running on Windows. Single-character + // machine names are allowed in Linux only. + $colon_pos = strpos($path, ':'); + if ($colon_pos > (drush_is_windows("LOCAL") ? 1 : 0)) { + $alias = substr($path, 0, $colon_pos); + $path = substr($path, $colon_pos + 1); + $site_alias_settings = drush_sitealias_get_record($alias); + if (empty($site_alias_settings) && (substr($path,0,1) == '@')) { + return NULL; + } + $machine = $alias; + } + else { + $machine = ''; + // if the path is a site alias or a local site... + $site_alias_settings = drush_sitealias_get_record($path); + if (empty($site_alias_settings) && (substr($path,0,1) == '@')) { + return NULL; + } + if (!empty($site_alias_settings) || drush_is_local_host($path)) { + $alias = $path; + $path = ''; + } + } + return array('alias' => $alias, 'path' => $path, 'machine' => $machine); +} + +/** + * Given a properly-escaped options string, replace any occurance of + * %files and so on embedded inside it with its corresponding path. + */ +function drush_sitealias_evaluate_paths_in_options($option_string) { + $path_aliases = _core_path_aliases(); + return str_replace(array_keys($path_aliases), array_values($path_aliases), $option_string); +} + +/** + * Evaluate a path from its shorthand form to a literal path + * usable by rsync. + * + * A path is "machine:/path" or "machine:path" or "/path" or "path". + * 'machine' might instead be an alias record, or the name + * of a site in the 'sites' folder. 'path' might be (or contain) + * '%root' or some other path alias. This function will examine + * all components of the path and evaluate them as necessary to + * come to the final path. + * + * @param path + * The path to evaluate + * @param additional_options + * An array of options that overrides whatever was passed in on + * the command line (like the 'process' context, but only for + * the scope of this one call). + * @param local_only + * If TRUE, force an error if the provided path points to a remote + * machine. + * @param os + * This should be the local system os, unless evaluate path is + * being called for rsync, in which case it should be "CWRSYNC" + * if cwrsync is being used, or "rsync" to automatically select + * between "LOCAL" and "CWRSYNC" based on the platform. + * @return + * The site record for the machine specified in the path, if any, + * with the path to pass to rsync (including the machine specifier) + * in the 'evaluated-path' item. + */ +function drush_sitealias_evaluate_path($path, &$additional_options, $local_only = FALSE, $os = NULL, $command_specific_prefix = '') { + $site_alias_settings = array(); + $path_aliases = array(); + $remote_user = ''; + + $preflight = _drush_sitealias_preflight_path($path); + if (!isset($preflight)) { + return NULL; + } + + $alias = $preflight['alias']; + $path = $preflight['path']; + $machine = $preflight['machine']; + + if (isset($alias)) { + // Note that the alias settings may have an 'os' component, but we do + // not want to use it here. The paths passed to rsync should always be + // escaped per the LOCAL rules, without regard to the remote platform type. + $site_alias_settings = drush_sitealias_get_record($alias); + if (!empty($command_specific_prefix)) { + drush_command_set_command_specific_options($command_specific_prefix); + drush_sitealias_command_default_options($site_alias_settings, $command_specific_prefix); + } + } + + if (!empty($site_alias_settings)) { + if ($local_only && array_key_exists('remote-host', $site_alias_settings)) { + return drush_set_error('DRUSH_REMOTE_SITE_IN_LOCAL_CONTEXT', dt("A remote site alias was used in a context where only a local alias is appropriate.")); + } + + // Apply any options from this alias that might affect our rsync + drush_sitealias_set_alias_context($site_alias_settings); + + // Use 'remote-host' from settings if available; otherwise site is local + if (drush_sitealias_is_remote_site($site_alias_settings)) { + $machine = drush_remote_host($site_alias_settings); + } + else { + $machine = ''; + } + } + else { + // Strip the machine portion of the path if the + // alias points to the local machine. + if (drush_is_local_host($machine)) { + $machine = ''; + } + else { + $machine = "$remote_user$machine"; + } + } + + // TOD: The code below is a little rube-goldberg-ish, and needs to be + // reworked. core-rsync will call this function twice: once to + // evaluate the destination, and then again to evaluate the source. Things + // get odd with --exclude-paths, especially in conjunction with command-specific + // and the --exclude-files option. @see testCommandSpecific() + + // If the --exclude-other-sites option is specified, then + // convert that into --include-paths='%site' and --exclude-sites. + if (drush_get_option_override($additional_options, 'exclude-other-sites', FALSE) && !drush_get_context('exclude-other-sites-processed', FALSE)) { + $include_path_option = drush_get_option_override($additional_options, 'include-paths', ''); + $additional_options['include-paths'] = '%site'; + if (!empty($include_path_option)) { + // We use PATH_SEPARATOR here because we are later going to explicitly explode() this variable using PATH_SEPARATOR. + $additional_options['include-paths'] .= PATH_SEPARATOR . $include_path_option; + } + $additional_options['exclude-sites'] = TRUE; + drush_set_context('exclude-other-sites-processed', TRUE); + } + else { + unset($additional_options['include-paths']); + } + // If the --exclude-files option is specified, then + // convert that into --exclude-paths='%files'. + if (drush_get_option_override($additional_options, 'exclude-files', FALSE) && !drush_get_option_override($additional_options, 'exclude-files-processed', FALSE, 'process')) { + $exclude_path_option = drush_get_option_override($additional_options, 'exclude-paths', ''); + $additional_options['exclude-paths'] = '%files'; + if (!empty($exclude_path_option)) { + // We use PATH_SEPARATOR here because we are later going to explicitly explode() this variable using PATH_SEPARATOR. + $additional_options['exclude-paths'] .= PATH_SEPARATOR . $exclude_path_option; + } + $additional_options['exclude-files-processed'] = TRUE; + } + else { + unset($additional_options['exclude-paths']); + } + + // If there was no site specification given, and the + // machine is local, then try to look + // up an alias record for the default drush site. + if (empty($site_alias_settings) && empty($machine)) { + $drush_uri = drush_get_context('DRUSH_SELECTED_URI', 'default'); + $site_alias_settings = drush_sitealias_get_record($drush_uri); + } + + // Always add transient defaults + _drush_sitealias_add_transient_defaults($site_alias_settings); + + // The $resolve_path variable is used by drush_sitealias_resolve_path_references + // to test to see if there are any path references such as %site or %files + // in it, so that resolution is only done if the path alias is referenced. + // Therefore, we can concatenate without worrying too much about the structure of + // this variable's contents. + $include_path = drush_get_option_override($additional_options, 'include-paths', ''); + $exclude_path = drush_get_option_override($additional_options, 'exclude-paths', ''); + if (is_array($include_path)) { + $include_path = implode('/', $include_path); + } + if (is_array($exclude_path)) { + $include_path = implode('/', $exclude_path); + } + $resolve_path = "$path/$include_path/$exclude_path"; + // Resolve path aliases such as %files, if any exist in the path + if (!empty($resolve_path)) { + drush_sitealias_resolve_path_references($site_alias_settings, $resolve_path); + } + + if (array_key_exists('path-aliases', $site_alias_settings)) { + $path_aliases = $site_alias_settings['path-aliases']; + } + + // Get the 'root' setting from the alias; if it does not + // exist, then get the root from the bootstrapped site. + if (array_key_exists('root', $site_alias_settings)) { + $drupal_root = $site_alias_settings['root']; + } + elseif (!drush_sitealias_is_remote_site($site_alias_settings)) { + drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_SITE); + $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT'); + } + if (empty($drupal_root)) { + $drupal_root = ''; + } + else { + // Add a slash to the end of the drupal root, as below. + $drupal_root = drush_trim_path($drupal_root) . "/"; + } + $full_path_aliases = $path_aliases; + foreach ($full_path_aliases as $key => $value) { + // Expand all relative path aliases to be based off of the Drupal root + if (!drush_is_absolute_path($value, "LOCAL") && ($key != '%root')) { + $full_path_aliases[$key] = $drupal_root . $value; + } + // We do not want slashes on the end of our path aliases. + $full_path_aliases[$key] = drush_trim_path($full_path_aliases[$key]); + } + + // Fill in path aliases in the path, the include path and the exclude path. + $path = str_replace(array_keys($full_path_aliases), array_values($full_path_aliases), $path); + if (!empty($include_path)) { + drush_set_option('include-paths', str_replace(array_keys($path_aliases), array_values($path_aliases), $include_path)); + } + if (!empty($exclude_path)) { + drush_set_option('exclude-paths', str_replace(array_keys($path_aliases), array_values($path_aliases), $exclude_path)); + } + // Next make the rsync path, which includes the machine + // and path components together. + // First make empty paths or relative paths start from the drupal root. + if (empty($path) || (!drush_is_absolute_path($path, "LOCAL"))) { + $path = $drupal_root . $path; + } + // When calculating a path for use with rsync, we must correct + // absolute paths in the form c:\path when cwrsync is in use. + $path = drush_correct_absolute_path_for_exec($path, $os); + + // If there is a $machine component, to the path, then + // add it to the beginning + $evaluated_path = drush_escapeshellarg($path, $os); + if (!empty($machine)) { + $evaluated_path = $machine . ':' . $evaluated_path; + } + + // + // Add our result paths: + // + // evaluated-path: machine:/path + // server-component: machine + // path-component: :/path + // path: /path + // user-path: path (as specified in input parameter) + // + $site_alias_settings['evaluated-path'] = $evaluated_path; + if (!empty($machine)) { + $site_alias_settings['server-component'] = $machine; + } + $site_alias_settings['path-component'] = (!empty($path) ? ':' . $path : ''); + $site_alias_settings['path'] = $path; + $site_alias_settings['user-path'] = $preflight['path']; + + return $site_alias_settings; +} + +/** + * Option keys used for site selection. + */ +function drush_sitealias_site_selection_keys() { + return array('remote-host', 'remote-user', 'ssh-options', '#name', 'os'); +} + + +function sitealias_find_local_drupal_root($site_list) { + $drupal_root = NULL; + + foreach ($site_list as $site) { + if (($drupal_root == NULL) && (array_key_exists('root', $site) && !array_key_exists('remote-host', $site))) { + $drupal_root = $site['root']; + } + } + + return $drupal_root; +} + + +/** + * Helper function to obtain the keys' names that need special handling in certain + * cases. + * @return + * A non-associative array containing the needed keys' names. + */ +function drush_get_special_keys() { + $special_keys = array( + 'command-specific', + 'site-aliases', + ); + return $special_keys; +} + +/** + * Read the tmp file where the persistent site setting is stored. + * + * @return string + * A valid site specification. + */ +function drush_sitealias_site_get() { + if (($filename = drush_sitealias_get_envar_filename()) && file_exists($filename)) { + $site = file_get_contents($filename); + return $site; + } + else { + return FALSE; + } +} + +/** + * Un-set the currently use'd site alias. + */ +function drush_sitealias_site_clear() { + if ($filename = drush_sitealias_get_envar_filename()) { + return drush_delete_dir($filename); + } + return FALSE; +} + +/** + * Returns the filename for the file that stores the DRUPAL_SITE variable. + * + * @param string $filename_prefix + * An arbitrary string to prefix the filename with. + * + * @return string|false + * Returns the full path to temp file if possible, or FALSE if not. + */ +function drush_sitealias_get_envar_filename($filename_prefix = 'drush-drupal-site-') { + $shell_pid = getenv('DRUSH_SHELL_PID'); + if (!$shell_pid && function_exists('posix_getppid')) { + $shell_pid = posix_getppid(); + } + if (!$shell_pid) { + return FALSE; + } + + $tmp = getenv('TMPDIR') ? getenv('TMPDIR') : '/tmp'; + $username = drush_get_username(); + + return "{$tmp}/drush-env-{$username}/{$filename_prefix}" . $shell_pid; +} + +/** + * Cache the specified alias in the alias path cache. The + * alias path cache creates a lookup from the site folder + * (/path/to/drupal/sites/default) to the provided alias record. + * + * Only the name of the alias and the path to the file it + * is stored in is cached; when it is retrieved, it is + * loaded directly from the correct file. + */ +function drush_sitealias_cache_alias_by_path($alias_record) { + if (!isset($alias_record['remote-host']) && isset($alias_record['root']) && isset($alias_record['uri']) && isset($alias_record['#name']) && isset($alias_record['#file'])) { + $path = drush_sitealias_local_site_path($alias_record); + if ($path) { + $cid = drush_get_cid('alias-path-', array(), array($path)); + $alias_path_data = array( + '#name' => $alias_record['#name'], + '#file' => $alias_record['#file'], + ); + drush_cache_set($cid, $alias_path_data); + } + } +} + +/** + * Look for a defined alias that points to the specified + * site directory. The cache is tested first; if nothing + * is cached, then an exhaustive search is done for the + * specified site. If the exhaustive search returns a + * match, then it is cached. + * + * @param $path + * /path/to/drupal/sites/default + * @return + * An alias record for the provided path + */ +function drush_sitealias_lookup_alias_by_path($path, $allow_best_match=FALSE) { + $result = drush_sitealias_quick_lookup_cached_alias_by_path($path); + $fallback = array(); + if (empty($result)) { + $aliases = _drush_sitealias_find_and_load_all_aliases(); + foreach ($aliases as $name => $alias_record) { + if (!isset($alias_record['remote-host']) && isset($alias_record['root']) && isset($alias_record['uri']) && isset($alias_record['#name']) && isset($alias_record['#file'])) { + if ($path == drush_sitealias_local_site_path($alias_record)) { + $result = $alias_record; + break; + } + if (substr($path, 0, strlen($alias_record['root'])) == $alias_record['root']) { + $fallback = $alias_record; + } + } + } + } + if (empty($result) && $allow_best_match) { + $result = $fallback; + } + if (!empty($result)) { + _drush_sitealias_add_inherited_values_to_record($result); + drush_sitealias_cache_alias_by_path($result); + } + return $result; +} + +/** + * Look for a cached alias that points to the specified + * site directory. Nothing is returned if there is no + * matching cached alias. + * + * @param $path + * /path/to/drupal/sites/default + * @return + * An alias record for the provided path + */ +function drush_sitealias_quick_lookup_cached_alias_by_path($path) { + $alias_record = array(); + $cid = drush_get_cid('alias-path-', array(), array($path)); + $alias_path_cache = drush_cache_get($cid); + if (isset($alias_path_cache->data)) { + $alias_name = $alias_path_cache->data['#name']; + $alias_file = $alias_path_cache->data['#file']; + + $alias_record = _drush_sitealias_find_and_load_alias_from_file($alias_name, array($alias_file)); + _drush_sitealias_add_inherited_values_to_record($alias_record); + $alias_record['#name'] = $alias_name; + } + return $alias_record; +} + +/** + * Return the site root, if there is one in the record. + */ +function drush_sitealias_get_root($alias_record) { + return array_key_exists('root', $alias_record) ? $alias_record['root'] : NULL; +} + +/** + * Decide on which side to run a core-rsync. + * + * @param $source + * @param $destination + * @param $runner Where to run the rsync operation: 'destination', 'source', + * 'auto' ('destination' if both are remote, otherwise '@self') or FALSE (@self) + * @return mixed + */ +function drush_get_runner($source, $destination, $runner = FALSE) { + if (is_string($source)) { + $source = drush_sitealias_get_record($site); + } + if (is_string($destination)) { + $destination = drush_sitealias_get_record($destination); + } + + // If both sites are remote, and --runner=auto, then we'll use the destination site. + if (drush_sitealias_is_remote_site($source) && drush_sitealias_is_remote_site($destination)) { + if ($runner == 'auto') { + $runner = 'destination'; + } + } + + // If the user explicitly requests a remote site, then return the selected one. + if ($runner == 'destination') { + return "@" . $destination['#name']; + } + if ($runner == 'source') { + return "@" . $source['#name']; + } + + // Default to running rsync locally. When in doubt, local is best, because + // we can always resolve aliases here. + return '@self'; +} diff --git a/vendor/drush/drush/includes/startup.inc b/vendor/drush/drush/includes/startup.inc new file mode 100644 index 0000000000..5daedead92 --- /dev/null +++ b/vendor/drush/drush/includes/startup.inc @@ -0,0 +1,466 @@ + 'key']. We add a default + // value of [1 => 'value'] to cover this case. If + // explode returns two items, the default value is ignored. + list($key, $value) = explode('=', $item, 2) + array(1 => ''); + $env[$key] = $value; + } + } + + return $env; +} + +/** + * Checks the provided location and return the appropriate + * Drush wrapper or Drush launcher script, if found. + * + * If the provided location looks like it might be a web + * root (i.e., it contains an index.php), then we will search + * in a number of locations in the general vicinity of the + * web root for a Drush executable. + * + * For other locations, we will look only in that specific + * directory, or in vendor/bin. + */ +function find_wrapper_or_launcher($location) { + if (file_exists($location. DIRECTORY_SEPARATOR. 'index.php')) { + return find_wrapper_or_launcher_in_vicinity($location); + } + return find_wrapper_or_launcher_in_specific_locations($location, ["", 'vendor'. DIRECTORY_SEPARATOR. 'bin']); +} + +/** + * We look for a "Drush wrapper" script that might + * be stored in the root of a site. If there is + * no wrapper script, then we look for the + * drush.launcher script in vendor/bin. We try just a + * few of the most common locations; if the user relocates + * their vendor directory anywhere else, then they must + * use a wrapper script to locate it. See the comment in + * 'examples/drush' for details. + */ +function find_wrapper_or_launcher_in_vicinity($location) { + $sep = DIRECTORY_SEPARATOR; + $drush_locations = [ + "", + "vendor{$sep}bin/", + "..{$sep}vendor{$sep}bin{$sep}", + "sites{$sep}all{$sep}vendor{$sep}bin{$sep}", + "sites{$sep}all{$sep}vendor{$sep}drush{$sep}drush{$sep}", + "sites{$sep}all{$sep}drush{$sep}drush{$sep}", + "drush{$sep}drush{$sep}", + ]; + + return find_wrapper_or_launcher_in_specific_locations($location, $drush_locations); +} + +function find_wrapper_or_launcher_in_specific_locations($location, $drush_locations) { + $sep = DIRECTORY_SEPARATOR; + foreach ($drush_locations as $d) { + $found_script = find_wrapper_or_launcher_at_location("$location$sep$d"); + if (!empty($found_script)) { + return $found_script; + } + } + return ""; +} + +/** + * We are somewhat "loose" about whether we are looking + * for "drush" or "drush.launcher", because in old versions + * of Drush, the "drush launcher" was named "drush". + * Otherwise, there wouldn't be any point in looking for + * "drush.launcher" at the root, or "drush" in a vendor directory. + * We also allow users to rename their drush wrapper to + * 'drush.wrapper' to avoid conflicting with a directory named + * 'drush' at the site root. + */ +function find_wrapper_or_launcher_at_location($location) { + $sep = DIRECTORY_SEPARATOR; + // Sanity-check: empty $location means that we should search + // at the cwd, not at the root of the filesystem. + if (empty($location)) { + $location = "."; + } + foreach (array('.launcher', '.wrapper', '') as $suffix) { + $check_location = "$location{$sep}drush$suffix"; + if (is_file($check_location)) { + return $check_location; + } + } + return ""; +} + +/** + * Determine whether current OS is a Windows variant. + */ +function drush_is_windows($os = NULL) { + // The _drush_get_os() function may not be available, so resolve "LOCAL" + if (!$os || $os == "LOCAL") { + $os = PHP_OS; + } + return strtoupper(substr($os, 0, 3)) === 'WIN'; +} + +function drush_escapeshellarg($arg, $os = NULL, $raw = FALSE) { + // Short-circuit escaping for simple params (keep stuff readable) + if (preg_match('|^[a-zA-Z0-9.:/_-]*$|', $arg)) { + return $arg; + } + elseif (drush_is_windows($os)) { + return _drush_escapeshellarg_windows($arg, $raw); + } + else { + return _drush_escapeshellarg_linux($arg, $raw); + } +} + +/** + * Linux version of escapeshellarg(). + * + * This is intended to work the same way that escapeshellarg() does on + * Linux. If we need to escape a string that will be used remotely on + * a Linux system, then we need our own implementation of escapeshellarg, + * because the Windows version behaves differently. + */ +function _drush_escapeshellarg_linux($arg, $raw = FALSE) { + // For single quotes existing in the string, we will "exit" + // single-quote mode, add a \' and then "re-enter" + // single-quote mode. The result of this is that + // 'quote' becomes '\''quote'\'' + $arg = preg_replace('/\'/', '\'\\\'\'', $arg); + + // Replace "\t", "\n", "\r", "\0", "\x0B" with a whitespace. + // Note that this replacement makes Drush's escapeshellarg work differently + // than the built-in escapeshellarg in PHP on Linux, as these characters + // usually are NOT replaced. However, this was done deliberately to be more + // conservative when running _drush_escapeshellarg_linux on Windows + // (this can happen when generating a command to run on a remote Linux server.) + $arg = str_replace(array("\t", "\n", "\r", "\0", "\x0B"), ' ', $arg); + + // Only wrap with quotes when needed. + if(!$raw) { + // Add surrounding quotes. + $arg = "'" . $arg . "'"; + } + + return $arg; +} + +/** + * Windows version of escapeshellarg(). + */ +function _drush_escapeshellarg_windows($arg, $raw = FALSE) { + // Double up existing backslashes + $arg = preg_replace('/\\\/', '\\\\\\\\', $arg); + + // Double up double quotes + $arg = preg_replace('/"/', '""', $arg); + + // Double up percents. + // $arg = preg_replace('/%/', '%%', $arg); + + // Only wrap with quotes when needed. + if(!$raw) { + // Add surrounding quotes. + $arg = '"' . $arg . '"'; + } + + return $arg; +} + +/** + * drush_startup is called once, by the Drush "finder" + * script -- the "drush" script at the Drush root. + * It finds the correct Drush "wrapper" or "launcher" + * script to use, and executes it with process replacement. + */ +function drush_startup($argv) { + $sep = DIRECTORY_SEPARATOR; + $found_script = ""; + $cwd = getcwd(); + $home = getenv("HOME"); + $use_dir = "$home{$sep}.drush{$sep}use"; + + // Get the arguments for the command. Shift off argv[0], + // which contains the name of this script. + $arguments = $argv; + array_shift($arguments); + + // We need to do at least a partial parsing of the options, + // so that we can find --root / -r and so on. + $VERBOSE=FALSE; + $DEBUG=FALSE; + $ROOT=FALSE; + $COMMAND=FALSE; + $ALIAS=FALSE; + $VAR=FALSE; + + foreach ($arguments as $arg) { + // If a variable to set was indicated on the + // previous iteration, then set the value of + // the named variable (e.g. "ROOT") to "$arg". + if ($VAR) { + $$VAR = "$arg"; + $VAR = FALSE; + } + else { + switch ($arg) { + case "-r": + $VAR = "ROOT"; + break; + + case "-dv": + case "-vd": + case "--debug": + case "-d": + $DEBUG = TRUE; + break; + + case "-dv": + case "-vd": + case "--verbose": + case "-v": + $VERBOSE = TRUE; + break; + } + if (!$COMMAND && !$ALIAS && ($arg[0] == '@')) { + $ALIAS = $arg; + } + elseif (!$COMMAND && ($arg[0] != '-')) { + $COMMAND = $arg; + } + if (substr($arg, 0, 7) == "--root=") { + $ROOT = substr($arg, 7); + } + } + } + + $NONE=($ALIAS == "@none"); + + // If we have found the site-local Drush script, then + // do not search for it again; use the environment value + // we set last time. + $found_script = getenv('DRUSH_FINDER_SCRIPT'); + + // If the @none alias is used, then we skip the Drush wrapper, + // and call the Drush launcher directly. + // + // In this instance, we are assuming that the 'drush' that is being + // called is: + // + // a) The global 'drush', or + // b) A site-local 'drush' in a vendor/bin directory. + // + // In either event, the appropriate 'drush.launcher' should be right next + // to this script (stored in the same directory). + if (empty($found_script) && $NONE) { + if (is_file(dirname(__DIR__) . "{$sep}drush.launcher")) { + $found_script = dirname(__DIR__) . "{$sep}drush.launcher"; + } + else { + fwrite(STDERR, "Could not find drush.launcher in " . dirname(__DIR__) . ". Check your installation.\n"); + exit(1); + } + } + + // Check for a root option: + // + // drush --root=/path + // + // If the site root is specified via a commandline option, then we + // should always use the Drush stored at this root, if there is one. + // We will first check for a "wrapper" script at the root, and then + // we will look for a "launcher" script in vendor/bin. + if (empty($found_script) && !empty($ROOT)) { + $found_script = find_wrapper_or_launcher($ROOT); + if (!empty($found_script)) { + chdir($ROOT); + } + } + + // If there is a .drush-use file, then its contents will + // contain the path to the Drush to use. + if (empty($found_script)) { + if (is_file(".drush-use")) { + $found_script = trim(file_get_contents(".drush-use")); + } + } + + // Look for a 'drush' wrapper or launcher at the cwd, + // and in each of the directories above the cwd. If + // we find one, use it. + if (empty($found_script)) { + $c = getcwd(); + // Windows can give us lots of different strings to represent the root + // directory as it often includes the drive letter. If we get the same + // result from dirname() twice in a row, then we know we're at the root. + $last = ''; + while (!empty($c) && ($c != $last)) { + $found_script = find_wrapper_or_launcher($c); + if ($found_script) { + chdir($c); + break; + } + $last = $c; + $c = dirname($c); + } + } + + if (!empty($found_script)) { + $found_script = realpath($found_script); + + // Guard against errors: if we have found a "drush" script + // (that is, theoretically a drush wrapper script), and + // there is a "drush.launcher" script in the same directory, + // then we will skip the "drush" script and use the drush launcher + // instead. This is because drush "wrapper" scripts should + // only ever exist at the root of a site, and there should + // never be a drush "launcher" at the root of a site. + // Therefore, if we find a "drush.launcher" next to a script + // called "drush", we have probably found a Drush install directory, + // not a site root. Adjust appropriately. Note that this + // also catches the case where a drush "finder" script finds itself. + if (is_file(dirname($found_script) . "{$sep}drush.launcher")) { + $found_script = dirname($found_script) . "{$sep}drush.launcher"; + } + } + + // Didn't find any site-local Drush, or @use'd Drush. + // Skip the Bash niceties of the launcher and proceed to drush_main() in either case: + // - No script was found and we are running a Phar + // - The found script *is* the Phar https://github.com/drush-ops/drush/pull/2246. + $phar_path = class_exists('Phar') ? Phar::running(FALSE) : ''; + if ((empty($found_script) && $phar_path) || !empty($found_script) && $found_script == $phar_path) { + drush_run_main($DEBUG, $sep, "Phar detected. Proceeding to drush_main()."); + } + + // Didn't find any site-local Drush, or @use'd Drush, or Phar. + // There should be a drush.launcher in same directory as this script. + if (empty($found_script)) { + $found_script = dirname(__DIR__) . "{$sep}drush.launcher"; + } + + if (drush_is_windows()) { + // Sometimes we found launcher in /bin, and sometimes not. Adjust accordingly. + if (strpos($found_script, 'bin')) { + $found_script = dirname($found_script). $sep. 'drush.php.bat'; + } + else { + array_unshift($arguments, dirname($found_script). $sep. 'drush.php'); + $found_script = 'php'; + } + } + + // Always use pcntl_exec if it exists. + $use_pcntl_exec = function_exists("pcntl_exec") && (strpos(ini_get('disable_functions'), 'pcntl_exec') === FALSE); + + // If we have posix_getppid, then pass in the shell pid so + // that 'site-set' et. al. can work correctly. + if (function_exists('posix_getppid')) { + putenv("DRUSH_SHELL_PID=" . posix_getppid()); + } + + // Set an environment variable indicating which script + // the Drush finder found. If we end up re-entrantly calling + // another Drush finder, then we will skip searching for + // a site-local Drush, and always use the drush.launcher + // found previously. This environment variable typically should + // not be set by clients. + putenv("DRUSH_FINDER_SCRIPT=$found_script"); + + // Emit a message in debug mode advertising the location of the + // script we found. + if ($DEBUG) { + $launch_method = $use_pcntl_exec ? 'pcntl_exec' : 'proc_open'; + fwrite(STDERR, "Using the Drush script found at $found_script using $launch_method\n"); + } + + if ($use_pcntl_exec) { + // Get the current environment for pnctl_exec. + $env = drush_env(); + + // Make sure Drush can locates original working directory. https://github.com/drush-ops/drush/issues/2285 + chdir($cwd); + + // Launch the new script in the same process. + // If the launch succeeds, then it will not return. + $error = pcntl_exec($found_script, $arguments, $env); + if (!$error) { + $errno = pcntl_get_last_error(); + $strerror = pcntl_strerror($errno); + fwrite(STDERR, "Error has occurred executing the Drush script found at $found_script\n"); + fwrite(STDERR, "(errno {$errno}) $strerror\n"); + } + exit(1); + } + else { + $escaped_args = array_map(function($item) { return drush_escapeshellarg($item); }, $arguments); + // Double quotes around $found_script as it can contain spaces. + $cmd = drush_escapeshellarg($found_script). ' '. implode(' ', $escaped_args); + if (drush_is_windows()) { + // Windows requires double quotes around whole command. + // @see https://bugs.php.net/bug.php?id=49139 + // @see https://bugs.php.net/bug.php?id=60181 + $cmd = '"'. $cmd. '"'; + } + $process = proc_open($cmd, array(0 => STDIN, 1 => STDOUT, 2 => STDERR), $pipes, $cwd); + $proc_status = proc_get_status($process); + $exit_code = proc_close($process); + exit($proc_status["running"] ? $exit_code : $proc_status["exitcode"] ); + } +} + +/** + * Run drush_main() and then exit. Used when we cannot hand over execution to + * the launcher. + * + * @param bool $DEBUG + * Are we in debug mode + * @param string $sep + * Directory separator + * @param string $msg + * Debug message to log before running drush_main() + */ +function drush_run_main($DEBUG, $sep, $msg) { +// Emit a message in debug mode advertising how we proceeded. + if ($DEBUG) { + fwrite(STDERR, $msg. "\n"); + } + require __DIR__ . "{$sep}preflight.inc"; + exit(drush_main()); +} diff --git a/vendor/drush/drush/lib/Drush/Boot/BaseBoot.php b/vendor/drush/drush/lib/Drush/Boot/BaseBoot.php new file mode 100644 index 0000000000..c816988c45 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Boot/BaseBoot.php @@ -0,0 +1,100 @@ + $error) { + drush_set_error($key, $error); + } + drush_set_error('DRUSH_COMMAND_NOT_EXECUTABLE', dt("The drush command '!args' could not be executed.", array('!args' => $args))); + } + elseif (!empty($args)) { + drush_set_error('DRUSH_COMMAND_NOT_FOUND', dt("The drush command '!args' could not be found. Run `drush cache-clear drush` to clear the commandfile cache if you have installed new extensions.", array('!args' => $args))); + } + // Set errors that occurred in the bootstrap phases. + $errors = drush_get_context('DRUSH_BOOTSTRAP_ERRORS', array()); + foreach ($errors as $code => $message) { + drush_set_error($code, $message); + } + } + + function bootstrap_and_dispatch() { + $phases = $this->bootstrap_init_phases(); + + $return = ''; + $command_found = FALSE; + _drush_bootstrap_output_prepare(); + foreach ($phases as $phase) { + if (drush_bootstrap_to_phase($phase)) { + $command = drush_parse_command(); + if (is_array($command)) { + $command += $this->command_defaults(); + // Insure that we have bootstrapped to a high enough + // phase for the command prior to enforcing requirements. + $bootstrap_result = drush_bootstrap_to_phase($command['bootstrap']); + $this->enforce_requirement($command); + + if ($bootstrap_result && empty($command['bootstrap_errors'])) { + drush_log(dt("Found command: !command (commandfile=!commandfile)", array('!command' => $command['command'], '!commandfile' => $command['commandfile'])), LogLevel::BOOTSTRAP); + + $command_found = TRUE; + // Dispatch the command(s). + $return = drush_dispatch($command); + + // Prevent a '1' at the end of the output. + if ($return === TRUE) { + $return = ''; + } + + if (drush_get_context('DRUSH_DEBUG') && !drush_get_context('DRUSH_QUIET')) { + // @todo Create version independant wrapper around Drupal timers. Use it. + drush_print_timers(); + } + break; + } + } + } + else { + break; + } + } + + if (!$command_found) { + // If we reach this point, command doesn't fit requirements or we have not + // found either a valid or matching command. + $this->report_command_error($command); + } + return $return; + } + + /** + * {@inheritdoc} + */ + public function terminate() { + } +} diff --git a/vendor/drush/drush/lib/Drush/Boot/Boot.php b/vendor/drush/drush/lib/Drush/Boot/Boot.php new file mode 100644 index 0000000000..9bad8e7ade --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Boot/Boot.php @@ -0,0 +1,109 @@ + method name. + */ + function bootstrap_phases(); + + /** + * List of bootstrap phases where Drush should stop and look for commandfiles. + * + * This allows us to bootstrap to a minimum neccesary to find commands. + * + * Once a command is found, Drush will ensure a bootstrap to the phase + * declared by the command. + * + * @return array of PHASE indexes. + */ + function bootstrap_init_phases(); + + /** + * Return an array of default values that should be added + * to every command (e.g. values needed in enforce_requirements(), + * etc.) + */ + function command_defaults(); + + /** + * Called by Drush when a command is selected, but + * before it runs. This gives the Boot class an + * opportunity to determine if any minimum + * requirements (e.g. minimum Drupal version) declared + * in the command have been met. + * + * @return TRUE if command is valid. $command['bootstrap_errors'] + * should be populated with an array of error messages if + * the command is not valid. + */ + function enforce_requirement(&$command); + + /** + * Called by Drush if a command is not found, or if the + * command was found, but did not meet requirements. + * + * The implementation in BaseBoot should be sufficient + * for most cases, so this method typically will not need + * to be overridden. + */ + function report_command_error($command); + + /** + * This method is called during the shutdown of drush. + * + * @return void + */ + public function terminate(); +} diff --git a/vendor/drush/drush/lib/Drush/Boot/DrupalBoot.php b/vendor/drush/drush/lib/Drush/Boot/DrupalBoot.php new file mode 100644 index 0000000000..f41bb10702 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Boot/DrupalBoot.php @@ -0,0 +1,576 @@ + 'bootstrap_drush', + DRUSH_BOOTSTRAP_DRUPAL_ROOT => 'bootstrap_drupal_root', + DRUSH_BOOTSTRAP_DRUPAL_SITE => 'bootstrap_drupal_site', + DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION => 'bootstrap_drupal_configuration', + DRUSH_BOOTSTRAP_DRUPAL_DATABASE => 'bootstrap_drupal_database', + DRUSH_BOOTSTRAP_DRUPAL_FULL => 'bootstrap_drupal_full', + DRUSH_BOOTSTRAP_DRUPAL_LOGIN => 'bootstrap_drupal_login'); + } + + /** + * List of bootstrap phases where Drush should stop and look for commandfiles. + * + * For Drupal, we try at these bootstrap phases: + * + * - Drush preflight: to find commandfiles in any system location, + * out of a Drupal installation. + * - Drupal root: to find commandfiles based on Drupal core version. + * - Drupal full: to find commandfiles defined within a Drupal directory. + * + * Once a command is found, Drush will ensure a bootstrap to the phase + * declared by the command. + * + * @return array of PHASE indexes. + */ + function bootstrap_init_phases() { + return array(DRUSH_BOOTSTRAP_DRUSH, DRUSH_BOOTSTRAP_DRUPAL_ROOT, DRUSH_BOOTSTRAP_DRUPAL_FULL); + } + + function enforce_requirement(&$command) { + parent::enforce_requirement($command); + $this->drush_enforce_requirement_drupal_dependencies($command); + } + + function report_command_error($command) { + // If we reach this point, command doesn't fit requirements or we have not + // found either a valid or matching command. + + // If no command was found check if it belongs to a disabled module. + if (!$command) { + $command = $this->drush_command_belongs_to_disabled_module(); + } + parent::report_command_error($command); + } + + function command_defaults() { + return array( + 'drupal dependencies' => array(), + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_LOGIN, + ); + } + + /** + * @return array of strings - paths to directories where contrib + * modules can be found + */ + abstract function contrib_modules_paths(); + + /** + * @return array of strings - paths to directories where contrib + * themes can be found + */ + abstract function contrib_themes_paths(); + + function commandfile_searchpaths($phase, $phase_max = FALSE) { + if (!$phase_max) { + $phase_max = $phase; + } + + $searchpath = array(); + switch ($phase) { + case DRUSH_BOOTSTRAP_DRUPAL_ROOT: + $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT'); + $searchpath[] = $drupal_root . '/../drush'; + $searchpath[] = $drupal_root . '/drush'; + $searchpath[] = $drupal_root . '/sites/all/drush'; + break; + case DRUSH_BOOTSTRAP_DRUPAL_SITE: + // If we are going to stop bootstrapping at the site, then + // we will quickly add all commandfiles that we can find for + // any extension associated with the site, whether it is enabled + // or not. If we are, however, going to continue on to bootstrap + // all the way to DRUSH_BOOTSTRAP_DRUPAL_FULL, then we will + // instead wait for that phase, which will more carefully add + // only those Drush commandfiles that are associated with + // enabled modules. + if ($phase_max < DRUSH_BOOTSTRAP_DRUPAL_FULL) { + $searchpath = array_merge($searchpath, $this->contrib_modules_paths()); + + // Adding commandfiles located within /profiles. Try to limit to one profile for speed. Note + // that Drupal allows enabling modules from a non-active profile so this logic is kinda dodgy. + $cid = drush_cid_install_profile(); + if ($cached = drush_cache_get($cid)) { + $profile = $cached->data; + $searchpath[] = "profiles/$profile/modules"; + $searchpath[] = "profiles/$profile/themes"; + } + else { + // If install_profile is not available, scan all profiles. + $searchpath[] = "profiles"; + $searchpath[] = "sites/all/profiles"; + } + + $searchpath = array_merge($searchpath, $this->contrib_themes_paths()); + } + break; + case DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION: + // Nothing to do here anymore. Left for documentation. + break; + case DRUSH_BOOTSTRAP_DRUPAL_FULL: + // Add enabled module paths, excluding the install profile. Since we are bootstrapped, + // we can use the Drupal API. + $ignored_modules = drush_get_option_list('ignored-modules', array()); + $cid = drush_cid_install_profile(); + if ($cached = drush_cache_get($cid)) { + $ignored_modules[] = $cached->data; + } + foreach (array_diff(drush_module_list(), $ignored_modules) as $module) { + $filepath = drupal_get_path('module', $module); + if ($filepath && $filepath != '/') { + $searchpath[] = $filepath; + } + } + + // Check all enabled themes including non-default and non-admin. + foreach (drush_theme_list() as $key => $value) { + $searchpath[] = drupal_get_path('theme', $key); + } + break; + } + + return $searchpath; + } + + /** + * Check if the given command belongs to a disabled module. + * + * @return array + * Array with a command-like bootstrap error or FALSE if Drupal was not + * bootstrapped fully or the command does not belong to a disabled module. + */ + function drush_command_belongs_to_disabled_module() { + if (drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)) { + _drush_find_commandfiles(DRUSH_BOOTSTRAP_DRUPAL_SITE, DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION); + drush_get_commands(TRUE); + $commands = drush_get_commands(); + $arguments = drush_get_arguments(); + $command_name = array_shift($arguments); + if (isset($commands[$command_name])) { + // We found it. Load its module name and set an error. + if (is_array($commands[$command_name]['drupal dependencies']) && count($commands[$command_name]['drupal dependencies'])) { + $modules = implode(', ', $commands[$command_name]['drupal dependencies']); + } + else { + // The command does not define Drupal dependencies. Derive them. + $command_files = commandfiles_cache()->get(); + $command_path = $commands[$command_name]['path'] . DIRECTORY_SEPARATOR . $commands[$command_name]['commandfile'] . '.drush.inc'; + $modules = array_search($command_path, $command_files); + } + return array( + 'bootstrap_errors' => array( + 'DRUSH_COMMAND_DEPENDENCY_ERROR' => dt('Command !command needs the following extension(s) enabled to run: !dependencies.', array( + '!command' => $command_name, + '!dependencies' => $modules, + )), + ), + ); + } + } + + return FALSE; + } + + /** + * Check that a command has its declared dependencies available or have no + * dependencies. + * + * @param $command + * Command to check. Any errors will be added to the 'bootstrap_errors' element. + * + * @return + * TRUE if command is valid. + */ + function drush_enforce_requirement_drupal_dependencies(&$command) { + // If the command bootstrap is DRUSH_BOOTSTRAP_MAX, then we will + // allow the requirements to pass if we have not successfully + // bootstrapped Drupal. The combination of DRUSH_BOOTSTRAP_MAX + // and 'drupal dependencies' indicates that the drush command + // will use the dependent modules only if they are available. + if ($command['bootstrap'] == DRUSH_BOOTSTRAP_MAX) { + // If we have not bootstrapped, then let the dependencies pass; + // if we have bootstrapped, then enforce them. + if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') < DRUSH_BOOTSTRAP_DRUPAL_FULL) { + return TRUE; + } + } + // If there are no drupal dependencies, then do nothing + if (!empty($command['drupal dependencies'])) { + foreach ($command['drupal dependencies'] as $dependency) { + drush_include_engine('drupal', 'environment'); + if(!drush_module_exists($dependency)) { + $command['bootstrap_errors']['DRUSH_COMMAND_DEPENDENCY_ERROR'] = dt('Command !command needs the following modules installed/enabled to run: !dependencies.', array('!command' => $command['command'], '!dependencies' => implode(', ', $command['drupal dependencies']))); + return FALSE; + } + } + } + return TRUE; + } + + /** + * Validate the DRUSH_BOOTSTRAP_DRUPAL_ROOT phase. + * + * In this function, we will check if a valid Drupal directory is available. + * We also determine the value that will be stored in the DRUSH_DRUPAL_ROOT + * context and DRUPAL_ROOT constant if it is considered a valid option. + */ + function bootstrap_drupal_root_validate() { + $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT'); + + if (empty($drupal_root)) { + return drush_bootstrap_error('DRUSH_NO_DRUPAL_ROOT', dt("A Drupal installation directory could not be found")); + } + if (!$signature = drush_valid_root($drupal_root)) { + return drush_bootstrap_error('DRUSH_INVALID_DRUPAL_ROOT', dt("The directory !drupal_root does not contain a valid Drupal installation", array('!drupal_root' => $drupal_root))); + } + + $version = drush_drupal_version($drupal_root); + $major_version = drush_drupal_major_version($drupal_root); + if ($major_version <= 5) { + return drush_set_error('DRUSH_DRUPAL_VERSION_UNSUPPORTED', dt('Drush !drush_version does not support Drupal !major_version.', array('!drush_version' => DRUSH_VERSION, '!major_version' => $major_version))); + } + + drush_bootstrap_value('drupal_root', $drupal_root); + define('DRUSH_DRUPAL_SIGNATURE', $signature); + + return TRUE; + } + + /** + * Bootstrap Drush with a valid Drupal Directory. + * + * In this function, the pwd will be moved to the root + * of the Drupal installation. + * + * The DRUSH_DRUPAL_ROOT context, DRUSH_DRUPAL_CORE context, DRUPAL_ROOT, and the + * DRUSH_DRUPAL_CORE constants are populated from the value that we determined during + * the validation phase. + * + * We also now load the drushrc.php for this specific Drupal site. + * We can now include files from the Drupal Tree, and figure + * out more context about the platform, such as the version of Drupal. + */ + function bootstrap_drupal_root() { + // Load the config options from Drupal's /drush and sites/all/drush directories. + drush_load_config('drupal'); + + $drupal_root = drush_set_context('DRUSH_DRUPAL_ROOT', drush_bootstrap_value('drupal_root')); + chdir($drupal_root); + $version = drush_drupal_version(); + $major_version = drush_drupal_major_version(); + + $core = $this->bootstrap_drupal_core($drupal_root); + + // DRUSH_DRUPAL_CORE should point to the /core folder in Drupal 8+ or to DRUPAL_ROOT + // in prior versions. + drush_set_context('DRUSH_DRUPAL_CORE', $core); + define('DRUSH_DRUPAL_CORE', $core); + + _drush_preflight_global_options(); + + drush_log(dt("Initialized Drupal !version root directory at !drupal_root", array("!version" => $version, '!drupal_root' => $drupal_root)), LogLevel::BOOTSTRAP); + } + + /** + * VALIDATE the DRUSH_BOOTSTRAP_DRUPAL_SITE phase. + * + * In this function we determine the URL used for the command, + * and check for a valid settings.php file. + * + * To do this, we need to set up the $_SERVER environment variable, + * to allow us to use conf_path to determine what Drupal will load + * as a configuration file. + */ + function bootstrap_drupal_site_validate() { + // Define the selected conf path as soon as we have identified that + // we have selected a Drupal site. Drush used to set this context + // during the drush_bootstrap_drush phase. + $drush_uri = _drush_bootstrap_selected_uri(); + drush_set_context('DRUSH_SELECTED_DRUPAL_SITE_CONF_PATH', drush_conf_path($drush_uri)); + + $this->bootstrap_drupal_site_setup_server_global($drush_uri); + return $this->bootstrap_drupal_site_validate_settings_present(); + } + + /** + * Set up the $_SERVER globals so that Drupal will see the same values + * that it does when serving pages via the web server. + */ + function bootstrap_drupal_site_setup_server_global($drush_uri) { + // Fake the necessary HTTP headers that Drupal needs: + if ($drush_uri) { + $drupal_base_url = parse_url($drush_uri); + // If there's no url scheme set, add http:// and re-parse the url + // so the host and path values are set accurately. + if (!array_key_exists('scheme', $drupal_base_url)) { + $drush_uri = 'http://' . $drush_uri; + $drupal_base_url = parse_url($drush_uri); + } + // Fill in defaults. + $drupal_base_url += array( + 'path' => '', + 'host' => NULL, + 'port' => NULL, + ); + $_SERVER['HTTP_HOST'] = $drupal_base_url['host']; + + if ($drupal_base_url['scheme'] == 'https') { + $_SERVER['HTTPS'] = 'on'; + } + + if ($drupal_base_url['port']) { + $_SERVER['HTTP_HOST'] .= ':' . $drupal_base_url['port']; + } + $_SERVER['SERVER_PORT'] = $drupal_base_url['port']; + + $_SERVER['REQUEST_URI'] = $drupal_base_url['path'] . '/'; + } + else { + $_SERVER['HTTP_HOST'] = 'default'; + $_SERVER['REQUEST_URI'] = '/'; + } + + $_SERVER['PHP_SELF'] = $_SERVER['REQUEST_URI'] . 'index.php'; + $_SERVER['SCRIPT_NAME'] = $_SERVER['PHP_SELF']; + $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; + $_SERVER['REQUEST_METHOD'] = 'GET'; + + $_SERVER['SERVER_SOFTWARE'] = NULL; + $_SERVER['HTTP_USER_AGENT'] = NULL; + $_SERVER['SCRIPT_FILENAME'] = DRUPAL_ROOT . '/index.php'; + } + + /** + * Validate that the Drupal site has all of the settings that it + * needs to operated. + */ + function bootstrap_drupal_site_validate_settings_present() { + $site = drush_bootstrap_value('site', $_SERVER['HTTP_HOST']); + + $conf_path = drush_bootstrap_value('conf_path', $this->conf_path(TRUE, TRUE)); + $conf_file = "$conf_path/settings.php"; + if (!file_exists($conf_file) && !isset($_SERVER['PRESSFLOW_SETTINGS'])) { + return drush_bootstrap_error('DRUPAL_SITE_SETTINGS_NOT_FOUND', dt("Could not find a Drupal settings.php file at !file.", + array('!file' => $conf_file))); + } + + return TRUE; + } + + /** + * Called by bootstrap_drupal_site to do the main work + * of the drush drupal site bootstrap. + */ + function bootstrap_do_drupal_site() { + $drush_uri = drush_get_context('DRUSH_SELECTED_URI'); + drush_set_context('DRUSH_URI', $drush_uri); + $site = drush_set_context('DRUSH_DRUPAL_SITE', drush_bootstrap_value('site')); + $conf_path = drush_set_context('DRUSH_DRUPAL_SITE_ROOT', drush_bootstrap_value('conf_path')); + + drush_log(dt("Initialized Drupal site !site at !site_root", array('!site' => $site, '!site_root' => $conf_path)), LogLevel::BOOTSTRAP); + + _drush_preflight_global_options(); + } + + /** + * Initialize a site on the Drupal root. + * + * We now set various contexts that we determined and confirmed to be valid. + * Additionally we load an optional drushrc.php file in the site directory. + */ + function bootstrap_drupal_site() { + drush_load_config('site'); + $this->bootstrap_do_drupal_site(); + } + + /** + * Initialize and load the Drupal configuration files. + * + * We process and store a normalized set of database credentials + * from the loaded configuration file, so we can validate them + * and access them easily in the future. + * + * Also override Drupal variables as per --variables option. + */ + function bootstrap_drupal_configuration() { + global $conf; + + $override = array( + 'dev_query' => FALSE, // Force Drupal6 not to store queries since we are not outputting them. + 'cron_safe_threshold' => 0, // Don't run poormanscron during Drush request (D7+). + ); + + $current_override = drush_get_option_list('variables'); + foreach ($current_override as $name => $value) { + if (is_numeric($name) && (strpos($value, '=') !== FALSE)) { + list($name, $value) = explode('=', $value, 2); + } + $override[$name] = $value; + } + $conf = is_array($conf) ? array_merge($conf, $override) : $conf; + } + + /** + * Validate the DRUSH_BOOTSTRAP_DRUPAL_DATABASE phase + * + * Attempt to make a working database connection using the + * database credentials that were loaded during the previous + * phase. + */ + function bootstrap_drupal_database_validate() { + if (!drush_valid_db_credentials()) { + return drush_bootstrap_error('DRUSH_DRUPAL_DB_ERROR'); + } + return TRUE; + } + + /** + * Test to see if the Drupal database has a specified + * table or tables. + * + * This is a bootstrap helper function designed to be called + * from the bootstrap_drupal_database_validate() methods of + * derived DrupalBoot classes. If a database exists, but is + * empty, then the Drupal database bootstrap will fail. To + * prevent this situation, we test for some table that is needed + * in an ordinary bootstrap, and return FALSE from the validate + * function if it does not exist, so that we do not attempt to + * start the database bootstrap. + * + * Note that we must manually do our own prefix testing here, + * because the existing wrappers we have for handling prefixes + * depend on bootstrapping to the "database" phase, and therefore + * are not available to validate this same phase. + * + * @param $required_tables + * Array of table names, or string with one table name + * + * @return TRUE if all tables in input parameter exist in + * the database. + */ + function bootstrap_drupal_database_has_table($required_tables) { + try { + $sql = drush_sql_get_class(); + $spec = $sql->db_spec(); + $prefix = isset($spec['prefix']) ? $spec['prefix'] : NULL; + if (!is_array($prefix)) { + $prefix = array('default' => $prefix); + } + $tables = $sql->listTables(); + foreach ((array)$required_tables as $required_table) { + $prefix_key = array_key_exists($required_table, $prefix) ? $required_table : 'default'; + if (!in_array($prefix[$prefix_key] . $required_table, $tables)) { + return FALSE; + } + } + } + catch (Exception $e) { + // Usually the checks above should return a result without + // throwing an exception, but we'll catch any that are + // thrown just in case. + return FALSE; + } + return TRUE; + } + + /** + * Boostrap the Drupal database. + */ + function bootstrap_drupal_database() { + // We presume that our derived classes will connect and then + // either fail, or call us via parent:: + drush_log(dt("Successfully connected to the Drupal database."), LogLevel::BOOTSTRAP); + } + + /** + * Attempt to load the full Drupal system. + */ + function bootstrap_drupal_full() { + drush_include_engine('drupal', 'environment'); + + $this->add_logger(); + + // Write correct install_profile to cache as needed. Used by _drush_find_commandfiles(). + $cid = drush_cid_install_profile(); + $install_profile = $this->get_profile(); + if ($cached_install_profile = drush_cache_get($cid)) { + // We have a cached profile. Check it for correctness and save new value if needed. + if ($cached_install_profile->data != $install_profile) { + drush_cache_set($cid, $install_profile); + } + } + else { + // No cached entry so write to cache. + drush_cache_set($cid, $install_profile); + } + + _drush_log_drupal_messages(); + } + + /** + * Log into the bootstrapped Drupal site with a specific + * username or user id. + */ + function bootstrap_drupal_login() { + $uid_or_name = drush_set_context('DRUSH_USER', drush_get_option('user', 0)); + $userversion = drush_user_get_class(); + if (!$account = $userversion->load_by_uid($uid_or_name)) { + if (!$account = $userversion->load_by_name($uid_or_name)) { + if (is_numeric($uid_or_name)) { + $message = dt('Could not login with user ID !user.', array('!user' => $uid_or_name)); + if ($uid_or_name === 0) { + $message .= ' ' . dt('This is typically caused by importing a MySQL database dump from a faulty tool which re-numbered the anonymous user ID in the users table. See !link for help recovering from this situation.', array('!link' => 'http://drupal.org/node/1029506')); + } + } + else { + $message = dt('Could not login with user account `!user\'.', array('!user' => $uid_or_name)); + } + return drush_set_error('DRUPAL_USER_LOGIN_FAILED', $message); + } + } + $userversion->setCurrentUser($account); + _drush_log_drupal_messages(); + } + +} diff --git a/vendor/drush/drush/lib/Drush/Boot/DrupalBoot6.php b/vendor/drush/drush/lib/Drush/Boot/DrupalBoot6.php new file mode 100644 index 0000000000..9539f38777 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Boot/DrupalBoot6.php @@ -0,0 +1,95 @@ +conf_path() . '/modules', + 'sites/all/modules', + ); + } + + function contrib_themes_paths() { + return array( + $this->conf_path() . '/themes', + 'sites/all/themes', + ); + } + + function bootstrap_drupal_core($drupal_root) { + define('DRUPAL_ROOT', $drupal_root); + require_once DRUPAL_ROOT . '/includes/bootstrap.inc'; + $core = DRUPAL_ROOT; + + return $core; + } + + function bootstrap_drupal_database_validate() { + return parent::bootstrap_drupal_database_validate() && $this->bootstrap_drupal_database_has_table('cache'); + } + + function bootstrap_drupal_database() { + drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE); + parent::bootstrap_drupal_database(); + } + + function bootstrap_drupal_configuration() { + drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION); + + parent::bootstrap_drupal_configuration(); + } + + function bootstrap_drupal_full() { + if (!drush_get_context('DRUSH_QUIET', FALSE)) { + ob_start(); + } + drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); + if (!drush_get_context('DRUSH_QUIET', FALSE)) { + ob_end_clean(); + } + + // Unset drupal error handler and restore drush's one. + restore_error_handler(); + + parent::bootstrap_drupal_full(); + } +} diff --git a/vendor/drush/drush/lib/Drush/Boot/DrupalBoot7.php b/vendor/drush/drush/lib/Drush/Boot/DrupalBoot7.php new file mode 100644 index 0000000000..30e55a5b35 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Boot/DrupalBoot7.php @@ -0,0 +1,98 @@ +conf_path() . '/modules', + 'sites/all/modules', + ); + } + + function contrib_themes_paths() { + return array( + $this->conf_path() . '/themes', + 'sites/all/themes', + ); + } + + function bootstrap_drupal_core($drupal_root) { + define('DRUPAL_ROOT', $drupal_root); + require_once DRUPAL_ROOT . '/includes/bootstrap.inc'; + $core = DRUPAL_ROOT; + + return $core; + } + + function bootstrap_drupal_database_validate() { + return parent::bootstrap_drupal_database_validate() && $this->bootstrap_drupal_database_has_table('blocked_ips'); + } + + function bootstrap_drupal_database() { + drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE); + parent::bootstrap_drupal_database(); + } + + function bootstrap_drupal_configuration() { + drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION); + + // Unset drupal error handler and restore drush's one. + restore_error_handler(); + + parent::bootstrap_drupal_configuration(); + } + + function bootstrap_drupal_full() { + if (!drush_get_context('DRUSH_QUIET', FALSE)) { + ob_start(); + } + drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); + if (!drush_get_context('DRUSH_QUIET', FALSE)) { + ob_end_clean(); + } + + parent::bootstrap_drupal_full(); + } +} diff --git a/vendor/drush/drush/lib/Drush/Boot/DrupalBoot8.php b/vendor/drush/drush/lib/Drush/Boot/DrupalBoot8.php new file mode 100644 index 0000000000..3037cc62ae --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Boot/DrupalBoot8.php @@ -0,0 +1,218 @@ +getSitePath(); + } + if (!isset($site_path) || empty($site_path)) { + $site_path = DrupalKernel::findSitePath($request, $require_settings); + } + return $site_path; + } + + function add_logger() { + // If we're running on Drupal 8 or later, we provide a logger which will send + // output to drush_log(). This should catch every message logged through every + // channel. + $container = \Drupal::getContainer(); + $parser = $container->get('logger.log_message_parser'); + $drushLogger = drush_get_context('DRUSH_LOG_CALLBACK'); + $logger = new \Drush\Log\DrushLog($parser, $drushLogger); + $container->get('logger.factory')->addLogger($logger); + } + + function contrib_modules_paths() { + return array( + $this->conf_path() . '/modules', + 'sites/all/modules', + 'modules', + ); + } + + /** + * @return array of strings - paths to directories where contrib + * themes can be found + */ + function contrib_themes_paths() { + return array( + $this->conf_path() . '/themes', + 'sites/all/themes', + 'themes', + ); + } + + function bootstrap_drupal_core($drupal_root) { + $core = DRUPAL_ROOT . '/core'; + + return $core; + } + + function bootstrap_drupal_database_validate() { + return parent::bootstrap_drupal_database_validate() && $this->bootstrap_drupal_database_has_table('key_value'); + } + + function bootstrap_drupal_database() { + // D8 omits this bootstrap level as nothing special needs to be done. + parent::bootstrap_drupal_database(); + } + + function bootstrap_drupal_configuration() { + $this->request = Request::createFromGlobals(); + $classloader = drush_drupal_load_autoloader(DRUPAL_ROOT); + // @todo - use Request::create() and then no need to set PHP superglobals + $kernelClass = new \ReflectionClass('\Drupal\Core\DrupalKernel'); + if ($kernelClass->hasMethod('addServiceModifier')) { + $this->kernel = DrupalKernel::createFromRequest($this->request, $classloader, 'prod'); + } + else { + $this->kernel = DrushDrupalKernel::createFromRequest($this->request, $classloader, 'prod'); + } + // @see Drush\Drupal\DrupalKernel::addServiceModifier() + $this->kernel->addServiceModifier(new DrushServiceModifier()); + + // Unset drupal error handler and restore Drush's one. + restore_error_handler(); + + // Disable automated cron if the module is enabled. + $GLOBALS['config']['automated_cron.settings']['interval'] = 0; + + parent::bootstrap_drupal_configuration(); + } + + function bootstrap_drupal_full() { + drush_log(dt('About to bootstrap the Drupal 8 Kernel.'), LogLevel::DEBUG); + // TODO: do we need to do ob_start any longer? + if (!drush_get_context('DRUSH_QUIET', FALSE)) { + ob_start(); + } + $this->kernel->boot(); + if (method_exists($this->kernel, 'preHandle')) { + $this->kernel->preHandle($this->request); + } + else { + $this->kernel->prepareLegacyRequest($this->request); + } + if (!drush_get_context('DRUSH_QUIET', FALSE)) { + ob_end_clean(); + } + drush_log(dt('Finished bootstraping the Drupal 8 Kernel.'), LogLevel::DEBUG); + + parent::bootstrap_drupal_full(); + + // Get a list of the modules to ignore + $ignored_modules = drush_get_option_list('ignored-modules', array()); + + // We have to get the service command list from the container, because + // it is constructed in an indirect way during the container initialization. + // The upshot is that the list of console commands is not available + // until after $kernel->boot() is called. + $container = \Drupal::getContainer(); + if ($container->has('drush.service.consolecommands')) { + $serviceCommandlist = $container->get('drush.service.consolecommands'); + foreach ($serviceCommandlist->getCommandList() as $command) { + if (!$this->commandIgnored($command, $ignored_modules)) { + drush_log(dt('Add a command: !name', ['!name' => $command->getName()]), LogLevel::DEBUG); + annotationcommand_adapter_cache_module_console_commands($command); + } + } + } + // Do the same thing with the annotation commands. + if ($container->has('drush.service.consolidationcommands')) { + $serviceCommandlist = $container->get('drush.service.consolidationcommands'); + foreach ($serviceCommandlist->getCommandList() as $commandhandler) { + if (!$this->commandIgnored($commandhandler, $ignored_modules)) { + drush_log(dt('Add a commandhandler: !name', ['!name' => get_class($commandhandler)]), LogLevel::DEBUG); + annotationcommand_adapter_cache_module_service_commands($commandhandler); + } + } + } + } + + public function commandIgnored($command, $ignored_modules) { + if (empty($ignored_modules)) { + return false; + } + $ignored_regex = '#\\\\(' . implode('|', $ignored_modules) . ')\\\\#'; + $class = new \ReflectionClass($command); + $commandNamespace = $class->getNamespaceName(); + return preg_match($ignored_regex, $commandNamespace); + } + + /** + * {@inheritdoc} + */ + public function terminate() { + parent::terminate(); + + if ($this->kernel) { + $response = Response::create(''); + $this->kernel->terminate($this->request, $response); + } + } +} diff --git a/vendor/drush/drush/lib/Drush/Boot/EmptyBoot.php b/vendor/drush/drush/lib/Drush/Boot/EmptyBoot.php new file mode 100644 index 0000000000..e7d9d62105 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Boot/EmptyBoot.php @@ -0,0 +1,45 @@ + '_drush_bootstrap_drush', + ); + } + + function bootstrap_init_phases() { + return array(DRUSH_BOOTSTRAP_DRUSH); + } + + function command_defaults() { + return array( + // TODO: Historically, commands that do not explicitly specify + // their bootstrap level default to DRUSH_BOOTSTRAP_DRUPAL_LOGIN. + // This isn't right any more, but we can't just change this to + // DRUSH_BOOTSTRAP_DRUSH, or we will start running commands that + // needed a full bootstrap with no bootstrap, and that won't work. + // For now, we will continue to force this to 'login'. Any command + // that does not declare 'bootstrap' is declaring that it is a Drupal + // command. + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_LOGIN, + ); + } +} diff --git a/vendor/drush/drush/lib/Drush/Cache/CacheInterface.php b/vendor/drush/drush/lib/Drush/Cache/CacheInterface.php new file mode 100644 index 0000000000..f3ab29ef59 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Cache/CacheInterface.php @@ -0,0 +1,112 @@ +bin = $bin; + $this->directory = $this->cacheDirectory(); + } + + /** + * Returns the cache directory for the given bin. + * + * @param string $bin + */ + function cacheDirectory($bin = NULL) { + $bin = $bin ? $bin : $this->bin; + return drush_directory_cache($bin); + } + + function get($cid) { + $cids = array($cid); + $cache = $this->getMultiple($cids); + return reset($cache); + } + + function getMultiple(&$cids) { + try { + $cache = array(); + foreach ($cids as $cid) { + $filename = $this->getFilePath($cid); + if (!file_exists($filename)) throw new \Exception; + + $item = $this->readFile($filename); + if ($item) { + $cache[$cid] = $item; + } + } + $cids = array_diff($cids, array_keys($cache)); + return $cache; + } + catch (\Exception $e) { + return array(); + } + } + + /** + * Returns the contents of the given filename unserialized. + * + * @param string $filename + * Absolute path to filename to read contents from. + */ + function readFile($filename) { + $item = file_get_contents($filename); + return $item ? unserialize($item) : FALSE; + } + + function set($cid, $data, $expire = DRUSH_CACHE_PERMANENT) { + $created = time(); + + $cache = new \stdClass; + $cache->cid = $cid; + $cache->data = is_object($data) ? clone $data : $data; + $cache->created = $created; + if ($expire == DRUSH_CACHE_TEMPORARY) { + $cache->expire = $created + 2591999; + } + // Expire time is in seconds if less than 30 days, otherwise is a timestamp. + elseif ($expire != DRUSH_CACHE_PERMANENT && $expire < 2592000) { + $cache->expire = $created + $expire; + } + else { + $cache->expire = $expire; + } + + // Ensure the cache directory still exists, in case a backend process + // cleared the cache after the cache was initialized. + drush_mkdir($this->directory); + + $filename = $this->getFilePath($cid); + return $this->writeFile($filename, $cache); + } + + /** + * Serializes data and write it to the given filename. + * + * @param string $filename + * Absolute path to filename to write cache data. + * @param $cache + * Cache data to serialize and write to $filename. + */ + function writeFile($filename, $cache) { + return file_put_contents($filename, serialize($cache)); + } + + function clear($cid = NULL, $wildcard = FALSE) { + $bin_dir = $this->cacheDirectory(); + $files = array(); + if (empty($cid)) { + drush_delete_dir($bin_dir, TRUE); + } + else { + if ($wildcard) { + if ($cid == '*') { + drush_delete_dir($bin_dir, TRUE); + } + else { + $matches = drush_scan_directory($bin_dir, "/^$cid/", array('.', '..')); + $files = $files + array_keys($matches); + } + } + else { + $files[] = $this->getFilePath($cid); + } + + foreach ($files as $f) { + if (file_exists($f)) { + unlink($f); + } + } + } + } + + function isEmpty() { + $files = drush_scan_directory($this->directory, "//", array('.', '..')); + return empty($files); + } + + /** + * Converts a cache id to a full path. + * + * @param $cid + * The cache ID of the data to retrieve. + * + * @return + * The full path to the cache file. + */ + protected function getFilePath($cid) { + return $this->directory . '/' . str_replace(array(':', '\\', '/'), '.', $cid) . self::EXTENSION; + } +} diff --git a/vendor/drush/drush/lib/Drush/Cache/JSONCache.php b/vendor/drush/drush/lib/Drush/Cache/JSONCache.php new file mode 100644 index 0000000000..6c6b3737c5 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Cache/JSONCache.php @@ -0,0 +1,24 @@ +cache = array(); + $this->deferred = array(); + } + + function get() { + return $this->cache; + } + + function deferred() { + return $this->deferred; + } + + function sort() { + ksort($this->cache); + } + + function add($commandfile) { + $load_command = FALSE; + + $module = basename($commandfile); + $module = preg_replace('/\.*drush[0-9]*\.inc/', '', $module); + $module_versionless = preg_replace('/\.d([0-9]+)$/', '', $module); + if (!isset($this->cache[$module_versionless])) { + $drupal_version = ''; + if (preg_match('/\.d([0-9]+)$/', $module, $matches)) { + $drupal_version = $matches[1]; + } + if (empty($drupal_version)) { + $load_command = TRUE; + } + else { + if (function_exists('drush_drupal_major_version') && ($drupal_version == drush_drupal_major_version())) { + $load_command = TRUE; + } + else { + // Signal that we should try again on + // the next bootstrap phase. + $this->deferred[$module] = $commandfile; + } + } + if ($load_command) { + $this->cache[$module_versionless] = $commandfile; + require_once $commandfile; + unset($this->deferred[$module]); + } + } + return $load_command; + } +} diff --git a/vendor/drush/drush/lib/Drush/Command/CommandfilesInterface.php b/vendor/drush/drush/lib/Drush/Command/CommandfilesInterface.php new file mode 100644 index 0000000000..acb7c2cdb6 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Command/CommandfilesInterface.php @@ -0,0 +1,10 @@ +arguments = $arguments; + $this->options = $options; + + // If a command name is provided as a parameter, then push + // it onto the front of the arguments list as a service + if ($command) { + $this->arguments = array_merge( + [ 'command' => $command ], + $this->arguments + ); + } + // Is it interactive, or is it not interactive? + // Call drush_get_option() here if value not passed in? + $this->interactive = $interactive; + } + + /** + * {@inheritdoc} + */ + public function getFirstArgument() + { + return reset($this->arguments); + } + + /** + * {@inheritdoc} + */ + public function hasParameterOption($values, $onlyParams = false) + { + $values = (array) $values; + + foreach ($values as $value) { + if (array_key_exists($value, $this->options)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getParameterOption($values, $default = false, $onlyParams = false) + { + $values = (array) $values; + + foreach ($values as $value) { + if (array_key_exists($value, $this->options)) { + return $this->getOption($value); + } + } + + return $default; + } + + /** + * {@inheritdoc} + */ + public function bind(InputDefinition $definition) + { + // no-op: this class exists to avoid validation + } + + /** + * {@inheritdoc} + */ + public function validate() + { + // no-op: this class exists to avoid validation + } + + /** + * {@inheritdoc} + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * {@inheritdoc} + */ + public function getArgument($name) + { + // TODO: better to throw if an argument that does not exist is requested? + return isset($this->arguments[$name]) ? $this->arguments[$name] : ''; + } + + /** + * {@inheritdoc} + */ + public function setArgument($name, $value) + { + $this->arguments[$name] = $value; + } + + /** + * {@inheritdoc} + */ + public function hasArgument($name) + { + return isset($this->arguments[$name]); + } + + /** + * {@inheritdoc} + */ + public function getOptions() + { + return $this->options; + } + + /** + * {@inheritdoc} + */ + public function getOption($name) + { + return $this->options[$name]; + } + + /** + * {@inheritdoc} + */ + public function setOption($name, $value) + { + $this->options[$name] = $value; + } + + /** + * {@inheritdoc} + */ + public function hasOption($name) + { + return isset($this->options[$name]); + } + + /** + * {@inheritdoc} + */ + public function isInteractive() + { + return $this->interactive; + } + + /** + * {@inheritdoc} + */ + public function setInteractive($interactive) + { + $this->interactive = $interactive; + } +} diff --git a/vendor/drush/drush/lib/Drush/Command/DrushOutputAdapter.php b/vendor/drush/drush/lib/Drush/Command/DrushOutputAdapter.php new file mode 100644 index 0000000000..5d2101bf06 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Command/DrushOutputAdapter.php @@ -0,0 +1,25 @@ +commandList[] = $command; + } + + public function getCommandList() + { + return $this->commandList; + } +} diff --git a/vendor/drush/drush/lib/Drush/CommandFiles/ExampleCommandFile.php b/vendor/drush/drush/lib/Drush/CommandFiles/ExampleCommandFile.php new file mode 100644 index 0000000000..ec764dd6f4 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/CommandFiles/ExampleCommandFile.php @@ -0,0 +1,59 @@ + 'table', 'fields' => '']) + { + $outputData = [ + 'en' => [ 'first' => 'One', 'second' => 'Two', 'third' => 'Three' ], + 'de' => [ 'first' => 'Eins', 'second' => 'Zwei', 'third' => 'Drei' ], + 'jp' => [ 'first' => 'Ichi', 'second' => 'Ni', 'third' => 'San' ], + 'es' => [ 'first' => 'Uno', 'second' => 'Dos', 'third' => 'Tres' ], + ]; + return new RowsOfFields($outputData); + } + + /** + * Demonstrate an alter hook with an option + * + * @hook alter example:table + * @option french Add a row with French numbers. + * @usage example:formatters --french + */ + public function alterFormatters($result, CommandData $commandData) + { + if ($commandData->input()->getOption('french')) { + $result['fr'] = [ 'first' => 'Un', 'second' => 'Deux', 'third' => 'Trois' ]; + } + + return $result; + } +} diff --git a/vendor/drush/drush/lib/Drush/CommandFiles/core/BrowseCommands.php b/vendor/drush/drush/lib/Drush/CommandFiles/core/BrowseCommands.php new file mode 100644 index 0000000000..18dd2d2a59 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/CommandFiles/core/BrowseCommands.php @@ -0,0 +1,60 @@ + NULL, 'redirect-port' => NULL]) { + // Redispatch if called against a remote-host so a browser is started on the + // the *local* machine. + $alias = drush_get_context('DRUSH_TARGET_SITE_ALIAS'); + if (drush_sitealias_is_remote_site($alias)) { + $site_record = drush_sitealias_get_record($alias); + $return = drush_invoke_process($site_record, 'browse', array($path), drush_redispatch_get_options(), array('integrate' => TRUE)); + if ($return['error_status']) { + return drush_set_error('Unable to execute browse command on remote alias.'); + } + else { + $link = $return['object']; + } + } + else { + if (!drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_FULL)) { + // Fail gracefully if unable to bootstrap Drupal. drush_bootstrap() has + // already logged an error. + return FALSE; + } + $link = drush_url($path, array('absolute' => TRUE)); + } + + drush_start_browser($link); + return $link; + } + + /* + * An argument completion provider + */ + static function complete() { + return ['values' => ['admin', 'admin/content', 'admin/reports', 'admin/structure', 'admin/people', 'admin/modules', 'admin/config']]; + } +} diff --git a/vendor/drush/drush/lib/Drush/CommandFiles/core/DrupliconCommands.php b/vendor/drush/drush/lib/Drush/CommandFiles/core/DrupliconCommands.php new file mode 100644 index 0000000000..4e58fff794 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/CommandFiles/core/DrupliconCommands.php @@ -0,0 +1,42 @@ +printed) { + return; + } + $this->printed = true; + $annotationData = $commandData->annotationData(); + $commandName = $annotationData['command']; + // For some reason, Drush help uses drush_invoke_process to call helpsingle + if ($commandName == 'helpsingle') { + return; + } + drush_log(dt('Displaying Druplicon for "!command" command.', array('!command' => $commandName))); + if ($commandData->input()->getOption('druplicon')) { + $misc_dir = DRUSH_BASE_PATH . '/misc'; + if (drush_get_context('DRUSH_NOCOLOR')) { + $content = file_get_contents($misc_dir . '/druplicon-no_color.txt'); + } + else { + $content = file_get_contents($misc_dir . '/druplicon-color.txt'); + } + drush_print($content); + } + } +} diff --git a/vendor/drush/drush/lib/Drush/Commands/DrushCommands.php b/vendor/drush/drush/lib/Drush/Commands/DrushCommands.php new file mode 100644 index 0000000000..346c50dfb9 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Commands/DrushCommands.php @@ -0,0 +1,102 @@ +io) { + $this->io = new SymfonyStyle($this->input(), $this->output()); + } + return $this->io; + } + + /** + * @return InputInterface + */ + protected function input() + { + return Drush::input(); + } + + /** + * @return OutputInterface + */ + protected function output() + { + return Drush::output(); + } + + /** + * Returns a logger object. + * + * @return LoggerInterface + */ + protected function logger() + { + return $this->logger; + } + + /** + * Print the contents of a file. + * + * @param string $file + * Full path to a file. + */ + protected function printFile($file) + { + drush_print_file($file); + } +} diff --git a/vendor/drush/drush/lib/Drush/Commands/core/SanitizeCommands.php b/vendor/drush/drush/lib/Drush/Commands/core/SanitizeCommands.php new file mode 100644 index 0000000000..132c99dc27 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Commands/core/SanitizeCommands.php @@ -0,0 +1,256 @@ +wrap to TRUE if a db-prefix is set with drush. + */ + protected function setWrap() { + $this->wrap = $wrap_table_name = (bool) drush_get_option('db-prefix'); + } + + + /** + * Sanitize the database by removed and obfuscating user data. + * + * @command sql-sanitize + * + * @todo "drush dependencies" array('sqlsync') + * + * @bootstrap DRUSH_BOOTSTRAP_NONE + * @description Run sanitization operations on the current database. + * @option db-prefix Enable replacement of braces in sanitize queries. + * @option db-url A Drupal 6 style database URL. E.g., + * mysql://root:pass@127.0.0.1/db + * @option sanitize-email The pattern for test email addresses in the + * sanitization operation, or "no" to keep email addresses unchanged. May + * contain replacement patterns %uid, %mail or %name. Example value: + * user+%uid@localhost + * @option sanitize-password The password to assign to all accounts in the + * sanitization operation, or "no" to keep passwords unchanged. Example + * value: password + * @option whitelist-fields A comma delimited list of fields exempt from sanitization. + * @aliases sqlsan + * @usage drush sql-sanitize --sanitize-password=no + * Sanitize database without modifying any passwords. + * @usage drush sql-sanitize --whitelist-fields=field_biography,field_phone_number + * Sanitizes database but exempts two user fields from modification. + * @see hook_drush_sql_sync_sanitize() for adding custom sanitize routines. + */ + public function sqlSanitize($options = [ + 'db-prefix' => FALSE, + 'db-url' => '', + 'sanitize-email' => '', + 'sanitize-password' => '', + 'whitelist-fields' => '', + ]) { + drush_sql_bootstrap_further(); + if ($options['db-prefix']) { + drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_DATABASE); + } + + // Drush itself implements this via sql_drush_sql_sync_sanitize(). + drush_command_invoke_all('drush_sql_sync_sanitize', 'default'); + $operations = drush_get_context('post-sync-ops'); + if (!empty($operations)) { + if (!drush_get_context('DRUSH_SIMULATE')) { + $messages = _drush_sql_get_post_sync_messages(); + if ($messages) { + drush_print(); + drush_print($messages); + } + } + $queries = array_column($operations, 'query'); + $sanitize_query = implode(" ", $queries); + } + if (!drush_confirm(dt('Do you really want to sanitize the current database?'))) { + return drush_user_abort(); + } + + if ($sanitize_query) { + $sql = drush_sql_get_class(); + $sanitize_query = $sql->query_prefix($sanitize_query); + $result = $sql->query($sanitize_query); + if (!$result) { + throw new \Exception(dt('Sanitize query failed.')); + } + } + } + + /** + * Performs database sanitization. + * + * @param int $major_version + * E.g., 6, 7, or 8. + */ + public function doSanitize($major_version) { + $this->setWrap(); + $this->sanitizeSessions(); + + if ($major_version == 8) { + $this->sanitizeComments(); + $this->sanitizeUserFields(); + } + } + + /** + * Sanitize string fields associated with the user. + * + * We've got to do a good bit of SQL-foo here because Drupal services are + * not yet available. + */ + public function sanitizeUserFields() { + /** @var SqlBase $sql_class */ + $sql_class = drush_sql_get_class(); + $tables = $sql_class->listTables(); + $whitelist_fields = (array) explode(',', drush_get_option('whitelist-fields')); + + foreach ($tables as $table) { + if (strpos($table, 'user__field_') === 0) { + $field_name = substr($table, 6, strlen($table)); + if (in_array($field_name, $whitelist_fields)) { + continue; + } + + $output = $this->query("SELECT data FROM config WHERE name = 'field.field.user.user.$field_name';"); + $field_config = unserialize($output[0]); + $field_type = $field_config['field_type']; + $randomizer = new Random(); + + switch ($field_type) { + + case 'email': + $this->sanitizeTableColumn($table, $field_name . '_value', $randomizer->name(10) . '@example.com'); + break; + + case 'string': + $this->sanitizeTableColumn($table, $field_name . '_value', $randomizer->name(255)); + break; + + case 'string_long': + $this->sanitizeTableColumn($table, $field_name . '_value', $randomizer->sentences(1)); + break; + + case 'telephone': + $this->sanitizeTableColumn($table, $field_name . '_value', '15555555555'); + break; + + case 'text': + $this->sanitizeTableColumn($table, $field_name . '_value', $randomizer->paragraphs(2)); + break; + + case 'text_long': + $this->sanitizeTableColumn($table, $field_name . '_value', $randomizer->paragraphs(10)); + break; + + case 'text_with_summary': + $this->sanitizeTableColumn($table, $field_name . '_value', $randomizer->paragraphs(2)); + $this->sanitizeTableColumn($table, $field_name . '_summary', $randomizer->name(255)); + break; + } + } + } + } + + /** + * Replaces all values in given table column with the specified value. + * + * @param string $table + * The database table name. + * @param string $column + * The database column to be updated. + * @param $value + * The new value. + */ + public function sanitizeTableColumn($table, $column, $value) { + $table_name_wrapped = $this->wrapTableName($table); + $sql = "UPDATE $table_name_wrapped SET $column='$value';"; + drush_sql_register_post_sync_op($table.$column, dt("Replaces all values in $table table with the same random long string."), $sql); + } + + /** + * Truncates the session table. + */ + public function sanitizeSessions() { + // Seems quite portable (SQLite?) - http://en.wikipedia.org/wiki/Truncate_(SQL) + $table_name = $this->wrapTableName('sessions'); + $sql_sessions = "TRUNCATE TABLE $table_name;"; + drush_sql_register_post_sync_op('sessions', dt('Truncate Drupal\'s sessions table'), $sql_sessions); + } + + /** + * Sanitizes comments_field_data table. + */ + public function sanitizeComments() { + + $comments_enabled = $this->query("SHOW TABLES LIKE 'comment_field_data';"); + if (!$comments_enabled) { + return; + } + + $comments_table = $this->wrapTableName('comment_field_data'); + $sql_comments = "UPDATE $comments_table SET name='Anonymous', mail='', homepage='http://example.com' WHERE uid = 0;"; + drush_sql_register_post_sync_op('anon_comments', dt('Remove names and email addresses from anonymous user comments.'), $sql_comments); + + $sql_comments = "UPDATE $comments_table SET name=CONCAT('User', `uid`), mail=CONCAT('user+', `uid`, '@example.com'), homepage='http://example.com' WHERE uid <> 0;"; + drush_sql_register_post_sync_op('auth_comments', dt('Replace names and email addresses from authenticated user comments.'), $sql_comments); + } + + /** + * Wraps a table name in brackets if a database prefix is being used. + * + * @param string $table_name + * The name of the database table. + * + * @return string + * The (possibly wrapped) table name. + */ + public function wrapTableName($table_name) { + if ($this->wrap) { + $processed = '{' . $table_name . '}'; + } + else { + $processed = $table_name; + } + + return $processed; + } + + /** + * Executes a sql command using drush sqlq and returns the output. + * + * @param string $query + * The SQL query to execute. Must end in a semicolon! + * + * @return string + * The output of the query. + */ + protected function query($query) { + $current = drush_get_context('DRUSH_SIMULATE'); + drush_set_context('DRUSH_SIMULATE', FALSE); + $sql = drush_sql_get_class(); + $success = $sql->query($query); + $output = drush_shell_exec_output(); + drush_set_context('DRUSH_SIMULATE', $current); + + return $output; + } + +} + diff --git a/vendor/drush/drush/lib/Drush/Commands/core/StatusCommands.php b/vendor/drush/drush/lib/Drush/Commands/core/StatusCommands.php new file mode 100644 index 0000000000..2f21810c8f --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Commands/core/StatusCommands.php @@ -0,0 +1,58 @@ + '', 'format' => 'table', 'fields' => '']) { + $data = _core_site_status_table($options['project']); + + return new AssociativeList($data); + } +} + diff --git a/vendor/drush/drush/lib/Drush/ConfigAdapter.php b/vendor/drush/drush/lib/Drush/ConfigAdapter.php new file mode 100644 index 0000000000..3cd4f49694 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/ConfigAdapter.php @@ -0,0 +1,110 @@ +config = $config; + } + + /** + * @inheritdoc + */ + public function has($key) + { + return $this->config->has($key); + } + + /** + * @inheritdoc + */ + public function get($key, $defaultFallback = null) + { + return $this->config->get($key, $defaultFallback); + } + + /** + * @inheritdoc + */ + public function set($key, $value) + { + $this->config->set($key, $value); + } + + /** + * @inheritdoc + */ + public function import($data) + { + return $this->config->import($data); + } + + /** + * @inheritdoc + */ + public function replace($data) + { + $this->config->replace($data); + } + + /** + * @inheritdoc + */ + public function combine($data) + { + return $this->config->combine($data); + } + + /** + * @inheritdoc + */ + public function export() + { + return $this->config->export(); + } + + /** + * @inheritdoc + */ + public function hasDefault($key) + { + return $this->config->hasDefault($key); + } + + /** + * @inheritdoc + */ + public function getDefault($key, $defaultFallback = null) + { + return $this->config->getDefault($key, $defaultFallback); + } + + /** + * @inheritdoc + */ + public function setDefault($key, $value) + { + return $this->setDefault($key, $value); + } +} diff --git a/vendor/drush/drush/lib/Drush/Drupal/DrupalKernel.php b/vendor/drush/drush/lib/Drush/Drupal/DrupalKernel.php new file mode 100644 index 0000000000..39fa15c0f9 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Drupal/DrupalKernel.php @@ -0,0 +1,63 @@ +initializeSettings($request); + return $kernel; + } + + /** + * Add a service modifier to the container builder. + * + * The container is not compiled until $kernel->boot(), so there is a chance + * for clients to add compiler passes et. al. before then. + */ + public function addServiceModifier(ServiceModifierInterface $serviceModifier) { + drush_log(dt("add service modifier"), LogLevel::DEBUG); + $this->serviceModifiers[] = $serviceModifier; + } + + /** + * @inheritdoc + */ + protected function getContainerBuilder() { + drush_log(dt("get container builder"), LogLevel::DEBUG); + $container = parent::getContainerBuilder(); + foreach ($this->serviceModifiers as $serviceModifier) { + $serviceModifier->alter($container); + } + return $container; + } + /** + * Initializes the service container. + * + * @return \Symfony\Component\DependencyInjection\ContainerInterface + */ + protected function initializeContainer() { + if (empty($this->moduleList) && !$this->containerNeedsRebuild) { + $container_definition = $this->getCachedContainerDefinition(); + foreach ($this->serviceModifiers as $serviceModifier) { + if (!$serviceModifier->check($container_definition)) { + $this->invalidateContainer(); + break; + } + } + } + return parent::initializeContainer(); + } +} diff --git a/vendor/drush/drush/lib/Drush/Drupal/DrushServiceModifier.php b/vendor/drush/drush/lib/Drush/Drupal/DrushServiceModifier.php new file mode 100644 index 0000000000..14a8a292ee --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Drupal/DrushServiceModifier.php @@ -0,0 +1,33 @@ +register('drush.service.consolecommands', 'Drush\Command\ServiceCommandlist'); + $container->addCompilerPass(new FindCommandsCompilerPass('drush.service.consolecommands', 'drush.command')); + $container->register('drush.service.consolidationcommands', 'Drush\Command\ServiceCommandlist'); + $container->addCompilerPass(new FindCommandsCompilerPass('drush.service.consolidationcommands', 'consolidation.commandhandler')); + } + /** + * Checks existing service definitions for the presence of modification. + * + * @param $container_definition + * Cached container definition + * @return bool + */ + public function check($container_definition) { + return isset($container_definition['services']['drush.service.consolecommands']) && + isset($container_definition['services']['drush.service.consolidationcommands']); + } +} diff --git a/vendor/drush/drush/lib/Drush/Drupal/ExtensionDiscovery.php b/vendor/drush/drush/lib/Drush/Drupal/ExtensionDiscovery.php new file mode 100644 index 0000000000..5373adf156 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Drupal/ExtensionDiscovery.php @@ -0,0 +1,12 @@ +boot(), when Drupal's dependency injection container is being + * compiled. Since we cannot use the container at this point (since its + * initialization is not yet complete), we instead alter the definition of + * a storage class in the container to add more setter injection method + * calls to 'addCommandReference'. + * + * Later, after the container has been completely initialized, we can + * fetch the storage class from the DI container (perhaps also via + * injection from a reference in the container). At that point, we can + * request the list of Console commands that were added via the + * (delayed) call(s) to addCommandReference. + * + * Documentation: + * + * http://symfony.com/doc/2.7/components/dependency_injection/tags.html#create-a-compilerpass + */ +class FindCommandsCompilerPass implements CompilerPassInterface +{ + protected $storageClassId; + protected $tagId; + + public function __construct($storageClassId, $tagId) + { + $this->storageClassId = $storageClassId; + $this->tagId = $tagId; + } + + public function process(ContainerBuilder $container) + { + drush_log(dt("process !storage !tag", ['!storage' => $this->storageClassId, '!tag' => $this->tagId]), LogLevel::DEBUG); + // We expect that our called registered the storage + // class under the storage class id before adding this + // compiler pass, but we will test this presumption to be sure. + if (!$container->has($this->storageClassId)) { + drush_log(dt("storage class not registered"), LogLevel::DEBUG); + return; + } + + $definition = $container->findDefinition( + $this->storageClassId + ); + + $taggedServices = $container->findTaggedServiceIds( + $this->tagId + ); + foreach ($taggedServices as $id => $tags) { + drush_log(dt("found tagged service !id", ['!id' => $id]), LogLevel::DEBUG); + $definition->addMethodCall( + 'addCommandReference', + array(new Reference($id)) + ); + } + } +} diff --git a/vendor/drush/drush/lib/Drush/Drush.php b/vendor/drush/drush/lib/Drush/Drush.php new file mode 100644 index 0000000000..1a311b6f24 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Drush.php @@ -0,0 +1,413 @@ +setConfig(new ConfigAdapter(new DrushConfig())); + // TODO: static::$processManager->setConfigRuntime() + } + + return static::$processManager; + } + + /** + * Return the input object + * + * @return InputInterface + */ + public static function input() + { + if (!static::$input) { + static::$input = annotationcommand_adapter_input(); + } + + return static::$input; + } + + /** + * Return the output object + * + * @return OutputInterface + */ + public static function output() + { + if (!static::$output) { + static::$output = new DrushOutputAdapter(); + } + + return static::$output; + } + + /** + * Run a Drush command on a site alias (or @self). + * + * Tip: Use injected process manager instead of this method. See below. + * + * A class should use ProcessManagerAwareInterface / ProcessManagerAwareTrait + * in order to have the Process Manager injected by Drush's DI container. + * For example: + * + * use Consolidation\SiteProcess\ProcessManagerAwareTrait; + * use Consolidation\SiteProcess\ProcessManagerAwareInterface; + * + * abstract class DrushCommands implements ProcessManagerAwareInterface ... + * { + * use ProcessManagerAwareTrait; + * } + * + * Since DrushCommands already uses ProcessManagerAwareTrait, all Drush + * commands may use the process manager to call other Drush commands. + * Other classes will need to ensure that the process manager is injected + * as shown above. + * + * Note, however, that an alias record is required to use the `drush` method. + * The alias manager will provide an alias record, but the alias manager is + * not injected by default into Drush commands. In order to use it, it is + * necessary to use SiteAliasManagerAwareTrait: + * + * use Consolidation\SiteAlias\SiteAliasManagerAwareInterface; + * use Consolidation\SiteAlias\SiteAliasManagerAwareTrait; + * + * class SiteInstallCommands extends DrushCommands implements SiteAliasManagerAwareInterface + * { + * use SiteAliasManagerAwareTrait; + * + * public function install(array $profile, ...) + * { + * $selfRecord = $this->siteAliasManager()->getSelf(); + * $args = ['system.site', ...]; + * $options = ['yes' => true]; + * $process = $this->processManager()->drush(selfRecord, 'config-set', $args, $options); + * $process->mustRun(); + * } + * } + * + * Objects that are fetched from the DI container, or any Drush command will + * automatically be given a reference to the alias manager if SiteAliasManagerAwareTrait + * is used. Other objects will need to be manually provided with a reference + * to the alias manager once it is created (call $obj->setAliasManager($aliasManager);). + * + * Clients that are using Drush::drush(), and need a reference to the alias + * manager may use Drush::aliasManager(). + * + * @param SiteAliasInterface $siteAlias + * @param string $command + * @param array $args + * @param array $options + * @param array $options_double_dash + * @return SiteProcess + */ + public static function drush(SiteAliasInterface $siteAlias, $command, $args = [], $options = [], $options_double_dash = []) + { + return static::processManager()->drush($siteAlias, $command, $args, $options, $options_double_dash); + } + + /** + * Run a bash fragment on a site alias. U + * + * Use Drush::drush() instead of this method when calling Drush. + * Tip: Consider using injected process manager instead of this method. @see \Drush\Drush::drush(). + * + * @param SiteAliasInterface $siteAlias + * @param array $args + * @param array $options + * @param array $options_double_dash + * @return ProcessBase + */ + public static function siteProcess(SiteAliasInterface $siteAlias, $args = [], $options = [], $options_double_dash = []) + { + return static::processManager()->siteProcess($siteAlias, $args, $options, $options_double_dash); + } + + /** + * Run a bash fragment locally. + * + * The timeout parameter on this method doesn't work. It exists for compatibility with parent. + * Call this method to get a Process and then call setters as needed. + * + * Tip: Consider using injected process manager instead of this method. @see \Drush\Drush::drush(). + * + * @param string|array $commandline The command line to run + * @param string|null $cwd The working directory or null to use the working dir of the current PHP process + * @param array|null $env The environment variables or null to use the same environment as the current PHP process + * @param mixed|null $input The input as stream resource, scalar or \Traversable, or null for no input + * @param int|float|null $timeout The timeout in seconds or null to disable + * @param array $options An array of options for proc_open + * + * @return ProcessBase + * A wrapper around Symfony Process. + */ + public static function process($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60) + { + return static::processManager()->process($commandline, $cwd, $env, $input, $timeout); + } + + /** + * Create a Process instance from a commandline string. + * + * Tip: Consider using injected process manager instead of this method. @see \Drush\Drush::drush(). + * + * @param string $command The commandline string to run + * @param string|null $cwd The working directory or null to use the working dir of the current PHP process + * @param array|null $env The environment variables or null to use the same environment as the current PHP process + * @param mixed|null $input The input as stream resource, scalar or \Traversable, or null for no input + * @param int|float|null $timeout The timeout in seconds or null to disable + * @return Process + */ + public static function shell($command, $cwd = null, array $env = null, $input = null, $timeout = 60) + { + return static::processManager()->shell($command, $cwd, $env, $input, $timeout); + } + + /** + * Return the path to this Drush. + * + * @deprecated Inject configuration and use $this->getConfig()->drushScript(). + */ + public static function drushScript() + { + return \Drush\Drush::config()->drushScript(); + } + + /** + * Return 'true' if we are in simulated mode + * + * @deprecated Inject configuration and use $this->getConfig()->simulate(). + */ + public static function simulate() + { + return \Drush\Drush::config()->simulate(); + } + + /** + * Return 'true' if we are in backend mode + * + * @deprecated Inject configuration and use $this->getConfig()->backend(). + */ + public static function backend() + { + return \Drush\Drush::config()->backend(); + } + + /** + * Return 'true' if we are in affirmative mode + */ + public static function affirmative() + { + return drush_get_context('DRUSH_AFFIRMATIVE'); + } + + /** + * Return 'true' if we are in negative mode + */ + public static function negative() + { + return drush_get_context('DRUSH_NEGATIVE'); + } + + /** + * Return 'true' if we are in verbose mode + */ + public static function verbose() + { + return drush_get_context('DRUSH_VERBOSE'); + } + + /** + * Return 'true' if we are in debug mode + */ + public static function debug() + { + return drush_get_context('DRUSH_DEBUG'); + } + + // public static function bootstrapManager() + + // public static function bootstrap() + + public static function redispatchOptions() + { + return drush_redispatch_get_options(); + } +} diff --git a/vendor/drush/drush/lib/Drush/DrushConfig.php b/vendor/drush/drush/lib/Drush/DrushConfig.php new file mode 100644 index 0000000000..2f9235034a --- /dev/null +++ b/vendor/drush/drush/lib/Drush/DrushConfig.php @@ -0,0 +1,138 @@ + $value) { + $data->set("options.$key", $value); + unset($contextData[$key]); + } + foreach ($contextData as $key => $value) { + $data->set($key, $value); + } + return $data->export(); + } + + /** + * Return the default value for a given configuration item. + * + * @param string $key + * + * @return mixed + */ + public function hasDefault($key) + { + $value = $this->getDefault($key); + return $value != null; + } + + /** + * Return the default value for a given configuration item. + * + * @param string $key + * @param mixed $default + * + * @return mixed + */ + public function getDefault($key, $default = null) + { + return drush_get_option($key, $default, 'default'); + } + + /** + * Set the default value for a configuration setting. This allows us to + * set defaults either before or after more specific configuration values + * are loaded. Keeping defaults separate from current settings also + * allows us to determine when a setting has been overridden. + * + * @param string $key + * @param string $value + */ + public function setDefault($key, $value) + { + drush_set_default($key, $value); + } + + /** + * Determine whether we are in 'backend' mode + */ + public function backend() + { + return drush_get_context('DRUSH_BACKEND'); + } + + /** + * Determine whether we are in 'simulate' mode + */ + public function simulate() + { + return drush_get_context('DRUSH_SIMULATE'); + } + + public function drushScript() + { + return DRUSH_COMMAND; + } +} diff --git a/vendor/drush/drush/lib/Drush/Exceptions/UserAbortException.php b/vendor/drush/drush/lib/Drush/Exceptions/UserAbortException.php new file mode 100644 index 0000000000..f05ee0cb4c --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Exceptions/UserAbortException.php @@ -0,0 +1,14 @@ +parser = $parser; + $this->logger = $logger; + } + + /** + * {@inheritdoc} + */ + public function log($level, $message, array $context = array()) { + // Translate the RFC logging levels into their Drush counterparts, more or + // less. + // @todo ALERT, CRITICAL and EMERGENCY are considered show-stopping errors, + // and they should cause Drush to exit or panic. Not sure how to handle this, + // though. + switch ($level) { + case RfcLogLevel::ALERT: + case RfcLogLevel::CRITICAL: + case RfcLogLevel::EMERGENCY: + case RfcLogLevel::ERROR: + $error_type = LogLevel::ERROR; + break; + + case RfcLogLevel::WARNING: + $error_type = LogLevel::WARNING; + break; + + // TODO: RfcLogLevel::DEBUG should be 'debug' rather than 'notice'? + case RfcLogLevel::DEBUG: + case RfcLogLevel::INFO: + case RfcLogLevel::NOTICE: + $error_type = LogLevel::NOTICE; + break; + + // TODO: Unknown log levels that are not defined + // in Psr\Log\LogLevel or Drush\Log\LogLevel SHOULD NOT be used. See + // https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md + // We should convert these to 'notice'. + default: + $error_type = $level; + break; + } + + // Populate the message placeholders and then replace them in the message. + $message_placeholders = $this->parser->parseMessagePlaceholders($message, $context); + + // Filter out any placeholders that can not be cast to strings. + $message_placeholders = array_filter($message_placeholders, function ($element) { + return is_scalar($element) || is_callable([$element, '__toString']); + }); + + $message = empty($message_placeholders) ? $message : strtr($message, $message_placeholders); + + $this->logger->log($error_type, $message, $context); + } + +} diff --git a/vendor/drush/drush/lib/Drush/Log/LogLevel.php b/vendor/drush/drush/lib/Drush/Log/LogLevel.php new file mode 100644 index 0000000000..f032ef2d8f --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Log/LogLevel.php @@ -0,0 +1,32 @@ + $parent[$key]); + } + if (!isset($parent[$key]) || !is_array($parent[$key])) { + $parent[$key] = array(); + } + $parent = &$parent[$key]; + } + + // Handle PHP constants. + if (defined($value)) { + $value = constant($value); + } + + // Insert actual value. + if ($last == '') { + $last = count($parent); + } + if (isset($merge_item) && isset($parent[$last]) && is_array($parent[$last])) { + $parent[$last][$merge_item] = $value; + } + else { + $parent[$last] = $value; + } + } + return $info; + } + } + +} diff --git a/vendor/drush/drush/lib/Drush/Make/Parser/ParserInterface.php b/vendor/drush/drush/lib/Drush/Make/Parser/ParserInterface.php new file mode 100644 index 0000000000..d9e16a29d3 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Make/Parser/ParserInterface.php @@ -0,0 +1,31 @@ + $item) { + $array[BaseCaster::PREFIX_PROTECTED . $property] = $item; + } + } + + return $array; + } + + /** + * Casts \Drupal\Core\Field\FieldItemListInterface classes. + */ + public static function castFieldItemList($list_item, $array, $stub, $isNested) { + if (!$isNested) { + foreach ($list_item as $delta => $item) { + $array[BaseCaster::PREFIX_VIRTUAL . $delta] = $item; + } + } + + return $array; + } + + /** + * Casts \Drupal\Core\Field\FieldItemInterface classes. + */ + public static function castFieldItem($item, $array, $stub, $isNested) { + if (!$isNested) { + $array[BaseCaster::PREFIX_VIRTUAL . 'value'] = $item->getValue(); + } + + return $array; + } + + /** + * Casts \Drupal\Core\Config\Entity\ConfigEntityInterface classes. + */ + public static function castConfigEntity($entity, $array, $stub, $isNested) { + if (!$isNested) { + foreach ($entity->toArray() as $property => $value) { + $array[BaseCaster::PREFIX_PROTECTED . $property] = $value; + } + } + + return $array; + } + + /** + * Casts \Drupal\Core\Config\ConfigBase classes. + */ + public static function castConfig($config, $array, $stub, $isNested) { + if (!$isNested) { + foreach ($config->get() as $property => $value) { + $array[BaseCaster::PREFIX_VIRTUAL . $property] = $value; + } + } + + return $array; + } + + /** + * Casts \Drupal\Component\DependencyInjection\Container classes. + */ + public static function castContainer($container, $array, $stub, $isNested) { + if (!$isNested) { + $service_ids = $container->getServiceIds(); + sort($service_ids); + foreach ($service_ids as $service_id) { + $service = $container->get($service_id); + $array[BaseCaster::PREFIX_VIRTUAL . $service_id] = is_object($service) ? get_class($service) : $service; + } + } + + return $array; + } + +} diff --git a/vendor/drush/drush/lib/Drush/Psysh/DrushCommand.php b/vendor/drush/drush/lib/Drush/Psysh/DrushCommand.php new file mode 100644 index 0000000000..7e9ae0077b --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Psysh/DrushCommand.php @@ -0,0 +1,244 @@ +config = $config; + parent::__construct(); + } + + /** + * Get Category of this command. + */ + public function getCategory() { + return $this->category; + } + + /** + * Sets the category title. + * + * @param string $category_title + */ + public function setCategory($category_title) { + $this->category = $category_title; + } + + /** + * {@inheritdoc} + */ + protected function configure() { + $this + ->setName($this->config['command']) + ->setAliases($this->buildAliasesFromConfig()) + ->setDefinition($this->buildDefinitionFromConfig()) + ->setDescription($this->config['description']) + ->setHelp($this->buildHelpFromConfig()); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) { + $args = $input->getArguments(); + $first = array_shift($args); + + // If the first argument is an alias, assign the next argument as the + // command. + if (strpos($first, '@') === 0) { + $alias = $first; + $command = array_shift($args); + } + // Otherwise, default the alias to '@self' and use the first argument as the + // command. + else { + $alias = '@self'; + $command = $first; + } + + $options = $input->getOptions(); + // Force the 'backend' option to TRUE. + $options['backend'] = TRUE; + + $return = drush_invoke_process($alias, $command, array_values($args), $options, ['interactive' => TRUE]); + + if ($return['error_status'] > 0) { + foreach ($return['error_log'] as $error_type => $errors) { + $output->write($errors); + } + // Add a newline after so the shell returns on a new line. + $output->writeln(''); + } + else { + $output->page(drush_backend_get_result()); + } + } + + /** + * Extract Drush command aliases from config array. + * + * @return array + * The command aliases. + */ + protected function buildAliasesFromConfig() { + return !empty($this->config['aliases']) ? $this->config['aliases'] : []; + } + + /** + * Build a command definition from Drush command configuration array. + * + * Currently, adds all non-hidden arguments and options, and makes a decent + * effort to guess whether an option accepts a value or not. It isn't always + * right :P + * + * @return array + * the command definition. + */ + protected function buildDefinitionFromConfig() { + $definitions = []; + + if (isset($this->config['arguments']) && !empty($this->config['arguments'])) { + $required_args = $this->config['required-arguments']; + + if ($required_args === FALSE) { + $required_args = 0; + } + elseif ($required_args === TRUE) { + $required_args = count($this->config['arguments']); + } + + foreach ($this->config['arguments'] as $name => $argument) { + if (!is_array($argument)) { + $argument = ['description' => $argument]; + } + + if (!empty($argument['hidden'])) { + continue; + } + + $input_type = ($required_args-- > 0) ? InputArgument::REQUIRED : InputArgument::OPTIONAL; + + $definitions[] = new InputArgument($name, $input_type, $argument['description'], NULL); + } + } + + // First create all global options. + $options = $this->config['options'] + drush_get_global_options(); + + // Add command specific options. + $definitions = array_merge($definitions, $this->createInputOptionsFromConfig($options)); + + return $definitions; + } + + /** + * Creates input definitions from command options. + * + * @param array $options_config + * + * @return \Symfony\Component\Console\Input\InputInterface[] + */ + protected function createInputOptionsFromConfig(array $options_config) { + $definitions = []; + + foreach ($options_config as $name => $option) { + // Some commands will conflict. + if (in_array($name, ['help', 'command'])) { + continue; + } + + if (!is_array($option)) { + $option = ['description' => $option]; + } + + if (!empty($option['hidden'])) { + continue; + } + + // @todo: Figure out if there's a way to detect InputOption::VALUE_NONE + // (i.e. flags) via the config array. + if (isset($option['value']) && $option['value'] === 'required') { + $input_type = InputOption::VALUE_REQUIRED; + } + else { + $input_type = InputOption::VALUE_OPTIONAL; + } + + $definitions[] = new InputOption($name, !empty($option['short-form']) ? $option['short-form'] : '', $input_type, $option['description']); + } + + return $definitions; + } + + /** + * Build a command help from the Drush configuration array. + * + * Currently it's a word-wrapped description, plus any examples provided. + * + * @return string + * The help string. + */ + protected function buildHelpFromConfig() { + $help = wordwrap($this->config['description']); + + $examples = []; + foreach ($this->config['examples'] as $ex => $def) { + // Skip empty examples and things with obvious pipes... + if (($ex === '') || (strpos($ex, '|') !== FALSE)) { + continue; + } + + $ex = preg_replace('/^drush\s+/', '', $ex); + $examples[$ex] = $def; + } + + if (!empty($examples)) { + $help .= "\n\ne.g."; + + foreach ($examples as $ex => $def) { + $help .= sprintf("\n// %s\n", wordwrap(OutputFormatter::escape($def), 75, "\n// ")); + $help .= sprintf(">>> %s\n", OutputFormatter::escape($ex)); + } + } + + return $help; + } + +} diff --git a/vendor/drush/drush/lib/Drush/Psysh/DrushHelpCommand.php b/vendor/drush/drush/lib/Drush/Psysh/DrushHelpCommand.php new file mode 100644 index 0000000000..9a3961f13c --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Psysh/DrushHelpCommand.php @@ -0,0 +1,126 @@ +setName('help') + ->setAliases(['?']) + ->setDefinition([ + new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', NULL), + ]) + ->setDescription('Show a list of commands. Type `help [foo]` for information about [foo].'); + } + + /** + * Helper for setting a subcommand to retrieve help for. + * + * @param \Symfony\Component\Console\Command\Command $command + */ + public function setCommand(Command $command) { + $this->command = $command; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) { + if ($this->command !== NULL) { + // Help for an individual command. + $output->page($this->command->asText()); + $this->command = NULL; + } + elseif ($name = $input->getArgument('command_name')) { + // Help for an individual command. + $output->page($this->getApplication()->get($name)->asText()); + } + else { + $categories = []; + + // List available commands. + $commands = $this->getApplication()->all(); + + // Find the alignment width. + $width = 0; + foreach ($commands as $command) { + $width = strlen($command->getName()) > $width ? strlen($command->getName()) : $width; + } + $width += 2; + + foreach ($commands as $name => $command) { + if ($name !== $command->getName()) { + continue; + } + + if ($command->getAliases()) { + $aliases = sprintf(' Aliases: %s', implode(', ', $command->getAliases())); + } + else { + $aliases = ''; + } + + if ($command instanceof DrushCommand) { + $category = (string) $command->getCategory(); + } + else { + $category = static::PSYSH_CATEGORY; + } + + if (!isset($categories[$category])) { + $categories[$category] = []; + } + + $categories[$category][] = sprintf(" %-${width}s %s%s", $name, $command->getDescription(), $aliases); + } + + $messages = []; + + foreach ($categories as $name => $category) { + $messages[] = ''; + $messages[] = sprintf('%s', OutputFormatter::escape($name)); + foreach ($category as $message) { + $messages[] = $message; + } + } + + $output->page($messages); + } + } + +} diff --git a/vendor/drush/drush/lib/Drush/Psysh/Shell.php b/vendor/drush/drush/lib/Drush/Psysh/Shell.php new file mode 100644 index 0000000000..7e065af8f3 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Psysh/Shell.php @@ -0,0 +1,62 @@ +getCommandFromInput($input)) { + return $this->get($name); + } + } + + /** + * Check whether a command is set for the current input string. + * + * @param string $input + * + * @return bool True if the shell has a command for the given input. + */ + protected function hasCommand($input) { + if ($name = $this->getCommandFromInput($input)) { + return $this->has($name); + } + + return false; + } + + /** + * Get the command from the current input, takes aliases into account. + * + * @param string $input + * The raw input + * + * @return string|NULL + * The current command. + */ + protected function getCommandFromInput($input) { + // Remove the alias from the start of the string before parsing and + // returning the command. Essentially, when choosing a command, we're + // ignoring the site alias. + $input = preg_replace('|^\@[^\s]+|', '', $input); + + $input = new StringInput($input); + return $input->getFirstArgument(); + } + +} diff --git a/vendor/drush/drush/lib/Drush/Queue/Queue6.php b/vendor/drush/drush/lib/Drush/Queue/Queue6.php new file mode 100644 index 0000000000..2b3a552079 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Queue/Queue6.php @@ -0,0 +1,28 @@ + $queue) { + static::$queues[$name]['worker callback'] = $queue['cron']['callback']; + if (isset($queue['cron']['time'])) { + static::$queues[$name]['time'] = $queue['cron']['time']; + } + } + } + return static::$queues; + } + + /** + * {@inheritdoc} + * + * @return \DrupalQueueInterface + */ + public function getQueue($name) { + return DrupalQueue::get($name); + } + + /** + * {@inheritdoc} + */ + public function run($name, $time_limit = 0) { + $info = $this->getInfo($name); + $function = $info['worker callback']; + $end = time() + $time_limit; + $queue = $this->getQueue($name); + $count = 0; + + while ((!$time_limit || time() < $end) && ($item = $queue->claimItem())) { + try { + drush_log(dt('Processing item @id from @name queue.', array('@name' => $name, 'id' => $item->item_id)), LogLevel::INFO); + $function($item->data); + $queue->deleteItem($item); + $count++; + } + catch (\Exception $e) { + // In case of exception log it and leave the item in the queue + // to be processed again later. + drush_set_error('DRUSH_QUEUE_EXCEPTION', $e->getMessage()); + } + } + + return $count; + } + +} diff --git a/vendor/drush/drush/lib/Drush/Queue/Queue8.php b/vendor/drush/drush/lib/Drush/Queue/Queue8.php new file mode 100644 index 0000000000..b0fa0a6679 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Queue/Queue8.php @@ -0,0 +1,83 @@ +workerManager = $manager ?: \Drupal::service('plugin.manager.queue_worker'); + } + + /** + * {@inheritdoc} + */ + public function getQueues() { + if (!isset(static::$queues)) { + static::$queues = array(); + foreach ($this->workerManager->getDefinitions() as $name => $info) { + static::$queues[$name] = $info; + } + } + return static::$queues; + } + + /** + * {@inheritdoc} + * + * @return \Drupal\Core\Queue\QueueInterface + */ + public function getQueue($name) { + return \Drupal::queue($name); + } + + /** + * {@inheritdoc} + */ + public function run($name, $time_limit = 0) { + $worker = $this->workerManager->createInstance($name); + $end = time() + $time_limit; + $queue = $this->getQueue($name); + $count = 0; + + while ((!$time_limit || time() < $end) && ($item = $queue->claimItem())) { + try { + drush_log(dt('Processing item @id from @name queue.', array('@name' => $name, '@id' => $item->item_id)), LogLevel::INFO); + $worker->processItem($item->data); + $queue->deleteItem($item); + $count++; + } + catch (RequeueException $e) { + // The worker requested the task to be immediately requeued. + $queue->releaseItem($item); + } + catch (SuspendQueueException $e) { + // If the worker indicates there is a problem with the whole queue, + // release the item and stop further processing. + $queue->releaseItem($item); + drush_set_error('DRUSH_SUSPEND_QUEUE_EXCEPTION', $e->getMessage()); + break; + } + catch (\Exception $e) { + // In case of any other kind of exception, log it and leave the item + // in the queue to be processed again later. + drush_set_error('DRUSH_QUEUE_EXCEPTION', $e->getMessage()); + } + } + + return $count; + } + +} diff --git a/vendor/drush/drush/lib/Drush/Queue/Queue9.php b/vendor/drush/drush/lib/Drush/Queue/Queue9.php new file mode 100644 index 0000000000..63480c73d2 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Queue/Queue9.php @@ -0,0 +1,11 @@ +getQueues()) as $name) { + $q = $this->getQueue($name); + $result[$name] = array( + 'queue' => $name, + 'items' => $q->numberOfItems(), + 'class' => get_class($q), + ); + } + return $result; + } + + /** + * {@inheritdoc} + */ + public function getInfo($name) { + $queues = $this->getQueues(); + if (!isset($queues[$name])) { + throw new QueueException(dt('Could not find the !name queue.', array('!name' => $name))); + } + return $queues[$name]; + } + +} diff --git a/vendor/drush/drush/lib/Drush/Queue/QueueException.php b/vendor/drush/drush/lib/Drush/Queue/QueueException.php new file mode 100644 index 0000000000..9f477e45e4 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Queue/QueueException.php @@ -0,0 +1,5 @@ +perms)) { + $perms = db_result(db_query("SELECT perm FROM {permission} pm LEFT JOIN {role} r ON r.rid = pm.rid WHERE r.rid = '%d'", $this->rid)); + $role_perms = explode(", ", $perms); + $this->perms = array_filter($role_perms); + } + return $this->perms; + } + + public function getModulePerms($module) { + return module_invoke($module, 'perm'); + } + + public function role_create($role_machine_name, $role_human_readable_name = '') { + $this->_admin_user_role_op($role_machine_name, t('Add role')); + return TRUE; + } + + public function delete() { + $this->_admin_user_role_op($this->rid, t('Delete role')); + } + + function _admin_user_role_op($role_machine_name, $op) { + // c.f. http://drupal.org/node/283261 + require_once(drupal_get_path('module', 'user') . "/user.admin.inc"); + + $form_id = "user_admin_new_role"; + $form_values = array(); + $form_values["name"] = $role_machine_name; + $form_values["op"] = $op; + $form_state = array(); + $form_state["values"] = $form_values; + + drupal_execute($form_id, $form_state); + } + + public function grant_permissions($perms_to_add) { + $perms = $this->getPerms(); + $this->perms = array_unique(array_merge($this->perms, $perms_to_add)); + $this->updatePerms(); + } + + public function revoke_permissions($perms_to_remove) { + $perms = $this->getPerms(); + $this->perms = array_diff($this->perms, $perms_to_remove); + $this->updatePerms(); + } + + function updatePerms() { + $new_perms = implode(", ", $this->perms); + drush_op('db_query', "UPDATE {permission} SET perm = '%s' WHERE rid= %d", $new_perms, $this->rid); + } +} diff --git a/vendor/drush/drush/lib/Drush/Role/Role7.php b/vendor/drush/drush/lib/Drush/Role/Role7.php new file mode 100644 index 0000000000..af8d46c266 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Role/Role7.php @@ -0,0 +1,35 @@ +rid => $this->name)); + return array_keys($perms[$this->rid]); + } + + public function getModulePerms($module) { + $perms = module_invoke($module, 'permission'); + return $perms ? array_keys($perms) : array(); + } + + public function role_create($role_machine_name, $role_human_readable_name = '') { + return user_role_save((object)array('name' => $role_machine_name)); + } + + public function delete() { + user_role_delete($this->rid); + } + + public function grant_permissions($perms) { + return drush_op('user_role_grant_permissions', $this->rid, $perms); + } + + public function revoke_permissions($perms) { + return drush_op('user_role_revoke_permissions', $this->rid, $perms); + } +} diff --git a/vendor/drush/drush/lib/Drush/Role/Role8.php b/vendor/drush/drush/lib/Drush/Role/Role8.php new file mode 100644 index 0000000000..0d6ef79eaf --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Role/Role8.php @@ -0,0 +1,66 @@ + $role_machine_name, + 'label' => $role_human_readable_name, + ), 'user_role'); + $role->save(); + return $role; + } + + public function getPerms() { + $role = Role::load($this->rid); + $perms = $role->getPermissions(); + // $perms = user_role_permissions(array($this->rid => $this->name)); + return $perms; + } + + public function getAllModulePerms() { + $perms = \Drupal::service('user.permissions')->getPermissions(); + return array_keys($perms); + } + + public function getModulePerms($module) { + $module_perms = array(); + $perms = \Drupal::service('user.permissions')->getPermissions(); + foreach ($perms as $name => $perm) { + if ($perm['provider'] == $module) { + $module_perms[] = $name; + } + } + return $module_perms; + } + + public function delete() { + $role = Role::load($this->rid); + $role->delete(); + } + + public function grant_permissions($perms) { + return drush_op('user_role_grant_permissions', $this->rid, $perms); + } + + public function revoke_permissions($perms) { + return drush_op('user_role_revoke_permissions', $this->rid, $perms); + } +} diff --git a/vendor/drush/drush/lib/Drush/Role/Role9.php b/vendor/drush/drush/lib/Drush/Role/Role9.php new file mode 100644 index 0000000000..7d47eec7ce --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Role/Role9.php @@ -0,0 +1,8 @@ + name pairs. + */ + public $roles; + + /** + * This constructor will allow the role to be selected either + * via the role id or via the role name. + */ + public function __construct($rid = NULL) { + if ($rid === NULL) { + $rid = $this->anonymousRole(); + } + $this->roles = user_roles(); + if (!is_numeric($rid)) { + $role_name = $rid; + if (in_array($role_name, $this->roles)) { + $rid = array_search($role_name, $this->roles); + } + } + + if (isset($this->roles[$rid])) { + $this->rid = $rid; + // In D8+ Role is an object. + $this->name = is_object($this->roles[$rid]) ? $this->roles[$rid]->label() : $this->roles[$rid]; + } + else { + throw new RoleException(dt('Could not find the role: !role', array('!role' => $rid))); + } + } + + abstract public function anonymousRole(); + + /* + * Get all perms for a given Role. + */ + public function getPerms() { + return array(); + } + + /* + * Get all perms for a given module. + */ + public function getModulePerms($module) { + return array(); + } + + /* + * Get all permissions site-wide. + */ + public function getAllModulePerms() { + $permissions = array(); + drush_include_engine('drupal', 'environment'); + $module_list = drush_module_list(); + ksort($module_list); + foreach ($module_list as $module) { + if ($perms = $this->getModulePerms($module)) { + $permissions = array_merge($permissions, $perms); + } + } + return $permissions; + } + + public function role_create($role_machine_name, $role_human_readable_name = '') { + } + + public function delete() { + } + + public function add($perm) { + $perms = $this->getPerms(); + if (!in_array($perm, $perms)) { + $this->grant_permissions(array($perm)); + return TRUE; + } + else { + drush_log(dt('"!role" already has the permission "!perm"', array( + '!perm' => $perm, + '!role' => $this->name, + )), 'ok'); + return FALSE; + } + } + + public function remove($perm) { + $perms = $this->getPerms(); + if (in_array($perm, $perms)) { + $this->revoke_permissions(array($perm)); + return TRUE; + } + else { + drush_log(dt('"!role" does not have the permission "!perm"', array( + '!perm' => $perm, + '!role' => $this->name, + )), 'ok'); + return FALSE; + } + } + + public function grant_permissions($perms) { + } + + public function revoke_permissions($perms) { + } +} diff --git a/vendor/drush/drush/lib/Drush/Role/RoleException.php b/vendor/drush/drush/lib/Drush/Role/RoleException.php new file mode 100644 index 0000000000..5751bf5273 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Role/RoleException.php @@ -0,0 +1,5 @@ +getAlias($name); + } + + /** + * @inheritdoc + */ + public function getSelf() + { + return $this->getAlias('@self'); + } + + /** + * @inheritdoc + */ + public function getAlias($aliasName) + { + $record = drush_sitealias_get_record($aliasName); + + // TODO: Convert $record to new site alias layout + + return new SiteAlias($record, $aliasName); + } + + /** + * @inheritdoc + */ + public function getMultiple($name = '') + { + // Not supported + return false; + } + + /** + * @inheritdoc + */ + public function listAllFilePaths($location = '') + { + // Not supported + return []; + } +} diff --git a/vendor/drush/drush/lib/Drush/SiteAlias/AliasManagerAdapterInjector.php b/vendor/drush/drush/lib/Drush/SiteAlias/AliasManagerAdapterInjector.php new file mode 100644 index 0000000000..864626ad38 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/SiteAlias/AliasManagerAdapterInjector.php @@ -0,0 +1,18 @@ +setSiteAliasManager(Drush::aliasManager()); + } + } +} diff --git a/vendor/drush/drush/lib/Drush/SiteAlias/ProcessManager.php b/vendor/drush/drush/lib/Drush/SiteAlias/ProcessManager.php new file mode 100644 index 0000000000..0060946cc3 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/SiteAlias/ProcessManager.php @@ -0,0 +1,159 @@ +drushSiteProcess($siteAlias, $args, $options, $options_double_dash); + } + + /** + * drushSiteProcess should be avoided in favor of the drush method above. + * drushSiteProcess exists specifically for use by the RedispatchHook, + * which does not have specific knowledge about which argument is the command. + * + * @param SiteAliasInterface $siteAlias + * @param array $args + * @param array $options + * @param array $options_double_dash + * @return ProcessBase + */ + public function drushSiteProcess(SiteAliasInterface $siteAlias, $args = [], $options = [], $options_double_dash = []) + { + // Fill in the root and URI from the site alias, if the caller + // did not already provide them in $options. + if ($siteAlias->has('uri')) { + $options += [ 'uri' => $siteAlias->uri(), ]; + } + if ($siteAlias->hasRoot()) { + $options += [ 'root' => $siteAlias->root(), ]; + } + + // The executable is always 'drush' (at some path or another) + array_unshift($args, $this->drushScript($siteAlias)); + + return $this->siteProcess($siteAlias, $args, $options, $options_double_dash); + } + + /** + * Determine the path to Drush to use + */ + public function drushScript(SiteAliasInterface $siteAlias) + { + $defaultDrushScript = 'drush'; + + // If the site alias has 'paths.drush-script', always use that. + if ($siteAlias->has('paths.drush-script')) { + return $siteAlias->get('paths.drush-script'); + } + + // If the provided site alias is for a remote site / container et. al., + // then use the 'drush' in the $PATH. + if ($this->hasTransport($siteAlias)) { + return $defaultDrushScript; + } + + // If the target is a local Drupal site that has a vendor/bin/drush, + // then use that. + if ($siteAlias->hasRoot()) { + $localDrushScript = Path::join($siteAlias->root(), 'vendor/bin/drush'); + if (file_exists($localDrushScript)) { + return $localDrushScript; + } + } + + // Otherwise, use the path to the version of Drush that is running + // right now (if available). + return $this->getConfig()->get('runtime.drush-script', $defaultDrushScript); + } + + /** + * @inheritdoc + * + * Use Drush::drush() or ProcessManager::drush() instead of this method + * when calling Drush. + */ + public function siteProcess(SiteAliasInterface $siteAlias, $args = [], $options = [], $optionsPassedAsArgs = []) + { + $process = parent::siteProcess($siteAlias, $args, $options, $optionsPassedAsArgs); + return $this->configureProcess($process); + } + + /** + * Run a bash fragment locally. + * + * The timeout parameter on this method doesn't work. It exists for compatibility with parent. + * Call this method to get a Process and then call setters as needed. + * + * @param array $commandline The command line to run with arguments as separate items in an array + * @param string|null $cwd The working directory or null to use the working dir of the current PHP process + * @param array|null $env The environment variables or null to use the same environment as the current PHP process + * @param mixed|null $input The input as stream resource, scalar or \Traversable, or null for no input + * @param int|float|null $timeout The timeout in seconds or null to disable + * + * @return ProcessBase + * A wrapper around Symfony Process. + */ + public function process($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60) + { + $process = parent::process($commandline, $cwd, $env, $input, $timeout); + return $this->configureProcess($process); + } + + /** + * Create a Process instance from a commandline string. + * @param string $command The commandline string to run + * @param string|null $cwd The working directory or null to use the working dir of the current PHP process + * @param array|null $env The environment variables or null to use the same environment as the current PHP process + * @param mixed|null $input The input as stream resource, scalar or \Traversable, or null for no input + * @param int|float|null $timeout The timeout in seconds or null to disable + * @return Process + */ + public function shell($command, $cwd = null, array $env = null, $input = null, $timeout = 60) + { + $process = parent::shell($command, $cwd, $env, $input, $timeout); + return $this->configureProcess($process); + } + + /** + * configureProcess sets up a process object so that it is ready to use. + */ + protected static function configureProcess(ProcessBase $process) + { + $process->setSimulated(Drush::simulate()); + $process->setVerbose(Drush::verbose()); + $process->inheritEnvironmentVariables(); + $process->setLogger(Drush::logger()); + $process->setRealtimeOutput(new SymfonyStyle(Drush::input(), Drush::output())); + $process->setTimeout(Drush::getTimeout()); + return $process; + } +} diff --git a/vendor/drush/drush/lib/Drush/SiteAlias/ProcessManagerInjector.php b/vendor/drush/drush/lib/Drush/SiteAlias/ProcessManagerInjector.php new file mode 100644 index 0000000000..455fe965c3 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/SiteAlias/ProcessManagerInjector.php @@ -0,0 +1,19 @@ +setProcessManager(Drush::processManager()); + } + } +} diff --git a/vendor/drush/drush/lib/Drush/Sql/Sql6.php b/vendor/drush/drush/lib/Drush/Sql/Sql6.php new file mode 100644 index 0000000000..1091f1340b --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Sql/Sql6.php @@ -0,0 +1,45 @@ + $type)), LogLevel::BOOTSTRAP); + return FALSE; + } + } + else { + drush_log(dt('!type database type is unsupported.', array('!type' => $type)), LogLevel::BOOTSTRAP); + return FALSE; + } + return TRUE; + } + +} diff --git a/vendor/drush/drush/lib/Drush/Sql/Sql7.php b/vendor/drush/drush/lib/Drush/Sql/Sql7.php new file mode 100644 index 0000000000..d1994e1e42 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Sql/Sql7.php @@ -0,0 +1,26 @@ +db_spec = $db_spec; + } + + /* + * Get the current $db_spec. + */ + public function db_spec() { + return $this->db_spec; + } + + /** + * The unix command used to connect to the database. + * @return string + */ + public function command() {} + + /** + * A string for connecting to a database. + * + * @param bool $hide_password + * If TRUE, DBMS should try to hide password from process list. + * On mysql, that means using --defaults-extra-file to supply the user+password. + * + * @return string + */ + public function connect($hide_password = TRUE) { + return trim($this->command() . ' ' . $this->creds($hide_password) . ' ' . drush_get_option('extra', $this->query_extra)); + } + + + /* + * Execute a SQL dump and return the path to the resulting dump file. + * + * @param string|bool @file + * The path where the dump file should be stored. If TRUE, generate a path + * based on usual backup directory and current date. + */ + public function dump($file = '') { + $file_suffix = ''; + $table_selection = $this->get_expanded_table_selection(); + $file = $this->dumpFile($file); + $cmd = $this->dumpCmd($table_selection); + // Gzip the output from dump command(s) if requested. + if (drush_get_option('gzip')) { + $cmd .= ' | gzip -f'; + $file_suffix .= '.gz'; + } + if ($file) { + $file .= $file_suffix; + $cmd .= ' > ' . drush_escapeshellarg($file); + } + + // Avoid the php memory of the $output array in drush_shell_exec(). + if (!$return = drush_op_system($cmd)) { + if ($file) { + drush_log(dt('Database dump saved to !path', array('!path' => $file)), LogLevel::SUCCESS); + drush_backend_set_result($file); + } + } + else { + return drush_set_error('DRUSH_SQL_DUMP_FAIL', 'Database dump failed'); + } + } + + /* + * Build bash for dumping a database. + * + * @param array $table_selection + * Supported keys: 'skip', 'structure', 'tables'. + * @return string + * One or more mysqldump/pg_dump/sqlite3/etc statements that are ready for executing. + * If multiple statements are needed, enclose in parenthesis. + */ + public function dumpCmd($table_selection) {} + + /* + * Generate a path to an output file for a SQL dump when needed. + * + * @param string|bool @file + * If TRUE, generate a path based on usual backup directory and current date. + * Otherwise, just return the path that was provided. + */ + public function dumpFile($file) { + $database = $this->db_spec['database']; + + // $file is passed in to us usually via --result-file. If the user + // has set $options['result-file'] = TRUE, then we + // will generate an SQL dump file in the same backup + // directory that pm-updatecode uses. + if ($file) { + if ($file === TRUE) { + // User did not pass a specific value for --result-file. Make one. + $backup = drush_include_engine('version_control', 'backup'); + $backup_dir = $backup->prepare_backup_dir($database); + if (empty($backup_dir)) { + $backup_dir = drush_find_tmp(); + } + $file = Path::join($backup_dir, '@DATABASE_@DATE.sql'); + } + $file = str_replace(array('@DATABASE', '@DATE'), array($database, gmdate('Ymd_His')), $file); + } + return $file; + } + + /** + * Execute a SQL query. + * + * Note: This is an API function. Try to avoid using drush_get_option() and instead + * pass params in. If you don't want to query results to print during --debug then + * provide a $result_file whose value can be drush_bit_bucket(). + * + * @param string $query + * The SQL to be executed. Should be NULL if $input_file is provided. + * @param string $input_file + * A path to a file containing the SQL to be executed. + * @param string $result_file + * A path to save query results to. Can be drush_bit_bucket() if desired. + * + * @return + * TRUE on success, FALSE on failure + */ + public function query($query, $input_file = NULL, $result_file = '') { + $input_file_original = $input_file; + if ($input_file && drush_file_is_tarball($input_file)) { + if (drush_shell_exec('gzip -d %s', $input_file)) { + $input_file = trim($input_file, '.gz'); + } + else { + return drush_set_error(dt('Failed to decompress input file.')); + } + } + + // Save $query to a tmp file if needed. We will redirect it in. + if (!$input_file) { + $query = $this->query_prefix($query); + $query = $this->query_format($query); + $input_file = drush_save_data_to_temp_file($query); + } + + $parts = array( + $this->command(), + $this->creds(), + $this->silent(), // This removes column header and various helpful things in mysql. + drush_get_option('extra', $this->query_extra), + $this->query_file, + drush_escapeshellarg($input_file), + ); + $exec = implode(' ', $parts); + + if ($result_file) { + $exec .= ' > '. drush_escapeshellarg($result_file); + } + + // In --verbose mode, drush_shell_exec() will show the call to mysql/psql/sqlite, + // but the sql query itself is stored in a temp file and not displayed. + // We show the query when --debug is used and this function created the temp file. + if ((drush_get_context('DRUSH_DEBUG') || drush_get_context('DRUSH_SIMULATE')) && empty($input_file_original)) { + drush_log('sql-query: ' . $query, LogLevel::NOTICE); + } + + $success = drush_shell_exec($exec); + + if ($success && drush_get_option('file-delete')) { + drush_op('drush_delete_dir', $input_file); + } + + return $success; + } + + /* + * A string to add to the command when queries should not print their results. + */ + public function silent() {} + + + public function query_prefix($query) { + // Inject table prefixes as needed. + if (drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_DATABASE)) { + // Enable prefix processing which can be dangerous so off by default. See http://drupal.org/node/1219850. + if (drush_get_option('db-prefix')) { + $drupal_major_version = drush_drupal_major_version(); + if ($drupal_major_version >= 8) { + $query = \Drupal\Core\Database\Database::getConnection()->prefixTables($query); + } + elseif ($drupal_major_version == 7) { + $query = \Database::getConnection()->prefixTables($query); + } + else { + $query = db_prefix_tables($query); + } + } + } + return $query; + } + + + public function query_format($query) { + return $query; + } + + /** + * Drop specified database. + * + * @param array $tables + * An array of table names + * @return boolean + * True if successful, FALSE if failed. + */ + public function drop($tables) { + $return = TRUE; + if ($tables) { + $sql = 'DROP TABLE '. implode(', ', $tables); + $return = $this->query($sql); + } + return $return; + } + + /** + * Build a SQL string for dropping and creating a database. + * + * @param string dbname + * The database name. + * @param boolean $quoted + * Quote the database name. Mysql uses backticks to quote which can cause problems + * in a Windows shell. Set TRUE if the CREATE is not running on the bash command line. + */ + public function createdb_sql($dbname, $quoted = FALSE) {} + + /** + * Create a new database. + * + * @param boolean $quoted + * Quote the database name. Mysql uses backticks to quote which can cause problems + * in a Windows shell. Set TRUE if the CREATE is not running on the bash command line. + * @return boolean + * True if successful, FALSE otherwise. + */ + public function createdb($quoted = FALSE) { + $dbname = $this->db_spec['database']; + $sql = $this->createdb_sql($dbname, $quoted); + // Adjust connection to allow for superuser creds if provided. + $this->su(); + return $this->query($sql); + } + + /** + * Drop all tables (if DB exists) or CREATE target database. + * + * return boolean + * TRUE or FALSE depending on success. + */ + public function drop_or_create() { + if ($this->db_exists()) { + return $this->drop($this->listTablesQuoted()); + } + else { + return $this->createdb(); + } + } + + /* + * Determine if the specified DB already exists. + * + * @return bool + */ + public function db_exists() {} + + public function delete() {} + + /** + * Build a fragment connection parameters. + * + * @param bool $hide_password + * If TRUE, DBMS should try to hide password from process list. + * On mysql, that means using --defaults-extra-file to supply the user+password. + * @return string + */ + public function creds($hide_password = TRUE) {} + + /** + * The active database driver. + * @return string + */ + public function scheme() { + return $this->db_spec['driver']; + } + + /** + * Get a list of all table names and expand input that may contain + * wildcards (`*`) if necessary so that the array returned only contains valid + * table names i.e. actual tables that exist, without a wildcard. + * + * @return array + * An array of tables with each table name in the appropriate + * element of the array. + */ + public function get_expanded_table_selection() { + $table_selection = drush_sql_get_table_selection(); + // Get the existing table names in the specified database. + $db_tables = $this->listTables(); + if (isset($table_selection['skip'])) { + $table_selection['skip'] = _drush_sql_expand_and_filter_tables($table_selection['skip'], $db_tables); + } + if (isset($table_selection['structure'])) { + $table_selection['structure'] = _drush_sql_expand_and_filter_tables($table_selection['structure'], $db_tables); + } + if (isset($table_selection['tables'])) { + $table_selection['tables'] = _drush_sql_expand_and_filter_tables($table_selection['tables'], $db_tables); + } + return $table_selection; + } + + /** + * Extract the name of all existing tables in the given database. + * + * @return array + * An array of table names which exist in the current database. + */ + public function listTables() {} + + /** + * Extract the name of all existing tables in the given database. + * + * @return array + * An array of table names which exist in the current database, + * appropriately quoted for the RDMS. + */ + public function listTablesQuoted() { + return $this->listTables(); + } + + /* + * Helper method to turn associative array into options with values. + * + * @return string + * A bash fragment. + */ + public function params_to_options($parameters) { + // Turn each parameter into a valid parameter string. + $parameter_strings = array(); + foreach ($parameters as $key => $value) { + // Only escape the values, not the keys or the rest of the string. + $value = drush_escapeshellarg($value); + $parameter_strings[] = "--$key=$value"; + } + + // Join the parameters and return. + return implode(' ', $parameter_strings); + } + + /** + * Adjust DB connection with superuser credentials if provided. + * + * The options 'db-su' and 'db-su-pw' will be retreived from the + * specified site alias record, if it exists and contains those items. + * If it does not, they will be fetched via drush_get_option. + * + * Note that in the context of sql-sync, the site alias record will + * be taken from the target alias (e.g. `drush sql-sync @source @target`), + * which will be overlayed with any options that begin with 'target-'; + * therefore, the commandline options 'target-db-su' and 'target-db-su-pw' + * may also affect the operation of this function. + * + * @return null + */ + public function su() { + $create_db_target = $this->db_spec; + + $create_db_target['database'] = ''; + $db_superuser = drush_get_option('db-su'); + if (isset($db_superuser)) { + $create_db_target['username'] = $db_superuser; + } + $db_su_pw = drush_get_option('db-su-pw'); + // If --db-su-pw is not provided and --db-su is, default to empty password. + // This way db cli command will take password from .my.cnf or .pgpass. + if (!empty($db_su_pw)) { + $create_db_target['password'] = $db_su_pw; + } + elseif (isset($db_superuser)) { + unset($create_db_target['password']); + } + $this->db_spec = $create_db_target; + } +} diff --git a/vendor/drush/drush/lib/Drush/Sql/SqlException.php b/vendor/drush/drush/lib/Drush/Sql/SqlException.php new file mode 100644 index 0000000000..343e0e1624 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Sql/SqlException.php @@ -0,0 +1,5 @@ +=7 requires PDO and Drush requires php 5.4+ which ships with PDO + // but it may be compiled with --disable-pdo. + if (!class_exists('\PDO')) { + drush_log(dt('PDO support is required.'), LogLevel::BOOTSTRAP); + return FALSE; + } + return TRUE; + } +} diff --git a/vendor/drush/drush/lib/Drush/Sql/Sqlmysql.php b/vendor/drush/drush/lib/Drush/Sql/Sqlmysql.php new file mode 100644 index 0000000000..a5748951fd --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Sql/Sqlmysql.php @@ -0,0 +1,184 @@ +db_spec['username']}" +password="{$this->db_spec['password']}" +EOT; + + $file = drush_save_data_to_temp_file($contents); + $parameters['defaults-extra-file'] = $file; + } + else { + // User is required. Drupal calls it 'username'. MySQL calls it 'user'. + $parameters['user'] = $this->db_spec['username']; + // EMPTY password is not the same as NO password, and is valid. + if (isset($this->db_spec['password'])) { + $parameters['password'] = $this->db_spec['password']; + } + } + + // Some drush commands (e.g. site-install) want to connect to the + // server, but not the database. Connect to the built-in database. + $parameters['database'] = empty($this->db_spec['database']) ? 'information_schema' : $this->db_spec['database']; + + // Default to unix socket if configured. + if (!empty($this->db_spec['unix_socket'])) { + $parameters['socket'] = $this->db_spec['unix_socket']; + } + // EMPTY host is not the same as NO host, and is valid (see unix_socket). + elseif (isset($this->db_spec['host'])) { + $parameters['host'] = $this->db_spec['host']; + } + + if (!empty($this->db_spec['port'])) { + $parameters['port'] = $this->db_spec['port']; + } + + if (!empty($this->db_spec['pdo']['unix_socket'])) { + $parameters['socket'] = $this->db_spec['pdo']['unix_socket']; + } + + if (!empty($this->db_spec['pdo'][PDO::MYSQL_ATTR_SSL_CA])) { + $parameters['ssl-ca'] = $this->db_spec['pdo'][PDO::MYSQL_ATTR_SSL_CA]; + } + + if (!empty($this->db_spec['pdo'][PDO::MYSQL_ATTR_SSL_CAPATH])) { + $parameters['ssl-capath'] = $this->db_spec['pdo'][PDO::MYSQL_ATTR_SSL_CAPATH]; + } + + if (!empty($this->db_spec['pdo'][PDO::MYSQL_ATTR_SSL_CERT])) { + $parameters['ssl-cert'] = $this->db_spec['pdo'][PDO::MYSQL_ATTR_SSL_CERT]; + } + + if (!empty($this->db_spec['pdo'][PDO::MYSQL_ATTR_SSL_CIPHER])) { + $parameters['ssl-cipher'] = $this->db_spec['pdo'][PDO::MYSQL_ATTR_SSL_CIPHER]; + } + + if (!empty($this->db_spec['pdo'][PDO::MYSQL_ATTR_SSL_KEY])) { + $parameters['ssl-key'] = $this->db_spec['pdo'][PDO::MYSQL_ATTR_SSL_KEY]; + } + + return $this->params_to_options($parameters); + } + + public function silent() { + return '--silent'; + } + + public function createdb_sql($dbname, $quoted = FALSE) { + if ($quoted) { + $dbname = '`' . $dbname . '`'; + } + $sql[] = sprintf('DROP DATABASE IF EXISTS %s;', $dbname); + $sql[] = sprintf('CREATE DATABASE %s /*!40100 DEFAULT CHARACTER SET utf8 */;', $dbname); + $db_superuser = drush_get_option('db-su'); + if (isset($db_superuser)) { + // - For a localhost database, create a localhost user. This is important for security. + // localhost is special and only allows local Unix socket file connections. + // - If the database is on a remote server, create a wilcard user with %. + // We can't easily know what IP adderss or hostname would represent our server. + $domain = ($this->db_spec['host'] == 'localhost') ? 'localhost' : '%'; + $user = sprintf("'%s'@'%s'", $this->db_spec['username'], $domain); + $sql[] = sprintf("DROP USER IF EXISTS %s;", $user); + $sql[] = sprintf("CREATE USER %s IDENTIFIED WITH mysql_native_password;", $user); + $sql[] = sprintf("SET PASSWORD FOR %s = PASSWORD('%s');", $user, $this->db_spec['password']); + $sql[] = sprintf('GRANT ALL PRIVILEGES ON %s.* TO %s;', $dbname, $user); + $sql[] = 'FLUSH PRIVILEGES;'; + } + return implode(' ', $sql); + } + + public function db_exists() { + $current = drush_get_context('DRUSH_SIMULATE'); + drush_set_context('DRUSH_SIMULATE', FALSE); + // Suppress output. We only care about return value. + $return = $this->query("SELECT 1;", NULL, drush_bit_bucket()); + drush_set_context('DRUSH_SIMULATE', $current); + return $return; + } + + public function listTables() { + $current = drush_get_context('DRUSH_SIMULATE'); + drush_set_context('DRUSH_SIMULATE', FALSE); + $return = $this->query('SHOW TABLES;'); + $tables = drush_shell_exec_output(); + drush_set_context('DRUSH_SIMULATE', $current); + return $tables; + } + + public function listTablesQuoted() { + $tables = $this->listTables(); + foreach ($tables as &$table) { + $table = "`$table`"; + } + return $tables; + } + + public function dumpCmd($table_selection) { + $parens = FALSE; + $skip_tables = $table_selection['skip']; + $structure_tables = $table_selection['structure']; + $tables = $table_selection['tables']; + + $ignores = array(); + $skip_tables = array_merge($structure_tables, $skip_tables); + $data_only = drush_get_option('data-only'); + // The ordered-dump option is only supported by MySQL for now. + // @todo add documention once a hook for drush_get_option_help() is available. + // @see drush_get_option_help() in drush.inc + $ordered_dump = drush_get_option('ordered-dump'); + + $exec = 'mysqldump '; + // mysqldump wants 'databasename' instead of 'database=databasename' for no good reason. + $only_db_name = str_replace('--database=', ' ', $this->creds()); + $exec .= $only_db_name; + + // We had --skip-add-locks here for a while to help people with insufficient permissions, + // but removed it because it slows down the import a lot. See http://drupal.org/node/1283978 + $extra = ' --no-autocommit --single-transaction --opt -Q'; + if (isset($data_only)) { + $extra .= ' --no-create-info'; + } + if (isset($ordered_dump)) { + $extra .= ' --skip-extended-insert --order-by-primary'; + } + if ($option = drush_get_option('extra', $this->query_extra)) { + $extra .= " $option"; + } + $exec .= $extra; + + if (!empty($tables)) { + $exec .= ' ' . implode(' ', $tables); + } + else { + // Append the ignore-table options. + foreach ($skip_tables as $table) { + $ignores[] = '--ignore-table=' . $this->db_spec['database'] . '.' . $table; + $parens = TRUE; + } + $exec .= ' '. implode(' ', $ignores); + + // Run mysqldump again and append output if we need some structure only tables. + if (!empty($structure_tables)) { + $exec .= " && mysqldump " . $only_db_name . " --no-data $extra " . implode(' ', $structure_tables); + $parens = TRUE; + } + } + return $parens ? "($exec)" : $exec; + } +} diff --git a/vendor/drush/drush/lib/Drush/Sql/Sqlmysqli.php b/vendor/drush/drush/lib/Drush/Sql/Sqlmysqli.php new file mode 100644 index 0000000000..52d5fabae8 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Sql/Sqlmysqli.php @@ -0,0 +1,7 @@ +db_spec['username'] . '/' . $this->db_spec['password'] . ($this->db_spec['host'] == 'USETNS' ? '@' . $this->db_spec['database'] : '@//' . $this->db_spec['host'] . ':' . ($db_spec['port'] ? $db_spec['port'] : '1521') . '/' . $this->db_spec['database']); + } + + public function createdb_sql($dbname) { + return drush_log("Unable to generate CREATE DATABASE sql for $dbname", LogLevel::ERROR); + } + + // @todo $suffix = '.sql'; + public function query_format($query) { + // remove trailing semicolon from query if we have it + $query = preg_replace('/\;$/', '', $query); + + // some sqlplus settings + $settings[] = "set TRIM ON"; + $settings[] = "set FEEDBACK OFF"; + $settings[] = "set UNDERLINE OFF"; + $settings[] = "set PAGES 0"; + $settings[] = "set PAGESIZE 50000"; + + // are we doing a describe ? + if (!preg_match('/^ *desc/i', $query)) { + $settings[] = "set LINESIZE 32767"; + } + + // are we doing a show tables ? + if (preg_match('/^ *show tables/i', $query)) { + $settings[] = "set HEADING OFF"; + $query = "select object_name from user_objects where object_type='TABLE' order by object_name asc"; + } + + // create settings string + $sqlp_settings = implode("\n", $settings) . "\n"; + + // important for sqlplus to exit correctly + return "${sqlp_settings}${query};\nexit;\n"; + } + + public function listTables() { + $return = $this->query("SELECT TABLE_NAME FROM USER_TABLES WHERE TABLE_NAME NOT IN ('BLOBS','LONG_IDENTIFIERS')"); + $tables = drush_shell_exec_output(); + if (!empty($tables)) { + // Shift off the header of the column of data returned. + array_shift($tables); + return $tables; + } + } + + // @todo $file is no longer provided. We are supposed to return bash that can be piped to gzip. + // Probably Oracle needs to override dump() entirely - http://stackoverflow.com/questions/2236615/oracle-can-imp-exp-go-to-stdin-stdout. + public function dumpCmd($table_selection) { + $create_db = drush_get_option('create-db'); + $exec = 'exp ' . $this->creds(); + // Change variable '$file' by reference in order to get drush_log() to report. + if (!$file) { + $file = $this->db_spec['username'] . '.dmp'; + } + $exec .= ' file=' . $file; + + if (!empty($tables)) { + $exec .= ' tables="(' . implode(',', $tables) . ')"'; + } + $exec .= ' owner=' . $this->db_spec['username']; + if ($option = drush_get_option('extra', $this->query_extra)) { + $exec .= " $option"; + } + return array($exec, $file); + } +} diff --git a/vendor/drush/drush/lib/Drush/Sql/Sqlpgsql.php b/vendor/drush/drush/lib/Drush/Sql/Sqlpgsql.php new file mode 100644 index 0000000000..e0fc46cef6 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Sql/Sqlpgsql.php @@ -0,0 +1,153 @@ +db_spec['password'])) { + $pgpass_parts = array( + empty($this->db_spec['host']) ? 'localhost' : $this->db_spec['host'], + empty($this->db_spec['port']) ? '5432' : $this->db_spec['port'], + // Database + '*', + $this->db_spec['username'], + $this->db_spec['password'] + ); + // Escape colon and backslash characters in entries. + // @see http://www.postgresql.org/docs/9.1/static/libpq-pgpass.html + array_walk($pgpass_parts, function (&$part) { + // The order of the replacements is important so that backslashes are + // not replaced twice. + $part = str_replace(array('\\', ':'), array('\\\\', '\:'), $part); + }); + $pgpass_contents = implode(':', $pgpass_parts); + $password_file = drush_save_data_to_temp_file($pgpass_contents); + chmod($password_file, 0600); + } + return $password_file; + } + + public function command() { + $environment = ""; + $pw_file = $this->password_file(); + if (isset($pw_file)) { + $environment = "PGPASSFILE={$pw_file} "; + } + return "{$environment}psql -q"; + } + + /* + * @param $hide_password + * Not used in postgres. Use .pgpass file instead. See http://drupal.org/node/438828. + */ + public function creds($hide_password = TRUE) { + // Some drush commands (e.g. site-install) want to connect to the + // server, but not the database. Connect to the built-in database. + $parameters['dbname'] = empty($this->db_spec['database']) ? 'template1' : $this->db_spec['database']; + + // Host and port are optional but have defaults. + $parameters['host'] = empty($this->db_spec['host']) ? 'localhost' : $this->db_spec['host']; + $parameters['port'] = empty($this->db_spec['port']) ? '5432' : $this->db_spec['port']; + + // Username is required. + $parameters['username'] = $this->db_spec['username']; + + // Don't set the password. + // @see http://drupal.org/node/438828 + + return $this->params_to_options($parameters); + } + + public function createdb_sql($dbname, $quoted = FALSE) { + if ($quoted) { + $dbname = '`' . $dbname . '`'; + } + $sql[] = sprintf('drop database if exists %s;', $dbname); + $sql[] = sprintf("create database %s ENCODING 'UTF8';", $dbname); + return implode(' ', $sql); + } + + public function db_exists() { + $database = $this->db_spec['database']; + // Get a new class instance that has no 'database'. + $db_spec_no_db = $this->db_spec; + unset($db_spec_no_db['database']); + $sql_no_db = drush_sql_get_class($db_spec_no_db); + $query = "SELECT 1 AS result FROM pg_database WHERE datname='$database'"; + drush_shell_exec($sql_no_db->connect() . ' -t -c %s', $query); + $output = drush_shell_exec_output(); + return (bool)$output[0]; + } + + public function query_format($query) { + if (strtolower($query) == 'show tables;') { + return PSQL_SHOW_TABLES; + } + return $query; + } + + public function listTables() { + $return = $this->query(PSQL_SHOW_TABLES); + $tables = drush_shell_exec_output(); + if (!empty($tables)) { + return $tables; + } + return array(); + } + + public function dumpCmd($table_selection) { + $parens = FALSE; + $skip_tables = $table_selection['skip']; + $structure_tables = $table_selection['structure']; + $tables = $table_selection['tables']; + + $ignores = array(); + $skip_tables = array_merge($structure_tables, $skip_tables); + $data_only = drush_get_option('data-only'); + + $create_db = drush_get_option('create-db'); + $exec = 'pg_dump '; + // Unlike psql, pg_dump does not take a '--dbname=' before the database name. + $extra = str_replace('--dbname=', ' ', $this->creds()); + if (isset($data_only)) { + $extra .= ' --data-only'; + } + if ($option = drush_get_option('extra')) { + $extra .= " $option"; + } + $exec .= $extra; + $exec .= (!isset($create_db) && !isset($data_only) ? ' --clean' : ''); + + if (!empty($tables)) { + foreach ($tables as $table) { + $exec .= " --table=$table"; + } + } + else { + foreach ($skip_tables as $table) { + $ignores[] = "--exclude-table=$table"; + } + $exec .= ' '. implode(' ', $ignores); + // Run pg_dump again and append output if we need some structure only tables. + if (!empty($structure_tables)) { + $parens = TRUE; + $schemaonlies = array(); + foreach ($structure_tables as $table) { + $schemaonlies[] = "--table=$table"; + } + $exec .= " && pg_dump --schema-only " . implode(' ', $schemaonlies) . $extra; + $exec .= (!isset($create_db) && !isset($data_only) ? ' --clean' : ''); + } + } + return $parens ? "($exec)" : $exec; + } +} diff --git a/vendor/drush/drush/lib/Drush/Sql/Sqlsqlite.php b/vendor/drush/drush/lib/Drush/Sql/Sqlsqlite.php new file mode 100644 index 0000000000..2182afb38b --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Sql/Sqlsqlite.php @@ -0,0 +1,101 @@ +db_spec['database']; + } + + public function createdb_sql($dbname, $quoted = false) { + return ''; + } + + /** + * Create a new database. + * + * @param boolean $quoted + * Quote the database name. Mysql uses backticks to quote which can cause problems + * in a Windows shell. Set TRUE if the CREATE is not running on the bash command line. + */ + public function createdb($quoted = FALSE) { + $file = $this->db_spec['database']; + if (file_exists($file)) { + drush_log("SQLITE: Deleting existing database '$file'", LogLevel::DEBUG); + drush_delete_dir($file, TRUE); + } + + // Make sure sqlite can create file + $path = dirname($file); + drush_log("SQLITE: creating '$path' for creating '$file'", LogLevel::DEBUG); + drush_mkdir($path); + if (!file_exists($path)) { + drush_log("SQLITE: Cannot create $path", LogLevel::ERROR); + return FALSE; + } + else { + return TRUE; + } + } + + public function db_exists() { + return file_exists($this->db_spec['database']); + } + + public function listTables() { + $return = $this->query('.tables'); + $tables_raw = drush_shell_exec_output(); + // SQLite's '.tables' command always outputs the table names in a column + // format, like this: + // table_alpha table_charlie table_echo + // table_bravo table_delta table_foxtrot + // …and there doesn't seem to be a way to fix that. So we need to do some + // clean-up. + foreach ($tables_raw as $line) { + preg_match_all('/[^\s]+/', $line, $matches); + if (!empty($matches[0])) { + foreach ($matches[0] as $match) { + $tables[] = $match; + } + } + } + return $tables; + } + + public function drop($tables) { + $sql = ''; + // SQLite only wants one table per DROP TABLE command (so we have to do + // "DROP TABLE foo; DROP TABLE bar;" instead of "DROP TABLE foo, bar;"). + foreach ($tables as $table) { + $sql .= "DROP TABLE $table; "; + } + return $this->query($sql); + } + + public function dumpCmd($table_selection) { + // Dumping is usually not necessary in SQLite, since all database data + // is stored in a single file which can be copied just + // like any other file. But it still has a use in migration purposes and + // building human-readable diffs and such, so let's do it anyway. + $exec = $this->connect(); + // SQLite's dump command doesn't support many of the features of its + // Postgres or MySQL equivalents. We may be able to fake some in the + // future, but for now, let's just support simple dumps. + $exec .= ' ".dump"'; + if ($option = drush_get_option('extra', $this->query_extra)) { + $exec .= " $option"; + } + return $exec; + } + + +} diff --git a/vendor/drush/drush/lib/Drush/Sql/Sqlsqlsrv.php b/vendor/drush/drush/lib/Drush/Sql/Sqlsqlsrv.php new file mode 100644 index 0000000000..62d9b5b712 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/Sql/Sqlsqlsrv.php @@ -0,0 +1,65 @@ +db_spec['database']) ? 'master' : $this->db_spec['database']; + // Host and port are optional but have defaults. + $host = empty($this->db_spec['host']) ? '.\SQLEXPRESS' : $this->db_spec['host']; + if ($this->db_spec['username'] == '') { + return ' -S ' . $host . ' -d ' . $database; + } + else { + return ' -S ' . $host . ' -d ' . $database . ' -U ' . $this->db_spec['username'] . ' -P ' . $this->db_spec['password']; + } + } + + public function db_exists() { + // TODO: untested, but the gist is here. + $database = $this->db_spec['database']; + // Get a new class instance that has no 'database'. + $db_spec_no_db = $this->db_spec; + unset($db_spec_no_db['database']); + $sql_no_db = drush_sql_get_class($db_spec_no_db); + $query = "if db_id('$database') IS NOT NULL print 1"; + drush_shell_exec($sql_no_db->connect() . ' -Q %s', $query); + $output = drush_shell_exec_output(); + return $output[0] == 1; + } + + public function listTables() { + $return = $this->query('SELECT TABLE_NAME FROM information_schema.tables'); + $tables = drush_shell_exec_output(); + if (!empty($tables)) { + // Shift off the header of the column of data returned. + array_shift($tables); + return $tables; + } + } + + // @todo $file is no longer provided. We are supposed to return bash that can be piped to gzip. + // Probably sqlsrv needs to override dump() entirely. + public function dumpCmd($table_selection) { + if (!$file) { + $file = $this->db_spec['database'] . '_' . date('Ymd_His') . '.bak'; + } + $exec = "sqlcmd -U \"" . $this->db_spec['username'] . "\" -P \"" . $this->db_spec['password'] . "\" -S \"" . $this->db_spec['host'] . "\" -Q \"BACKUP DATABASE [" . $this->db_spec['database'] . "] TO DISK='" . $file . "'\""; + if ($option = drush_get_option('extra', $this->query_extra)) { + $exec .= " $option"; + } + return array($exec, $file); + } + + +} diff --git a/vendor/drush/drush/lib/Drush/UpdateService/Project.php b/vendor/drush/drush/lib/Drush/UpdateService/Project.php new file mode 100644 index 0000000000..0a0762b573 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/UpdateService/Project.php @@ -0,0 +1,729 @@ +xpath('/error')) { + $error = (string)$error[0]; + if (strpos($error, 'No release history available for') === 0) { + $project_status = 'unsupported'; + } + elseif (strpos($error, 'No release history was found for the requested project') === 0) { + $project_status = 'unknown'; + } + // Any other error we are not aware of. + else { + $project_status = 'unknown'; + } + } + // The xml has a project, but still it can have errors. + else { + $this->parsed = self::parseXml($xml); + if (empty($this->parsed['releases'])) { + $error = dt('No available releases found for the requested project (!name).', array('!name' => $this->parsed['short_name'])); + $project_status = 'unknown'; + } + else { + $error = FALSE; + $project_status = $xml->xpath('/project/project_status'); + $project_status = (string)$project_status[0]; + } + } + + if (drush_drupal_major_version() >= 8) { + foreach ($this->parsed['releases'] as $version => $info) { + if (!$this->versionIsCompatible($version, $info)) { + unset($this->parsed['releases'][$version]); + } + } + } + + $this->project_status = $project_status; + $this->error = $error; + if ($error) { + drush_set_error('DRUSH_RELEASE_INFO_ERROR', $error); + } + } + + protected function versionIsCompatible($version, $info) { + // If there is no "core_compatibility" field then assume this version + // is only compatible with Drupal 8. + if (!isset($info['core_compatibility'])) { + return drush_drupal_major_version() == 8; + } + if (preg_match('#^[0-9]*\.x$#', $info['core_compatibility'])) { + return $info['core_compatibility'] == drush_drupal_major_version() . '.x'; + } + return Semver::satisfies(drush_drupal_version(), $info['core_compatibility']); + } + + /** + * Downloads release info xml from update service. + * + * @param array $request + * A request array. + * @param int $cache_duration + * Cache lifetime. + * + * @return \Drush\UpdateService\Project + */ + public static function getInstance(array $request, $cache_duration = ReleaseInfo::CACHE_LIFETIME) { + $url = self::buildFetchUrl($request); + drush_log(dt('Downloading release history from !url', array('!url' => $url))); + + $path = drush_download_file($url, drush_tempnam($request['name']), $cache_duration); + $xml = simplexml_load_file($path); + if (!$xml) { + $error = dt('Failed to get available update data from !url', array('!url' => $url)); + return drush_set_error('DRUSH_RELEASE_INFO_ERROR', $error); + } + + return new Project($xml); + } + + /** + * Returns URL to the updates service for the given request. + * + * @param array $request + * A request array. + * + * @return string + * URL to the updates service. + * + * @see \Drupal\update\UpdateFetcher::buildFetchUrl() + */ + public static function buildFetchUrl(array $request) { + $status_url = isset($request['status url']) ? $request['status url'] : ReleaseInfo::DEFAULT_URL; + $drupal_version = $request['drupal_version']; + if (drush_drupal_major_version() >= 8) { + $drupal_version = 'current'; + } + if ($drupal_version == '9.x') { + $drupal_version = 'all'; + } + return $status_url . '/' . $request['name'] . '/' . $drupal_version; + } + + /** + * Parses update service xml. + * + * @param \SimpleXMLElement $xml + * XML element from the updates service. + * + * @return array + * Project update information. + */ + private static function parseXml(\SimpleXMLElement $xml) { + $project_info = array(); + + // Extract general project info. + $items = array('title', 'short_name', 'dc:creator', 'type', 'api_version', + 'recommended_major', 'supported_majors', 'default_major', 'supported_branches', + 'project_status', 'link', + ); + foreach ($items as $item) { + if (array_key_exists($item, (array)$xml)) { + $value = $xml->xpath($item); + $project_info[$item] = (string)$value[0]; + } + } + $supported_branches = []; + if (isset($project_info['supported_branches'])) { + $supported_branches = explode(',', $project_info['supported_branches']); + } + + // Parse project type. + $project_types = array( + 'core' => 'project_core', + 'profile' => 'project_distribution', + 'module' => 'project_module', + 'theme' => 'project_theme', + 'theme engine' => 'project_theme_engine', + 'translation' => 'project_translation', + 'utility' => 'project_drupalorg', + ); + $type = $project_info['type']; + // Probably unused but kept for possible legacy compat. + $type = ($type == 'profile-legacy') ? 'profile' : $type; + $project_info['project_type'] = array_search($type, $project_types); + + // Extract project terms. + $project_info['terms'] = array(); + if ($xml->terms) { + foreach ($xml->terms->children() as $term) { + $term_name = (string) $term->name; + $term_value = (string) $term->value; + if (!isset($project_info[$term_name])) { + $project_info['terms'][$term_name] = array(); + } + $project_info['terms'][$term_name][] = $term_value; + } + } + + // Extract and parse releases info. + // In addition to the info in the update service, here we calculate + // release statuses as Recommended, Security, etc. + + $recommended_major = empty($project_info['recommended_major']) ? '' : $project_info['recommended_major']; + $supported_majors = empty($project_info['supported_majors']) ? array() : array_flip(explode(',', $project_info['supported_majors'])); + + $items = array( + 'name', 'date', 'status', 'type', + 'version', 'tag', 'version_major', 'version_patch', 'version_extra', + 'release_link', 'download_link', 'mdhash', 'filesize', 'core_compatibility', + ); + + $releases = array(); + $releases_xml = @$xml->xpath("/project/releases/release[status='published']"); + foreach ($releases_xml as $release) { + $release_array = (array) $release; + $release_info = array(); + $statuses = array(); + + // Extract general release info. + foreach ($items as $item) { + if (array_key_exists($item, $release_array)) { + $value = $release->xpath($item); + $release_info[$item] = (string)$value[0]; + } + } + + // Extract release terms. + $release_info['terms'] = array(); + if ($release->terms) { + foreach ($release->terms->children() as $term) { + $term_name = (string) $term->name; + $term_value = (string) $term->value; + if (!isset($release_info['terms'][$term_name])) { + $release_info['terms'][$term_name] = array(); + } + $release_info['terms'][$term_name][] = $term_value; + + // Add "Security" for security updates, and nothing + // for the other kinds. + if (strpos($term_value, "Security") !== FALSE) { + $statuses[] = "Security"; + } + } + } + + // Extract files. + $release_info['files'] = array(); + if (!empty($release->files)) { + foreach ($release->files->children() as $file) { + // Normalize keys to match the ones in the release info. + $item = array( + 'download_link' => (string) $file->url, + 'date' => (string) $file->filedate, + 'mdhash' => (string) $file->md5, + 'filesize' => (string) $file->size, + 'archive_type' => (string) $file->archive_type, + ); + if (!empty($file->variant)) { + $item['variant'] = (string) $file->variant; + } + $release_info['files'][] = $item; + + // Copy the mdhash from the matching download file into the + // root of the release object (make /current structure like /8.x) + if ($item['download_link'] == $release_info['download_link'] && !isset($release_info['mdhash'])) { + $release_info['mdhash'] = $item['mdhash']; + } + } + } + + // '/current' does not include version_major et.al.; put them back if missing. + if (!isset($release_info['version_major'])) { + $two_part_version_key = 'version_patch'; + $version = preg_replace('#^[89]\.x-#', '', $release_info['version']); + if ($version == $release_info['version']) { + $two_part_version_key = 'version_minor'; + } + if (preg_match('#-([a-z]+[0-9]*)$#', $version, $matches)) { + $release_info['version_extra'] = $matches[1]; + $version = preg_replace('#-[a-z]+[0-9]*$#', '', $version); + } + $version = preg_replace('#\.x$#', '', $version); + $parts = explode('.', $version); + + $release_info['version_major'] = $parts[0]; + if (count($parts) > 1) { + $release_info[$two_part_version_key] = $parts[1]; + } + if (count($parts) > 2) { + $release_info['version_minor'] = $parts[1]; + $release_info['version_patch'] = $parts[2]; + } + } + + // Calculate statuses. + if (array_key_exists($release_info['version_major'], $supported_majors)) { + $statuses[] = "Supported"; + unset($supported_majors[$release_info['version_major']]); + } + if ($release_info['version_major'] == $recommended_major) { + if (!isset($latest_version)) { + $latest_version = $release_info['version']; + } + // The first stable version (no 'version extra') in the recommended major + // is the recommended release + if (empty($release_info['version_extra']) && (!isset($recommended_version))) { + $statuses[] = "Recommended"; + $recommended_version = $release_info['version']; + } + } + if (!empty($release_info['version_extra']) && ($release_info['version_extra'] == "dev")) { + $statuses[] = "Development"; + } else { + $supported_branch = static::branchIsSupported($release_info['version'], $supported_branches); + if ($supported_branch) { + $statuses[] = "Supported"; + $supported_branches = array_diff($supported_branches, [$supported_branch]); + $sup_maj = $release_info['version_major']; + if (!empty($project_info['supported_majors'])) { + $sup_maj = $project_info['supported_majors'] . ',' . $sup_maj; + } + $project_info['supported_majors'] = $sup_maj; + } + } + + $release_info['release_status'] = $statuses; + $releases[$release_info['version']] = $release_info; + } + + // If there's no "Recommended major version", we want to recommend + // the most recent release. + if (!$recommended_major) { + $latest_version = key($releases); + } + + // If there is no -stable- release in the recommended major, + // then take the latest version in the recommended major to be + // the recommended release. + if (!isset($recommended_version) && isset($latest_version)) { + $recommended_version = $latest_version; + $releases[$recommended_version]['release_status'][] = "Recommended"; + } + + $project_info['releases'] = $releases; + if (isset($recommended_version)) { + $project_info['recommended'] = $recommended_version; + } + + return $project_info; + } + + private static function branchIsSupported($version, $supported_branches) { + foreach ($supported_branches as $supported_branch) { + if (substr($version, 0, strlen($supported_branch)) == $supported_branch) { + return $supported_branch; + } + } + return false; + } + + /** + * Gets the project type. + * + * @return string + * Type of the project. + */ + public function getType() { + return $this->parsed['project_type']; + } + + /** + * Gets the project status in the update service. + * + * This is the project status in drupal.org: insecure, revoked, published etc. + * + * @return string + */ + public function getStatus() { + return $this->project_status; + } + + /** + * Whether this object represents a project in the update service or an error. + */ + public function isValid() { + return ($this->error === FALSE); + } + + /** + * Gets the parsed xml. + * + * @return array or FALSE if the xml has an error. + */ + public function getInfo() { + return (!$this->error) ? $this->parsed : FALSE; + } + + /** + * Helper to pick the best release in a list of candidates. + * + * The best one is the first stable release if there are stable + * releases; otherwise, it will be the first of the candidates. + * + * @param array $releases + * Array of release arrays. + * + * @return array|bool + */ + public static function getBestRelease(array $releases) { + if (empty($releases)) { + return FALSE; + } + else { + // If there are releases found, let's try first to fetch one with no + // 'version_extra'. Otherwise, use all. + $stable_releases = array(); + foreach ($releases as $one_release) { + if (!array_key_exists('version_extra', $one_release)) { + $stable_releases[] = $one_release; + } + } + if (!empty($stable_releases)) { + $releases = $stable_releases; + } + } + + // First published release is just the first value in $releases. + return reset($releases); + } + + private function searchReleases($key, $value) { + $releases = array(); + foreach ($this->parsed['releases'] as $version => $release) { + if ($release['status'] == 'published' && isset($release[$key]) && strcmp($release[$key], $value) == 0) { + $releases[$version] = $release; + } + } + return $releases; + } + + /** + * Returns the specific release that matches the request version. + * + * @param string $version + * Version of the release to pick. + * @return array|bool + * The release or FALSE if no version specified or no release found. + */ + public function getSpecificRelease($version = NULL) { + if (!empty($version)) { + $matches = array(); + // See if we only have a branch version. + if (preg_match('/^\d+\.x-(\d+)$/', $version, $matches)) { + $releases = $this->searchReleases('version_major', $matches[1]); + } + else { + // In some cases, the request only says something like '7.x-3.x' but the + // version strings include '-dev' on the end, so we need to append that + // here for the xpath to match below. + if (substr($version, -2) == '.x') { + $version .= '-dev'; + } + $releases = $this->searchReleases('version', $version); + } + if (empty($releases)) { + return FALSE; + } + return self::getBestRelease($releases); + } + return array(); + } + + /** + * Pick the first dev release from XML list. + * + * @return array|bool + * The selected release xml object or FALSE. + */ + public function getDevRelease() { + $releases = $this->searchReleases('version_extra', 'dev'); + return self::getBestRelease($releases); + } + + /** + * Pick most appropriate release from XML list. + * + * @return array|bool + * The selected release xml object or FALSE. + */ + public function getRecommendedOrSupportedRelease() { + $majors = array(); + + $recommended_major = empty($this->parsed['recommended_major']) ? 0 : $this->parsed['recommended_major']; + if ($recommended_major != 0) { + $majors[] = $this->parsed['recommended_major']; + } + if (!empty($this->parsed['supported_majors'])) { + $supported = explode(',', $this->parsed['supported_majors']); + foreach ($supported as $v) { + if ($v != $recommended_major) { + $majors[] = $v; + } + } + } + $releases = array(); + foreach ($majors as $major) { + $releases = $this->searchReleases('version_major', $major); + if (!empty($releases)) { + break; + } + } + + return self::getBestRelease($releases); + } + + /** + * Comparison routine to order releases by date. + * + * @param array $a + * Release to compare. + * @param array $b + * Release to compare. + * + * @return int + * -1, 0 or 1 whether $a is greater, equal or lower than $b. + */ + private static function compareDates(array $a, array $b) { + if ($a['date'] == $b['date']) { + return ($a['version_major'] > $b['version_major']) ? -1 : 1; + } + if ($a['version_major'] == $b['version_major']) { + return ($a['date'] > $b['date']) ? -1 : 1; + } + return ($a['version_major'] > $b['version_major']) ? -1 : 1; + } + + /** + * Comparison routine to order releases by version. + * + * @param array $a + * Release to compare. + * @param array $b + * Release to compare. + * + * @return int + * -1, 0 or 1 whether $a is greater, equal or lower than $b. + */ + private static function compareVersions(array $a, array $b) { + $defaults = array( + 'version_patch' => '', + 'version_extra' => '', + 'date' => 0, + ); + $a += $defaults; + $b += $defaults; + if ($a['version_major'] != $b['version_major']) { + return ($a['version_major'] > $b['version_major']) ? -1 : 1; + } + else if ($a['version_patch'] != $b['version_patch']) { + return ($a['version_patch'] > $b['version_patch']) ? -1 : 1; + } + else if ($a['version_extra'] != $b['version_extra']) { + // Don't rely on version_extra alphabetical order. + return ($a['date'] > $b['date']) ? -1 : 1; + } + + return 0; + } + + /** + * Filter project releases by a criteria and returns a list. + * + * If no filter is provided, the first Recommended, Supported, Security + * or Development release on each major version will be shown. + * + * @param string $filter + * Valid values: + * - 'all': Select all releases. + * - 'dev': Select all development releases. + * @param string $installed_version + * Version string. If provided, Select all releases in the same + * version_major branch until the provided one is found. + * On any other branch, the default behaviour will be applied. + * + * @return array + * List of releases matching the filter criteria. + */ + function filterReleases($filter = '', $installed_version = NULL) { + $releases = $this->parsed['releases']; + usort($releases, array($this, 'compareDates')); + + $installed_version = pm_parse_version($installed_version); + + // Iterate through and filter out the releases we're interested in. + $options = array(); + $limits_list = array(); + foreach ($releases as $release) { + $eligible = FALSE; + + // Mark as eligible if the filter criteria matches. + if ($filter == 'all') { + $eligible = TRUE; + } + elseif ($filter == 'dev') { + if (!empty($release['version_extra']) && ($release['version_extra'] == 'dev')) { + $eligible = TRUE; + } + } + // The Drupal core version scheme (ex: 7.31) is different to + // other projects (ex 7.x-3.2). We need to manage this special case. + elseif (($this->getType() != 'core') && ($installed_version['version_major'] == $release['version_major'])) { + // In case there's no filter, select all releases until the installed one. + // Always show the dev release. + if (isset($release['version_extra']) && ($release['version_extra'] == 'dev')) { + $eligible = TRUE; + } + else { + if (self::compareVersions($release, $installed_version) < 1) { + $eligible = TRUE; + } + } + } + // Otherwise, pick only the first release in each status. + // For example after we pick out the first security release, + // we won't pick any other. We do this on a per-major-version basis, + // though, so if a project has three major versions, then we will + // pick out the first security release from each. + else { + foreach ($release['release_status'] as $one_status) { + $test_key = $release['version_major'] . $one_status; + if (empty($limits_list[$test_key])) { + $limits_list[$test_key] = TRUE; + $eligible = TRUE; + } + } + } + + if ($eligible) { + $options[$release['version']] = $release; + } + } + + // Add Installed status. + if (!is_null($installed_version) && isset($options[$installed_version['version']])) { + $options[$installed_version['version']]['release_status'][] = 'Installed'; + } + + return $options; + } + + /** + * Prints release notes for given projects. + * + * @param string $version + * Version of the release to get notes. + * @param bool $print_status + * Whether to print a informative note. + * @param string $tmpfile + * If provided, a file that contains contents to show before the + * release notes. + */ + function getReleaseNotes($version = NULL, $print_status = TRUE, $tmpfile = NULL) { + $project_name = $this->parsed['short_name']; + if (!isset($tmpfile)) { + $tmpfile = drush_tempnam('rln-' . $project_name . '.'); + } + + // Select versions to show. + $versions = array(); + if (!is_null($version)) { + $versions[] = $version; + } + else { + // If requested project is installed, + // show release notes for the installed version and all newer versions. + if (isset($this->parsed['recommended'], $this->parsed['installed'])) { + $releases = array_reverse($this->parsed['releases']); + foreach($releases as $version => $release) { + if ($release['date'] >= $this->parsed['releases'][$this->parsed['installed']]['date']) { + $release += array('version_extra' => ''); + $this->parsed['releases'][$this->parsed['installed']] += array('version_extra' => ''); + if ($release['version_extra'] == 'dev' && $this->parsed['releases'][$this->parsed['installed']]['version_extra'] != 'dev') { + continue; + } + $versions[] = $version; + } + } + } + else { + // Project is not installed and user did not specify a version, + // so show the release notes for the recommended version. + $versions[] = $this->parsed['recommended']; + } + } + + foreach ($versions as $version) { + if (!isset($this->parsed['releases'][$version]['release_link'])) { + drush_log(dt("Project !project does not have release notes for version !version.", array('!project' => $project_name, '!version' => $version)), LogLevel::WARNING); + continue; + } + + // Download the release node page and get the html as xml to explore it. + $release_link = $this->parsed['releases'][$version]['release_link']; + $filename = drush_download_file($release_link, drush_tempnam($project_name)); + @$dom = new \DOMDocument(); + $success = $dom->loadHTMLFile($filename); + if ($success) { + drush_log(dt("Successfully parsed and loaded the HTML contained in the release notes' page for !project (!version) project.", array('!project' => $project_name, '!version' => $version)), LogLevel::NOTICE); + } + else { + drush_log(dt("Error while requesting the release notes page for !project project.", array('!project' => $project_name)), LogLevel::ERROR); + continue; + } + $xml = simplexml_import_dom($dom); + + // Extract last update time and the notes. + $last_updated = $xml->xpath('//div[contains(@class,"views-field-changed")]'); + $last_updated = $last_updated[0]->asXML(); + $notes = $xml->xpath('//div[contains(@class,"field-name-body")]'); + $notes = (!empty($notes)) ? $notes[0]->asXML() : dt("There're no release notes."); + + // Build the notes header. + $header = array(); + $header[] = '
    '; + $header[] = dt("> RELEASE NOTES FOR '!name' PROJECT, VERSION !version:", array('!name' => strtoupper($project_name), '!version' => $version)); + $header[] = dt("> !last_updated.", array('!last_updated' => trim(drush_html_to_text($last_updated)))); + if ($print_status) { + $header[] = '> ' . implode(', ', $this->parsed['releases'][$version]['release_status']); + } + $header[] = '
    '; + + // Finally add the release notes for the requested project to the tmpfile. + $content = implode("\n", $header) . "\n" . $notes . "\n"; + #TODO# accept $html as a method argument + if (!drush_get_option('html', FALSE)) { + $content = drush_html_to_text($content, array('br', 'p', 'ul', 'ol', 'li', 'hr')); + } + file_put_contents($tmpfile, $content, FILE_APPEND); + } + + #TODO# don't print! Just return the filename + drush_print_file($tmpfile); + } +} diff --git a/vendor/drush/drush/lib/Drush/UpdateService/ReleaseInfo.php b/vendor/drush/drush/lib/Drush/UpdateService/ReleaseInfo.php new file mode 100644 index 0000000000..b50384d7d3 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/UpdateService/ReleaseInfo.php @@ -0,0 +1,238 @@ +engine_type = $type; + $this->engine = $engine; + + if (is_null($config)) { + $config = array(); + } + $config += array( + 'cache-duration' => drush_get_option('cache-duration-releasexml', self::CACHE_LIFETIME), + ); + $this->engine_config = $config; + $this->cache = array(); + } + + /** + * Returns configured cache duration. + */ + public function getCacheDuration() { + return $this->engine_config['cache-duration']; + } + + /** + * Returns a project's release info from the update service. + * + * @param array $request + * A request array. + * + * @param bool $refresh + * Whether to discard cached object. + * + * @return \Drush\UpdateService\Project + */ + public function get($request, $refresh = FALSE) { + if ($refresh || !isset($this->cache[$request['name']])) { + $project_release_info = Project::getInstance($request, $this->getCacheDuration()); + if ($project_release_info && !$project_release_info->isValid()) { + $project_release_info = FALSE; + } + $this->cache[$request['name']] = $project_release_info; + } + return $this->cache[$request['name']]; + } + + /** + * Delete cached update service file of a project. + * + * @param array $request + * A request array. + */ + public function clearCached(array $request) { + if (isset($this->cache[$request['name']])) { + unset($this->cache[$request['name']]); + } + $url = Project::buildFetchUrl($request); + $cache_file = drush_download_file_name($url); + if (file_exists($cache_file)) { + unlink($cache_file); + } + } + + /** + * Select the most appropriate release for a project, based on a strategy. + * + * @param Array &$request + * A request array. + * The array will be expanded with the project type. + * @param String $restrict_to + * One of: + * 'dev': Forces choosing a -dev release. + * 'version': Forces choosing a point release. + * '': No restriction. + * Default is ''. + * @param String $select + * Strategy for selecting a release, should be one of: + * - auto: Try to select the latest release, if none found allow the user + * to choose. + * - always: Force the user to choose a release. + * - never: Try to select the latest release, if none found then fail. + * - ignore: Ignore and return NULL. + * If no supported release is found, allow to ask the user to choose one. + * @param Boolean $all + * In case $select = TRUE this indicates that all available releases will be + * offered the user to choose. + * + * @return array + * The selected release. + */ + public function selectReleaseBasedOnStrategy($request, $restrict_to = '', $select = 'never', $all = FALSE, $version = NULL) { + if (!in_array($select, array('auto', 'never', 'always', 'ignore'))) { + return drush_set_error('DRUSH_PM_UNKNOWN_SELECT_STRATEGY', dt("Error: select strategy must be one of: auto, never, always, ignore", array())); + } + + $project_release_info = $this->get($request); + if (!$project_release_info) { + return FALSE; + } + + if ($select != 'always') { + if (isset($request['version'])) { + $release = $project_release_info->getSpecificRelease($request['version']); + if ($release === FALSE) { + return drush_set_error('DRUSH_PM_COULD_NOT_FIND_VERSION', dt("Could not locate !project version !version.", array( + '!project' => $request['name'], + '!version' => $request['version'], + ))); + } + } + if ($restrict_to == 'dev') { + // If you specified a specific release AND --dev, that is either + // redundant (okay), or contradictory (error). + if (!empty($release)) { + if ($release['version_extra'] != 'dev') { + return drush_set_error('DRUSH_PM_COULD_NOT_FIND_VERSION', dt("You requested both --dev and !project version !version, which is not a '-dev' release.", array( + '!project' => $request['name'], + '!version' => $request['version'], + ))); + } + } + else { + $release = $project_release_info->getDevRelease(); + if ($release === FALSE) { + return drush_set_error('DRUSH_PM_NO_DEV_RELEASE', dt('There is no development release for project !project.', array('!project' => $request['name']))); + } + } + } + // If there was no specific release requested, try to identify the most appropriate release. + if (empty($release)) { + $release = $project_release_info->getRecommendedOrSupportedRelease(); + } + if ($release) { + return $release; + } + else { + $message = dt('There are no stable releases for project !project.', array('!project' => $request['name'])); + if ($select == 'never') { + return drush_set_error('DRUSH_PM_NO_STABLE_RELEASE', $message); + } + drush_log($message, LogLevel::WARNING); + if ($select == 'ignore') { + return NULL; + } + } + } + + // At this point the only chance is to ask the user to choose a release. + if ($restrict_to == 'dev') { + $filter = 'dev'; + } + elseif ($all) { + $filter = 'all'; + } + else { + $filter = ''; + } + $releases = $project_release_info->filterReleases($filter, $version); + + // Special checking: Drupal 6 is EOL, so there are no stable + // releases for ANY contrib project. In this case, we'll default + // to the best release, unless the user specified --select. + $version_major = drush_drupal_major_version(); + if (($select != 'always') && ($version_major < 7)) { + $bestRelease = Project::getBestRelease($releases); + if (!empty($bestRelease)) { + $message = dt('Drupal !major has reached EOL, so there are no stable releases for any contrib projects. Selected the best release, !project.', array('!major' => $version_major, '!project' => $bestRelease['name'])); + drush_log($message, LogLevel::WARNING); + return $bestRelease; + } + } + + $options = array(); + foreach($releases as $release) { + $options[$release['version']] = array($release['version'], '-', gmdate('Y-M-d', $release['date']), '-', implode(', ', $release['release_status'])); + } + $choice = drush_choice($options, dt('Choose one of the available releases for !project:', array('!project' => $request['name']))); + if (!$choice) { + return drush_user_abort(); + } + + return $releases[$choice]; + } + + /** + * Check if a project is available in the update service. + * + * Optionally check for consistency by comparing given project type and + * the type obtained from the update service. + * + * @param array $request + * A request array. + * @param string $type + * Optional. If provided, will do a consistent check of the project type. + * + * @return boolean + * True if the project exists and type matches. + */ + public function checkProject($request, $type = NULL) { + $project_release_info = $this->get($request); + if (!$project_release_info) { + return FALSE; + } + if ($type) { + if ($project_release_info->getType() != $type) { + return FALSE; + } + } + + return TRUE; + } +} diff --git a/vendor/drush/drush/lib/Drush/UpdateService/StatusInfoDrupal6.php b/vendor/drush/drush/lib/Drush/UpdateService/StatusInfoDrupal6.php new file mode 100644 index 0000000000..b0cc4d6428 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/UpdateService/StatusInfoDrupal6.php @@ -0,0 +1,71 @@ +update_check_disabled = $conf['update_advanced_check_disabled']; + $conf['update_advanced_check_disabled'] = $check_disabled; + } + } + + /** + * {@inheritdoc} + */ + function afterGetStatus(&$update_info, $projects, $check_disabled) { + // Restore Drupal settings. + if (!is_null($check_disabled)) { + global $conf; + $conf['update_advanced_check_disabled'] = $this->update_check_disabled; + unset($this->update_check_disabled); + } + + // update_advanced.module sets a different project type + // for disabled projects. Here we normalize it. + if ($check_disabled) { + foreach ($update_info as $key => $project) { + if (in_array($project['project_type'], array('disabled-module', 'disabled-theme'))) { + $update_info[$key]['project_type'] = substr($project['project_type'], strpos($project['project_type'], '-') + 1); + } + } + } + } + + /** + * Obtains release info for all installed projects via update.module. + * + * @see update_get_available(). + * @see update_manual_status(). + */ + protected function getAvailableReleases() { + // We force a refresh if the cache is not available. + if (!cache_get('update_available_releases', 'cache_update')) { + $this->refresh(); + } + + $available = update_get_available(TRUE); + + // Force to invalidate some update_status caches that are only cleared + // when visiting update status report page. + if (function_exists('_update_cache_clear')) { + _update_cache_clear('update_project_data'); + _update_cache_clear('update_project_projects'); + } + + return $available; + } +} + diff --git a/vendor/drush/drush/lib/Drush/UpdateService/StatusInfoDrupal7.php b/vendor/drush/drush/lib/Drush/UpdateService/StatusInfoDrupal7.php new file mode 100644 index 0000000000..0e6ae6eb46 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/UpdateService/StatusInfoDrupal7.php @@ -0,0 +1,109 @@ +update_check_disabled = $conf['update_check_disabled']; + $conf['update_check_disabled'] = $check_disabled; + } + } + + /** + * {@inheritdoc} + */ + function afterGetStatus(&$update_info, $projects, $check_disabled) { + // Restore Drupal settings. + if (!is_null($check_disabled)) { + global $conf; + $conf['update_check_disabled'] = $this->update_check_disabled; + unset($this->update_check_disabled); + } + + // update.module sets a different project type + // for disabled projects. Here we normalize it. + if ($check_disabled) { + foreach ($update_info as $key => $project) { + if (in_array($project['project_type'], array('module-disabled', 'theme-disabled'))) { + $update_info[$key]['project_type'] = substr($project['project_type'], 0, strpos($project['project_type'], '-')); + } + } + } + } + + /** + * Obtains release info for all installed projects via update.module. + * + * @see update_get_available(). + * @see update_manual_status(). + */ + protected function getAvailableReleases() { + // Force to invalidate some caches that are only cleared + // when visiting update status report page. This allow to detect changes in + // .info files. + _update_cache_clear('update_project_data'); + _update_cache_clear('update_project_projects'); + + // From update_get_available(): Iterate all projects and create a fetch task + // for those we have no information or is obsolete. + $available = _update_get_cached_available_releases(); + + module_load_include('inc', 'update', 'update.compare'); + $update_projects = update_get_projects(); + + foreach ($update_projects as $key => $project) { + if (empty($available[$key])) { + update_create_fetch_task($project); + continue; + } + if ($project['info']['_info_file_ctime'] > $available[$key]['last_fetch']) { + $available[$key]['fetch_status'] = UPDATE_FETCH_PENDING; + } + if (empty($available[$key]['releases'])) { + $available[$key]['fetch_status'] = UPDATE_FETCH_PENDING; + } + if (!empty($available[$key]['fetch_status']) && $available[$key]['fetch_status'] == UPDATE_FETCH_PENDING) { + update_create_fetch_task($project); + } + } + + // Set a batch to process all pending tasks. + $batch = array( + 'operations' => array( + array('update_fetch_data_batch', array()), + ), + 'finished' => 'update_fetch_data_finished', + 'file' => drupal_get_path('module', 'update') . '/update.fetch.inc', + ); + batch_set($batch); + drush_backend_batch_process(); + + // Clear any error set by a failed update fetch task. This avoid rollbacks. + drush_clear_error(); + + // Calculate update status data. + $available = _update_get_cached_available_releases(); + return $available; + } +} + diff --git a/vendor/drush/drush/lib/Drush/UpdateService/StatusInfoDrupal8.php b/vendor/drush/drush/lib/Drush/UpdateService/StatusInfoDrupal8.php new file mode 100644 index 0000000000..64b6a956f4 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/UpdateService/StatusInfoDrupal8.php @@ -0,0 +1,172 @@ +engine_type = $type; + $this->engine = $engine; + $this->engine_config = $config; + } + + /** + * {@inheritdoc} + */ + function lastCheck() { + $last_check = \Drupal::state()->get('update.last_check') ?: 0; + return $last_check; + } + + /** + * {@inheritdoc} + */ + function refresh() { + update_refresh(); + } + + /** + * Perform adjustments before running get status. + * + * - Enforce check-disabled option on update module. + */ + function beforeGetStatus(&$projects, $check_disabled) { + // If check-disabled option was provided, alter Drupal settings temporarily. + // There's no other way to hook into this. + if (!is_null($check_disabled)) { + $config = \Drupal::config('update.settings'); + $this->update_check_disabled = $config->get('check.disabled_extensions'); + $config->set('check.disabled_extensions', (bool)$check_disabled); + } + } + + /** + * Get update information for all installed projects. + * + * @return + * Array of update status information. + */ + function getStatus($projects, $check_disabled) { + $this->beforeGetStatus($projects, $check_disabled); + $available = $this->getAvailableReleases(); + $update_info = $this->calculateUpdateStatus($available, $projects); + $this->afterGetStatus($update_info, $projects, $check_disabled); + return $update_info; + } + + /** + * Perform adjustments after running get status. + * + * - Restore check-disabled setting in update module. + * - Adjust project type for disabled projects. + */ + function afterGetStatus(&$update_info, $projects, $check_disabled) { + // Restore Drupal settings. + if (!is_null($check_disabled)) { + \Drupal::config('update.settings')->set('check.disabled_extensions', $this->update_check_disabled); + unset($this->update_check_disabled); + } + + // update.module sets a different project type + // for disabled projects. Here we normalize it. + if ($check_disabled) { + foreach ($update_info as $key => $project) { + if (in_array($project['project_type'], array('module-disabled', 'theme-disabled'))) { + $update_info[$key]['project_type'] = substr($project['project_type'], 0, strpos($project['project_type'], '-')); + } + } + } + } + + /** + * Obtains release info for all installed projects via update.module. + * + * @see update_get_available(). + * @see \Drupal\update\Controller\UpdateController::updateStatusManually() + */ + protected function getAvailableReleases() { + // Force to invalidate some caches that are only cleared + // when visiting update status report page. This allow to detect changes in + // .info.yml files. + \Drupal::keyValueExpirable('update')->deleteMultiple(array('update_project_projects', 'update_project_data')); + + // From update_get_available(): Iterate all projects and create a fetch task + // for those we have no information or is obsolete. + $available = \Drupal::keyValueExpirable('update_available_releases')->getAll(); + $update_projects = \Drupal::service('update.manager')->getProjects(); + foreach ($update_projects as $key => $project) { + if (empty($available[$key])) { + \Drupal::service('update.processor')->createFetchTask($project); + continue; + } + if ($project['info']['_info_file_ctime'] > $available[$key]['last_fetch']) { + $available[$key]['fetch_status'] = UPDATE_FETCH_PENDING; + } + if (empty($available[$key]['releases'])) { + $available[$key]['fetch_status'] = UPDATE_FETCH_PENDING; + } + if (!empty($available[$key]['fetch_status']) && $available[$key]['fetch_status'] == UPDATE_FETCH_PENDING) { + \Drupal::service('update.processor')->createFetchTask($project); + } + } + + // Set a batch to process all pending tasks. + $batch = array( + 'operations' => array( + array(array(\Drupal::service('update.manager'), 'fetchDataBatch'), array()), + ), + 'finished' => 'update_fetch_data_finished', + 'file' => drupal_get_path('module', 'update') . '/update.fetch.inc', + ); + batch_set($batch); + drush_backend_batch_process(); + + // Clear any error set by a failed update fetch task. This avoid rollbacks. + drush_clear_error(); + + return \Drupal::keyValueExpirable('update_available_releases')->getAll(); + } + + /** + * Calculates update status for all projects via update.module. + */ + protected function calculateUpdateStatus($available, $projects) { + module_load_include('inc', 'update', 'update.compare'); + $data = update_calculate_project_data($available); + + foreach ($data as $project_name => $project) { + // Discard custom projects. + if ($project['status'] == UPDATE_UNKNOWN) { + unset($data[$project_name]); + continue; + } + // Discard projects with unknown installation path. + if ($project_name != 'drupal' && !isset($projects[$project_name]['path'])) { + unset($data[$project_name]); + continue; + } + + // Add some info from the project to $data. + $data[$project_name] += array( + 'path' => isset($projects[$project_name]['path']) ? $projects[$project_name]['path'] : '', + 'label' => $projects[$project_name]['label'], + ); + // Store all releases, not just the ones selected by update.module. + // We use it to allow the user to update to a specific version. + if (isset($available[$project_name]['releases'])) { + $data[$project_name]['releases'] = $available[$project_name]['releases']; + } + } + + return $data; + } +} + diff --git a/vendor/drush/drush/lib/Drush/UpdateService/StatusInfoDrush.php b/vendor/drush/drush/lib/Drush/UpdateService/StatusInfoDrush.php new file mode 100644 index 0000000000..636cae4376 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/UpdateService/StatusInfoDrush.php @@ -0,0 +1,420 @@ +engine_type = $type; + $this->engine = $engine; + $this->engine_config = $config; + } + + /** + * {@inheritdoc} + */ + function lastCheck() { + $older = 0; + + // Iterate all projects and get the time of the older release info. + $projects = drush_get_projects(); + foreach ($projects as $project_name => $project) { + $request = pm_parse_request($project_name, NULL, $projects); + $url = Project::buildFetchUrl($request); + $cache_file = drush_download_file_name($url); + if (file_exists($cache_file)) { + $ctime = filectime($cache_file); + $older = (!$older) ? $ctime : min($ctime, $older); + } + } + + return $older; + } + + /** + * {@inheritdoc} + */ + function refresh() { + $release_info = drush_include_engine('release_info', 'updatexml'); + + // Clear all caches for the available projects. + $projects = drush_get_projects(); + foreach ($projects as $project_name => $project) { + $request = pm_parse_request($project_name, NULL, $projects); + $release_info->clearCached($request); + } + } + + /** + * Get update information for all installed projects. + * + * @return + * Array of update status information. + */ + function getStatus($projects, $check_disabled) { + // Exclude disabled projects. + if (!$check_disabled) { + foreach ($projects as $project_name => $project) { + if (!$project['status']) { + unset($projects[$project_name]); + } + } + } + $available = $this->getAvailableReleases($projects); + $update_info = $this->calculateUpdateStatus($available, $projects); + return $update_info; + } + + /** + * Obtains release info for projects. + */ + private function getAvailableReleases($projects) { + drush_log(dt('Checking available update data ...'), LogLevel::OK); + + $release_info = drush_include_engine('release_info', 'updatexml'); + + $available = array(); + foreach ($projects as $project_name => $project) { + // Discard projects with unknown installation path. + if ($project_name != 'drupal' && !isset($project['path'])) { + continue; + } + drush_log(dt('Checking available update data for !project.', array('!project' => $project['label'])), LogLevel::OK); + $request = $project_name . (isset($project['core']) ? '-' . $project['core'] : ''); + $request = pm_parse_request($request, NULL, $projects); + $project_release_info = $release_info->get($request); + if ($project_release_info) { + $available[$project_name] = $project_release_info; + } + } + + // Clear any error set by a failed project. This avoid rollbacks. + drush_clear_error(); + + return $available; + } + + /** + * Calculates update status for given projects. + */ + private function calculateUpdateStatus($available, $projects) { + $update_info = array(); + foreach ($available as $project_name => $project_release_info) { + // Obtain project 'global' status. NULL status is ok (project published), + // otherwise it signals something is bad with the project (revoked, etc). + $project_status = $this->calculateProjectStatus($project_release_info); + // Discard custom projects. + if ($project_status == DRUSH_UPDATESTATUS_UNKNOWN) { + continue; + } + + // Prepare update info. + $project = $projects[$project_name]; + $is_core = ($project['type'] == 'core'); + $version = pm_parse_version($project['version'], $is_core); + // If project version ends with 'dev', this is a dev snapshot. + $install_type = (substr($project['version'], -3, 3) == 'dev') ? 'dev' : 'official'; + $project_update_info = array( + 'name' => $project_name, + 'label' => $project['label'], + 'path' => isset($project['path']) ? $project['path'] : '', + 'install_type' => $install_type, + 'existing_version' => $project['version'], + 'existing_major' => $version['version_major'], + 'status' => $project_status, + 'datestamp' => empty($project['datestamp']) ? NULL : $project['datestamp'], + ); + + // If we don't have a project status yet, it means this is + // a published project and we need to obtain its update status + // and recommended release. + if (is_null($project_status)) { + $this->calculateProjectUpdateStatus($project_release_info, $project_update_info); + } + + // We want to ship all release info data including all releases, + // not just the ones selected by calculateProjectUpdateStatus(). + // We use it to allow the user to update to a specific version. + unset($project_update_info['releases']); + $update_info[$project_name] = $project_update_info + $project_release_info->getInfo(); + } + + return $update_info; + } + + /** + * Obtain the project status in the update service. + * + * This is not the update status of the installed version + * but the project 'global' status (unpublished, revoked, etc). + * + * @see update_calculate_project_status(). + */ + private function calculateProjectStatus($project_release_info) { + $project_status = NULL; + + // If connection to the update service went wrong, or the received xml + // is malformed, we don't have a UpdateService::Project object. + if (!$project_release_info) { + $project_status = DRUSH_UPDATESTATUS_NOT_FETCHED; + } + else { + switch ($project_release_info->getStatus()) { + case 'insecure': + $project_status = DRUSH_UPDATESTATUS_NOT_SECURE; + break; + case 'unpublished': + case 'revoked': + $project_status = DRUSH_UPDATESTATUS_REVOKED; + break; + case 'unsupported': + $project_status = DRUSH_UPDATESTATUS_NOT_SUPPORTED; + break; + case 'unknown': + $project_status = DRUSH_UPDATESTATUS_UNKNOWN; + break; + } + } + return $project_status; + } + + /** + * Obtain the update status of a project and the recommended release. + * + * This is a stripped down version of update_calculate_project_status(). + * That function has the same logic in Drupal 6,7,8. + * Note: in Drupal 6 this is part of update_calculate_project_data(). + * + * @see update_calculate_project_status(). + */ + private function calculateProjectUpdateStatus($project_release_info, &$project_data) { + $available = $project_release_info->getInfo(); + + /** + * Here starts the code adapted from update_calculate_project_status(). + * Line 492 in Drupal 7. + * + * Changes are: + * - Use DRUSH_UPDATESTATUS_* constants instead of DRUSH_UPDATESTATUS_* + * - Remove error conditions we already handle + * - Remove presentation code ('extra' and 'reason' keys in $project_data) + * - Remove "also available" information. + */ + + // Figure out the target major version. + $existing_major = $project_data['existing_major']; + $supported_majors = array(); + if (isset($available['supported_majors'])) { + $supported_majors = explode(',', $available['supported_majors']); + } + elseif (isset($available['default_major'])) { + // Older release history XML file without supported or recommended. + $supported_majors[] = $available['default_major']; + } + + if (in_array($existing_major, $supported_majors)) { + // Still supported, stay at the current major version. + $target_major = $existing_major; + } + elseif (isset($available['recommended_major'])) { + // Since 'recommended_major' is defined, we know this is the new XML + // format. Therefore, we know the current release is unsupported since + // its major version was not in the 'supported_majors' list. We should + // find the best release from the recommended major version. + $target_major = $available['recommended_major']; + $project_data['status'] = DRUSH_UPDATESTATUS_NOT_SUPPORTED; + } + elseif (isset($available['default_major'])) { + // Older release history XML file without recommended, so recommend + // the currently defined "default_major" version. + $target_major = $available['default_major']; + } + else { + // Malformed XML file? Stick with the current version. + $target_major = $existing_major; + } + + // Make sure we never tell the admin to downgrade. If we recommended an + // earlier version than the one they're running, they'd face an + // impossible data migration problem, since Drupal never supports a DB + // downgrade path. In the unfortunate case that what they're running is + // unsupported, and there's nothing newer for them to upgrade to, we + // can't print out a "Recommended version", but just have to tell them + // what they have is unsupported and let them figure it out. + $target_major = max($existing_major, $target_major); + + $release_patch_changed = ''; + $patch = ''; + + foreach ($available['releases'] as $version => $release) { + // First, if this is the existing release, check a few conditions. + if ($project_data['existing_version'] === $version) { + if (isset($release['terms']['Release type']) && + in_array('Insecure', $release['terms']['Release type'])) { + $project_data['status'] = DRUSH_UPDATESTATUS_NOT_SECURE; + } + elseif ($release['status'] == 'unpublished') { + $project_data['status'] = DRUSH_UPDATESTATUS_REVOKED; + } + elseif (isset($release['terms']['Release type']) && + in_array('Unsupported', $release['terms']['Release type'])) { + $project_data['status'] = DRUSH_UPDATESTATUS_NOT_SUPPORTED; + } + } + + // Otherwise, ignore unpublished, insecure, or unsupported releases. + if ($release['status'] == 'unpublished' || + (isset($release['terms']['Release type']) && + (in_array('Insecure', $release['terms']['Release type']) || + in_array('Unsupported', $release['terms']['Release type'])))) { + continue; + } + + // See if this is a higher major version than our target and discard it. + // Note: at this point Drupal record it as an "Also available" release. + if (isset($release['version_major']) && $release['version_major'] > $target_major) { + continue; + } + + // Look for the 'latest version' if we haven't found it yet. Latest is + // defined as the most recent version for the target major version. + if (!isset($project_data['latest_version']) + && $release['version_major'] == $target_major) { + $project_data['latest_version'] = $version; + $project_data['releases'][$version] = $release; + } + + // Look for the development snapshot release for this branch. + if (!isset($project_data['dev_version']) + && $release['version_major'] == $target_major + && isset($release['version_extra']) + && $release['version_extra'] == 'dev') { + $project_data['dev_version'] = $version; + $project_data['releases'][$version] = $release; + } + + // Look for the 'recommended' version if we haven't found it yet (see + // phpdoc at the top of this function for the definition). + if (!isset($project_data['recommended']) + && $release['version_major'] == $target_major + && isset($release['version_patch'])) { + if ($patch != $release['version_patch']) { + $patch = $release['version_patch']; + $release_patch_changed = $release; + } + if (empty($release['version_extra']) && $patch == $release['version_patch']) { + $project_data['recommended'] = $release_patch_changed['version']; + $project_data['releases'][$release_patch_changed['version']] = $release_patch_changed; + } + } + + // Stop searching once we hit the currently installed version. + if ($project_data['existing_version'] === $version) { + break; + } + + // If we're running a dev snapshot and have a timestamp, stop + // searching for security updates once we hit an official release + // older than what we've got. Allow 100 seconds of leeway to handle + // differences between the datestamp in the .info file and the + // timestamp of the tarball itself (which are usually off by 1 or 2 + // seconds) so that we don't flag that as a new release. + if ($project_data['install_type'] == 'dev') { + if (empty($project_data['datestamp'])) { + // We don't have current timestamp info, so we can't know. + continue; + } + elseif (isset($release['date']) && ($project_data['datestamp'] + 100 > $release['date'])) { + // We're newer than this, so we can skip it. + continue; + } + } + + // See if this release is a security update. + if (isset($release['terms']['Release type']) + && in_array('Security update', $release['terms']['Release type'])) { + $project_data['security updates'][] = $release; + } + } + + // If we were unable to find a recommended version, then make the latest + // version the recommended version if possible. + if (!isset($project_data['recommended']) && isset($project_data['latest_version'])) { + $project_data['recommended'] = $project_data['latest_version']; + } + + // + // Check to see if we need an update or not. + // + + if (!empty($project_data['security updates'])) { + // If we found security updates, that always trumps any other status. + $project_data['status'] = DRUSH_UPDATESTATUS_NOT_SECURE; + } + + if (isset($project_data['status'])) { + // If we already know the status, we're done. + return; + } + + // If we don't know what to recommend, there's nothing we can report. + // Bail out early. + if (!isset($project_data['recommended'])) { + $project_data['status'] = DRUSH_UPDATESTATUS_UNKNOWN; + $project_data['reason'] = t('No available releases found'); + return; + } + + // If we're running a dev snapshot, compare the date of the dev snapshot + // with the latest official version, and record the absolute latest in + // 'latest_dev' so we can correctly decide if there's a newer release + // than our current snapshot. + if ($project_data['install_type'] == 'dev') { + if (isset($project_data['dev_version']) && $available['releases'][$project_data['dev_version']]['date'] > $available['releases'][$project_data['latest_version']]['date']) { + $project_data['latest_dev'] = $project_data['dev_version']; + } + else { + $project_data['latest_dev'] = $project_data['latest_version']; + } + } + + // Figure out the status, based on what we've seen and the install type. + switch ($project_data['install_type']) { + case 'official': + if ($project_data['existing_version'] === $project_data['recommended'] || $project_data['existing_version'] === $project_data['latest_version']) { + $project_data['status'] = DRUSH_UPDATESTATUS_CURRENT; + } + else { + $project_data['status'] = DRUSH_UPDATESTATUS_NOT_CURRENT; + } + break; + + case 'dev': + $latest = $available['releases'][$project_data['latest_dev']]; + if (empty($project_data['datestamp'])) { + $project_data['status'] = DRUSH_UPDATESTATUS_NOT_CHECKED; + } + elseif (($project_data['datestamp'] + 100 > $latest['date'])) { + $project_data['status'] = DRUSH_UPDATESTATUS_CURRENT; + } + else { + $project_data['status'] = DRUSH_UPDATESTATUS_NOT_CURRENT; + } + break; + + default: + $project_data['status'] = DRUSH_UPDATESTATUS_UNKNOWN; + } + } +} + diff --git a/vendor/drush/drush/lib/Drush/UpdateService/StatusInfoInterface.php b/vendor/drush/drush/lib/Drush/UpdateService/StatusInfoInterface.php new file mode 100644 index 0000000000..3d3592a8d9 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/UpdateService/StatusInfoInterface.php @@ -0,0 +1,35 @@ + $name)); + } + + /** + * {@inheritdoc} + */ + public function load_by_mail($mail) { + return user_load(array('mail' => $mail)); + } + +} diff --git a/vendor/drush/drush/lib/Drush/User/User7.php b/vendor/drush/drush/lib/Drush/User/User7.php new file mode 100644 index 0000000000..531cacb4e4 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/User/User7.php @@ -0,0 +1,14 @@ +save(); + return new UserSingle8($account); + } + + /** + * Attempt to load a user account. + * + * @param int $uid + * @return \Drupal\user\Entity\User; + */ + public function load_by_uid($uid) { + return User::load($uid); + } + + /** + * {inheritdoc} + */ + public function getCurrentUserAsAccount() { + return \Drupal::currentUser()->getAccount(); + } + + /** + * Set the current user in Drupal. + * + * @param \Drupal\Core\Session\AccountInterface $account + */ + public function setCurrentUser($account) { + // Some parts of Drupal still rely on a global user object. + // @todo remove once https://www.drupal.org/node/2163205 is in. + global $user; + $user = $account; + \Drupal::currentUser()->setAccount($account); + } +} diff --git a/vendor/drush/drush/lib/Drush/User/User9.php b/vendor/drush/drush/lib/Drush/User/User9.php new file mode 100644 index 0000000000..00f7b2b4a7 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/User/User9.php @@ -0,0 +1,20 @@ +getStorage('user') + ->create($properties); + $account->save(); + return new UserSingle9($account); + } +} diff --git a/vendor/drush/drush/lib/Drush/User/UserList.php b/vendor/drush/drush/lib/Drush/User/UserList.php new file mode 100644 index 0000000000..3c38e2132d --- /dev/null +++ b/vendor/drush/drush/lib/Drush/User/UserList.php @@ -0,0 +1,134 @@ +accounts = $this->getFromOptions() + $this->getFromParameters($inputs)) { + return $this; + } + else { + throw new UserListException('Unable to find a matching user.'); + } + } + + /** + * Iterate over each account and call the specified method. + * + * @param $method + * A method on a UserSingleBase object. + * @param array $params + * An array of params to pass to the method. + * @return array + * An associate array of values keyed by account ID. + */ + public function each($method, array $params = array()) { + foreach ($this->accounts as $account) { + $return[$account->id()] = call_user_func_array(array($account, $method), $params); + } + return $return; + } + + /* + * Check common options for specifying users. If valid, return the accounts. + * + * @return \Drush\User\UserSingleBase[] + */ + function getFromOptions() { + $accounts = array(); + $userversion = drush_user_get_class(); + if ($mails = _convert_csv_to_array(drush_get_option('mail'))) { + foreach ($mails as $mail) { + if ($account = $userversion->load_by_mail($mail)) { + $single = drush_usersingle_get_class($account); + $accounts[$single->id()] = $single; + } + else { + throw new UserListException('Unable to find a matching user for ' . $mail . '.'); + } + } + } + if ($names = _convert_csv_to_array(drush_get_option('name'))) { + foreach ($names as $name) { + if ($account = $userversion->load_by_name($name)) { + $single = drush_usersingle_get_class($account); + $accounts[$single->id()] = $single; + } + else { + throw new UserListException('Unable to find a matching user for ' . $name . '.'); + } + } + } + if ($userids = _convert_csv_to_array(drush_get_option('uid'))) { + foreach ($userids as $userid) { + if (is_numeric($userid) && $account = $userversion->load_by_uid($userid)) { + $single = drush_usersingle_get_class($account); + $accounts[$single->id()] = $single; + } + else { + throw new UserListException('Unable to find a matching user for ' . $userid . '.'); + } + } + } + return $accounts; + } + + /** + * Given a comma-separated list of inputs, return accounts + * for users that match by uid,name or email address. + * + * @param string $inputs + * A comma delimited string (or array) of arguments, specifying user account(s). + * + * @throws UserListException + * If any input is unmatched, an exception is thrown. + * + * @return \Drush\User\UserSingleBase[] + * An associative array of UserSingleBase objects, keyed by user id. + */ + public static function getFromParameters($inputs) { + $accounts = array(); + $userversion = drush_user_get_class(); + if ($inputs && $userversion) { + $inputs = _convert_csv_to_array($inputs); + foreach($inputs as $input) { + if (is_numeric($input) && $account = $userversion->load_by_uid($input)) { + + } + elseif ($account = $userversion->load_by_name($input)) { + + } + elseif ($account = $userversion->load_by_mail($input)) { + + } + else { + // Unable to load an account for the input. + throw new UserListException('Unable to find a matching user for ' . $input . '.'); + } + // Populate $accounts with a UserSingle object. Will go into $this->accounts. + $single = drush_usersingle_get_class($account); + $accounts[$single->id()] = $single; + } + } + return $accounts; + } + + /* + * A comma delimited list of names built from $this->accounts. + */ + public function names() { + $names = array(); + foreach ($this->accounts as $account) { + $names[] = $account->getUsername(); + } + return implode(', ', $names); + } +} diff --git a/vendor/drush/drush/lib/Drush/User/UserListException.php b/vendor/drush/drush/lib/Drush/User/UserListException.php new file mode 100644 index 0000000000..4b770c3a55 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/User/UserListException.php @@ -0,0 +1,5 @@ +account->uid); + } +} diff --git a/vendor/drush/drush/lib/Drush/User/UserSingle7.php b/vendor/drush/drush/lib/Drush/User/UserSingle7.php new file mode 100644 index 0000000000..98c98c9b1c --- /dev/null +++ b/vendor/drush/drush/lib/Drush/User/UserSingle7.php @@ -0,0 +1,46 @@ +account->uid)); + } + + public function unblock() { + user_user_operations_unblock(array($this->account->uid)); + } + + public function addRole($rid) { + user_multiple_role_edit(array($this->account->uid), 'add_role', $rid); + } + + public function removeRole($rid) { + user_multiple_role_edit(array($this->account->uid), 'remove_role', $rid); + } + + function info() { + $userinfo = (array)$this->account; + unset($userinfo['data']); + unset($userinfo['block']); + unset($userinfo['form_build_id']); + foreach (array('created', 'access', 'login') as $key) { + $userinfo['user_' . $key] = drush_format_date($userinfo[$key]); + } + $userinfo['user_status'] = $userinfo['status'] ? 'active' : 'blocked'; + return $userinfo; + } + + function password($pass) { + user_save($this->account, array('pass' => $pass)); + } + + public function getUsername() { + return $this->account->name; + } + + public function id() { + return $this->account->uid; + } +} diff --git a/vendor/drush/drush/lib/Drush/User/UserSingle8.php b/vendor/drush/drush/lib/Drush/User/UserSingle8.php new file mode 100644 index 0000000000..07db56c9f2 --- /dev/null +++ b/vendor/drush/drush/lib/Drush/User/UserSingle8.php @@ -0,0 +1,7 @@ + $this->account->id(), + 'name' => $this->account->getAccountName(), + 'password' => $this->account->getPassword(), + 'mail' => $this->account->getEmail(), + 'user_created' => $this->account->getCreatedTime(), + 'created' => drush_format_date($this->account->getCreatedTime()), + 'user_access' => $this->account->getLastAccessedTime(), + 'access' => drush_format_date($this->account->getLastAccessedTime()), + 'user_login' => $this->account->getLastLoginTime(), + 'login' => drush_format_date($this->account->getLastLoginTime()), + 'user_status' => $this->account->get('status')->value, + 'status' => $this->account->isActive() ? 'active' : 'blocked', + 'timezone' => $this->account->getTimeZone(), + 'roles' => $this->account->getRoles(), + 'langcode' => $this->account->getPreferredLangcode(), + 'uuid' => $this->account->uuid->value, + ); + } + +} diff --git a/vendor/drush/drush/lib/Drush/User/UserSingleBase.php b/vendor/drush/drush/lib/Drush/User/UserSingleBase.php new file mode 100644 index 0000000000..e50965247d --- /dev/null +++ b/vendor/drush/drush/lib/Drush/User/UserSingleBase.php @@ -0,0 +1,136 @@ +account = $account; + } + + /** + * A flatter and simpler array presentation of a Drupal $user object. + * + * @return array + */ + public function info() { + return array( + 'uid' => $this->account->id(), + 'name' => $this->account->getUsername(), + 'password' => $this->account->getPassword(), + 'mail' => $this->account->getEmail(), + 'user_created' => $this->account->getCreatedTime(), + 'created' => drush_format_date($this->account->getCreatedTime()), + 'user_access' => $this->account->getLastAccessedTime(), + 'access' => drush_format_date($this->account->getLastAccessedTime()), + 'user_login' => $this->account->getLastLoginTime(), + 'login' => drush_format_date($this->account->getLastLoginTime()), + 'user_status' => $this->account->get('status')->value, + 'status' => $this->account->isActive() ? 'active' : 'blocked', + 'timezone' => $this->account->getTimeZone(), + 'roles' => $this->account->getRoles(), + 'langcode' => $this->account->getPreferredLangcode(), + 'uuid' => $this->account->uuid->value, + ); + } + + /** + * Block a user from login. + */ + public function block() { + $this->account->block(); + $this->account->save(); + } + + /** + * Unblock a user from login. + */ + public function unblock() { + $this->account->get('status')->value = 1; + $this->account->save(); + } + + /** + * Add a role to the current user. + * + * @param $rid + * A role ID. + */ + public function addRole($rid) { + $this->account->addRole($rid); + $this->account->save(); + } + + /** + * Remove a role from the current user. + * + * @param $rid + * A role ID. + */ + public function removeRole($rid) { + $this->account->removeRole($rid); + $this->account->save(); + } + + /** + * Block a user and remove or reassign their content. + */ + public function cancel() { + if (drush_get_option('delete-content')) { + user_cancel(array(), $this->id(), 'user_cancel_delete'); + } + else { + user_cancel(array(), $this->id(), 'user_cancel_reassign'); + } + // I got the following technique here: http://drupal.org/node/638712 + $batch =& batch_get(); + $batch['progressive'] = FALSE; + drush_backend_batch_process(); + } + + /** + * Change a user's password. + * + * @param $password + */ + public function password($password) { + $this->account->setPassword($password); + $this->account->save(); + } + + /** + * Build a one time login link. + * + * @param string $path + * @return string + */ + public function passResetUrl($path = '') { + $url = user_pass_reset_url($this->account) . '/login'; + if ($path) { + $url .= '?destination=' . $path; + } + return $url; + } + + /** + * Get a user's name. + * @return string + */ + public function getUsername() { + if (method_exists($this->account, 'getAccountName')) { + return $this->account->getAccountName(); + } + return $this->account->getUsername(); + } + + /** + * Return an id from a Drupal user account. + * @return int + */ + public function id() { + return $this->account->id(); + } +} diff --git a/vendor/drush/drush/lib/Drush/User/UserVersion.php b/vendor/drush/drush/lib/Drush/User/UserVersion.php new file mode 100644 index 0000000000..c0dcda5aeb --- /dev/null +++ b/vendor/drush/drush/lib/Drush/User/UserVersion.php @@ -0,0 +1,78 @@ +getCurrentUserAsAccount()); + } + + /** + * Set the current "global" user account in Drupal. + + * @param + * A user object. + */ + public function setCurrentUser($account) { + global $user; + $user = $account; + } +} diff --git a/vendor/drush/drush/misc/druplicon-color.txt b/vendor/drush/drush/misc/druplicon-color.txt new file mode 100644 index 0000000000..d930be83c6 --- /dev/null +++ b/vendor/drush/drush/misc/druplicon-color.txt @@ -0,0 +1,50 @@ +                             .,.                                          +                                  .cd:..                                            +                                      .xXd,,'..                                         +                                    .lXWx;;;,,..                                     +                                  .,dXWXo;;;;;,,..                              +                                 .;dKWWKx:;;;;;;;,,'..                                   +                             .;oOXNXKOo;;;;;;;;;;;;,,,'..                               +                     .:dOXWMMN0Okl;;;;;;;;;;;;;;;;,,,'..                        +                    .,lk0NMMMMMMNKOxc;;;;;;;;;;;;;;;;;;;;,,,'..                    +               .'cx0XWMMMMMMMWX0kd:;;;;;;;;;;;;;;;;;;;;;;;;;,,,..                 +              .'cx0NMMMMMMMMMWX0Oxl;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,'..               +          .;d0NMMMMMMMMMMWX0Oxl:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,...              +         .:kXWMMMMMMMMMWNK0kdl:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,'..       +          .cONMMMMMMMMMWNX0Okoc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,'.      +        .;kNMMMMMMMMWNX0Okdl:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,'..       +      .oXMMMMMMWWXK0Oxdl:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,'..       +    ,oKWWWWNXKK0kxoc:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,'..       +    'lOO0000OOxdlc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,'''.      +    .,lxkxxdolc:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,''''..   +   .,;;;::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,'''''..   +  .,;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,'''''''.   + .';;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,'''''''..  +.',;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,'''''''''.. +.,;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,''''''''''.. +',;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,''''''''''''. +,,;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,''''''''''''''. +,;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,'''''''''''''''. +,;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,'''''''''''''''''' +,;;;;;;;;;;;;;;;;;;;;;;;;;;;cldxkkOkkxxdlc;;;;;;;;;;;;;;;;;;;;;;;;,,''''''''''','''''''' +,;;;;;;;;;;;;;;;;;;;;;;;:ox0XWMMMMMMMMMWNX0kxdc;;;;;;;;;;;;;;;;,,,'''''''';cdk00Okl,'''' +,;;;;;;;;;;;;;;;;;;;;;cxKWMMMMMMMMMMMMMMMMMMMWN0xl;;;;;;;;;;,,,'''''''';lkKWMMMMMMW0c''' +',;;;;;;;;;;;;;;;;;;:dKWMMMMMMMMMMMMMMMMMMMMMMMMMN0xl;;;;;,,'''''''';okXWMMMMMMMMMMM0:'. +.,;;;;;;;;;;;;;;;;;:kNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMN0xl;''''''',:oOXWMMMMMMMMMMMMMMNd'. +.',;;;;;;;;;;;;;;;;xWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMN0xolccoxONMMMMMMMMMMMMMMMMMMWd'. + .,;;;;;;;;;;;;;;;oXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWNWMMMMMMMMMMMMMMMMMMMMMMXl.. + .',;;;;;;;;;;;;;;xNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWX0OOOKNMMMMMMMMMMMMMMMMMMMM0;.  +  .',;;;;;;;;;;;;;dNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWN0xl:,''',cxXWMMMMMMMMMMMMMMMMWd.   +   .',;;;;;;;;;;;;lKMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWKko:,'''''''''':dKWMMMMMMMMMMMMMWk,    +    .',;;;;;;;;;;;;dXMMMMMMMMMMMMMMMMMMMMMMMMWX0xl;'''',;:::;,''''';oKWMMMMMMMMMMWO,     +    ..',,;;;;;;;;;;o0NMMMMMMMMMMMMMMMMMMN0xdo:,''',cdO0XXXXK0kl,'''';o0WMMMMMMMNd,      +     .''',,,,,,,,,,;lk0XWMMMMMMMMWNX0ko:,'''''';oONN0xdoodx0NNx,''''';lOXWWWXkc.       +       ..'''''''''''''',:lloodddoolc;'''''''''',xN0dc,'''''',oK0:''''''',:clc;..      +        ...''''''''''''''''''''''''''''''''''''':c,''''''''''';;''''''''''''..     +           ..''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''...      +            ...'''''''''''''''''''''',lxdc,'''''''''''''''''',:oxkl''''''..             +          ...''''''''''''''''''':kNMNK0OxdollcccclllodxO0XX0d;'''..                +          ..'''''''''''''''''',cxOKXXNWMMWWWWWWWWNXKOxo:'''..                   +                   ...'.'''''''''''''''',;:clooodddoollc:,'''...                    +                        ....'''''''''''''''''''''''''''.....                          +                         ....'..''''''''''''........                               diff --git a/vendor/drush/drush/misc/druplicon-no_color.txt b/vendor/drush/drush/misc/druplicon-no_color.txt new file mode 100644 index 0000000000..5a330a52a0 --- /dev/null +++ b/vendor/drush/drush/misc/druplicon-no_color.txt @@ -0,0 +1,50 @@ + .,. + .cd:.. + .xXd,,'.. + .lXWx;;;,,.. + .,dXWXo;;;;;,,.. + .;dKWWKx:;;;;;;;,,'.. + .;oOXNXKOo;;;;;;;;;;;;,,,'.. + .:dOXWMMN0Okl;;;;;;;;;;;;;;;;,,,'.. + .,lk0NMMMMMMNKOxc;;;;;;;;;;;;;;;;;;;;,,,'.. + .'cx0XWMMMMMMMWX0kd:;;;;;;;;;;;;;;;;;;;;;;;;;,,,.. + .'cx0NMMMMMMMMMWX0Oxl;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,'.. + .;d0NMMMMMMMMMMWX0Oxl:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,... + .:kXWMMMMMMMMMWNK0kdl:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,'.. + .cONMMMMMMMMMWNX0Okoc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,'. + .;kNMMMMMMMMWNX0Okdl:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,'.. + .oXMMMMMMWWXK0Oxdl:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,'.. + ,oKWWWWNXKK0kxoc:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,'.. + 'lOO0000OOxdlc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,'''. + .,lxkxxdolc:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,''''.. + .,;;;::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,'''''.. + .,;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,'''''''. + .';;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,'''''''.. +.',;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,'''''''''.. +.,;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,''''''''''.. +',;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,''''''''''''. +,,;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,''''''''''''''. +,;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,'''''''''''''''. +,;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,'''''''''''''''''' +,;;;;;;;;;;;;;;;;;;;;;;;;;;;cldxkkOkkxxdlc;;;;;;;;;;;;;;;;;;;;;;;;,,''''''''''','''''''' +,;;;;;;;;;;;;;;;;;;;;;;;:ox0XWMMMMMMMMMWNX0kxdc;;;;;;;;;;;;;;;;,,,'''''''';cdk00Okl,'''' +,;;;;;;;;;;;;;;;;;;;;;cxKWMMMMMMMMMMMMMMMMMMMWN0xl;;;;;;;;;;,,,'''''''';lkKWMMMMMMW0c''' +',;;;;;;;;;;;;;;;;;;:dKWMMMMMMMMMMMMMMMMMMMMMMMMMN0xl;;;;;,,'''''''';okXWMMMMMMMMMMM0:'. +.,;;;;;;;;;;;;;;;;;:kNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMN0xl;''''''',:oOXWMMMMMMMMMMMMMMNd'. +.',;;;;;;;;;;;;;;;;xWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMN0xolccoxONMMMMMMMMMMMMMMMMMMWd'. + .,;;;;;;;;;;;;;;;oXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWNWMMMMMMMMMMMMMMMMMMMMMMXl.. + .',;;;;;;;;;;;;;;xNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWX0OOOKNMMMMMMMMMMMMMMMMMMMM0;. + .',;;;;;;;;;;;;;dNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWN0xl:,''',cxXWMMMMMMMMMMMMMMMMWd. + .',;;;;;;;;;;;;lKMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWKko:,'''''''''':dKWMMMMMMMMMMMMMWk, + .',;;;;;;;;;;;;dXMMMMMMMMMMMMMMMMMMMMMMMMWX0xl;'''',;:::;,''''';oKWMMMMMMMMMMWO, + ..',,;;;;;;;;;;o0NMMMMMMMMMMMMMMMMMMN0xdo:,''',cdO0XXXXK0kl,'''';o0WMMMMMMMNd, + .''',,,,,,,,,,;lk0XWMMMMMMMMWNX0ko:,'''''';oONN0xdoodx0NNx,''''';lOXWWWXkc. + ..'''''''''''''',:lloodddoolc;'''''''''',xN0dc,'''''',oK0:''''''',:clc;.. + ...''''''''''''''''''''''''''''''''''''':c,''''''''''';;''''''''''''.. + ..''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''... + ...'''''''''''''''''''''',lxdc,'''''''''''''''''',:oxkl''''''.. + ...''''''''''''''''''':kNMNK0OxdollcccclllodxO0XX0d;'''.. + ..'''''''''''''''''',cxOKXXNWMMWWWWWWWWNXKOxo:'''.. + ...'.'''''''''''''''',;:clooodddoollc:,'''... + ....'''''''''''''''''''''''''''..... + ....'..''''''''''''........ diff --git a/vendor/drush/drush/misc/windrush_build/README.md b/vendor/drush/drush/misc/windrush_build/README.md new file mode 100644 index 0000000000..c5a7fd6b84 --- /dev/null +++ b/vendor/drush/drush/misc/windrush_build/README.md @@ -0,0 +1,8 @@ +This directory holds the build script for Drush's Windows distribution. This script is only useful to Drush administrators who are generating a new build. + +To use this script: + +- Edit the metadata at the top +- Run the script +- Attach the .zip file to the corresponding Release on Github. +- Update the links at bottom of docs/install.md diff --git a/vendor/drush/drush/misc/windrush_build/assets/composer.bat b/vendor/drush/drush/misc/windrush_build/assets/composer.bat new file mode 100644 index 0000000000..3b62e48cd8 --- /dev/null +++ b/vendor/drush/drush/misc/windrush_build/assets/composer.bat @@ -0,0 +1,6 @@ +@echo off + +SET SCRIPT_HOME=%~dp0 +SET PATH=%SCRIPT_HOME%php;%PATH% + +@php.exe %SCRIPT_HOME%composer.phar %* diff --git a/vendor/drush/drush/misc/windrush_build/assets/drush.bat b/vendor/drush/drush/misc/windrush_build/assets/drush.bat new file mode 100644 index 0000000000..e7ea6cc016 --- /dev/null +++ b/vendor/drush/drush/misc/windrush_build/assets/drush.bat @@ -0,0 +1,7 @@ +@echo off + +SET SCRIPT_HOME=%~dp0 +SET PHP_PATH=%SCRIPT_HOME%php +SET PATH=%SCRIPT_HOME%tools\bin;%PHP_PATH%;%PATH% + +@php.exe "%SCRIPT_HOME%vendor\drush\drush\drush.php" --php="php.exe" %* diff --git a/vendor/drush/drush/misc/windrush_build/assets/notify_env_change.exe b/vendor/drush/drush/misc/windrush_build/assets/notify_env_change.exe new file mode 100644 index 0000000000..82bb64079f Binary files /dev/null and b/vendor/drush/drush/misc/windrush_build/assets/notify_env_change.exe differ diff --git a/vendor/drush/drush/misc/windrush_build/assets/setenv.bat b/vendor/drush/drush/misc/windrush_build/assets/setenv.bat new file mode 100644 index 0000000000..9a3ae1889f --- /dev/null +++ b/vendor/drush/drush/misc/windrush_build/assets/setenv.bat @@ -0,0 +1,2 @@ +@cscript //NoLogo %~dp0setenv.js +@pause \ No newline at end of file diff --git a/vendor/drush/drush/misc/windrush_build/assets/setenv.js b/vendor/drush/drush/misc/windrush_build/assets/setenv.js new file mode 100644 index 0000000000..67074bb970 --- /dev/null +++ b/vendor/drush/drush/misc/windrush_build/assets/setenv.js @@ -0,0 +1,27 @@ +var shell = WScript.CreateObject("WScript.Shell"); +var fs = new ActiveXObject("Scripting.FileSystemObject"); + +var PATH_KEY = "HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment\\Path"; +var path = shell.RegRead(PATH_KEY); +var windrush = fs.GetParentFolderName(WScript.ScriptFullName) + +var inPath = path.toLowerCase().indexOf(windrush.toLowerCase()) != -1; + +WScript.Echo("Adding '" + windrush + "' to your PATH variable..."); +if (inPath) { + WScript.Echo("'" + windrush + "' is already in your PATH variable"); +} else { + try { + shell.RegWrite(PATH_KEY, windrush + ";" + path, "REG_EXPAND_SZ"); + var oExec = shell.Exec(windrush + "\\tools\\bin\\notify_env_change.exe"); + while (oExec.Status == 0) + WScript.Sleep(100); + if (oExec.ExitCode != 0) + WScript.Echo("Failed to notify the system about PATH change. Reboot required"); + WScript.Echo("Done."); + } catch (err) { + WScript.Echo("Could not write PATH variable to the registry.\nYou may have insufficient permissions to that. Try running this script as administrator"); + } + +} + diff --git a/vendor/drush/drush/misc/windrush_build/windrush_build b/vendor/drush/drush/misc/windrush_build/windrush_build new file mode 100755 index 0000000000..c14290a9fa --- /dev/null +++ b/vendor/drush/drush/misc/windrush_build/windrush_build @@ -0,0 +1,103 @@ +#!/bin/bash + +######################################## +# Configurations + +BINTOOL_GIT=https://github.com/acquia/DevDesktopCommon.git +PHP_VER=5.4.41-nts-Win32-VC9-x86 +# NOTE there in also "5.5" string in the download url. Change it if upgrading to 5.6 +MYSQL_VER=5.5.41-win32 +# goes to composer require command +DRUSH_VER=7.0.0 + +TOPDIR=windrush + +######################################## +# Utils + +fail() { + echo "$1">&2 + kill -s TERM $$ +} + +replace_in_file() { + sed "s/$1/$2/" "$3" > aa.tmp + mv aa.tmp "$3" +} + +enable_php_extension() { + for a in $1 ; do + replace_in_file ";extension=php_$a.dll" "extension=php_$a.dll" "$2" + done +} + + +######################################## +# Prepare $TOPDIR dir + +[ -e $TOPDIR ] && ( rm -r -f $TOPDIR || fail "Could not remove $TOPDIR dir" ) +mkdir $TOPDIR + + +######################################## +# MSYS & other binary tools +# + +# msys & stuff from the DD repo +#svn export $BINTOOL_SVN $TOPDIR/tools || fail "Svn failed to export from $BINTOOL_SVN" +[ -e DevDesktopCommon ] && rm -r -f DevDesktopCommon +git clone $BINTOOL_GIT || fail "Git failed to get $BINTOOL_GIT" +mv DevDesktopCommon/bintools-win/msys $TOPDIR/tools +rm -r -f DevDesktopCommon + +# mysql +MYSQL_ZIP=mysql-$MYSQL_VER.zip +MYSQL_URL=http://dev.mysql.com/get/Downloads/MySQL-5.5/$MYSQL_ZIP +if [ ! -e $MYSQL_ZIP ]; then + wget $MYSQL_URL || fail "Could not download MySQL from $MYSQL_URL" +fi + +unzip -o -j $MYSQL_ZIP mysql-$MYSQL_VER/bin/mysql.exe -d $TOPDIR/tools/bin +unzip -o -j $MYSQL_ZIP mysql-$MYSQL_VER/bin/mysqldump.exe -d $TOPDIR/tools/bin + +######################################## +# PHP + +PHP_ZIP=php-$PHP_VER.zip +PHP_URL=http://windows.php.net/downloads/releases/$PHP_ZIP +if [ ! -e $PHP_ZIP ]; then + wget $PHP_URL || fail "Could not download PHP from $PHP_URL" +fi + +unzip $PHP_ZIP -d $TOPDIR/php + +PHP_INI=$TOPDIR/php/php.ini + +cp "$PHP_INI-development" "$PHP_INI" +replace_in_file '; extension_dir = "ext"' 'extension_dir = "ext"' "$PHP_INI" +enable_php_extension "bz2 curl fileinfo gd2 gettext intl mbstring mysql mysqli openssl pdo_mysql soap sockets tidy xmrpc xsl" "$PHP_INI" + +######################################## +# Drush, composer and setenv stuff + +cd $TOPDIR +php -r "readfile('https://getcomposer.org/installer');" | php +./composer.phar require drush/drush:$DRUSH_VER || fail "Composer failed" +cd .. + +cp assets/drush.bat $TOPDIR +cp assets/composer.bat $TOPDIR +cp assets/setenv.bat $TOPDIR +cp assets/setenv.js $TOPDIR +cp assets/notify_env_change.exe $TOPDIR/tools/bin + + +######################################## +# Zip everything up + +[ -e $TOPDIR.zip ] && rm $TOPDIR.zip +zip -r $TOPDIR.zip $TOPDIR +cd .. + + +echo "Done." diff --git a/vendor/drush/drush/mkdocs.yml b/vendor/drush/drush/mkdocs.yml new file mode 100644 index 0000000000..470a41ae8e --- /dev/null +++ b/vendor/drush/drush/mkdocs.yml @@ -0,0 +1,28 @@ +site_name: Drush docs +theme: readthedocs +repo_url: https://github.com/drush-ops/drush +include_search: true +pages: +- Home: index.md +- General: + - Install: install.md + - Install (alternatives): install-alternative.md + - Usage: usage.md + - Example files: examples.md + - Cron: cron.md + - Make: make.md + - Output formats: output-formats.md + - Shell aliases: shellaliases.md + - Shell scripts: shellscripts.md + - Strict options: strict-options.md + - Bastion: bastion.md + - Exporting Drupal configuration: config-exporting.md +- Writing commands: + - Command Authoring: commands.md + - Bootstrap: bootstrap.md + - Context system: context.md +theme: readthedocs +site_author: "" +repo_url: https://github.com/drush-ops/drush +include_search: true +#use_directory_urls: false diff --git a/vendor/drush/drush/shippable.yml b/vendor/drush/drush/shippable.yml new file mode 100644 index 0000000000..2670e5a25a --- /dev/null +++ b/vendor/drush/drush/shippable.yml @@ -0,0 +1,22 @@ +language: php + +php: + - "7.1" + +build: + ci: + # Set up php configuration + - echo 'mbstring.http_input = pass' >> $HOME/.phpenv/versions/$(phpenv version-name)/etc/php.ini + - echo 'mbstring.http_output = pass' >> $HOME/.phpenv/versions/$(phpenv version-name)/etc/php.ini + - echo 'memory_limit = -1' >> $HOME/.phpenv/versions/$(phpenv version-name)/etc/php.ini + - echo 'sendmail_path = /bin/true' >> $HOME/.phpenv/versions/$(phpenv version-name)/etc/php.ini + # Disable xdebug for faster Composer operations + # - rm $HOME/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini + # Install / update our tools + # - composer self-update + # - composer install --prefer-dist --no-interaction + # Run lint check + # - composer lint + # No tests here + - echo 'No shippable tests on this branch' + diff --git a/vendor/drush/drush/src/Internal/Symfony/Yaml/CHANGELOG.md b/vendor/drush/drush/src/Internal/Symfony/Yaml/CHANGELOG.md new file mode 100644 index 0000000000..f55b57047e --- /dev/null +++ b/vendor/drush/drush/src/Internal/Symfony/Yaml/CHANGELOG.md @@ -0,0 +1,28 @@ +CHANGELOG +========= + +2.8.0 +----- + + * Deprecated usage of a colon in an unquoted mapping value + * Deprecated usage of @, \`, | and > at the beginning of an unquoted string + * When surrounding strings with double-quotes, you must now escape `\` characters. Not + escaping those characters (when surrounded by double-quotes) is deprecated. + + Before: + + ```yml + class: "Foo\Var" + ``` + + After: + + ```yml + class: "Foo\\Var" + ``` + +2.1.0 +----- + + * Yaml::parse() does not evaluate loaded files as PHP files by default + anymore (call Yaml::enablePhpParsing() to get back the old behavior) diff --git a/vendor/drush/drush/src/Internal/Symfony/Yaml/Dumper.php b/vendor/drush/drush/src/Internal/Symfony/Yaml/Dumper.php new file mode 100644 index 0000000000..dae47a86bd --- /dev/null +++ b/vendor/drush/drush/src/Internal/Symfony/Yaml/Dumper.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Drush\Internal\Symfony\Yaml; + +/** + * Dumper dumps PHP variables to YAML strings. + * + * @author Fabien Potencier + */ +class Dumper +{ + /** + * The amount of spaces to use for indentation of nested nodes. + * + * @var int + */ + protected $indentation = 4; + + /** + * Sets the indentation. + * + * @param int $num The amount of spaces to use for indentation of nested nodes + */ + public function setIndentation($num) + { + if ($num < 1) { + throw new \InvalidArgumentException('The indentation must be greater than zero.'); + } + + $this->indentation = (int) $num; + } + + /** + * Dumps a PHP value to YAML. + * + * @param mixed $input The PHP value + * @param int $inline The level where you switch to inline YAML + * @param int $indent The level of indentation (used internally) + * @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types (a PHP resource or object), false otherwise + * @param bool $objectSupport True if object support is enabled, false otherwise + * + * @return string The YAML representation of the PHP value + */ + public function dump($input, $inline = 0, $indent = 0, $exceptionOnInvalidType = false, $objectSupport = false) + { + $output = ''; + $prefix = $indent ? str_repeat(' ', $indent) : ''; + + if ($inline <= 0 || !\is_array($input) || empty($input)) { + $output .= $prefix.Inline::dump($input, $exceptionOnInvalidType, $objectSupport); + } else { + $isAHash = Inline::isHash($input); + + foreach ($input as $key => $value) { + $willBeInlined = $inline - 1 <= 0 || !\is_array($value) || empty($value); + + $output .= sprintf('%s%s%s%s', + $prefix, + $isAHash ? Inline::dump($key, $exceptionOnInvalidType, $objectSupport).':' : '-', + $willBeInlined ? ' ' : "\n", + $this->dump($value, $inline - 1, $willBeInlined ? 0 : $indent + $this->indentation, $exceptionOnInvalidType, $objectSupport) + ).($willBeInlined ? "\n" : ''); + } + } + + return $output; + } +} diff --git a/vendor/drush/drush/src/Internal/Symfony/Yaml/Escaper.php b/vendor/drush/drush/src/Internal/Symfony/Yaml/Escaper.php new file mode 100644 index 0000000000..f45c1b1e92 --- /dev/null +++ b/vendor/drush/drush/src/Internal/Symfony/Yaml/Escaper.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Drush\Internal\Symfony\Yaml; + +/** + * Escaper encapsulates escaping rules for single and double-quoted + * YAML strings. + * + * @author Matthew Lewinski + * + * @internal + */ +class Escaper +{ + // Characters that would cause a dumped string to require double quoting. + const REGEX_CHARACTER_TO_ESCAPE = "[\\x00-\\x1f]|\xc2\x85|\xc2\xa0|\xe2\x80\xa8|\xe2\x80\xa9"; + + // Mapping arrays for escaping a double quoted string. The backslash is + // first to ensure proper escaping because str_replace operates iteratively + // on the input arrays. This ordering of the characters avoids the use of strtr, + // which performs more slowly. + private static $escapees = array('\\', '\\\\', '\\"', '"', + "\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", + "\x08", "\x09", "\x0a", "\x0b", "\x0c", "\x0d", "\x0e", "\x0f", + "\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17", + "\x18", "\x19", "\x1a", "\x1b", "\x1c", "\x1d", "\x1e", "\x1f", + "\xc2\x85", "\xc2\xa0", "\xe2\x80\xa8", "\xe2\x80\xa9", + ); + private static $escaped = array('\\\\', '\\"', '\\\\', '\\"', + '\\0', '\\x01', '\\x02', '\\x03', '\\x04', '\\x05', '\\x06', '\\a', + '\\b', '\\t', '\\n', '\\v', '\\f', '\\r', '\\x0e', '\\x0f', + '\\x10', '\\x11', '\\x12', '\\x13', '\\x14', '\\x15', '\\x16', '\\x17', + '\\x18', '\\x19', '\\x1a', '\\e', '\\x1c', '\\x1d', '\\x1e', '\\x1f', + '\\N', '\\_', '\\L', '\\P', + ); + + /** + * Determines if a PHP value would require double quoting in YAML. + * + * @param string $value A PHP value + * + * @return bool True if the value would require double quotes + */ + public static function requiresDoubleQuoting($value) + { + return 0 < preg_match('/'.self::REGEX_CHARACTER_TO_ESCAPE.'/u', $value); + } + + /** + * Escapes and surrounds a PHP value with double quotes. + * + * @param string $value A PHP value + * + * @return string The quoted, escaped string + */ + public static function escapeWithDoubleQuotes($value) + { + return sprintf('"%s"', str_replace(self::$escapees, self::$escaped, $value)); + } + + /** + * Determines if a PHP value would require single quoting in YAML. + * + * @param string $value A PHP value + * + * @return bool True if the value would require single quotes + */ + public static function requiresSingleQuoting($value) + { + // Determines if a PHP value is entirely composed of a value that would + // require single quoting in YAML. + if (\in_array(strtolower($value), array('null', '~', 'true', 'false', 'y', 'n', 'yes', 'no', 'on', 'off'))) { + return true; + } + + // Determines if the PHP value contains any single characters that would + // cause it to require single quoting in YAML. + return 0 < preg_match('/[ \s \' " \: \{ \} \[ \] , & \* \# \?] | \A[ \- ? | < > = ! % @ ` ]/x', $value); + } + + /** + * Escapes and surrounds a PHP value with single quotes. + * + * @param string $value A PHP value + * + * @return string The quoted, escaped string + */ + public static function escapeWithSingleQuotes($value) + { + return sprintf("'%s'", str_replace('\'', '\'\'', $value)); + } +} diff --git a/vendor/drush/drush/src/Internal/Symfony/Yaml/Exception/DumpException.php b/vendor/drush/drush/src/Internal/Symfony/Yaml/Exception/DumpException.php new file mode 100644 index 0000000000..6821a4afae --- /dev/null +++ b/vendor/drush/drush/src/Internal/Symfony/Yaml/Exception/DumpException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Drush\Internal\Symfony\Yaml\Exception; + +/** + * Exception class thrown when an error occurs during dumping. + * + * @author Fabien Potencier + */ +class DumpException extends RuntimeException +{ +} diff --git a/vendor/drush/drush/src/Internal/Symfony/Yaml/Exception/ExceptionInterface.php b/vendor/drush/drush/src/Internal/Symfony/Yaml/Exception/ExceptionInterface.php new file mode 100644 index 0000000000..7af76d338c --- /dev/null +++ b/vendor/drush/drush/src/Internal/Symfony/Yaml/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Drush\Internal\Symfony\Yaml\Exception; + +/** + * Exception interface for all exceptions thrown by the component. + * + * @author Fabien Potencier + */ +interface ExceptionInterface +{ +} diff --git a/vendor/drush/drush/src/Internal/Symfony/Yaml/Exception/ParseException.php b/vendor/drush/drush/src/Internal/Symfony/Yaml/Exception/ParseException.php new file mode 100644 index 0000000000..f51933c03c --- /dev/null +++ b/vendor/drush/drush/src/Internal/Symfony/Yaml/Exception/ParseException.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Drush\Internal\Symfony\Yaml\Exception; + +/** + * Exception class thrown when an error occurs during parsing. + * + * @author Fabien Potencier + */ +class ParseException extends RuntimeException +{ + private $parsedFile; + private $parsedLine; + private $snippet; + private $rawMessage; + + /** + * @param string $message The error message + * @param int $parsedLine The line where the error occurred + * @param string|null $snippet The snippet of code near the problem + * @param string|null $parsedFile The file name where the error occurred + * @param \Exception|null $previous The previous exception + */ + public function __construct($message, $parsedLine = -1, $snippet = null, $parsedFile = null, \Exception $previous = null) + { + $this->parsedFile = $parsedFile; + $this->parsedLine = $parsedLine; + $this->snippet = $snippet; + $this->rawMessage = $message; + + $this->updateRepr(); + + parent::__construct($this->message, 0, $previous); + } + + /** + * Gets the snippet of code near the error. + * + * @return string The snippet of code + */ + public function getSnippet() + { + return $this->snippet; + } + + /** + * Sets the snippet of code near the error. + * + * @param string $snippet The code snippet + */ + public function setSnippet($snippet) + { + $this->snippet = $snippet; + + $this->updateRepr(); + } + + /** + * Gets the filename where the error occurred. + * + * This method returns null if a string is parsed. + * + * @return string The filename + */ + public function getParsedFile() + { + return $this->parsedFile; + } + + /** + * Sets the filename where the error occurred. + * + * @param string $parsedFile The filename + */ + public function setParsedFile($parsedFile) + { + $this->parsedFile = $parsedFile; + + $this->updateRepr(); + } + + /** + * Gets the line where the error occurred. + * + * @return int The file line + */ + public function getParsedLine() + { + return $this->parsedLine; + } + + /** + * Sets the line where the error occurred. + * + * @param int $parsedLine The file line + */ + public function setParsedLine($parsedLine) + { + $this->parsedLine = $parsedLine; + + $this->updateRepr(); + } + + private function updateRepr() + { + $this->message = $this->rawMessage; + + $dot = false; + if ('.' === substr($this->message, -1)) { + $this->message = substr($this->message, 0, -1); + $dot = true; + } + + if (null !== $this->parsedFile) { + if (\PHP_VERSION_ID >= 50400) { + $jsonOptions = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; + } else { + $jsonOptions = 0; + } + $this->message .= sprintf(' in %s', json_encode($this->parsedFile, $jsonOptions)); + } + + if ($this->parsedLine >= 0) { + $this->message .= sprintf(' at line %d', $this->parsedLine); + } + + if ($this->snippet) { + $this->message .= sprintf(' (near "%s")', $this->snippet); + } + + if ($dot) { + $this->message .= '.'; + } + } +} diff --git a/vendor/drush/drush/src/Internal/Symfony/Yaml/Exception/RuntimeException.php b/vendor/drush/drush/src/Internal/Symfony/Yaml/Exception/RuntimeException.php new file mode 100644 index 0000000000..71000518ea --- /dev/null +++ b/vendor/drush/drush/src/Internal/Symfony/Yaml/Exception/RuntimeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Drush\Internal\Symfony\Yaml\Exception; + +/** + * Exception class thrown when an error occurs during parsing. + * + * @author Romain Neutron + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/drush/drush/src/Internal/Symfony/Yaml/Inline.php b/vendor/drush/drush/src/Internal/Symfony/Yaml/Inline.php new file mode 100644 index 0000000000..78921aaa1b --- /dev/null +++ b/vendor/drush/drush/src/Internal/Symfony/Yaml/Inline.php @@ -0,0 +1,609 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Drush\Internal\Symfony\Yaml; + +use Drush\Internal\Symfony\Yaml\Exception\DumpException; +use Drush\Internal\Symfony\Yaml\Exception\ParseException; + +/** + * Inline implements a YAML parser/dumper for the YAML inline syntax. + * + * @author Fabien Potencier + */ +class Inline +{ + const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+)"|\'([^\']*+(?:\'\'[^\']*+)*+)\')'; + + private static $exceptionOnInvalidType = false; + private static $objectSupport = false; + private static $objectForMap = false; + + /** + * Converts a YAML string to a PHP value. + * + * @param string $value A YAML string + * @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types (a PHP resource or object), false otherwise + * @param bool $objectSupport True if object support is enabled, false otherwise + * @param bool $objectForMap True if maps should return a stdClass instead of array() + * @param array $references Mapping of variable names to values + * + * @return mixed A PHP value + * + * @throws ParseException + */ + public static function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false, $references = array()) + { + self::$exceptionOnInvalidType = $exceptionOnInvalidType; + self::$objectSupport = $objectSupport; + self::$objectForMap = $objectForMap; + + $value = trim($value); + + if ('' === $value) { + return ''; + } + + if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) { + $mbEncoding = mb_internal_encoding(); + mb_internal_encoding('ASCII'); + } + + $i = 0; + switch ($value[0]) { + case '[': + $result = self::parseSequence($value, $i, $references); + ++$i; + break; + case '{': + $result = self::parseMapping($value, $i, $references); + ++$i; + break; + default: + $result = self::parseScalar($value, null, array('"', "'"), $i, true, $references); + } + + // some comments are allowed at the end + if (preg_replace('/\s+#.*$/A', '', substr($value, $i))) { + throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i))); + } + + if (isset($mbEncoding)) { + mb_internal_encoding($mbEncoding); + } + + return $result; + } + + /** + * Dumps a given PHP variable to a YAML string. + * + * @param mixed $value The PHP variable to convert + * @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types (a PHP resource or object), false otherwise + * @param bool $objectSupport True if object support is enabled, false otherwise + * + * @return string The YAML string representing the PHP value + * + * @throws DumpException When trying to dump PHP resource + */ + public static function dump($value, $exceptionOnInvalidType = false, $objectSupport = false) + { + switch (true) { + case \is_resource($value): + if ($exceptionOnInvalidType) { + throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value))); + } + + return 'null'; + case \is_object($value): + if ($objectSupport) { + return '!php/object:'.serialize($value); + } + + if ($exceptionOnInvalidType) { + throw new DumpException('Object support when dumping a YAML file has been disabled.'); + } + + return 'null'; + case \is_array($value): + return self::dumpArray($value, $exceptionOnInvalidType, $objectSupport); + case null === $value: + return 'null'; + case true === $value: + return 'true'; + case false === $value: + return 'false'; + case ctype_digit($value): + return \is_string($value) ? "'$value'" : (int) $value; + case is_numeric($value): + $locale = setlocale(LC_NUMERIC, 0); + if (false !== $locale) { + setlocale(LC_NUMERIC, 'C'); + } + if (\is_float($value)) { + $repr = (string) $value; + if (is_infinite($value)) { + $repr = str_ireplace('INF', '.Inf', $repr); + } elseif (floor($value) == $value && $repr == $value) { + // Preserve float data type since storing a whole number will result in integer value. + $repr = '!!float '.$repr; + } + } else { + $repr = \is_string($value) ? "'$value'" : (string) $value; + } + if (false !== $locale) { + setlocale(LC_NUMERIC, $locale); + } + + return $repr; + case '' == $value: + return "''"; + case Escaper::requiresDoubleQuoting($value): + return Escaper::escapeWithDoubleQuotes($value); + case Escaper::requiresSingleQuoting($value): + case Parser::preg_match(self::getHexRegex(), $value): + case Parser::preg_match(self::getTimestampRegex(), $value): + return Escaper::escapeWithSingleQuotes($value); + default: + return $value; + } + } + + /** + * Check if given array is hash or just normal indexed array. + * + * @internal + * + * @param array $value The PHP array to check + * + * @return bool true if value is hash array, false otherwise + */ + public static function isHash(array $value) + { + $expectedKey = 0; + + foreach ($value as $key => $val) { + if ($key !== $expectedKey++) { + return true; + } + } + + return false; + } + + /** + * Dumps a PHP array to a YAML string. + * + * @param array $value The PHP array to dump + * @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types (a PHP resource or object), false otherwise + * @param bool $objectSupport True if object support is enabled, false otherwise + * + * @return string The YAML string representing the PHP array + */ + private static function dumpArray($value, $exceptionOnInvalidType, $objectSupport) + { + // array + if ($value && !self::isHash($value)) { + $output = array(); + foreach ($value as $val) { + $output[] = self::dump($val, $exceptionOnInvalidType, $objectSupport); + } + + return sprintf('[%s]', implode(', ', $output)); + } + + // hash + $output = array(); + foreach ($value as $key => $val) { + $output[] = sprintf('%s: %s', self::dump($key, $exceptionOnInvalidType, $objectSupport), self::dump($val, $exceptionOnInvalidType, $objectSupport)); + } + + return sprintf('{ %s }', implode(', ', $output)); + } + + /** + * Parses a YAML scalar. + * + * @param string $scalar + * @param string[] $delimiters + * @param string[] $stringDelimiters + * @param int &$i + * @param bool $evaluate + * @param array $references + * + * @return string + * + * @throws ParseException When malformed inline YAML string is parsed + * + * @internal + */ + public static function parseScalar($scalar, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true, $references = array()) + { + if (\in_array($scalar[$i], $stringDelimiters)) { + // quoted scalar + $output = self::parseQuotedScalar($scalar, $i); + + if (null !== $delimiters) { + $tmp = ltrim(substr($scalar, $i), ' '); + if ('' === $tmp) { + throw new ParseException(sprintf('Unexpected end of line, expected one of "%s".', implode('', $delimiters))); + } + if (!\in_array($tmp[0], $delimiters)) { + throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i))); + } + } + } else { + // "normal" string + if (!$delimiters) { + $output = substr($scalar, $i); + $i += \strlen($output); + + // remove comments + if (Parser::preg_match('/[ \t]+#/', $output, $match, PREG_OFFSET_CAPTURE)) { + $output = substr($output, 0, $match[0][1]); + } + } elseif (Parser::preg_match('/^(.+?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) { + $output = $match[1]; + $i += \strlen($output); + } else { + throw new ParseException(sprintf('Malformed inline YAML string: %s.', $scalar)); + } + + // a non-quoted string cannot start with @ or ` (reserved) nor with a scalar indicator (| or >) + if ($output && ('@' === $output[0] || '`' === $output[0] || '|' === $output[0] || '>' === $output[0])) { + @trigger_error(sprintf('Not quoting the scalar "%s" starting with "%s" is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.', $output, $output[0]), E_USER_DEPRECATED); + + // to be thrown in 3.0 + // throw new ParseException(sprintf('The reserved indicator "%s" cannot start a plain scalar; you need to quote the scalar.', $output[0])); + } + + if ($evaluate) { + $output = self::evaluateScalar($output, $references); + } + } + + return $output; + } + + /** + * Parses a YAML quoted scalar. + * + * @param string $scalar + * @param int &$i + * + * @return string + * + * @throws ParseException When malformed inline YAML string is parsed + */ + private static function parseQuotedScalar($scalar, &$i) + { + if (!Parser::preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) { + throw new ParseException(sprintf('Malformed inline YAML string: %s.', substr($scalar, $i))); + } + + $output = substr($match[0], 1, \strlen($match[0]) - 2); + + $unescaper = new Unescaper(); + if ('"' == $scalar[$i]) { + $output = $unescaper->unescapeDoubleQuotedString($output); + } else { + $output = $unescaper->unescapeSingleQuotedString($output); + } + + $i += \strlen($match[0]); + + return $output; + } + + /** + * Parses a YAML sequence. + * + * @param string $sequence + * @param int &$i + * @param array $references + * + * @return array + * + * @throws ParseException When malformed inline YAML string is parsed + */ + private static function parseSequence($sequence, &$i = 0, $references = array()) + { + $output = array(); + $len = \strlen($sequence); + ++$i; + + // [foo, bar, ...] + while ($i < $len) { + switch ($sequence[$i]) { + case '[': + // nested sequence + $output[] = self::parseSequence($sequence, $i, $references); + break; + case '{': + // nested mapping + $output[] = self::parseMapping($sequence, $i, $references); + break; + case ']': + return $output; + case ',': + case ' ': + break; + default: + $isQuoted = \in_array($sequence[$i], array('"', "'")); + $value = self::parseScalar($sequence, array(',', ']'), array('"', "'"), $i, true, $references); + + // the value can be an array if a reference has been resolved to an array var + if (!\is_array($value) && !$isQuoted && false !== strpos($value, ': ')) { + // embedded mapping? + try { + $pos = 0; + $value = self::parseMapping('{'.$value.'}', $pos, $references); + } catch (\InvalidArgumentException $e) { + // no, it's not + } + } + + $output[] = $value; + + --$i; + } + + ++$i; + } + + throw new ParseException(sprintf('Malformed inline YAML string: %s.', $sequence)); + } + + /** + * Parses a YAML mapping. + * + * @param string $mapping + * @param int &$i + * @param array $references + * + * @return array|\stdClass + * + * @throws ParseException When malformed inline YAML string is parsed + */ + private static function parseMapping($mapping, &$i = 0, $references = array()) + { + $output = array(); + $len = \strlen($mapping); + ++$i; + $allowOverwrite = false; + + // {foo: bar, bar:foo, ...} + while ($i < $len) { + switch ($mapping[$i]) { + case ' ': + case ',': + ++$i; + continue 2; + case '}': + if (self::$objectForMap) { + return (object) $output; + } + + return $output; + } + + // key + $key = self::parseScalar($mapping, array(':', ' '), array('"', "'"), $i, false); + + if ('<<' === $key) { + $allowOverwrite = true; + } + + // value + $done = false; + + while ($i < $len) { + switch ($mapping[$i]) { + case '[': + // nested sequence + $value = self::parseSequence($mapping, $i, $references); + // Spec: Keys MUST be unique; first one wins. + // Parser cannot abort this mapping earlier, since lines + // are processed sequentially. + // But overwriting is allowed when a merge node is used in current block. + if ('<<' === $key) { + foreach ($value as $parsedValue) { + $output += $parsedValue; + } + } elseif ($allowOverwrite || !isset($output[$key])) { + $output[$key] = $value; + } + $done = true; + break; + case '{': + // nested mapping + $value = self::parseMapping($mapping, $i, $references); + // Spec: Keys MUST be unique; first one wins. + // Parser cannot abort this mapping earlier, since lines + // are processed sequentially. + // But overwriting is allowed when a merge node is used in current block. + if ('<<' === $key) { + $output += $value; + } elseif ($allowOverwrite || !isset($output[$key])) { + $output[$key] = $value; + } + $done = true; + break; + case ':': + case ' ': + break; + default: + $value = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i, true, $references); + // Spec: Keys MUST be unique; first one wins. + // Parser cannot abort this mapping earlier, since lines + // are processed sequentially. + // But overwriting is allowed when a merge node is used in current block. + if ('<<' === $key) { + $output += $value; + } elseif ($allowOverwrite || !isset($output[$key])) { + $output[$key] = $value; + } + $done = true; + --$i; + } + + ++$i; + + if ($done) { + continue 2; + } + } + } + + throw new ParseException(sprintf('Malformed inline YAML string: %s.', $mapping)); + } + + /** + * Evaluates scalars and replaces magic values. + * + * @param string $scalar + * @param array $references + * + * @return mixed The evaluated YAML string + * + * @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved + */ + private static function evaluateScalar($scalar, $references = array()) + { + $scalar = trim($scalar); + $scalarLower = strtolower($scalar); + + if (0 === strpos($scalar, '*')) { + if (false !== $pos = strpos($scalar, '#')) { + $value = substr($scalar, 1, $pos - 2); + } else { + $value = substr($scalar, 1); + } + + // an unquoted * + if (false === $value || '' === $value) { + throw new ParseException('A reference must contain at least one character.'); + } + + if (!array_key_exists($value, $references)) { + throw new ParseException(sprintf('Reference "%s" does not exist.', $value)); + } + + return $references[$value]; + } + + switch (true) { + case 'null' === $scalarLower: + case '' === $scalar: + case '~' === $scalar: + return; + case 'true' === $scalarLower: + return true; + case 'false' === $scalarLower: + return false; + // Optimise for returning strings. + case '+' === $scalar[0] || '-' === $scalar[0] || '.' === $scalar[0] || '!' === $scalar[0] || is_numeric($scalar[0]): + switch (true) { + case 0 === strpos($scalar, '!str'): + return (string) substr($scalar, 5); + case 0 === strpos($scalar, '! '): + return (int) self::parseScalar(substr($scalar, 2)); + case 0 === strpos($scalar, '!php/object:'): + if (self::$objectSupport) { + return unserialize(substr($scalar, 12)); + } + + if (self::$exceptionOnInvalidType) { + throw new ParseException('Object support when parsing a YAML file has been disabled.'); + } + + return; + case 0 === strpos($scalar, '!!php/object:'): + if (self::$objectSupport) { + return unserialize(substr($scalar, 13)); + } + + if (self::$exceptionOnInvalidType) { + throw new ParseException('Object support when parsing a YAML file has been disabled.'); + } + + return; + case 0 === strpos($scalar, '!!float '): + return (float) substr($scalar, 8); + case ctype_digit($scalar): + $raw = $scalar; + $cast = (int) $scalar; + + return '0' == $scalar[0] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw); + case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)): + $raw = $scalar; + $cast = (int) $scalar; + + return '0' == $scalar[1] ? octdec($scalar) : (((string) $raw === (string) $cast) ? $cast : $raw); + case is_numeric($scalar): + case Parser::preg_match(self::getHexRegex(), $scalar): + return '0x' === $scalar[0].$scalar[1] ? hexdec($scalar) : (float) $scalar; + case '.inf' === $scalarLower: + case '.nan' === $scalarLower: + return -log(0); + case '-.inf' === $scalarLower: + return log(0); + case Parser::preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar): + return (float) str_replace(',', '', $scalar); + case Parser::preg_match(self::getTimestampRegex(), $scalar): + $timeZone = date_default_timezone_get(); + date_default_timezone_set('UTC'); + $time = strtotime($scalar); + date_default_timezone_set($timeZone); + + return $time; + } + // no break + default: + return (string) $scalar; + } + } + + /** + * Gets a regex that matches a YAML date. + * + * @return string The regular expression + * + * @see http://www.yaml.org/spec/1.2/spec.html#id2761573 + */ + private static function getTimestampRegex() + { + return <<[0-9][0-9][0-9][0-9]) + -(?P[0-9][0-9]?) + -(?P[0-9][0-9]?) + (?:(?:[Tt]|[ \t]+) + (?P[0-9][0-9]?) + :(?P[0-9][0-9]) + :(?P[0-9][0-9]) + (?:\.(?P[0-9]*))? + (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?) + (?::(?P[0-9][0-9]))?))?)? + $~x +EOF; + } + + /** + * Gets a regex that matches a YAML number in hexadecimal notation. + * + * @return string + */ + private static function getHexRegex() + { + return '~^0x[0-9a-f]++$~i'; + } +} diff --git a/vendor/drush/drush/src/Internal/Symfony/Yaml/LICENSE b/vendor/drush/drush/src/Internal/Symfony/Yaml/LICENSE new file mode 100644 index 0000000000..21d7fb9e2f --- /dev/null +++ b/vendor/drush/drush/src/Internal/Symfony/Yaml/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2018 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/drush/drush/src/Internal/Symfony/Yaml/Parser.php b/vendor/drush/drush/src/Internal/Symfony/Yaml/Parser.php new file mode 100644 index 0000000000..bcd905c571 --- /dev/null +++ b/vendor/drush/drush/src/Internal/Symfony/Yaml/Parser.php @@ -0,0 +1,852 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Drush\Internal\Symfony\Yaml; + +use Drush\Internal\Symfony\Yaml\Exception\ParseException; + +/** + * Parser parses YAML strings to convert them to PHP arrays. + * + * @author Fabien Potencier + */ +class Parser +{ + const BLOCK_SCALAR_HEADER_PATTERN = '(?P\||>)(?P\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P +#.*)?'; + // BC - wrongly named + const FOLDED_SCALAR_PATTERN = self::BLOCK_SCALAR_HEADER_PATTERN; + + private $offset = 0; + private $totalNumberOfLines; + private $lines = array(); + private $currentLineNb = -1; + private $currentLine = ''; + private $refs = array(); + private $skippedLineNumbers = array(); + private $locallySkippedLineNumbers = array(); + + /** + * @param int $offset The offset of YAML document (used for line numbers in error messages) + * @param int|null $totalNumberOfLines The overall number of lines being parsed + * @param int[] $skippedLineNumbers Number of comment lines that have been skipped by the parser + */ + public function __construct($offset = 0, $totalNumberOfLines = null, array $skippedLineNumbers = array()) + { + $this->offset = $offset; + $this->totalNumberOfLines = $totalNumberOfLines; + $this->skippedLineNumbers = $skippedLineNumbers; + } + + /** + * Parses a YAML string to a PHP value. + * + * @param string $value A YAML string + * @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types (a PHP resource or object), false otherwise + * @param bool $objectSupport True if object support is enabled, false otherwise + * @param bool $objectForMap True if maps should return a stdClass instead of array() + * + * @return mixed A PHP value + * + * @throws ParseException If the YAML is not valid + */ + public function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false) + { + if (false === preg_match('//u', $value)) { + throw new ParseException('The YAML value does not appear to be valid UTF-8.'); + } + + $this->refs = array(); + + $mbEncoding = null; + $e = null; + $data = null; + + if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) { + $mbEncoding = mb_internal_encoding(); + mb_internal_encoding('UTF-8'); + } + + try { + $data = $this->doParse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap); + } catch (\Exception $e) { + } catch (\Throwable $e) { + } + + if (null !== $mbEncoding) { + mb_internal_encoding($mbEncoding); + } + + $this->lines = array(); + $this->currentLine = ''; + $this->refs = array(); + $this->skippedLineNumbers = array(); + $this->locallySkippedLineNumbers = array(); + + if (null !== $e) { + throw $e; + } + + return $data; + } + + private function doParse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false) + { + $this->currentLineNb = -1; + $this->currentLine = ''; + $value = $this->cleanup($value); + $this->lines = explode("\n", $value); + $this->locallySkippedLineNumbers = array(); + + if (null === $this->totalNumberOfLines) { + $this->totalNumberOfLines = \count($this->lines); + } + + $data = array(); + $context = null; + $allowOverwrite = false; + + while ($this->moveToNextLine()) { + if ($this->isCurrentLineEmpty()) { + continue; + } + + // tab? + if ("\t" === $this->currentLine[0]) { + throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + $isRef = $mergeNode = false; + if (self::preg_match('#^\-((?P\s+)(?P.+))?$#u', rtrim($this->currentLine), $values)) { + if ($context && 'mapping' == $context) { + throw new ParseException('You cannot define a sequence item when in a mapping', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + $context = 'sequence'; + + if (isset($values['value']) && self::preg_match('#^&(?P[^ ]+) *(?P.*)#u', $values['value'], $matches)) { + $isRef = $matches['ref']; + $values['value'] = $matches['value']; + } + + // array + if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) { + $data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $exceptionOnInvalidType, $objectSupport, $objectForMap); + } else { + if (isset($values['leadspaces']) + && self::preg_match('#^(?P'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P.+))?$#u', rtrim($values['value']), $matches) + ) { + // this is a compact notation element, add to next block and parse + $block = $values['value']; + if ($this->isNextLineIndented()) { + $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + \strlen($values['leadspaces']) + 1); + } + + $data[] = $this->parseBlock($this->getRealCurrentLineNb(), $block, $exceptionOnInvalidType, $objectSupport, $objectForMap); + } else { + $data[] = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap, $context); + } + } + if ($isRef) { + $this->refs[$isRef] = end($data); + } + } elseif ( + self::preg_match('#^(?P'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\[\{].*?) *\:(\s+(?P.+))?$#u', rtrim($this->currentLine), $values) + && (false === strpos($values['key'], ' #') || \in_array($values['key'][0], array('"', "'"))) + ) { + if ($context && 'sequence' == $context) { + throw new ParseException('You cannot define a mapping item when in a sequence', $this->currentLineNb + 1, $this->currentLine); + } + $context = 'mapping'; + + // force correct settings + Inline::parse(null, $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs); + try { + $key = Inline::parseScalar($values['key']); + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + + // Convert float keys to strings, to avoid being converted to integers by PHP + if (\is_float($key)) { + $key = (string) $key; + } + + if ('<<' === $key && (!isset($values['value']) || !self::preg_match('#^&(?P[^ ]+)#u', $values['value'], $refMatches))) { + $mergeNode = true; + $allowOverwrite = true; + if (isset($values['value']) && 0 === strpos($values['value'], '*')) { + $refName = substr($values['value'], 1); + if (!array_key_exists($refName, $this->refs)) { + throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + $refValue = $this->refs[$refName]; + + if (!\is_array($refValue)) { + throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + $data += $refValue; // array union + } else { + if (isset($values['value']) && '' !== $values['value']) { + $value = $values['value']; + } else { + $value = $this->getNextEmbedBlock(); + } + $parsed = $this->parseBlock($this->getRealCurrentLineNb() + 1, $value, $exceptionOnInvalidType, $objectSupport, $objectForMap); + + if (!\is_array($parsed)) { + throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + if (isset($parsed[0])) { + // If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes + // and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier + // in the sequence override keys specified in later mapping nodes. + foreach ($parsed as $parsedItem) { + if (!\is_array($parsedItem)) { + throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem); + } + + $data += $parsedItem; // array union + } + } else { + // If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the + // current mapping, unless the key already exists in it. + $data += $parsed; // array union + } + } + } elseif ('<<' !== $key && isset($values['value']) && self::preg_match('#^&(?P[^ ]+) *(?P.*)#u', $values['value'], $matches)) { + $isRef = $matches['ref']; + $values['value'] = $matches['value']; + } + + if ($mergeNode) { + // Merge keys + } elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#') || '<<' === $key) { + // hash + // if next line is less indented or equal, then it means that the current value is null + if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) { + // Spec: Keys MUST be unique; first one wins. + // But overwriting is allowed when a merge node is used in current block. + if ($allowOverwrite || !isset($data[$key])) { + $data[$key] = null; + } + } else { + $value = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport, $objectForMap); + + if ('<<' === $key) { + $this->refs[$refMatches['ref']] = $value; + $data += $value; + } elseif ($allowOverwrite || !isset($data[$key])) { + // Spec: Keys MUST be unique; first one wins. + // But overwriting is allowed when a merge node is used in current block. + $data[$key] = $value; + } + } + } else { + $value = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap, $context); + // Spec: Keys MUST be unique; first one wins. + // But overwriting is allowed when a merge node is used in current block. + if ($allowOverwrite || !isset($data[$key])) { + $data[$key] = $value; + } + } + if ($isRef) { + $this->refs[$isRef] = $data[$key]; + } + } else { + // multiple documents are not supported + if ('---' === $this->currentLine) { + throw new ParseException('Multiple documents are not supported.', $this->currentLineNb + 1, $this->currentLine); + } + + // 1-liner optionally followed by newline(s) + if (\is_string($value) && $this->lines[0] === trim($value)) { + try { + $value = Inline::parse($this->lines[0], $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs); + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + + return $value; + } + + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + } + + if ($objectForMap && !\is_object($data) && 'mapping' === $context) { + $object = new \stdClass(); + + foreach ($data as $key => $value) { + $object->$key = $value; + } + + $data = $object; + } + + return empty($data) ? null : $data; + } + + private function parseBlock($offset, $yaml, $exceptionOnInvalidType, $objectSupport, $objectForMap) + { + $skippedLineNumbers = $this->skippedLineNumbers; + + foreach ($this->locallySkippedLineNumbers as $lineNumber) { + if ($lineNumber < $offset) { + continue; + } + + $skippedLineNumbers[] = $lineNumber; + } + + $parser = new self($offset, $this->totalNumberOfLines, $skippedLineNumbers); + $parser->refs = &$this->refs; + + return $parser->doParse($yaml, $exceptionOnInvalidType, $objectSupport, $objectForMap); + } + + /** + * Returns the current line number (takes the offset into account). + * + * @return int The current line number + */ + private function getRealCurrentLineNb() + { + $realCurrentLineNumber = $this->currentLineNb + $this->offset; + + foreach ($this->skippedLineNumbers as $skippedLineNumber) { + if ($skippedLineNumber > $realCurrentLineNumber) { + break; + } + + ++$realCurrentLineNumber; + } + + return $realCurrentLineNumber; + } + + /** + * Returns the current line indentation. + * + * @return int The current line indentation + */ + private function getCurrentLineIndentation() + { + return \strlen($this->currentLine) - \strlen(ltrim($this->currentLine, ' ')); + } + + /** + * Returns the next embed block of YAML. + * + * @param int $indentation The indent level at which the block is to be read, or null for default + * @param bool $inSequence True if the enclosing data structure is a sequence + * + * @return string A YAML string + * + * @throws ParseException When indentation problem are detected + */ + private function getNextEmbedBlock($indentation = null, $inSequence = false) + { + $oldLineIndentation = $this->getCurrentLineIndentation(); + $blockScalarIndentations = array(); + + if ($this->isBlockScalarHeader()) { + $blockScalarIndentations[] = $this->getCurrentLineIndentation(); + } + + if (!$this->moveToNextLine()) { + return; + } + + if (null === $indentation) { + $newIndent = $this->getCurrentLineIndentation(); + + $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem(); + + if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) { + throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + } else { + $newIndent = $indentation; + } + + $data = array(); + if ($this->getCurrentLineIndentation() >= $newIndent) { + $data[] = substr($this->currentLine, $newIndent); + } else { + $this->moveToPreviousLine(); + + return; + } + + if ($inSequence && $oldLineIndentation === $newIndent && isset($data[0][0]) && '-' === $data[0][0]) { + // the previous line contained a dash but no item content, this line is a sequence item with the same indentation + // and therefore no nested list or mapping + $this->moveToPreviousLine(); + + return; + } + + $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem(); + + if (empty($blockScalarIndentations) && $this->isBlockScalarHeader()) { + $blockScalarIndentations[] = $this->getCurrentLineIndentation(); + } + + $previousLineIndentation = $this->getCurrentLineIndentation(); + + while ($this->moveToNextLine()) { + $indent = $this->getCurrentLineIndentation(); + + // terminate all block scalars that are more indented than the current line + if (!empty($blockScalarIndentations) && $indent < $previousLineIndentation && '' !== trim($this->currentLine)) { + foreach ($blockScalarIndentations as $key => $blockScalarIndentation) { + if ($blockScalarIndentation >= $this->getCurrentLineIndentation()) { + unset($blockScalarIndentations[$key]); + } + } + } + + if (empty($blockScalarIndentations) && !$this->isCurrentLineComment() && $this->isBlockScalarHeader()) { + $blockScalarIndentations[] = $this->getCurrentLineIndentation(); + } + + $previousLineIndentation = $indent; + + if ($isItUnindentedCollection && !$this->isCurrentLineEmpty() && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) { + $this->moveToPreviousLine(); + break; + } + + if ($this->isCurrentLineBlank()) { + $data[] = substr($this->currentLine, $newIndent); + continue; + } + + // we ignore "comment" lines only when we are not inside a scalar block + if (empty($blockScalarIndentations) && $this->isCurrentLineComment()) { + // remember ignored comment lines (they are used later in nested + // parser calls to determine real line numbers) + // + // CAUTION: beware to not populate the global property here as it + // will otherwise influence the getRealCurrentLineNb() call here + // for consecutive comment lines and subsequent embedded blocks + $this->locallySkippedLineNumbers[] = $this->getRealCurrentLineNb(); + + continue; + } + + if ($indent >= $newIndent) { + $data[] = substr($this->currentLine, $newIndent); + } elseif (0 == $indent) { + $this->moveToPreviousLine(); + + break; + } else { + throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + } + + return implode("\n", $data); + } + + /** + * Moves the parser to the next line. + * + * @return bool + */ + private function moveToNextLine() + { + if ($this->currentLineNb >= \count($this->lines) - 1) { + return false; + } + + $this->currentLine = $this->lines[++$this->currentLineNb]; + + return true; + } + + /** + * Moves the parser to the previous line. + * + * @return bool + */ + private function moveToPreviousLine() + { + if ($this->currentLineNb < 1) { + return false; + } + + $this->currentLine = $this->lines[--$this->currentLineNb]; + + return true; + } + + /** + * Parses a YAML value. + * + * @param string $value A YAML value + * @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types false otherwise + * @param bool $objectSupport True if object support is enabled, false otherwise + * @param bool $objectForMap True if maps should return a stdClass instead of array() + * @param string $context The parser context (either sequence or mapping) + * + * @return mixed A PHP value + * + * @throws ParseException When reference does not exist + */ + private function parseValue($value, $exceptionOnInvalidType, $objectSupport, $objectForMap, $context) + { + if (0 === strpos($value, '*')) { + if (false !== $pos = strpos($value, '#')) { + $value = substr($value, 1, $pos - 2); + } else { + $value = substr($value, 1); + } + + if (!array_key_exists($value, $this->refs)) { + throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLineNb + 1, $this->currentLine); + } + + return $this->refs[$value]; + } + + if (self::preg_match('/^'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, $matches)) { + $modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : ''; + + return $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), (int) abs($modifiers)); + } + + try { + $parsedValue = Inline::parse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs); + + if ('mapping' === $context && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) { + @trigger_error(sprintf('Using a colon in the unquoted mapping value "%s" in line %d is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.', $value, $this->getRealCurrentLineNb() + 1), E_USER_DEPRECATED); + + // to be thrown in 3.0 + // throw new ParseException('A colon cannot be used in an unquoted mapping value.'); + } + + return $parsedValue; + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + } + + /** + * Parses a block scalar. + * + * @param string $style The style indicator that was used to begin this block scalar (| or >) + * @param string $chomping The chomping indicator that was used to begin this block scalar (+ or -) + * @param int $indentation The indentation indicator that was used to begin this block scalar + * + * @return string The text value + */ + private function parseBlockScalar($style, $chomping = '', $indentation = 0) + { + $notEOF = $this->moveToNextLine(); + if (!$notEOF) { + return ''; + } + + $isCurrentLineBlank = $this->isCurrentLineBlank(); + $blockLines = array(); + + // leading blank lines are consumed before determining indentation + while ($notEOF && $isCurrentLineBlank) { + // newline only if not EOF + if ($notEOF = $this->moveToNextLine()) { + $blockLines[] = ''; + $isCurrentLineBlank = $this->isCurrentLineBlank(); + } + } + + // determine indentation if not specified + if (0 === $indentation) { + if (self::preg_match('/^ +/', $this->currentLine, $matches)) { + $indentation = \strlen($matches[0]); + } + } + + if ($indentation > 0) { + $pattern = sprintf('/^ {%d}(.*)$/', $indentation); + + while ( + $notEOF && ( + $isCurrentLineBlank || + self::preg_match($pattern, $this->currentLine, $matches) + ) + ) { + if ($isCurrentLineBlank && \strlen($this->currentLine) > $indentation) { + $blockLines[] = substr($this->currentLine, $indentation); + } elseif ($isCurrentLineBlank) { + $blockLines[] = ''; + } else { + $blockLines[] = $matches[1]; + } + + // newline only if not EOF + if ($notEOF = $this->moveToNextLine()) { + $isCurrentLineBlank = $this->isCurrentLineBlank(); + } + } + } elseif ($notEOF) { + $blockLines[] = ''; + } + + if ($notEOF) { + $blockLines[] = ''; + $this->moveToPreviousLine(); + } elseif (!$notEOF && !$this->isCurrentLineLastLineInDocument()) { + $blockLines[] = ''; + } + + // folded style + if ('>' === $style) { + $text = ''; + $previousLineIndented = false; + $previousLineBlank = false; + + for ($i = 0, $blockLinesCount = \count($blockLines); $i < $blockLinesCount; ++$i) { + if ('' === $blockLines[$i]) { + $text .= "\n"; + $previousLineIndented = false; + $previousLineBlank = true; + } elseif (' ' === $blockLines[$i][0]) { + $text .= "\n".$blockLines[$i]; + $previousLineIndented = true; + $previousLineBlank = false; + } elseif ($previousLineIndented) { + $text .= "\n".$blockLines[$i]; + $previousLineIndented = false; + $previousLineBlank = false; + } elseif ($previousLineBlank || 0 === $i) { + $text .= $blockLines[$i]; + $previousLineIndented = false; + $previousLineBlank = false; + } else { + $text .= ' '.$blockLines[$i]; + $previousLineIndented = false; + $previousLineBlank = false; + } + } + } else { + $text = implode("\n", $blockLines); + } + + // deal with trailing newlines + if ('' === $chomping) { + $text = preg_replace('/\n+$/', "\n", $text); + } elseif ('-' === $chomping) { + $text = preg_replace('/\n+$/', '', $text); + } + + return $text; + } + + /** + * Returns true if the next line is indented. + * + * @return bool Returns true if the next line is indented, false otherwise + */ + private function isNextLineIndented() + { + $currentIndentation = $this->getCurrentLineIndentation(); + $EOF = !$this->moveToNextLine(); + + while (!$EOF && $this->isCurrentLineEmpty()) { + $EOF = !$this->moveToNextLine(); + } + + if ($EOF) { + return false; + } + + $ret = $this->getCurrentLineIndentation() > $currentIndentation; + + $this->moveToPreviousLine(); + + return $ret; + } + + /** + * Returns true if the current line is blank or if it is a comment line. + * + * @return bool Returns true if the current line is empty or if it is a comment line, false otherwise + */ + private function isCurrentLineEmpty() + { + return $this->isCurrentLineBlank() || $this->isCurrentLineComment(); + } + + /** + * Returns true if the current line is blank. + * + * @return bool Returns true if the current line is blank, false otherwise + */ + private function isCurrentLineBlank() + { + return '' == trim($this->currentLine, ' '); + } + + /** + * Returns true if the current line is a comment line. + * + * @return bool Returns true if the current line is a comment line, false otherwise + */ + private function isCurrentLineComment() + { + //checking explicitly the first char of the trim is faster than loops or strpos + $ltrimmedLine = ltrim($this->currentLine, ' '); + + return '' !== $ltrimmedLine && '#' === $ltrimmedLine[0]; + } + + private function isCurrentLineLastLineInDocument() + { + return ($this->offset + $this->currentLineNb) >= ($this->totalNumberOfLines - 1); + } + + /** + * Cleanups a YAML string to be parsed. + * + * @param string $value The input YAML string + * + * @return string A cleaned up YAML string + */ + private function cleanup($value) + { + $value = str_replace(array("\r\n", "\r"), "\n", $value); + + // strip YAML header + $count = 0; + $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#u', '', $value, -1, $count); + $this->offset += $count; + + // remove leading comments + $trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count); + if (1 == $count) { + // items have been removed, update the offset + $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); + $value = $trimmedValue; + } + + // remove start of the document marker (---) + $trimmedValue = preg_replace('#^\-\-\-.*?\n#s', '', $value, -1, $count); + if (1 == $count) { + // items have been removed, update the offset + $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); + $value = $trimmedValue; + + // remove end of the document marker (...) + $value = preg_replace('#\.\.\.\s*$#', '', $value); + } + + return $value; + } + + /** + * Returns true if the next line starts unindented collection. + * + * @return bool Returns true if the next line starts unindented collection, false otherwise + */ + private function isNextLineUnIndentedCollection() + { + $currentIndentation = $this->getCurrentLineIndentation(); + $notEOF = $this->moveToNextLine(); + + while ($notEOF && $this->isCurrentLineEmpty()) { + $notEOF = $this->moveToNextLine(); + } + + if (false === $notEOF) { + return false; + } + + $ret = $this->getCurrentLineIndentation() === $currentIndentation && $this->isStringUnIndentedCollectionItem(); + + $this->moveToPreviousLine(); + + return $ret; + } + + /** + * Returns true if the string is un-indented collection item. + * + * @return bool Returns true if the string is un-indented collection item, false otherwise + */ + private function isStringUnIndentedCollectionItem() + { + return '-' === rtrim($this->currentLine) || 0 === strpos($this->currentLine, '- '); + } + + /** + * Tests whether or not the current line is the header of a block scalar. + * + * @return bool + */ + private function isBlockScalarHeader() + { + return (bool) self::preg_match('~'.self::BLOCK_SCALAR_HEADER_PATTERN.'$~', $this->currentLine); + } + + /** + * A local wrapper for `preg_match` which will throw a ParseException if there + * is an internal error in the PCRE engine. + * + * This avoids us needing to check for "false" every time PCRE is used + * in the YAML engine + * + * @throws ParseException on a PCRE internal error + * + * @see preg_last_error() + * + * @internal + */ + public static function preg_match($pattern, $subject, &$matches = null, $flags = 0, $offset = 0) + { + if (false === $ret = preg_match($pattern, $subject, $matches, $flags, $offset)) { + switch (preg_last_error()) { + case PREG_INTERNAL_ERROR: + $error = 'Internal PCRE error.'; + break; + case PREG_BACKTRACK_LIMIT_ERROR: + $error = 'pcre.backtrack_limit reached.'; + break; + case PREG_RECURSION_LIMIT_ERROR: + $error = 'pcre.recursion_limit reached.'; + break; + case PREG_BAD_UTF8_ERROR: + $error = 'Malformed UTF-8 data.'; + break; + case PREG_BAD_UTF8_OFFSET_ERROR: + $error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.'; + break; + default: + $error = 'Error.'; + } + + throw new ParseException($error); + } + + return $ret; + } +} diff --git a/vendor/drush/drush/src/Internal/Symfony/Yaml/Unescaper.php b/vendor/drush/drush/src/Internal/Symfony/Yaml/Unescaper.php new file mode 100644 index 0000000000..cf33ad3a83 --- /dev/null +++ b/vendor/drush/drush/src/Internal/Symfony/Yaml/Unescaper.php @@ -0,0 +1,156 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Drush\Internal\Symfony\Yaml; + +/** + * Unescaper encapsulates unescaping rules for single and double-quoted + * YAML strings. + * + * @author Matthew Lewinski + * + * @internal + */ +class Unescaper +{ + /** + * Parser and Inline assume UTF-8 encoding, so escaped Unicode characters + * must be converted to that encoding. + * + * @deprecated since version 2.5, to be removed in 3.0 + * + * @internal + */ + const ENCODING = 'UTF-8'; + + /** + * Regex fragment that matches an escaped character in a double quoted string. + */ + const REGEX_ESCAPED_CHARACTER = '\\\\(x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8}|.)'; + + /** + * Unescapes a single quoted string. + * + * @param string $value A single quoted string + * + * @return string The unescaped string + */ + public function unescapeSingleQuotedString($value) + { + return str_replace('\'\'', '\'', $value); + } + + /** + * Unescapes a double quoted string. + * + * @param string $value A double quoted string + * + * @return string The unescaped string + */ + public function unescapeDoubleQuotedString($value) + { + $self = $this; + $callback = function ($match) use ($self) { + return $self->unescapeCharacter($match[0]); + }; + + // evaluate the string + return preg_replace_callback('/'.self::REGEX_ESCAPED_CHARACTER.'/u', $callback, $value); + } + + /** + * Unescapes a character that was found in a double-quoted string. + * + * @param string $value An escaped character + * + * @return string The unescaped character + * + * @internal This method is public to be usable as callback. It should not + * be used in user code. Should be changed in 3.0. + */ + public function unescapeCharacter($value) + { + switch ($value[1]) { + case '0': + return "\x0"; + case 'a': + return "\x7"; + case 'b': + return "\x8"; + case 't': + return "\t"; + case "\t": + return "\t"; + case 'n': + return "\n"; + case 'v': + return "\xB"; + case 'f': + return "\xC"; + case 'r': + return "\r"; + case 'e': + return "\x1B"; + case ' ': + return ' '; + case '"': + return '"'; + case '/': + return '/'; + case '\\': + return '\\'; + case 'N': + // U+0085 NEXT LINE + return "\xC2\x85"; + case '_': + // U+00A0 NO-BREAK SPACE + return "\xC2\xA0"; + case 'L': + // U+2028 LINE SEPARATOR + return "\xE2\x80\xA8"; + case 'P': + // U+2029 PARAGRAPH SEPARATOR + return "\xE2\x80\xA9"; + case 'x': + return self::utf8chr(hexdec(substr($value, 2, 2))); + case 'u': + return self::utf8chr(hexdec(substr($value, 2, 4))); + case 'U': + return self::utf8chr(hexdec(substr($value, 2, 8))); + default: + @trigger_error('Not escaping a backslash in a double-quoted string is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.', E_USER_DEPRECATED); + + return $value; + } + } + + /** + * Get the UTF-8 character for the given code point. + * + * @param int $c The unicode code point + * + * @return string The corresponding UTF-8 character + */ + private static function utf8chr($c) + { + if (0x80 > $c %= 0x200000) { + return \chr($c); + } + if (0x800 > $c) { + return \chr(0xC0 | $c >> 6).\chr(0x80 | $c & 0x3F); + } + if (0x10000 > $c) { + return \chr(0xE0 | $c >> 12).\chr(0x80 | $c >> 6 & 0x3F).\chr(0x80 | $c & 0x3F); + } + + return \chr(0xF0 | $c >> 18).\chr(0x80 | $c >> 12 & 0x3F).\chr(0x80 | $c >> 6 & 0x3F).\chr(0x80 | $c & 0x3F); + } +} diff --git a/vendor/drush/drush/src/Internal/Symfony/Yaml/Yaml.php b/vendor/drush/drush/src/Internal/Symfony/Yaml/Yaml.php new file mode 100644 index 0000000000..9784570e28 --- /dev/null +++ b/vendor/drush/drush/src/Internal/Symfony/Yaml/Yaml.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Drush\Internal\Symfony\Yaml; + +use Drush\Internal\Symfony\Yaml\Exception\ParseException; + +/** + * Yaml offers convenience methods to load and dump YAML. + * + * @author Fabien Potencier + */ +class Yaml +{ + /** + * Parses YAML into a PHP value. + * + * Usage: + * + * $array = Yaml::parse(file_get_contents('config.yml')); + * print_r($array); + * + * As this method accepts both plain strings and file names as an input, + * you must validate the input before calling this method. Passing a file + * as an input is a deprecated feature and will be removed in 3.0. + * + * Note: the ability to pass file names to the Yaml::parse method is deprecated since Symfony 2.2 and will be removed in 3.0. Pass the YAML contents of the file instead. + * + * @param string $input Path to a YAML file or a string containing YAML + * @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types false otherwise + * @param bool $objectSupport True if object support is enabled, false otherwise + * @param bool $objectForMap True if maps should return a stdClass instead of array() + * + * @return mixed The YAML converted to a PHP value + * + * @throws ParseException If the YAML is not valid + */ + public static function parse($input, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false) + { + // if input is a file, process it + $file = ''; + if (false === strpos($input, "\n") && is_file($input)) { + @trigger_error('The ability to pass file names to the '.__METHOD__.' method is deprecated since Symfony 2.2 and will be removed in 3.0. Pass the YAML contents of the file instead.', E_USER_DEPRECATED); + + if (false === is_readable($input)) { + throw new ParseException(sprintf('Unable to parse "%s" as the file is not readable.', $input)); + } + + $file = $input; + $input = file_get_contents($file); + } + + $yaml = new Parser(); + + try { + return $yaml->parse($input, $exceptionOnInvalidType, $objectSupport, $objectForMap); + } catch (ParseException $e) { + if ($file) { + $e->setParsedFile($file); + } + + throw $e; + } + } + + /** + * Dumps a PHP value to a YAML string. + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. + * + * @param mixed $input The PHP value + * @param int $inline The level where you switch to inline YAML + * @param int $indent The amount of spaces to use for indentation of nested nodes + * @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types (a PHP resource or object), false otherwise + * @param bool $objectSupport True if object support is enabled, false otherwise + * + * @return string A YAML string representing the original PHP value + */ + public static function dump($input, $inline = 2, $indent = 4, $exceptionOnInvalidType = false, $objectSupport = false) + { + if ($indent < 1) { + throw new \InvalidArgumentException('The indentation must be greater than zero.'); + } + + $yaml = new Dumper(); + $yaml->setIndentation($indent); + + return $yaml->dump($input, $inline, 0, $exceptionOnInvalidType, $objectSupport); + } +} diff --git a/vendor/drush/drush/src/TestTraits/CliTestTrait.php b/vendor/drush/drush/src/TestTraits/CliTestTrait.php new file mode 100644 index 0000000000..da89f76bf2 --- /dev/null +++ b/vendor/drush/drush/src/TestTraits/CliTestTrait.php @@ -0,0 +1,222 @@ +process ? $this->process->getOutput() : ''; + } + + /** + * Accessor for the last stderr output, non-trimmed. + * + * @return string + * Raw stderr as text. + * + * @access public + */ + public function getErrorOutputRaw() + { + return $this->process ? $this->process->getErrorOutput() : ''; + } + + /** + * Actually runs the command. + * + * @param string $command + * The actual command line to run. + * @param integer $expected_return + * The return code to expect + * @param sting cd + * The directory to run the command in. + * @param array $env + * Extra environment variables. + * @param string $input + * A string representing the STDIN that is piped to the command. + */ + public function execute($command, $expected_return = 0, $cd = null, $env = null, $input = null) + { + try { + // Process uses a default timeout of 60 seconds, set it to 0 (none). + $this->process = new Process($command, $cd, $env, $input, 0); + $this->process->inheritEnvironmentVariables(true); + if ($this->timeout) { + $this->process->setTimeout($this->timeout) + ->setIdleTimeout($this->idleTimeout); + } + $return = $this->process->run(); + if ($expected_return !== $return) { + $message = 'Unexpected exit code ' . $return . ' (expected ' . $expected_return . ") for command:\n" . $command; + throw new \Exception($message . $this->buildProcessMessage()); + } + // Reset timeouts to default. + $this->timeout = $this->defaultTimeout; + $this->idleTimeout = $this->defaultIdleTimeout; + } catch (ProcessTimedOutException $e) { + if ($e->isGeneralTimeout()) { + $message = 'Command runtime exceeded ' . $this->timeout . " seconds:\n" . $command; + } else { + $message = 'Command had no output for ' . $this->idleTimeout . " seconds:\n" . $command; + } + throw new \Exception($message . $this->buildProcessMessage()); + } + } + + public static function escapeshellarg($arg) + { + // Short-circuit escaping for simple params (keep stuff readable) + if (preg_match('|^[a-zA-Z0-9.:/_-]*$|', $arg)) { + return $arg; + } elseif (self::isWindows()) { + return self::_escapeshellargWindows($arg); + } else { + return escapeshellarg($arg); + } + } + + public static function isWindows() + { + return strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'; + } + + public static function _escapeshellargWindows($arg) + { + // Double up existing backslashes + $arg = preg_replace('/\\\/', '\\\\\\\\', $arg); + + // Double up double quotes + $arg = preg_replace('/"/', '""', $arg); + + // Double up percents. + $arg = preg_replace('/%/', '%%', $arg); + + // Add surrounding quotes. + $arg = '"' . $arg . '"'; + + return $arg; + } + + /** + * Borrowed from \Symfony\Component\Process\Exception\ProcessTimedOutException + * + * @return string + */ + public function buildProcessMessage() + { + $error = sprintf( + "%s\n\nExit Code: %s(%s)\n\nWorking directory: %s", + $this->process->getCommandLine(), + $this->process->getExitCode(), + $this->process->getExitCodeText(), + $this->process->getWorkingDirectory() + ); + + if (!$this->process->isOutputDisabled()) { + $error .= sprintf( + "\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s", + $this->process->getOutput(), + $this->process->getErrorOutput() + ); + } + + return $error; + } + + /** + * Checks that the output matches the expected output. + * + * This matches against a simplified version of the actual output that has + * absolute paths and duplicate whitespace removed, to avoid false negatives + * on minor differences. + * + * @param string $expected + * The expected output. + * @param string $filter + * Optional regular expression that should be ignored in the error output. + */ + + protected function assertOutputEquals($expected, $filter = '') + { + $output = $this->getSimplifiedOutput(); + if (!empty($filter)) { + $output = preg_replace($filter, '', $output); + } + $this->assertEquals($expected, $output); + } + + /** + * Checks that the error output matches the expected output. + * + * This matches against a simplified version of the actual output that has + * absolute paths and duplicate whitespace removed, to avoid false negatives + * on minor differences. + * + * @param string $expected + * The expected output. + * @param string $filter + * Optional regular expression that should be ignored in the error output. + */ + protected function assertErrorOutputEquals($expected, $filter = '') + { + $output = $this->getSimplifiedErrorOutput(); + if (!empty($filter)) { + $output = preg_replace($filter, '', $output); + } + $this->assertEquals($expected, $output); + } +} diff --git a/vendor/drush/drush/src/TestTraits/DrushTestTrait.php b/vendor/drush/drush/src/TestTraits/DrushTestTrait.php new file mode 100644 index 0000000000..722b840089 --- /dev/null +++ b/vendor/drush/drush/src/TestTraits/DrushTestTrait.php @@ -0,0 +1,116 @@ +&1. + * @param array $env + * Environment variables to pass along to the subprocess. + */ + public function drush($command, array $args = [], array $options = [], $site_specification = null, $cd = null, $expected_return = 0, $suffix = null, $env = []) + { + // Set UA for SimpleTests which typically extend BrowserTestCase (i.e. contrib modules). + if (isset($this->databasePrefix) && function_exists('drupal_generate_test_ua') && !isset($env['HTTP_USER_AGENT'])) { + $env['HTTP_USER_AGENT'] = drupal_generate_test_ua($this->databasePrefix); + } + + $global_option_list = ['simulate', 'root', 'uri', 'include', 'config', 'alias-path', 'ssh-options']; + $cmd[] = self::getPathToDrush(); + + // Insert global options. + foreach ($options as $key => $value) { + if (in_array($key, $global_option_list)) { + unset($options[$key]); + $cmd[] = $this->convertKeyValueToFlag($key, $value); + } + } + + $cmd[] = "--no-interaction"; + + // Insert site specification and drush command. + if (!empty($site_specification)) { + $cmd[] = self::escapeshellarg($site_specification); + } + $cmd[] = $command; + + // Insert drush command arguments. + foreach ($args as $arg) { + $cmd[] = self::escapeshellarg($arg); + } + // insert drush command options + foreach ($options as $key => $value) { + $cmd[] = $this->convertKeyValueToFlag($key, $value); + } + + $cmd[] = $suffix; + $exec = array_filter($cmd, 'strlen'); // Remove NULLs + + $cmd = implode(' ', $exec); + $this->execute($cmd, $expected_return, $cd, $env); + } + + /** + * Given an option key / value pair, convert to a + * "--key=value" string. + * + * @param string $key The option name + * @param string $value The option value (or empty) + * @return string + */ + protected function convertKeyValueToFlag($key, $value) + { + if (!isset($value)) { + return "--$key"; + } + return "--$key=" . self::escapeshellarg($value); + } + + /** + * Return the major version of Drush + * + * @return string e.g. "8" or "9" + */ + public function drushMajorVersion() + { + static $major; + + if (!isset($major)) { + $this->drush('version', [], ['field' => 'drush-version']); + $version = trim($this->getOutput()); + list($major) = explode('.', $version); + } + return (int)$major; + } +} diff --git a/vendor/drush/drush/src/TestTraits/OutputUtilsTrait.php b/vendor/drush/drush/src/TestTraits/OutputUtilsTrait.php new file mode 100644 index 0000000000..a8467cd6ba --- /dev/null +++ b/vendor/drush/drush/src/TestTraits/OutputUtilsTrait.php @@ -0,0 +1,165 @@ +simplifyOutput($this->getOutput()); + } + + /** + * Returns a simplified version of the error output to facilitate testing. + * + * @return string + * A simplified version of the error output that has things like full + * paths and superfluous whitespace removed from it. + */ + protected function getSimplifiedErrorOutput() + { + return $this->simplifyOutput($this->getErrorOutput()); + } + + /** + * Remove things like full paths and extra whitespace from the given string. + * + * @param string $output + * The output string to simplify. + * + * @return string + * The simplified output. + */ + protected function simplifyOutput($output) + { + // We do not care if Drush inserts a -t or not in the string. Depends on whether there is a tty. + $output = preg_replace('# -t #', ' ', $output); + // Remove multiple blank lines + $output = preg_replace("#\n\n\n*#m", "\n\n", $output); + // Remove double spaces from output to help protect test from false negatives if spacing changes subtly + $output = preg_replace('# *#', ' ', $output); + // Remove leading and trailing spaces. + $output = preg_replace('#^[ \t]*#m', '', $output); + $output = preg_replace('#[ \t]*$#m', '', $output); + // Remove verbose info for rsync. + $output = preg_replace('# -akzv --stats --progress #', ' -akz ', $output); + // Debug flags may be added to command strings if we are in debug mode. Take those out so that tests in phpunit --debug mode work + $output = preg_replace('# --debug #', ' ', $output); + $output = preg_replace('# --verbose #', ' ', $output); + $output = preg_replace('# -vvv #', ' ', $output); + + foreach ($this->pathsToSimplify() as $path => $simplification) { + $output = str_replace($path, $simplification, $output); + } + + return $output; + } + + public function pathsToSimplify() + { + return []; + } + + /** + * Accessor for the last output, trimmed. + * + * @return string + * Trimmed output as text. + * + * @access public + */ + public function getOutput() + { + return trim($this->getOutputRaw()); + } + + /** + * Accessor for the last stderr output, trimmed. + * + * @return string + * Trimmed stderr as text. + * + * @access public + */ + public function getErrorOutput() + { + return trim($this->getErrorOutputRaw()); + } + + /** + * Accessor for the last output, rtrimmed and split on newlines. + * + * @return array + * Output as array of lines. + * + * @access public + */ + public function getOutputAsList() + { + return array_map('rtrim', explode("\n", $this->getOutput())); + } + + /** + * Accessor for the last stderr output, rtrimmed and split on newlines. + * + * @return array + * Stderr as array of lines. + * + * @access public + */ + public function getErrorOutputAsList() + { + return array_map('rtrim', explode("\n", $this->getErrorOutput())); + } + + /** + * Accessor for the last output, decoded from json. + * + * @param string $key + * Optionally return only a top level element from the json object. + * + * @return object + * Decoded object. + */ + public function getOutputFromJSON($key = null) + { + $output = $this->getOutput(); + $json = json_decode($output, true); + if (!$json) { + throw new \Exception("No json output received.\n\nOutput:\n\n$output\n\nStderr:\n\n" . $this->getErrorOutput()); + } + if (isset($key)) { + $json = $json[$key]; + } + return $json; + } +} diff --git a/vendor/drush/drush/tests/COVERAGE.txt b/vendor/drush/drush/tests/COVERAGE.txt new file mode 100644 index 0000000000..e3f2c93a6b --- /dev/null +++ b/vendor/drush/drush/tests/COVERAGE.txt @@ -0,0 +1,89 @@ +COMMANDS +------------ +pm-download: GOOD. + need test for `pm-download --select` (hard to test; depends on state of project releases on d.o.) +pm-updatecode: GOOD. +pm-update: FAIR. Implicitly tested by pm-updatecode but updatedb not yet tested. +pm-releasenotes +pm-releases +pm-enable: GOOD. testEnDisUnList(). +pm-disable: GOOD. testEnDisUnList(). +pm-uninstall: GOOD. testEnDisUnList(). +pm-list: GOOD. testEnDisUnList(). +pm-info: GOOD. testEnDisUnList(). +pm-refresh: +version-control: FAIR. See updatecode. To be deprecated all git workflow after git.drupal.org? +package-hander: + +sql-cli: +sql-connect: +sql-query: FAIR. Implicit by site-install, sql-sync +sql-dump: GOOD. tested by sqlDumpTest. +sql-sync: GOOD. testLocalSqlSync(). + need test: --source-dump, --target-dump, --source-dump-dir and --target-dump-dir and permutations of same used together. +sql-drop: FAIR. Implicit by site-install +sql-sanitize: FAIR. Implicit by testLocalSqlSync() + + +updatedb: NONE. Used to be implicitly tested by siteUpgradeTest. +archive-dump: GOOD +archive-restore: GOOD. Has own test and implicitly tested by environment cache in Unish framework. +help +version: GOOD. Implicit by testStandaloneScript() +php-eval: GOOD. Implicitly tested by many tests (e.g. completeTest). +php-script: GOOD. +drupal-directory: GOOD +cache-get: GOOD +cache-set: GOOD +cache-clear: GOOD +core-config: NONE +core-cron +core-status: FAIR: Implicit test by contextTest. +docs +core-rsync +core-quick-drupal: GOOD +image: GOOD +queue-*: GOOD +runserver +search-* +shellalias: GOOD + need test: shell alias with site alias +site-install: FAIR. Implicit test by setUpDrupal(). +state: NONE +ssh: GOOD +topic +usage-* +variable-*: GOOD +watchdog-*: GOOD + +user-*: GOOD. + +field-*: GOOD. + +make: GOOD + +INCLUDES +------------ +backend: GOOD + need test: --pipe with remote alias and --pipe with list of aliases +batch: GOOD +bootstrap: +command: FAIR +complete: GOOD +context: FAIR. Many functions implicitly tested. Option merging (config, include, alias-path) not tested. +drush: NONE. +environment +sitealias. FAIR. Explicit test for alias lists. Single aliases implicitly tested by contextTest. Option propagation tested by backendTest. Option recovery for @self alias tested by sqlDumpTest. +dbtng: Good. Implicit by variable-*. +drupal +exec: GOOD: Implicitly tested all over. +filesystem +output + + +ROOT +------------- +drush: + need test: drush.ini +drush.php +drush.complete.sh: N/A diff --git a/vendor/drush/drush/tests/Drush/Tests/Make/Parser/ParserIniTest.php b/vendor/drush/drush/tests/Drush/Tests/Make/Parser/ParserIniTest.php new file mode 100644 index 0000000000..adc1c250cf --- /dev/null +++ b/vendor/drush/drush/tests/Drush/Tests/Make/Parser/ParserIniTest.php @@ -0,0 +1,43 @@ +assertFalse(ParserIni::supportedFile('-')); + $this->assertFalse(ParserIni::supportedFile('/tmp/foo/bar/baz.make.yml')); + $this->assertTrue(ParserIni::supportedFile('./baz/foo.make')); + } + + /** + * @dataProvider providerParse + * @covers ::parse + */ + public function testParse($ini, $expected) { + $parsed = ParserIni::parse($ini); + $this->assertSame($expected, $parsed); + } + + /** + * Provides INI snippets to test the parser. + */ + public function providerParse() { + $snippets[] = array('foo[bar][baz] = one', array('foo' => array('bar' => array('baz' => 'one')))); + $snippets[] = array("; A comment should not be part of the returned array\nprojects[] = drupal", array('projects' => array('drupal'))); + + // @todo make more tests. + return $snippets; + } + +} diff --git a/vendor/drush/drush/tests/Drush/Tests/Make/Parser/ParserYamlTest.php b/vendor/drush/drush/tests/Drush/Tests/Make/Parser/ParserYamlTest.php new file mode 100644 index 0000000000..b8746513fd --- /dev/null +++ b/vendor/drush/drush/tests/Drush/Tests/Make/Parser/ParserYamlTest.php @@ -0,0 +1,57 @@ +assertFalse(ParserYaml::supportedFile('-')); + $this->assertTrue(ParserYaml::supportedFile('/tmp/foo/bar/baz.make.yml')); + $this->assertFalse(ParserYaml::supportedFile('./baz/foo.make')); + } + + /** + * @dataProvider providerParse + * @covers ::parse + */ + public function testParse($yaml, $expected) { + $parsed = ParserYaml::parse($yaml); + $this->assertSame($expected, $parsed); + } + + /** + * Provides YAML snippets to test the parser. + */ + public function providerParse() { + $yaml = <<<'YAML' +foo: + bar: + baz: one +YAML; + $snippets[] = array($yaml, array('foo' => array('bar' => array('baz' => 'one')))); + + $yaml = <<<'YAML' +projects: + drupal: ~ + views: + version: '3.0' +YAML; + + $snippets[] = array($yaml, array('projects' => array('drupal' => NULL, 'views' => array('version' => '3.0')))); + + // @todo make more tests. + return $snippets; + } + +} diff --git a/vendor/drush/drush/tests/README.md b/vendor/drush/drush/tests/README.md new file mode 100644 index 0000000000..b86e0da667 --- /dev/null +++ b/vendor/drush/drush/tests/README.md @@ -0,0 +1,29 @@ +Drush's test suite is based on [PHPUnit](http://www.phpunit.de). In order to maintain +high quality, our tests are run on every push by [Travis](https://travis-ci.org/drush-ops/drush) + +Usage +-------- +1. Review the configuration settings in [tests/phpunit.xml.dist](phpunit.xml.dist). If customization is needed, copy to phpunit.xml and edit away. +1. Run unit tests: `unish.sh` + +Advanced usage +--------- +- Run only tests matching a regex: `unish.sh --filter=testVersionString` +- Skip slow tests (usually those with network usage): `unish.sh --exclude-group slow` +- XML results: `unish.sh --filter=testVersionString --log-junit results.xml` +- Use an alternate version of Drupal: `UNISH_DRUPAL_MAJOR_VERSION=8 unish.sh ...` +- Skip teardown (to examine test sites after a failure): `UNISH_DIRTY=1 unish.sh ...` + +Reuse by Drush Commandfiles +----------- +Drush commandfiles are encouraged to ship with PHPUnit test cases that +extend UnitUnishTestCase and CommandUnishTestCase. In order to run +the tests, you have to point to the phpunit.xml file that used by Drush. +The devel project has a wrapper script which demonstrates this - +http://drupalcode.org/project/devel.git/blob/refs/heads/8.x-1.x:/run-tests-drush.sh + +Cache +----------- +In order to speed up test runs, Unish (the Drush testing class) caches built Drupal sites +and restores them as requested by tests. Once in while, you might need to clear this cache +by deleting the /drush-cache directory. diff --git a/vendor/drush/drush/tests/Unish/CommandUnishTestCase.php b/vendor/drush/drush/tests/Unish/CommandUnishTestCase.php new file mode 100644 index 0000000000..305f141036 --- /dev/null +++ b/vendor/drush/drush/tests/Unish/CommandUnishTestCase.php @@ -0,0 +1,363 @@ +getOutputRaw()); + } + + /** + * Accessor for the last output, non-trimmed. + * + * @return string + * Raw output as text. + * + * @access public + */ + function getOutputRaw() { + return $this->process ? $this->process->getOutput() : ''; + } + + /** + * Accessor for the last output, rtrimmed and split on newlines. + * + * @return array + * Output as array of lines. + * + * @access public + */ + function getOutputAsList() { + return array_map('rtrim', explode("\n", $this->getOutput())); + } + + /** + * Accessor for the last stderr output, trimmed. + * + * @return string + * Trimmed stderr as text. + * + * @access public + */ + function getErrorOutput() { + return trim($this->getErrorOutputRaw()); + } + + /** + * Accessor for the last stderr output, non-trimmed. + * + * @return string + * Raw stderr as text. + * + * @access public + */ + function getErrorOutputRaw() { + return $this->process ? $this->process->getErrorOutput() : ''; + } + + /** + * Accessor for the last stderr output, rtrimmed and split on newlines. + * + * @return array + * Stderr as array of lines. + * + * @access public + */ + function getErrorOutputAsList() { + return array_map('rtrim', explode("\n", $this->getErrorOutput())); + } + + /** + * Accessor for the last output, decoded from json. + * + * @param string $key + * Optionally return only a top level element from the json object. + * + * @return object + * Decoded object. + */ + function getOutputFromJSON($key = NULL) { + $json = json_decode($this->getOutput()); + if (isset($key)) { + $json = $json->{$key}; // http://stackoverflow.com/questions/2925044/hyphens-in-keys-of-object + } + return $json; + } + + /** + * Actually runs the command. + * + * @param string $command + * The actual command line to run. + * @param integer $expected_return + * The return code to expect + * @param sting cd + * The directory to run the command in. + * @param array $env + * @todo: Not fully implemented yet. Inheriting environment is hard - http://stackoverflow.com/questions/3780866/why-is-my-env-empty. + * @see drush_env(). + * Extra environment variables. + * @param string $input + * A string representing the STDIN that is piped to the command. + * @return integer + * Exit code. Usually self::EXIT_ERROR or self::EXIT_SUCCESS. + */ + function execute($command, $expected_return = self::EXIT_SUCCESS, $cd = NULL, $env = NULL, $input = NULL) { + $return = 1; + $this->tick(); + + // Apply the environment variables we need for our test to the head of the + // command (excludes Windows). Process does have an $env argument, but it replaces the entire + // environment with the one given. This *could* be used for ensuring the + // test ran with a clean environment, but it also makes tests fail hard on + // Travis, for unknown reasons. + // @see https://github.com/drush-ops/drush/pull/646 + $prefix = ''; + if($env && !$this->is_windows()) { + foreach ($env as $env_name => $env_value) { + $prefix .= $env_name . '=' . self::escapeshellarg($env_value) . ' '; + } + } + $this->log("Executing: $command", 'warning'); + + try { + // Process uses a default timeout of 60 seconds, set it to 0 (none). + $this->process = new Process($command, $cd, NULL, $input, 0); + if (!getenv('UNISH_NO_TIMEOUTS')) { + $this->process->setTimeout($this->timeout) + ->setIdleTimeout($this->idleTimeout); + } + $return = $this->process->run(); + if ($expected_return !== $return) { + $message = 'Unexpected exit code ' . $return . ' (expected ' . $expected_return . ") for command:\n" . $command; + throw new UnishProcessFailedError($message, $this->process); + } + // Reset timeouts to default. + $this->timeout = $this->defaultTimeout; + $this->idleTimeout = $this->defaultIdleTimeout; + return $return; + } + catch (ProcessTimedOutException $e) { + if ($e->isGeneralTimeout()) { + $message = 'Command runtime exceeded ' . $this->timeout . " seconds:\n" . $command; + } + else { + $message = 'Command had no output for ' . $this->idleTimeout . " seconds:\n" . $command; + } + throw new UnishProcessFailedError($message, $this->process); + } + } + + /** + * Invoke drush in via execute(). + * + * @param command + * A defined drush command such as 'cron', 'status' or any of the available ones such as 'drush pm'. + * @param args + * Command arguments. + * @param $options + * An associative array containing options. + * @param $site_specification + * A site alias or site specification. Include the '@' at start of a site alias. + * @param $cd + * A directory to change into before executing. + * @param $expected_return + * The expected exit code. Usually self::EXIT_ERROR or self::EXIT_SUCCESS. + * @param $suffix + * Any code to append to the command. For example, redirection like 2>&1. + * @param array $env + * Environment variables to pass along to the subprocess. @todo - not used. + * @return integer + * An exit code. + */ + function drush($command, array $args = array(), array $options = array(), $site_specification = NULL, $cd = NULL, $expected_return = self::EXIT_SUCCESS, $suffix = NULL, $env = array()) { + $global_option_list = array('simulate', 'root', 'uri', 'include', 'config', 'alias-path', 'ssh-options', 'backend'); + $hide_stderr = FALSE; + $cmd[] = UNISH_DRUSH; + + // Insert global options. + foreach ($options as $key => $value) { + if (in_array($key, $global_option_list)) { + unset($options[$key]); + if ($key == 'backend') { + $hide_stderr = TRUE; + $value = NULL; + } + if (!isset($value)) { + $cmd[] = "--$key"; + } + else { + $cmd[] = "--$key=" . self::escapeshellarg($value); + } + } + } + + if ($level = $this->log_level()) { + $cmd[] = '--' . $level; + } + $cmd[] = "--nocolor"; + + // Insert code coverage argument before command, in order for it to be + // parsed as a global option. This matters for commands like ssh and rsync + // where options after the command are passed along to external commands. + $result = $this->getTestResultObject(); + if ($result->getCollectCodeCoverageInformation()) { + $coverage_file = tempnam(UNISH_TMP, 'drush_coverage'); + if ($coverage_file) { + $cmd[] = "--drush-coverage=" . $coverage_file; + } + } + + // Insert site specification and drush command. + $cmd[] = empty($site_specification) ? NULL : self::escapeshellarg($site_specification); + $cmd[] = $command; + + // Insert drush command arguments. + foreach ($args as $arg) { + $cmd[] = self::escapeshellarg($arg); + } + // insert drush command options + foreach ($options as $key => $value) { + if (!isset($value)) { + $cmd[] = "--$key"; + } + else { + $cmd[] = "--$key=" . self::escapeshellarg($value); + } + } + + $cmd[] = $suffix; + if ($hide_stderr) { + $cmd[] = '2>' . $this->bit_bucket(); + } + $exec = array_filter($cmd, function ($item) { return !empty($item); }); // Remove NULLs + // Set sendmail_path to 'true' to disable any outgoing emails + // that tests might cause Drupal to send. + + $php_options = (array_key_exists('PHP_OPTIONS', $env)) ? $env['PHP_OPTIONS'] . " " : ""; + // @todo The PHP Options below are not yet honored by execute(). See .travis.yml for an alternative way. + $env['PHP_OPTIONS'] = "${php_options}-d sendmail_path='true'"; + $return = $this->execute(implode(' ', $exec), $expected_return, $cd, $env); + + // Ignore code coverage information. + if (!empty($coverage_file)) { + unlink($coverage_file); + } + + return $return; + } + + /** + * A slightly less functional copy of drush_backend_parse_output(). + */ + function parse_backend_output($string) { + $regex = sprintf(UNISH_BACKEND_OUTPUT_DELIMITER, '(.*)'); + preg_match("/$regex/s", $string, $match); + if (isset($match[1])) { + // we have our JSON encoded string + $output = $match[1]; + // remove the match we just made and any non printing characters + $string = trim(str_replace(sprintf(UNISH_BACKEND_OUTPUT_DELIMITER, $match[1]), '', $string)); + } + + if (!empty($output)) { + $data = json_decode($output, TRUE); + if (is_array($data)) { + return $data; + } + } + return $string; + } + + /** + * Ensure that an expected log message appears in the Drush log. + * + * $this->drush('command', array(), array('backend' => NULL)); + * $parsed = $this->parse_backend_output($this->getOutput()); + * $this->assertLogHasMessage($parsed['log'], "Expected message", 'debug') + * + * @param $log Parsed log entries from backend invoke + * @param $message The expected message that must be contained in + * some log entry's 'message' field. Substrings will match. + * @param $logType The type of log message to look for; all other + * types are ignored. If FALSE (the default), then all log types + * will be searched. + */ + function assertLogHasMessage($log, $message, $logType = FALSE) { + foreach ($log as $entry) { + if (!$logType || ($entry['type'] == $logType)) { + if (strpos($entry['message'], $message) !== FALSE) { + return TRUE; + } + } + } + $this->fail("Could not find expected message in log: " . $message); + } + + function drush_major_version() { + static $major; + + if (!isset($major)) { + $this->drush('version', array('drush_version'), array('pipe' => NULL)); + $version = trim($this->getOutput()); + list($major) = explode('.', $version); + } + return (int)$major; + } +} diff --git a/vendor/drush/drush/tests/Unish/UnishProcessFailedError.php b/vendor/drush/drush/tests/Unish/UnishProcessFailedError.php new file mode 100644 index 0000000000..b579f1ecfe --- /dev/null +++ b/vendor/drush/drush/tests/Unish/UnishProcessFailedError.php @@ -0,0 +1,18 @@ +getOutput()) { + $message .= "\n\nCommand output:\n" . $output; + } + if ($stderr = $process->getErrorOutput()) { + $message .= "\n\nCommand stderr:\n" . $stderr; + } + + parent::__construct($message); + } +} diff --git a/vendor/drush/drush/tests/Unish/UnishTestCase.php b/vendor/drush/drush/tests/Unish/UnishTestCase.php new file mode 100644 index 0000000000..21f1f507af --- /dev/null +++ b/vendor/drush/drush/tests/Unish/UnishTestCase.php @@ -0,0 +1,467 @@ +log_level()) { + case 'verbose': + if (in_array($type, array('notice', 'verbose'))) fwrite(STDERR, $line); + break; + case 'debug': + fwrite(STDERR, $line); + break; + default: + if ($type == 'notice') fwrite(STDERR, $line); + break; + } + } + + function log_level() { + // -d is reserved by `phpunit` + if (in_array('--debug', $_SERVER['argv'])) { + return 'debug'; + } + elseif (in_array('--verbose', $_SERVER['argv']) || in_array('-v', $_SERVER['argv'])) { + return 'verbose'; + } + } + + public static function is_windows() { + return strtoupper(substr(PHP_OS, 0, 3)) == "WIN"; + } + + public static function get_tar_executable() { + return self::is_windows() ? "bsdtar.exe" : "tar"; + } + + /** + * Print out a tick mark. + * + * Useful for longer running tests to indicate they're working. + */ + function tick() { + static $chars = array('/', '-', '\\', '|'); + static $counter = 0; + // ANSI support is flaky on Win32, so don't try to do ticks there. + if (!$this->is_windows()) { + print $chars[($counter++ % 4)] . "\033[1D"; + } + } + + /** + * Converts a Windows path (dir1\dir2\dir3) into a Unix path (dir1/dir2/dir3). + * Also converts a cygwin "drive emulation" path (/cygdrive/c/dir1) into a + * proper drive path, still with Unix slashes (c:/dir1). + * + * @copied from Drush's environment.inc + */ + function convert_path($path) { + $path = str_replace('\\','/', $path); + $path = preg_replace('/^\/cygdrive\/([A-Za-z])(.*)$/', '\1:\2', $path); + + return $path; + } + + /** + * Borrowed from Drush. + * Checks operating system and returns + * supported bit bucket folder. + */ + function bit_bucket() { + if (!$this->is_windows()) { + return '/dev/null'; + } + else { + return 'nul'; + } + } + + public static function escapeshellarg($arg) { + // Short-circuit escaping for simple params (keep stuff readable) + if (preg_match('|^[a-zA-Z0-9.:/_-]*$|', $arg)) { + return $arg; + } + elseif (self::is_windows()) { + return self::_escapeshellarg_windows($arg); + } + else { + return escapeshellarg($arg); + } + } + + public static function _escapeshellarg_windows($arg) { + // Double up existing backslashes + $arg = preg_replace('/\\\/', '\\\\\\\\', $arg); + + // Double up double quotes + $arg = preg_replace('/"/', '""', $arg); + + // Double up percents. + $arg = preg_replace('/%/', '%%', $arg); + + // Add surrounding quotes. + $arg = '"' . $arg . '"'; + + return $arg; + } + + /** + * Helper function to generate a random string of arbitrary length. + * + * Copied from drush_generate_password(), which is otherwise not available here. + * + * @param $length + * Number of characters the generated string should contain. + * @return + * The generated string. + */ + public function randomString($length = 10) { + // This variable contains the list of allowable characters for the + // password. Note that the number 0 and the letter 'O' have been + // removed to avoid confusion between the two. The same is true + // of 'I', 1, and 'l'. + $allowable_characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'; + + // Zero-based count of characters in the allowable list: + $len = strlen($allowable_characters) - 1; + + // Declare the password as a blank string. + $pass = ''; + + // Loop the number of times specified by $length. + for ($i = 0; $i < $length; $i++) { + + // Each iteration, pick a random character from the + // allowable string and append it to the password: + $pass .= $allowable_characters[mt_rand(0, $len)]; + } + + return $pass; + } + + public function mkdir($path) { + if (!is_dir($path)) { + if ($this->mkdir(dirname($path))) { + if (@mkdir($path)) { + return TRUE; + } + } + return FALSE; + } + return TRUE; + } + + public function recursive_copy($src, $dst) { + $dir = opendir($src); + $this->mkdir($dst); + while(false !== ( $file = readdir($dir)) ) { + if (( $file != '.' ) && ( $file != '..' )) { + if ( is_dir($src . '/' . $file) ) { + $this->recursive_copy($src . '/' . $file,$dst . '/' . $file); + } + else { + copy($src . '/' . $file,$dst . '/' . $file); + } + } + } + closedir($dir); + } + + function webroot() { + return UNISH_SANDBOX . DIRECTORY_SEPARATOR . 'web'; + } + + function getSites() { + return self::$sites; + } + + function directory_cache($subdir = '') { + return getenv('CACHE_PREFIX') . '/' . $subdir; + } + + /** + * @param $env + * @return string + */ + function db_url($env) { + return substr(UNISH_DB_URL, 0, 6) == 'sqlite' ? "sqlite://sites/$env/files/unish.sqlite" : UNISH_DB_URL . '/unish_' . $env; + } + + function db_driver($db_url = UNISH_DB_URL) { + return parse_url(UNISH_DB_URL, PHP_URL_SCHEME); + } + + function defaultInstallationVersion() { + // There's a leading dot in UNISH_DRUPAL_MINOR_VERSION + return UNISH_DRUPAL_MAJOR_VERSION . UNISH_DRUPAL_MINOR_VERSION; + } + + function setUpDrupal($num_sites = 1, $install = FALSE, $version_string = NULL, $profile = NULL) { + $version_type = 'requested'; + if (!$version_string) { + $version_type = 'default'; + $version_string = $this->defaultInstallationVersion(); + } + $sites_subdirs_all = array('dev', 'stage', 'prod', 'retired', 'elderly', 'dead', 'dust'); + $sites_subdirs = array_slice($sites_subdirs_all, 0, $num_sites); + $root = $this->webroot(); + $major_version = substr($version_string, 0, 1); + + if (!isset($profile)) { + $profile = $major_version >= 7 ? 'testing' : 'default'; + } + $db_driver = $this->db_driver(UNISH_DB_URL); + + $cache_keys = array($num_sites, $install ? 'install' : 'noinstall', $version_string, $profile, $db_driver); + $source = $this->directory_cache('environments') . '/' . implode('-', $cache_keys) . '.tar.gz'; + $fetchAndInstall = true; + if (file_exists($source)) { + $this->log('Cache HIT. Environment: ' . $source, 'verbose'); + try { + $this->drush('archive-restore', array($source), array('destination' => $root, 'overwrite' => NULL)); + $fetchAndInstall = false; + } + catch (\Exception $e) { + $this->log('Unexpected error restoring saved environment. Re-installing instead.', 'warning'); + } + } + if ($fetchAndInstall) { + $this->log('Cache MISS. Environment: ' . $source, 'verbose'); + // Build the site(s), install (if needed), then cache. + foreach ($sites_subdirs as $subdir) { + $this->fetchInstallDrupal($subdir, $install, $version_string, $profile); + } + $options = array( + 'destination' => $source, + 'root' => $root, + 'uri' => reset($sites_subdirs), + 'overwrite' => NULL, + ); + if ($install) { + $this->drush('archive-dump', array('@sites'), $options); + } + } + // Write an empty sites.php if we are on D7+. Needed for multi-site on D8 and + // used on D7 in \Unish\saCase::testBackendHonorsAliasOverride. + // Skip if there is no example.sites.php, e.g. in Drupal 9. + if ($major_version >= 7 && !file_exists($root . '/sites/sites.php') && file_exists($root . '/sites/example.sites.php')) { + copy($root . '/sites/example.sites.php', $root . '/sites/sites.php'); + } + + // Print the result of a run of 'drush status' on the Drupal we are testing against + $options = array( + 'root' => $this->webroot(), + 'uri' => reset($sites_subdirs), + ); + $this->drush('core-status', array('Drupal version'), $options); + $header = "\nTesting on "; + fwrite(STDERR, $header . $this->getOutput() . " given $version_type version $version_string\n\n"); + + // Stash details about each site. + foreach ($sites_subdirs as $subdir) { + self::$sites[$subdir] = array( + 'root' => $root, + 'uri' => $subdir, + 'db_url' => $this->db_url($subdir), + ); + // Make an alias for the site + $this->writeSiteAlias($subdir, $root, $subdir); + } + return self::$sites; + } + + function fetchInstallDrupal($env = 'dev', $install = FALSE, $version_string = NULL, $profile = NULL, $separate_roots = FALSE) { + if (!$version_string) { + $version_string = UNISH_DRUPAL_MAJOR_VERSION; + } + $root = $this->webroot(); + $uri = $separate_roots ? "default" : "$env"; + $options = array(); + $site = "$root/sites/$uri"; + + if (substr($version_string, 0, 1) == 6 && $this->db_driver(UNISH_DB_URL) == 'sqlite') { + // Validate + $this->markTestSkipped("Drupal 6 does not support SQLite."); + } + + // Download Drupal if not already present. + if (!file_exists($root) && (substr($version_string, 0, 1) == 6)) { + if (!$this->downloadDrupal6($version_string, $root)) { + $this->markTestSkipped("Could not download d6lts."); + } + } + if (!file_exists($root)) { + $options += array( + 'destination' => dirname($root), + 'drupal-project-rename' => basename($root), + 'yes' => NULL, + 'quiet' => NULL, + 'cache' => NULL, + ); + $this->drush('pm-download', array("drupal-$version_string"), $options); + // @todo This path is not proper in D8. + mkdir($root . '/sites/all/drush', 0777, TRUE); + } + + // If specified, install Drupal as a multi-site. + if ($install) { + $options = array( + 'root' => $root, + 'db-url' => $this->db_url($env), + 'sites-subdir' => $uri, + 'yes' => NULL, + 'quiet' => NULL, + ); + $this->drush('site-install', array($profile), $options); + // Give us our write perms back. + chmod($site, 0777); + chmod("$site/settings.php", 0777); + } + else { + @mkdir($site); + touch("$site/settings.php"); + } + } + + // pm-download cannot download from d6lts, so we will rough in our own download function + function downloadDrupal6($drupal_6_test_version, $root) { + if (($drupal_6_test_version == '6') || ($drupal_6_test_version == '6.x')) { + $drupal_6_test_version = '6.46'; + } + return $this->downloadFromGitHub('d6lts/drupal', $drupal_6_test_version, $root, "drupal-$drupal_6_test_version"); + } + + function downloadFromGitHub($project, $version, $target, $rootDirToRemove = '') { + $url = "https://github.com/{$project}/archive/{$version}.zip"; + $tarPath = dirname($target) . '/' . basename($url); + if (!$this->cachedDownload($url, $tarPath)) { + return false; + } + $zipDir = $target; + if (!empty($rootDirToRemove)) { + $zipDir = dirname($zipDir); + } + passthru("unzip -od $zipDir $tarPath >/dev/null 2>&1", $status); + if ($status != 0) { + return false; + } + if (!empty($rootDirToRemove)) { + rename("$zipDir/$rootDirToRemove", $zipDir . '/' . basename($target)); + } + return file_exists($target); + } + + function cachedDownload($url, $target) { + $dlCacheDir = $this->directory_cache('dl'); + @mkdir($dlCacheDir); + $cacheFile = $dlCacheDir . '/' . basename($target); + if (!file_exists($cacheFile)) { + passthru("curl -L --output $cacheFile $url >/dev/null 2>&1", $status); + if ($status != 0) { + return false; + } + } + copy($cacheFile, $target); + return file_exists($target); + } + + function writeSiteAlias($name, $root, $uri) { + $alias_definition = array($name => array('root' => $root, 'uri' => $uri)); + file_put_contents(UNISH_SANDBOX . '/etc/drush/' . $name . '.alias.drushrc.php', $this->unish_file_aliases($alias_definition)); + } + + /** + * Prepare the contents of an aliases file. + */ + function unish_file_aliases($aliases) { + foreach ($aliases as $name => $alias) { + $records[] = sprintf('$aliases[\'%s\'] = %s;', $name, var_export($alias, TRUE)); + } + $contents = "setupGlobalExtensionsForTests(); + + $options = []; + + // We modified the set of available Drush commands; we need to clear the Drush command cache + $this->drush('cc', array('drush'), $options); + + // drush foobar + $options['include'] = "$globalExtensions"; + $this->drush('foobar', array(), $options); + $output = $this->getOutput(); + $this->assertEquals('baz', $output); + + // Clear the Drush command cache again and test again with new includes + $this->drush('cc', array('drush'), $options); + + // drush foobar again, except include the 'Commands' folder when passing --include + $options['include'] = "$globalExtensions/Commands"; + $this->drush('foobar', array(), $options); + $output = $this->getOutput(); + $this->assertEquals('baz', $output); + } + + public function testExecuteWoot() { + $sites = $this->setUpDrupal(1, TRUE); + $uri = key($sites); + $root = $this->webroot(); + $options = array( + 'root' => $root, + 'uri' => $uri, + 'yes' => NULL, + ); + + // Copy the 'woot' module over to the Drupal site we just set up. + $wootModuleDir = $this->setupModulesForTests($root); + + // These are not good asserts, but for the purposes of isolation.... + $targetDir = $root . DIRECTORY_SEPARATOR . $this->drupalSitewideDirectory() . '/modules/woot'; + if (UNISH_DRUPAL_MAJOR_VERSION >= 8) { + $commandFile = $targetDir . "/src/Commands/WootCommands.php"; + } else { + $commandFile = $targetDir . "/Commands/WootCommands.php"; + } + $this->assertFileExists(dirname(dirname(dirname($commandFile)))); + $this->assertFileExists(dirname(dirname($commandFile))); + $this->assertFileExists(dirname($commandFile)); + $this->assertFileExists($commandFile); + + // Enable out module. This will also clear the commandfile cache. + $this->drush('pm-enable', array('woot'), $options); + + // In theory this is not necessary, but this test keeps failing. + $this->drush('cc', array('drush'), $options); + + // drush woot --help + $this->drush('woot', array(), $options + ['help' => NULL]); + $output = $this->getOutput(); + $this->assertStringContainsString('Woot mightily.', $output); + $this->assertStringContainsString('Aliases: wt', $output); + + // drush help woot + $this->drush('help', array('woot'), $options); + $output = $this->getOutput(); + $this->assertStringContainsString('Woot mightily.', $output); + + // drush woot + $this->drush('woot', array(), $options); + $output = $this->getOutput(); + $this->assertEquals('Woot!', $output); + + // drush my-cat --help + $this->drush('my-cat', array(), $options + ['help' => NULL]); + $output = $this->getOutput(); + $this->assertStringContainsString('This is the my-cat command', $output); + $this->assertStringContainsString('bet alpha --flip', $output); + $this->assertStringContainsString('The first parameter', $output); + $this->assertStringContainsString('The other parameter', $output); + $this->assertStringContainsString('Whether or not the second parameter', $output); + $this->assertStringContainsString('Aliases: c', $output); + + // drush help my-cat + $this->drush('help', array('my-cat'), $options); + $output = $this->getOutput(); + $this->assertStringContainsString('This is the my-cat command', $output); + + // drush my-cat bet alpha --flip + $this->drush('my-cat', array('bet', 'alpha'), $options + ['flip' => NULL]); + $output = $this->getOutput(); + $this->assertEquals('alphabet', $output); + + // drush woot --help with the 'woot' module ignored + $this->drush('woot', array(), $options + ['help' => NULL, 'ignored-modules' => 'woot'], NULL, NULL, self::EXIT_ERROR); + + // drush my-cat bet alpha --flip + $this->drush('my-cat', array('bet', 'alpha'), $options + ['flip' => NULL, 'ignored-modules' => 'woot'], NULL, NULL, self::EXIT_ERROR); + + $this->drush('try-formatters', array(), $options); + $output = $this->getOutput(); + $expected = <<assertEquals(trim(preg_replace('#[ \n]+#', ' ', $expected)), trim(preg_replace('#[ \n]+#', ' ', $output))); + + $this->drush('try-formatters --format=yaml --fields=III,II', array(), $options, NULL, NULL, self::EXIT_SUCCESS); + $output = $this->getOutput(); + $expected = <<assertEquals($expected, $output); + + $this->drush('try-formatters', array(), $options + ['backend' => NULL]); + $parsed = $this->parse_backend_output($this->getOutput()); + $data = $parsed['object']; + $expected = <<assertEquals($expected, json_encode($data)); + + // drush try-formatters --help + $this->drush('try-formatters', array(), $options + ['help' => NULL], NULL, NULL, self::EXIT_SUCCESS); + $output = $this->getOutput(); + $this->assertStringContainsString('Demonstrate formatters', $output); + $this->assertStringContainsString('try:formatters --fields=first,third', $output); + $this->assertStringContainsString('try:formatters --fields=III,II', $output); + $this->assertStringContainsString('--fields=', $output); + $this->assertStringContainsString('Fields to output. All available', $output); + $this->assertStringContainsString('--format=', $output); + $this->assertStringContainsString('Select output format. Available:', $output); + $this->assertStringContainsString('Aliases: try-formatters', $output); + + // If we are running Drupal version 8 or later, then also check to + // see if the demo:greet and annotated:greet commands are available. + if (UNISH_DRUPAL_MAJOR_VERSION >= 8) { + $this->drush('demo:greet symfony', array(), $options, NULL, NULL, self::EXIT_SUCCESS); + $output = $this->getOutput(); + $this->assertEquals('Hello symfony', $output); + + $this->drush('annotated:greet symfony', array(), $options, NULL, NULL, self::EXIT_SUCCESS); + $output = $this->getOutput(); + $this->assertEquals('Hello symfony', $output); + } + + // Flush the Drush cache so that our 'woot' command is not cached. + $this->drush('cache-clear', array('drush'), $options, NULL, NULL, self::EXIT_SUCCESS); + } + + public function setupGlobalExtensionsForTests() { + $globalExtension = __DIR__ . '/resources/global-includes'; + $targetDir = UNISH_SANDBOX . DIRECTORY_SEPARATOR . 'global-includes'; + $this->mkdir($targetDir); + $this->recursive_copy($globalExtension, $targetDir); + return $targetDir; + } + + public function setupModulesForTests($root) { + $wootMajor = UNISH_DRUPAL_MAJOR_VERSION; + $wootModule = __DIR__ . '/resources/modules/d' . $wootMajor . '/woot'; + $targetDir = $root . DIRECTORY_SEPARATOR . $this->drupalSitewideDirectory() . '/modules/woot'; + $this->mkdir($targetDir); + $this->recursive_copy($wootModule, $targetDir); + return $targetDir; + } +} diff --git a/vendor/drush/drush/tests/archiveDumpTest.php b/vendor/drush/drush/tests/archiveDumpTest.php new file mode 100644 index 0000000000..3c067bd6fb --- /dev/null +++ b/vendor/drush/drush/tests/archiveDumpTest.php @@ -0,0 +1,133 @@ += 7 ? 'testing' : 'default'; + $this->fetchInstallDrupal(self::uri, TRUE, NULL, $profile); + $root = $this->webroot(); + $dump_dest = UNISH_SANDBOX . DIRECTORY_SEPARATOR . 'dump.tar.gz'; + $options = array( + 'root' => $root, + 'uri' => self::uri, + 'yes' => NULL, + 'destination' => $dump_dest, + 'overwrite' => NULL, + ); + if ($no_core) { + $options['no-core'] = NULL; + } + $this->drush('archive-dump', array(self::uri), $options); + + return $dump_dest; + } + + /** + * Untar an archive and return the path to the untarred folder. + */ + private function unTar($dump_dest) { + $untar_dest = UNISH_SANDBOX . DIRECTORY_SEPARATOR . 'untar'; + unish_file_delete_recursive($untar_dest, TRUE); + $tar = self::get_tar_executable(); + $exec = sprintf("mkdir %s && cd %s && $tar -xzf %s", $untar_dest, $untar_dest, $dump_dest); + $this->execute($exec); + + return $untar_dest; + } + + /** + * Test if tarball generated by archive-dump looks right. + */ + public function testArchiveDump() { + $dump_dest = $this->archiveDump(FALSE); + $docroot = basename($this->webroot()); + + // Confirm that the dump exists + $this->assertFileExists($dump_dest); + + // Untar the archive and make sure it looks right. + $untar_dest = $this->unTar($dump_dest); + + if (strpos(UNISH_DB_URL, 'mysql') !== FALSE) { + $this->assertFileExists($untar_dest . '/unish_' . self::uri . '.sql'); + $this->execute(sprintf('head %s/unish_%s.sql | grep " dump"', $untar_dest, self::uri)); + } + $this->assertFileExists($untar_dest . '/MANIFEST.ini'); + $this->assertFileExists($untar_dest . '/' . $docroot); + + return $dump_dest; + } + + /** + * Test archive-restore. + * + * Restore the archive generated in testArchiveDump() and verify that the + * directory contents are identical. + * + * @depends testArchiveDump + */ + public function testArchiveRestore($dump_dest) { + $restore_dest = UNISH_SANDBOX . DIRECTORY_SEPARATOR . 'restore'; + $options = array( + 'yes' => NULL, + 'destination' => $restore_dest, + ); + $this->drush('archive-restore', array($dump_dest), $options); + $original_codebase = drush_dir_md5($this->webroot()); + $restored_codebase = drush_dir_md5($restore_dest); + $this->assertEquals($original_codebase, $restored_codebase); + } + + /** + * Test if tarball generated by archive-dump with --no-core looks right. + */ + public function testArchiveDumpNoCore() { + $dump_dest = $this->archiveDump(TRUE); + $untar_dest = $this->unTar($dump_dest); + $docroot = basename($this->webroot()); + $this->assertFileExists($untar_dest . '/MANIFEST.ini'); + $this->assertFileExists($untar_dest . '/' . $docroot); + $modules_dir = UNISH_DRUPAL_MAJOR_VERSION >= 8 ? '/core/modules' : '/modules'; + $this->assertFileDoesNotExist($untar_dest . '/' . $docroot . $modules_dir, 'No modules directory should exist with --no-core'); + + return $dump_dest; + } + + /** + * Test archive-restore for a site archive (--no-core). + * + * @depends testArchiveDumpNoCore + */ + public function testArchiveRestoreNoCore($dump_dest) { + $root = $this->webroot(); + $original_codebase = drush_dir_md5($root); + unish_file_delete_recursive($root . '/sites/' . self::uri, TRUE); + $options = array( + 'yes' => NULL, + 'destination' => $root, + ); + $this->drush('archive-restore', array($dump_dest), $options); + + $restored_codebase = drush_dir_md5($root); + $this->assertEquals($original_codebase, $restored_codebase); + } +} diff --git a/vendor/drush/drush/tests/backendTest.php b/vendor/drush/drush/tests/backendTest.php new file mode 100644 index 0000000000..ebf65df800 --- /dev/null +++ b/vendor/drush/drush/tests/backendTest.php @@ -0,0 +1,263 @@ +markTestIncomplete('Started failing due to https://github.com/drush-ops/drush/pull/555'); + + $aliasPath = UNISH_SANDBOX . '/aliases'; + mkdir($aliasPath); + $aliasFile = $aliasPath . '/foo.aliases.drushrc.php'; + $aliasContents = << '/fake/path/to/root', 'uri' => 'default'); +EOD; + file_put_contents($aliasFile, $aliasContents); + $options = array( + 'alias-path' => $aliasPath, + 'include' => dirname(__FILE__), // Find unit.drush.inc commandfile. + 'script-path' => dirname(__FILE__) . '/resources', // Find unit.drush.inc commandfile. + 'backend' => TRUE, + ); + $this->drush('php-script', array('testDispatchUsingAlias_script'), $options); + $parsed = $this->parse_backend_output($this->getOutput()); + + // $parsed['with'] and $parsed['without'] now contain an array + // each with the original arguments passed in with and without + // 'dispatch-using-alias', respectively. + $argDifference = array_diff($parsed['object']['with'], $parsed['object']['without']); + $this->assertEquals(array_diff(array_values($argDifference), array('@foo.dev')), array()); + $argDifference = array_diff($parsed['object']['without'], $parsed['object']['with']); + $this->assertEquals(array_diff(array_values($argDifference), array('--root=/fake/path/to/root', '--uri=default')), array()); + } + + /** + * Covers the following origin responsibilities. + * - A remote host is recognized in site specification. + * - Generates expected ssh command. + * + * General handling of site aliases will be in sitealiasTest.php. + */ + function testOrigin() { + $site_specification = 'user@server/path/to/drupal#sitename'; + $exec = sprintf('%s %s version arg1 arg2 --simulate --ssh-options=%s 2>%s', UNISH_DRUSH, self::escapeshellarg($site_specification), self::escapeshellarg('-i mysite_dsa'), self::escapeshellarg($this->bit_bucket())); + $this->execute($exec); + $bash = $this->escapeshellarg('drush --uri=sitename --root=/path/to/drupal version arg1 arg2 2>&1'); + $expected = "Simulating backend invoke: ssh -i mysite_dsa user@server $bash 2>&1"; + $output = $this->getOutput(); + $this->assertStringContainsString($expected, $output, 'Expected ssh command was built'); + + // Assure that arguments and options are passed along to a command thats not recognized locally. + $this->drush('non-existent-command', array('foo'), array('bar' => 'baz', 'simulate' => NULL), $site_specification); + $output = $this->getOutput(); + $this->assertStringContainsString('foo', $output); + $this->assertStringContainsString('--bar=baz', $output); + } + + /** + * Covers the following target responsibilities. + * - Interpret stdin as options as per REST API. + * - Successfully execute specified command. + * - JSON object has expected contents (including errors). + * - JSON object is wrapped in expected delimiters. + */ + function testTarget() { + $stdin = json_encode(array('filter'=>'sql')); + $exec = sprintf('%s version --backend 2>%s', UNISH_DRUSH, self::escapeshellarg($this->bit_bucket())); + $this->execute($exec, self::EXIT_SUCCESS, NULL, NULL, $stdin); + $parsed = $this->parse_backend_output($this->getOutput()); + $this->assertTrue((bool) $parsed, 'Successfully parsed backend output'); + $this->assertArrayHasKey('log', $parsed); + $this->assertArrayHasKey('output', $parsed); + $this->assertArrayHasKey('object', $parsed); + $this->assertEquals(self::EXIT_SUCCESS, $parsed['error_status']); + // This assertion shows that `version` was called and that stdin options were respected. + $this->assertStringStartsWith(' Drush Version ', $parsed['output']); + $this->assertEquals('Starting Drush preflight.', $parsed['log'][1]['message']); + + // Check error propogation by requesting an invalid command (missing Drupal site). + $this->drush('core-cron', array(), array('backend' => NULL), NULL, NULL, self::EXIT_ERROR); + $parsed = $this->parse_backend_output($this->getOutput()); + $this->assertEquals(1, $parsed['error_status']); + $this->assertArrayHasKey('DRUSH_COMMAND_INSUFFICIENT_BOOTSTRAP', $parsed['error_log']); + } + + /** + * Covers the following target responsibilities. + * - Insures that the 'Drush version' line from drush status appears in the output. + * - Insures that the backend output start marker appears in the output (this is a backend command). + * - Insures that the drush output appears before the backend output start marker (output is displayed in 'real time' as it is produced). + */ + function testRealtimeOutput() { + $exec = sprintf('%s core-status --backend --nocolor 2>&1', UNISH_DRUSH); + $this->execute($exec); + + $output = $this->getOutput(); + $drush_version_offset = strpos($output, "Drush version"); + $backend_output_offset = strpos($output, "DRUSH_BACKEND_OUTPUT_START>>>"); + + $this->assertTrue($drush_version_offset !== FALSE, "'Drush version' string appears in output."); + $this->assertTrue($backend_output_offset !== FALSE, "Drush backend output marker appears in output."); + $this->assertTrue($drush_version_offset < $backend_output_offset, "Drush version string appears in output before the backend output marker."); + } + + /** + * Covers the following target responsibilities. + * - Insures that function result is returned in --backend mode + */ + function testBackendFunctionResult() { + $php = "return 'bar'"; + $this->drush('php-eval', array($php), array('backend' => NULL)); + $parsed = $this->parse_backend_output($this->getOutput()); + // assert that $parsed has 'bar' + $this->assertEquals("'bar'", var_export($parsed['object'], TRUE)); + } + + /** + * Covers the following target responsibilities. + * - Insures that backend_set_result is returned in --backend mode + * - Insures that the result code for the function does not overwrite + * the explicitly-set value + */ + function testBackendSetResult() { + $php = "drush_backend_set_result('foo'); return 'bar'"; + $this->drush('php-eval', array($php), array('backend' => NULL)); + $parsed = $this->parse_backend_output($this->getOutput()); + // assert that $parsed has 'foo' and not 'bar' + $this->assertEquals("'foo'", var_export($parsed['object'], TRUE)); + } + + /** + * Covers the following target responsibilities. + * - Insures that the backend option 'invoke-multiple' will cause multiple commands to be executed. + * - Insures that the right number of commands run. + * - Insures that the 'concurrent'-format result array is returned. + * - Insures that correct results are returned from each command. + */ + function testBackendInvokeMultiple() { + $options = array( + 'backend' => NULL, + 'include' => dirname(__FILE__), // Find unit.drush.inc commandfile. + ); + $php = "\$values = drush_invoke_process('@none', 'unit-return-options', array('value'), array('x' => 'y', 'strict' => 0), array('invoke-multiple' => '3')); return \$values;"; + $this->drush('php-eval', array($php), $options); + $parsed = $this->parse_backend_output($this->getOutput()); + // assert that $parsed has a 'concurrent'-format output result + $this->assertEquals('concurrent', implode(',', array_keys($parsed['object']))); + // assert that the concurrent output has indexes 0, 1 and 2 (in any order) + $concurrent_indexes = array_keys($parsed['object']['concurrent']); + sort($concurrent_indexes); + $this->assertEquals('0,1,2', implode(',', $concurrent_indexes)); + foreach ($parsed['object']['concurrent'] as $index => $values) { + // assert that each result contains 'x' => 'y' and nothing else + $this->assertEquals("array ( + 'x' => 'y', +)", var_export($values['object'], TRUE)); + } + } + /** + * Covers the following target responsibilities. + * - Insures that arrays are stripped when using --backend mode's method GET + * - Insures that arrays can be returned as the function result of + * backend invoke. + */ + function testBackendMethodGet() { + $options = array( + 'backend' => NULL, + 'include' => dirname(__FILE__), // Find unit.drush.inc commandfile. + ); + $php = "\$values = drush_invoke_process('@none', 'unit-return-options', array('value'), array('x' => 'y', 'strict' => 0, 'data' => array('a' => 1, 'b' => 2)), array('method' => 'GET')); return array_key_exists('object', \$values) ? \$values['object'] : 'no result';"; + $this->drush('php-eval', array($php), $options); + $parsed = $this->parse_backend_output($this->getOutput()); + // assert that $parsed has 'x' but not 'data' + $this->assertEquals("array ( + 'x' => 'y', +)", var_export($parsed['object'], TRUE)); + } + + /** + * Covers the following target responsibilities. + * - Insures that complex arrays can be passed through when using --backend mode's method POST + * - Insures that arrays can be returned as the function result of + * backend invoke. + */ + function testBackendMethodPost() { + $options = array( + 'backend' => NULL, + 'include' => dirname(__FILE__), // Find unit.drush.inc commandfile. + ); + $php = "\$values = drush_invoke_process('@none', 'unit-return-options', array('value'), array('x' => 'y', 'strict' => 0, 'data' => array('a' => 1, 'b' => 2)), array('method' => 'POST')); return array_key_exists('object', \$values) ? \$values['object'] : 'no result';"; + $this->drush('php-eval', array($php), $options); + $parsed = $this->parse_backend_output($this->getOutput()); + // assert that $parsed has 'x' and 'data' + $this->assertEquals(array ( + 'x' => 'y', + 'data' => + array ( + 'a' => 1, + 'b' => 2, + ), +), $parsed['object']); + } + + /** + * Covers the following target responsibilities. + * - Insures that backend invoke can properly re-assemble packets + * that are split across process-read-size boundaries. + * + * This test works by repeating testBackendMethodGet(), while setting + * '#process-read-size' to a very small value, insuring that packets + * will be split. + */ + function testBackendReassembleSplitPackets() { + $options = array( + 'backend' => NULL, + 'include' => dirname(__FILE__), // Find unit.drush.inc commandfile. + ); + $min = 1; + $max = 4; + $read_sizes_to_test = array(4096); + if (in_array('--debug', $_SERVER['argv'])) { + $read_sizes_to_test[] = 128; + $read_sizes_to_test[] = 16; + $max = 16; + } + foreach ($read_sizes_to_test as $read_size) { + $log_message=""; + for ($i = $min; $i <= $max; $i++) { + $log_message .= "X"; + $php = "\$values = drush_invoke_process('@none', 'unit-return-options', array('value'), array('log-message' => '$log_message', 'x' => 'y$read_size', 'strict' => 0, 'data' => array('a' => 1, 'b' => 2)), array('method' => 'GET', '#process-read-size' => $read_size)); return array_key_exists('object', \$values) ? \$values['object'] : 'no result';"; + $this->drush('php-eval', array($php), $options); + $parsed = $this->parse_backend_output($this->getOutput()); + // assert that $parsed has 'x' but not 'data' + $all_warnings=array(); + foreach ($parsed['log'] as $log) { + if ($log['type'] == 'warning') { + $all_warnings[] = $log['message']; + } + } + $this->assertEquals("$log_message,done", implode(',', $all_warnings), 'Log reconstruction with read_size ' . $read_size); + $this->assertEquals("array ( + 'x' => 'y$read_size', +)", var_export($parsed['object'], TRUE)); + } + } + } +} diff --git a/vendor/drush/drush/tests/backendUnitTest.php b/vendor/drush/drush/tests/backendUnitTest.php new file mode 100644 index 0000000000..6d6ece0aff --- /dev/null +++ b/vendor/drush/drush/tests/backendUnitTest.php @@ -0,0 +1,38 @@ +markTestSkipped('Fork tests not a priority on Windows.'); + } + + // Ensure that file that will be created by forked process does not exist + // before invocation. + $test_file = UNISH_SANDBOX . '/fork_test.txt'; + if (file_exists($test_file)) { + unlink($test_file); + } + + // Sleep for a millisecond, then create the file + $ev_php = "usleep(1000);fopen('$test_file','a');"; + drush_invoke_process("@none", "ev", array($ev_php), array(), array("fork" => TRUE)); + + // Test file does not exist immediate after process forked + $this->assertEquals(file_exists($test_file), FALSE); + // Check every 100th of a second for up to 4 seconds to see if the file appeared + $repetitions = 400; + while (!file_exists($test_file) && ($repetitions > 0)) { + usleep(10000); + } + // Assert that the file did finally appear + $this->assertEquals(file_exists($test_file), TRUE); + } +} diff --git a/vendor/drush/drush/tests/batchTest.php b/vendor/drush/drush/tests/batchTest.php new file mode 100644 index 0000000000..3db4d768f1 --- /dev/null +++ b/vendor/drush/drush/tests/batchTest.php @@ -0,0 +1,33 @@ +setUpDrupal(1, TRUE); + $options = array( + 'root' => $this->webroot(), + 'uri' => key($sites), + 'yes' => NULL, + 'include' => dirname(__FILE__), + ); + $this->drush('unit-batch', array(), $options); + // Collect log messages that begin with "!!!" (@see: _drush_unit_batch_operation()) + $parsed = $this->parse_backend_output($this->getOutput()); + $special_log_msgs = ''; + foreach ($parsed['log'] as $key => $log) { + if(substr($log['message'],0,3) == '!!!') { + $special_log_msgs .= $log['message']; + } + } + $this->assertEquals("!!! ArrayObject does its job.", $special_log_msgs, 'Batch messages were logged'); + } +} diff --git a/vendor/drush/drush/tests/bootstrap.inc b/vendor/drush/drush/tests/bootstrap.inc new file mode 100644 index 0000000000..2e3cbda127 --- /dev/null +++ b/vendor/drush/drush/tests/bootstrap.inc @@ -0,0 +1,198 @@ + 2) && ($unish_drupal_major >= 8)) { + $unish_drupal_minor = $matches[2]; + } + } + } + if (getenv('UNISH_DRUPAL_MAJOR_VERSION')) { + $unish_drupal_major = getenv('UNISH_DRUPAL_MAJOR_VERSION'); + } + elseif (isset($GLOBALS['UNISH_DRUPAL_MAJOR_VERSION'])) { + $unish_drupal_major = $GLOBALS['UNISH_DRUPAL_MAJOR_VERSION']; + } + define('UNISH_DRUPAL_MAJOR_VERSION', $unish_drupal_major); + if ($unish_drupal_major >= 8) { + if (getenv('UNISH_DRUPAL_MINOR_VERSION')) { + $unish_drupal_minor = '.' . getenv('UNISH_DRUPAL_MINOR_VERSION'); + } + elseif (isset($GLOBALS['UNISH_DRUPAL_MINOR_VERSION'])) { + $unish_drupal_minor = '.' . $GLOBALS['UNISH_DRUPAL_MINOR_VERSION']; + } + } + define('UNISH_DRUPAL_MINOR_VERSION', $unish_drupal_minor); + + // We read from env then globals then default to mysql. + $unish_db_url = 'mysql://root:@127.0.0.1'; + if (getenv('UNISH_DB_URL')) { + $unish_db_url = getenv('UNISH_DB_URL'); + } + elseif (isset($GLOBALS['UNISH_DB_URL'])) { + $unish_db_url = $GLOBALS['UNISH_DB_URL']; + } + if (UNISH_DRUPAL_MAJOR_VERSION == '6') { + $unish_db_url = preg_replace('#^mysql://#', 'mysqli://', $unish_db_url); + } + define('UNISH_DB_URL', $unish_db_url); + + // UNISH_DRUSH value can come from phpunit.xml. + if (!defined('UNISH_DRUSH')) { + // Let the UNISH_DRUSH environment variable override if set. + $unish_drush = isset($_SERVER['UNISH_DRUSH']) ? $_SERVER['UNISH_DRUSH'] : NULL; + $unish_drush = isset($GLOBALS['UNISH_DRUSH']) ? $GLOBALS['UNISH_DRUSH'] : $unish_drush; + if (empty($unish_drush)) { + $script = \Unish\UnitUnishTestCase::is_windows() ? 'dr.bat' : 'drush'; + $unish_drush = dirname(__DIR__) . DIRECTORY_SEPARATOR . $script; + } + define('UNISH_DRUSH', $unish_drush); + } + + define('UNISH_TMP', realpath(getenv('UNISH_TMP') ? getenv('UNISH_TMP') : (isset($GLOBALS['UNISH_TMP']) ? $GLOBALS['UNISH_TMP'] : sys_get_temp_dir()))); + define('UNISH_SANDBOX', UNISH_TMP . DIRECTORY_SEPARATOR . 'drush-sandbox'); + + // Cache dir lives outside the sandbox so that we get persistence across classes. + define('UNISH_CACHE', UNISH_TMP . DIRECTORY_SEPARATOR . 'drush-cache'); + putenv("CACHE_PREFIX=" . UNISH_CACHE); + // Wipe at beginning of run. + if (file_exists(UNISH_CACHE)) { + // TODO: We no longer clean up cache dir between runs. Much faster, but we + // we should watch for subtle problems. To manually clean up, delete the + // UNISH_TMP/drush-cache directory. + // unish_file_delete_recursive($cache, TRUE); + } + else { + $ret = mkdir(UNISH_CACHE, 0777, TRUE); + } + + $home = UNISH_SANDBOX . DIRECTORY_SEPARATOR . 'home'; + putenv("HOME=$home"); + putenv("HOMEDRIVE=$home"); + + putenv('ETC_PREFIX=' . UNISH_SANDBOX); + putenv('SHARE_PREFIX=' . UNISH_SANDBOX); + putenv('TEMP=' . UNISH_TMP); + + define('UNISH_USERGROUP', isset($GLOBALS['UNISH_USERGROUP']) ? $GLOBALS['UNISH_USERGROUP'] : NULL); + + define('UNISH_BACKEND_OUTPUT_DELIMITER', 'DRUSH_BACKEND_OUTPUT_START>>>%s<<getSites()) { + $this->setUpDrupal(1, TRUE); + } + } + + function testCacheGet() { + $options = $this->getOptions(); + // Test the cache get command. + $inputs = array( + 6 => array('variables', NULL), + 7 => array('schema', NULL), + 8 => array('system.date', 'config'), + 9 => array('system.date', 'config'), + ); + list($key, $bin) = $inputs[UNISH_DRUPAL_MAJOR_VERSION]; + $this->drush('cache-get', [$key, $bin], $options + array('format' => 'json')); + $schema = $this->getOutputFromJSON('data'); + $this->assertNotEmpty($schema); + + // Test that get-ing a non-existant cid fails. + $this->drush('cache-get', array('test-failure-cid'), $options + array('format' => 'json'), NULL, NULL, self::EXIT_ERROR); + } + + function testCacheSet() { + $options = $this->getOptions(); + // Test setting a new cache item. + $expected = 'cache test string'; + $this->drush('cache-set', array('cache-test-cid', $expected), $options); + $this->drush('cache-get', array('cache-test-cid'), $options + array('format' => 'json')); + $data = $this->getOutputFromJSON('data'); + $this->assertEquals($expected, $data); + + // Test cache-set using all arguments and many options. + $expected = array('key' => 'value'); + $input = array('data'=> $expected); + $stdin = json_encode($input); + $bin = UNISH_DRUPAL_MAJOR_VERSION >= 8 ? 'default' : 'cache'; + $exec = sprintf('%s cache-set %s %s my_cache_id - %s CACHE_PERMANENT --format=json --cache-get 2>%s', UNISH_DRUSH, "--root=" . self::escapeshellarg($options['root']), '--uri=' . $options['uri'], $bin, $this->bit_bucket()); + $return = $this->execute($exec, self::EXIT_SUCCESS, NULL, [], $stdin); + $this->drush('cache-get', array('my_cache_id'), $options + array('format' => 'json')); + $data = $this->getOutputFromJSON('data'); + $this->assertEquals((object)$expected, $data); + } + + function testCacheRebuild() { + $options = $this->getOptions(); + // Test cache-clear all and cache-rebuild (D8+). + if (UNISH_DRUPAL_MAJOR_VERSION >= 8) { + $this->drush('cache-rebuild', array(), $options); + } + else { + $this->drush('cache-clear', array('all'), $options); + } + $result = $this->drush('cache-get', array('cache-test-cid'), $options + array('format' => 'json'), NULL, NULL, self::EXIT_ERROR); + $this->assertEquals(self::EXIT_ERROR, $result); + } + + function getOptions() { + return array( + 'yes' => NULL, + 'root' => $this->webroot(), + 'uri' => key($this->getSites()), + ); + } +} diff --git a/vendor/drush/drush/tests/commandSpecificTest.php b/vendor/drush/drush/tests/commandSpecificTest.php new file mode 100644 index 0000000000..89897cce10 --- /dev/null +++ b/vendor/drush/drush/tests/commandSpecificTest.php @@ -0,0 +1,84 @@ + UNISH_SANDBOX, + 'uri' => 'site1.com', + 'source-command-specific' => array( + 'core-rsync' => array( + 'exclude-paths' => 'excluded_by_source', + ), + ), + 'target-command-specific' => array( + 'core-rsync' => array( + 'exclude-paths' => 'excluded_by_target', + ), + ), + 'path-aliases' => array( + '%files' => 'sites/default/files', + ), + ); + $contents = $this->unish_file_aliases($aliases); + $return = file_put_contents($path, $contents); + } + + function testCommandSpecific() { + $options = array( + 'alias-path' => UNISH_SANDBOX, + 'simulate' => NULL, + 'include-vcs' => NULL, + ); + $this->drush('core-rsync', array('/tmp', '@site1'), $options, NULL, NULL, self::EXIT_SUCCESS, '2>&1'); + $output = trim($this->getOutput()); + $this->assertStringContainsString('excluded_by_target', $output); + $this->drush('core-rsync', array('@site1', '/tmp'), $options, NULL, NULL, self::EXIT_SUCCESS, '2>&1'); + $output = trim($this->getOutput()); + $this->assertStringContainsString('excluded_by_source', $output); + $this->drush('core-rsync', array('@site1', '@site1'), $options, NULL, NULL, self::EXIT_SUCCESS, '2>&1'); + $output = trim($this->getOutput()); + $this->assertStringContainsString('excluded_by_target', $output); + // Now do that all again with 'exclude-files' + $options['exclude-files'] = NULL; + $this->drush('core-rsync', array('/tmp', '@site1'), $options, NULL, NULL, self::EXIT_SUCCESS, '2>&1'); + $output = trim($this->getOutput()); + $this->assertStringContainsString('sites/default/files', $output); + $this->assertStringContainsString('excluded_by_target', $output); + $this->assertStringNotContainsString('include-vcs', $output); + $this->assertStringNotContainsString('exclude-paths', $output); + $this->assertStringNotContainsString('exclude-files-processed', $output); + $this->drush('core-rsync', array('@site1', '/tmp'), $options, NULL, NULL, self::EXIT_SUCCESS, '2>&1'); + $output = trim($this->getOutput()); + $this->assertStringContainsString('sites/default/files', $output); +// This one does not work. @see drush_sitealias_evaluate_path +// $this->assertStringContainsString('excluded_by_source', $output); + $this->assertStringNotContainsString('include-vcs', $output); + $this->assertStringNotContainsString('exclude-paths', $output); + $this->assertStringNotContainsString('exclude-files-processed', $output); + $this->drush('core-rsync', array('@site1', '@site1'), $options, NULL, NULL, self::EXIT_SUCCESS, '2>&1'); + $output = trim($this->getOutput()); + $this->assertStringContainsString('sites/default/files', $output); + $this->assertStringContainsString('excluded_by_target', $output); + $this->assertStringNotContainsString('include-vcs', $output); + $this->assertStringNotContainsString('exclude-paths', $output); + $this->assertStringNotContainsString('exclude-files-processed', $output); + } +} diff --git a/vendor/drush/drush/tests/commandTest.php b/vendor/drush/drush/tests/commandTest.php new file mode 100644 index 0000000000..bcadd7f88a --- /dev/null +++ b/vendor/drush/drush/tests/commandTest.php @@ -0,0 +1,110 @@ + dirname(__FILE__), + ); + $this->drush('unit-invoke', array(), $options, NULL, NULL, self::EXIT_ERROR); + $called = $this->getOutputFromJSON(); + $this->assertSame($expected, $called); + } + + /** + * Assert that minimum bootstrap phase is honored. + * + * Not testing dependency on a module since that requires an installed Drupal. + * Too slow for little benefit. + */ + public function testRequirementBootstrapPhase() { + // Assure that core-cron fails when run outside of a Drupal site. + $return = $this->drush('core-cron', array(), array('quiet' => NULL), NULL, NULL, self::EXIT_ERROR); + $this->assertEquals(self::EXIT_ERROR, $return); + } + + /** + * Assert that unknown options are caught and flagged as errors + */ + public function testUnknownOptions() { + // Make sure an ordinary 'version' command works + $return = $this->drush('version', array(), array('pipe' => NULL)); + $this->assertEquals(self::EXIT_SUCCESS, $return); + // Add an unknown option --magic=1234 and insure it fails + $return = $this->drush('version', array(), array('pipe' => NULL, 'magic' => 1234), NULL, NULL, self::EXIT_ERROR); + $this->assertEquals(self::EXIT_ERROR, $return); + // Finally, add in a hook that uses hook_drush_help_alter to allow the 'magic' option. + // We need to run 'drush cc drush' to clear the commandfile cache; otherwise, our include will not be found. + $include_path = dirname(__FILE__) . '/hooks/magic_help_alter'; + $return = $this->drush('version', array(), array('include' => $include_path, 'pipe' => NULL, 'magic' => '1234', 'strict' => NULL)); + $this->assertEquals(self::EXIT_SUCCESS, $return); + } + + /** + * Assert that errors are thrown for commands with missing callbacks. + */ + public function testMissingCommandCallback() { + $options = array( + 'include' => dirname(__FILE__), // Find unit.drush.inc commandfile. + //'show-invoke' => TRUE, + ); + $return = $this->drush('missing-callback', array(), $options, NULL, NULL, self::EXIT_ERROR); + $this->assertEquals(self::EXIT_ERROR, $return); + } + + /** + * Assert that commands depending on unknown commandfiles are detected. + */ + public function testMissingDrushDependency() { + $options = array( + 'include' => dirname(__FILE__), // Find unit.drush.inc commandfile. + 'backend' => NULL, // To obtain and parse the error log. + ); + $this->drush('unit-drush-dependency', array(), $options, NULL, NULL, self::EXIT_ERROR); + $parsed = $this->parse_backend_output($this->getOutput()); + $this->assertArrayHasKey("DRUSH_COMMANDFILE_DEPENDENCY_ERROR", $parsed['error_log']); + } + + /** + * Assert that commands in disabled/uninstalled modules throw an error. + */ + public function testDisabledModule() { + // TODO: enable test when devel available for Drupal 9 + if (UNISH_DRUPAL_MAJOR_VERSION >= 9) { + $this->markTestSkipped('Drupal 9 version of devel not available yet.'); + } + $sites = $this->setUpDrupal(1, TRUE); + $uri = key($sites); + $root = $this->webroot(); + $options = array( + 'root' => $root, + 'uri' => $uri, + 'cache' => NULL, + ); + $this->drush('pm-download', array('devel'), $options); + $options += array( + 'backend' => NULL, // To obtain and parse the error log. + ); + // Assert that this has an error. + $result = $this->drush('devel-reinstall', array(), $options, NULL, NULL, self::EXIT_ERROR); + $this->assertEquals(self::EXIT_ERROR, $result); + } +} diff --git a/vendor/drush/drush/tests/commandUnitTest.php b/vendor/drush/drush/tests/commandUnitTest.php new file mode 100644 index 0000000000..47e70289a7 --- /dev/null +++ b/vendor/drush/drush/tests/commandUnitTest.php @@ -0,0 +1,64 @@ +drush_major_version(); + $major_plus1 = $major + 1; + + // Write matched and unmatched files to the system search path. + $files = array( + Path::join($path, "$major.drush$major.inc"), + Path::join($path, "drush$major/drush$major.drush.inc"), + Path::join($path, "$major_plus1.drush$major_plus1.inc"), + Path::join($path, "drush$major_plus1/drush$major_plus1.drush.inc"), + ); + $this->mkdir(Path::join($path, 'drush'. $major)); + $this->mkdir(Path::join($path, 'drush'. $major_plus1)); + foreach ($files as $file) { + $contents = <<assertContains($files[0], $loaded); //Loaded a version-specific command file. + $this->assertContains($files[1], $loaded); //Loaded a version-specific command directory. + $this->assertNotContains($files[2], $loaded); //Did not load a mismatched version-specific command file. + $this->assertNotContains($files[3], $loaded); //Did not load a a mismatched version-specific command directory. + } + + /** + * Assert that $command has interesting properties. Reference command by + * it's alias (dl) to assure that those aliases are built as expected. + */ + public function testGetCommands() { + drush_preflight(); + $commands = drush_get_commands(); + $command = $commands['dl']; + + $this->assertEquals('dl', current($command['aliases'])); + $this->assertArrayHasKey('version_control', $command['engines']); + $this->assertArrayHasKey('package_handler', $command['engines']); + $this->assertArrayHasKey('release_info', $command['engines']); + $this->assertEquals('pm-download', $command['command']); + $this->assertEquals('pm', $command['commandfile']); + $this->assertEquals('drush_command', $command['callback']); + $this->assertArrayHasKey('examples', $command['sections']); + $this->assertTrue($command['is_alias']); + + $command = $commands['pm-download']; + $this->assertArrayNotHasKey('is_alias', $command); + } +} diff --git a/vendor/drush/drush/tests/completeTest.php b/vendor/drush/drush/tests/completeTest.php new file mode 100644 index 0000000000..2de37168df --- /dev/null +++ b/vendor/drush/drush/tests/completeTest.php @@ -0,0 +1,213 @@ + 'pm-uninstall', + ); + "; + file_put_contents(UNISH_SANDBOX . '/drushrc.php', trim($contents)); + } + + + + public function testComplete() { + if ($this->is_windows()) { + $this->markTestSkipped('Complete tests not fully working nor needed on Windows.'); + } + + $this->markTestSkipped("This test was working on TravisCI. A pull request to fix it on CircleCI would be welcome."); + + // We copy our completetest commandfile into our path. + // We cannot use --include since complete deliberately avoids drush + // command dispatch. + copy(dirname(__FILE__) . '/completetest.drush.inc', getenv('HOME') . '/.drush/completetest.drush.inc'); + + $sites = $this->setUpDrupal(2); + $env = key($sites); + $root = $this->webroot(); + // We copy the unit test command into (only) our dev site, so we have a + // detectable difference we can use to detect cache correctness between + // sites. + mkdir("$root/sites/$env/modules"); + copy(dirname(__FILE__) . '/completetestsite.drush.inc', "$root/sites/$env/modules/completetestsite.drush.inc"); + // Clear the cache, so it finds our test command. + $this->drush('php-eval', array('drush_cache_clear_all();'), array(), '@' . $env); + + // Create a sample directory and file to test file/directory completion. + mkdir("astronaut"); + mkdir("asteroid"); + mkdir("asteroid/ceres"); + mkdir("asteroid/chiron"); + touch('astronaut/aldrin.tar.gz'); + touch('astronaut/armstrong.tar.gz'); + touch('astronaut/yuri gagarin.tar.gz'); + touch('zodiac.tar.gz'); + touch('zodiac.txt'); + + // Create directory for temporary debug logs. + mkdir(UNISH_SANDBOX . '/complete-debug'); + + // Test cache clearing for global cache, which should affect all + // environments. First clear the cache: + $this->drush('php-eval', array('drush_complete_cache_clear();')); + // Confirm we get cache rebuilds for runs both in and out of a site + // which is expected since these should resolve to separate cache IDs. + $this->verifyComplete('@dev aaaaaaaard-', 'aaaaaaaard-ant', 'aaaaaaaard-zebra', FALSE); + $this->verifyComplete('aaaaaaaard-', 'aaaaaaaard-ant', 'aaaaaaaard-wolf', FALSE); + // Next, rerun and check results to confirm cache IDs are generated + // correctly on our fast bootstrap when returning the cached result. + $this->verifyComplete('@dev aaaaaaaard-', 'aaaaaaaard-ant', 'aaaaaaaard-zebra'); + $this->verifyComplete('aaaaaaaard-', 'aaaaaaaard-ant', 'aaaaaaaard-wolf'); + + // Test cache clearing for a completion type, which should be effective only + // for current environment - i.e. a specific site should not be effected. + $this->drush('php-eval', array('drush_complete_cache_clear("command-names");')); + $this->verifyComplete('@dev aaaaaaaard-', 'aaaaaaaard-ant', 'aaaaaaaard-zebra'); + $this->verifyComplete('aaaaaaaard-', 'aaaaaaaard-ant', 'aaaaaaaard-wolf', FALSE); + + // Test cache clearing for a command specific completion type, which should + // be effective only for current environment. Prime caches first. + $this->verifyComplete('@dev aaaaaaaard a', 'aardvark', 'aardwolf', FALSE); + $this->verifyComplete('aaaaaaaard a', 'aardvark', 'aardwolf', FALSE); + $this->drush('php-eval', array('drush_complete_cache_clear("arguments", "aaaaaaaard");')); + // We cleared the global cache for this argument, not the site specific + // cache should still exist. + $this->verifyComplete('@dev aaaaaaaard a', 'aardvark', 'aardwolf'); + $this->verifyComplete('aaaaaaaard a', 'aardvark', 'aardwolf', FALSE); + + // Test overall context sensitivity - almost all of these are cache hits. + // No context (i.e. "drush "), should list aliases and commands. + $this->verifyComplete('""', '@dev', 'zzzzzzzzebra'); + // Site alias alone. + $this->verifyComplete('@', '@dev', '@stage'); + // Command alone. + $this->verifyComplete('aaaaaaaa', 'aaaaaaaard', 'aaaaaaaard-wolf'); + // Command with single result. + $this->verifyComplete('aaaaaaaard-v', 'aaaaaaaard-vark', 'aaaaaaaard-vark'); + // Command with no results should produce no output. + $this->verifyComplete('dont-name-a-command-like-this', '', ''); + // Commands that start the same as another command (i.e. aaaaaaaard is a + // valid command, but we should still list aaaaaaaardwolf when completing on + // "aaaaaaaard"). + $this->verifyComplete('@dev aaaaaaaard', 'aaaaaaaard', 'aaaaaaaard-zebra'); + // Global option alone. + $this->verifyComplete('--n', '--no', '--notify-audio'); + // Site alias + command. + $this->verifyComplete('@dev aaaaaaaa', 'aaaaaaaard', 'aaaaaaaard-zebra'); + // Site alias + command, should allow no further site aliases or commands. + $this->verifyComplete('@dev aaaaaaaard-wolf @', '', '', FALSE); + $this->verifyComplete('@dev aaaaaaaard-wolf aaaaaaaa', '', ''); + // Command + command option. + $this->verifyComplete('aaaaaaaard --', '--ears', '--nose'); + // Site alias + command + command option. + $this->verifyComplete('@dev aaaaaaaard --', '--ears', '--nose'); + // Command + all arguments + $this->verifyComplete('aaaaaaaard ""', 'aardvark', 'zebra'); + // Command + argument. + $this->verifyComplete('aaaaaaaard a', 'aardvark', 'aardwolf'); + // Site alias + command + regular argument. + // Note: this is checked implicitly by the argument cache testing above. + + // Site alias + command + file/directory argument tests. + // Current directory substrings. + // NOTE: This command arg has not been used yet, so cache miss is expected. + $this->verifyComplete('archive-restore ""', 'asteroid/', 'zodiac.tar.gz', FALSE); + $this->verifyComplete('archive-restore a', 'asteroid/', 'astronaut/'); + $this->verifyComplete('archive-restore ast', 'asteroid/', 'astronaut/'); + $this->verifyComplete('archive-restore aste', 'asteroid/', 'asteroid/'); + $this->verifyComplete('archive-restore asteroid', 'asteroid/', 'asteroid/'); + $this->verifyComplete('archive-restore asteroid/', 'ceres', 'chiron'); + $this->verifyComplete('archive-restore asteroid/ch', 'asteroid/chiron/', 'asteroid/chiron/'); + $this->verifyComplete('archive-restore astronaut/', 'aldrin.tar.gz', 'yuri gagarin.tar.gz'); + $this->verifyComplete('archive-restore astronaut/y', 'astronaut/yuri\ gagarin.tar.gz', 'astronaut/yuri\ gagarin.tar.gz'); + // Leading dot style current directory substrings. + $this->verifyComplete('archive-restore .', './asteroid/', './zodiac.tar.gz'); + $this->verifyComplete('archive-restore ./', './asteroid/', './zodiac.tar.gz'); + $this->verifyComplete('archive-restore ./a', './asteroid/', './astronaut/'); + $this->verifyComplete('archive-restore ./ast', './asteroid/', './astronaut/'); + $this->verifyComplete('archive-restore ./aste', './asteroid/', './asteroid/'); + $this->verifyComplete('archive-restore ./asteroid', './asteroid/', './asteroid/'); + $this->verifyComplete('archive-restore ./asteroid/', 'ceres', 'chiron'); + $this->verifyComplete('archive-restore ./asteroid/ch', './asteroid/chiron/', './asteroid/chiron/'); + $this->verifyComplete('archive-restore ./astronaut/', 'aldrin.tar.gz', 'yuri gagarin.tar.gz'); + $this->verifyComplete('archive-restore ./astronaut/y', './astronaut/yuri\ gagarin.tar.gz', './astronaut/yuri\ gagarin.tar.gz'); + // Absolute path substrings. + $path = getcwd(); + $this->verifyComplete('archive-restore ' . $path, $path . '/', $path . '/'); + $this->verifyComplete('archive-restore ' . $path . '/', 'asteroid', 'zodiac.tar.gz'); + $this->verifyComplete('archive-restore ' . $path . '/a', $path . '/asteroid', $path . '/astronaut'); + $this->verifyComplete('archive-restore ' . $path . '/ast', 'asteroid', 'astronaut'); + $this->verifyComplete('archive-restore ' . $path . '/aste', $path . '/asteroid/', $path . '/asteroid/'); + $this->verifyComplete('archive-restore ' . $path . '/asteroid', $path . '/asteroid/', $path . '/asteroid/'); + $this->verifyComplete('archive-restore ' . $path . '/asteroid/', $path . '/asteroid/ceres', $path . '/asteroid/chiron'); + $this->verifyComplete('archive-restore ' . $path . '/asteroid/ch', $path . '/asteroid/chiron/', $path . '/asteroid/chiron/'); + $this->verifyComplete('archive-restore ' . $path . '/astronaut/', 'aldrin.tar.gz', 'yuri gagarin.tar.gz'); + $this->verifyComplete('archive-restore ' . $path . '/astronaut/y', $path . '/astronaut/yuri\ gagarin.tar.gz', $path . '/astronaut/yuri\ gagarin.tar.gz'); + // Absolute via parent path substrings. + $this->verifyComplete('archive-restore ' . $path . '/asteroid/../astronaut/', 'aldrin.tar.gz', 'yuri gagarin.tar.gz'); + $this->verifyComplete('archive-restore ' . $path . '/asteroid/../astronaut/y', $path . '/asteroid/../astronaut/yuri\ gagarin.tar.gz', $path . '/asteroid/../astronaut/yuri\ gagarin.tar.gz'); + // Parent directory path substrings. + chdir('asteroid/chiron'); + $this->verifyComplete('archive-restore ../../astronaut/', 'aldrin.tar.gz', 'yuri gagarin.tar.gz'); + $this->verifyComplete('archive-restore ../../astronaut/y', '../../astronaut/yuri\ gagarin.tar.gz', '../../astronaut/yuri\ gagarin.tar.gz'); + chdir($path); + } + + /** + * Helper function to call completion and make common checks. + * + * @param $command + * The command line to attempt to complete. + * @param $first + * String indicating the expected first completion suggestion. + * @param $last + * String indicating the expected last completion suggestion. + * @param bool $cache_hit + * Optional parameter, if TRUE or omitted the debug log is checked to + * ensure a cache hit on the last cache debug log entry, if FALSE then a + * cache miss is checked for. + */ + function verifyComplete($command, $first, $last, $cache_hit = TRUE) { + // We capture debug output to a separate file, so we can check for cache + // hits/misses. + $debug_file = tempnam(UNISH_SANDBOX . '/complete-debug', 'complete-debug'); + // Commands should take the format: + // drush --early=includes/complete.inc [--complete-debug] drush [@alias] [command]... + $exec = sprintf('%s --early=includes/complete.inc --config=%s --complete-debug %s %s 2> %s', UNISH_DRUSH, UNISH_SANDBOX . '/drushrc.php', UNISH_DRUSH, $command, $debug_file); + $this->execute($exec); + $result = $this->getOutputAsList(); + $actual = reset($result); + $this->assertEquals("$command: (f) $first", "$command: (f) $actual"); + $actual = end($result); + $this->assertEquals("$command: (l) $last", "$command: (l) $actual"); + // If checking for HIT, we ensure no MISS exists, if checking for MISS we + // ensure no HIT exists. However, we exclude the first cache report, since + // it is expected that the command-names cache (loaded when matching + // command names) may sometimes be a HIT even when we are testing for a MISS + // in the actual cache we are loading to complete against. + $check_not_exist = 'HIT'; + if ($cache_hit) { + $check_not_exist = 'MISS'; + } + $contents = file_get_contents($debug_file); + // Find the all cache messages of type "-complete-" + preg_match_all("/Cache [A-Z]* cid:.*-complete-/", $contents, $matches); + $contents = implode("\n", $matches[0]); + $first_cache_pos = strpos($contents, 'Cache ') + 6; + $this->assertFalse(strpos($contents, 'Cache ' . $check_not_exist . ' cid', $first_cache_pos)); + unlink($debug_file); + } +} diff --git a/vendor/drush/drush/tests/completetest.drush.inc b/vendor/drush/drush/tests/completetest.drush.inc new file mode 100644 index 0000000000..deb7127bcc --- /dev/null +++ b/vendor/drush/drush/tests/completetest.drush.inc @@ -0,0 +1,55 @@ + 'No-op command, used to test various completions for commands that start the same as other commands.', + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'callback' => 'drush_completetest_noop', + 'command-hook' => 'completetest_noop', + ); + } + $items['aaaaaaaard']['arguments'] = array('name' => 'Name'); + $items['aaaaaaaard']['options'] = array( + 'ears' => 'Ears', + 'eyes' => 'Eyes', + 'nose' => 'Nose', + 'legs' => 'Legs', + ); + return $items; +} + +function drush_completetest_noop() { + // No-op. +} + +/** + * Command argument complete callback. + * + * @return + * Array of completions. + */ +function completetest_completetest_noop_complete() { + return array( + 'values' => array( + 'aardvark', + 'aardwolf', + 'zebra', + ), + ); +} diff --git a/vendor/drush/drush/tests/completetestsite.drush.inc b/vendor/drush/drush/tests/completetestsite.drush.inc new file mode 100644 index 0000000000..8fd224cfb3 --- /dev/null +++ b/vendor/drush/drush/tests/completetestsite.drush.inc @@ -0,0 +1,18 @@ + 'No-op command, used to test various completions for commands that start the same as other commands.', + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'callback' => 'drush_completetest_noop', + ); + return $items; +} + diff --git a/vendor/drush/drush/tests/configPulltest.php b/vendor/drush/drush/tests/configPulltest.php new file mode 100644 index 0000000000..c7283b75d1 --- /dev/null +++ b/vendor/drush/drush/tests/configPulltest.php @@ -0,0 +1,34 @@ +markTestSkipped('Config only available on D8+.'); + } + + $this->setUpDrupal(2, TRUE); + } + + /* + * Make sure a change propogates using config-pull+config-import. + */ + function testConfigPull() { + list($source, $destination) = array_keys($this->getSites()); + $source = "@$source"; + $destination = "@$destination"; + $this->drush('config-set', array('system.site', 'name', 'testConfigPull'), array('yes' => NULL), $source); + $this->drush('config-pull', array($source, $destination), array()); + $this->drush('config-import', array(), array(), $destination); + $this->drush('config-get', array('system.site', 'name'), array(), $source); + $this->assertEquals("'system.site:name': testConfigPull", $this->getOutput(), 'Config was successfully pulled.'); + } +} diff --git a/vendor/drush/drush/tests/configTest.php b/vendor/drush/drush/tests/configTest.php new file mode 100644 index 0000000000..bb74f58a7b --- /dev/null +++ b/vendor/drush/drush/tests/configTest.php @@ -0,0 +1,88 @@ +markTestSkipped('Config only available on D8+.'); + } + + if (!$this->getSites()) { + $this->setUpDrupal(1, TRUE); + $this->drush('pm-enable', array('config'), $this->options()); + } + } + + function testConfigGetSet() { + $options = $this->options(); + $this->drush('config-set', array('system.site', 'name', 'config_test'), $options); + $this->drush('config-get', array('system.site', 'name'), $options); + $this->assertEquals("'system.site:name': config_test", $this->getOutput(), 'Config was successfully set and get.'); + } + + function testConfigList() { + $options = $this->options(); + $this->drush('config-list', array(), $options); + $result = $this->getOutputAsList(); + $this->assertNotEmpty($result, 'An array of config names was returned.'); + $this->assertTrue(in_array('update.settings', $result), 'update.settings name found in the config names.'); + + $this->drush('config-list', array('system'), $options); + $result = $this->getOutputAsList(); + $this->assertTrue(in_array('system.site', $result), 'system.site found in list of config names with "system" prefix.'); + + $this->drush('config-list', array('system'), $options + array('format' => 'json')); + $result = $this->getOutputFromJSON(); + $this->assertNotEmpty($result, 'Valid, non-empty JSON output was returned.'); + } + + function testConfigExportImport() { + $options = $this->options(); + // Get path to sync dir. + $this->drush('core-status', array('config-sync'), $options + array('format' => 'json')); + $sync_relative_path = $this->getOutputFromJSON('config-sync'); + $this->assertNotEmpty($sync_relative_path); + $sync = $this->webroot() . '/' . $sync_relative_path; + $system_site_yml = $sync . '/system.site.yml'; + $core_extension_yml = $sync . '/core.extension.yml'; + + // Test export + $this->drush('config-export', array(), $options); + $this->assertFileExists($system_site_yml); + + // Test import by finishing the round trip. + $contents = file_get_contents($system_site_yml); + $contents = preg_replace('/front: .*/', 'front: unish', $contents); + $contents = file_put_contents($system_site_yml, $contents); + $this->drush('config-import', array(), $options); + $this->drush('config-get', array('system.site', 'page'), $options + array('format' => 'json')); + $page = $this->getOutputFromJSON('system.site:page'); + $this->assertStringContainsString('unish', $page->front, 'Config was successfully imported.'); + + // Similar, but this time via --partial option. + $contents = file_get_contents($system_site_yml); + $contents = preg_replace('/front: .*/', 'front: unish partial', $contents); + $partial_path = UNISH_SANDBOX . '/partial'; + mkdir($partial_path); + $contents = file_put_contents($partial_path. '/system.site.yml', $contents); + $this->drush('config-import', array(), $options + array('partial' => NULL, 'source' => $partial_path)); + $this->drush('config-get', array('system.site', 'page'), $options + array('format' => 'json')); + $page = $this->getOutputFromJSON('system.site:page'); + $this->assertStringContainsString('unish partial', $page->front, '--partial was successfully imported.'); + } + + function options() { + return array( + 'yes' => NULL, + 'root' => $this->webroot(), + 'uri' => key($this->getSites()), + ); + } +} diff --git a/vendor/drush/drush/tests/contextTest.php b/vendor/drush/drush/tests/contextTest.php new file mode 100644 index 0000000000..560c4f5a51 --- /dev/null +++ b/vendor/drush/drush/tests/contextTest.php @@ -0,0 +1,182 @@ +log("webroot: " . $this->webroot() . "\n", 'warning'); + $this->env = key($this->getSites()); + $this->site = $this->webroot() . '/sites/' . $this->env; + $this->home = UNISH_SANDBOX . '/home'; + $this->paths = array( + 'custom' => UNISH_SANDBOX, + 'site' => $this->site, + 'drupal' => $this->webroot() . '/sites/all/drush', + 'drupal-parent' => dirname($this->webroot()) . '/drush', + 'user' => $this->home, + 'home.drush' => $this->home . '/.drush', + 'system' => UNISH_SANDBOX . '/etc/drush', + // We don't want to write a file into drush dir since it is not in the sandbox. + // 'drush' => dirname(realpath(UNISH_DRUSH)), + ); + // Run each path through realpath() since the paths we'll compare against + // will have already run through drush_load_config_file(). + foreach ($this->paths as $key => $path) { + @mkdir($path); + $this->paths[$key] = realpath($path); + } + } + + /** + * Try to write a tiny drushrc.php to each place that Drush checks. Also + * write a sites/dev/aliases.drushrc.php file to the sandbox. + */ + function set_up() { + parent::set_up(); + + if (!$this->getSites()) { + $this->setUpDrupal(); + } + $this->setUpPaths(); + + // These files are only written to sandbox so get automatically cleaned up. + foreach ($this->paths as $key => $path) { + $contents = <<written[] = $this->convert_path($path); + } + } + + // Also write a site alias so we can test its supremacy in context hierarchy. + $path = $this->webroot() . '/sites/' . $this->env . '/aliases.drushrc.php'; + $aliases['contextAlias'] = array( + 'contextConfig' => 'alias1', + 'command-specific' => array ( + 'unit-eval' => array ( + 'contextConfig' => 'alias-specific', + ), + ), + ); + $contents = $this->unish_file_aliases($aliases); + $return = file_put_contents($path, $contents); + } + + /** + * Assure that all possible config files get loaded. + */ + function testConfigSearchPaths() { + $options = array( + 'pipe' => NULL, + 'config' => UNISH_SANDBOX, + 'root' => $this->webroot(), + 'uri' => key($this->getSites()) + ); + $this->drush('core-status', array('Drush configuration'), $options); + $loaded = $this->getOutputFromJSON('drush-conf'); + $loaded = array_map(array(&$this, 'convert_path'), $loaded); + $this->assertSame($this->written, $loaded); + } + + /** + * Assure that matching version-specific config files are loaded and others are ignored. + */ + function testConfigVersionSpecific() { + $major = $this->drush_major_version(); + // Arbitrarily choose the system search path. + $path = realpath(UNISH_SANDBOX . '/etc/drush'); + $contents = <<drush('core-status', array('Drush configuration'), array('pipe' => NULL)); + $loaded = $this->getOutputFromJSON('drush-conf'); + // Next 2 lines needed for Windows compatibility. + $loaded = array_map(array(&$this, 'convert_path'), $loaded); + $files = array_map(array(&$this, 'convert_path'), $files); + $this->assertTrue(in_array($files[0], $loaded), 'Loaded a version-specific config file.'); + $this->assertFalse(in_array($files[1], $loaded), 'Did not load a mismatched version-specific config file.'); + } + + /** + * Assure that options are loaded into right context and hierarchy is + * respected by drush_get_option(). + * + * Stdin context not exercised here. See backendCase::testTarget(). + */ + function testContextHierarchy() { + // The 'custom' config file has higher priority than cli and regular config files. + $eval = '$contextConfig = drush_get_option("contextConfig", "n/a");'; + $eval .= '$cli1 = drush_get_option("cli1");'; + $eval .= 'print json_encode(get_defined_vars());'; + $config = UNISH_SANDBOX . '/drushrc.php'; + $options = array( + 'cli1' => NULL, + 'config' => $config, + 'root' => $this->webroot(), + 'uri' => key($this->getSites()), + ); + $this->drush('php-eval', array($eval), $options); + $output = $this->getOutput(); + $actuals = json_decode(trim($output)); + $this->assertEquals('custom', $actuals->contextConfig); + $this->assertTrue($actuals->cli1); + + // Site alias trumps 'custom'. + $eval = '$contextConfig = drush_get_option("contextConfig", "n/a");'; + $eval .= 'print json_encode(get_defined_vars());'; + $options = array( + 'config' => $config, + 'root' => $this->webroot(), + 'uri' => key($this->getSites()), + ); + $this->drush('php-eval', array($eval), $options, '@contextAlias'); + $output = $this->getOutput(); + $actuals = json_decode(trim($output)); + $this->assertEquals('alias1', $actuals->contextConfig); + + // Command specific wins over non-specific. If it did not, $expected would + // be 'site'. Note we call unit-eval command in order not to purturb + // php-eval with options in config file. + $eval = '$contextConfig = drush_get_option("contextConfig", "n/a");'; + $eval .= 'print json_encode(get_defined_vars());'; + $options = array( + 'root' => $this->webroot(), + 'uri' => key($this->getSites()), + 'include' => dirname(__FILE__), // Find unit.drush.inc commandfile. + ); + $this->drush('unit-eval', array($eval), $options); + $output = $this->getOutput(); + $actuals = json_decode(trim($output)); + $this->assertEquals('site-specific', $actuals->contextConfig); + } +} diff --git a/vendor/drush/drush/tests/coreTest.php b/vendor/drush/drush/tests/coreTest.php new file mode 100644 index 0000000000..13c0cf33b5 --- /dev/null +++ b/vendor/drush/drush/tests/coreTest.php @@ -0,0 +1,164 @@ +getSites()) { + $this->setUpDrupal(1, TRUE); + } + } + + /** + * Test to see if rsync @site:%files calculates the %files path correctly. + * This tests the non-optimized code path in drush_sitealias_resolve_path_references. + */ + function testRsyncPercentFiles() { + $root = $this->webroot(); + $site = key($this->getSites()); + $options = array( + 'root' => $root, + 'uri' => key($this->getSites()), + 'simulate' => NULL, + 'include-conf' => NULL, + 'include-vcs' => NULL, + 'yes' => NULL, + ); + $this->drush('core-rsync', array("@$site:%files", "/tmp"), $options, NULL, NULL, self::EXIT_SUCCESS, '2>&1;'); + $output = $this->getOutput(); + $level = $this->log_level(); + $pattern = in_array($level, array('verbose', 'debug')) ? "Calling system(rsync -e 'ssh ' -akzv --stats --progress --yes %s /tmp);" : "Calling system(rsync -e 'ssh ' -akz --yes %s /tmp);"; + $expected = sprintf($pattern, UNISH_SANDBOX . "/web/sites/$site/files"); + $this->assertEquals($expected, $output); + } + + /** + * Test to see if the optimized code path in drush_sitealias_resolve_path_references + * that avoids a call to backend invoke when evaluating %files works. + */ + function testPercentFilesOptimization() { + $root = $this->webroot(); + $site = key($this->getSites()); + $options = array( + 'root' => $root, + 'uri' => key($this->getSites()), + 'simulate' => NULL, + 'include-conf' => NULL, + 'include-vcs' => NULL, + 'yes' => NULL, + 'strict' => 0, // invoke from script: do not verify options + ); + $php = '$a=drush_sitealias_get_record("@' . $site . '"); drush_sitealias_resolve_path_references($a, "%files"); print_r($a["path-aliases"]["%files"]);'; + $this->drush('ev', array($php), $options); + $output = $this->getOutput(); + $expected = "sites/dev/files"; + $this->assertEquals($expected, $output); + } + + /** + * Test standalone php-script scripts. Assure that script args and options work. + */ + public function testStandaloneScript() { + if ($this->is_windows()) { + $this->markTestSkipped('Standalone scripts not currently available on Windows.'); + } + + $this->drush('version', array('drush_version'), array('pipe' => NULL)); + $standard = $this->getOutput(); + + // Write out a hellounish.script into the sandbox. The correct /path/to/drush + // is in the shebang line. + $filename = 'hellounish.script'; + $data = '#!/usr/bin/env [PATH-TO-DRUSH] + +$arg = drush_shift(); +drush_invoke("version", $arg); +'; + $data = str_replace('[PATH-TO-DRUSH]', UNISH_DRUSH, $data); + $script = UNISH_SANDBOX . '/' . $filename; + file_put_contents($script, $data); + chmod($script, 0755); + $this->execute("$script drush_version --pipe"); + $standalone = $this->getOutput(); + $this->assertEquals($standard, $standalone); + } + + function testDrupalDirectory() { + if (explode('.', UNISH_DRUPAL_MINOR_VERSION)[0] < '5') { + $this->markTestSkipped('Test uses devel, which requires Drupal 8.5.x or later'); + } + $root = $this->webroot(); + $sitewide = $this->drupalSitewideDirectory(); + $options = array( + 'root' => $root, + 'uri' => key($this->getSites()), + 'yes' => NULL, + 'skip' => NULL, + 'cache' => NULL, + 'strict' => 0, // invoke from script: do not verify options + ); + $this->drush('drupal-directory', array('%files'), $options); + $output = $this->getOutput(); + $this->assertEquals($root . '/sites/dev/files', $output); + + $this->drush('drupal-directory', array('%modules'), $options); + $output = $this->getOutput(); + $this->assertEquals($root . $sitewide . '/modules', $output); + + $this->drush('pm-download', array('devel'), $options); + $this->drush('pm-enable', array('devel'), $options); + $this->drush('pm-download', array('empty_theme'), $options); + + $this->drush('drupal-directory', array('devel'), $options); + $output = $this->getOutput(); + $this->assertEquals(realpath($root . $sitewide . '/modules/devel'), $output); + + $this->drush('drupal-directory', array('empty_theme'), $options); + $output = $this->getOutput(); + $this->assertEquals(realpath($root . $sitewide . '/themes/empty_theme'), $output); + } + + function testCoreRequirements() { + $root = $this->webroot(); + $options = array( + 'root' => $root, + 'uri' => key($this->getSites()), + 'pipe' => NULL, + 'ignore' => 'cron,http requests,update,update_core,trusted_host_patterns,coverage_core', // no network access when running in tests, so ignore these + 'strict' => 0, // invoke from script: do not verify options + ); + // Drupal 6 has reached EOL, so we will always get errors for 'update_contrib'; + // therefore, we ignore it for this release. + if (UNISH_DRUPAL_MAJOR_VERSION < 7) { + $options['ignore'] .= ',update_contrib'; + } + // Verify that there are no severity 2 items in the status report + $this->drush('core-requirements', array(), $options + array('severity' => '2')); + $output = $this->getOutput(); + $this->assertEquals('', $output); + + $this->drush('core-requirements', array(), $options); + $loaded = $this->getOutputFromJSON(); + // Pick a subset that are valid for D6/D7/D8. + $expected = array( + // 'install_profile' => -1, + // 'node_access' => -1, + 'php' => -1, + // 'php_extensions' => -1, + 'php_memory_limit' => -1, + 'php_register_globals' => -1, + 'settings.php' => -1, + ); + foreach ($expected as $key => $value) { + if (isset($loaded->$key)) { + $this->assertEquals($value, $loaded->$key->sid); + } + } + } +} diff --git a/vendor/drush/drush/tests/devel.xml b/vendor/drush/drush/tests/devel.xml new file mode 100644 index 0000000000..eb12be8801 --- /dev/null +++ b/vendor/drush/drush/tests/devel.xml @@ -0,0 +1,1101 @@ + + +Devel +devel +moshe weitzman +6.x + +2 +1,2 +2 +published + +http://drupal.org/project/devel + + ProjectsModules + ProjectsAdministration + ProjectsDeveloper + ProjectsUtility + + ProjectsDrush + Maintenance statusActively maintained + + + + + + + devel 6.x-2.2 + 6.x-2.2 + + DRUPAL-6--2-2 + 2 + 2 + unpublished + http://drupal.org/node/990464 + http://ftp.drupal.org/files/projects/devel-6.x-2.2.tar.gz + + 1291650063 + 07251c1cd56daf7d402fb5aea7dcfe48 + 172845 + + + http://ftp.drupal.org/files/projects/devel-6.x-2.2.tar.gz + tar.gz + + 07251c1cd56daf7d402fb5aea7dcfe48 + 172845 + 1291650063 + + + http://ftp.drupal.org/files/projects/devel-6.x-2.2.zip + zip + + 086412aa4c02ced793455486b48261e9 + 219993 + 1293230730 + + + + Release typeNew features + + Release typeBug fixes + + + + + devel 6.x-2.2-rc1 + 6.x-2.2-rc1 + + DRUPAL-6--2-2-RC1 + 2 + rc1 + 2 + published + http://drupal.org/node/990464 + http://ftp.drupal.org/files/projects/devel-6.x-2.2-rc1.tar.gz + + 1291650063 + 07251c1cd56daf7d402fb5aea7dcfe48 + 172845 + + + http://ftp.drupal.org/files/projects/devel-6.x-2.2-rc1.tar.gz + tar.gz + + 07251c1cd56daf7d402fb5aea7dcfe48 + 172845 + 1291650063 + + + http://ftp.drupal.org/files/projects/devel-6.x-2.2-rc1.zip + zip + + 086412aa4c02ced793455486b48261e9 + 219993 + 1293230730 + + + + Release typeNew features + + Release typeBug fixes + + + + + devel 6.x-2.1 + 6.x-2.1 + + DRUPAL-6--2-1 + 2 + 1 + published + http://drupal.org/node/990464 + http://ftp.drupal.org/files/projects/devel-6.x-2.1.tar.gz + + 1291650063 + 07251c1cd56daf7d402fb5aea7dcfe48 + 172845 + + + http://ftp.drupal.org/files/projects/devel-6.x-2.1.tar.gz + tar.gz + + 07251c1cd56daf7d402fb5aea7dcfe48 + 172845 + 1291650063 + + + http://ftp.drupal.org/files/projects/devel-6.x-2.1.zip + zip + + 086412aa4c02ced793455486b48261e9 + 219993 + 1293230730 + + + + Release typeNew features + + Release typeBug fixes + + + + + + devel 6.x-1.23 + 6.x-1.23 + + DRUPAL-6--1-23 + 1 + 23 + published + http://drupal.org/node/990464 + http://ftp.drupal.org/files/projects/devel-6.x-1.23.tar.gz + + 1291650063 + 07251c1cd56daf7d402fb5aea7dcfe48 + 172845 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.23.tar.gz + tar.gz + + 07251c1cd56daf7d402fb5aea7dcfe48 + 172845 + 1291650063 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.23.zip + zip + + 086412aa4c02ced793455486b48261e9 + 219993 + 1293230730 + + + + Release typeNew features + + Release typeBug fixes + + + + + + devel 6.x-1.22 + 6.x-1.22 + DRUPAL-6--1-22 + + 1 + 22 + published + http://drupal.org/node/882460 + http://ftp.drupal.org/files/projects/devel-6.x-1.22.tar.gz + 1281718291 + + a8a87cae8f101c4aad46688524ab7583 + 175140 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.22.tar.gz + tar.gz + a8a87cae8f101c4aad46688524ab7583 + + 175140 + 1281718291 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.22.zip + zip + cc207ab34421975216e6ee424c595001 + + 222352 + 1293230727 + + + + Release typeBug fixes + + + + + devel 6.x-1.21 + 6.x-1.21 + DRUPAL-6--1-21 + 1 + 21 + + published + http://drupal.org/node/874116 + http://ftp.drupal.org/files/projects/devel-6.x-1.21.tar.gz + 1280961079 + 15bf95b8c3fbe3f570f3f7b590f52ded + 175008 + + + + http://ftp.drupal.org/files/projects/devel-6.x-1.21.tar.gz + tar.gz + 15bf95b8c3fbe3f570f3f7b590f52ded + 175008 + 1280961079 + + + + http://ftp.drupal.org/files/projects/devel-6.x-1.21.zip + zip + f19fe1188a2774b383f146aee228a083 + 222217 + 1293230734 + + + + + Release typeSecurity update + + + + devel 6.x-1.20 + + 6.x-1.20 + DRUPAL-6--1-20 + 1 + 20 + published + http://drupal.org/node/777998 + + http://ftp.drupal.org/files/projects/devel-6.x-1.20.tar.gz + 1271886306 + b605d6a42e430660ef3418541d711c1c + 174042 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.20.tar.gz + + tar.gz + b605d6a42e430660ef3418541d711c1c + 174042 + 1271886306 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.20.zip + + zip + ff11ac877bdbbb29fa9dba024871e3bc + 221132 + 1293230727 + + + + + Release typeBug fixes + + + + devel 6.x-1.19 + 6.x-1.19 + DRUPAL-6--1-19 + + 1 + 19 + published + http://drupal.org/node/746888 + http://ftp.drupal.org/files/projects/devel-6.x-1.19.tar.gz + 1268976904 + + 375c3b79e9e74fb636bf8a6d2fde87d6 + 173570 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.19.tar.gz + tar.gz + 375c3b79e9e74fb636bf8a6d2fde87d6 + + 173570 + 1268976904 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.19.zip + zip + b9da9992305f2aa8e26bd9550c119189 + + 220677 + 1293230724 + + + + Release typeNew features + Release typeBug fixes + + + + + devel 6.x-1.18 + 6.x-1.18 + DRUPAL-6--1-18 + 1 + + 18 + published + http://drupal.org/node/585982 + http://ftp.drupal.org/files/projects/devel-6.x-1.18.tar.gz + 1253731829 + 5f2b9e5f4b74beec35a1d1cff379ab5c + + 165785 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.18.tar.gz + tar.gz + 5f2b9e5f4b74beec35a1d1cff379ab5c + 165785 + + 1253731829 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.18.zip + zip + 2e2808ec65aaa44d23f2a96c6af8bfbb + + 216330 + 1293230728 + + + + Release typeBug fixes + Release typeSecurity update + + + + + devel 6.x-1.17 + 6.x-1.17 + DRUPAL-6--1-17 + 1 + + 17 + published + http://drupal.org/node/554032 + http://ftp.drupal.org/files/projects/devel-6.x-1.17.tar.gz + 1250713853 + 31b2cbea82226a729b11bbc91920a7cd + + 163024 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.17.tar.gz + tar.gz + 31b2cbea82226a729b11bbc91920a7cd + 163024 + + 1250713853 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.17.zip + zip + 5e2a367f2e43c90de1e676e3037e1570 + 213291 + + 1293230731 + + + + Release typeNew features + Release typeBug fixes + + + + + devel 6.x-1.16 + 6.x-1.16 + DRUPAL-6--1-16 + 1 + + 16 + published + http://drupal.org/node/430072 + http://ftp.drupal.org/files/projects/devel-6.x-1.16.tar.gz + 1239375932 + 6dc83de5de101460f8eee2db3ea4b7d9 + + 163998 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.16.tar.gz + tar.gz + 6dc83de5de101460f8eee2db3ea4b7d9 + 163998 + + 1239375932 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.16.zip + zip + 65ad91bc0308b10a9d11107f2d35bc2f + 215213 + + 1293230728 + + + + Release typeBug fixes + + + + + devel 6.x-1.15 + 6.x-1.15 + DRUPAL-6--1-15 + 1 + 15 + + published + http://drupal.org/node/419014 + http://ftp.drupal.org/files/projects/devel-6.x-1.15.tar.gz + 1238471715 + e7cd175671ed62a95bab7093c636e663 + 163945 + + + + http://ftp.drupal.org/files/projects/devel-6.x-1.15.tar.gz + tar.gz + e7cd175671ed62a95bab7093c636e663 + 163945 + 1238471715 + + + + http://ftp.drupal.org/files/projects/devel-6.x-1.15.zip + zip + f0719bb64bdeb1d3c2f327457351b9d1 + 215342 + 1293230731 + + + + + Release typeNew features + Release typeBug fixes + + + + + devel 6.x-1.14 + 6.x-1.14 + DRUPAL-6--1-14 + 1 + 14 + + published + http://drupal.org/node/368174 + http://ftp.drupal.org/files/projects/devel-6.x-1.14.tar.gz + 1233610814 + 11af2d7fa3050febfe3def770683f173 + 162780 + + + + http://ftp.drupal.org/files/projects/devel-6.x-1.14.tar.gz + tar.gz + 11af2d7fa3050febfe3def770683f173 + 162780 + 1233610814 + + + + http://ftp.drupal.org/files/projects/devel-6.x-1.14.zip + zip + c691c6d03d1f605493807cf7dad7dce1 + 214014 + 1293230725 + + + + + Release typeBug fixes + + + + devel 6.x-1.13 + + 6.x-1.13 + DRUPAL-6--1-13 + 1 + 13 + published + http://drupal.org/node/353382 + + http://ftp.drupal.org/files/projects/devel-6.x-1.13.tar.gz + 1230872109 + 669870433908fccd31e7de9b57434c64 + 161893 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.13.tar.gz + + tar.gz + 669870433908fccd31e7de9b57434c64 + 161893 + 1230872109 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.13.zip + + zip + 06ed93306fe8476af6aa62aac763afa4 + 212645 + 1293230728 + + + + + Release typeNew features + Release typeBug fixes + + + + devel 6.x-1.12 + + 6.x-1.12 + DRUPAL-6--1-12 + 1 + 12 + published + http://drupal.org/node/319216 + + http://ftp.drupal.org/files/projects/devel-6.x-1.12.tar.gz + 1223583012 + 69b345c09ece34812b04e7ccbc2cdf23 + 146188 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.12.tar.gz + + tar.gz + 69b345c09ece34812b04e7ccbc2cdf23 + 146188 + 1223583012 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.12.zip + + zip + 8c66e91fc8c64388facf8e7e7054e58a + 197116 + 1293230726 + + + + + Release typeNew features + Release typeBug fixes + + + + devel 6.x-1.11 + + 6.x-1.11 + DRUPAL-6--1-11 + 1 + 11 + published + http://drupal.org/node/310335 + + http://ftp.drupal.org/files/projects/devel-6.x-1.11.tar.gz + 1221759046 + 93bc2cc8da26d759e46b24f1b9d4e3e2 + 145693 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.11.tar.gz + + tar.gz + 93bc2cc8da26d759e46b24f1b9d4e3e2 + 145693 + 1221759046 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.11.zip + + zip + 3c1063443d81927e3b90e129bc169a24 + 196477 + 1293230729 + + + + + Release typeNew features + Release typeBug fixes + + + + devel 6.x-1.10 + + 6.x-1.10 + DRUPAL-6--1-10 + 1 + 10 + published + http://drupal.org/node/283183 + + http://ftp.drupal.org/files/projects/devel-6.x-1.10.tar.gz + 1216171206 + 688ed33b3a2a63bc1bce9da3c2d104de + 145188 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.10.tar.gz + + tar.gz + 688ed33b3a2a63bc1bce9da3c2d104de + 145188 + 1216171206 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.10.zip + + zip + c9e85aac38d3c2f18015c820ec325206 + 195931 + 1293230732 + + + + + Release typeNew features + Release typeBug fixes + + + + devel 6.x-1.9 + + 6.x-1.9 + DRUPAL-6--1-9 + 1 + 9 + published + http://drupal.org/node/270064 + + http://ftp.drupal.org/files/projects/devel-6.x-1.9.tar.gz + 1213331113 + 8b88659f45e31d8c05fd6990c491ea2a + 145153 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.9.tar.gz + + tar.gz + 8b88659f45e31d8c05fd6990c491ea2a + 145153 + 1213331113 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.9.zip + + zip + 0d934dafa2cedd0d1bc5ec450cc9570f + 195856 + 1293230729 + + + + + Release typeNew features + Release typeBug fixes + + + + devel 6.x-1.8 + + 6.x-1.8 + DRUPAL-6--1-8 + 1 + 8 + published + http://drupal.org/node/259615 + + http://ftp.drupal.org/files/projects/devel-6.x-1.8.tar.gz + 1211039106 + d8d8f4ee8dd61ccad625443ebafbe421 + 76796 + + + + http://ftp.drupal.org/files/projects/devel-6.x-1.8.tar.gz + tar.gz + d8d8f4ee8dd61ccad625443ebafbe421 + 76796 + 1211039106 + + + + http://ftp.drupal.org/files/projects/devel-6.x-1.8.zip + zip + 72b3468d765e1f9e827d9399f2d7218c + 92505 + 1293230732 + + + + + Release typeNew features + Release typeBug fixes + + + + + devel 6.x-1.7 + 6.x-1.7 + DRUPAL-6--1-7 + 1 + 7 + + published + http://drupal.org/node/237840 + http://ftp.drupal.org/files/projects/devel-6.x-1.7.tar.gz + 1206300306 + b63eba9e1588baa2f8031589897eb376 + 72504 + + + + http://ftp.drupal.org/files/projects/devel-6.x-1.7.tar.gz + tar.gz + b63eba9e1588baa2f8031589897eb376 + 72504 + 1206300306 + + + + http://ftp.drupal.org/files/projects/devel-6.x-1.7.zip + zip + 9e4711ebace4e7cf0d7dc4e4a1386be3 + 87847 + 1293230726 + + + + + + devel 6.x-1.6 + 6.x-1.6 + DRUPAL-6--1-6 + + 1 + 6 + published + http://drupal.org/node/229249 + http://ftp.drupal.org/files/projects/devel-6.x-1.6.tar.gz + 1204515005 + + dc2fd4030922e17258a0fa886557aa9f + 72109 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.6.tar.gz + tar.gz + dc2fd4030922e17258a0fa886557aa9f + + 72109 + 1204515005 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.6.zip + zip + 65cf489c56834571eaf43a30e87a25ef + + 87441 + 1293230732 + + + + Release typeBug fixes + + + + + devel 6.x-1.5 + 6.x-1.5 + DRUPAL-6--1-5 + 1 + 5 + + published + http://drupal.org/node/227742 + http://ftp.drupal.org/files/projects/devel-6.x-1.5.tar.gz + 1204163705 + 4755dc85712f86c24a7615c83bf2d587 + 72424 + + + + http://ftp.drupal.org/files/projects/devel-6.x-1.5.tar.gz + tar.gz + 4755dc85712f86c24a7615c83bf2d587 + 72424 + 1204163705 + + + + http://ftp.drupal.org/files/projects/devel-6.x-1.5.zip + zip + 3def5895dcd04439f2e7c69e33ae3ef7 + 87108 + 1293230729 + + + + + Release typeBug fixes + + + + devel 6.x-1.4 + + 6.x-1.4 + DRUPAL-6--1-4 + 1 + 4 + published + http://drupal.org/node/224889 + + http://ftp.drupal.org/files/projects/devel-6.x-1.4.tar.gz + 1203609008 + 9361284333474368c49b807943e7eb09 + 72289 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.4.tar.gz + + tar.gz + 9361284333474368c49b807943e7eb09 + 72289 + 1203609008 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.4.zip + + zip + 0ee1899e32edcdb56ac13b067c8bb2aa + 86956 + 1293230733 + + + + + Release typeBug fixes + + + + devel 6.x-1.3 + 6.x-1.3 + DRUPAL-6--1-3 + + 1 + 3 + published + http://drupal.org/node/223052 + http://ftp.drupal.org/files/projects/devel-6.x-1.3.tar.gz + 1203291304 + + 5dbb78276404810a219395d790bb349d + 72231 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.3.tar.gz + tar.gz + 5dbb78276404810a219395d790bb349d + + 72231 + 1203291304 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.3.zip + zip + 293c305a758cd84b1a5b1e0f56c6883f + + 86893 + 1293230727 + + + + Release typeBug fixes + + + + + devel 6.x-1.2 + 6.x-1.2 + DRUPAL-6--1-2 + 1 + 2 + + published + http://drupal.org/node/223032 + http://ftp.drupal.org/files/projects/devel-6.x-1.2.tar.gz + 1203288005 + ec2d609a553289636f2388c54fc69e8b + 72232 + + + + http://ftp.drupal.org/files/projects/devel-6.x-1.2.tar.gz + tar.gz + ec2d609a553289636f2388c54fc69e8b + 72232 + 1203288005 + + + + http://ftp.drupal.org/files/projects/devel-6.x-1.2.zip + zip + 5855930a3427fdc2c3449aebf8ad68f9 + 86895 + 1293230730 + + + + + Release typeBug fixes + + + + devel 6.x-1.1 + + 6.x-1.1 + DRUPAL-6--1-1 + 1 + 1 + published + http://drupal.org/node/222919 + + http://ftp.drupal.org/files/projects/devel-6.x-1.1.tar.gz + 1203267606 + 2d1065298518decda8eb0d0473851ed1 + 72225 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.1.tar.gz + + tar.gz + 2d1065298518decda8eb0d0473851ed1 + 72225 + 1203267606 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.1.zip + + zip + f72a6870d67d929970c78151f7737ebc + 86896 + 1293230733 + + + + + Release typeNew features + Release typeBug fixes + + + + devel 6.x-1.0 + + 6.x-1.0 + DRUPAL-6--1-0 + 1 + 0 + published + http://drupal.org/node/208665 + + http://ftp.drupal.org/files/projects/devel-6.x-1.0.tar.gz + 1200012604 + 3dbd3cd4c15f001c1ac7067ca58c74ff + 53802 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.0.tar.gz + + tar.gz + 3dbd3cd4c15f001c1ac7067ca58c74ff + 53802 + 1200012604 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.0.zip + + zip + 10600179cfa8a301784839983fb49397 + 64756 + 1293230727 + + + + + Release typeSecurity update + + + + devel 6.x-1.x-dev + 6.x-1.x-dev + DRUPAL-6--1 + + 1 + dev + published + http://drupal.org/node/224617 + http://ftp.drupal.org/files/projects/devel-6.x-1.x-dev.tar.gz + 1295179817 + + fec92fdc9dd40f34b2e7f9876fd44014 + 126254 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.x-dev.tar.gz + tar.gz + fec92fdc9dd40f34b2e7f9876fd44014 + + 126254 + 1295179817 + + + http://ftp.drupal.org/files/projects/devel-6.x-1.x-dev.zip + zip + 91255187be4c5cd7bacf7112e6b0189b + + 171436 + 1295179818 + + + + + devel 6.x-0.2 + + 6.x-0.2 + DRUPAL-6--0-2 + 0 + 2 + unpublished + 1203287704 + + a071278bf76df5acf5320086f80ff4f5 + 72235 + + + tar.gz + a071278bf76df5acf5320086f80ff4f5 + 72235 + + 1203287704 + + + + Release typeBug fixes + + + + + diff --git a/vendor/drush/drush/tests/drushScriptTest.php b/vendor/drush/drush/tests/drushScriptTest.php new file mode 100644 index 0000000000..f65e921d02 --- /dev/null +++ b/vendor/drush/drush/tests/drushScriptTest.php @@ -0,0 +1,150 @@ +markTestSkipped('Environment variables not yet passed along to Process by execute().'); + + + // @todo: could probably run this test on mingw + if ($this->is_windows()) { + $this->markTestSkipped('Environment variable tests not currently functional on Windows.'); + } + + $options = array(); + $env = array('PHP_OPTIONS' => '-d default_mimetype="text/drush"'); + $this->drush('ev', array('print ini_get("default_mimetype");'), $options, NULL, NULL, self::EXIT_SUCCESS, NULL, $env); + $output = $this->getOutput(); + $this->assertEquals('text/drush', $output); + } + + public function testDrushFinder() { + // We don't really need a real Drupal site; we could + // create a fake site, as long as we had the right signature + // files to allow us to bootstrap to the DRUPAL_ROOT phase. + $this->setUpDrupal(1, TRUE); + + $globalDrushDotPhp = Path::join(UNISH_DRUSH, '../drush.php'); + + // Control: test `drush --root ` ... with no site-local Drush + $drush_location = $this->getDrushLocation(); + $this->assertEquals($globalDrushDotPhp, $drush_location); + + // We will try copying a site-local Drush to + // all of the various locations the 'drush finder' + // might expect to find it. + $drush_locations = array( + "vendor", + "../vendor", + "sites/all/vendor", + "sites/all", + ); + + foreach ($drush_locations as $drush_base) { + $drush_root = $this->create_site_local_drush($drush_base); + + // Test `drush --root ` ... with a site-local Drush + $drush_location = $this->getDrushLocation(array('root' => $this->webroot())); + $this->assertEquals(realpath($drush_root . '/drush.php'), realpath($drush_location)); + // Ensure that --local was NOT added + $result = $this->drush('ev', array('return drush_get_option("local");'), array('root' => $this->webroot())); + $output = $this->getOutput(); + $this->assertEquals("", $output); + + // Run the `drush --root` test again, this time with + // a drush.wrapper script in place. + $this->createDrushWrapper($drush_base); + $drush_location = $this->getDrushLocation(array('root' => $this->webroot())); + $this->assertEquals(realpath($drush_root . '/drush.php'), realpath($drush_location)); + // Test to see if --local was added + $result = $this->drush('ev', array('return drush_get_option("local");'), array('root' => $this->webroot())); + $output = $this->getOutput(); + $this->assertEquals("TRUE", $output); + + // Get rid of the symlink and site-local Drush we created + $this->remove_site_local_drush($drush_base); + } + + // Next, try again with a site-local Drush in a location + // that Drush does not search. + $mysterious_location = "path/drush/does/not/search"; + $drush_root = $this->create_site_local_drush($mysterious_location); + // We should not find the site-local Drush without a Drush wrapper. + $drush_location = $this->getDrushLocation(array('root' => $this->webroot())); + $this->assertEquals($globalDrushDotPhp, $drush_location); + $this->createDrushWrapper($mysterious_location); + // Now that there is a Drush wrapper, we should be able to find the site-local Drush. + $drush_location = $this->getDrushLocation(array('root' => $this->webroot())); + $this->assertEquals(realpath($drush_root . '/drush.php'), $drush_location); + } + + /** + * Copy UNISH_DRUSH into the specified site-local location. + */ + function create_site_local_drush($drush_base) { + $drush_root = $this->webroot() . '/' . $drush_base . '/drush/drush'; + $bin_dir = $this->webroot() . '/' . $drush_base . '/bin'; + + $this->mkdir(dirname($drush_root)); + $this->recursive_copy(dirname(UNISH_DRUSH), $drush_root); + @chmod($drush_root . '/drush', 0777); + @chmod($drush_root . '/drush.launcher', 0777); + $this->mkdir($bin_dir); + symlink($drush_root . '/drush', $bin_dir . '/drush'); + + return $drush_root; + } + + function remove_site_local_drush($drush_base) { + // Get rid of the symlink and site-local Drush we created + unish_file_delete_recursive($this->webroot() . '/' . $drush_base . '/drush/drush'); + unlink($this->webroot() . '/' . $drush_base . '/bin/drush'); + if (file_exists($this->webroot() . '/drush.wrapper')) { + unlink($this->webroot() . '/drush.wrapper'); + } + } + + /** + * TODO: Create a Drush wrapper script, and copy it to + * to the root of the fake Drupal site, and point it + * at the specified site-local Drush script. + */ + function createDrushWrapper($drush_base) { + $drush_launcher = $drush_base . '/drush/drush/drush.launcher'; + + $drush_wrapper_src = dirname(UNISH_DRUSH) . '/examples/drush.wrapper'; + $drush_wrapper_contents = file_get_contents($drush_wrapper_src); + $drush_wrapper_contents = preg_replace('#\.\./vendor/bin/drush.launcher#', $drush_launcher, $drush_wrapper_contents); + $drush_wrapper_target = $this->webroot() . '/drush.wrapper'; + + file_put_contents($drush_wrapper_target, $drush_wrapper_contents); + @chmod($drush_wrapper_target, 0777); + } + + /** + * Get the current location of the Drush script via + * `drush status 'Drush script' --format=yaml`. This + * will return results other than UNISH_DRUSH in the + * presence of a site-local Drush. + */ + function getDrushLocation($options = array()) { + $options += array( + 'format' => 'yaml', + 'verbose' => NULL, + ); + $result = $this->drush('status', array('Drush script'), $options); + + $output = $this->getOutput(); + list($key, $value) = explode(": ", $output); + return trim($value, "'"); + } +} diff --git a/vendor/drush/drush/tests/expandWildcardTablesUnitTest.php b/vendor/drush/drush/tests/expandWildcardTablesUnitTest.php new file mode 100644 index 0000000000..fa317b042d --- /dev/null +++ b/vendor/drush/drush/tests/expandWildcardTablesUnitTest.php @@ -0,0 +1,80 @@ +assertEquals($db_tables, $expanded_db_tables); + } + + /** + * Tests drush_sql_filter_tables(). + * + * @see drush_sql_filter_tables(). + */ + public function testFilterTables() { + // Array of tables to search for. + $wildcard_input = array( + 'cache', + 'cache_*', + ); + // Mock array of tables to test with. + $db_tables = array( + 'cache', + 'cache_bootstrap', + 'cache_field', + 'cache_filter', + 'cache_form', + 'cache_menu', + 'cache_page', + 'cache_path', + 'cache_update', + ); + $expected_result = array( + 'cache', + ); + + $actual_result = drush_sql_filter_tables($wildcard_input, $db_tables); + $this->assertEquals($expected_result, $actual_result); + } +} diff --git a/vendor/drush/drush/tests/fieldTest.php b/vendor/drush/drush/tests/fieldTest.php new file mode 100644 index 0000000000..6232f437ca --- /dev/null +++ b/vendor/drush/drush/tests/fieldTest.php @@ -0,0 +1,77 @@ +markTestSkipped("Field API not available in Drupal 6."); + } + + if (UNISH_DRUPAL_MAJOR_VERSION >= 8) { + $this->markTestSkipped("Field commands are not yet ported to D8."); + } + + $sites = $this->setUpDrupal(1, TRUE); + $options = array( + 'yes' => NULL, + 'root' => $this->webroot(), + 'uri' => key($sites), + ); + + $expected_url = '/admin/config/people/accounts/fields/subtitle'; + if (UNISH_DRUPAL_MAJOR_VERSION >= 8) { + // Prepend for D8. We might want to change setUpDrupal() to add clean url. + $expected_url = '/index.php' . $expected_url; + } + // Create two field instances on article content type. + $this->drush('field-create', array('user', 'city,text,text_textfield', 'subtitle,text,text_textfield'), $options + array('entity_type' => 'user')); + $output = $this->getOutput(); + list($city, $subtitle) = explode(' ', $output); + $url = parse_url($subtitle); + $this->assertEquals($expected_url, $url['path']); + + // Assure that the second field instance was created correctly (subtitle). + $this->verifyInstance('subtitle', $options); + + // Assure that field update URL looks correct. + $this->drush('field-update', array('subtitle'), $options); + $output = $this->getOutput(); + $url = parse_url($this->getOutput()); + $this->assertEquals($expected_url, $url['path']); + + // Assure that field-clone actually clones. + $this->drush('field-clone', array('subtitle', 'subtitlecloned'), $options); + $this->verifyInstance('subtitlecloned', $options); + + // Assure that delete works. + $this->drush('field-delete', array('subtitlecloned'), $options); + $this->verifyInstance('subtitlecloned', $options, FALSE); + } + + function verifyInstance($name, $options, $expected = TRUE) { + $this->drush('field-info', array('fields'), $options + array('format' => 'json')); + $output = $this->getOutputFromJSON(); + $found = FALSE; + foreach($output as $key => $field) { + if ($key == $name) { + $this->assertEquals('text', $field->type, $name . ' field is of type=text.'); + $this->assertEquals('user', current($field->bundle), $name . ' field was added to user bundle.'); + $found = TRUE; + break; + } + } + if ($expected) { + $this->assertTrue($found, $name . ' field was created.'); + } + else { + $this->assertFalse($found, $name . ' field was not present.'); + } + } +} diff --git a/vendor/drush/drush/tests/filesystemTest.php b/vendor/drush/drush/tests/filesystemTest.php new file mode 100644 index 0000000000..03365403e6 --- /dev/null +++ b/vendor/drush/drush/tests/filesystemTest.php @@ -0,0 +1,47 @@ +is_windows()) { + $this->markTestSkipped("s-bit test doesn't apply on Windows."); + } + if (UNISH_USERGROUP === NULL) { + $this->markTestSkipped("s-bit test skipped because of UNISH_USERGROUP was not set."); + } + + $dest = UNISH_SANDBOX . '/test-filesystem-sbit'; + mkdir($dest); + chgrp($dest, UNISH_USERGROUP); + chmod($dest, 02755); // rwxr-sr-x + + $this->drush('pm-download', array('devel'), array('cache' => NULL, 'skip' => NULL, 'destination' => $dest)); + + $group = posix_getgrgid(filegroup($dest . '/devel/README.txt')); + $this->assertEquals($group['name'], UNISH_USERGROUP, 'Group is preserved.'); + + $perms = fileperms($dest . '/devel') & 02000; + $this->assertEquals($perms, 02000, 's-bit is preserved.'); + } + + public function testExecuteBits() { + if ($this->is_windows()) { + $this->markTestSkipped("execute bit test doesn't apply on Windows."); + } + + $dest = UNISH_SANDBOX . '/test-filesystem-execute'; + mkdir($dest); + $this->execute(sprintf("git clone --depth=1 https://github.com/drush-ops/drush.git %s", $dest . '/drush')); + + $perms = fileperms($dest . '/drush/drush') & 0111; + $this->assertEquals($perms, 0111, 'Execute permission is preserved.'); + } +} + diff --git a/vendor/drush/drush/tests/generateMakeTest.php b/vendor/drush/drush/tests/generateMakeTest.php new file mode 100644 index 0000000000..a2a20bd897 --- /dev/null +++ b/vendor/drush/drush/tests/generateMakeTest.php @@ -0,0 +1,130 @@ +_testGenerateMake('devel', 'bootstrap'); + } + + function testGenerateMakeOmega() { + # TODO: Don't skip this test by default once the underlying issue is resolved. + # See: https://github.com/drush-ops/drush/issues/2030 + $run_omega_make_test = getenv("DRUSH_TEST_MAKE_OMEGA"); + if ($run_omega_make_test) { + return $this->_testGenerateMake('devel', 'omega'); + } + else { + $this->markTestSkipped('Set `DRUSH_TEST_MAKE_OMEGA=1`, in order to run this test. See: https://github.com/drush-ops/drush/issues/2028'); + } + } + + function _testGenerateMake($module, $theme) { + if (UNISH_DRUPAL_MAJOR_VERSION != '7') { + $this->markTestSkipped("Drush generate make tests depend on projects not available on older and newer versions of Drupal. Tests need updating, but Drush make is deprecated; Composer is recommended."); + } + $sites = $this->setUpDrupal(1, TRUE); + $major_version = UNISH_DRUPAL_MAJOR_VERSION . '.x'; + + $options = array( + 'yes' => NULL, + 'pipe' => NULL, + 'root' => $this->webroot(), + 'uri' => key($sites), + 'cache' => NULL, + 'strict' => 0, // Don't validate options + ); + // Omega requires these core modules. + $this->drush('pm-enable', array('block', 'search', 'help'), $options); + $this->drush('pm-download', array($theme, $module), $options); + $this->drush('pm-enable', array($theme, $module), $options); + + $makefile = UNISH_SANDBOX . '/dev.make.yml'; + + // First generate a simple makefile with no version information + $this->drush('generate-makefile', array($makefile), array('exclude-versions' => NULL) + $options); + $expected = <<assertEquals($expected, $actual); + + // Next generate a simple makefile with no version information in .ini format + $makefile = UNISH_SANDBOX . '/dev.make'; + $this->drush('generate-makefile', array($makefile), array('exclude-versions' => NULL, 'format' => 'ini') + $options); + $expected = <<assertEquals($expected, $actual); + + // Download a module to a 'contrib' directory to test the subdir feature + $this->mkdir(Path::join($this->webroot(). '/sites/all/modules/contrib')); + $this->drush('pm-download', array('libraries'), array('destination' => 'sites/all/modules/contrib') + $options); + $this->drush('pm-enable', array('libraries'), $options); + $makefile = UNISH_SANDBOX . '/dev.make.yml'; + $this->drush('generate-makefile', array($makefile), array('exclude-versions' => NULL) + $options); + $expected = <<assertEquals($expected, $actual); + + // Again in .ini format. + $makefile = UNISH_SANDBOX . '/dev.make'; + $this->drush('generate-makefile', array($makefile), array('exclude-versions' => NULL, 'format' => 'ini') + $options); + $expected = <<assertEquals($expected, $actual); + + // Generate a makefile with version numbers (in .ini format). + $this->drush('generate-makefile', array($makefile), array('format' => 'ini') + $options); + $actual = file_get_contents($makefile); + $this->assertStringContainsString('projects[' . $module . '][version] = "', $actual); + } +} diff --git a/vendor/drush/drush/tests/hooks/magic_help_alter/magic.drush.inc b/vendor/drush/drush/tests/hooks/magic_help_alter/magic.drush.inc new file mode 100644 index 0000000000..9ad93d944e --- /dev/null +++ b/vendor/drush/drush/tests/hooks/magic_help_alter/magic.drush.inc @@ -0,0 +1,12 @@ +markTestSkipped("Image styles not available in Drupal 6 core."); + } + + $sites = $this->setUpDrupal(1, TRUE, null, 'standard'); + $options = array( + 'yes' => NULL, + 'root' => $this->webroot(), + 'uri' => key($sites), + ); + $logo = UNISH_DRUPAL_MAJOR_VERSION >= 8 ? 'core/themes/bartik/screenshot.png' : 'themes/bartik/screenshot.png'; + $styles_dir = $options['root'] . '/sites/' . key($sites) . '/files/styles/'; + $thumbnail = $styles_dir . 'thumbnail/public/' . $logo; + $medium = $styles_dir . 'medium/public/' . $logo; + + // Test that "drush image-derive" works. + $style_name = 'thumbnail'; + $this->drush('image-derive', array($style_name, $logo), $options); + $this->assertFileExists($thumbnail); + + // Test that "drush image-flush thumbnail" deletes derivatives created by the thumbnail image style. + $this->drush('image-flush', array($style_name), $options); + $this->assertFileDoesNotExist($thumbnail); + + // Check that "drush image-flush --all" deletes all image styles by creating two different ones and testing its + // existance afterwards. + $this->drush('image-derive', array('thumbnail', $logo), $options); + $this->assertFileExists($thumbnail); + $this->drush('image-derive', array('medium', $logo), $options); + $this->assertFileExists($medium); + $this->drush('image-flush', array(), array('all' => TRUE) + $options); + $this->assertFileDoesNotExist($thumbnail); + $this->assertFileDoesNotExist($medium); + } +} diff --git a/vendor/drush/drush/tests/initCommandTest.php b/vendor/drush/drush/tests/initCommandTest.php new file mode 100644 index 0000000000..654f9e3b35 --- /dev/null +++ b/vendor/drush/drush/tests/initCommandTest.php @@ -0,0 +1,34 @@ +drush('core-init', array(), array('backend' => NULL, 'add-path' => TRUE, 'yes' => NULL)); + $parsed = $this->parse_backend_output($this->getOutput()); + // First test to ensure that the command claimed to have made the expected progress + $this->assertLogHasMessage($parsed['log'], "Copied example Drush configuration file", 'ok'); + $this->assertLogHasMessage($parsed['log'], "Copied example Drush bash configuration file", 'ok'); + $this->assertLogHasMessage($parsed['log'], "Updated bash configuration file", 'ok'); + // Next we will test to see if there is evidence that those + // operations worked. + $home = getenv("HOME"); + $this->assertFileExists("$home/.drush/drushrc.php"); + $this->assertFileExists("$home/.drush/drush.bashrc"); + $this->assertFileExists("$home/.bashrc"); + + // Check to see if the .bashrc file sources our drush.bashrc file, + // and whether it adds the path to UNISH_DRUSH to the $PATH + $bashrc_contents = file_get_contents("$home/.bashrc"); + $this->assertStringContainsString('drush.bashrc', $bashrc_contents); + $this->assertStringContainsString(dirname(UNISH_DRUSH), $bashrc_contents); + } +} diff --git a/vendor/drush/drush/tests/lockMakeTest.php b/vendor/drush/drush/tests/lockMakeTest.php new file mode 100644 index 0000000000..c35276618f --- /dev/null +++ b/vendor/drush/drush/tests/lockMakeTest.php @@ -0,0 +1,88 @@ +makefile_path = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'makefiles'; + } + + /** + * Run a given makefile test. + * + * @param $test + * The test makefile to run, as defined by $this->getMakefile(); + */ + private function runLockfileTest($test) { + $default_options = array( + 'result-file' => UNISH_SANDBOX . '/test.lock.yml', + ); + $config = $this->getLockfile($test); + $options = array_merge($config['options'], $default_options); + $makefile = $this->makefile_path . DIRECTORY_SEPARATOR . $config['makefile']; + $lockfile = $this->makefile_path . DIRECTORY_SEPARATOR . 'lockfiles' . DIRECTORY_SEPARATOR . $config['lockfile']; + $this->drush('make-lock', array($makefile), $options); + $expected = trim(file_get_contents($lockfile)); + $actual = trim(file_get_contents($options['result-file'])); + + $this->assertEquals($expected, $actual); + } + + function getLockfile($key) { + static $tests; + $tests = $this->listLockfileTests(); + return $tests[$key]; + } + + function listLockfileTests() { + $tests = array( + 'default' => array( + 'name' => 'lock', + 'makefile' => 'lock-default.make.yml', + 'lockfile' => 'default.lock.yml', + 'options' => array(), + ), + 'git' => array( + 'name' => 'git', + 'makefile' => 'lock-git.make.yml', + 'lockfile' => 'git.lock.yml', + 'options' => array(), + ), + ); + return $tests; + } + + /************************************************************************ + * * + * List of lock tests (in alphabetical order, for easier navigation.) * + * * + ************************************************************************/ + + /** + * Test locking basic version data. + */ + function testDefaultLock() { + $this->runLockfileTest('default'); + } + + /** + * Test locking git version data. + */ + function testGitLock() { + $this->runLockfileTest('git'); + } + +} diff --git a/vendor/drush/drush/tests/makeConvertTest.php b/vendor/drush/drush/tests/makeConvertTest.php new file mode 100644 index 0000000000..f543b9c274 --- /dev/null +++ b/vendor/drush/drush/tests/makeConvertTest.php @@ -0,0 +1,110 @@ +drush('make-convert', array($source_file), $options); + $output = $this->getOutput(); + foreach ($expected_lines as $expected_line) { + $this->assertStringContainsString($expected_line, $output); + } + } + + /** + * Data provider for testMakeConvert(). + * + * @return array + * An array of test case data. See testMakeConvert() signature. + */ + public function providerTestMakeConvert() { + return array( + array( + // Source filename in makefiles directory. + 'patches.make', + // Command pptions. + array('format' => 'yml'), + // Expected output lines. + array( + 'core: 7.x', + 'features:', + 'version: 1.0-beta4', + 'patch:', + "- 'http://drupal.org/files/issues/features-drush-backend-invoke-25.patch'", + ), + ), + array( + 'patches.make', + array('format' => 'composer'), + array( + '"drupal/drupal": "7.*",', + '"drupal/features": "^1.0-beta4",', + '"patches": {', + '"drupal/features": {', + '"Enter drupal/features patch #0 description here": "http://drupal.org/files/issues/features-drush-backend-invoke-25.patch"', + ), + ), + array( + 'patches.make.yml', + array('format' => 'composer'), + array( + '"drupal/drupal": "7.*",', + '"drupal/features": "^1.0-beta4",', + '"patches": {', + '"drupal/features": {', + '"Enter drupal/features patch #0 description here": "http://drupal.org/files/issues/features-drush-backend-invoke-25.patch"', + ), + ), + array( + 'composer.lock', + array('format' => 'make'), + array( + 'core = 7.x', + 'api = 2', + // Ensure Drupal core tag is set correctly. + 'projects[drupal][download][tag] = "7.67"', + 'projects[features][download][type] = "git"', + 'projects[features][download][url] = "https://git.drupalcode.org/project/features.git"', + 'projects[features][download][tag] = "1.x-0.0-beta4"', + 'projects[features][patch][0] = "https://drupal.org/files/issues/features-drush-backend-invoke-25.patch"'), + ), + array( + 'composer.lock', + array('format' => 'yml'), + array( + 'core: 7.x', + 'api: 2', + // Ensure Drupal core tag is set correctly. + "tag: '7.67'", + 'features:', + 'tag: 1.x-0.0-beta4', + 'patch:', + "- 'https://drupal.org/files/issues/features-drush-backend-invoke-25.patch'", + ), + ), + ); + } + +} diff --git a/vendor/drush/drush/tests/makeTest.php b/vendor/drush/drush/tests/makeTest.php new file mode 100644 index 0000000000..3fbf70bfc1 --- /dev/null +++ b/vendor/drush/drush/tests/makeTest.php @@ -0,0 +1,795 @@ +makefile_path = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'makefiles'; + } + + /** + * Run a given makefile test. + * + * @param $test + * The test makefile to run, as defined by $this->getMakefile(); + */ + private function runMakefileTest($test) { + $default_options = array( + 'test' => NULL, + 'md5' => 'print', + ); + $config = $this->getMakefile($test); + $options = array_merge($config['options'], $default_options); + $makefile = $this->makefile_path . DIRECTORY_SEPARATOR . $config['makefile']; + $return = !empty($config['fail']) ? self::EXIT_ERROR : self::EXIT_SUCCESS; + $result = $this->drush('make', array($makefile), $options, NULL, NULL, $return); + $this->assertEquals($return, $result); + + // Check the log for the build hash if this test should pass. + if (empty($config['fail'])) { + $output = $this->getOutput(); + $this->assertStringContainsString($config['md5'], $output, $config['name'] . ' - build md5 matches expected value: ' . $config['md5']); + } + } + + function getMakefile($key) { + static $tests; + $tests = $this->listMakefileTests(); + return $tests[$key]; + } + + function listMakefileTests() { + $tests = array( + 'bzr' => array( + 'name' => 'Bzr', + 'makefile' => 'bzr.make', + 'build' => TRUE, + 'md5' => '272e2b9bb27794c54396f2f03c159725', + 'options' => array(), + ), + 'bz2' => array( + 'name' => 'bzip2', + 'makefile' => 'bz2.make', + 'build' => TRUE, + 'md5' => '5ec081203131a1a3277c8b23f9ddb995', + 'options' => array('no-core' => NULL), + ), + 'bz2-singlefile' => array( + 'name' => 'bzip2 single file', + 'makefile' => 'bz2-singlefile.make', + 'build' => TRUE, + 'md5' => '4f9d57f6caaf6ece0526d867327621cc', + 'options' => array('no-core' => NULL), + ), + 'contrib-destination' => array( + 'name' => 'Contrib-destination attribute', + 'makefile' => 'contrib-destination.make', + 'build' => TRUE, + 'md5' => '2aed36201ede1849ce43d9b7d6f7e9e1', + 'options' => array('no-core' => NULL, 'contrib-destination' => '.'), + ), + 'defaults' => array( + 'name' => 'Test defaults array.', + 'makefile' => 'defaults.make', + 'build' => TRUE, + 'md5' => 'e6c0d6b37cd8573cbd188742b95a274e', + 'options' => array('no-core' => NULL, 'contrib-destination' => '.'), + ), + 'file' => array( + 'name' => 'File extraction', + 'makefile' => 'file.make', + 'build' => TRUE, + 'md5' => '4e9883d6f9f6572af287635689c2545d', + 'options' => array('no-core' => NULL), + ), + 'file-extract' => array( + 'name' => 'Extract archives', + 'makefile' => 'file-extract.make', + 'build' => TRUE, + 'md5' => 'b43d271ab3510eb33c1e300c78893458', + // @todo This test often fails with concurrency set to more than one. + 'options' => array('no-core' => NULL, 'concurrency' => 1), + ), + 'get' => array( + 'name' => 'Test GET retrieval of projects', + 'makefile' => 'get.make', + 'build' => TRUE, + 'md5' => '4bf18507da89bed601548210c22a3bed', + 'options' => array('no-core' => NULL), + ), + 'git' => array( + 'name' => 'GIT integration', + 'makefile' => 'git.make', + 'build' => TRUE, + 'md5' => '4c80d78b50c89b5ba11a997bafec2b43', + 'options' => array('no-core' => NULL, 'no-gitinfofile' => NULL), + ), + 'git-simple' => array( + 'name' => 'Simple git integration', + 'makefile' => 'git-simple.make', + 'build' => TRUE, + 'md5' => '0147681209adef163a8ac2c0cff2a07e', + 'options' => array('no-core' => NULL, 'no-gitinfofile' => NULL), + ), + 'git-simple-8' => array( + 'name' => 'Simple git integration for D8', + 'makefile' => 'git-simple-8.make', + 'build' => TRUE, + 'options' => array('no-core' => NULL), + ), + 'gzip' => array( + 'name' => 'gzip', + 'makefile' => 'gzip.make', + 'build' => TRUE, + 'md5' => '25b514df18a87b655437388af083e22c', + 'options' => array('no-core' => NULL), + ), + 'ignore-checksums' => array( + 'name' => 'Ignore invalid checksum/s', + 'makefile' => 'md5-fail.make', + 'build' => TRUE, + 'md5' => 'f76ec174a775ce67f8e9edcb02336ef2', + 'options' => array('no-core' => NULL, 'ignore-checksums' => NULL), + ), + 'include' => array( + 'name' => 'Including files and property overrides', + 'makefile' => 'include.make', + 'build' => TRUE, + 'md5' => 'e2e230ec5eccaf5618050559ab11510d', + 'options' => array(), + ), + 'includes-git' => array( + 'name' => 'Including makefiles from remote repositories', + 'makefile' => 'includes-main.make', + 'build' => TRUE, + 'options' => array(), + ), + 'limit-libraries' => array( + 'name' => 'Limit libraries downloaded', + 'makefile' => 'limited-projects-libraries.make', + 'build' => TRUE, + 'md5' => 'cb0da4465d86eb34cafb167787862eb6', + 'options' => array('no-core' => NULL, 'libraries' => 'drush_make'), + ), + 'limit-libraries-multiple' => array( + 'name' => 'Limit multiple libraries downloaded', + 'makefile' => 'limited-projects-libraries.make', + 'build' => TRUE, + 'md5' => '7c10e6fc65728a77a2b0aed4ec2a29cd', + 'options' => array('no-core' => NULL, 'libraries' => 'drush_make,token'), + ), + 'limit-projects' => array( + 'name' => 'Limit projects downloaded', + 'makefile' => 'limited-projects-libraries.make', + 'build' => TRUE, + 'md5' => '3149650120e541d7d0fa577eef0ee9a3', + 'options' => array('no-core' => NULL, 'projects' => 'boxes'), + ), + 'limit-projects-multiple' => array( + 'name' => 'Limit multiple projects downloaded', + 'makefile' => 'limited-projects-libraries.make', + 'build' => TRUE, + 'md5' => 'ef8996c4d6c6f0d229e2237c73860071', + 'options' => array('no-core' => NULL, 'projects' => 'boxes,admin_menu'), + ), + 'md5-fail' => array( + 'name' => 'Failed MD5 validation test', + 'makefile' => 'md5-fail.make', + 'build' => FALSE, + 'md5' => FALSE, + 'options' => array('no-core' => NULL), + 'fail' => TRUE, + ), + 'md5-succeed' => array( + 'name' => 'MD5 validation', + 'makefile' => 'md5-succeed.make', + 'build' => TRUE, + 'md5' => 'f76ec174a775ce67f8e9edcb02336ef2', + 'options' => array('no-core' => NULL), + ), + 'no-patch-txt' => array( + 'name' => 'Test --no-patch-txt option', + 'makefile' => 'patches.make', + 'build' => TRUE, + 'md5' => '59267a04f98374ed5b0b75e90cefcd9c', + 'options' => array('no-core' => NULL, 'no-patch-txt' => NULL), + ), + 'options-array' => array( + 'name' => 'Test global options array', + 'makefile' => 'options-array.make', + 'build' => TRUE, + 'options' => array(), + ), + 'options-project' => array( + 'name' => 'Test per-project options array', + 'makefile' => 'options-project.make', + 'build' => TRUE, + 'options' => array(), + ), + 'patch' => array( + 'name' => 'Test patching and writing of PATCHES.txt file', + 'makefile' => 'patches.make', + 'build' => TRUE, + 'md5' => '536ee287344c24f47e0808622d7d091b', + 'options' => array('no-core' => NULL), + ), + 'recursion' => array( + 'name' => 'Recursion', + 'makefile' => 'recursion.make', + 'build' => TRUE, + 'md5' => 'cd095bd6dadb2f0d3e81f85f13685372', + 'options' => array( + 'no-core' => NULL, + 'contrib-destination' => 'profiles/drupal_forum', + ), + ), + 'recursion-override' => array( + 'name' => 'Recursion overrides', + 'makefile' => 'recursion-override.make', + 'build' => TRUE, + 'md5' => 'a13c3d5d416be9fa78569514844b96a2', + 'options' => array( + 'no-core' => NULL, + ), + ), + 'subtree' => array( + 'name' => 'Use subtree from downloaded archive', + 'makefile' => 'subtree.make', + 'build' => TRUE, + 'md5' => 'db3770d8b4c9ce77510cbbcc566da9b8', + 'options' => array('no-core' => NULL), + ), + 'svn' => array( + 'name' => 'SVN', + 'makefile' => 'svn.make', + 'build' => TRUE, + 'md5' => '0cb28a15958d7fc4bbf8bf6b00bc6514', + 'options' => array('no-core' => NULL), + ), + 'translations' => array( + 'name' => 'Translation downloads', + 'makefile' => 'translations.make', + 'options' => array( + 'translations' => 'es,pt-br', + 'no-core' => NULL, + ), + ), + 'translations-inside' => array( + 'name' => 'Translation downloads inside makefile', + 'makefile' => 'translations-inside.make', + ), + 'translations-inside7' => array( + 'name' => 'Translation downloads inside makefile, core 7.x', + 'makefile' => 'translations-inside7.make', + ), + 'use-distribution-as-core' => array( + 'name' => 'Use distribution as core', + 'makefile' => 'use-distribution-as-core.make', + 'build' => TRUE, + 'md5' => '643a603025a20d498eb583a1e7970bad', + 'options' => array(), + ), + ); + // Replicate ini tests for YAML format. + foreach ($tests as $id => $test) { + $id_yaml = $id . '-yaml'; + $tests[$id_yaml] = $test; + $tests[$id_yaml]['name'] = $tests[$id]['name'] . '(in YAML format)'; + $tests[$id_yaml]['makefile'] = $tests[$id]['makefile'] . '.yml'; + } + return $tests; + } + + /************************************************************************ + * * + * List of make tests (in alphabetical order, for easier navigation.) * + * * + ************************************************************************/ + + /** + * Test .info file writing and the use of a git reference cache for + * git downloads. + */ + function testInfoFileWritingGit() { + // Use the git-simple.make file. + $config = $this->getMakefile('git-simple'); + + $options = array('no-core' => NULL); + $makefile = $this->makefile_path . DIRECTORY_SEPARATOR . $config['makefile']; + $this->drush('make', array($makefile, UNISH_SANDBOX . '/test-build'), $options); + + // Test cck_signup.info file. + $this->assertFileExists(UNISH_SANDBOX . '/test-build/sites/all/modules/cck_signup/cck_signup.info'); + $contents = file_get_contents(UNISH_SANDBOX . '/test-build/sites/all/modules/cck_signup/cck_signup.info'); + $this->assertStringContainsString('; Information added by drush on 2011-07-27', $contents); + $this->assertStringContainsString('version = "2fe932c"', $contents); + $this->assertStringContainsString('project = "cck_signup"', $contents); + + // Verify that a reference cache was created. + $cache_dir = UNISH_CACHE . DIRECTORY_SEPARATOR . 'cache'; + $this->assertFileExists($cache_dir . '/git/cck_signup-' . md5('https://git.drupalcode.org/project/cck_signup.git')); + + // Test context_admin.info file. + $this->assertFileExists(UNISH_SANDBOX . '/test-build/sites/all/modules/context_admin/context_admin.info'); + $contents = file_get_contents(UNISH_SANDBOX . '/test-build/sites/all/modules/context_admin/context_admin.info'); + $this->assertStringContainsString('; Information added by drush on 2011-10-27', $contents); + $this->assertStringContainsString('version = "eb9f05e"', $contents); + $this->assertStringContainsString('project = "context_admin"', $contents); + + // Verify git reference cache exists. + $this->assertFileExists($cache_dir . '/git/context_admin-' . md5('https://git.drupalcode.org/project/context_admin.git')); + + // Text caption_filter .info rewrite. + $this->assertFileExists(UNISH_SANDBOX . '/test-build/sites/all/modules/contrib/caption_filter/caption_filter.info'); + $contents = file_get_contents(UNISH_SANDBOX . '/test-build/sites/all/modules/contrib/caption_filter/caption_filter.info'); + $this->assertStringContainsString('; Information added by drush on 2011-09-20', $contents); + $this->assertStringContainsString('version = "7.x-1.2+0-dev"', $contents); + $this->assertStringContainsString('project = "caption_filter"', $contents); + } + + /** + * Test .info file writing and the use of a git reference cache for + * git downloads. + */ + function testInfoYamlFileWritingGit() { + // Use the Drupal 8 .make file. + $config = $this->getMakefile('git-simple-8'); + + $options = array('no-core' => NULL); + $makefile = $this->makefile_path . DIRECTORY_SEPARATOR . $config['makefile']; + $this->drush('make', array($makefile, UNISH_SANDBOX . '/test-build'), $options); + + $this->assertFileExists(UNISH_SANDBOX . '/test-build/modules/honeypot/honeypot.info.yml'); + $contents = file_get_contents(UNISH_SANDBOX . '/test-build/modules/honeypot/honeypot.info.yml'); + $this->assertStringContainsString('# Information added by drush on 2015-09-03', $contents); + $this->assertStringContainsString("version: '8.x-1.x-dev'", $contents); + $this->assertStringContainsString("project: 'honeypot'", $contents); + } + + function testMakeBzr() { + // Silently skip bzr test if bzr is not installed. + exec('which bzr', $output, $whichBzrErrorCode); + if (!$whichBzrErrorCode) { + $this->runMakefileTest('bzr'); + } + else { + $this->markTestSkipped('bzr command is not available.'); + } + } + + function testMakeBZ2() { + // Silently skip bz2 test if bz2 is not installed. + exec('which bzip2', $output, $whichBzip2ErrorCode); + if (!$whichBzip2ErrorCode) { + $this->runMakefileTest('bz2'); + } + else { + $this->markTestSkipped('bzip2 command not available.'); + } + } + + /* TODO: http://download.gna.org/wkhtmltopdf/obsolete/linux/wkhtmltopdf-0.11.0_rc1-static-amd64.tar.bz2 cannot be downloaded any longer + function testMakeBZ2SingleFile() { + // Silently skip bz2 test if bz2 is not installed. + exec('which bzip2', $output, $whichBzip2ErrorCode); + if (!$whichBzip2ErrorCode) { + $this->runMakefileTest('bz2-singlefile'); + } + else { + $this->markTestSkipped('bzip2 command not available.'); + } + } + */ + + function testMakeContribDestination() { + $this->runMakefileTest('contrib-destination'); + } + + /** @group make.yml */ + function testMakeContribDestinationYaml() { + $this->runMakefileTest('contrib-destination-yaml'); + } + + function testMakeDefaults() { + $this->runMakefileTest('defaults'); + } + + /** @group make.yml */ + function testMakeDefaultsYaml() { + $this->runMakefileTest('defaults-yaml'); + } + + function testMakeFile() { + $this->markTestSkipped('Test depends on drush_make, which can no longer be downloaded from drupal.org.'); + $this->runMakefileTest('file'); + } + + function testMakeFileExtract() { + // Silently skip file extraction test if unzip is not installed. + exec('which unzip', $output, $whichUnzipErrorCode); + if (!$whichUnzipErrorCode) { + $this->runMakefileTest('file-extract'); + } + else { + $this->markTestSkipped('unzip command not available.'); + } + } + + function testMakeGet() { + $this->runMakefileTest('get'); + } + + function testMakeGit() { + $this->runMakefileTest('git'); + } + + function testMakeGitSimple() { + $this->runMakefileTest('git-simple'); + } + + function testMakeGZip() { + // Silently skip gzip test if either gzip or unzip is not installed. + exec('which gzip', $output, $whichGzipErrorCode); + if (!$whichGzipErrorCode) { + exec('which unzip', $output, $whichUnzipErrorCode); + if (!$whichUnzipErrorCode) { + $this->runMakefileTest('gzip'); + } + else { + $this->markTestSkipped('unzip command not available.'); + } + } + else { + $this->markTestSkipped('gzip command not available.'); + } + } + + function testMakeIgnoreChecksums() { + $this->markTestSkipped('Test depends on drush_make, which can no longer be downloaded from drupal.org.'); + $this->runMakefileTest('ignore-checksums'); + } + + function testMakeInclude() { + $this->runMakefileTest('include'); + } + + /** @group make.yml */ + function testMakeIncludeYaml() { + $this->runMakefileTest('include-yaml'); + } + + /** + * Test git support on includes directive. + */ + function testMakeIncludesGit() { + $config = $this->getMakefile('includes-git'); + $options = array(); + $makefile = $this->makefile_path . DIRECTORY_SEPARATOR . $config['makefile']; + $this->drush('make', array($makefile, UNISH_SANDBOX . '/test-git-includes'), $options); + + // Verify that core and example main module were downloaded. + $this->assertFileExists(UNISH_SANDBOX . '/test-git-includes/README.txt'); + $this->assertFileExists(UNISH_SANDBOX . '/test-git-includes/sites/all/modules/contrib/apachesolr/README.txt'); + + // Verify that module included in sub platform was downloaded. + $this->assertFileExists(UNISH_SANDBOX . '/test-git-includes/sites/all/modules/contrib/jquery_update/README.txt'); + } + + function testMakeLimitProjects() { + $this->runMakefileTest('limit-projects'); + $this->runMakefileTest('limit-projects-multiple'); + } + + function testMakeLimitLibraries() { + $this->runMakefileTest('limit-libraries'); + $this->runMakefileTest('limit-libraries-multiple'); + } + + + function testMakeMd5Fail() { + $this->runMakefileTest('md5-fail'); + } + + function testMakeMd5Succeed() { + $this->markTestSkipped('Test depends on drush_make, which can no longer be downloaded from drupal.org.'); + $this->runMakefileTest('md5-succeed'); + } + + /** + * Test that make_move_build() doesn't wipe out directories that it shouldn't. + */ + function testMakeMoveBuild() { + // Manually download a module. + $options = array( + 'default-major' => 6, // The makefile used below is core = "6.x". + 'destination' => UNISH_SANDBOX . '/sites/all/modules/contrib', + 'yes' => NULL, + 'dev' => NULL, + ); + $this->drush('pm-download', array('cck_signup'), $options); + + // Build a make file. + $config = $this->getMakefile('contrib-destination'); + $makefile = $this->makefile_path . DIRECTORY_SEPARATOR . $config['makefile']; + $this->drush('make', array($makefile, '.'), $config['options']); + + // Verify that the manually downloaded module still exists. + $this->assertFileExists(UNISH_SANDBOX . '/sites/all/modules/contrib/cck_signup/README.txt'); + } + + function testMakeNoPatchTxt() { + $this->runMakefileTest('no-patch-txt'); + } + + function testMakeNoRecursion() { + $config = $this->getMakefile('recursion'); + $makefile = $this->makefile_path . DIRECTORY_SEPARATOR . $config['makefile']; + + $install_directory = UNISH_SANDBOX . DIRECTORY_SEPARATOR . 'norecursion'; + $this->drush('make', array('--no-core', '--no-recursion', $makefile, $install_directory)); + $this->assertStringNotContainsString("ctools", $this->getOutput(), "Make with --no-recursion does not recurse into drupal_forum to download ctools."); + } + + /** + * Test no-core and working-copy in options array. + */ + function testMakeOptionsArray() { + // Use the goptions-array.make file. + $config = $this->getMakefile('options-array'); + + $makefile_path = dirname(__FILE__) . '/makefiles'; + $makefile = $makefile_path . '/' . $config['makefile']; + $install_directory = UNISH_SANDBOX . '/options-array'; + $this->drush('make', array($makefile, $install_directory)); + + // Test cck_signup .git/HEAD file. + $this->assertFileExists($install_directory . '/sites/all/modules/cck_signup/.git/HEAD'); + $contents = file_get_contents($install_directory . '/sites/all/modules/cck_signup/.git/HEAD'); + $this->assertStringContainsString('2fe932c', $contents); + + // Test context_admin .git/HEAD file. + $this->assertFileExists($install_directory . '/sites/all/modules/context_admin/.git/HEAD'); + $contents = file_get_contents($install_directory . '/sites/all/modules/context_admin/.git/HEAD'); + $this->assertStringContainsString('eb9f05e', $contents); + } + + /** + * Test per project working-copy option. + */ + function testMakeOptionsProject() { + // Use the options-project.make file. + $config = $this->getMakefile('options-project'); + + $makefile_path = dirname(__FILE__) . '/makefiles'; + $options = array('no-core' => NULL); + $makefile = $makefile_path . '/' . $config['makefile']; + $install_directory = UNISH_SANDBOX . '/options-project'; + $this->drush('make', array($makefile, $install_directory), $options); + + // Test context_admin .git/HEAD file. + $this->assertFileExists($install_directory . '/sites/all/modules/context_admin/.git/HEAD'); + $contents = file_get_contents($install_directory . '/sites/all/modules/context_admin/.git/HEAD'); + $this->assertStringContainsString('eb9f05e', $contents); + + // Test cck_signup .git/HEAD file does not exist. + $this->assertFileDoesNotExist($install_directory . '/sites/all/modules/cck_signup/.git/HEAD'); + + // Test caption_filter .git/HEAD file. + $this->assertFileExists($install_directory . '/sites/all/modules/contrib/caption_filter/.git/HEAD'); + $contents = file_get_contents($install_directory . '/sites/all/modules/contrib//caption_filter/.git/HEAD'); + $this->assertStringContainsString('c9794cf', $contents); + } + + function testMakePatch() { + $this->runMakefileTest('patch'); + } + + function testMakeRecursion() { + $this->runMakefileTest('recursion'); + } + + function testMakeRecursionOverride() { + // @todo This is skipped for now since the test relies on sourceforge. + // It can be replaced if a suitable module that installs projects (not + // libraries, which aren't properly overridable). + $this->markTestSkipped('skipping recursion-override test'); + return; + + // Silently skip file extraction test if unzip is not installed. + exec('which unzip', $output, $whichUnzipErrorCode); + if (!$whichUnzipErrorCode) { + $this->runMakefileTest('recursion-override'); + } + else { + $this->markTestSkipped('unzip command not available.'); + } + } + + function testMakeSubtree() { + // Silently skip subtree test if unzip is not installed. + exec('which unzip', $output, $whichUnzipErrorCode); + if (!$whichUnzipErrorCode) { + $config = $this->getMakefile('subtree'); + + $makefile = $this->makefile_path . DIRECTORY_SEPARATOR . $config['makefile']; + $install_directory = UNISH_SANDBOX . DIRECTORY_SEPARATOR . 'subtree'; + $this->drush('make', array('--no-core', $makefile, $install_directory)); + + $files['nivo-slider'] = array( + 'exists' => array( + 'jquery.nivo.slider.js', + 'jquery.nivo.slider.pack.js', + 'license.txt', + 'nivo-slider.css', + 'README', + ), + 'notexists' => array( + '__MACOSX', + 'nivo-slider', + ), + ); + $files['fullcalendar'] = array( + 'exists' => array( + 'fullcalendar.css', + 'fullcalendar.js', + 'fullcalendar.min.js', + 'fullcalendar.print.css', + 'gcal.js', + ), + 'notexists' => array( + 'changelog.txt', + 'demos', + 'fullcalendar', + 'GPL-LICENSE.txt', + 'jquery', + 'MIT-LICENSE.txt', + ), + ); + $basedir = $install_directory . DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR . 'all' . DIRECTORY_SEPARATOR . 'libraries'; + foreach ($files as $lib => $details) { + $dir = $basedir . DIRECTORY_SEPARATOR . $lib; + if (!empty($details['exists'])) { + foreach ($details['exists'] as $file) { + $this->assertFileExists($dir . DIRECTORY_SEPARATOR . $file); + } + } + + if (!empty($details['notexists'])) { + foreach ($details['notexists'] as $file) { + $this->assertFileDoesNotExist($dir . DIRECTORY_SEPARATOR . $file); + } + } + } + } + else { + $this->markTestSkipped('unzip command not available.'); + } + } + + function testMakeSvn() { + return $this->markTestSkipped('svn support is deprecated.'); + // Silently skip svn test if svn is not installed. + exec('which svn', $output, $whichSvnErrorCode); + if (!$whichSvnErrorCode) { + $this->runMakefileTest('svn'); + } + else { + $this->markTestSkipped('svn command not available.'); + } + } + + /** + * Translations can change arbitrarily, so these test for the existence of .po + * files, rather than trying to match a build hash. + */ + function testMakeTranslations() { + $config = $this->getMakefile('translations'); + + $makefile = $this->makefile_path . DIRECTORY_SEPARATOR . $config['makefile']; + $install_directory = UNISH_SANDBOX . '/translations'; + $this->drush('make', array($makefile, $install_directory), $config['options']); + + $po_files = array( + 'sites/all/modules/token/translations/pt-br.po', + 'sites/all/modules/token/translations/es.po', + ); + + foreach ($po_files as $po_file) { + $this->assertFileExists($install_directory . '/' . $po_file); + } + } + + /** + * Translations can change arbitrarily, so these test for the existence of .po + * files, rather than trying to match a build hash. + */ + function testMakeTranslationsInside() { + $config = $this->getMakefile('translations-inside'); + + $makefile = $this->makefile_path . '/' . $config['makefile']; + $install_directory = UNISH_SANDBOX . '/translations-inside'; + $this->drush('make', array($makefile, $install_directory)); + + $po_files = array( + 'profiles/default/translations/pt-br.po', + 'profiles/default/translations/es.po', + 'sites/all/modules/token/translations/pt-br.po', + 'sites/all/modules/token/translations/es.po', + 'modules/system/translations/pt-br.po', + 'modules/system/translations/es.po', + ); + + foreach ($po_files as $po_file) { + $this->assertFileExists($install_directory . '/' . $po_file); + } + } + + /** + * Translations can change arbitrarily, so these test for the existence of .po + * files, rather than trying to match a build hash. + */ + function testMakeTranslationsInside7() { + $config = $this->getMakefile('translations-inside7'); + + $makefile = $this->makefile_path . DIRECTORY_SEPARATOR . $config['makefile']; + $install_directory = UNISH_SANDBOX . '/translations-inside7'; + $this->drush('make', array($makefile, $install_directory)); + + $po_files = array( + 'profiles/minimal/translations/pt-br.po', + 'profiles/minimal/translations/es.po', + 'profiles/testing/translations/pt-br.po', + 'profiles/testing/translations/es.po', + 'profiles/standard/translations/pt-br.po', + 'profiles/standard/translations/es.po', + 'sites/all/modules/token/translations/pt-br.po', + 'sites/all/modules/token/translations/es.po', + 'modules/system/translations/pt-br.po', + 'modules/system/translations/es.po', + ); + + foreach ($po_files as $po_file) { + $this->assertFileExists($install_directory . '/' . $po_file); + } + } + + /** + * Test that a distribution can be used as a "core" project. + */ + function testMakeUseDistributionAsCore() { + $this->runMakefileTest('use-distribution-as-core'); + } + + /** + * Test that files without a core attribute are correctly identified. + */ + public function testNoCoreMakefileParsing() { + require_once __DIR__ . '/../commands/make/make.utilities.inc'; + + // INI. + $data = file_get_contents(__DIR__ . '/makefiles/no-core.make'); + $parsed = _make_determine_format($data); + $this->assertEquals('ini', $parsed['format']); + $this->assertEquals(42, $parsed['projects']['foo']['version']); + + // YAML. + $data = file_get_contents(__DIR__ . '/makefiles/no-core.make.yml'); + $parsed = _make_determine_format($data); + $this->assertEquals('yaml', $parsed['format']); + $this->assertEquals(42, $parsed['projects']['foo']['version']); + } + +} diff --git a/vendor/drush/drush/tests/makefiles/bz2-singlefile.make b/vendor/drush/drush/tests/makefiles/bz2-singlefile.make new file mode 100644 index 0000000000..bf31860ff8 --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/bz2-singlefile.make @@ -0,0 +1,8 @@ +api = 2 +core = 6.x + +; wkhtmltopdf-0.11.0_rc1-static-amd64.tar.bz2 contains the single file "wkhtmltopdf-amd64". +; This should move that single file to sites/all/libraries/wkhtmltopdf . +libraries[wkhtmltopdf][destination] = libraries +libraries[wkhtmltopdf][download][type] = get +libraries[wkhtmltopdf][download][url] = http://download.gna.org/wkhtmltopdf/obsolete/linux/wkhtmltopdf-0.11.0_rc1-static-amd64.tar.bz2 diff --git a/vendor/drush/drush/tests/makefiles/bz2.make b/vendor/drush/drush/tests/makefiles/bz2.make new file mode 100644 index 0000000000..b972f0d565 --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/bz2.make @@ -0,0 +1,8 @@ +core = 6.x +api = 2 + +; bison-1.30.tar.bz2 contains wrapper folder "bison-1.30/". +; This should move that wrapper folder to sites/all/libraries/bison/ . +libraries[bison][destination] = libraries +libraries[bison][download][type] = get +libraries[bison][download][url] = http://ftp.gnu.org/gnu/bison/bison-1.30.tar.bz2 diff --git a/vendor/drush/drush/tests/makefiles/bzr.make b/vendor/drush/drush/tests/makefiles/bzr.make new file mode 100644 index 0000000000..538eff23be --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/bzr.make @@ -0,0 +1,7 @@ +core = 6.x +api = 2 + +projects[mv][type] = "core" +projects[mv][download][type] = "bzr" +projects[mv][download][url] = "lp:mv" +projects[mv][download][revision] = 30 diff --git a/vendor/drush/drush/tests/makefiles/composer.json b/vendor/drush/drush/tests/makefiles/composer.json new file mode 100644 index 0000000000..bcebf81c75 --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/composer.json @@ -0,0 +1,53 @@ +{ + "name": "vendor/project", + "description": "Enter project description here", + "type": "project", + "repositories": { + "drupal_org": { + "type": "composer", + "url": "https://packages.drupal.org/7" + } + }, + "require": { + "composer/installers": "^1.0.20", + "cweagans/composer-patches": "~1.0", + "drupal/drupal": "7.*", + "drupal/wysiwyg": "2.1", + "drupal/features": "1.0-beta4", + "drupal/context": "3.0-beta2" + }, + "minimum-stability": "dev", + "prefer-stable": true, + "extra": { + "installer-paths": { + "core": [ + "type:drupal-core" + ], + "docroot/modules/contrib/{$name}": [ + "type:drupal-module" + ], + "docroot/profiles/contrib/{$name}": [ + "type:drupal-profile" + ], + "docroot/themes/contrib/{$name}": [ + "type:drupal-theme" + ], + "drush/contrib/{$name}": [ + "type:drupal-drush" + ] + }, + "patches": { + "drupal/wysiwyg": { + "Enter drupal/wysiwyg patch #0 description here": "https://drupal.org/files/0001-feature.inc-from-624018-211.patch", + "Enter drupal/wysiwyg patch #1 description here": "patches-local-test-wysiwyg.patch" + }, + "drupal/features": { + "Enter drupal/features patch #0 description here": "https://drupal.org/files/issues/features-drush-backend-invoke-25.patch" + }, + "drupal/context": { + "Enter drupal/context patch #0 description here": "https://drupal.org/files/issues/custom_blocks_arent_editable-make.patch", + "Enter drupal/context patch #1 description here": "https://drupal.org/files/issues/661094-context-permissions.patch" + } + } + } +} diff --git a/vendor/drush/drush/tests/makefiles/composer.lock b/vendor/drush/drush/tests/makefiles/composer.lock new file mode 100644 index 0000000000..fd7109a431 --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/composer.lock @@ -0,0 +1,623 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "56569fe34e56cb046e9e4a04bdb17b4b", + "packages": [ + { + "name": "composer/installers", + "version": "v1.6.0", + "source": { + "type": "git", + "url": "https://github.com/composer/installers.git", + "reference": "cfcca6b1b60bc4974324efb5783c13dca6932b5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/installers/zipball/cfcca6b1b60bc4974324efb5783c13dca6932b5b", + "reference": "cfcca6b1b60bc4974324efb5783c13dca6932b5b", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0" + }, + "replace": { + "roundcube/plugin-installer": "*", + "shama/baton": "*" + }, + "require-dev": { + "composer/composer": "1.0.*@dev", + "phpunit/phpunit": "^4.8.36" + }, + "type": "composer-plugin", + "extra": { + "class": "Composer\\Installers\\Plugin", + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Installers\\": "src/Composer/Installers" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kyle Robinson Young", + "email": "kyle@dontkry.com", + "homepage": "https://github.com/shama" + } + ], + "description": "A multi-framework Composer library installer", + "homepage": "https://composer.github.io/installers/", + "keywords": [ + "Craft", + "Dolibarr", + "Eliasis", + "Hurad", + "ImageCMS", + "Kanboard", + "Lan Management System", + "MODX Evo", + "Mautic", + "Maya", + "OXID", + "Plentymarkets", + "Porto", + "RadPHP", + "SMF", + "Thelia", + "WolfCMS", + "agl", + "aimeos", + "annotatecms", + "attogram", + "bitrix", + "cakephp", + "chef", + "cockpit", + "codeigniter", + "concrete5", + "croogo", + "dokuwiki", + "drupal", + "eZ Platform", + "elgg", + "expressionengine", + "fuelphp", + "grav", + "installer", + "itop", + "joomla", + "kohana", + "laravel", + "lavalite", + "lithium", + "magento", + "majima", + "mako", + "mediawiki", + "modulework", + "modx", + "moodle", + "osclass", + "phpbb", + "piwik", + "ppi", + "puppet", + "pxcms", + "reindex", + "roundcube", + "shopware", + "silverstripe", + "sydes", + "symfony", + "typo3", + "wordpress", + "yawik", + "zend", + "zikula" + ], + "time": "2018-08-27T06:10:37+00:00" + }, + { + "name": "cweagans/composer-patches", + "version": "1.6.6", + "source": { + "type": "git", + "url": "https://github.com/cweagans/composer-patches.git", + "reference": "1d89dcc730e7f42426c434b88261fcfb3bce651e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cweagans/composer-patches/zipball/1d89dcc730e7f42426c434b88261fcfb3bce651e", + "reference": "1d89dcc730e7f42426c434b88261fcfb3bce651e", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0", + "php": ">=5.3.0" + }, + "require-dev": { + "composer/composer": "~1.0", + "phpunit/phpunit": "~4.6" + }, + "type": "composer-plugin", + "extra": { + "class": "cweagans\\Composer\\Patches" + }, + "autoload": { + "psr-4": { + "cweagans\\Composer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Cameron Eagans", + "email": "me@cweagans.net" + } + ], + "description": "Provides a way to patch Composer packages.", + "time": "2018-10-24T15:51:16+00:00" + }, + { + "name": "drupal/context", + "version": "3.0.0-beta2", + "source": { + "type": "git", + "url": "https://git.drupalcode.org/project/context.git", + "reference": "7.x-3.0-beta2" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/context-7.x-3.0-beta2.zip", + "reference": "7.x-3.0-beta2", + "shasum": "e2aebffe4cf68b63a08f6bf73717bdd560cde44c" + }, + "require": { + "drupal/ctools": "*", + "drupal/drupal": "~7.0" + }, + "type": "drupal-module", + "extra": { + "branch-alias": { + "dev-3.x": "3.x-dev" + }, + "drupal": { + "version": "7.x-3.0-beta2", + "datestamp": "1533185792", + "security-coverage": { + "status": "not-covered", + "message": "Beta releases are not covered by Drupal security advisories." + } + }, + "patches_applied": { + "Enter drupal/context patch #0 description here": "https://drupal.org/files/issues/custom_blocks_arent_editable-make.patch", + "Enter drupal/context patch #1 description here": "https://drupal.org/files/issues/661094-context-permissions.patch" + } + }, + "notification-url": "https://packages.drupal.org/7/downloads", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "NormySan", + "homepage": "https://www.drupal.org/user/112352" + }, + { + "name": "Steven Jones", + "homepage": "https://www.drupal.org/user/99644" + }, + { + "name": "alex_b", + "homepage": "https://www.drupal.org/user/53995" + }, + { + "name": "boshtian", + "homepage": "https://www.drupal.org/user/1773456" + }, + { + "name": "colan", + "homepage": "https://www.drupal.org/user/58704" + }, + { + "name": "emanaton", + "homepage": "https://www.drupal.org/user/120853" + }, + { + "name": "febbraro", + "homepage": "https://www.drupal.org/user/43670" + }, + { + "name": "fizk", + "homepage": "https://www.drupal.org/user/473174" + }, + { + "name": "hass", + "homepage": "https://www.drupal.org/user/85918" + }, + { + "name": "hefox", + "homepage": "https://www.drupal.org/user/426416" + }, + { + "name": "hyrcan", + "homepage": "https://www.drupal.org/user/26618" + }, + { + "name": "jmiccolis", + "homepage": "https://www.drupal.org/user/31731" + }, + { + "name": "nedjo", + "homepage": "https://www.drupal.org/user/4481" + }, + { + "name": "tekante", + "homepage": "https://www.drupal.org/user/640024" + }, + { + "name": "yhahn", + "homepage": "https://www.drupal.org/user/264833" + } + ], + "description": "Provide modules with a cache that lasts for a single page request.", + "homepage": "https://www.drupal.org/project/context", + "support": { + "source": "https://git.drupalcode.org/project/context" + } + }, + { + "name": "drupal/ctools", + "version": "1.15.0", + "source": { + "type": "git", + "url": "https://git.drupalcode.org/project/ctools.git", + "reference": "7.x-1.15" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/ctools-7.x-1.15.zip", + "reference": "7.x-1.15", + "shasum": "d55e992f3c68dc663505b3e68a0418fee17cf960" + }, + "require": { + "drupal/drupal": "~7.0" + }, + "require-dev": { + "drupal/advanced_help": "*", + "drupal/page_manager": "*", + "drupal/panels": "*", + "drupal/views": "*" + }, + "type": "drupal-module", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + }, + "drupal": { + "version": "7.x-1.15", + "datestamp": "1549603684", + "security-coverage": { + "status": "covered", + "message": "Covered by Drupal's security advisory policy" + } + } + }, + "notification-url": "https://packages.drupal.org/7/downloads", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "EclipseGc", + "homepage": "https://www.drupal.org/user/61203" + }, + { + "name": "damiankloip", + "homepage": "https://www.drupal.org/user/1037976" + }, + { + "name": "dawehner", + "homepage": "https://www.drupal.org/user/99340" + }, + { + "name": "esmerel", + "homepage": "https://www.drupal.org/user/164022" + }, + { + "name": "japerry", + "homepage": "https://www.drupal.org/user/45640" + }, + { + "name": "joelpittet", + "homepage": "https://www.drupal.org/user/160302" + }, + { + "name": "merlinofchaos", + "homepage": "https://www.drupal.org/user/26979" + }, + { + "name": "neclimdul", + "homepage": "https://www.drupal.org/user/48673" + }, + { + "name": "sdboyer", + "homepage": "https://www.drupal.org/user/146719" + }, + { + "name": "sun", + "homepage": "https://www.drupal.org/user/54136" + }, + { + "name": "tim.plunkett", + "homepage": "https://www.drupal.org/user/241634" + } + ], + "description": "A library of helpful tools by Merlin of Chaos.", + "homepage": "https://www.drupal.org/project/ctools", + "support": { + "source": "https://git.drupalcode.org/project/ctools" + } + }, + { + "name": "drupal/drupal", + "version": "7.67.0", + "source": { + "type": "git", + "url": "https://git.drupalcode.org/project/drupal.git", + "reference": "7.67" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/drupal-7.67.zip", + "reference": "7.67", + "shasum": "b7143116f747df1f5a07674f9b7132cc98589b55" + }, + "type": "drupal-core", + "extra": { + "branch-alias": { + "dev-7.x": "7.x-dev" + }, + "drupal": { + "version": "7.67", + "datestamp": "1557377067", + "security-coverage": { + "status": "covered", + "message": "Covered by Drupal's security advisory policy" + } + } + }, + "notification-url": "https://packages.drupal.org/7/downloads", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "David_Rothstein", + "homepage": "https://www.drupal.org/user/124982" + }, + { + "name": "Dries", + "homepage": "https://www.drupal.org/user/1" + }, + { + "name": "Drupal", + "homepage": "https://www.drupal.org/user/3" + }, + { + "name": "Fabianx", + "homepage": "https://www.drupal.org/user/693738" + }, + { + "name": "Gábor Hojtsy", + "homepage": "https://www.drupal.org/user/4166" + }, + { + "name": "alexpott", + "homepage": "https://www.drupal.org/user/157725" + }, + { + "name": "catch", + "homepage": "https://www.drupal.org/user/35733" + }, + { + "name": "cilefen", + "homepage": "https://www.drupal.org/user/1850070" + }, + { + "name": "drumm", + "homepage": "https://www.drupal.org/user/3064" + }, + { + "name": "effulgentsia", + "homepage": "https://www.drupal.org/user/78040" + }, + { + "name": "larowlan", + "homepage": "https://www.drupal.org/user/395439" + }, + { + "name": "lauriii", + "homepage": "https://www.drupal.org/user/1078742" + }, + { + "name": "plach", + "homepage": "https://www.drupal.org/user/183211" + }, + { + "name": "stefan.r", + "homepage": "https://www.drupal.org/user/551886" + }, + { + "name": "webchick", + "homepage": "https://www.drupal.org/user/24967" + }, + { + "name": "xjm", + "homepage": "https://www.drupal.org/user/65776" + }, + { + "name": "yoroy", + "homepage": "https://www.drupal.org/user/41502" + } + ], + "homepage": "https://www.drupal.org/project/drupal", + "support": { + "source": "https://git.drupalcode.org/project/drupal" + } + }, + { + "name": "drupal/features", + "version": "1.0.0-beta4", + "source": { + "type": "git", + "url": "https://git.drupalcode.org/project/features.git", + "reference": "7.x-1.0-beta4" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/features-7.x-1.0-beta4.zip", + "reference": "7.x-1.0-beta4", + "shasum": "c18cc2730473f98a7d16a082f3b5f160d0ff9795" + }, + "require": { + "drupal/drupal": "~7.0" + }, + "type": "drupal-module", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + }, + "drupal": { + "version": "7.x-1.0-beta4", + "datestamp": "1460561197", + "security-coverage": { + "status": "not-covered", + "message": "Beta releases are not covered by Drupal security advisories." + } + }, + "patches_applied": { + "Enter drupal/features patch #0 description here": "https://drupal.org/files/issues/features-drush-backend-invoke-25.patch" + } + }, + "notification-url": "https://packages.drupal.org/7/downloads", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "dawehner", + "homepage": "https://www.drupal.org/user/99340" + }, + { + "name": "e2thex", + "homepage": "https://www.drupal.org/user/189123" + }, + { + "name": "febbraro", + "homepage": "https://www.drupal.org/user/43670" + }, + { + "name": "jmiccolis", + "homepage": "https://www.drupal.org/user/31731" + }, + { + "name": "joseph.olstad", + "homepage": "https://www.drupal.org/user/1321830" + }, + { + "name": "mpotter", + "homepage": "https://www.drupal.org/user/616192" + }, + { + "name": "nedjo", + "homepage": "https://www.drupal.org/user/4481" + }, + { + "name": "tim.plunkett", + "homepage": "https://www.drupal.org/user/241634" + } + ], + "description": "Provides feature management for Drupal.", + "homepage": "https://www.drupal.org/project/features", + "support": { + "source": "https://git.drupalcode.org/project/features" + } + }, + { + "name": "drupal/wysiwyg", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://git.drupalcode.org/project/wysiwyg.git", + "reference": "7.x-2.1" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/wysiwyg-7.x-2.1.zip", + "reference": "7.x-2.1", + "shasum": "3be46576436e84ae32f091f1b09287d3f7cc4748" + }, + "require": { + "drupal/drupal": "~7.0" + }, + "type": "drupal-module", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + }, + "drupal": { + "version": "7.x-2.1", + "datestamp": "1390358005", + "security-coverage": { + "status": "covered", + "message": "Covered by Drupal's security advisory policy" + } + }, + "patches_applied": { + "Enter drupal/wysiwyg patch #0 description here": "https://drupal.org/files/0001-feature.inc-from-624018-211.patch", + "Enter drupal/wysiwyg patch #1 description here": "patches-local-test-wysiwyg.patch" + } + }, + "notification-url": "https://packages.drupal.org/7/downloads", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "TwoD", + "homepage": "https://www.drupal.org/user/244227" + }, + { + "name": "sun", + "homepage": "https://www.drupal.org/user/54136" + } + ], + "description": "Allows to edit content with client-side editors.", + "homepage": "https://www.drupal.org/project/wysiwyg", + "support": { + "source": "https://git.drupalcode.org/project/wysiwyg" + } + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": [], + "prefer-stable": true, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/vendor/drush/drush/tests/makefiles/contrib-destination.make b/vendor/drush/drush/tests/makefiles/contrib-destination.make new file mode 100644 index 0000000000..cbf8dc7ca3 --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/contrib-destination.make @@ -0,0 +1,12 @@ +core = "6.x" +api = 2 + +projects[boxes][subdir] = "contrib" +projects[boxes][version] = "1.0" + +projects[admin][subdir] = "contrib" +projects[admin][version] = "2.0" + +projects[zen][subdir] = "contrib" +projects[zen][version] = "2.1" +projects[zen][type] = "theme" diff --git a/vendor/drush/drush/tests/makefiles/contrib-destination.make.yml b/vendor/drush/drush/tests/makefiles/contrib-destination.make.yml new file mode 100644 index 0000000000..3ff3356578 --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/contrib-destination.make.yml @@ -0,0 +1,13 @@ +core: '6.x' +api: 2 +projects: + boxes: + subdir: 'contrib' + version: '1.0' + admin: + subdir: 'contrib' + version: '2.0' + zen: + subdir: 'contrib' + version: '2.1' + type: 'theme' diff --git a/vendor/drush/drush/tests/makefiles/defaults.make b/vendor/drush/drush/tests/makefiles/defaults.make new file mode 100644 index 0000000000..368b8e4a75 --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/defaults.make @@ -0,0 +1,12 @@ +core = "6.x" +api = 2 + +defaults[projects][subdir] = "contrib" + +projects[boxes][version] = "1.0" + +projects[admin][version] = "2.0" + +; Override project default +projects[devel][version] = "1.27" +projects[devel][subdir] = "development" diff --git a/vendor/drush/drush/tests/makefiles/defaults.make.yml b/vendor/drush/drush/tests/makefiles/defaults.make.yml new file mode 100644 index 0000000000..33c8b3716a --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/defaults.make.yml @@ -0,0 +1,16 @@ +core: 6.x +api: 2 + +defaults: + projects: + subdir: "contrib" + +projects: + boxes: + version: "1.0" + admin: + version: "2.0" + # Override project default + devel: + version: "1.27" + subdir: "development" diff --git a/vendor/drush/drush/tests/makefiles/file-extract.make b/vendor/drush/drush/tests/makefiles/file-extract.make new file mode 100644 index 0000000000..f2e05b54c9 --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/file-extract.make @@ -0,0 +1,17 @@ +core = 6.x +api = 2 + +projects[admin_menu][download][type] = "file" +projects[admin_menu][download][url] = "http://ftp.drupal.org/files/projects/admin_menu-6.x-1.0-beta.zip?param=value" +projects[admin_menu][download][md5] = "673ec1bb431113142016d5eb0a0ef77f" + +projects[devel][download][type] = "file" +projects[devel][download][url] = "http://ftp.drupal.org/files/projects/devel-6.x-1.0.tar.gz?param=value" +projects[devel][download][md5] = "3dbd3cd4c15f001c1ac7067ca58c74ff" + +libraries[vegas][download][type] = "get" +libraries[vegas][download][url] = "https://github.com/jaysalvat/vegas/zipball/v1.2" + +libraries[autopager][download][type] = "get" +libraries[autopager][download][url] = "https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/jquery-autopager/jquery.autopager-1.0.0.js" +libraries[autopager][directory_name] = "autopager" diff --git a/vendor/drush/drush/tests/makefiles/file.make b/vendor/drush/drush/tests/makefiles/file.make new file mode 100644 index 0000000000..ebff66d876 --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/file.make @@ -0,0 +1,11 @@ +core = 6.x +api = 2 + +libraries[drush_make][download][type] = "file" +libraries[drush_make][download][url] = "https://cgit.drupalcode.org/drush_make/plain/README.txt?h=6.x-3.x&id=8d4e770aeb1cfc180d48b76b70f9bf5ee250eade" +libraries[drush_make][download][filename] = "README.txt" + +; Single file download +libraries[responsiveslides][download][type] = "get" +libraries[responsiveslides][download][url] = "https://raw.github.com/viljamis/ResponsiveSlides.js/v1.54/responsiveslides.min.js" +libraries[responsiveslides][download][filename] = "responsiveslides.js" diff --git a/vendor/drush/drush/tests/makefiles/get.make b/vendor/drush/drush/tests/makefiles/get.make new file mode 100644 index 0000000000..0fc20cec7a --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/get.make @@ -0,0 +1,9 @@ +core = 6.x +api = 2 + +; Tarball file download +libraries[drush_make][download][type] = file +libraries[drush_make][download][url] = http://ftp.drupal.org/files/projects/drush_make-6.x-2.0-beta8.tar.gz +libraries[drush_make][directory_name] = drush_make +libraries[drush_make][destination] = libraries +libraries[drush_make][lock] = Locked diff --git a/vendor/drush/drush/tests/makefiles/git-simple-8.make b/vendor/drush/drush/tests/makefiles/git-simple-8.make new file mode 100644 index 0000000000..008b8302e6 --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/git-simple-8.make @@ -0,0 +1,5 @@ +core = 8.x +api = 2 + +projects[honeypot][download][branch] = "8.x-1.x" +projects[honeypot][download][revision] = "419477a" diff --git a/vendor/drush/drush/tests/makefiles/git-simple.make b/vendor/drush/drush/tests/makefiles/git-simple.make new file mode 100644 index 0000000000..3061d70fcd --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/git-simple.make @@ -0,0 +1,16 @@ +core = 6.x +api = 2 + +; Test that make defaults to download type of git if any download +; parameters are present. +projects[cck_signup][download][revision] = "2fe932c" + +; Test that make defaults to download type of git if any download +; parameters are present. +projects[context_admin][download][revision] = "eb9f05e" + +; When branch is passed in addition to revision, .info file rewriting has better versioning. +projects[caption_filter][subdir] = "contrib" +projects[caption_filter][download][type] = "git" +projects[caption_filter][download][branch] = "7.x-1.x" +projects[caption_filter][download][revision] = "c9794cf" diff --git a/vendor/drush/drush/tests/makefiles/git.make b/vendor/drush/drush/tests/makefiles/git.make new file mode 100644 index 0000000000..266f8fb4ad --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/git.make @@ -0,0 +1,31 @@ +core = 6.x +api = 2 + +; Test that a specific tag can be pulled. +projects[tao][type] = theme +projects[tao][download][type] = git +projects[tao][download][tag] = 6.x-3.2 + +; Test that a branch can be pulled. We use a super-old "stale" branch in the +; Drupalbin project that we expect not to change. +projects[drupalbin][type] = profile +projects[drupalbin][download][type] = git +projects[drupalbin][download][branch] = 5.x-1.x + +; Test that a specific revision can be pulled. Note that provision is not +; actually a module. +projects[visitor][type] = module +projects[visitor][download][type] = git +projects[visitor][download][revision] = 5f256032cd4bcc2db45c962306d12c85131388ef + +; Test a non-Drupal.org repository. +projects[os_lightbox][type] = "module" +projects[os_lightbox][download][type] = "git" +projects[os_lightbox][download][url] = "https://github.com/opensourcery/os_lightbox.git" +projects[os_lightbox][download][revision] = "8d60090f2" + +; Test a refspec fetch. +projects[storypal][type] = module +projects[storypal][download][type] = git +projects[storypal][download][url] = https://git.drupal.org/project/storypal.git +projects[storypal][download][refspec] = refs/tags/7.x-1.0 diff --git a/vendor/drush/drush/tests/makefiles/gzip.make b/vendor/drush/drush/tests/makefiles/gzip.make new file mode 100644 index 0000000000..b5989f261b --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/gzip.make @@ -0,0 +1,10 @@ +api = 2 +core = 6.x + +; getid3 doesn't contain a wrapper folder. All files are in the root of the archive. +libraries[getid3][destination] = libraries +libraries[getid3][download][type] = get +libraries[getid3][download][url] = "https://github.com/JamesHeinrich/getID3/archive/v1.9.8.zip" +libraries[getid3][directory_name] = getid3 +; http://drupal.org/node/1336886 +libraries[getid3][patch][] = "https://www.drupal.org/files/issues/1336886-11.patch" diff --git a/vendor/drush/drush/tests/makefiles/include.make b/vendor/drush/drush/tests/makefiles/include.make new file mode 100644 index 0000000000..33ea8bd51d --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/include.make @@ -0,0 +1,12 @@ +core = 6.x +api = 2 + +projects[drupal] = 6.17 +projects[eldorado_superfly][version] = 1.1 +includes[] = included.make + +; Final build should contain: +; - drupal 6.17 +; - token +; - cck 2.6 +; - eldorado_superfly 1.1 \ No newline at end of file diff --git a/vendor/drush/drush/tests/makefiles/include.make.yml b/vendor/drush/drush/tests/makefiles/include.make.yml new file mode 100644 index 0000000000..fdd259049e --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/include.make.yml @@ -0,0 +1,16 @@ +core: 6.x +api: 2 + +projects: + drupal: "6.17" + eldorado_superfly: + version: "1.1" + +includes: + - included.make.yml + +# Final build should contain: +# - drupal 6.17 +# - token +# - cck 2.6 +# - eldorado_superfly 1.1 diff --git a/vendor/drush/drush/tests/makefiles/included.make b/vendor/drush/drush/tests/makefiles/included.make new file mode 100644 index 0000000000..7d1c03092b --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/included.make @@ -0,0 +1,6 @@ +core = 6.x +api = 2 + +projects[cck] = 2.6 +projects[token] = FALSE +includes[] = included2.make \ No newline at end of file diff --git a/vendor/drush/drush/tests/makefiles/included.make.yml b/vendor/drush/drush/tests/makefiles/included.make.yml new file mode 100644 index 0000000000..6893810b83 --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/included.make.yml @@ -0,0 +1,9 @@ +core: 6.x +api: 2 + +projects: + cck: "2.6" + token: FALSE + +includes: + - included2.make.yml diff --git a/vendor/drush/drush/tests/makefiles/included2.make b/vendor/drush/drush/tests/makefiles/included2.make new file mode 100644 index 0000000000..c8532d2c43 --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/included2.make @@ -0,0 +1,6 @@ +core = 6.x +api = 2 + +projects[views] = 2.11 +projects[token] = 1.13 +projects[eldorado_superfly] = 1.0 \ No newline at end of file diff --git a/vendor/drush/drush/tests/makefiles/included2.make.yml b/vendor/drush/drush/tests/makefiles/included2.make.yml new file mode 100644 index 0000000000..530e58feaa --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/included2.make.yml @@ -0,0 +1,7 @@ +core: 6.x +api: 2 + +projects: + views: "2.11" + token: "1.13" + eldorado_superfly: "1.0" diff --git a/vendor/drush/drush/tests/makefiles/includes-main.make b/vendor/drush/drush/tests/makefiles/includes-main.make new file mode 100644 index 0000000000..a3d5902e80 --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/includes-main.make @@ -0,0 +1,14 @@ +core = 7.x +api = 2 + +; Main makefile containing drupal core +includes[platform][makefile] = 'tests/makefiles/includes-platform.make' +includes[platform][download][type] = "git" +includes[platform][download][url] = "https://github.com/pmatias/drush.git" +includes[platform][download][branch] = "includes-git-support" + +; Sub platform makefile +includes[subplatform][makefile] = 'tests/makefiles/includes-sub-platform.make' +includes[subplatform][download][type] = "git" +includes[subplatform][download][url] = "https://github.com/pmatias/drush.git" +includes[subplatform][download][branch] = "includes-git-support" diff --git a/vendor/drush/drush/tests/makefiles/includes-platform.make b/vendor/drush/drush/tests/makefiles/includes-platform.make new file mode 100644 index 0000000000..c885c5b4b3 --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/includes-platform.make @@ -0,0 +1,10 @@ +api = 2 +core = 7.x + +; Drupal core +projects[drupal][type] = "core" +projects[drupal][version] = 7.34 + +; Contrib modules +projects[apachesolr][subdir] = "contrib" +projects[apachesolr][version] = "1.6" diff --git a/vendor/drush/drush/tests/makefiles/includes-sub-platform.make b/vendor/drush/drush/tests/makefiles/includes-sub-platform.make new file mode 100644 index 0000000000..8e1f403e60 --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/includes-sub-platform.make @@ -0,0 +1,6 @@ +api = 2 +core = 7.x + +; Contrib modules +projects[jquery_update][version] = "2.4" +projects[jquery_update][subdir] = "contrib" diff --git a/vendor/drush/drush/tests/makefiles/limited-projects-libraries.make b/vendor/drush/drush/tests/makefiles/limited-projects-libraries.make new file mode 100644 index 0000000000..72a0f15251 --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/limited-projects-libraries.make @@ -0,0 +1,15 @@ +core = "7.x" +api = 2 + +projects[boxes][version] = "1.0-beta7" + +projects[admin_menu][version] = "3.0-rc1" + +; Use drupal.org as a nice stable source of libraries. +libraries[drush_make][download][type] = "file" +libraries[drush_make][download][url] = "http://ftp.drupal.org/files/projects/drush_make-6.x-2.3.tar.gz" + +; Use drupal.org as a nice stable source of libraries. +libraries[token][download][type] = "file" +libraries[token][download][url] = "http://ftp.drupal.org/files/projects/token-7.x-1.0-rc1.tar.gz" + diff --git a/vendor/drush/drush/tests/makefiles/lock-default.make.yml b/vendor/drush/drush/tests/makefiles/lock-default.make.yml new file mode 100644 index 0000000000..451742a98e --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/lock-default.make.yml @@ -0,0 +1,8 @@ +api: 2 +core: 7 + +projects: + drupal: + version: 7.42 + atom: + version: ~ diff --git a/vendor/drush/drush/tests/makefiles/lock-git.make.yml b/vendor/drush/drush/tests/makefiles/lock-git.make.yml new file mode 100644 index 0000000000..9acc722168 --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/lock-git.make.yml @@ -0,0 +1,9 @@ +api: 2 +core: 7 + +projects: + drupal: + version: 7.42 + atom: + download: + url: https://git.drupalcode.org/project/atom.git diff --git a/vendor/drush/drush/tests/makefiles/lockfiles/default.lock.yml b/vendor/drush/drush/tests/makefiles/lockfiles/default.lock.yml new file mode 100644 index 0000000000..a73bda0cae --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/lockfiles/default.lock.yml @@ -0,0 +1,8 @@ +core: 7.x +api: 2 +projects: + drupal: + version: '7.42' + type: core + atom: + version: 1.0-beta1 diff --git a/vendor/drush/drush/tests/makefiles/lockfiles/git.lock.yml b/vendor/drush/drush/tests/makefiles/lockfiles/git.lock.yml new file mode 100644 index 0000000000..b2adaebcd2 --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/lockfiles/git.lock.yml @@ -0,0 +1,12 @@ +core: 7.x +api: 2 +projects: + drupal: + version: '7.42' + type: core + atom: + download: + url: 'https://git.drupalcode.org/project/atom.git' + type: git + branch: 8.x-1.x + revision: c6b3f3fe1edd24641c56d080bb94ad71824d9b42 diff --git a/vendor/drush/drush/tests/makefiles/md5-fail.make b/vendor/drush/drush/tests/makefiles/md5-fail.make new file mode 100644 index 0000000000..dae44eb6f7 --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/md5-fail.make @@ -0,0 +1,7 @@ +core = 6.x +api = 2 + +libraries[drush_make][download][type] = "file" +libraries[drush_make][download][url] = "https://cgit.drupalcode.org/drush_make/plain/README.txt?h=6.x-3.x&id=8d4e770aeb1cfc180d48b76b70f9bf5ee250eade" +libraries[drush_make][download][filename] = "README.txt" +libraries[drush_make][download][md5] = "- fail -" diff --git a/vendor/drush/drush/tests/makefiles/md5-succeed.make b/vendor/drush/drush/tests/makefiles/md5-succeed.make new file mode 100644 index 0000000000..a10608bb4b --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/md5-succeed.make @@ -0,0 +1,7 @@ +core = 6.x +api = 2 + +libraries[drush_make][download][type] = "file" +libraries[drush_make][download][url] = "https://cgit.drupalcode.org/drush_make/plain/README.txt?h=6.x-3.x&id=8d4e770aeb1cfc180d48b76b70f9bf5ee250eade" +libraries[drush_make][download][filename] = "README.txt" +libraries[drush_make][download][md5] = "c8968d801a953b9ea735364d6f3dfabc" diff --git a/vendor/drush/drush/tests/makefiles/no-core.make b/vendor/drush/drush/tests/makefiles/no-core.make new file mode 100644 index 0000000000..53dc627ff6 --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/no-core.make @@ -0,0 +1,3 @@ +; A file that does not include the `core` attribute (as is possible for included .make files). +; ============================================================================================ +projects[foo][version] = 42 diff --git a/vendor/drush/drush/tests/makefiles/no-core.make.yml b/vendor/drush/drush/tests/makefiles/no-core.make.yml new file mode 100644 index 0000000000..872223c057 --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/no-core.make.yml @@ -0,0 +1,5 @@ +# A file that does not include the `core` attribute (as is possible for included .make files). +# ============================================================================================ +projects: + foo: + version: 42 diff --git a/vendor/drush/drush/tests/makefiles/options-array.make b/vendor/drush/drush/tests/makefiles/options-array.make new file mode 100644 index 0000000000..7dc9258217 --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/options-array.make @@ -0,0 +1,15 @@ +core = 6.x +api = 2 + +; Test that make preserves VCS directories. +options[working-copy] = TRUE +; Test that make does not require a Drupal core project. +options[no-core] = TRUE + +; Test that make defaults to download type of git if any download +; parameters are present. +projects[cck_signup][download][revision] = "2fe932c" + +; Test that make defaults to download type of git if any download +; parameters are present. +projects[context_admin][download][revision] = "eb9f05e" diff --git a/vendor/drush/drush/tests/makefiles/options-project.make b/vendor/drush/drush/tests/makefiles/options-project.make new file mode 100644 index 0000000000..1726f76028 --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/options-project.make @@ -0,0 +1,19 @@ +core = 6.x +api = 2 + +; Test that revision passed in uses git to download project. +projects[context_admin][download][revision] = "eb9f05e" + +; Test that make preserves VCS directories. +projects[context_admin][options][working-copy] = TRUE + +; Test that make defaults to download type of git if any download +; parameters are present. +projects[cck_signup][download][revision] = "2fe932c" + +; When branch is passed in addition to revision, .info file rewriting has better versioning. +projects[caption_filter][subdir] = "contrib" +projects[caption_filter][download][type] = "git" +projects[caption_filter][download][branch] = "7.x-1.x" +projects[caption_filter][download][revision] = "c9794cf" +projects[caption_filter][options][working-copy] = TRUE diff --git a/vendor/drush/drush/tests/makefiles/patches-local-test-wysiwyg.patch b/vendor/drush/drush/tests/makefiles/patches-local-test-wysiwyg.patch new file mode 100644 index 0000000000..b6ef7316d2 --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/patches-local-test-wysiwyg.patch @@ -0,0 +1,17 @@ +diff --git a/wysiwyg.module b/wysiwyg.module +index 771cbd7..647ac31 100644 +--- a/wysiwyg.module ++++ b/wysiwyg.module +@@ -112,6 +112,12 @@ function wysiwyg_theme() { + } + + /** ++ * This patch doesn't really do anything. It just adds a comment to ++ * wysiwyg.module so that we can test Drush Make's support of applying patches ++ * that are on the local filesystem. ++ */ ++ ++/** + * Implementation of hook_help(). + */ + function wysiwyg_help($path, $arg) { diff --git a/vendor/drush/drush/tests/makefiles/patches.make b/vendor/drush/drush/tests/makefiles/patches.make new file mode 100644 index 0000000000..08c653c73e --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/patches.make @@ -0,0 +1,19 @@ +core = 7.x +api = 2 + +; Test that patches work +projects[wysiwyg][version] = "2.1" +; http://drupal.org/node/624018#comment-5098162 +projects[wysiwyg][patch][] = "http://drupal.org/files/0001-feature.inc-from-624018-211.patch" +; Test local filesystem patches +projects[wysiwyg][patch][] = "patches-local-test-wysiwyg.patch" + +; http://drupal.org/node/1152908#comment-5010536 +projects[features][version] = "1.0-beta4" +projects[features][patch][] = "http://drupal.org/files/issues/features-drush-backend-invoke-25.patch" + +projects[context][version] = "3.0-beta2" +; http://drupal.org/node/1251406#comment-5020012 +projects[context][patch][] = "http://drupal.org/files/issues/custom_blocks_arent_editable-make.patch" +; http://drupal.org/node/661094#comment-4735064 +projects[context][patch][] = "http://drupal.org/files/issues/661094-context-permissions.patch" diff --git a/vendor/drush/drush/tests/makefiles/patches.make.yml b/vendor/drush/drush/tests/makefiles/patches.make.yml new file mode 100644 index 0000000000..318d6e1f4e --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/patches.make.yml @@ -0,0 +1,18 @@ +core: 7.x +api: '2' +projects: + wysiwyg: + version: '2.1' + patch: + - 'http://drupal.org/files/0001-feature.inc-from-624018-211.patch' + - patches-local-test-wysiwyg.patch + features: + version: 1.0-beta4 + patch: + - 'http://drupal.org/files/issues/features-drush-backend-invoke-25.patch' + context: + version: 3.0-beta2 + patch: + - 'http://drupal.org/files/issues/custom_blocks_arent_editable-make.patch' + - 'http://drupal.org/files/issues/661094-context-permissions.patch' + diff --git a/vendor/drush/drush/tests/makefiles/qd-devel.make b/vendor/drush/drush/tests/makefiles/qd-devel.make new file mode 100644 index 0000000000..2c7712a396 --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/qd-devel.make @@ -0,0 +1,6 @@ +core = "7.x" +api = 2 + +projects[drupal][version] = "7.78" +defaults[projects][subdir] = "contrib" +projects[devel] = "1.7" diff --git a/vendor/drush/drush/tests/makefiles/recursion-override.make b/vendor/drush/drush/tests/makefiles/recursion-override.make new file mode 100644 index 0000000000..bfbb3116ee --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/recursion-override.make @@ -0,0 +1,9 @@ +core = 7.x +api = 2 + +defaults[projects][subdir] = "contrib" + +; Custom formatters contains the library module v1, we want v2. +projects[libraries][version] = 2.1 + +projects[custom_formatters][version] = 2.2 diff --git a/vendor/drush/drush/tests/makefiles/recursion.make b/vendor/drush/drush/tests/makefiles/recursion.make new file mode 100644 index 0000000000..8edabadd40 --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/recursion.make @@ -0,0 +1,7 @@ +core = 7.x +api = 2 + +projects[drupal_forum][type] = "profile" +projects[drupal_forum][version] = "1.0-alpha2" + +projects[views] = "3.0-rc3" diff --git a/vendor/drush/drush/tests/makefiles/subtree.make b/vendor/drush/drush/tests/makefiles/subtree.make new file mode 100644 index 0000000000..bfd7536ec8 --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/subtree.make @@ -0,0 +1,15 @@ +core = 6.x +api = 2 + +; nivo-slider2.7.1.zip contains Mac OS X metadata (a "__MACOSX/" folder) in addition to the desired content in the "nivo-slider/" folder. +; Using the "subtree" directive, we tell Drush Make we only want the "nivo-slider/" folder. +libraries[nivo-slider][download][type] = get +libraries[nivo-slider][download][url] = https://github.com/downloads/gilbitron/Nivo-Slider/nivo-slider2.7.1.zip +libraries[nivo-slider][download][sha1] = bd8e14b82f5b9c6f533a4e1aa26a790cd66c3cb9 +libraries[nivo-slider][download][subtree] = nivo-slider + +; Tell Drush Make we only want the "fullcalendar-1.5.3/fullcalendar/" folder. +libraries[fullcalendar][download][type] = get +libraries[fullcalendar][download][url] = https://github.com/arshaw/fullcalendar/releases/download/v1.5.3/fullcalendar-1.5.3.zip +libraries[fullcalendar][download][sha1] = c7219b1ddd2b11ccdbf83ebd116872affbc45d7a +libraries[fullcalendar][download][subtree] = fullcalendar-1.5.3/fullcalendar diff --git a/vendor/drush/drush/tests/makefiles/svn.make b/vendor/drush/drush/tests/makefiles/svn.make new file mode 100644 index 0000000000..2e6952275f --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/svn.make @@ -0,0 +1,7 @@ +core = 6.x +api = 2 + +; Test an SVN repo with revision flag. +libraries[flot][download][type] = svn +libraries[flot][download][url] = http://flot.googlecode.com/svn/trunk +libraries[flot][download][revision] = 10 diff --git a/vendor/drush/drush/tests/makefiles/translations-inside.make b/vendor/drush/drush/tests/makefiles/translations-inside.make new file mode 100644 index 0000000000..1bdb8e1f14 --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/translations-inside.make @@ -0,0 +1,9 @@ +core = 6.x +api = 2 + +translations[] = es +translations[] = pt-br + +projects[drupal][version] = "6.22" +projects[token][version] = "1.18" + diff --git a/vendor/drush/drush/tests/makefiles/translations-inside7.make b/vendor/drush/drush/tests/makefiles/translations-inside7.make new file mode 100644 index 0000000000..a0298dbcf5 --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/translations-inside7.make @@ -0,0 +1,9 @@ +core = 7.x +api = 2 + +translations[] = es +translations[] = pt-br + +projects[drupal][version] = "7.10" +projects[token][version] = "1.0-beta7" + diff --git a/vendor/drush/drush/tests/makefiles/translations.make b/vendor/drush/drush/tests/makefiles/translations.make new file mode 100644 index 0000000000..6ae114eb1c --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/translations.make @@ -0,0 +1,6 @@ +core = 6.x +api = 2 + +projects[token][version] = "1.18" + +; @TODO: Add a project that uses a custom l10n_server diff --git a/vendor/drush/drush/tests/makefiles/use-distribution-as-core.make b/vendor/drush/drush/tests/makefiles/use-distribution-as-core.make new file mode 100644 index 0000000000..489c520f3a --- /dev/null +++ b/vendor/drush/drush/tests/makefiles/use-distribution-as-core.make @@ -0,0 +1,7 @@ +api = 2 +core = 7.x + +projects[commerce_kickstart][type] = core +projects[commerce_kickstart][version] = "7.x-1.19" +projects[oauth2_server][type] = module +projects[oauth2_server][version] = "1.0-beta2" diff --git a/vendor/drush/drush/tests/outputFormatUnitTest.php b/vendor/drush/drush/tests/outputFormatUnitTest.php new file mode 100644 index 0000000000..b6055cc14d --- /dev/null +++ b/vendor/drush/drush/tests/outputFormatUnitTest.php @@ -0,0 +1,166 @@ +assertEquals($expected, trim(drush_format($data, array(), $format)), $name . ': '. $format); + } + + public function provider() { + $json = '{"a":{"b":2,"c":3},"d":{"e":5,"f":6}}'; + if (version_compare(phpversion(), '5.4.0', '>=')) { + $json = json_encode(json_decode($json), JSON_PRETTY_PRINT); + } + + return array( + array( + 'name' => 'String test', + 'format' => 'string', + 'data' => array('drush version' => '6.0-dev'), + 'expected' => '6.0-dev', + ), + array( + 'name' => 'List test', + 'format' => 'list', + 'data' => array('drush version' => '6.0-dev'), + 'expected' => '6.0-dev', + ), + array( + 'name' => 'Key-value test', + 'format' => 'key-value', + 'data' => array('drush version' => '6.0-dev'), + 'expected' => 'drush version : 6.0-dev', + ), +// array( +// 'name' => 'Table test', +// 'format' => 'table', +// 'data' => array( +// 'a' => array('b' => 2, 'c' => 3), +// 'd' => array('b' => 5, 'c' => 6), +// ), +// 'expected' => "b c +// 2 3 +// 5 6", +// ), + array( + 'name' => 'print-r test', + 'format' => 'print-r', + 'data' => array( + 'a' => array('b' => 2, 'c' => 3), + 'd' => array('b' => 5, 'c' => 6), + ), + 'expected' => "Array +( + [a] => Array + ( + [b] => 2 + [c] => 3 + ) + + [d] => Array + ( + [b] => 5 + [c] => 6 + ) + +)", + ), + array( + 'name' => 'json test', + 'format' => 'json', + 'data' => array( + 'a' => array('b' => 2, 'c' => 3), + 'd' => array('e' => 5, 'f' => 6), + ), + 'expected' => $json, + ), +// array( +// 'name' => 'key-value test 1d array', +// 'format' => 'key-value', +// 'data' => array( +// 'b' => 'Two B or ! Two B, that is the comparison', +// 'c' => 'I see that C has gone to Sea', +// ), +// 'expected' => "b : Two B or ! Two B, that is the comparison +// c : I see that C has gone to Sea", +// ), +// array( +// 'name' => 'key-value test 2d array', +// 'format' => 'key-value', +// 'data' => array( +// 'a' => array( +// 'b' => 'Two B or ! Two B, that is the comparison', +// 'c' => 'I see that C has gone to Sea', +// ), +// 'd' => array( +// 'e' => 'Elephants and electron microscopes', +// 'f' => 'My margin is too small', +// ) +// ), +// 'expected' => "a : Two B or ! Two B, that is the comparison +// I see that C has gone to Sea +// d : Elephants and electron microscopes +// My margin is too small", +// ), + array( + 'name' => 'export test', + 'format' => 'var_export', + 'data' => array( + 'a' => array('b' => 2, 'c' => 3), + 'd' => array('e' => 5, 'f' => 6), + ), + 'expected' => "array( + 'a' => array( + 'b' => 2, + 'c' => 3, + ), + 'd' => array( + 'e' => 5, + 'f' => 6, + ), +)", + ), +// array( +// 'name' => 'config test', +// 'format' => 'config', +// 'data' => array( +// 'a' => array('b' => 2, 'c' => 3), +// 'd' => array('e' => 5, 'f' => 6), +// ), +// 'expected' => "\$config[\"a\"] = array ( +// 'b' => 2, +// 'c' => 3, +//); +//\$config[\"d\"] = array ( +// 'e' => 5, +// 'f' => 6, +//);", +// ), + array( + 'name' => 'variables test', + 'format' => 'variables', + 'data' => array( + 'a' => array('b' => 2, 'c' => 3), + 'd' => array('e' => 5, 'f' => 6), + ), + 'expected' => "\$a[\"b\"] = 2; +\$a[\"c\"] = 3; +\$d[\"e\"] = 5; +\$d[\"f\"] = 6;", + ), + ); + } +} diff --git a/vendor/drush/drush/tests/phpunit.xml.dist b/vendor/drush/drush/tests/phpunit.xml.dist new file mode 100644 index 0000000000..57e125cf2f --- /dev/null +++ b/vendor/drush/drush/tests/phpunit.xml.dist @@ -0,0 +1,39 @@ + + + + + . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + . + + diff --git a/vendor/drush/drush/tests/pmDownloadTest.php b/vendor/drush/drush/tests/pmDownloadTest.php new file mode 100644 index 0000000000..c39d4175e6 --- /dev/null +++ b/vendor/drush/drush/tests/pmDownloadTest.php @@ -0,0 +1,122 @@ +drush('pm-download', array('devel'), array('cache' => NULL, 'skip' => NULL)); // No FirePHP + $this->assertFileExists(UNISH_SANDBOX . '/devel/README.txt'); + + $this->drush('pm-download', array('drupal-7.500'), array('backend' => NULL), NULL, NULL, self::EXIT_ERROR); + $parsed = $this->parse_backend_output($this->getOutput()); + $this->assertArrayHasKey('DRUSH_PM_COULD_NOT_FIND_VERSION', $parsed['error_log']); + } + + protected function exectedReadme() { + if (version_compare(UNISH_DRUPAL_MAJOR_VERSION . UNISH_DRUPAL_MINOR_VERSION, '8.8.0', '>=')) { + return 'README.md'; + } + return 'README.txt'; + } + + // @todo Test pure drush commandfile projects. They get special destination. + public function testDestination() { + $expectedReadme = $this->exectedReadme(); + + // Setup two Drupal sites. Skip install for speed. + $sites = $this->setUpDrupal(2, FALSE); + $uri = key($sites); + $root = $this->webroot(); + + // Common options for the invocations below. + $devel_options = array( + 'cache' => NULL, + 'skip' => NULL, // No FirePHP + 'strict' => 0, // Invoke from script: do not verify options + ); + + // Default to Drupal sitewide directory. + $options = array( + 'root' => $root, + 'uri' => $uri, + ) + $devel_options; + $this->drush('pm-download', array('devel'), $options); + $this->assertFileExists($root . '/' . $this->drupalSitewideDirectory() . '/modules/devel/' . $expectedReadme); + + // --use-site-dir + // Expand above $options. + $options += array('use-site-dir' => NULL); + $this->drush('pm-download', array('devel'), $options); + $this->assertFileExists("$root/sites/$uri/modules/devel/$expectedReadme"); + unish_file_delete_recursive("{$root}/sites/{$uri}/modules/devel", TRUE); + + // If we are in site specific dir, then download belongs there. + $path_stage = "$root/sites/$uri"; + // dir gets created by --use-site-dir above, + $options = $devel_options; + $this->drush('pm-download', array('devel'), $options, NULL, $path_stage); + $this->assertFileExists($path_stage . '/modules/devel/' . $expectedReadme); + + // --destination with absolute path. + $destination = UNISH_SANDBOX . '/test-destination1'; + mkdir($destination); + $options = array( + 'destination' => $destination, + ) + $devel_options; + $this->drush('pm-download', array('devel'), $options); + $this->assertFileExists($destination . '/devel/README.txt'); + + // --destination with a relative path. + $destination = 'test-destination2'; + mkdir(UNISH_SANDBOX . '/' . $destination); + $options = array( + 'destination' => $destination, + ) + $devel_options; + $this->drush('pm-download', array('devel'), $options); + $this->assertFileExists(UNISH_SANDBOX . '/' . $destination . '/devel/README.txt'); + } + + public function testSelect() { + $options = array( + 'select' => NULL, + 'choice' => 0, // Cancel. + ); + // --select. Specify 6.x since that has so many releases. + $this->drush('pm-download', array('devel-6.x'), $options, NULL, NULL, CommandUnishTestCase::UNISH_EXITCODE_USER_ABORT); + $items = $this->getOutputAsList(); + $output = $this->getOutput(); + // 4 items are: Select message + Cancel + 2 versions. + $this->assertEquals(4, count($items), '--select offerred 2 options.'); + $this->assertStringContainsString('6.x-1.x-dev', $output, 'Dev release was shown by --select.'); + + // --select --dev. Specify 6.x since that has so many releases. + $this->drush('pm-download', array('devel-6.x'), $options + array('dev' => NULL), NULL, NULL, CommandUnishTestCase::UNISH_EXITCODE_USER_ABORT); + $items = $this->getOutputAsList(); + $output = $this->getOutput(); + // 12 items are: Select message + Cancel + 1 option. + $this->assertEquals(3, count($items), '--select --dev expected to offer only one option.'); + $this->assertStringContainsString('6.x-1.x-dev', $output, 'Assure that --dev lists the only dev release.'); + + // --select --all. Specify 5.x since this is frozen. + $this->drush('pm-download', array('devel-5.x'), $options + array('all' => NULL), NULL, NULL, CommandUnishTestCase::UNISH_EXITCODE_USER_ABORT); + $items = $this->getOutputAsList(); + $output = $this->getOutput(); + // 12 items are: Select message + Cancel + 9 options. + $this->assertEquals(11, count($items), '--select --all offerred 8 options.'); + $this->assertStringContainsString('5.x-0.1', $output, 'Assure that --all lists very old releases.'); + } + + public function testPackageHandler() { + $options = array( + 'cache' => NULL, + 'package-handler' => 'git_drupalorg', + 'yes' => NULL, + ); + $this->drush('pm-download', array('devel'), $options); + $this->assertFileExists(UNISH_SANDBOX . '/devel/README.txt'); + $this->assertFileExists(UNISH_SANDBOX . '/devel/.git'); + } +} diff --git a/vendor/drush/drush/tests/pmEnDisUnListInfoTest.php b/vendor/drush/drush/tests/pmEnDisUnListInfoTest.php new file mode 100644 index 0000000000..7ed5cb01d5 --- /dev/null +++ b/vendor/drush/drush/tests/pmEnDisUnListInfoTest.php @@ -0,0 +1,165 @@ += 9) { + $moduleToTest = 'token'; + $expectedTitle = 'Token'; + } + $sites = $this->setUpDrupal(1, TRUE); + $options_no_pipe = array( + 'yes' => NULL, + 'root' => $this->webroot(), + 'uri' => key($sites), + 'cache' => NULL, + 'skip' => NULL, // No FirePHP + 'strict' => 0, // Don't validate options + ); + $options = $options_no_pipe + array( + 'pipe' => NULL, + ); + + // Test pm-download downloads a module and pm-list lists it. + $this->drush('pm-download', array($moduleToTest), $options); + $this->drush('pm-list', array(), $options + array('no-core' => NULL, 'status' => 'disabled,not installed')); + $out = $this->getOutput(); + $list = $this->getOutputAsList(); + $this->assertTrue(in_array($moduleToTest, $list)); + + // Test pm-enable enables a module and shows the permissions it provides. + $this->drush('pm-enable', array($moduleToTest), $options_no_pipe); + $output = $this->getOutput(); + if ($moduleToTest == 'devel') { + $this->assertStringContainsString('access devel information', $output); + } + + // Test pm-list shows the module as enabled. + $this->drush('pm-list', array(), $options + array('status' => 'enabled')); + $list = $this->getOutputAsList(); + $this->assertTrue(in_array($moduleToTest, $list)); + + // Test pm-info shows some module info. + $this->drush('pm-info', array($moduleToTest), $options); + $output = $this->getOutputFromJSON($moduleToTest); + $expected = array( + 'extension' => $moduleToTest, + 'project' => $moduleToTest, + 'type' => 'module', + 'title' => $expectedTitle, + 'status' => 'enabled', + ); + foreach ($expected as $key => $value) { + $this->assertEquals($output->{$key}, $value); + } + + // Check output fields in pm-list + $this->drush('pm-list', [], $options + ['format' => 'json']); + $extensionProperties = (array)$this->getOutputFromJSON(); + $moduleProperties = (array)$extensionProperties[$moduleToTest]; + $this->assertEquals($moduleToTest, $moduleProperties['project']); + $this->assertEquals('Enabled', $moduleProperties['status']); + $this->assertEquals('Module', $moduleProperties['type']); + + // Test pm-projectinfo shows some project info. + $this->drush('pm-projectinfo', array($moduleToTest), $options); + $output = $this->getOutputFromJSON($moduleToTest); + $expected = array( + 'label' => "$expectedTitle ($moduleToTest)", + 'type' => 'module', + 'status' => '1', + ); + foreach ($expected as $key => $value) { + $this->assertEquals($output->{$key}, $value); + } + + // Test the testing install profile theme is installed. + $themeToCheck = 'garland'; + if (UNISH_DRUPAL_MAJOR_VERSION >= 7) { + $themeToCheck = 'bartik'; + } + if (UNISH_DRUPAL_MAJOR_VERSION >= 8) { + $themeToCheck = 'stark'; + // UNISH_DRUPAL_MINOR_VERSION is something like ".8.0-alpha1". + if (UNISH_DRUPAL_MINOR_VERSION[1] <= 8) { + $themeToCheck = 'classy'; + $this->markTestSkipped('Project "panels", used in this test, no longer works with earlier versions of Drupal 8.'); + } + } + if (UNISH_DRUPAL_MAJOR_VERSION >= 9) { + $themeToCheck = 'stark'; + } + $this->assertContains($themeToCheck, $list, 'Themes are in the pm-list'); + + // Test cache was cleared after enabling a module. + $table = UNISH_DRUPAL_MAJOR_VERSION >= 8 ? 'router' : 'menu_router'; + $path = UNISH_DRUPAL_MAJOR_VERSION >= 8 ? '/admin/config/development/devel' : (UNISH_DRUPAL_MAJOR_VERSION == 7 ? 'devel/settings' : 'admin/settings/devel'); + $this->drush('sql-query', array("SELECT path FROM $table WHERE path = '$path';"), array('root' => $this->webroot(), 'uri' => key($sites))); + $list = $this->getOutputAsList(); + $this->assertTrue(in_array($path, $list), 'Cache was cleared after modules were enabled'); + + // Test pm-list filtering. + $this->drush('pm-list', array(), $options + array('package' => 'Core')); + $list = $this->getOutputAsList(); + $this->assertFalse(in_array($moduleToTest, $list), 'Module to test is not part of core package'); + + // Test module disabling. + if (UNISH_DRUPAL_MAJOR_VERSION <= 7) { + $this->drush('pm-disable', array($moduleToTest), $options); + $this->drush('pm-list', array(), $options + array('status' => 'disabled')); + $list = $this->getOutputAsList(); + $this->assertTrue(in_array($moduleToTest, $list)); + } + + // Test module uninstall. + $this->drush('pm-uninstall', array($moduleToTest), $options); + $this->drush('pm-list', array(), $options + array('status' => 'not installed', 'type' => 'module')); + $list = $this->getOutputAsList(); + $this->assertTrue(in_array($moduleToTest, $list)); + + // Test pm-enable is able to download dependencies. + // @todo pathauto has no usable D8 release yet. + // Also, Drupal 6 has no stable releases any longer, so resolve-dependencies are inconvenient to test. + if (UNISH_DRUPAL_MAJOR_VERSION ==7) { + $this->drush('pm-download', array('pathauto'), $options); + $this->drush('pm-enable', array('pathauto'), $options + array('resolve-dependencies' => TRUE)); + $this->drush('pm-list', array(), $options + array('status' => 'enabled')); + $list = $this->getOutputAsList(); + $this->assertTrue(in_array('token', $list)); + } + + if (UNISH_DRUPAL_MAJOR_VERSION !=6) { + if (substr(UNISH_DRUPAL_MINOR_VERSION, 0, 2) == '.4') { + $this->markTestSkipped("panels module broken with Drupal 8.4.x"); + } + // Test that pm-enable downloads missing projects and dependencies. + $this->drush('pm-enable', array('panels'), $options + array('resolve-dependencies' => TRUE)); + $this->drush('pm-list', array(), $options + array('status' => 'enabled')); + $list = $this->getOutputAsList(); + $this->assertTrue(in_array('ctools', $list)); + } + + // Test that pm-enable downloads missing projects + // and dependencies with project namespace (date:date_popup). + if (UNISH_DRUPAL_MAJOR_VERSION == 7) { + $this->drush('pm-enable', array('date_datepicker_inline'), $options + array('resolve-dependencies' => TRUE)); + $this->drush('pm-list', array(), $options + array('status' => 'enabled')); + $list = $this->getOutputAsList(); + $this->assertTrue(in_array('date_popup', $list)); + } + + } +} diff --git a/vendor/drush/drush/tests/pmReleaseNotesTest.php b/vendor/drush/drush/tests/pmReleaseNotesTest.php new file mode 100644 index 0000000000..cd3b95c517 --- /dev/null +++ b/vendor/drush/drush/tests/pmReleaseNotesTest.php @@ -0,0 +1,20 @@ +drush('pm-releasenotes', array('drupal-7.1')); + $output = $this->getOutput(); + $this->assertStringContainsString("RELEASE NOTES FOR 'DRUPAL' PROJECT, VERSION 7.1", $output); + $this->assertStringContainsString('SA-CORE-2011-001 - Drupal core - Multiple vulnerabilities', $output); + } +} + diff --git a/vendor/drush/drush/tests/pmRequestTest.php b/vendor/drush/drush/tests/pmRequestTest.php new file mode 100644 index 0000000000..d9fd606aca --- /dev/null +++ b/vendor/drush/drush/tests/pmRequestTest.php @@ -0,0 +1,108 @@ +setUpDrupal(1, FALSE); + $uri = key($sites); + $root = $this->webroot(); + + $drupal_version = UNISH_DRUPAL_MAJOR_VERSION; + // We expect this oddity: that Drupal 9 is treated like 8 in release_xml + if ($drupal_version == 9) { + $drupal_version = 8; + } + + // Common options for below commands. + $options = array( + 'root' => $root, + 'uri' => $uri, + 'format' => 'yaml', + ); + + // Tests for core versions. + $is_core = 1; + + $version = ''; + $expected = <<drush('php-eval', array("return pm_parse_version('${version}', ${is_core})"), $options); + $this->assertEquals($expected, $this->getOutput(), 'Core version not provided. Pick version of the bootstrapped site.'); + + $version = '5'; + $expected = <<drush('php-eval', array("return pm_parse_version('${version}', ${is_core})"), $options); + $this->assertEquals($expected, $this->getOutput(), 'Core version provided.'); + + // Tests for non-core versions. + $is_core = 0; + + $version = ''; + $expected = <<drush('php-eval', array("return pm_parse_version('${version}', ${is_core})"), $options); + $this->assertEquals($expected, $this->getOutput(), 'Project version not provided. Pick version of the bootstrapped site.'); + + $version = '1.0'; + $expected = <<drush('php-eval', array("return pm_parse_version('${version}')"), $options); + $this->assertEquals($expected, $this->getOutput()); + + $version = '1.x'; + $expected = <<drush('php-eval', array("return pm_parse_version('${version}')"), $options); + $this->assertEquals($expected, $this->getOutput()); + } +} diff --git a/vendor/drush/drush/tests/pmRequestUnitTest.php b/vendor/drush/drush/tests/pmRequestUnitTest.php new file mode 100644 index 0000000000..d680767044 --- /dev/null +++ b/vendor/drush/drush/tests/pmRequestUnitTest.php @@ -0,0 +1,266 @@ +assertEquals('8.0.0-beta2', $version_parts['version']); + $this->assertEquals('8.x', $version_parts['drupal_version']); + $this->assertEquals('8', $version_parts['version_major']); + $this->assertEquals('0', $version_parts['version_minor']); + $this->assertEquals('0', $version_parts['version_patch']); + $this->assertEquals('beta2', $version_parts['version_extra']); + $this->assertEquals('8.0.0-beta2', $version_parts['project_version']); + + $version = '8.0.x-dev'; + $version_parts = pm_parse_version($version, TRUE); + $this->assertEquals('8.0.x-dev', $version_parts['version']); + $this->assertEquals('8.x', $version_parts['drupal_version']); + $this->assertEquals('8', $version_parts['version_major']); + $this->assertEquals('0', $version_parts['version_minor']); + $this->assertEquals('', $version_parts['version_patch']); + $this->assertEquals('dev', $version_parts['version_extra']); + $this->assertEquals('8.0.x-dev', $version_parts['project_version']); + + $version = '8.0.0-beta3+252-dev'; + $version_parts = pm_parse_version($version, TRUE); + $this->assertEquals('8.0.0-beta3+252-dev', $version_parts['version']); + $this->assertEquals('8.x', $version_parts['drupal_version']); + $this->assertEquals('8', $version_parts['version_major']); + $this->assertEquals('0', $version_parts['version_minor']); + $this->assertEquals('0', $version_parts['version_patch']); + $this->assertEquals('beta3', $version_parts['version_extra']); + $this->assertEquals('252', $version_parts['version_offset']); + $this->assertEquals('8.0.0-beta3+252-dev', $version_parts['project_version']); + + $version = '8.1.x'; + $version_parts = pm_parse_version($version, TRUE); + $this->assertEquals('8.1.x-dev', $version_parts['version']); + $this->assertEquals('8.x', $version_parts['drupal_version']); + $this->assertEquals('8', $version_parts['version_major']); + $this->assertEquals('1', $version_parts['version_minor']); + $this->assertEquals('', $version_parts['version_patch']); + $this->assertEquals('dev', $version_parts['version_extra']); + $this->assertEquals('8.1.x-dev', $version_parts['project_version']); + + $version = '8.0.1'; + $version_parts = pm_parse_version($version, TRUE); + $this->assertEquals('8.0.1', $version_parts['version']); + $this->assertEquals('8.x', $version_parts['drupal_version']); + $this->assertEquals('8', $version_parts['version_major']); + $this->assertEquals('0', $version_parts['version_minor']); + $this->assertEquals('1', $version_parts['version_patch']); + $this->assertEquals('', $version_parts['version_extra']); + $this->assertEquals('8.0.1', $version_parts['project_version']); + } + + /** + * Tests for pm_parse_version() with drupal version scheme for core. + */ + public function testVersionParserCore() { + _drush_add_commandfiles(array(DRUSH_BASE_PATH . '/commands/pm')); + + drush_set_option('default-major', UNISH_DRUPAL_MAJOR_VERSION); + + $version = ''; + $version_parts = pm_parse_version($version, TRUE); + $this->assertEquals('', $version_parts['version']); + $this->assertEquals(UNISH_DRUPAL_MAJOR_VERSION . '.x', $version_parts['drupal_version']); + $this->assertEquals(UNISH_DRUPAL_MAJOR_VERSION, $version_parts['version_major']); + $this->assertEquals('', $version_parts['version_minor']); + $this->assertEquals('', $version_parts['version_patch']); + $this->assertEquals('', $version_parts['version_extra']); + $this->assertEquals('', $version_parts['project_version']); + + // We use version 5 in these tests to avoid false positives from + // pm_parse_version(), in case it was picking drush's default-major. + + $version = '5'; + $version_parts = pm_parse_version($version, TRUE); + $this->assertEquals('', $version_parts['version']); + $this->assertEquals('5.x', $version_parts['drupal_version']); + $this->assertEquals('', $version_parts['project_version']); + $this->assertEquals('5', $version_parts['version_major']); + $this->assertEquals('', $version_parts['version_minor']); + $this->assertEquals('', $version_parts['version_patch']); + $this->assertEquals('', $version_parts['version_extra']); + + $version = '5.x'; + $version_parts = pm_parse_version($version, TRUE); + $this->assertEquals('5.x-dev', $version_parts['version']); + $this->assertEquals('5.x', $version_parts['drupal_version']); + $this->assertEquals('5', $version_parts['version_major']); + $this->assertEquals('', $version_parts['version_minor']); + $this->assertEquals('', $version_parts['version_patch']); + $this->assertEquals('dev', $version_parts['version_extra']); + $this->assertEquals('5.x-dev', $version_parts['project_version']); + + $version = '5.x-dev'; + $version_parts = pm_parse_version($version, TRUE); + $this->assertEquals('5.x-dev', $version_parts['version']); + $this->assertEquals('5.x', $version_parts['drupal_version']); + $this->assertEquals('5', $version_parts['version_major']); + $this->assertEquals('', $version_parts['version_minor']); + $this->assertEquals('', $version_parts['version_patch']); + $this->assertEquals('dev', $version_parts['version_extra']); + $this->assertEquals('5.x-dev', $version_parts['project_version']); + + $version = '5.0'; + $version_parts = pm_parse_version($version, TRUE); + $this->assertEquals('5.0', $version_parts['version']); + $this->assertEquals('5.x', $version_parts['drupal_version']); + $this->assertEquals('5', $version_parts['version_major']); + $this->assertEquals('', $version_parts['version_minor']); + $this->assertEquals('0', $version_parts['version_patch']); + $this->assertEquals('', $version_parts['version_extra']); + $this->assertEquals('5.0', $version_parts['project_version']); + + $version = '5.0-beta1'; + $version_parts = pm_parse_version($version, TRUE); + $this->assertEquals('5.0-beta1', $version_parts['version']); + $this->assertEquals('5.x', $version_parts['drupal_version']); + $this->assertEquals('5', $version_parts['version_major']); + $this->assertEquals('', $version_parts['version_minor']); + $this->assertEquals('0', $version_parts['version_patch']); + $this->assertEquals('beta1', $version_parts['version_extra']); + $this->assertEquals('5.0-beta1', $version_parts['project_version']); + } + + /** + * Tests for pm_parse_version() with project versions. + */ + public function testVersionParserContrib() { + _drush_add_commandfiles(array(DRUSH_BASE_PATH . '/commands/pm')); + + drush_set_option('default-major', UNISH_DRUPAL_MAJOR_VERSION); + + $version = ''; + $version_parts = pm_parse_version($version); + $this->assertEquals('', $version_parts['version']); + $this->assertEquals(UNISH_DRUPAL_MAJOR_VERSION . '.x', $version_parts['drupal_version']); + $this->assertEquals('', $version_parts['version_major']); + $this->assertEquals('', $version_parts['version_minor']); + $this->assertEquals('', $version_parts['version_patch']); + $this->assertEquals('', $version_parts['version_extra']); + $this->assertEquals('', $version_parts['project_version']); + + $version = '7'; + $version_parts = pm_parse_version($version); + $this->assertEquals('', $version_parts['version']); + $this->assertEquals('7.x', $version_parts['drupal_version']); + $this->assertEquals('', $version_parts['version_major']); + $this->assertEquals('', $version_parts['version_minor']); + $this->assertEquals('', $version_parts['version_patch']); + $this->assertEquals('', $version_parts['version_extra']); + $this->assertEquals('', $version_parts['project_version']); + + $version = '7.x'; + $version_parts = pm_parse_version($version); + $this->assertEquals('', $version_parts['version']); + $this->assertEquals('7.x', $version_parts['drupal_version']); + $this->assertEquals('', $version_parts['version_major']); + $this->assertEquals('', $version_parts['version_minor']); + $this->assertEquals('', $version_parts['version_patch']); + $this->assertEquals('', $version_parts['version_extra']); + $this->assertEquals('', $version_parts['project_version']); + + $version = '7.x-1.0-beta1'; + $version_parts = pm_parse_version($version); + $this->assertEquals('7.x', $version_parts['drupal_version']); + $this->assertEquals('1', $version_parts['version_major']); + $this->assertEquals('', $version_parts['version_minor']); + $this->assertEquals('0', $version_parts['version_patch']); + $this->assertEquals('beta1', $version_parts['version_extra']); + $this->assertEquals('1.0-beta1', $version_parts['project_version']); + + $version = '7.x-1.0-beta1+30-dev'; + $version_parts = pm_parse_version($version); + $this->assertEquals('7.x', $version_parts['drupal_version']); + $this->assertEquals('1', $version_parts['version_major']); + $this->assertEquals('', $version_parts['version_minor']); + $this->assertEquals('0', $version_parts['version_patch']); + $this->assertEquals('beta1', $version_parts['version_extra']); + $this->assertEquals('30', $version_parts['version_offset']); + $this->assertEquals('1.0-beta1+30-dev', $version_parts['project_version']); + + $version = '7.x-1.0'; + $version_parts = pm_parse_version($version); + $this->assertEquals('7.x', $version_parts['drupal_version']); + $this->assertEquals('1', $version_parts['version_major']); + $this->assertEquals('', $version_parts['version_minor']); + $this->assertEquals('0', $version_parts['version_patch']); + $this->assertEquals('', $version_parts['version_extra']); + $this->assertEquals('1.0', $version_parts['project_version']); + + $version = '7.x-1.0+30-dev'; + $version_parts = pm_parse_version($version); + $this->assertEquals('7.x', $version_parts['drupal_version']); + $this->assertEquals('1', $version_parts['version_major']); + $this->assertEquals('', $version_parts['version_minor']); + $this->assertEquals('0', $version_parts['version_patch']); + $this->assertEquals('', $version_parts['version_extra']); + $this->assertEquals('30', $version_parts['version_offset']); + $this->assertEquals('1.0+30-dev', $version_parts['project_version']); + + // Since we're not on a bootstrapped site, the version string + // for the following cases is interpreted as a core version. + // Tests on a bootstrapped site are in \pmRequestCase::testVersionParser() + $version = '6.x'; + $version_parts = pm_parse_version($version); + $this->assertEquals('6.x', $version_parts['drupal_version']); + $this->assertEquals('', $version_parts['version_major']); + $this->assertEquals('', $version_parts['version_minor']); + $this->assertEquals('', $version_parts['version_patch']); + $this->assertEquals('', $version_parts['version_extra']); + $this->assertEquals('', $version_parts['project_version']); + + $version = '6.22'; + $version_parts = pm_parse_version($version); + $this->assertEquals('6.x', $version_parts['drupal_version']); + $this->assertEquals('', $version_parts['version_major']); + $this->assertEquals('', $version_parts['version_minor']); + $this->assertEquals('', $version_parts['version_patch']); + $this->assertEquals('', $version_parts['version_extra']); + $this->assertEquals('', $version_parts['project_version']); + } + + /** + * Tests for pm_parse_request(). + */ + public function testRequestParser() { + $request = 'devel-7.x-1.2'; + $request = pm_parse_request($request); + $this->assertEquals('devel', $request['name']); + $this->assertEquals('7.x-1.2', $request['version']); + + $request = 'field-conditional-state'; + $request = pm_parse_request($request); + $this->assertEquals('field-conditional-state', $request['name']); + $this->assertEquals('', $request['version']); + + $request = 'field-conditional-state-7.x-1.2'; + $request = pm_parse_request($request); + $this->assertEquals('field-conditional-state', $request['name']); + $this->assertEquals('7.x-1.2', $request['version']); + } +} diff --git a/vendor/drush/drush/tests/pmUpdateCodeTest.php b/vendor/drush/drush/tests/pmUpdateCodeTest.php new file mode 100644 index 0000000000..57d60577da --- /dev/null +++ b/vendor/drush/drush/tests/pmUpdateCodeTest.php @@ -0,0 +1,146 @@ +drush('pm-releases', array($project), array('all' => NULL, 'fields' => 'Release')); + $list = $this->getOutputAsList(); + // Line 0 is "Release" + // Line 1 is "...-dev" + // Line 2 is "...-dev" + // Line 3 is "...-dev" + // Line 4 is current best release + // Line 5 is the previous release + return trim($list[5]); + } + + /** + * Download old core and older contrib releases which will always need updating. + */ + public function set_up() { + if (PHP_MAJOR_VERSION >= 8) { + $this->markTestSkipped("Old module versions in these tests not necessarily compatible with PHP 8"); + } + if (UNISH_DRUPAL_MAJOR_VERSION >= 9) { + $this->markTestSkipped("Test not supported in Drupal 9"); + } + elseif (UNISH_DRUPAL_MAJOR_VERSION >= 8) { + // Make sure that we can still update from the previous release + // to the current release. + $core = $this->getPreviousStable("drupal-8"); + $modules_str = 'unish-8.x-1.2,honeypot-8.x-1.19-beta14'; + $this->modules = array('block', 'unish', 'honeypot'); + } + elseif (UNISH_DRUPAL_MAJOR_VERSION == 7) { + $core = '7.0-rc3'; + $modules_str = 'devel-7.x-1.0-rc1,webform-7.x-3.4-beta1'; + $this->modules = array('menu', 'devel', 'webform'); + } + else { + $this->markTestSkipped("pm-update* no longer supported with Drupal 6; drupal.org does not allow stable releases for Drupal 6 contrib modules."); + } + + $sites = $this->setUpDrupal(1, TRUE, $core); + $options = array( + 'root' => $this->webroot(), + 'uri' => key($sites), + 'yes' => NULL, + 'quiet' => NULL, + 'cache' => NULL, + 'skip' => NULL, // No FirePHP + 'strict' => 0, + ); + + $this->drush('pm-download', array($modules_str), $options); + $this->drush('pm-enable', $this->modules, $options); + } + + function testUpdateCode() { + if (UNISH_DRUPAL_MAJOR_VERSION < 7) { + $this->markTestSkipped("pm-update does not work once Drupal core reaches EOL."); + } + $extension = UNISH_DRUPAL_MAJOR_VERSION >= 8 ? '.info.yml' : '.info'; + $first = $this->modules[1]; + $second = $this->modules[2]; + + $options = array( + 'root' => $this->webroot(), + 'uri' => key($this->getSites()), + 'yes' => NULL, + 'no-core' => NULL, + 'backup-dir' => UNISH_SANDBOX . '/backups', + 'cache' => NULL, + 'check-updatedb' => 0, + // Needed in order to get 'Up to date' in the return value of updatestatus. See pm_project_filter(). + 'verbose' => NULL, + 'strict' => 0, + ); + + // Upgrade a specific module. + $this->drush('pm-updatecode', array($first), $options + array()); + + // Assure that first was upgraded and second was not. + $this->drush('pm-updatestatus', array(), $options + array('format' => 'json')); + $all = $this->getOutputFromJSON(); + $this->assertEquals($all->$first->existing_version, $all->$first->candidate_version); + $this->assertNotEquals($all->$second->existing_version, $all->$second->candidate_version); + + // Lock second, and update core. + $this->drush('pm-updatecode', array(), $options + array('lock' => $second)); + $list = $this->getOutputAsList(); // For debugging. + $this->drush('pm-updatestatus', array(), $options + array('format' => 'json')); + $all = $this->getOutputFromJSON(); + // Don't update core in this test. Avoids working around the + // `You have requested a non-existent service "path_alias.repository"` bug. + $this->assertEquals($all->drupal->existing_version, $all->drupal->existing_version); + $this->assertNotEquals($all->$second->existing_version, $all->$second->candidate_version); + + // Unlock second, update, and check. + $this->drush('pm-updatecode', array(), $options + array('unlock' => $second, 'no-backup' => NULL)); + $list = $this->getOutputAsList(); + $this->drush('pm-updatestatus', array(), $options + array('format' => 'json')); + $all = $this->getOutputFromJSON(); + $this->assertEquals($all->$second->existing_version, $all->$second->candidate_version); + + // Verify that we keep backups as instructed. + $backup_dir = UNISH_SANDBOX . '/backups'; + $Directory = new \RecursiveDirectoryIterator($backup_dir); + $Iterator = new \RecursiveIteratorIterator($Directory); + $found = FALSE; + foreach ($Iterator as $item) { + if (basename($item) == $first . $extension) { + $found = TRUE; + break; + } + } + $this->assertTrue($found, 'Backup exists and contains the first module.'); + + $Iterator = new \RecursiveIteratorIterator($Directory); + $found = FALSE; + foreach ($Iterator as $item) { + if (basename($item) == $second . '.module') { + $found = TRUE; + break; + } + } + $this->assertFalse($found, 'Backup exists and does not contain the second module.'); + } +} diff --git a/vendor/drush/drush/tests/pmUpdateStatusTest.php b/vendor/drush/drush/tests/pmUpdateStatusTest.php new file mode 100644 index 0000000000..81f510a4df --- /dev/null +++ b/vendor/drush/drush/tests/pmUpdateStatusTest.php @@ -0,0 +1,187 @@ +markTestSkipped("pm-update* no longer supported with Drupal 6; drupal.org does not allow stable releases for Drupal 6 contrib modules."); + } + + $sites = $this->setUpDrupal(1, TRUE, "7.81"); + $options = array( + 'root' => $this->webroot(), + 'uri' => key($sites), + 'yes' => NULL, + 'cache' => NULL, + 'skip' => NULL, // No FirePHP + 'strict' => 0, + ); + + // Prepare a list of modules with several update statuses. + $modules_dl = array(); + $modules_en = array(); + // Update available but not a security one. Cross fingers they never release a security update. + $modules_dl[] = 'bad_judgement-1.0-rc38'; + $modules_en[] = 'bad_judgement'; + // Old devel release with a security update available. + $modules_dl[] = 'devel-7.x-1.0-rc1'; + $modules_en[] = 'devel'; + // Installed version not supported. + $modules_dl[] = 'cck-2.x-dev'; + $modules_en[] = 'cck'; + // Up to date. + $modules_dl[] = 'ctools'; + $modules_en[] = 'ctools'; + + // Download and enable the modules. Additionally download a module from git, so it has no version information. + $this->drush('pm-download', $modules_dl, $options); + $this->drush('pm-download', array('zen'), $options + array('package-handler' => 'git_drupalorg')); + $modules_en[] = 'zen'; + // self::EXIT_ERROR because of bad_judgement. + $this->drush('pm-enable', $modules_en, $options, NULL, NULL, self::EXIT_ERROR); + } + + /** + * Test several update statuses via drupal backend. + */ + function testUpdateStatusDrupal() { + $this->doTest('drupal'); + } + + /** + * Test several update statuses via drush backend. + */ + function testUpdateStatusDrush() { + $this->doTest('drush'); + } + + function doTest($update_backend) { + + // Test several projects with a variety of statuses. + $options = array( + 'root' => $this->webroot(), + 'uri' => key($this->getSites()), + 'verbose' => NULL, + 'backend' => NULL, + 'update-backend' => $update_backend, + ); + $this->drush('pm-updatestatus', array(), $options); + $parsed = $this->parse_backend_output($this->getOutput()); + $data = $parsed['object']; + + $expected = array( + 'drupal' => 'SECURITY UPDATE available', + 'bad_judgement' => 'Update available', + 'ctools' => 'Up to date', + 'devel' => 'SECURITY UPDATE available', + 'cck' => 'Installed version not supported', + 'zen' => 'Project was not packaged by drupal.org but obtained from git. You need to enable git_deploy module', + ); + foreach ($expected as $module => $status_msg) { + $this->assertArrayHasKey($module, $data, "$module module present in pm-updatestatus output"); + $this->assertEquals($status_msg, $data[$module]['status_msg'], "$module status is '$status_msg'"); + } + + + // Test statuses when asked for specific projects and versions. + $args = array( + 'bad_judgement-1.0-rc38', + 'ctools-0.0', + 'devel-1.5', + 'foo', + ); + $this->drush('pm-updatestatus', $args, $options); + $parsed = $this->parse_backend_output($this->getOutput()); + $data = $parsed['object']; + + $expected = array( + 'bad_judgement' => 'Specified version already installed', + 'ctools' => 'Specified version not found', + 'devel' => 'Specified version available', + 'foo' => 'Specified project not found', + ); + foreach ($expected as $module => $status_msg) { + $this->assertArrayHasKey($module, $data, "$module module present in pm-updatestatus output"); + $this->assertEquals($data[$module]['status_msg'], $status_msg, "$module status is '$status_msg'"); + } + // We don't expect any output for other projects than the provided ones. + $not_expected = array( + 'drupal', + 'cck', + 'zen', + ); + foreach ($not_expected as $module) { + $this->assertArrayNotHasKey($module, $data, "$module module not present in pm-updatestatus output"); + } + + + // Test --security-only. + $this->drush('pm-updatestatus', array(), $options + array('security-only' => NULL)); + $parsed = $this->parse_backend_output($this->getOutput()); + $data = $parsed['object']; + + $expected = array( + 'drupal' => 'SECURITY UPDATE available', + 'devel' => 'SECURITY UPDATE available', + ); + foreach ($expected as $module => $status_msg) { + $this->assertArrayHasKey($module, $data, "$module module present in pm-updatestatus output"); + $this->assertEquals($data[$module]['status_msg'], $status_msg, "$module status is '$status_msg'"); + } + // We don't expect any output for projects without security updates. + $not_expected = array( + 'bad_judgement', + 'ctools', + 'cck', + 'zen', + ); + foreach ($not_expected as $module) { + $this->assertArrayNotHasKey($module, $data, "$module module not present in pm-updatestatus output"); + } + + + // Test --check-disabled. + $dis_options = array( + 'root' => $this->webroot(), + 'uri' => key($this->getSites()), + 'yes' => NULL, + ); + $this->drush('pm-disable', array('devel'), $dis_options); + + $this->drush('pm-updatestatus', array(), $options + array('check-disabled' => 1)); + $parsed = $this->parse_backend_output($this->getOutput()); + $data = $parsed['object']; + $this->assertArrayHasKey('devel', $data, "devel module present in pm-updatestatus output"); + + $this->drush('pm-updatestatus', array(), $options + array('check-disabled' => 0)); + $parsed = $this->parse_backend_output($this->getOutput()); + $data = $parsed['object']; + $this->assertArrayNotHasKey('devel', $data, "devel module not present in pm-updatestatus output"); + } +} + diff --git a/vendor/drush/drush/tests/queueTest.php b/vendor/drush/drush/tests/queueTest.php new file mode 100644 index 0000000000..00d91bbc86 --- /dev/null +++ b/vendor/drush/drush/tests/queueTest.php @@ -0,0 +1,114 @@ +markTestSkipped("Queue API not available in Drupal 6."); + } + + if (UNISH_DRUPAL_MAJOR_VERSION == 7) { + $expected = 'aggregator_feeds,%items,SystemQueue'; + } + else { + $expected = 'aggregator_feeds,%items,Drupal\Core\Queue\DatabaseQueue'; + } + + $sites = $this->setUpDrupal(1, TRUE); + $options = array( + 'yes' => NULL, + 'root' => $this->webroot(), + 'uri' => key($sites), + ); + + // Enable aggregator since it declares a queue. + $this->drush('pm-enable', array('aggregator'), $options); + + $this->drush('queue-list', array(), $options); + $output = $this->getOutput(); + $this->assertStringContainsString('aggregator_feeds', $output, 'Queue list shows the declared queue.'); + + $this->drush('php-script', array('queue_script-D' . UNISH_DRUPAL_MAJOR_VERSION), $options + array('script-path' => dirname(__FILE__) . '/resources')); + $this->drush('queue-list', array(), $options + array('pipe' => TRUE)); + $output = trim($this->getOutput()); + $parts = explode(",", $output); + $this->assertEquals(str_replace('%items', 1, $expected), $output, 'Item was successfully added to the queue.'); + $output = $this->getOutput(); + + $this->drush('queue-run', array('aggregator_feeds'), $options); + $this->drush('queue-list', array(), $options + array('pipe' => TRUE)); + $output = trim($this->getOutput()); + $parts = explode(",", $output); + $this->assertEquals(str_replace('%items', 0, $expected), $output, 'Queue item processed.'); + } + + /** + * Tests the RequeueException. + */ + public function testRequeueException() { + if (UNISH_DRUPAL_MAJOR_VERSION < 8) { + $this->markTestSkipped("RequeueException only available in Drupal 8."); + } + + $sites = $this->setUpDrupal(1, TRUE); + $options = array( + 'yes' => NULL, + 'root' => $this->webroot(), + 'uri' => key($sites), + ); + + // Copy the 'woot' module over to the Drupal site we just set up. + $this->setupModulesForTests($this->webroot()); + + // Enable woot module, which contains a queue worker that throws a + // RequeueException. + $this->drush('pm-enable', array('woot'), $options, NULL, NULL, self::EXIT_SUCCESS); + + // Add an item to the queue. + $this->drush('php-script', array('requeue_script'), $options + array('script-path' => dirname(__FILE__) . '/resources')); + + // Check that the queue exists and it has one item in it. + $expected = 'woot_requeue_exception,%items,Drupal\Core\Queue\DatabaseQueue'; + $this->drush('queue-list', array(), $options + array('pipe' => TRUE)); + $output = trim($this->getOutput()); + $this->assertEquals(str_replace('%items', 1, $expected), $output, 'Item was successfully added to the queue.'); + + // Process the queue. + $this->drush('queue-run', array('woot_requeue_exception'), $options); + + // Check that the item was processed after being requeued once. + // Here is the detailed workflow of what the above command did. + // 1. Drush calls drush queue-run woot_requeue_exception. + // 2. Drush claims the item. The worker sets a state variable (see below) + // and throws the RequeueException. + // 3. Drush catches the exception and puts it back in the queue. + // 4. Drush claims the next item, which is the one that we just requeued. + // 5. The worker finds the state variable, so it does not throw the + // RequeueException this time (see below). + // 6. Drush removes the item from the queue. + // 7. Command finishes. The queue is empty. + $this->drush('queue-list', array(), $options + array('pipe' => TRUE)); + $output = trim($this->getOutput()); + $this->assertEquals(str_replace('%items', 0, $expected), $output, 'Queue item processed after being requeued.'); + } + + /** + * Copies the woot module into Drupal. + * + * @param string $root + * The path to the root directory of Drupal. + */ + public function setupModulesForTests($root) { + $wootMajor = UNISH_DRUPAL_MAJOR_VERSION == '9' ? '8' : UNISH_DRUPAL_MAJOR_VERSION; + $wootModule = __DIR__ . '/resources/modules/d' . $wootMajor . '/woot'; + $modulesDir = "$root/sites/all/modules"; + $this->mkdir($modulesDir); + \symlink($wootModule, "$modulesDir/woot"); + } + +} diff --git a/vendor/drush/drush/tests/quickDrupalTest.php b/vendor/drush/drush/tests/quickDrupalTest.php new file mode 100644 index 0000000000..6c4cf4b9f4 --- /dev/null +++ b/vendor/drush/drush/tests/quickDrupalTest.php @@ -0,0 +1,75 @@ +makefile_path = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'makefiles'; + } + + /** + * Run a given quick-drupal test. + * + * @param $test + * The test makefile to run, as defined by $this->getQuickDrupalTestParameters(); + */ + private function runQuickDrupalTest($test) { + $config = $this->getQuickDrupalTestParameters($test); + $default_options = array( + 'yes' => NULL, + 'no-server' => NULL, + ); + $options = array_merge($config['options'], $default_options); + if (array_key_exists('makefile', $config)) { + $makefile = $this->makefile_path . DIRECTORY_SEPARATOR . $config['makefile']; + $options['makefile'] = $makefile; + } + $return = !empty($config['fail']) ? self::EXIT_ERROR : self::EXIT_SUCCESS; + $target = UNISH_SANDBOX . '/qd-' . $test; + $options['root'] = $target; + $this->drush('core-quick-drupal', $config['args'], $options, NULL, NULL, $return); + + // Use pm-list to determine if all of the correct modules were enabled + if (empty($config['fail'])) { + $this->drush('pm-list', array(), array('root' => $target, 'status' => 'enabled', 'no-core' => NULL, 'pipe' => NULL)); + $output = $this->getOutput(); + $this->assertEquals($config['expected-modules'], $output, 'quick-drupal included the correct set of modules'); + } + } + + function testQuickDrupal() { + $this->markTestSkipped("This test was working on TravisCI. A pull request to fix it on CircleCI would be welcome."); + $this->runQuickDrupalTest('devel'); + } + + function getQuickDrupalTestParameters($key) { + $tests = array( + 'devel' => array( + 'name' => 'Test quick-drupal with a makefile that downloads devel', + 'makefile' => 'qd-devel.make', + 'expected-modules' => 'devel', + 'args' => array(), + 'options' => array( + 'skip' => NULL, // for speed up enable of devel module. + 'browser' => 0, + 'profile' => UNISH_DRUPAL_MAJOR_VERSION == 6 ? 'standard' : 'testing', + ), + ), + ); + return $tests[$key]; + } +} diff --git a/vendor/drush/drush/tests/releaseInfoTest.php b/vendor/drush/drush/tests/releaseInfoTest.php new file mode 100644 index 0000000000..ff534c4a90 --- /dev/null +++ b/vendor/drush/drush/tests/releaseInfoTest.php @@ -0,0 +1,61 @@ +getSpecificRelease('6.x-1.18'); + $this->assertEquals('6.x-1.18', $release['version']); + + // Pick latest recommended+published with no further specification. + // 6.x-2.2 is skipped because it is unpublished. + // 6.x-2.2-rc1 is skipped because it is not a stable release. + $release = $project_release_info->getRecommendedOrSupportedRelease(); + $this->assertEquals('6.x-2.1', $release['version']); + + // Pick latest from a specific branch. + $release = $project_release_info->getSpecificRelease('6.x-1'); + $this->assertEquals('6.x-1.23', $release['version']); + + // Pick latest from a different branch. + // 6.x-2.2 is skipped because it is unpublished. + // 6.x-2.2-rc1 is skipped because it is not a stable release. + $release = $project_release_info->getSpecificRelease('6.x-2'); + $this->assertEquals('6.x-2.1', $release['version']); + + // Pick a -dev release. + $release = $project_release_info->getSpecificRelease('6.x-1.x'); + $this->assertEquals('6.x-1.x-dev', $release['version']); + + // Test UpdateServiceProject::getSpecificRelease(). + // Test we get latest release in branch 1. + $release = $project_release_info->getSpecificRelease('6.x-1'); + $this->assertEquals('6.x-1.23', $release['version']); + + // Test UpdateServiceProject::getDevRelease(). + $release = $project_release_info->getDevRelease(); + $this->assertEquals('6.x-1.x-dev', $release['version']); + } +} diff --git a/vendor/drush/drush/tests/resources/create_node_types.php b/vendor/drush/drush/tests/resources/create_node_types.php new file mode 100644 index 0000000000..a0c07a0b3b --- /dev/null +++ b/vendor/drush/drush/tests/resources/create_node_types.php @@ -0,0 +1,28 @@ + 'page', + 'name' => 'Basic page', + 'base' => 'node_content', + 'description' => 'Use basic pages for your static content, such as an \'About us\' page.', + 'custom' => 1, + 'modified' => 1, + 'locked' => 0, + ), + array( + 'type' => 'article', + 'name' => 'Article', + 'base' => 'node_content', + 'description' => 'Use articles for time-sensitive content like news, press releases or blog posts.', + 'custom' => 1, + 'modified' => 1, + 'locked' => 0, + ), +); + +foreach ($types as $type) { + $type = node_type_set_defaults($type); + node_type_save($type); + node_add_body_field($type); +} diff --git a/vendor/drush/drush/tests/resources/example.profile b/vendor/drush/drush/tests/resources/example.profile new file mode 100644 index 0000000000..dd0d2fab8d --- /dev/null +++ b/vendor/drush/drush/tests/resources/example.profile @@ -0,0 +1,42 @@ + 'Example', + 'description' => 'Example profile with a couple of basic added configuration options.', + ); +} + +function example_profile_modules() { + return array(); +} + +function example_form_alter(&$form, $form_state, $form_id) { + if ($form_id == 'install_configure') { + $form['my_options'] = array( + '#type' => 'fieldset', + '#title' => t('Example options'), + ); + $form['my_options']['myopt1'] = array( + '#type' => 'textfield', + '#title' => 'Example option 1' + ); + $form['my_options']['myopt2'] = array( + '#type' => 'select', + '#title' => t('Example option 2'), + '#options' => array( + 0 => t('Something'), + 1 => t('Something else'), + 2 => t('Something completely different'), + ), + ); + + // Make sure we don't clobber the original auto-detected submit func + $form['#submit'] = array('install_configure_form_submit', 'example_install_configure_form_submit'); + } +} + +function example_install_configure_form_submit($form, &$form_state) { + variable_set('myopt1', $form_state['values']['myopt1']); + variable_set('myopt2', $form_state['values']['myopt2']); +} \ No newline at end of file diff --git a/vendor/drush/drush/tests/resources/global-includes/Commands/FoobarCommands.php b/vendor/drush/drush/tests/resources/global-includes/Commands/FoobarCommands.php new file mode 100644 index 0000000000..0e934dfac1 --- /dev/null +++ b/vendor/drush/drush/tests/resources/global-includes/Commands/FoobarCommands.php @@ -0,0 +1,21 @@ + false]) + { + if ($options['flip']) { + return "{$two}{$one}"; + } + return "{$one}{$two}"; + } + + /** + * Demonstrate formatters. Default format is 'table'. + * + * @field-labels + * first: I + * second: II + * third: III + * @usage try:formatters --format=yaml + * @usage try:formatters --format=csv + * @usage try:formatters --fields=first,third + * @usage try:formatters --fields=III,II + * @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields + */ + public function tryFormatters($options = ['format' => 'table', 'fields' => '']) + { + $outputData = [ + 'en' => [ 'first' => 'One', 'second' => 'Two', 'third' => 'Three' ], + 'de' => [ 'first' => 'Eins', 'second' => 'Zwei', 'third' => 'Drei' ], + 'jp' => [ 'first' => 'Ichi', 'second' => 'Ni', 'third' => 'San' ], + 'es' => [ 'first' => 'Uno', 'second' => 'Dos', 'third' => 'Tres' ], + ]; + return new RowsOfFields($outputData); + } +} diff --git a/vendor/drush/drush/tests/resources/modules/d6/woot/woot.info b/vendor/drush/drush/tests/resources/modules/d6/woot/woot.info new file mode 100644 index 0000000000..45f4a3ccb2 --- /dev/null +++ b/vendor/drush/drush/tests/resources/modules/d6/woot/woot.info @@ -0,0 +1,4 @@ +name = woot +description = Woot Mightily +core = 6.x +files[] = woot.module diff --git a/vendor/drush/drush/tests/resources/modules/d6/woot/woot.module b/vendor/drush/drush/tests/resources/modules/d6/woot/woot.module new file mode 100644 index 0000000000..f51b34a5be --- /dev/null +++ b/vendor/drush/drush/tests/resources/modules/d6/woot/woot.module @@ -0,0 +1,22 @@ + 'Woot', + 'description' => 'Woot mightily.', + 'page callback' => 'woot_page', + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); + + return $items; +} + +function woot_page() { + return array('#markup' => 'Woot!'); +} diff --git a/vendor/drush/drush/tests/resources/modules/d7/woot/Commands/WootCommands.php b/vendor/drush/drush/tests/resources/modules/d7/woot/Commands/WootCommands.php new file mode 100644 index 0000000000..ba23075068 --- /dev/null +++ b/vendor/drush/drush/tests/resources/modules/d7/woot/Commands/WootCommands.php @@ -0,0 +1,66 @@ + false]) + { + if ($options['flip']) { + return "{$two}{$one}"; + } + return "{$one}{$two}"; + } + + /** + * Demonstrate formatters. Default format is 'table'. + * + * @field-labels + * first: I + * second: II + * third: III + * @usage try:formatters --format=yaml + * @usage try:formatters --format=csv + * @usage try:formatters --fields=first,third + * @usage try:formatters --fields=III,II + * @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields + */ + public function tryFormatters($options = ['format' => 'table', 'fields' => '']) + { + $outputData = [ + 'en' => [ 'first' => 'One', 'second' => 'Two', 'third' => 'Three' ], + 'de' => [ 'first' => 'Eins', 'second' => 'Zwei', 'third' => 'Drei' ], + 'jp' => [ 'first' => 'Ichi', 'second' => 'Ni', 'third' => 'San' ], + 'es' => [ 'first' => 'Uno', 'second' => 'Dos', 'third' => 'Tres' ], + ]; + return new RowsOfFields($outputData); + } +} diff --git a/vendor/drush/drush/tests/resources/modules/d7/woot/woot.info b/vendor/drush/drush/tests/resources/modules/d7/woot/woot.info new file mode 100644 index 0000000000..4a01a94af5 --- /dev/null +++ b/vendor/drush/drush/tests/resources/modules/d7/woot/woot.info @@ -0,0 +1,4 @@ +name = woot +description = Woot Mightily +core = 7.x +files[] = woot.module diff --git a/vendor/drush/drush/tests/resources/modules/d7/woot/woot.module b/vendor/drush/drush/tests/resources/modules/d7/woot/woot.module new file mode 100644 index 0000000000..f51b34a5be --- /dev/null +++ b/vendor/drush/drush/tests/resources/modules/d7/woot/woot.module @@ -0,0 +1,22 @@ + 'Woot', + 'description' => 'Woot mightily.', + 'page callback' => 'woot_page', + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); + + return $items; +} + +function woot_page() { + return array('#markup' => 'Woot!'); +} diff --git a/vendor/drush/drush/tests/resources/modules/d8/woot/composer.json b/vendor/drush/drush/tests/resources/modules/d8/woot/composer.json new file mode 100644 index 0000000000..72d8757320 --- /dev/null +++ b/vendor/drush/drush/tests/resources/modules/d8/woot/composer.json @@ -0,0 +1,14 @@ +{ + "name": "drupal/woot", + "type": "drupal-module", + "description": "Woot Mightily.", + "keywords": ["Drupal"], + "license": "GPL-2.0+", + "homepage": "http://drupal.org/project/woot", + "minimum-stability": "dev", + "support": { + "issues": "http://drupal.org/project/issues/woot", + "source": "http://cgit.drupalcode.org/woot" + }, + "require": { } +} diff --git a/vendor/drush/drush/tests/resources/modules/d8/woot/src/Commands/AnnotatedGreetCommand.php b/vendor/drush/drush/tests/resources/modules/d8/woot/src/Commands/AnnotatedGreetCommand.php new file mode 100644 index 0000000000..1a42b81341 --- /dev/null +++ b/vendor/drush/drush/tests/resources/modules/d8/woot/src/Commands/AnnotatedGreetCommand.php @@ -0,0 +1,41 @@ +getArgument('name'); + if ($name) { + $text = 'Hello '.$name; + } else { + $text = 'Hello'; + } + + if ($input->getOption('yell')) { + $text = strtoupper($text); + } + + $output->writeln($text); + } +} diff --git a/vendor/drush/drush/tests/resources/modules/d8/woot/src/Commands/GreetCommand.php b/vendor/drush/drush/tests/resources/modules/d8/woot/src/Commands/GreetCommand.php new file mode 100644 index 0000000000..644630b7d9 --- /dev/null +++ b/vendor/drush/drush/tests/resources/modules/d8/woot/src/Commands/GreetCommand.php @@ -0,0 +1,52 @@ +setName('demo:greet') + ->setDescription('Greet someone') + ->addArgument( + 'name', + InputArgument::OPTIONAL, + 'Who do you want to greet?' + ) + ->addOption( + 'yell', + null, + InputOption::VALUE_NONE, + 'If set, the task will yell in uppercase letters' + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $name = $input->getArgument('name'); + if ($name) { + $text = 'Hello '.$name; + } else { + $text = 'Hello'; + } + + if ($input->getOption('yell')) { + $text = strtoupper($text); + } + + $output->writeln($text); + } +} diff --git a/vendor/drush/drush/tests/resources/modules/d8/woot/src/Commands/WootCommands.php b/vendor/drush/drush/tests/resources/modules/d8/woot/src/Commands/WootCommands.php new file mode 100644 index 0000000000..ba23075068 --- /dev/null +++ b/vendor/drush/drush/tests/resources/modules/d8/woot/src/Commands/WootCommands.php @@ -0,0 +1,66 @@ + false]) + { + if ($options['flip']) { + return "{$two}{$one}"; + } + return "{$one}{$two}"; + } + + /** + * Demonstrate formatters. Default format is 'table'. + * + * @field-labels + * first: I + * second: II + * third: III + * @usage try:formatters --format=yaml + * @usage try:formatters --format=csv + * @usage try:formatters --fields=first,third + * @usage try:formatters --fields=III,II + * @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields + */ + public function tryFormatters($options = ['format' => 'table', 'fields' => '']) + { + $outputData = [ + 'en' => [ 'first' => 'One', 'second' => 'Two', 'third' => 'Three' ], + 'de' => [ 'first' => 'Eins', 'second' => 'Zwei', 'third' => 'Drei' ], + 'jp' => [ 'first' => 'Ichi', 'second' => 'Ni', 'third' => 'San' ], + 'es' => [ 'first' => 'Uno', 'second' => 'Dos', 'third' => 'Tres' ], + ]; + return new RowsOfFields($outputData); + } +} diff --git a/vendor/drush/drush/tests/resources/modules/d8/woot/src/Controller/WootController.php b/vendor/drush/drush/tests/resources/modules/d8/woot/src/Controller/WootController.php new file mode 100644 index 0000000000..4f9234d019 --- /dev/null +++ b/vendor/drush/drush/tests/resources/modules/d8/woot/src/Controller/WootController.php @@ -0,0 +1,31 @@ + 'markup', + '#markup' => $this->t('Woot!') + ]; + } + +} diff --git a/vendor/drush/drush/tests/resources/modules/d8/woot/src/Plugin/QueueWorker/WootRequeueException.php b/vendor/drush/drush/tests/resources/modules/d8/woot/src/Plugin/QueueWorker/WootRequeueException.php new file mode 100644 index 0000000000..6830e56d02 --- /dev/null +++ b/vendor/drush/drush/tests/resources/modules/d8/woot/src/Plugin/QueueWorker/WootRequeueException.php @@ -0,0 +1,33 @@ +get('woot_requeue_exception')) { + $state->set('woot_requeue_exception', 1); + throw new RequeueException('I am not done yet!'); + } + else { + $state->set('woot_requeue_exception', 2); + } + } + +} diff --git a/vendor/drush/drush/tests/resources/modules/d8/woot/src/WootManager.php b/vendor/drush/drush/tests/resources/modules/d8/woot/src/WootManager.php new file mode 100644 index 0000000000..9802835585 --- /dev/null +++ b/vendor/drush/drush/tests/resources/modules/d8/woot/src/WootManager.php @@ -0,0 +1,54 @@ +currentUser = $current_user; + } + + /** + * Woof mightily. Note that we can include commands directly + * inside a service class. + * + * @command woof + * @aliases wf + */ + public function woof() + { + return 'Woof!'; + } +} diff --git a/vendor/drush/drush/tests/resources/modules/d8/woot/woot.info.yml b/vendor/drush/drush/tests/resources/modules/d8/woot/woot.info.yml new file mode 100644 index 0000000000..67be3af9f2 --- /dev/null +++ b/vendor/drush/drush/tests/resources/modules/d8/woot/woot.info.yml @@ -0,0 +1,6 @@ +name: Woot +type: module +description: Woot Mightily. +core: 8.x +core_version_requirement: ^8 || ^9 +package: Other diff --git a/vendor/drush/drush/tests/resources/modules/d8/woot/woot.module b/vendor/drush/drush/tests/resources/modules/d8/woot/woot.module new file mode 100644 index 0000000000..b1535c4d4a --- /dev/null +++ b/vendor/drush/drush/tests/resources/modules/d8/woot/woot.module @@ -0,0 +1,33 @@ +' . t('About') . ''; + $output .= '

    ' . t('Woot Mightily.') . '

    '; + return $output; + + default: + } +} + +/** + * Implements hook_theme(). + */ +function woot_theme() { + $theme = []; + + return $theme; +} diff --git a/vendor/drush/drush/tests/resources/modules/d8/woot/woot.routing.yml b/vendor/drush/drush/tests/resources/modules/d8/woot/woot.routing.yml new file mode 100644 index 0000000000..a607603b65 --- /dev/null +++ b/vendor/drush/drush/tests/resources/modules/d8/woot/woot.routing.yml @@ -0,0 +1,8 @@ + +woot.woot_controller_woot: + path: '/woot' + defaults: + _controller: '\Drupal\woot\Controller\WootController::woot' + _title: 'Woot' + requirements: + _permission: 'access content' diff --git a/vendor/drush/drush/tests/resources/modules/d8/woot/woot.services.yml b/vendor/drush/drush/tests/resources/modules/d8/woot/woot.services.yml new file mode 100644 index 0000000000..186c082b8b --- /dev/null +++ b/vendor/drush/drush/tests/resources/modules/d8/woot/woot.services.yml @@ -0,0 +1,18 @@ +services: + woot.manager: + class: Drupal\woot\WootManager + arguments: ['@current_user'] + tags: + - { name: consolidation.commandhandler } + woot.command: + class: Drupal\woot\Commands\WootCommands + tags: + - { name: consolidation.commandhandler } + greet.command: + class: Drupal\woot\Commands\GreetCommand + tags: + - { name: drush.command } + annotated_greet.command: + class: Drupal\woot\Commands\AnnotatedGreetCommand + tags: + - { name: drush.command } diff --git a/vendor/drush/drush/tests/resources/modules/d9/woot/composer.json b/vendor/drush/drush/tests/resources/modules/d9/woot/composer.json new file mode 100644 index 0000000000..72d8757320 --- /dev/null +++ b/vendor/drush/drush/tests/resources/modules/d9/woot/composer.json @@ -0,0 +1,14 @@ +{ + "name": "drupal/woot", + "type": "drupal-module", + "description": "Woot Mightily.", + "keywords": ["Drupal"], + "license": "GPL-2.0+", + "homepage": "http://drupal.org/project/woot", + "minimum-stability": "dev", + "support": { + "issues": "http://drupal.org/project/issues/woot", + "source": "http://cgit.drupalcode.org/woot" + }, + "require": { } +} diff --git a/vendor/drush/drush/tests/resources/modules/d9/woot/src/Commands/AnnotatedGreetCommand.php b/vendor/drush/drush/tests/resources/modules/d9/woot/src/Commands/AnnotatedGreetCommand.php new file mode 100644 index 0000000000..1a42b81341 --- /dev/null +++ b/vendor/drush/drush/tests/resources/modules/d9/woot/src/Commands/AnnotatedGreetCommand.php @@ -0,0 +1,41 @@ +getArgument('name'); + if ($name) { + $text = 'Hello '.$name; + } else { + $text = 'Hello'; + } + + if ($input->getOption('yell')) { + $text = strtoupper($text); + } + + $output->writeln($text); + } +} diff --git a/vendor/drush/drush/tests/resources/modules/d9/woot/src/Commands/GreetCommand.php b/vendor/drush/drush/tests/resources/modules/d9/woot/src/Commands/GreetCommand.php new file mode 100644 index 0000000000..644630b7d9 --- /dev/null +++ b/vendor/drush/drush/tests/resources/modules/d9/woot/src/Commands/GreetCommand.php @@ -0,0 +1,52 @@ +setName('demo:greet') + ->setDescription('Greet someone') + ->addArgument( + 'name', + InputArgument::OPTIONAL, + 'Who do you want to greet?' + ) + ->addOption( + 'yell', + null, + InputOption::VALUE_NONE, + 'If set, the task will yell in uppercase letters' + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $name = $input->getArgument('name'); + if ($name) { + $text = 'Hello '.$name; + } else { + $text = 'Hello'; + } + + if ($input->getOption('yell')) { + $text = strtoupper($text); + } + + $output->writeln($text); + } +} diff --git a/vendor/drush/drush/tests/resources/modules/d9/woot/src/Commands/WootCommands.php b/vendor/drush/drush/tests/resources/modules/d9/woot/src/Commands/WootCommands.php new file mode 100644 index 0000000000..ba23075068 --- /dev/null +++ b/vendor/drush/drush/tests/resources/modules/d9/woot/src/Commands/WootCommands.php @@ -0,0 +1,66 @@ + false]) + { + if ($options['flip']) { + return "{$two}{$one}"; + } + return "{$one}{$two}"; + } + + /** + * Demonstrate formatters. Default format is 'table'. + * + * @field-labels + * first: I + * second: II + * third: III + * @usage try:formatters --format=yaml + * @usage try:formatters --format=csv + * @usage try:formatters --fields=first,third + * @usage try:formatters --fields=III,II + * @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields + */ + public function tryFormatters($options = ['format' => 'table', 'fields' => '']) + { + $outputData = [ + 'en' => [ 'first' => 'One', 'second' => 'Two', 'third' => 'Three' ], + 'de' => [ 'first' => 'Eins', 'second' => 'Zwei', 'third' => 'Drei' ], + 'jp' => [ 'first' => 'Ichi', 'second' => 'Ni', 'third' => 'San' ], + 'es' => [ 'first' => 'Uno', 'second' => 'Dos', 'third' => 'Tres' ], + ]; + return new RowsOfFields($outputData); + } +} diff --git a/vendor/drush/drush/tests/resources/modules/d9/woot/src/Controller/WootController.php b/vendor/drush/drush/tests/resources/modules/d9/woot/src/Controller/WootController.php new file mode 100644 index 0000000000..4f9234d019 --- /dev/null +++ b/vendor/drush/drush/tests/resources/modules/d9/woot/src/Controller/WootController.php @@ -0,0 +1,31 @@ + 'markup', + '#markup' => $this->t('Woot!') + ]; + } + +} diff --git a/vendor/drush/drush/tests/resources/modules/d9/woot/src/Plugin/QueueWorker/WootRequeueException.php b/vendor/drush/drush/tests/resources/modules/d9/woot/src/Plugin/QueueWorker/WootRequeueException.php new file mode 100644 index 0000000000..6830e56d02 --- /dev/null +++ b/vendor/drush/drush/tests/resources/modules/d9/woot/src/Plugin/QueueWorker/WootRequeueException.php @@ -0,0 +1,33 @@ +get('woot_requeue_exception')) { + $state->set('woot_requeue_exception', 1); + throw new RequeueException('I am not done yet!'); + } + else { + $state->set('woot_requeue_exception', 2); + } + } + +} diff --git a/vendor/drush/drush/tests/resources/modules/d9/woot/src/WootManager.php b/vendor/drush/drush/tests/resources/modules/d9/woot/src/WootManager.php new file mode 100644 index 0000000000..9802835585 --- /dev/null +++ b/vendor/drush/drush/tests/resources/modules/d9/woot/src/WootManager.php @@ -0,0 +1,54 @@ +currentUser = $current_user; + } + + /** + * Woof mightily. Note that we can include commands directly + * inside a service class. + * + * @command woof + * @aliases wf + */ + public function woof() + { + return 'Woof!'; + } +} diff --git a/vendor/drush/drush/tests/resources/modules/d9/woot/woot.info.yml b/vendor/drush/drush/tests/resources/modules/d9/woot/woot.info.yml new file mode 100644 index 0000000000..7bc131a710 --- /dev/null +++ b/vendor/drush/drush/tests/resources/modules/d9/woot/woot.info.yml @@ -0,0 +1,5 @@ +name: Woot +type: module +description: Woot Mightily. +core_version_requirement: ^8.8 || ^9 +package: Other diff --git a/vendor/drush/drush/tests/resources/modules/d9/woot/woot.module b/vendor/drush/drush/tests/resources/modules/d9/woot/woot.module new file mode 100644 index 0000000000..b1535c4d4a --- /dev/null +++ b/vendor/drush/drush/tests/resources/modules/d9/woot/woot.module @@ -0,0 +1,33 @@ +' . t('About') . ''; + $output .= '

    ' . t('Woot Mightily.') . '

    '; + return $output; + + default: + } +} + +/** + * Implements hook_theme(). + */ +function woot_theme() { + $theme = []; + + return $theme; +} diff --git a/vendor/drush/drush/tests/resources/modules/d9/woot/woot.routing.yml b/vendor/drush/drush/tests/resources/modules/d9/woot/woot.routing.yml new file mode 100644 index 0000000000..a607603b65 --- /dev/null +++ b/vendor/drush/drush/tests/resources/modules/d9/woot/woot.routing.yml @@ -0,0 +1,8 @@ + +woot.woot_controller_woot: + path: '/woot' + defaults: + _controller: '\Drupal\woot\Controller\WootController::woot' + _title: 'Woot' + requirements: + _permission: 'access content' diff --git a/vendor/drush/drush/tests/resources/modules/d9/woot/woot.services.yml b/vendor/drush/drush/tests/resources/modules/d9/woot/woot.services.yml new file mode 100644 index 0000000000..186c082b8b --- /dev/null +++ b/vendor/drush/drush/tests/resources/modules/d9/woot/woot.services.yml @@ -0,0 +1,18 @@ +services: + woot.manager: + class: Drupal\woot\WootManager + arguments: ['@current_user'] + tags: + - { name: consolidation.commandhandler } + woot.command: + class: Drupal\woot\Commands\WootCommands + tags: + - { name: consolidation.commandhandler } + greet.command: + class: Drupal\woot\Commands\GreetCommand + tags: + - { name: drush.command } + annotated_greet.command: + class: Drupal\woot\Commands\AnnotatedGreetCommand + tags: + - { name: drush.command } diff --git a/vendor/drush/drush/tests/resources/queue_script-D7.php b/vendor/drush/drush/tests/resources/queue_script-D7.php new file mode 100644 index 0000000000..5fb7f95620 --- /dev/null +++ b/vendor/drush/drush/tests/resources/queue_script-D7.php @@ -0,0 +1,12 @@ + 'test', + 'url' => 'https://www.drupal.org/project/issues/rss/goofy?categories=All', + 'refresh' => 3600, + 'block' => 5, +)); + +// Let cron call DrupalQueue::createItem() for us. +aggregator_cron(); diff --git a/vendor/drush/drush/tests/resources/queue_script-D8.php b/vendor/drush/drush/tests/resources/queue_script-D8.php new file mode 100644 index 0000000000..679a2a3008 --- /dev/null +++ b/vendor/drush/drush/tests/resources/queue_script-D8.php @@ -0,0 +1,14 @@ + 'test', + 'url' => 'http://drupal.org/project/issues/rss/drupal?categories=All', + 'refresh' => 3600, +)); +$feed->save(); + +// Let cron call QueueInterface::createItem() for us. +aggregator_cron(); diff --git a/vendor/drush/drush/tests/resources/queue_script-D9.php b/vendor/drush/drush/tests/resources/queue_script-D9.php new file mode 100644 index 0000000000..679a2a3008 --- /dev/null +++ b/vendor/drush/drush/tests/resources/queue_script-D9.php @@ -0,0 +1,14 @@ + 'test', + 'url' => 'http://drupal.org/project/issues/rss/drupal?categories=All', + 'refresh' => 3600, +)); +$feed->save(); + +// Let cron call QueueInterface::createItem() for us. +aggregator_cron(); diff --git a/vendor/drush/drush/tests/resources/requeue_script.php b/vendor/drush/drush/tests/resources/requeue_script.php new file mode 100644 index 0000000000..03a4affbe5 --- /dev/null +++ b/vendor/drush/drush/tests/resources/requeue_script.php @@ -0,0 +1,12 @@ +get('woot_requeue_exception', TRUE); +$queue->createItem(['foo' => 'bar']); diff --git a/vendor/drush/drush/tests/resources/testDispatchUsingAlias_script.php b/vendor/drush/drush/tests/resources/testDispatchUsingAlias_script.php new file mode 100644 index 0000000000..490e24de0b --- /dev/null +++ b/vendor/drush/drush/tests/resources/testDispatchUsingAlias_script.php @@ -0,0 +1,6 @@ + TRUE)); +$valuesWithoutAlias = drush_invoke_process("@dev", "unit-return-argv", array(), array(), array()); +return array('with' => $valuesUsingAlias['object'], 'without' => $valuesWithoutAlias['object']); + diff --git a/vendor/drush/drush/tests/resources/user_fields-D8.php b/vendor/drush/drush/tests/resources/user_fields-D8.php new file mode 100644 index 0000000000..3fefc62c82 --- /dev/null +++ b/vendor/drush/drush/tests/resources/user_fields-D8.php @@ -0,0 +1,67 @@ + 'joe.user.alt@myhome.com', + 'field_user_string' => 'Private info', + 'field_user_string_long' => 'Really private info', + 'field_user_telephone' => '4104442222', + 'field_user_text' => 'Super private info', + 'field_user_text_long' => 'Super duper private info', + 'field_user_text_with_summary' => 'Private', +]; + +$user = User::create([ + 'name' => $args[2], + 'mail' => $args[3], + 'pass' => 'password', +]); + +foreach ($values as $field_name => $value) { + $user->set($field_name, $value); +} + +$return = $user->save(); + +/** + * Create a field on an entity. + * + * @param string $field_name + * The name of the field. + * @param string $field_type + * The field type. + * @param string $entity_type + * The entity type. E.g., user. + * @param $bundle + * The entity bundle. E.g., article. + */ +function create_field($field_name, $field_type, $entity_type, $bundle) { + $field_storage = FieldStorageConfig::create(array( + 'field_name' => $field_name, + 'entity_type' => $entity_type, + 'type' => $field_type, + )); + $field_storage->save(); + FieldConfig::create([ + 'field_storage' => $field_storage, + 'bundle' => $bundle, + 'label' => $field_name, + 'settings' => [], + ])->save(); +} diff --git a/vendor/drush/drush/tests/resources/user_fields-D9.php b/vendor/drush/drush/tests/resources/user_fields-D9.php new file mode 100644 index 0000000000..3fefc62c82 --- /dev/null +++ b/vendor/drush/drush/tests/resources/user_fields-D9.php @@ -0,0 +1,67 @@ + 'joe.user.alt@myhome.com', + 'field_user_string' => 'Private info', + 'field_user_string_long' => 'Really private info', + 'field_user_telephone' => '4104442222', + 'field_user_text' => 'Super private info', + 'field_user_text_long' => 'Super duper private info', + 'field_user_text_with_summary' => 'Private', +]; + +$user = User::create([ + 'name' => $args[2], + 'mail' => $args[3], + 'pass' => 'password', +]); + +foreach ($values as $field_name => $value) { + $user->set($field_name, $value); +} + +$return = $user->save(); + +/** + * Create a field on an entity. + * + * @param string $field_name + * The name of the field. + * @param string $field_type + * The field type. + * @param string $entity_type + * The entity type. E.g., user. + * @param $bundle + * The entity bundle. E.g., article. + */ +function create_field($field_name, $field_type, $entity_type, $bundle) { + $field_storage = FieldStorageConfig::create(array( + 'field_name' => $field_name, + 'entity_type' => $entity_type, + 'type' => $field_type, + )); + $field_storage->save(); + FieldConfig::create([ + 'field_storage' => $field_storage, + 'bundle' => $bundle, + 'label' => $field_name, + 'settings' => [], + ])->save(); +} diff --git a/vendor/drush/drush/tests/roleTest.php b/vendor/drush/drush/tests/roleTest.php new file mode 100644 index 0000000000..d986b43618 --- /dev/null +++ b/vendor/drush/drush/tests/roleTest.php @@ -0,0 +1,50 @@ +setUpDrupal(1, TRUE, UNISH_DRUPAL_MAJOR_VERSION, UNISH_DRUPAL_MAJOR_VERSION == 6 ? 'default' : 'standard'); + $root = $this->webroot(); + $name = "example"; + $options = array( + 'root' => $root, + 'uri' => key($sites), + 'yes' => NULL, + ); + $anonymous = 'anonymous'; + $authenticated = 'authenticated'; + if (UNISH_DRUPAL_MAJOR_VERSION < 8) { + $anonymous .= ' user'; + $authenticated .= ' user'; + } + $this->drush('role-list', array($anonymous), $options + array('pipe' => NULL) ); + $output = $this->getOutput(); + $this->assertStringContainsString('access content', $output); + $this->drush('role-list', array($authenticated), $options + array('pipe' => NULL) ); + $output = $this->getOutput(); + $this->assertStringContainsString('access content', $output); + $this->drush('role-add-perm', array($anonymous, 'administer nodes'), $options ); + $this->drush('role-list', array($anonymous), $options + array('pipe' => NULL) ); + $output = $this->getOutput(); + $this->assertStringContainsString('administer nodes', $output); + $this->drush('role-remove-perm', array($anonymous, 'administer nodes'), $options ); + $this->drush('role-list', array($anonymous), $options + array('pipe' => NULL) ); + $output = $this->getOutput(); + $this->assertStringNotContainsString('administer nodes', $output); + } +} diff --git a/vendor/drush/drush/tests/shellAliasTest.php b/vendor/drush/drush/tests/shellAliasTest.php new file mode 100644 index 0000000000..a8387a4939 --- /dev/null +++ b/vendor/drush/drush/tests/shellAliasTest.php @@ -0,0 +1,188 @@ + 'topic core-global-options', + 'pull' => '!git pull', + 'echosimple' => '!echo {{@target}}', + 'echotest' => '!echo {{@target}} {{%root}} {{%mypath}}', + 'compound-command' => '!cd {{%sandbox}} && echo second', + ); + "; + file_put_contents(UNISH_SANDBOX . '/drushrc.php', trim($contents)); + if (!file_exists(UNISH_SANDBOX . '/b')) { + mkdir(UNISH_SANDBOX . '/b'); + } + $contents = " + '!echo alternate config file included too', + ); + "; + file_put_contents(UNISH_SANDBOX . '/b/drushrc.php', trim($contents)); + $aliases['myalias'] = array( + 'root' => '/path/to/drupal', + 'uri' => 'mysite.org', + '#peer' => '@live', + 'path-aliases' => array ( + '%mypath' => '/srv/data/mypath', + '%sandbox' => UNISH_SANDBOX, + ), + ); + $contents = $this->unish_file_aliases($aliases); + file_put_contents(UNISH_SANDBOX . '/aliases.drushrc.php', $contents); + } + + /** + * Test shell aliases to Drush commands. + */ + public function testShellAliasDrushLocal() { + $options = array( + 'config' => UNISH_SANDBOX, + ); + $this->drush('glopts', array(), $options); + $output = $this->getOutput(); + $this->assertStringContainsString('These options are applicable to most drush commands.', $output, 'Successfully executed local shell alias to drush command'); + } + + /** + * Test shell aliases to Bash commands. Assure we pass along extra arguments + * and options. + */ + public function testShellAliasBashLocal() { + $options = array( + 'config' => UNISH_SANDBOX, + 'simulate' => NULL, + 'rebase' => NULL, + ); + $this->drush('pull', array('origin'), $options, NULL, NULL, self::EXIT_SUCCESS, '2>&1'); + $output = $this->getOutput(); + $this->assertStringContainsString('Calling proc_open(git pull origin --rebase);', $output); + } + + public function testShellAliasDrushRemote() { + $options = array( + 'config' => UNISH_SANDBOX, + 'simulate' => NULL, + 'ssh-options' => '', + ); + $this->drush('glopts', array(), $options, 'user@server/path/to/drupal#sitename'); + // $expected might be different on non unix platforms. We shall see. + // n.b. --config is not included in calls to remote systems. + $bash = $this->escapeshellarg('drush --config=drush-sandbox --nocolor --uri=sitename --root=/path/to/drupal core-topic core-global-options 2>&1'); + $expected = "Simulating backend invoke: ssh -t user@server $bash 2>&1"; + $output = $this->getOutput(); + // Remove any coverage arguments. The filename changes, so it's not possible + // to create a string for assertEquals, and the need for both shell escaping + // and regexp escaping different parts of the expected output for + // assertMatchesRegularExpression makes it easier just to remove the argument before checking + // the output. + $output = preg_replace('{--drush-coverage=[^ ]+ }', '', $output); + $output = preg_replace('{--config=[^ ]+ +}', '--config=drush-sandbox ', $output); + $this->assertEquals($expected, $output, 'Expected remote shell alias to a drush command was built'); + } + + public function testShellAliasBashRemote() { + $options = array( + 'config' => UNISH_SANDBOX, + 'simulate' => NULL, + 'ssh-options' => '', + 'rebase' => NULL, + ); + $this->drush('pull', array('origin'), $options, 'user@server/path/to/drupal#sitename', NULL, self::EXIT_SUCCESS, '2>&1'); + // $expected might be different on non unix platforms. We shall see. + $exec = self::escapeshellarg('cd /path/to/drupal && git pull origin --rebase'); + $expected = "Calling proc_open(ssh user@server $exec);"; + $output = $this->getOutput(); + $this->assertEquals($expected, $output, 'Expected remote shell alias to a bash command was built'); + } + + /** + * Test shell aliases with simple replacements -- no alias. + */ + public function testShellAliasSimpleReplacement() { + $options = array( + 'config' => UNISH_SANDBOX, + ); + $this->drush('echosimple', array(), $options); + // Windows command shell prints quotes (but not always?). See http://drupal.org/node/1452944. + $expected = '@none'; + $output = $this->getOutput(); + $this->assertEquals($expected, $output); + } + + /** + * Test shell aliases with complex replacements -- no alias. + */ + public function testShellAliasReplacementNoAlias() { + $options = array( + 'config' => UNISH_SANDBOX, + ); + // echo test has replacements that are not satisfied, so this is expected to return an error. + $return = $this->drush('echotest', array(), $options, NULL, NULL, self::EXIT_ERROR); + $this->assertEquals(self::EXIT_ERROR, $return); + } + + /** + * Test shell aliases with replacements -- alias. + */ + public function testShellAliasReplacementWithAlias() { + $options = array( + 'config' => UNISH_SANDBOX, + 'alias-path' => UNISH_SANDBOX, + ); + $this->drush('echotest', array(), $options, '@myalias'); + // Windows command shell prints quotes (not always?). See http://drupal.org/node/1452944. + $expected = '@myalias'; + $expected .= ' /path/to/drupal /srv/data/mypath'; + $output = $this->getOutput(); + $this->assertEquals($expected, $output); + } + + /** + * Test shell aliases with replacements and compound commands. + */ + public function testShellAliasCompoundCommands() { + $options = array( + 'config' => UNISH_SANDBOX, + 'alias-path' => UNISH_SANDBOX, + ); + $this->drush('compound-command', array(), $options, '@myalias'); + $expected = 'second'; + $output = $this->getOutput(); + $this->assertEquals($expected, $output); + } + + + /** + * Test shell aliases with multiple config files. + */ + public function testShellAliasMultipleConfigFiles() { + $options = array( + 'config' => UNISH_SANDBOX . "/b" . PATH_SEPARATOR . UNISH_SANDBOX, + 'alias-path' => UNISH_SANDBOX, + ); + $this->drush('also', array(), $options); + $expected = "alternate config file included too"; + $output = $this->getOutput(); + $this->assertEquals($expected, $output); + } + +} diff --git a/vendor/drush/drush/tests/siteAliasTest.php b/vendor/drush/drush/tests/siteAliasTest.php new file mode 100644 index 0000000000..3389c882a0 --- /dev/null +++ b/vendor/drush/drush/tests/siteAliasTest.php @@ -0,0 +1,376 @@ + 'fake.remote-host.com', + 'remote-user' => 'www-admin', + 'root' => '/fake/path/to/root', + 'uri' => 'default', + 'command-specific' => array( + 'rsync' => array( + 'delete' => TRUE, + ), + ), + ); + \$aliases['env-test'] = array( + 'root' => '/fake/path/to/root', + '#env-vars' => array( + 'DRUSH_ENV_TEST' => 'WORKING_CASE', + 'DRUSH_ENV_TEST2' => '{foo:[bar:{key:"val"},bar2:{key:"long val"}]}', + 'DRUSH_ENV_TEST3' => "WORKING CASE = TRUE", + ), + 'uri' => 'default', + ); +EOD; + file_put_contents($aliasFile, $aliasContents); + $options = array( + 'alias-path' => $aliasPath, + 'include' => dirname(__FILE__), // Find unit.drush.inc commandfile. + 'simulate' => TRUE, + ); + $this->drush('core-rsync', array('/a', '/b'), $options, '@test'); + $output = $this->getOutput(); + $command_position = strpos($output, 'core-rsync'); + $global_option_position = strpos($output, '--alias-path='); + $command_specific_position = strpos($output, '--delete'); + $this->assertTrue($command_position !== FALSE); + $this->assertTrue($global_option_position !== FALSE); + $this->assertTrue($command_specific_position !== FALSE); + $this->assertTrue($command_position > $global_option_position); + $this->assertTrue($command_position < $command_specific_position); + + $eval = '$env_test = getenv("DRUSH_ENV_TEST");'; + $eval .= '$env_test2 = getenv("DRUSH_ENV_TEST2");'; + $eval .= 'print json_encode(get_defined_vars());'; + $config = UNISH_SANDBOX . '/drushrc.php'; + $options = array( + 'alias-path' => $aliasPath, + 'root' => $this->webroot(), + 'uri' => key($this->getSites()), + 'include' => dirname(__FILE__), // Find unit.drush.inc commandfile. + ); + $this->drush('unit-eval', array($eval), $options, '@env-test'); + $output = $this->getOutput(); + $actuals = json_decode(trim($output)); + $this->assertEquals('WORKING_CASE', $actuals->env_test); + + if ($this->is_windows()) { + $this->markTestSkipped('@todo. Needs quoting fix, and environment variables not widely used on Windows.'); + } + + $this->assertEquals('{foo:[bar:{key:"val"},bar2:{key:"long val"}]}', $actuals->env_test2); + $eval = 'print getenv("DRUSH_ENV_TEST3");'; + $this->drush('unit-eval', array($eval), $options, '@env-test'); + $output = $this->getOutput(); + $this->assertEquals( "WORKING CASE = TRUE", $output); + } + + + /** + * Test to see if rsync @site:%files calculates the %files path correctly. + * This tests the non-optimized code path in drush_sitealias_resolve_path_references. + * + * @todo This test does not appear to accomplish its goal. + */ + function testRsyncBothRemote() { + $aliasPath = UNISH_SANDBOX . '/site-alias-directory'; + file_exists($aliasPath) ?: mkdir($aliasPath); + $aliasFile = $aliasPath . '/remote.aliases.drushrc.php'; + $aliasContents = << 'fake.remote-host.com', + 'remote-user' => 'www-admin', + 'root' => '/fake/path/to/root', + 'uri' => 'default', + ); + \$aliases['two'] = array( + 'remote-host' => 'other-fake.remote-host.com', + 'remote-user' => 'www-admin', + 'root' => '/other-fake/path/to/root', + 'uri' => 'default', + ); +EOD; + file_put_contents($aliasFile, $aliasContents); + $options = array( + 'alias-path' => $aliasPath, + 'simulate' => TRUE, + 'yes' => NULL, + ); + $this->drush('core-rsync', array("@remote.one:files", "@remote.two:tmp"), $options, NULL, NULL, self::EXIT_SUCCESS, '2>&1;'); + $output = $this->getOutput(); + $level = $this->log_level(); + $pattern = in_array($level, array('verbose', 'debug')) ? "Calling system(rsync -e 'ssh ' -akzv --stats --progress --yes %s /tmp);" : "Calling system(rsync -e 'ssh ' -akz --yes %s /tmp);"; + $expected = sprintf($pattern, UNISH_SANDBOX . "/web/sites/default/files"); + + + // Expected ouput: + // Simulating backend invoke: /path/to/php -d sendmail_path='true' /path/to/drush.php --php=/path/to/php --php-options=' -d sendmail_path='\''true'\''' --backend=2 --alias-path=/path/to/site-alias-directory --nocolor --root=/fake/path/to/root --uri=default core-rsync '@remote.one:files' /path/to/tmpdir 2>&1 + // Simulating backend invoke: /path/to/php -d sendmail_path='true' /path/to/drush.php --php=/path/to/php --php-options=' -d sendmail_path='\''true'\''' --backend=2 --alias-path=/path/to/site-alias-directory --nocolor --root=/fake/path/to/root --uri=default core-rsync /path/to/tmpdir/files '@remote.two:tmp' 2>&1' + // Since there are a lot of variable items in the output (e.g. path + // to a temporary folder), so we will use 'assertStringContainsString' to + // assert on portions of the output that does not vary. + $this->assertStringContainsString('Simulating backend invoke', $output); + $this->assertStringContainsString("core-rsync '@remote.one:files' /", $output); + $this->assertStringContainsString("/files '@remote.two:tmp'", $output); + } + + /** + * Assure that site lists work as expected. + * @todo Use --backend for structured return data. Depends on http://drupal.org/node/1043922 + */ + public function testSAList() { + $sites = $this->setUpDrupal(2); + $subdirs = array_keys($sites); + $eval = 'print "bon";'; + $options = array( + 'yes' => NULL, + 'verbose' => NULL, + 'root' => $this->webroot(), + ); + foreach ($subdirs as $dir) { + $dirs[] = "#$dir"; + } + $this->drush('php-eval', array($eval), $options, implode(',', $dirs)); + $output = $this->getOutputAsList(); + $expected = "#stage >> bon +#dev >> bon"; + $actual = implode("\n", $output); + $actual = trim(preg_replace('/^#[a-z]* *>> *$/m', '', $actual)); // ignore blank lines + $this->assertEquals($expected, $actual); + } + + /** + * Ensure that requesting a non-existent alias throws an error. + */ + public function testBadAlias() { + $return = $this->drush('sa', array('@badalias'), array(), NULL, NULL, self::EXIT_ERROR); + $this->assertEquals(self::EXIT_ERROR, $return); + } + + /** + * Test wildcard aliases + */ + public function testWildcardAliases() { + $aliasPath = UNISH_SANDBOX . '/site-alias-directory'; + file_exists($aliasPath) ?: mkdir($aliasPath); + $aliasFile = $aliasPath . '/wild.aliases.drushrc.php'; + $aliasContents = << '\${env-name}.remote-host.com', + 'remote-user' => 'www-admin', + 'root' => '/path/to/\${env-name}', + 'uri' => 'https://\${env-name}.foo.example.com', + ); +EOD; + file_put_contents($aliasFile, $aliasContents); + $options = array( + 'alias-path' => $aliasPath, + 'yes' => NULL, + ); + $this->drush('sa', array('@foo.bar'), $options, NULL, NULL, self::EXIT_SUCCESS); + $output = $this->getOutput(); + $this->assertEquals("\$aliases[\"foo.bar\"] = array ( + 'remote-host' => 'bar.remote-host.com', + 'remote-user' => 'www-admin', + 'root' => '/path/to/bar', + 'uri' => 'https://bar.foo.example.com', +);", $output); + } + + /** + * Ensure that a --uri on CLI overrides on provided by site alias during a backend invoke. + */ + public function testBackendHonorsAliasOverride() { + if (UNISH_DRUPAL_MAJOR_VERSION == 6) { + $this->markTestSkipped("Sites.php not available in Drupal 6 core."); + } + + // Test a standard remote dispatch. + $this->drush('core-status', array(), array('uri' => 'http://example.com', 'simulate' => NULL), 'user@server/path/to/drupal#sitename'); + $this->assertStringContainsString('--uri=http://example.com', $this->getOutput()); + + // Test a local-handling command which uses drush_redispatch_get_options(). + $this->drush('browse', array(), array('uri' => 'http://example.com', 'simulate' => NULL), 'user@server/path/to/drupal#sitename'); + $this->assertStringContainsString('--uri=http://example.com', $this->getOutput()); + + // Test a command which uses drush_invoke_process('@self') internally. + $sites = $this->setUpDrupal(1, TRUE); + $name = key($sites); + $sites_php = "\n\$sites['example.com'] = '$name';"; + @mkdir($sites[$name]['root'] . '/sites'); + file_put_contents($sites[$name]['root'] . '/sites/sites.php', $sites_php, FILE_APPEND); + $this->drush('pm-updatecode', array(), array('uri' => 'http://example.com', 'no' => NULL, 'no-core' => NULL, 'verbose' => NULL), '@' . $name); + $this->assertStringContainsString('--uri=http://example.com', $this->getErrorOutput()); + + // Test a remote alias that does not have a 'root' element + $aliasPath = UNISH_SANDBOX . '/site-alias-directory'; + @mkdir($aliasPath); + $aliasContents = << 'remoteuri', + 'remote-host' => 'exampleisp.com', + 'remote-user' => 'www-admin', + ); +EOD; + file_put_contents("$aliasPath/rootlessremote.aliases.drushrc.php", $aliasContents); + $this->drush('core-status', array(), array('uri' => 'http://example.com', 'simulate' => NULL, 'alias-path' => $aliasPath), '@rootlessremote'); + $output = $this->getOutput(); + $this->assertStringContainsString(' ssh ', $output); + $this->assertStringContainsString('--uri=http://example.com', $output); + + // Test a remote alias that does not have a 'root' element with cwd inside a Drupal root directory + $root = $this->webroot(); + $this->drush('core-status', array(), array('uri' => 'http://example.com', 'simulate' => NULL, 'alias-path' => $aliasPath), '@rootlessremote', $root); + $output = $this->getOutput(); + $this->assertStringContainsString(' ssh ', $output); + $this->assertStringContainsString('--uri=http://example.com', $output); + } + + /** + * Test to see if we can access aliases defined inside of + * a provided Drupal root in various locations where they + * may be stored. + */ + public function testAliasFilesInDocroot() { + $root = $this->webroot(); + + $aliasContents = << '/fake/path/to/othersite', + 'uri' => 'default', + ); +EOD; + @mkdir($root . "/drush"); + @mkdir($root . "/drush/site-aliases"); + file_put_contents($root . "/drush/site-aliases/atroot.aliases.drushrc.php", $aliasContents); + + $aliasContents = << '/fake/path/to/othersite', + 'uri' => 'default', + ); +EOD; + @mkdir($root . "/sites"); + @mkdir($root . "/sites/all"); + @mkdir($root . "/sites/all/drush"); + @mkdir($root . "/sites/all/drush/site-aliases"); + file_put_contents($root . "/sites/all/drush/site-aliases/sitefolder.aliases.drushrc.php", $aliasContents); + + $aliasContents = << '/fake/path/to/othersite', + 'uri' => 'default', + ); +EOD; + @mkdir($root . "/../drush"); + @mkdir($root . "/../drush/site-aliases"); + file_put_contents($root . "/../drush/site-aliases/aboveroot.aliases.drushrc.php", $aliasContents); + + // Ensure that none of these 'sa' commands return an error + $return = $this->drush('sa', array('@atroot'), array(), '@dev'); + $this->assertEquals(self::EXIT_SUCCESS, $return); + $return = $this->drush('sa', array('@insitefolder'), array(), '@dev'); + $this->assertEquals(self::EXIT_SUCCESS, $return); + $return = $this->drush('sa', array('@aboveroot'), array(), '@dev'); + $this->assertEquals(self::EXIT_SUCCESS, $return); + } + + + /** + * Ensure that Drush searches deep inside specified search locations + * for alias files. + */ + public function testDeepAliasSearching() { + $aliasPath = UNISH_SANDBOX . '/site-alias-directory'; + file_exists($aliasPath) ?: mkdir($aliasPath); + $deepPath = $aliasPath . '/deep'; + file_exists($deepPath) ?: mkdir($deepPath); + $aliasFile = $deepPath . '/baz.aliases.drushrc.php'; + $aliasContents = << 'fake.remote-host.com', + 'remote-user' => 'www-admin', + 'root' => '/fake/path/to/root', + 'uri' => 'default', + 'command-specific' => array( + 'rsync' => array( + 'delete' => TRUE, + ), + ), + ); +EOD; + file_put_contents($aliasFile, $aliasContents); + $options = array( + 'alias-path' => $aliasPath, + 'simulate' => TRUE, + ); + + $this->drush('sa', array('@deep'), $options); + + // Verify that the files directory is not recursed into. + $filesPath = $aliasPath . '/files'; + file_exists($filesPath) ?: mkdir($filesPath); + $aliasFile = $filesPath . '/biz.aliases.drushrc.php'; + $aliasContents = << 'fake.remote-host.com', + 'remote-user' => 'www-admin', + 'root' => '/fake/path/to/root', + 'uri' => 'default', + 'command-specific' => array( + 'rsync' => array( + 'delete' => TRUE, + ), + ), + ); +EOD; + file_put_contents($aliasFile, $aliasContents); + $options = array( + 'alias-path' => $aliasPath, + 'simulate' => TRUE, + ); + + // This should not find the '@nope' alias. + $return = $this->drush('sa', array('@nope'), $options, NULL, NULL, self::EXIT_ERROR); + $this->assertEquals(self::EXIT_ERROR, $return); + } +} diff --git a/vendor/drush/drush/tests/siteAliasUnitTest.php b/vendor/drush/drush/tests/siteAliasUnitTest.php new file mode 100644 index 0000000000..4b52510072 --- /dev/null +++ b/vendor/drush/drush/tests/siteAliasUnitTest.php @@ -0,0 +1,58 @@ + 'fake.remote-host.com', + 'remote-user' => 'www-admin', + 'root' => '/fake/path/to/root', + 'uri' => 'default', + 'command-specific' => array( + 'rsync' => array( + 'delete' => TRUE, + ), + ), + ); + // Site alias which overrides some settings from $site_alias_a. + $site_alias_b = array( + 'remote-host' => 'another-fake.remote-host.com', + 'remote-user' => 'www-other', + 'root' => '/fake/path/to/root', + 'uri' => 'default', + 'command-specific' => array( + 'rsync' => array( + 'delete' => FALSE, + ), + ), + ); + // Expected result from merging $site_alias_a and $site_alias_b. + $site_alias_expected = array( + 'remote-host' => 'another-fake.remote-host.com', + 'remote-user' => 'www-other', + 'root' => '/fake/path/to/root', + 'uri' => 'default', + 'command-specific' => array( + 'rsync' => array( + 'delete' => FALSE, + ), + ), + ); + + $site_alias_result = _sitealias_array_merge($site_alias_a, $site_alias_b); + $this->assertEquals($site_alias_expected, $site_alias_result); + } +} diff --git a/vendor/drush/drush/tests/siteIntallD6Test.php b/vendor/drush/drush/tests/siteIntallD6Test.php new file mode 100644 index 0000000000..b7b553f9d4 --- /dev/null +++ b/vendor/drush/drush/tests/siteIntallD6Test.php @@ -0,0 +1,78 @@ +markTestSkipped('This test class is designed for Drupal 6.'); + return; + } + } + + /** + * Test a D6 install with extra options. + */ + public function testExtraConfigurationOptions() { + // Set up codebase without installing Drupal. + $sites = $this->setUpDrupal(1, FALSE, '6'); + $root = $this->webroot(); + $site = key($sites); + + // Copy the "example" test profile into the newly created site's profiles directory + $profile_dir = "$root/profiles/example"; + mkdir($profile_dir); + copy(dirname(__FILE__) . '/resources/example.profile', $profile_dir . '/example.profile'); + + $test_string = $this->randomString(); + // example.profile Has values 0-2 defined as allowed. + $test_int = rand(0, 2); + $site_name = $this->randomString(); + + $this->drush('site-install', array( + // First argument is the profile name + 'example', + // Then the extra profile options + "myopt1=$test_string", + "myopt2=$test_int", + ), + array( + 'db-url' => $this->db_url($site), + 'yes' => NULL, + 'sites-subdir' => $site, + 'root' => $root, + 'site-name' => $site_name, + 'uri' => $site, + )); + + $this->checkVariable('site_name', $site_name, $site); + $this->checkVariable('myopt1', $test_string, $site); + $this->checkVariable('myopt2', $test_int, $site); + } + + /** + * Check the value of a Drupal variable against an expectation using drush. + * + * @param $name + * The variable name. + * @param $value + * The expected value of this variable. + * @param $site + * The name of an individual multisite installation site. + */ + private function checkVariable($name, $value, $site) { + $options = array( + 'root' => $this->webroot(), + 'uri' => $site, + ); + + $this->drush('variable-get', array($name), $options); + $this->assertEquals("$name: $value", $this->getOutput()); + } +} diff --git a/vendor/drush/drush/tests/siteSetTest.php b/vendor/drush/drush/tests/siteSetTest.php new file mode 100644 index 0000000000..46d29d3ea7 --- /dev/null +++ b/vendor/drush/drush/tests/siteSetTest.php @@ -0,0 +1,39 @@ +is_windows()) { + $this->markTestSkipped('Site-set not currently available on Windows.'); + } + $sites = $this->setUpDrupal(1, TRUE); + $site_names = array_keys($sites); + $alias = '@' . $site_names[0]; + + $this->drush('ev', array("drush_invoke('site-set', '$alias'); print drush_sitealias_site_get();")); + $output = $this->getOutput(); + $this->assertEquals("Site set to $alias\n$alias", $output); + + $this->drush('site-set', array()); + $output = $this->getOutput(); + $this->assertEquals('Site set to @none', $output); + + $this->drush('site-set', array($alias)); + $expected = 'Site set to ' . $alias; + $output = $this->getOutput(); + $this->assertEquals($expected, $output); + + $this->drush('ev', array("drush_invoke('site-set', '@none'); drush_invoke('site-set', '$alias'); drush_invoke('site-set', '@none'); drush_invoke('site-set', '-'); print drush_sitealias_site_get();")); + $output = $this->getOutput(); + $this->assertEquals("Site set to @none +Site set to $alias +Site set to @none +Site set to $alias +$alias", $output); + } +} diff --git a/vendor/drush/drush/tests/siteSetUnitTest.php b/vendor/drush/drush/tests/siteSetUnitTest.php new file mode 100644 index 0000000000..3b2bf79ac9 --- /dev/null +++ b/vendor/drush/drush/tests/siteSetUnitTest.php @@ -0,0 +1,25 @@ +is_windows()) { + $this->markTestSkipped('Site-set not currently available on Windows.'); + } + + $tmp_path = UNISH_TMP; + putenv("TMPDIR=$tmp_path"); + $posix_pid = posix_getppid(); + $username = drush_get_username(); + + $expected_file = UNISH_TMP . '/drush-env-' . $username . '/drush-drupal-site-' . $posix_pid; + $filename = drush_sitealias_get_envar_filename(); + + $this->assertEquals($expected_file, $filename); + } +} diff --git a/vendor/drush/drush/tests/siteSshTest.php b/vendor/drush/drush/tests/siteSshTest.php new file mode 100644 index 0000000000..0cf8c5823e --- /dev/null +++ b/vendor/drush/drush/tests/siteSshTest.php @@ -0,0 +1,73 @@ +is_windows()) { + $this->markTestSkipped('ssh command not currently available on Windows.'); + } + + $options = array( + 'simulate' => NULL, + ); + $this->drush('ssh', array(), $options, 'user@server/path/to/drupal#sitename', NULL, self::EXIT_SUCCESS, '2>&1'); + $output = $this->getOutput(); + $expected = sprintf('Calling proc_open(ssh -o PasswordAuthentication=no -t %s@%s %s);', self::escapeshellarg('user'), self::escapeshellarg('server'), "'cd /path/to/drupal && bash -l'"); + $this->assertEquals($expected, $output); + } + + /** + * Test drush ssh --simulate 'date'. + * @todo Run over a site list. drush_sitealias_get_record() currently cannot + * handle a site list comprised of longhand site specifications. + */ + public function testNonInteractive() { + $options = array( + 'cd' => '0', + 'simulate' => NULL, + ); + $this->drush('ssh', array('date'), $options, 'user@server/path/to/drupal#sitename', NULL, self::EXIT_SUCCESS, '2>&1'); + $output = $this->getOutput(); + $expected = sprintf('Calling proc_open(ssh -o PasswordAuthentication=no %s@%s %s);', self::escapeshellarg('user'), self::escapeshellarg('server'), self::escapeshellarg('date')); + $this->assertEquals($expected, $output); + } + + /** + * Test drush ssh with multiple arguments (preferred form). + */ + public function testSshMultipleArgs() { + $options = array( + 'cd' => '0', + 'simulate' => NULL, + ); + $this->drush('ssh', array('ls', '/path1', '/path2'), $options, 'user@server/path/to/drupal#sitename', NULL, self::EXIT_SUCCESS, '2>&1'); + $output = $this->getOutput(); + $expected = sprintf('Calling proc_open(ssh -o PasswordAuthentication=no %s@%s %s);', self::escapeshellarg('user'), self::escapeshellarg('server'), self::escapeshellarg('ls /path1 /path2')); + $this->assertEquals($expected, $output); + } + + /** + * Test drush ssh with multiple arguments (legacy form). + */ + public function testSshMultipleArgsLegacy() { + $options = array( + 'cd' => '0', + 'simulate' => NULL, + ); + $this->drush('ssh', array('ls /path1 /path2'), $options, 'user@server/path/to/drupal#sitename', NULL, self::EXIT_SUCCESS, '2>&1'); + $output = $this->getOutput(); + $expected = sprintf('Calling proc_open(ssh -o PasswordAuthentication=no %s@%s %s);', self::escapeshellarg('user'), self::escapeshellarg('server'), self::escapeshellarg('ls /path1 /path2')); + $this->assertEquals($expected, $output); + } +} diff --git a/vendor/drush/drush/tests/sqlConnectCreateTest.php b/vendor/drush/drush/tests/sqlConnectCreateTest.php new file mode 100644 index 0000000000..2b2f3f430c --- /dev/null +++ b/vendor/drush/drush/tests/sqlConnectCreateTest.php @@ -0,0 +1,68 @@ +setUpDrupal(1, TRUE); + $options = array( + 'yes' => NULL, + 'root' => $this->webroot(), + 'uri' => key($sites), + ); + + // Get the connection details with sql-connect and check its structure. + $this->drush('sql-connect', array(), $options); + $connectionString = $this->getOutput(); + + // Not all drivers need -e option like sqlite + $shell_options = "-e"; + $db_driver = $this->db_driver(); + if ($db_driver == 'mysql') { + $this->assertMatchesRegularExpression('/^mysql --user=[^\s]+ --password=.* --database=[^\s]+ --host=[^\s]+/', $connectionString); + } + elseif ($db_driver == 'sqlite') { + $this->assertStringContainsString('sqlite3', $connectionString); + $shell_options = ''; + } + elseif ($db_driver == 'pgsql') { + $this->assertMatchesRegularExpression('/^psql -q --dbname=[^\s]+ --host=[^\s]+ --port=[^\s]+ --username=[^\s]+/', $connectionString); + } + else { + $this->markTestSkipped('sql-connect test does not recognize database type in ' . UNISH_DB_URL); + } + + // Issue a query and check the result to verify the connection. + $this->execute($connectionString . ' ' . $shell_options . ' "SELECT uid FROM users where uid = 1;"'); + $output = $this->getOutput(); + $this->assertStringContainsString('1', $output); + + // Run 'core-status' and insure that we can bootstrap Drupal. + $this->drush('core-status', array("Drupal bootstrap"), $options); + $output = $this->getOutput(); + $this->assertStringContainsString('Successful', $output); + + // Test to see if 'sql-create' can erase the database. + // The only output is a confirmation string, so we'll run + // other commands to confirm that this worked. + $this->drush('sql-create', array(), $options); + + // Try to execute a query. This should give a "table not found" error. + $this->execute($connectionString . ' ' . $shell_options . ' "SELECT uid FROM users where uid = 1;"', self::EXIT_ERROR); + + // We should still be able to run 'core-status' without getting an + // error, although Drupal should not bootstrap any longer. + $this->drush('core-status', array("Drupal bootstrap"), $options); + $output = $this->getOutput(); + $this->assertStringNotContainsString('Successful', $output); + } +} diff --git a/vendor/drush/drush/tests/sqlDumpTest.php b/vendor/drush/drush/tests/sqlDumpTest.php new file mode 100644 index 0000000000..199983ab21 --- /dev/null +++ b/vendor/drush/drush/tests/sqlDumpTest.php @@ -0,0 +1,121 @@ +db_driver() == 'sqlite') { + $this->markTestSkipped('SQL Dump does not apply to SQLite.'); + return; + } + + $this->setUpDrupal(1, TRUE); + $root = $this->webroot(); + $uri = 'dev'; + $full_dump_file_path = UNISH_SANDBOX . DIRECTORY_SEPARATOR . 'full_db.sql'; + + $options = array( + 'result-file' => $full_dump_file_path, + // Last 5 entries are for D8+ + 'skip-tables-list' => 'hist*,cache*,router,config*,watchdog,key_valu*', + 'yes' => NULL, + ); + $site_selection_options = array( + 'root' => $root, + 'uri' => $uri, + ); + + // Test --extra option + if ($this->db_driver() == 'mysql') { + $this->drush('sql-dump', array(), array_merge($options, $site_selection_options, array('extra' => '--skip-add-drop-table'))); + $this->assertFileExists($full_dump_file_path); + $full_dump_file = file_get_contents($full_dump_file_path); + $this->assertStringNotContainsString('DROP TABLE IF EXISTS', $full_dump_file); + } + + $table_to_check = 'history'; + if (UNISH_DRUPAL_MAJOR_VERSION > 7) { + $table_to_check = 'cache_config'; + } + + // First, do a test without any aliases, and dump the whole database + $this->drush('sql-dump', array(), array_merge($options, $site_selection_options)); + $this->assertFileExists($full_dump_file_path); + $full_dump_file = file_get_contents($full_dump_file_path); + // Test that we have sane contents. + $this->assertStringContainsString('queue', $full_dump_file); + // Test skip-files-list and wildcard expansion. + $this->assertStringNotContainsString($table_to_check, $full_dump_file); + // Next, set up an alias file and run a couple of simulated + // tests to see if options are propagated correctly. + // Control: insure options are not set when not specified + unset($options['skip-tables-list']); + unlink($full_dump_file_path); + $this->drush('sql-dump', array(), array_merge($options, $site_selection_options)); + $this->assertFileExists($full_dump_file_path); + $full_dump_file = file_get_contents($full_dump_file_path); + // Test that we have sane contents. + $this->assertStringContainsString('queue', $full_dump_file); + // Test skip-files-list and wildcard expansion. + $this->assertStringContainsString($table_to_check, $full_dump_file); + + $aliasPath = UNISH_SANDBOX . '/aliases'; + mkdir($aliasPath); + $aliasFile = $aliasPath . '/bar.aliases.drushrc.php'; + $aliasContents = << '$root', + 'uri' => '$uri', + 'site' => 'stage', + 'command-specific' => array( + 'sql-dump' => array( + 'skip-tables-list' => 'hist*,cache*,router,config*,watchdog,key_valu*', + ), + ), + ); +EOD; + file_put_contents($aliasFile, $aliasContents); + $options['alias-path'] = $aliasPath; + unlink($full_dump_file_path); + // Now run again with an alias, and test to see if the option is there + $this->drush('sql-dump', array(), array_merge($options), '@test'); + $this->assertFileExists($full_dump_file_path); + $full_dump_file = file_get_contents($full_dump_file_path); + // Test that we have sane contents. + $this->assertStringContainsString('queue', $full_dump_file); + // Test skip-files-list and wildcard expansion. + $this->assertStringNotContainsString($table_to_check, $full_dump_file); + // Repeat control test: options not recovered in absence of an alias. + unlink($full_dump_file_path); + $this->drush('sql-dump', array(), array_merge($options, $site_selection_options)); + $this->assertFileExists($full_dump_file_path); + $full_dump_file = file_get_contents($full_dump_file_path); + // Test that we have sane contents. + $this->assertStringContainsString('queue', $full_dump_file); + // Test skip-files-list and wildcard expansion. + $this->assertStringContainsString($table_to_check, $full_dump_file); + // Now run yet with @self, and test to see that Drush can recover the option + // --skip-tables-list, defined in @test. + unlink($full_dump_file_path); + $this->drush('sql-dump', array(), array_merge($options, $site_selection_options), '@self'); + $this->assertFileExists($full_dump_file_path); + $full_dump_file = file_get_contents($full_dump_file_path); + // Test that we have sane contents. + $this->assertStringContainsString('queue', $full_dump_file); + // Test skip-files-list and wildcard expansion. + $this->assertStringNotContainsString($table_to_check, $full_dump_file); + } +} diff --git a/vendor/drush/drush/tests/sqlSyncTest.php b/vendor/drush/drush/tests/sqlSyncTest.php new file mode 100644 index 0000000000..86a4a29b86 --- /dev/null +++ b/vendor/drush/drush/tests/sqlSyncTest.php @@ -0,0 +1,184 @@ +db_driver() == 'sqlite') { + $this->markTestSkipped('SQL Sync does not apply to SQLite.'); + return; + } + + $sites = $this->setUpDrupal(2, TRUE); + return $this->localSqlSync(); + } + /** + * Do the same test as above, but use Drupal 6 sites instead of Drupal 7. + */ + public function testLocalSqlSyncD6() { + if (UNISH_DRUPAL_MAJOR_VERSION != 6) { + $this->markTestSkipped('This test class is designed for Drupal 6.'); + return; + } + + chdir(UNISH_TMP); // Avoids perm denied Windows error. + $this->setUpBeforeClass(); + $sites = $this->setUpDrupal(2, TRUE, '6'); + return $this->localSqlSync(); + } + + public function localSqlSync() { + + $options = array( + 'root' => $this->webroot(), + 'uri' => 'stage', + 'yes' => NULL, + ); + + // Create a user in the staging site + $name = 'joe.user'; + $mail = "joe.user@myhome.com"; + + if (UNISH_DRUPAL_MAJOR_VERSION >= 8) { + // Add user fields and a test User. + $this->drush('pm-enable', array('field,text,telephone,comment'), $options + array('yes' => NULL)); + $this->drush('php-script', array( + 'user_fields-D' . UNISH_DRUPAL_MAJOR_VERSION, + $name, + $mail + ), $options + array( + 'script-path' => __DIR__ . '/resources', + 'debug' => NULL + )); + } + else { + $this->drush('user-create', array($name), $options + array('password' => 'password', 'mail' => $mail)); + } + + // Copy stage to dev with --sanitize. + $sync_options = array( + 'sanitize' => NULL, + 'yes' => NULL, + // Test wildcards expansion from within sql-sync. Also avoid D8 persistent entity cache. + 'structure-tables-list' => 'cache,cache*', + ); + $this->drush('sql-sync', array('@stage', '@dev'), $sync_options); + + // Confirm that the sample user has the correct email address on the staging site + $this->drush('user-information', array($name), $options + array('pipe' => NULL)); + $output = $this->getOutput(); + $row = str_getcsv($output); + $uid = $row[1]; + $this->assertEquals($mail, $row[2], 'email address is unchanged on source site.'); + $this->assertEquals($name, $row[0]); + + $options = array( + 'root' => $this->webroot(), + 'uri' => 'dev', + 'yes' => NULL, + ); + // Confirm that the sample user's email address has been sanitized on the dev site + $this->drush('user-information', array($name), $options + array('pipe' => NULL)); + $output = $this->getOutput(); + $row = str_getcsv($output); + $uid = $row[1]; + $this->assertEquals("user+$uid@localhost.localdomain", $row[2], 'email address was sanitized on destination site.'); + $this->assertEquals($name, $row[0]); + + // @todo Confirm that the role_permissions table no longer exists in dev site (i.e. wildcard expansion works in sql-sync). + // $this->drush('sql-query', array('SELECT * FROM role_permission'), $options, NULL, NULL, self::EXIT_ERROR); + + // Copy stage to dev with --sanitize and a fixed sanitized email + $sync_options = array( + 'sanitize' => NULL, + 'yes' => NULL, + 'sanitize-email' => 'user@mysite.org', + // Test wildcards expansion from within sql-sync. Also avoid D8 persistent entity cache. + 'structure-tables-list' => 'cache,cache*', + ); + $this->drush('sql-sync', array('@stage', '@dev'), $sync_options); + + $options = array( + 'root' => $this->webroot(), + 'uri' => 'dev', + 'yes' => NULL, + ); + // Confirm that the sample user's email address has been sanitized on the dev site + $this->drush('user-information', array($name), $options + array('pipe' => NULL)); + $output = $this->getOutput(); + $row = str_getcsv($output); + $uid = $row[1]; + $this->assertEquals("user@mysite.org", $row[2], 'email address was sanitized (fixed email) on destination site.'); + $this->assertEquals($name, $row[0]); + + // TODO: Make this >= 8 once the --sanitize option is fixed on Drupal 9 + if (UNISH_DRUPAL_MAJOR_VERSION == 8) { + // Assert that field_user_telephone DOES contain "5555555555". + $this->assertUserFieldContents('field_user_telephone', '5555555555', $options, TRUE); + + $fields = [ + 'field_user_email' => 'joe.user.alt@myhome.com', + 'field_user_string' => 'Private info', + 'field_user_string_long' => 'Really private info', + ]; + + // TODO: 'text' fields currently NOT being sanitized + // 'field_user_text' => 'Super private info', + // 'field_user_text_long' => 'Super duper private info', + // 'field_user_text_with_summary' => 'Private', + + // Assert that field DO NOT contain values. + foreach ($fields as $field_name => $value) { + $this->assertUserFieldContents($field_name, $value, $options); + } + } + } + + /** + * Assert that a field on the user entity does or does not contain a value. + * + * @param string $field_name + * The machine name of the field. + * @param string $value + * The field value. + * @param array $options + * Options to be added to the sql-query command. + * @param bool $should_contain + * Whether the field should contain the value. Defaults to false. + */ + public function assertUserFieldContents($field_name, $value, $options = [], $should_contain = FALSE) { + $table = 'user__' . $field_name; + $column = $field_name . '_value'; + $this->drush('sql-query', [ "SELECT $column FROM $table LIMIT 1" ], $options); + $output = $this->getOutput(); + $this->assertNotEmpty($output); + + if ($should_contain) { + $this->assertStringContainsString($value, $output); + } + else { + $this->assertStringNotContainsString($value, $output); + } + } +} diff --git a/vendor/drush/drush/tests/tablesUnitTest.php b/vendor/drush/drush/tests/tablesUnitTest.php new file mode 100644 index 0000000000..3996e88ed0 --- /dev/null +++ b/vendor/drush/drush/tests/tablesUnitTest.php @@ -0,0 +1,111 @@ +original_columns = drush_get_context('DRUSH_COLUMNS'); + + // Some table data we reuse between tests. + $this->numbers = array( + array('1', '12', '123'), + array('1234', '12345', '123456'), + array('1234567', '12345678', '123456789'), + ); + $this->words = array( + array('Drush is a command line shell', 'scripting interface', 'for Drupal'), + array('A veritable', 'Swiss Army knife', 'designed to make life easier for us'), + ); + } + + function tear_down() { + drush_set_context('DRUSH_COLUMNS', $this->original_columns); + } + + /** + * Tests drush_format_table() at various table widths with automatic column + * sizing. + * + * @see drush_format_table(). + */ + public function testFormatAutoWidths() { + // print "\n'" . str_replace("\n", "' . PHP_EOL . '", $output) . "'\n"; + drush_set_context('DRUSH_COLUMNS', 16); + $output = drush_format_table($this->numbers); + $expected = ' 1 12 123 ' . PHP_EOL . ' 123 123 1234 ' . PHP_EOL . ' 4 45 56 ' . PHP_EOL . ' 123 123 1234 ' . PHP_EOL . ' 456 456 5678 ' . PHP_EOL . ' 7 78 9 ' . PHP_EOL; + $this->assertEquals($expected, $output); + + drush_set_context('DRUSH_COLUMNS', 22); + $output = drush_format_table($this->numbers); + $expected = ' 1 12 123 ' . PHP_EOL . ' 1234 12345 123456 ' . PHP_EOL . ' 12345 12345 123456 ' . PHP_EOL . ' 67 678 789 ' . PHP_EOL; + $this->assertEquals($expected, $output); + + drush_set_context('DRUSH_COLUMNS', 24); + $output = drush_format_table($this->numbers); + $expected = ' 1 12 123 ' . PHP_EOL . ' 1234 12345 123456 ' . PHP_EOL . ' 123456 123456 123456 ' . PHP_EOL . ' 7 78 789 ' . PHP_EOL; + $this->assertEquals($expected, $output); + + drush_set_context('DRUSH_COLUMNS', 80); + $output = drush_format_table($this->numbers); + $expected = ' 1 12 123 ' . PHP_EOL . ' 1234 12345 123456 ' . PHP_EOL . ' 1234567 12345678 123456789 ' . PHP_EOL; + $this->assertEquals($expected, $output); + } + + /** + * Tests drush_format_table() at various table widths. + * + * @see drush_format_table(). + */ + public function testFormatWidths() { + // print "\n'" . str_replace("\n", "' . PHP_EOL . '", $output) . "'\n"; + drush_set_context('DRUSH_COLUMNS', 22); + $output = drush_format_table($this->numbers, FALSE, array(2)); + $expected = ' 1 12 123 ' . PHP_EOL . ' 12 12345 123456 ' . PHP_EOL . ' 34 ' . PHP_EOL . ' 12 1234567 1234567 ' . PHP_EOL . ' 34 8 89 ' . PHP_EOL . ' 56 ' . PHP_EOL . ' 7 ' . PHP_EOL; + $this->assertEquals($expected, $output); + + $output = drush_format_table($this->numbers, FALSE, array(10)); + $expected = ' 1 12 123 ' . PHP_EOL . ' 1234 123 123 ' . PHP_EOL . ' 45 456 ' . PHP_EOL . ' 1234567 123 123 ' . PHP_EOL . ' 456 456 ' . PHP_EOL . ' 78 789 ' . PHP_EOL; + $this->assertEquals($expected, $output); + + $output = drush_format_table($this->numbers, FALSE, array(2, 2)); + $expected = ' 1 12 123 ' . PHP_EOL . ' 12 12 123456 ' . PHP_EOL . ' 34 34 ' . PHP_EOL . ' 5 ' . PHP_EOL . ' 12 12 123456789 ' . PHP_EOL . ' 34 34 ' . PHP_EOL . ' 56 56 ' . PHP_EOL . ' 7 78 ' . PHP_EOL; + $this->assertEquals($expected, $output); + + $output = drush_format_table($this->numbers, FALSE, array(4, 4, 4)); + $expected = ' 1 12 123 ' . PHP_EOL . ' 1234 1234 1234 ' . PHP_EOL . ' 5 56 ' . PHP_EOL . ' 1234 1234 1234 ' . PHP_EOL . ' 567 5678 5678 ' . PHP_EOL . ' 9 ' . PHP_EOL; + $this->assertEquals($expected, $output); + } + + /** + * Tests drush_format_table() with a header. + * + * @see drush_format_table(). + */ + public function testFormatTableHeader() { + drush_set_context('DRUSH_COLUMNS', 16); + $rows = $this->numbers; + array_unshift($rows, array('A', 'B', 'C')); + $output = drush_format_table($rows, TRUE); + $expected = ' A B C ' . PHP_EOL . ' 1 12 123 ' . PHP_EOL . ' 123 123 1234 ' . PHP_EOL . ' 4 45 56 ' . PHP_EOL . ' 123 123 1234 ' . PHP_EOL . ' 456 456 5678 ' . PHP_EOL . ' 7 78 9 ' . PHP_EOL; + $this->assertEquals($expected, $output); + } + + /** + * Tests drush_format_table() with word wrapping. + * + * @see drush_format_table(). + */ + public function testFormatTableWordWrap() { + drush_set_context('DRUSH_COLUMNS', 60); + $output = drush_format_table($this->words); + $expected = ' Drush is a command scripting for Drupal ' . PHP_EOL . ' line shell interface ' . PHP_EOL . ' A veritable Swiss Army knife designed to make ' . PHP_EOL . ' life easier for us ' . PHP_EOL; + $this->assertEquals($expected, $output); + } +} diff --git a/vendor/drush/drush/tests/unit.drush.inc b/vendor/drush/drush/tests/unit.drush.inc new file mode 100644 index 0000000000..817f89fe59 --- /dev/null +++ b/vendor/drush/drush/tests/unit.drush.inc @@ -0,0 +1,151 @@ + 'No-op command, used to test completion for commands that start the same as other commands.', + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + ); + $items['unit-eval'] = array( + 'description' => 'Works like php-eval. Used for testing $command_specific context.', + 'bootstrap' => DRUSH_BOOTSTRAP_MAX, + 'callback' => 'drush_core_php_eval', + ); + $items['unit-invoke'] = array( + 'description' => 'Return an array indicating which invoke hooks got called.', + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'callback' => 'drush_unit_invoke_primary', + ); + $items['unit-batch'] = array( + 'description' => 'Run a batch process.', + 'bootstrap' => DRUSH_BOOTSTRAP_MAX, + ); + $items['unit-return-options'] = array( + 'description' => 'Return options as function result.', + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + ); + $items['unit-return-argv'] = array( + 'description' => 'Return original argv as function result.', + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + ); + $items['missing-callback'] = array( + 'description' => 'Command with no callback function, to test error reporting.', + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + ); + $items['unit-drush-dependency'] = array( + 'description' => 'Command depending on an unknown commandfile.', + 'bootstrap' => DRUSH_BOOTSTRAP_NONE, + 'drush dependencies' => array('unknown-commandfile'), + ); + return $items; +} + +// Implement each invoke hook with the same single line of code. +// That line records that the hook was called. +function drush_unit_invoke_init() {unit_invoke_log(__FUNCTION__);} +function drush_unit_invoke_validate() {unit_invoke_log(__FUNCTION__);} +function drush_unit_pre_unit_invoke() {unit_invoke_log(__FUNCTION__);} +// Primary callback is not invoked when command specifies a 'callback'. +// function drush_unit_invoke() {unit_invoke_log(__FUNCTION__);} +function drush_unit_invoke_primary() {unit_invoke_log(__FUNCTION__);} +function drush_unit_pre_unit_invoke_rollback() {unit_invoke_log(__FUNCTION__);} +function drush_unit_post_unit_invoke_rollback() {unit_invoke_log(__FUNCTION__);} + +// Record that hook_drush_init() fired. +function unit_drush_init() { + $command = drush_get_command(); + if ($command['command'] == 'unit-invoke') { + unit_invoke_log(__FUNCTION__); + } +} + +function drush_unit_post_unit_invoke() { + // Record that this hook was called. + unit_invoke_log(__FUNCTION__); + + // Make sure we enter into rollback. + drush_set_error(''); +} + +/** + * The final invoke hook. Emit the call history. + * Cannot use 'exit' as it does not fire in rollback scenario. + */ +function drush_unit_invoke_validate_rollback() { + unit_invoke_log(__FUNCTION__); + print json_encode(unit_invoke_log()); +} + +function unit_invoke_log($function = NULL) { + static $called = array(); + if ($function) { + $called[] = $function; + } + else { + return $called; + } +} + +/** + * Command callback. + */ +function drush_unit_batch() { + // Reduce php memory/time limits to test backend respawn. + // TODO. + + $batch = array( + 'operations' => array( + array('_drush_unit_batch_operation', array()), + ), + 'finished' => '_drush_unit_batch_finished', + // 'file' => Doesn't work for us. Drupal 7 enforces this path + // to be relative to DRUPAL_ROOT. + // @see _batch_process(). + ); + batch_set($batch); + drush_backend_batch_process(); + + // Print the batch output. + drush_backend_output(); +} + +function _drush_unit_batch_operation(&$context) { + $context['message'] = "!!! ArrayObject does its job."; + + for ($i = 0; $i < 5; $i++) { + drush_print("Iteration $i"); + } + $context['finished'] = 1; +} + +function _drush_unit_batch_finished() { + // Restore php limits. + // TODO. +} + +// Return all of the option values passed in to this routine, minus the +// global options. +function drush_unit_return_options() { + $all_option_values = array_merge(drush_get_context('cli'), drush_get_context('stdin')); + foreach (drush_get_global_options() as $key => $info) { + unset($all_option_values[$key]); + } + if (isset($all_option_values['log-message'])) { + drush_log($all_option_values['log-message'], 'warning'); + drush_log("done", 'warning'); + unset($all_option_values['log-message']); + } + return $all_option_values; +} + +// Return all of the original arguments passed to this script +function drush_unit_return_argv() { + return drush_get_context('argv'); +} diff --git a/vendor/drush/drush/tests/userTest.php b/vendor/drush/drush/tests/userTest.php new file mode 100644 index 0000000000..e52719ab27 --- /dev/null +++ b/vendor/drush/drush/tests/userTest.php @@ -0,0 +1,183 @@ +getSites()) { + $this->setUpDrupal(1, TRUE); + self::$authenticated = 'authenticated'; + self::$status_prop = 'status'; + if (UNISH_DRUPAL_MAJOR_VERSION < 8) { + self::$authenticated .= ' user'; + } + else { + self::$status_prop = 'user_status'; + } + + $this->userCreate(); + } + } + + function testBlockUnblock() { + $this->drush('user-block', array(self::NAME), $this->options()); + $this->drush('user-information', array(self::NAME), $this->options() + array('format' => 'json')); + $uid = UNISH_DRUPAL_MAJOR_VERSION == 6 ? 3 : 2; + $output = $this->getOutputFromJSON($uid); + $this->assertEquals(0, $output->{self::$status_prop}, 'User is blocked.'); + + // user-unblock + $this->drush('user-unblock', array(self::NAME), $this->options()); + $this->drush('user-information', array(self::NAME), $this->options() + array('format' => 'json')); + $output = $this->getOutputFromJSON($uid); + $this->assertEquals(1, $output->{self::$status_prop}, 'User is unblocked.'); + } + + function testUserRole() { + // First, create the role since we use testing install profile. + $this->drush('role-create', array('test role'), $this->options()); + $this->drush('user-add-role', array('test role', self::NAME), $this->options()); + $this->drush('user-information', array(self::NAME), $this->options() + array('format' => 'json')); + $uid = UNISH_DRUPAL_MAJOR_VERSION == 6 ? 3 : 2; + $output = $this->getOutputFromJSON($uid); + $expected = array(self::$authenticated, 'test role'); + $this->assertEquals($expected, array_values((array)$output->roles), 'User has test role.'); + + // user-remove-role + $this->drush('user-remove-role', array('test role', self::NAME), $this->options()); + $this->drush('user-information', array(self::NAME), $this->options() + array('format' => 'json')); + $output = $this->getOutputFromJSON($uid); + $expected = array(self::$authenticated); + $this->assertEquals($expected, array_values((array)$output->roles), 'User removed test role.'); + } + + function testUserPassword() { + $newpass = 'newpass'; + $name = self::NAME; + $this->drush('user-password', array(self::NAME), $this->options() + array('password' => $newpass)); + // user_authenticate() is more complex in D6 so skip it. + switch (UNISH_DRUPAL_MAJOR_VERSION) { + case 6: + $this->markTestSkipped('Drupal 6 authentication too complex for testing.'); + break; + case 7: + $eval = "return user_authenticate('$name', '$newpass')"; + break; + case 8: + case 9: + $eval = "return \\Drupal::service('user.auth')->authenticate('$name', '$newpass');"; + break; + } + $this->drush('php-eval', array($eval), $this->options()); + $output = $this->getOutput(); + $this->assertEquals("'2'", $output, 'User can login with new password.'); + } + + function testUserLogin() { + // Check if user-login on non-bootstrapped environment returns error. + $this->drush('user-login', array(), array(), NULL, NULL, self::EXIT_ERROR); + + // Check user-login + $user_login_options = $this->options() + array('simulate' => TRUE, 'browser' => 'unish'); + // Collect full logs so we can check browser. + $this->drush('user-login', array(), $user_login_options + array('backend' => NULL)); + $parsed = $this->parse_backend_output($this->getOutput()); + $url = parse_url($parsed['output']); + $this->assertStringContainsString('/user/reset/1', $url['path'], 'Login returned a reset URL for uid 1 by default'); + $browser = FALSE; + foreach ($parsed['log'] as $key => $log) { + // Regarding 'strip_tags', see https://github.com/drush-ops/drush/issues/1637 + if (strpos(strip_tags($log['message']), 'Opening browser unish at http://') === 0) { + $browser = TRUE; + } + } + $this->assertEquals($browser, TRUE, 'Correct browser opened at correct URL'); + // Check specific user and path argument. + $uid = UNISH_DRUPAL_MAJOR_VERSION == 6 ? 3 : 2; + $this->drush('user-login', array(self::NAME, 'node/add'), $user_login_options); + $output = $this->getOutput(); + $url = parse_url($output); + // user_pass_reset_url encodes the URL in D6, but not in D7 or D8 + $query = $url['query']; + if (UNISH_DRUPAL_MAJOR_VERSION < 7) { + $query = urldecode($query); + } + $this->assertStringContainsString('/user/reset/' . $uid, $url['path'], 'Login with user argument returned a valid reset URL'); + $this->assertEquals('destination=node/add', $query, 'Login included destination path in URL'); + // Check path used as only argument when using uid option. + $this->drush('user-login', array('node/add'), $user_login_options + array('uid' => $uid)); + $output = $this->getOutput(); + $url = parse_url($output); + $this->assertStringContainsString('/user/reset/' . $uid, $url['path'], 'Login with uid option returned a valid reset URL'); + $query = $url['query']; + if (UNISH_DRUPAL_MAJOR_VERSION < 7) { + $query = urldecode($query); + } + $this->assertEquals('destination=node/add', $query, 'Login included destination path in URL'); + } + + function testUserCancel() { + // create content + // @todo Creation of node types and content has changed in D8. + if (UNISH_DRUPAL_MAJOR_VERSION >= 8) { + $this->markTestSkipped("@todo Creation of node types and content has changed in D8. Started to fix this"); + } + if (UNISH_DRUPAL_MAJOR_VERSION >= 7) { + // create_node_types script does not work for D6 + $this->drush('php-script', array('create_node_types'), $this->options() + array('script-path' => dirname(__FILE__) . '/resources')); + $name = self::NAME; + $newpass = 'newpass'; + $eval = "return user_authenticate('$name', '$newpass')"; + $this->drush('php-eval', array($eval), $this->options()); + $eval = "\$node = (object) array('title' => 'foo', 'uid' => 2, 'type' => 'page',);"; + if (UNISH_DRUPAL_MAJOR_VERSION >= 8) { + $eval .= " \$node = node_submit(entity_create('node', \$node));"; + } + if (UNISH_DRUPAL_MAJOR_VERSION >= 9) { + $eval .= " \\Drupal::entityTypeManager()->getStorage('node')->create(\$node)->save();"; + } + else { + $eval .= " node_save(\$node);"; + } + $this->drush('php-eval', array($eval), $this->options()); + $this->drush('user-cancel', array(self::NAME), $this->options() + array('delete-content' => NULL)); + $eval = 'print (string) user_load(2)'; + $this->drush('php-eval', array($eval), $this->options()); + $output = $this->getOutput(); + $this->assertEmpty($output, 'User was deleted'); + $eval = 'print (string) node_load(2)'; + $this->drush('php-eval', array($eval), $this->options()); + $output = $this->getOutput(); + $this->assertEmpty($output, 'Content was deleted'); + } + } + + function UserCreate() { + $this->drush('user-create', array(self::NAME), $this->options() + array('password' => 'password', 'mail' => "example@example.com")); + $this->drush('user-information', array(self::NAME), $this->options() + array('format' => 'json')); + $uid = UNISH_DRUPAL_MAJOR_VERSION == 6 ? 3 : 2; + $output = $this->getOutputFromJSON($uid); + $this->assertEquals('example@example.com', $output->mail); + $this->assertEquals(self::NAME, $output->name); + $this->assertEquals(1, $output->{self::$status_prop}, 'Newly created user is Active.'); + $expected = array(self::$authenticated); + $this->assertEquals($expected, array_values((array)$output->roles), 'Newly created user has one role.'); + } + + function options() { + return array( + 'root' => $this->webroot(), + 'uri' => key($this->getSites()), + 'yes' => NULL, + ); + } +} diff --git a/vendor/drush/drush/tests/variableTest.php b/vendor/drush/drush/tests/variableTest.php new file mode 100644 index 0000000000..74eb64eb7c --- /dev/null +++ b/vendor/drush/drush/tests/variableTest.php @@ -0,0 +1,58 @@ += 8) { + $this->markTestSkipped("Variable system was removed in Drupal 8."); + } + + $sites = $this->setUpDrupal(1, TRUE); + $options_without_pipe = array( + 'yes' => NULL, + 'root' => $this->webroot(), + 'uri' => key($sites), + ); + $options = $options_without_pipe + array( + 'pipe' => NULL, + ); + + $this->drush('variable-set', array('test_integer', '3.14159'), $options); + $this->drush('variable-get', array('test_integer'), $options); + $var_export = $this->getOutput(); + eval($var_export); + $this->assertEquals("3.14159", $variables['test_integer'], 'Integer variable was successfully set and get.'); + + $this->drush('variable-set', array('date_default_timezone', 'US/Mountain'), $options); + $this->drush('variable-get', array('date_default_timezone'), $options); // Wildcard get. + $var_export = $this->getOutput(); + eval($var_export); + $this->assertEquals('US/Mountain', $variables['date_default_timezone'], 'Variable was successfully set and get.'); + + $this->drush('variable-set', array('site_name', 'control'), $options + array('exact' => NULL)); + $this->drush('variable-set', array('site_na', 'unish'), $options + array('always-set' => NULL)); + $this->drush('variable-get', array('site_na'), $options + array('exact' => NULL)); + $var_export = $this->getOutput(); + eval($var_export); + $this->assertEquals('unish', $variables['site_na'], '--always-set option works as expected.'); + + $this->drush('variable-set', array('site_n', 'exactish'), $options + array('exact' => NULL)); + $this->drush('variable-get', array('site_n'), $options + array('exact' => NULL)); + $var_export = $this->getOutput(); + eval($var_export); + $this->assertEquals('exactish', $variables['site_n'], '--exact option works as expected.'); + + $this->drush('variable-get', array('site_n'), $options_without_pipe + array('exact' => NULL)); + $site_n_value = $this->getOutput(); + $this->assertEquals('exactish', $site_n_value, '--exact option works as expected with --pipe.'); + + $this->drush('variable-delete', array('site_name'), $options); + $output = $this->getOutput(); + $this->assertEmpty($output, 'Variable was successfully deleted.'); + } +} diff --git a/vendor/drush/drush/tests/watchdogTest.php b/vendor/drush/drush/tests/watchdogTest.php new file mode 100644 index 0000000000..be8de027ab --- /dev/null +++ b/vendor/drush/drush/tests/watchdogTest.php @@ -0,0 +1,60 @@ +setUpDrupal(1, TRUE); + $options = array( + 'yes' => NULL, + 'root' => $this->webroot(), + 'uri' => key($sites), + ); + + if (UNISH_DRUPAL_MAJOR_VERSION >= 7) { + $this->drush('pm-enable', array('dblog'), $options); + } + if (UNISH_DRUPAL_MAJOR_VERSION >= 8) { + $eval1 = "\\Drupal::logger('drush')->notice('Unish rocks.');"; + } + else { + $eval1 = "watchdog('drush', 'Unish rocks.');"; + } + $this->drush('php-eval', array($eval1), $options); + $this->drush('watchdog-show', array(), $options + array('count' => 50)); + $output = $this->getOutput(); + $this->assertStringContainsString('Unish rocks.', $output); + + // Add a new entry with a long message with the letter 'd' and verify that watchdog-show does + // not print it completely in the listing unless --full is given. + // As the output is formatted so lines may be splitted, assertStringContainsString does not work + // in this scenario. Therefore, we will count the number of times a character is present. + $message_chars = 300; + $char = '*'; + $message = str_repeat($char, $message_chars); + if (UNISH_DRUPAL_MAJOR_VERSION >= 8) { + $eval2 = "\\Drupal::logger('drush')->notice('$message');"; + } + else { + $eval2 = "watchdog('drush', '$message');"; + } + $this->drush('php-eval', array($eval2), $options); + $this->drush('watchdog-show', array(), $options); + $output = $this->getOutput(); + $this->assertGreaterThan(substr_count($output, $char), $message_chars); + $this->drush('watchdog-show', array(), $options + array('extended' => NULL)); + $output = $this->getOutput(); + $this->assertGreaterThanOrEqual($message_chars, substr_count($output, $char)); + + // Tests message deletion + $this->drush('watchdog-delete', array('all'), $options); + $output = $this->getOutput(); + $this->drush('watchdog-show', array(), $options); + $output = $this->getOutput(); + $this->assertEmpty($output); + } +} diff --git a/vendor/drush/drush/tests/xhUnitTest.php b/vendor/drush/drush/tests/xhUnitTest.php new file mode 100644 index 0000000000..1a95078271 --- /dev/null +++ b/vendor/drush/drush/tests/xhUnitTest.php @@ -0,0 +1,108 @@ + $option_value) { + drush_set_option($option_name, $option_value); + } + $this->assertEquals($expected, xh_flags(), $name); + } + + /** + * Provides drush XHProf options and the results we expect from xh_flags(). + */ + public function xhOptionProvider() { + + if (!defined('XHPROF_FLAGS_NO_BUILTINS')) { + define('XHPROF_FLAGS_NO_BUILTINS', 1); + define('XHPROF_FLAGS_CPU', 2); + define('XHPROF_FLAGS_MEMORY', 3); + } + + return array( + array( + 'name' => 'No flag options provided (default)', + 'options' => array(), + 'expected' => 0, + ), + array( + 'name' => 'Default flag options explicitly provided', + 'options' => array( + 'xh-profile-builtins' => TRUE, + 'xh-profile-cpu' => FALSE, + 'xh-profile-memory' => FALSE, + ), + 'expected' => 0, + ), + array( + 'name' => 'Disable profiling of built-ins', + 'options' => array( + 'xh-profile-builtins' => FALSE, + 'xh-profile-cpu' => FALSE, + 'xh-profile-memory' => FALSE, + ), + 'expected' => XHPROF_FLAGS_NO_BUILTINS, + ), + array( + 'name' => 'Enable profiling of CPU', + 'options' => array( + 'xh-profile-builtins' => TRUE, + 'xh-profile-cpu' => TRUE, + 'xh-profile-memory' => FALSE, + ), + 'expected' => XHPROF_FLAGS_CPU, + ), + array( + 'name' => 'Enable profiling of CPU, without builtins', + 'options' => array( + 'xh-profile-builtins' => FALSE, + 'xh-profile-cpu' => TRUE, + 'xh-profile-memory' => FALSE, + ), + 'expected' => XHPROF_FLAGS_NO_BUILTINS | XHPROF_FLAGS_CPU, + ), + array( + 'name' => 'Enable profiling of Memory', + 'options' => array( + 'xh-profile-builtins' => TRUE, + 'xh-profile-cpu' => FALSE, + 'xh-profile-memory' => TRUE, + ), + 'expected' => XHPROF_FLAGS_MEMORY, + ), + array( + 'name' => 'Enable profiling of Memory, without builtins', + 'options' => array( + 'xh-profile-builtins' => FALSE, + 'xh-profile-cpu' => FALSE, + 'xh-profile-memory' => TRUE, + ), + 'expected' => XHPROF_FLAGS_NO_BUILTINS | XHPROF_FLAGS_MEMORY, + ), + array( + 'name' => 'Enable profiling of CPU & Memory', + 'options' => array( + 'xh-profile-builtins' => TRUE, + 'xh-profile-cpu' => TRUE, + 'xh-profile-memory' => TRUE, + ), + 'expected' => XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY, + ), + ); + } + +} diff --git a/vendor/drush/drush/unish.bat b/vendor/drush/drush/unish.bat new file mode 100644 index 0000000000..81bfe33156 --- /dev/null +++ b/vendor/drush/drush/unish.bat @@ -0,0 +1,5 @@ +@ECHO OFF +REM Run Unish, the test suite for Drush. +setlocal DISABLEDELAYEDEXPANSION +SET BIN_TARGET=%~dp0vendor/phpunit/phpunit/phpunit +php "%BIN_TARGET%" --configuration tests %* \ No newline at end of file diff --git a/vendor/drush/drush/unish.sh b/vendor/drush/drush/unish.sh new file mode 100755 index 0000000000..17e6295a72 --- /dev/null +++ b/vendor/drush/drush/unish.sh @@ -0,0 +1,2 @@ +# Run Unish, the test suite for Drush. +vendor/bin/phpunit --configuration tests $@ diff --git a/vendor/nikic/php-parser/LICENSE b/vendor/nikic/php-parser/LICENSE new file mode 100644 index 0000000000..2e56718358 --- /dev/null +++ b/vendor/nikic/php-parser/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2011, Nikita Popov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/nikic/php-parser/README.md b/vendor/nikic/php-parser/README.md new file mode 100644 index 0000000000..708cdfcbd7 --- /dev/null +++ b/vendor/nikic/php-parser/README.md @@ -0,0 +1,225 @@ +PHP Parser +========== + +[![Coverage Status](https://coveralls.io/repos/github/nikic/PHP-Parser/badge.svg?branch=master)](https://coveralls.io/github/nikic/PHP-Parser?branch=master) + +This is a PHP 5.2 to PHP 8.1 parser written in PHP. Its purpose is to simplify static code analysis and +manipulation. + +[**Documentation for version 4.x**][doc_master] (stable; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 8.1). + +[Documentation for version 3.x][doc_3_x] (unsupported; for running on PHP >= 5.5; for parsing PHP 5.2 to PHP 7.2). + +Features +-------- + +The main features provided by this library are: + + * Parsing PHP 5, PHP 7, and PHP 8 code into an abstract syntax tree (AST). + * Invalid code can be parsed into a partial AST. + * The AST contains accurate location information. + * Dumping the AST in human-readable form. + * Converting an AST back to PHP code. + * Experimental: Formatting can be preserved for partially changed ASTs. + * Infrastructure to traverse and modify ASTs. + * Resolution of namespaced names. + * Evaluation of constant expressions. + * Builders to simplify AST construction for code generation. + * Converting an AST into JSON and back. + +Quick Start +----------- + +Install the library using [composer](https://getcomposer.org): + + php composer.phar require nikic/php-parser + +Parse some PHP code into an AST and dump the result in human-readable form: + +```php +create(ParserFactory::PREFER_PHP7); +try { + $ast = $parser->parse($code); +} catch (Error $error) { + echo "Parse error: {$error->getMessage()}\n"; + return; +} + +$dumper = new NodeDumper; +echo $dumper->dump($ast) . "\n"; +``` + +This dumps an AST looking something like this: + +``` +array( + 0: Stmt_Function( + byRef: false + name: Identifier( + name: test + ) + params: array( + 0: Param( + type: null + byRef: false + variadic: false + var: Expr_Variable( + name: foo + ) + default: null + ) + ) + returnType: null + stmts: array( + 0: Stmt_Expression( + expr: Expr_FuncCall( + name: Name( + parts: array( + 0: var_dump + ) + ) + args: array( + 0: Arg( + value: Expr_Variable( + name: foo + ) + byRef: false + unpack: false + ) + ) + ) + ) + ) + ) +) +``` + +Let's traverse the AST and perform some kind of modification. For example, drop all function bodies: + +```php +use PhpParser\Node; +use PhpParser\Node\Stmt\Function_; +use PhpParser\NodeTraverser; +use PhpParser\NodeVisitorAbstract; + +$traverser = new NodeTraverser(); +$traverser->addVisitor(new class extends NodeVisitorAbstract { + public function enterNode(Node $node) { + if ($node instanceof Function_) { + // Clean out the function body + $node->stmts = []; + } + } +}); + +$ast = $traverser->traverse($ast); +echo $dumper->dump($ast) . "\n"; +``` + +This gives us an AST where the `Function_::$stmts` are empty: + +``` +array( + 0: Stmt_Function( + byRef: false + name: Identifier( + name: test + ) + params: array( + 0: Param( + type: null + byRef: false + variadic: false + var: Expr_Variable( + name: foo + ) + default: null + ) + ) + returnType: null + stmts: array( + ) + ) +) +``` + +Finally, we can convert the new AST back to PHP code: + +```php +use PhpParser\PrettyPrinter; + +$prettyPrinter = new PrettyPrinter\Standard; +echo $prettyPrinter->prettyPrintFile($ast); +``` + +This gives us our original code, minus the `var_dump()` call inside the function: + +```php + [ + 'startLine', 'endLine', 'startFilePos', 'endFilePos', 'comments' +]]); +$parser = (new PhpParser\ParserFactory)->create( + PhpParser\ParserFactory::PREFER_PHP7, + $lexer +); +$dumper = new PhpParser\NodeDumper([ + 'dumpComments' => true, + 'dumpPositions' => $attributes['with-positions'], +]); +$prettyPrinter = new PhpParser\PrettyPrinter\Standard; + +$traverser = new PhpParser\NodeTraverser(); +$traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver); + +foreach ($files as $file) { + if (strpos($file, ' Code $code\n"); + } else { + if (!file_exists($file)) { + fwrite(STDERR, "File $file does not exist.\n"); + exit(1); + } + + $code = file_get_contents($file); + fwrite(STDERR, "====> File $file:\n"); + } + + if ($attributes['with-recovery']) { + $errorHandler = new PhpParser\ErrorHandler\Collecting; + $stmts = $parser->parse($code, $errorHandler); + foreach ($errorHandler->getErrors() as $error) { + $message = formatErrorMessage($error, $code, $attributes['with-column-info']); + fwrite(STDERR, $message . "\n"); + } + if (null === $stmts) { + continue; + } + } else { + try { + $stmts = $parser->parse($code); + } catch (PhpParser\Error $error) { + $message = formatErrorMessage($error, $code, $attributes['with-column-info']); + fwrite(STDERR, $message . "\n"); + exit(1); + } + } + + foreach ($operations as $operation) { + if ('dump' === $operation) { + fwrite(STDERR, "==> Node dump:\n"); + echo $dumper->dump($stmts, $code), "\n"; + } elseif ('pretty-print' === $operation) { + fwrite(STDERR, "==> Pretty print:\n"); + echo $prettyPrinter->prettyPrintFile($stmts), "\n"; + } elseif ('json-dump' === $operation) { + fwrite(STDERR, "==> JSON dump:\n"); + echo json_encode($stmts, JSON_PRETTY_PRINT), "\n"; + } elseif ('var-dump' === $operation) { + fwrite(STDERR, "==> var_dump():\n"); + var_dump($stmts); + } elseif ('resolve-names' === $operation) { + fwrite(STDERR, "==> Resolved names.\n"); + $stmts = $traverser->traverse($stmts); + } + } +} + +function formatErrorMessage(PhpParser\Error $e, $code, $withColumnInfo) { + if ($withColumnInfo && $e->hasColumnInfo()) { + return $e->getMessageWithColumnInfo($code); + } else { + return $e->getMessage(); + } +} + +function showHelp($error = '') { + if ($error) { + fwrite(STDERR, $error . "\n\n"); + } + fwrite($error ? STDERR : STDOUT, << false, + 'with-positions' => false, + 'with-recovery' => false, + ]; + + array_shift($args); + $parseOptions = true; + foreach ($args as $arg) { + if (!$parseOptions) { + $files[] = $arg; + continue; + } + + switch ($arg) { + case '--dump': + case '-d': + $operations[] = 'dump'; + break; + case '--pretty-print': + case '-p': + $operations[] = 'pretty-print'; + break; + case '--json-dump': + case '-j': + $operations[] = 'json-dump'; + break; + case '--var-dump': + $operations[] = 'var-dump'; + break; + case '--resolve-names': + case '-N'; + $operations[] = 'resolve-names'; + break; + case '--with-column-info': + case '-c'; + $attributes['with-column-info'] = true; + break; + case '--with-positions': + case '-P': + $attributes['with-positions'] = true; + break; + case '--with-recovery': + case '-r': + $attributes['with-recovery'] = true; + break; + case '--help': + case '-h'; + showHelp(); + break; + case '--': + $parseOptions = false; + break; + default: + if ($arg[0] === '-') { + showHelp("Invalid operation $arg."); + } else { + $files[] = $arg; + } + } + } + + return [$operations, $files, $attributes]; +} diff --git a/vendor/nikic/php-parser/composer.json b/vendor/nikic/php-parser/composer.json new file mode 100644 index 0000000000..2fd064a212 --- /dev/null +++ b/vendor/nikic/php-parser/composer.json @@ -0,0 +1,41 @@ +{ + "name": "nikic/php-parser", + "type": "library", + "description": "A PHP parser written in PHP", + "keywords": [ + "php", + "parser" + ], + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Nikita Popov" + } + ], + "require": { + "php": ">=7.0", + "ext-tokenizer": "*" + }, + "require-dev": { + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0", + "ircmaxell/php-yacc": "^0.0.7" + }, + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "autoload-dev": { + "psr-4": { + "PhpParser\\": "test/PhpParser/" + } + }, + "bin": [ + "bin/php-parse" + ] +} diff --git a/vendor/nikic/php-parser/grammar/README.md b/vendor/nikic/php-parser/grammar/README.md new file mode 100644 index 0000000000..4bae11d826 --- /dev/null +++ b/vendor/nikic/php-parser/grammar/README.md @@ -0,0 +1,30 @@ +What do all those files mean? +============================= + + * `php5.y`: PHP 5 grammar written in a pseudo language + * `php7.y`: PHP 7 grammar written in a pseudo language + * `tokens.y`: Tokens definition shared between PHP 5 and PHP 7 grammars + * `parser.template`: A `kmyacc` parser prototype file for PHP + * `tokens.template`: A `kmyacc` prototype file for the `Tokens` class + * `rebuildParsers.php`: Preprocesses the grammar and builds the parser using `kmyacc` + +.phpy pseudo language +===================== + +The `.y` file is a normal grammar in `kmyacc` (`yacc`) style, with some transformations +applied to it: + + * Nodes are created using the syntax `Name[..., ...]`. This is transformed into + `new Name(..., ..., attributes())` + * Some function-like constructs are resolved (see `rebuildParsers.php` for a list) + +Building the parser +=================== + +Run `php grammar/rebuildParsers.php` to rebuild the parsers. Additional options: + + * The `KMYACC` environment variable can be used to specify an alternative `kmyacc` binary. + By default the `phpyacc` dev dependency will be used. To use the original `kmyacc`, you + need to compile [moriyoshi's fork](https://github.com/moriyoshi/kmyacc-forked). + * The `--debug` option enables emission of debug symbols and creates the `y.output` file. + * The `--keep-tmp-grammar` option preserves the preprocessed grammar file. diff --git a/vendor/nikic/php-parser/grammar/parser.template b/vendor/nikic/php-parser/grammar/parser.template new file mode 100644 index 0000000000..6166607c9e --- /dev/null +++ b/vendor/nikic/php-parser/grammar/parser.template @@ -0,0 +1,106 @@ +semValue +#semval($,%t) $this->semValue +#semval(%n) $stackPos-(%l-%n) +#semval(%n,%t) $stackPos-(%l-%n) + +namespace PhpParser\Parser; + +use PhpParser\Error; +use PhpParser\Node; +use PhpParser\Node\Expr; +use PhpParser\Node\Name; +use PhpParser\Node\Scalar; +use PhpParser\Node\Stmt; +#include; + +/* This is an automatically GENERATED file, which should not be manually edited. + * Instead edit one of the following: + * * the grammar files grammar/php5.y or grammar/php7.y + * * the skeleton file grammar/parser.template + * * the preprocessing script grammar/rebuildParsers.php + */ +class #(-p) extends \PhpParser\ParserAbstract +{ + protected $tokenToSymbolMapSize = #(YYMAXLEX); + protected $actionTableSize = #(YYLAST); + protected $gotoTableSize = #(YYGLAST); + + protected $invalidSymbol = #(YYBADCH); + protected $errorSymbol = #(YYINTERRTOK); + protected $defaultAction = #(YYDEFAULT); + protected $unexpectedTokenRule = #(YYUNEXPECTED); + + protected $YY2TBLSTATE = #(YY2TBLSTATE); + protected $numNonLeafStates = #(YYNLSTATES); + + protected $symbolToName = array( + #listvar terminals + ); + + protected $tokenToSymbol = array( + #listvar yytranslate + ); + + protected $action = array( + #listvar yyaction + ); + + protected $actionCheck = array( + #listvar yycheck + ); + + protected $actionBase = array( + #listvar yybase + ); + + protected $actionDefault = array( + #listvar yydefault + ); + + protected $goto = array( + #listvar yygoto + ); + + protected $gotoCheck = array( + #listvar yygcheck + ); + + protected $gotoBase = array( + #listvar yygbase + ); + + protected $gotoDefault = array( + #listvar yygdefault + ); + + protected $ruleToNonTerminal = array( + #listvar yylhs + ); + + protected $ruleToLength = array( + #listvar yylen + ); +#if -t + + protected $productions = array( + #production-strings; + ); +#endif + + protected function initReduceCallbacks() { + $this->reduceCallbacks = [ +#reduce + %n => function ($stackPos) { + %b + }, +#noact + %n => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, +#endreduce + ]; + } +} +#tailcode; diff --git a/vendor/nikic/php-parser/grammar/php5.y b/vendor/nikic/php-parser/grammar/php5.y new file mode 100644 index 0000000000..a62e9a310c --- /dev/null +++ b/vendor/nikic/php-parser/grammar/php5.y @@ -0,0 +1,1036 @@ +%pure_parser +%expect 6 + +%tokens + +%% + +start: + top_statement_list { $$ = $this->handleNamespaces($1); } +; + +top_statement_list_ex: + top_statement_list_ex top_statement { pushNormalizing($1, $2); } + | /* empty */ { init(); } +; + +top_statement_list: + top_statement_list_ex + { makeZeroLengthNop($nop, $this->lookaheadStartAttributes); + if ($nop !== null) { $1[] = $nop; } $$ = $1; } +; + +ampersand: + T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG + | T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG +; + +reserved_non_modifiers: + T_INCLUDE | T_INCLUDE_ONCE | T_EVAL | T_REQUIRE | T_REQUIRE_ONCE | T_LOGICAL_OR | T_LOGICAL_XOR | T_LOGICAL_AND + | T_INSTANCEOF | T_NEW | T_CLONE | T_EXIT | T_IF | T_ELSEIF | T_ELSE | T_ENDIF | T_ECHO | T_DO | T_WHILE + | T_ENDWHILE | T_FOR | T_ENDFOR | T_FOREACH | T_ENDFOREACH | T_DECLARE | T_ENDDECLARE | T_AS | T_TRY | T_CATCH + | T_FINALLY | T_THROW | T_USE | T_INSTEADOF | T_GLOBAL | T_VAR | T_UNSET | T_ISSET | T_EMPTY | T_CONTINUE | T_GOTO + | T_FUNCTION | T_CONST | T_RETURN | T_PRINT | T_YIELD | T_LIST | T_SWITCH | T_ENDSWITCH | T_CASE | T_DEFAULT + | T_BREAK | T_ARRAY | T_CALLABLE | T_EXTENDS | T_IMPLEMENTS | T_NAMESPACE | T_TRAIT | T_INTERFACE | T_CLASS + | T_CLASS_C | T_TRAIT_C | T_FUNC_C | T_METHOD_C | T_LINE | T_FILE | T_DIR | T_NS_C | T_HALT_COMPILER | T_FN + | T_MATCH +; + +semi_reserved: + reserved_non_modifiers + | T_STATIC | T_ABSTRACT | T_FINAL | T_PRIVATE | T_PROTECTED | T_PUBLIC +; + +identifier_ex: + T_STRING { $$ = Node\Identifier[$1]; } + | semi_reserved { $$ = Node\Identifier[$1]; } +; + +identifier: + T_STRING { $$ = Node\Identifier[$1]; } +; + +reserved_non_modifiers_identifier: + reserved_non_modifiers { $$ = Node\Identifier[$1]; } +; + +namespace_name: + T_STRING { $$ = Name[$1]; } + | T_NAME_QUALIFIED { $$ = Name[$1]; } +; + +legacy_namespace_name: + namespace_name { $$ = $1; } + | T_NAME_FULLY_QUALIFIED { $$ = Name[substr($1, 1)]; } +; + +plain_variable: + T_VARIABLE { $$ = Expr\Variable[parseVar($1)]; } +; + +top_statement: + statement { $$ = $1; } + | function_declaration_statement { $$ = $1; } + | class_declaration_statement { $$ = $1; } + | T_HALT_COMPILER + { $$ = Stmt\HaltCompiler[$this->lexer->handleHaltCompiler()]; } + | T_NAMESPACE namespace_name ';' + { $$ = Stmt\Namespace_[$2, null]; + $$->setAttribute('kind', Stmt\Namespace_::KIND_SEMICOLON); + $this->checkNamespace($$); } + | T_NAMESPACE namespace_name '{' top_statement_list '}' + { $$ = Stmt\Namespace_[$2, $4]; + $$->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); + $this->checkNamespace($$); } + | T_NAMESPACE '{' top_statement_list '}' + { $$ = Stmt\Namespace_[null, $3]; + $$->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); + $this->checkNamespace($$); } + | T_USE use_declarations ';' { $$ = Stmt\Use_[$2, Stmt\Use_::TYPE_NORMAL]; } + | T_USE use_type use_declarations ';' { $$ = Stmt\Use_[$3, $2]; } + | group_use_declaration ';' { $$ = $1; } + | T_CONST constant_declaration_list ';' { $$ = Stmt\Const_[$2]; } +; + +use_type: + T_FUNCTION { $$ = Stmt\Use_::TYPE_FUNCTION; } + | T_CONST { $$ = Stmt\Use_::TYPE_CONSTANT; } +; + +group_use_declaration: + T_USE use_type legacy_namespace_name T_NS_SEPARATOR '{' unprefixed_use_declarations '}' + { $$ = Stmt\GroupUse[$3, $6, $2]; } + | T_USE legacy_namespace_name T_NS_SEPARATOR '{' inline_use_declarations '}' + { $$ = Stmt\GroupUse[$2, $5, Stmt\Use_::TYPE_UNKNOWN]; } +; + +unprefixed_use_declarations: + unprefixed_use_declarations ',' unprefixed_use_declaration + { push($1, $3); } + | unprefixed_use_declaration { init($1); } +; + +use_declarations: + use_declarations ',' use_declaration { push($1, $3); } + | use_declaration { init($1); } +; + +inline_use_declarations: + inline_use_declarations ',' inline_use_declaration { push($1, $3); } + | inline_use_declaration { init($1); } +; + +unprefixed_use_declaration: + namespace_name + { $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); } + | namespace_name T_AS identifier + { $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); } +; + +use_declaration: + legacy_namespace_name + { $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); } + | legacy_namespace_name T_AS identifier + { $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); } +; + +inline_use_declaration: + unprefixed_use_declaration { $$ = $1; $$->type = Stmt\Use_::TYPE_NORMAL; } + | use_type unprefixed_use_declaration { $$ = $2; $$->type = $1; } +; + +constant_declaration_list: + constant_declaration_list ',' constant_declaration { push($1, $3); } + | constant_declaration { init($1); } +; + +constant_declaration: + identifier '=' static_scalar { $$ = Node\Const_[$1, $3]; } +; + +class_const_list: + class_const_list ',' class_const { push($1, $3); } + | class_const { init($1); } +; + +class_const: + identifier_ex '=' static_scalar { $$ = Node\Const_[$1, $3]; } +; + +inner_statement_list_ex: + inner_statement_list_ex inner_statement { pushNormalizing($1, $2); } + | /* empty */ { init(); } +; + +inner_statement_list: + inner_statement_list_ex + { makeZeroLengthNop($nop, $this->lookaheadStartAttributes); + if ($nop !== null) { $1[] = $nop; } $$ = $1; } +; + +inner_statement: + statement { $$ = $1; } + | function_declaration_statement { $$ = $1; } + | class_declaration_statement { $$ = $1; } + | T_HALT_COMPILER + { throw new Error('__HALT_COMPILER() can only be used from the outermost scope', attributes()); } +; + +non_empty_statement: + '{' inner_statement_list '}' + { + if ($2) { + $$ = $2; prependLeadingComments($$); + } else { + makeNop($$, $this->startAttributeStack[#1], $this->endAttributes); + if (null === $$) { $$ = array(); } + } + } + | T_IF parentheses_expr statement elseif_list else_single + { $$ = Stmt\If_[$2, ['stmts' => toArray($3), 'elseifs' => $4, 'else' => $5]]; } + | T_IF parentheses_expr ':' inner_statement_list new_elseif_list new_else_single T_ENDIF ';' + { $$ = Stmt\If_[$2, ['stmts' => $4, 'elseifs' => $5, 'else' => $6]]; } + | T_WHILE parentheses_expr while_statement { $$ = Stmt\While_[$2, $3]; } + | T_DO statement T_WHILE parentheses_expr ';' { $$ = Stmt\Do_ [$4, toArray($2)]; } + | T_FOR '(' for_expr ';' for_expr ';' for_expr ')' for_statement + { $$ = Stmt\For_[['init' => $3, 'cond' => $5, 'loop' => $7, 'stmts' => $9]]; } + | T_SWITCH parentheses_expr switch_case_list { $$ = Stmt\Switch_[$2, $3]; } + | T_BREAK ';' { $$ = Stmt\Break_[null]; } + | T_BREAK expr ';' { $$ = Stmt\Break_[$2]; } + | T_CONTINUE ';' { $$ = Stmt\Continue_[null]; } + | T_CONTINUE expr ';' { $$ = Stmt\Continue_[$2]; } + | T_RETURN ';' { $$ = Stmt\Return_[null]; } + | T_RETURN expr ';' { $$ = Stmt\Return_[$2]; } + | T_GLOBAL global_var_list ';' { $$ = Stmt\Global_[$2]; } + | T_STATIC static_var_list ';' { $$ = Stmt\Static_[$2]; } + | T_ECHO expr_list ';' { $$ = Stmt\Echo_[$2]; } + | T_INLINE_HTML { $$ = Stmt\InlineHTML[$1]; } + | yield_expr ';' { $$ = Stmt\Expression[$1]; } + | expr ';' { $$ = Stmt\Expression[$1]; } + | T_UNSET '(' variables_list ')' ';' { $$ = Stmt\Unset_[$3]; } + | T_FOREACH '(' expr T_AS foreach_variable ')' foreach_statement + { $$ = Stmt\Foreach_[$3, $5[0], ['keyVar' => null, 'byRef' => $5[1], 'stmts' => $7]]; } + | T_FOREACH '(' expr T_AS variable T_DOUBLE_ARROW foreach_variable ')' foreach_statement + { $$ = Stmt\Foreach_[$3, $7[0], ['keyVar' => $5, 'byRef' => $7[1], 'stmts' => $9]]; } + | T_DECLARE '(' declare_list ')' declare_statement { $$ = Stmt\Declare_[$3, $5]; } + | T_TRY '{' inner_statement_list '}' catches optional_finally + { $$ = Stmt\TryCatch[$3, $5, $6]; $this->checkTryCatch($$); } + | T_THROW expr ';' { $$ = Stmt\Throw_[$2]; } + | T_GOTO identifier ';' { $$ = Stmt\Goto_[$2]; } + | identifier ':' { $$ = Stmt\Label[$1]; } + | expr error { $$ = Stmt\Expression[$1]; } + | error { $$ = array(); /* means: no statement */ } +; + +statement: + non_empty_statement { $$ = $1; } + | ';' + { makeNop($$, $this->startAttributeStack[#1], $this->endAttributes); + if ($$ === null) $$ = array(); /* means: no statement */ } +; + +catches: + /* empty */ { init(); } + | catches catch { push($1, $2); } +; + +catch: + T_CATCH '(' name plain_variable ')' '{' inner_statement_list '}' + { $$ = Stmt\Catch_[array($3), $4, $7]; } +; + +optional_finally: + /* empty */ { $$ = null; } + | T_FINALLY '{' inner_statement_list '}' { $$ = Stmt\Finally_[$3]; } +; + +variables_list: + variable { init($1); } + | variables_list ',' variable { push($1, $3); } +; + +optional_ref: + /* empty */ { $$ = false; } + | ampersand { $$ = true; } +; + +optional_arg_ref: + /* empty */ { $$ = false; } + | T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG { $$ = true; } +; + +optional_ellipsis: + /* empty */ { $$ = false; } + | T_ELLIPSIS { $$ = true; } +; + +function_declaration_statement: + T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type '{' inner_statement_list '}' + { $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $9]]; } +; + +class_declaration_statement: + class_entry_type identifier extends_from implements_list '{' class_statement_list '}' + { $$ = Stmt\Class_[$2, ['type' => $1, 'extends' => $3, 'implements' => $4, 'stmts' => $6]]; + $this->checkClass($$, #2); } + | T_INTERFACE identifier interface_extends_list '{' class_statement_list '}' + { $$ = Stmt\Interface_[$2, ['extends' => $3, 'stmts' => $5]]; + $this->checkInterface($$, #2); } + | T_TRAIT identifier '{' class_statement_list '}' + { $$ = Stmt\Trait_[$2, ['stmts' => $4]]; } +; + +class_entry_type: + T_CLASS { $$ = 0; } + | T_ABSTRACT T_CLASS { $$ = Stmt\Class_::MODIFIER_ABSTRACT; } + | T_FINAL T_CLASS { $$ = Stmt\Class_::MODIFIER_FINAL; } +; + +extends_from: + /* empty */ { $$ = null; } + | T_EXTENDS class_name { $$ = $2; } +; + +interface_extends_list: + /* empty */ { $$ = array(); } + | T_EXTENDS class_name_list { $$ = $2; } +; + +implements_list: + /* empty */ { $$ = array(); } + | T_IMPLEMENTS class_name_list { $$ = $2; } +; + +class_name_list: + class_name { init($1); } + | class_name_list ',' class_name { push($1, $3); } +; + +for_statement: + statement { $$ = toArray($1); } + | ':' inner_statement_list T_ENDFOR ';' { $$ = $2; } +; + +foreach_statement: + statement { $$ = toArray($1); } + | ':' inner_statement_list T_ENDFOREACH ';' { $$ = $2; } +; + +declare_statement: + non_empty_statement { $$ = toArray($1); } + | ';' { $$ = null; } + | ':' inner_statement_list T_ENDDECLARE ';' { $$ = $2; } +; + +declare_list: + declare_list_element { init($1); } + | declare_list ',' declare_list_element { push($1, $3); } +; + +declare_list_element: + identifier '=' static_scalar { $$ = Stmt\DeclareDeclare[$1, $3]; } +; + +switch_case_list: + '{' case_list '}' { $$ = $2; } + | '{' ';' case_list '}' { $$ = $3; } + | ':' case_list T_ENDSWITCH ';' { $$ = $2; } + | ':' ';' case_list T_ENDSWITCH ';' { $$ = $3; } +; + +case_list: + /* empty */ { init(); } + | case_list case { push($1, $2); } +; + +case: + T_CASE expr case_separator inner_statement_list_ex { $$ = Stmt\Case_[$2, $4]; } + | T_DEFAULT case_separator inner_statement_list_ex { $$ = Stmt\Case_[null, $3]; } +; + +case_separator: + ':' + | ';' +; + +while_statement: + statement { $$ = toArray($1); } + | ':' inner_statement_list T_ENDWHILE ';' { $$ = $2; } +; + +elseif_list: + /* empty */ { init(); } + | elseif_list elseif { push($1, $2); } +; + +elseif: + T_ELSEIF parentheses_expr statement { $$ = Stmt\ElseIf_[$2, toArray($3)]; } +; + +new_elseif_list: + /* empty */ { init(); } + | new_elseif_list new_elseif { push($1, $2); } +; + +new_elseif: + T_ELSEIF parentheses_expr ':' inner_statement_list { $$ = Stmt\ElseIf_[$2, $4]; } +; + +else_single: + /* empty */ { $$ = null; } + | T_ELSE statement { $$ = Stmt\Else_[toArray($2)]; } +; + +new_else_single: + /* empty */ { $$ = null; } + | T_ELSE ':' inner_statement_list { $$ = Stmt\Else_[$3]; } +; + +foreach_variable: + variable { $$ = array($1, false); } + | ampersand variable { $$ = array($2, true); } + | list_expr { $$ = array($1, false); } +; + +parameter_list: + non_empty_parameter_list { $$ = $1; } + | /* empty */ { $$ = array(); } +; + +non_empty_parameter_list: + parameter { init($1); } + | non_empty_parameter_list ',' parameter { push($1, $3); } +; + +parameter: + optional_param_type optional_arg_ref optional_ellipsis plain_variable + { $$ = Node\Param[$4, null, $1, $2, $3]; $this->checkParam($$); } + | optional_param_type optional_arg_ref optional_ellipsis plain_variable '=' static_scalar + { $$ = Node\Param[$4, $6, $1, $2, $3]; $this->checkParam($$); } +; + +type: + name { $$ = $1; } + | T_ARRAY { $$ = Node\Identifier['array']; } + | T_CALLABLE { $$ = Node\Identifier['callable']; } +; + +optional_param_type: + /* empty */ { $$ = null; } + | type { $$ = $1; } +; + +optional_return_type: + /* empty */ { $$ = null; } + | ':' type { $$ = $2; } +; + +argument_list: + '(' ')' { $$ = array(); } + | '(' non_empty_argument_list ')' { $$ = $2; } + | '(' yield_expr ')' { $$ = array(Node\Arg[$2, false, false]); } +; + +non_empty_argument_list: + argument { init($1); } + | non_empty_argument_list ',' argument { push($1, $3); } +; + +argument: + expr { $$ = Node\Arg[$1, false, false]; } + | ampersand variable { $$ = Node\Arg[$2, true, false]; } + | T_ELLIPSIS expr { $$ = Node\Arg[$2, false, true]; } +; + +global_var_list: + global_var_list ',' global_var { push($1, $3); } + | global_var { init($1); } +; + +global_var: + plain_variable { $$ = $1; } + | '$' variable { $$ = Expr\Variable[$2]; } + | '$' '{' expr '}' { $$ = Expr\Variable[$3]; } +; + +static_var_list: + static_var_list ',' static_var { push($1, $3); } + | static_var { init($1); } +; + +static_var: + plain_variable { $$ = Stmt\StaticVar[$1, null]; } + | plain_variable '=' static_scalar { $$ = Stmt\StaticVar[$1, $3]; } +; + +class_statement_list_ex: + class_statement_list_ex class_statement { if ($2 !== null) { push($1, $2); } } + | /* empty */ { init(); } +; + +class_statement_list: + class_statement_list_ex + { makeZeroLengthNop($nop, $this->lookaheadStartAttributes); + if ($nop !== null) { $1[] = $nop; } $$ = $1; } +; + +class_statement: + variable_modifiers property_declaration_list ';' + { $$ = Stmt\Property[$1, $2]; $this->checkProperty($$, #1); } + | T_CONST class_const_list ';' { $$ = Stmt\ClassConst[$2, 0]; } + | method_modifiers T_FUNCTION optional_ref identifier_ex '(' parameter_list ')' optional_return_type method_body + { $$ = Stmt\ClassMethod[$4, ['type' => $1, 'byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9]]; + $this->checkClassMethod($$, #1); } + | T_USE class_name_list trait_adaptations { $$ = Stmt\TraitUse[$2, $3]; } +; + +trait_adaptations: + ';' { $$ = array(); } + | '{' trait_adaptation_list '}' { $$ = $2; } +; + +trait_adaptation_list: + /* empty */ { init(); } + | trait_adaptation_list trait_adaptation { push($1, $2); } +; + +trait_adaptation: + trait_method_reference_fully_qualified T_INSTEADOF class_name_list ';' + { $$ = Stmt\TraitUseAdaptation\Precedence[$1[0], $1[1], $3]; } + | trait_method_reference T_AS member_modifier identifier_ex ';' + { $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, $4]; } + | trait_method_reference T_AS member_modifier ';' + { $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, null]; } + | trait_method_reference T_AS identifier ';' + { $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; } + | trait_method_reference T_AS reserved_non_modifiers_identifier ';' + { $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; } +; + +trait_method_reference_fully_qualified: + name T_PAAMAYIM_NEKUDOTAYIM identifier_ex { $$ = array($1, $3); } +; +trait_method_reference: + trait_method_reference_fully_qualified { $$ = $1; } + | identifier_ex { $$ = array(null, $1); } +; + +method_body: + ';' /* abstract method */ { $$ = null; } + | '{' inner_statement_list '}' { $$ = $2; } +; + +variable_modifiers: + non_empty_member_modifiers { $$ = $1; } + | T_VAR { $$ = 0; } +; + +method_modifiers: + /* empty */ { $$ = 0; } + | non_empty_member_modifiers { $$ = $1; } +; + +non_empty_member_modifiers: + member_modifier { $$ = $1; } + | non_empty_member_modifiers member_modifier { $this->checkModifier($1, $2, #2); $$ = $1 | $2; } +; + +member_modifier: + T_PUBLIC { $$ = Stmt\Class_::MODIFIER_PUBLIC; } + | T_PROTECTED { $$ = Stmt\Class_::MODIFIER_PROTECTED; } + | T_PRIVATE { $$ = Stmt\Class_::MODIFIER_PRIVATE; } + | T_STATIC { $$ = Stmt\Class_::MODIFIER_STATIC; } + | T_ABSTRACT { $$ = Stmt\Class_::MODIFIER_ABSTRACT; } + | T_FINAL { $$ = Stmt\Class_::MODIFIER_FINAL; } +; + +property_declaration_list: + property_declaration { init($1); } + | property_declaration_list ',' property_declaration { push($1, $3); } +; + +property_decl_name: + T_VARIABLE { $$ = Node\VarLikeIdentifier[parseVar($1)]; } +; + +property_declaration: + property_decl_name { $$ = Stmt\PropertyProperty[$1, null]; } + | property_decl_name '=' static_scalar { $$ = Stmt\PropertyProperty[$1, $3]; } +; + +expr_list: + expr_list ',' expr { push($1, $3); } + | expr { init($1); } +; + +for_expr: + /* empty */ { $$ = array(); } + | expr_list { $$ = $1; } +; + +expr: + variable { $$ = $1; } + | list_expr '=' expr { $$ = Expr\Assign[$1, $3]; } + | variable '=' expr { $$ = Expr\Assign[$1, $3]; } + | variable '=' ampersand variable { $$ = Expr\AssignRef[$1, $4]; } + | variable '=' ampersand new_expr { $$ = Expr\AssignRef[$1, $4]; } + | new_expr { $$ = $1; } + | T_CLONE expr { $$ = Expr\Clone_[$2]; } + | variable T_PLUS_EQUAL expr { $$ = Expr\AssignOp\Plus [$1, $3]; } + | variable T_MINUS_EQUAL expr { $$ = Expr\AssignOp\Minus [$1, $3]; } + | variable T_MUL_EQUAL expr { $$ = Expr\AssignOp\Mul [$1, $3]; } + | variable T_DIV_EQUAL expr { $$ = Expr\AssignOp\Div [$1, $3]; } + | variable T_CONCAT_EQUAL expr { $$ = Expr\AssignOp\Concat [$1, $3]; } + | variable T_MOD_EQUAL expr { $$ = Expr\AssignOp\Mod [$1, $3]; } + | variable T_AND_EQUAL expr { $$ = Expr\AssignOp\BitwiseAnd[$1, $3]; } + | variable T_OR_EQUAL expr { $$ = Expr\AssignOp\BitwiseOr [$1, $3]; } + | variable T_XOR_EQUAL expr { $$ = Expr\AssignOp\BitwiseXor[$1, $3]; } + | variable T_SL_EQUAL expr { $$ = Expr\AssignOp\ShiftLeft [$1, $3]; } + | variable T_SR_EQUAL expr { $$ = Expr\AssignOp\ShiftRight[$1, $3]; } + | variable T_POW_EQUAL expr { $$ = Expr\AssignOp\Pow [$1, $3]; } + | variable T_COALESCE_EQUAL expr { $$ = Expr\AssignOp\Coalesce [$1, $3]; } + | variable T_INC { $$ = Expr\PostInc[$1]; } + | T_INC variable { $$ = Expr\PreInc [$2]; } + | variable T_DEC { $$ = Expr\PostDec[$1]; } + | T_DEC variable { $$ = Expr\PreDec [$2]; } + | expr T_BOOLEAN_OR expr { $$ = Expr\BinaryOp\BooleanOr [$1, $3]; } + | expr T_BOOLEAN_AND expr { $$ = Expr\BinaryOp\BooleanAnd[$1, $3]; } + | expr T_LOGICAL_OR expr { $$ = Expr\BinaryOp\LogicalOr [$1, $3]; } + | expr T_LOGICAL_AND expr { $$ = Expr\BinaryOp\LogicalAnd[$1, $3]; } + | expr T_LOGICAL_XOR expr { $$ = Expr\BinaryOp\LogicalXor[$1, $3]; } + | expr '|' expr { $$ = Expr\BinaryOp\BitwiseOr [$1, $3]; } + | expr T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG expr { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; } + | expr T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG expr { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; } + | expr '^' expr { $$ = Expr\BinaryOp\BitwiseXor[$1, $3]; } + | expr '.' expr { $$ = Expr\BinaryOp\Concat [$1, $3]; } + | expr '+' expr { $$ = Expr\BinaryOp\Plus [$1, $3]; } + | expr '-' expr { $$ = Expr\BinaryOp\Minus [$1, $3]; } + | expr '*' expr { $$ = Expr\BinaryOp\Mul [$1, $3]; } + | expr '/' expr { $$ = Expr\BinaryOp\Div [$1, $3]; } + | expr '%' expr { $$ = Expr\BinaryOp\Mod [$1, $3]; } + | expr T_SL expr { $$ = Expr\BinaryOp\ShiftLeft [$1, $3]; } + | expr T_SR expr { $$ = Expr\BinaryOp\ShiftRight[$1, $3]; } + | expr T_POW expr { $$ = Expr\BinaryOp\Pow [$1, $3]; } + | '+' expr %prec T_INC { $$ = Expr\UnaryPlus [$2]; } + | '-' expr %prec T_INC { $$ = Expr\UnaryMinus[$2]; } + | '!' expr { $$ = Expr\BooleanNot[$2]; } + | '~' expr { $$ = Expr\BitwiseNot[$2]; } + | expr T_IS_IDENTICAL expr { $$ = Expr\BinaryOp\Identical [$1, $3]; } + | expr T_IS_NOT_IDENTICAL expr { $$ = Expr\BinaryOp\NotIdentical [$1, $3]; } + | expr T_IS_EQUAL expr { $$ = Expr\BinaryOp\Equal [$1, $3]; } + | expr T_IS_NOT_EQUAL expr { $$ = Expr\BinaryOp\NotEqual [$1, $3]; } + | expr T_SPACESHIP expr { $$ = Expr\BinaryOp\Spaceship [$1, $3]; } + | expr '<' expr { $$ = Expr\BinaryOp\Smaller [$1, $3]; } + | expr T_IS_SMALLER_OR_EQUAL expr { $$ = Expr\BinaryOp\SmallerOrEqual[$1, $3]; } + | expr '>' expr { $$ = Expr\BinaryOp\Greater [$1, $3]; } + | expr T_IS_GREATER_OR_EQUAL expr { $$ = Expr\BinaryOp\GreaterOrEqual[$1, $3]; } + | expr T_INSTANCEOF class_name_reference { $$ = Expr\Instanceof_[$1, $3]; } + | parentheses_expr { $$ = $1; } + /* we need a separate '(' new_expr ')' rule to avoid problems caused by a s/r conflict */ + | '(' new_expr ')' { $$ = $2; } + | expr '?' expr ':' expr { $$ = Expr\Ternary[$1, $3, $5]; } + | expr '?' ':' expr { $$ = Expr\Ternary[$1, null, $4]; } + | expr T_COALESCE expr { $$ = Expr\BinaryOp\Coalesce[$1, $3]; } + | T_ISSET '(' variables_list ')' { $$ = Expr\Isset_[$3]; } + | T_EMPTY '(' expr ')' { $$ = Expr\Empty_[$3]; } + | T_INCLUDE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_INCLUDE]; } + | T_INCLUDE_ONCE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_INCLUDE_ONCE]; } + | T_EVAL parentheses_expr { $$ = Expr\Eval_[$2]; } + | T_REQUIRE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_REQUIRE]; } + | T_REQUIRE_ONCE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_REQUIRE_ONCE]; } + | T_INT_CAST expr { $$ = Expr\Cast\Int_ [$2]; } + | T_DOUBLE_CAST expr + { $attrs = attributes(); + $attrs['kind'] = $this->getFloatCastKind($1); + $$ = new Expr\Cast\Double($2, $attrs); } + | T_STRING_CAST expr { $$ = Expr\Cast\String_ [$2]; } + | T_ARRAY_CAST expr { $$ = Expr\Cast\Array_ [$2]; } + | T_OBJECT_CAST expr { $$ = Expr\Cast\Object_ [$2]; } + | T_BOOL_CAST expr { $$ = Expr\Cast\Bool_ [$2]; } + | T_UNSET_CAST expr { $$ = Expr\Cast\Unset_ [$2]; } + | T_EXIT exit_expr + { $attrs = attributes(); + $attrs['kind'] = strtolower($1) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE; + $$ = new Expr\Exit_($2, $attrs); } + | '@' expr { $$ = Expr\ErrorSuppress[$2]; } + | scalar { $$ = $1; } + | array_expr { $$ = $1; } + | scalar_dereference { $$ = $1; } + | '`' backticks_expr '`' { $$ = Expr\ShellExec[$2]; } + | T_PRINT expr { $$ = Expr\Print_[$2]; } + | T_YIELD { $$ = Expr\Yield_[null, null]; } + | T_YIELD_FROM expr { $$ = Expr\YieldFrom[$2]; } + | T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type + '{' inner_statement_list '}' + { $$ = Expr\Closure[['static' => false, 'byRef' => $2, 'params' => $4, 'uses' => $6, 'returnType' => $7, 'stmts' => $9]]; } + | T_STATIC T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type + '{' inner_statement_list '}' + { $$ = Expr\Closure[['static' => true, 'byRef' => $3, 'params' => $5, 'uses' => $7, 'returnType' => $8, 'stmts' => $10]]; } +; + +parentheses_expr: + '(' expr ')' { $$ = $2; } + | '(' yield_expr ')' { $$ = $2; } +; + +yield_expr: + T_YIELD expr { $$ = Expr\Yield_[$2, null]; } + | T_YIELD expr T_DOUBLE_ARROW expr { $$ = Expr\Yield_[$4, $2]; } +; + +array_expr: + T_ARRAY '(' array_pair_list ')' + { $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_LONG; + $$ = new Expr\Array_($3, $attrs); } + | '[' array_pair_list ']' + { $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_SHORT; + $$ = new Expr\Array_($2, $attrs); } +; + +scalar_dereference: + array_expr '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } + | T_CONSTANT_ENCAPSED_STRING '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[Scalar\String_::fromString($1, attributes()), $3]; } + | constant '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } + | scalar_dereference '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } + /* alternative array syntax missing intentionally */ +; + +anonymous_class: + T_CLASS ctor_arguments extends_from implements_list '{' class_statement_list '}' + { $$ = array(Stmt\Class_[null, ['type' => 0, 'extends' => $3, 'implements' => $4, 'stmts' => $6]], $2); + $this->checkClass($$[0], -1); } +; + +new_expr: + T_NEW class_name_reference ctor_arguments { $$ = Expr\New_[$2, $3]; } + | T_NEW anonymous_class + { list($class, $ctorArgs) = $2; $$ = Expr\New_[$class, $ctorArgs]; } +; + +lexical_vars: + /* empty */ { $$ = array(); } + | T_USE '(' lexical_var_list ')' { $$ = $3; } +; + +lexical_var_list: + lexical_var { init($1); } + | lexical_var_list ',' lexical_var { push($1, $3); } +; + +lexical_var: + optional_ref plain_variable { $$ = Expr\ClosureUse[$2, $1]; } +; + +function_call: + name argument_list { $$ = Expr\FuncCall[$1, $2]; } + | class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_ex argument_list + { $$ = Expr\StaticCall[$1, $3, $4]; } + | class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '{' expr '}' argument_list + { $$ = Expr\StaticCall[$1, $4, $6]; } + | static_property argument_list + { $$ = $this->fixupPhp5StaticPropCall($1, $2, attributes()); } + | variable_without_objects argument_list + { $$ = Expr\FuncCall[$1, $2]; } + | function_call '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } + /* alternative array syntax missing intentionally */ +; + +class_name: + T_STATIC { $$ = Name[$1]; } + | name { $$ = $1; } +; + +name: + T_STRING { $$ = Name[$1]; } + | T_NAME_QUALIFIED { $$ = Name[$1]; } + | T_NAME_FULLY_QUALIFIED { $$ = Name\FullyQualified[substr($1, 1)]; } + | T_NAME_RELATIVE { $$ = Name\Relative[substr($1, 10)]; } +; + +class_name_reference: + class_name { $$ = $1; } + | dynamic_class_name_reference { $$ = $1; } +; + +dynamic_class_name_reference: + object_access_for_dcnr { $$ = $1; } + | base_variable { $$ = $1; } +; + +class_name_or_var: + class_name { $$ = $1; } + | reference_variable { $$ = $1; } +; + +object_access_for_dcnr: + base_variable T_OBJECT_OPERATOR object_property + { $$ = Expr\PropertyFetch[$1, $3]; } + | object_access_for_dcnr T_OBJECT_OPERATOR object_property + { $$ = Expr\PropertyFetch[$1, $3]; } + | object_access_for_dcnr '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } + | object_access_for_dcnr '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; } +; + +exit_expr: + /* empty */ { $$ = null; } + | '(' ')' { $$ = null; } + | parentheses_expr { $$ = $1; } +; + +backticks_expr: + /* empty */ { $$ = array(); } + | T_ENCAPSED_AND_WHITESPACE + { $$ = array(Scalar\EncapsedStringPart[Scalar\String_::parseEscapeSequences($1, '`', false)]); } + | encaps_list { parseEncapsed($1, '`', false); $$ = $1; } +; + +ctor_arguments: + /* empty */ { $$ = array(); } + | argument_list { $$ = $1; } +; + +common_scalar: + T_LNUMBER { $$ = $this->parseLNumber($1, attributes(), true); } + | T_DNUMBER { $$ = Scalar\DNumber::fromString($1, attributes()); } + | T_CONSTANT_ENCAPSED_STRING { $$ = Scalar\String_::fromString($1, attributes(), false); } + | T_LINE { $$ = Scalar\MagicConst\Line[]; } + | T_FILE { $$ = Scalar\MagicConst\File[]; } + | T_DIR { $$ = Scalar\MagicConst\Dir[]; } + | T_CLASS_C { $$ = Scalar\MagicConst\Class_[]; } + | T_TRAIT_C { $$ = Scalar\MagicConst\Trait_[]; } + | T_METHOD_C { $$ = Scalar\MagicConst\Method[]; } + | T_FUNC_C { $$ = Scalar\MagicConst\Function_[]; } + | T_NS_C { $$ = Scalar\MagicConst\Namespace_[]; } + | T_START_HEREDOC T_ENCAPSED_AND_WHITESPACE T_END_HEREDOC + { $$ = $this->parseDocString($1, $2, $3, attributes(), stackAttributes(#3), false); } + | T_START_HEREDOC T_END_HEREDOC + { $$ = $this->parseDocString($1, '', $2, attributes(), stackAttributes(#2), false); } +; + +static_scalar: + common_scalar { $$ = $1; } + | class_name T_PAAMAYIM_NEKUDOTAYIM identifier_ex { $$ = Expr\ClassConstFetch[$1, $3]; } + | name { $$ = Expr\ConstFetch[$1]; } + | T_ARRAY '(' static_array_pair_list ')' { $$ = Expr\Array_[$3]; } + | '[' static_array_pair_list ']' { $$ = Expr\Array_[$2]; } + | static_operation { $$ = $1; } +; + +static_operation: + static_scalar T_BOOLEAN_OR static_scalar { $$ = Expr\BinaryOp\BooleanOr [$1, $3]; } + | static_scalar T_BOOLEAN_AND static_scalar { $$ = Expr\BinaryOp\BooleanAnd[$1, $3]; } + | static_scalar T_LOGICAL_OR static_scalar { $$ = Expr\BinaryOp\LogicalOr [$1, $3]; } + | static_scalar T_LOGICAL_AND static_scalar { $$ = Expr\BinaryOp\LogicalAnd[$1, $3]; } + | static_scalar T_LOGICAL_XOR static_scalar { $$ = Expr\BinaryOp\LogicalXor[$1, $3]; } + | static_scalar '|' static_scalar { $$ = Expr\BinaryOp\BitwiseOr [$1, $3]; } + | static_scalar T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG static_scalar + { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; } + | static_scalar T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG static_scalar + { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; } + | static_scalar '^' static_scalar { $$ = Expr\BinaryOp\BitwiseXor[$1, $3]; } + | static_scalar '.' static_scalar { $$ = Expr\BinaryOp\Concat [$1, $3]; } + | static_scalar '+' static_scalar { $$ = Expr\BinaryOp\Plus [$1, $3]; } + | static_scalar '-' static_scalar { $$ = Expr\BinaryOp\Minus [$1, $3]; } + | static_scalar '*' static_scalar { $$ = Expr\BinaryOp\Mul [$1, $3]; } + | static_scalar '/' static_scalar { $$ = Expr\BinaryOp\Div [$1, $3]; } + | static_scalar '%' static_scalar { $$ = Expr\BinaryOp\Mod [$1, $3]; } + | static_scalar T_SL static_scalar { $$ = Expr\BinaryOp\ShiftLeft [$1, $3]; } + | static_scalar T_SR static_scalar { $$ = Expr\BinaryOp\ShiftRight[$1, $3]; } + | static_scalar T_POW static_scalar { $$ = Expr\BinaryOp\Pow [$1, $3]; } + | '+' static_scalar %prec T_INC { $$ = Expr\UnaryPlus [$2]; } + | '-' static_scalar %prec T_INC { $$ = Expr\UnaryMinus[$2]; } + | '!' static_scalar { $$ = Expr\BooleanNot[$2]; } + | '~' static_scalar { $$ = Expr\BitwiseNot[$2]; } + | static_scalar T_IS_IDENTICAL static_scalar { $$ = Expr\BinaryOp\Identical [$1, $3]; } + | static_scalar T_IS_NOT_IDENTICAL static_scalar { $$ = Expr\BinaryOp\NotIdentical [$1, $3]; } + | static_scalar T_IS_EQUAL static_scalar { $$ = Expr\BinaryOp\Equal [$1, $3]; } + | static_scalar T_IS_NOT_EQUAL static_scalar { $$ = Expr\BinaryOp\NotEqual [$1, $3]; } + | static_scalar '<' static_scalar { $$ = Expr\BinaryOp\Smaller [$1, $3]; } + | static_scalar T_IS_SMALLER_OR_EQUAL static_scalar { $$ = Expr\BinaryOp\SmallerOrEqual[$1, $3]; } + | static_scalar '>' static_scalar { $$ = Expr\BinaryOp\Greater [$1, $3]; } + | static_scalar T_IS_GREATER_OR_EQUAL static_scalar { $$ = Expr\BinaryOp\GreaterOrEqual[$1, $3]; } + | static_scalar '?' static_scalar ':' static_scalar { $$ = Expr\Ternary[$1, $3, $5]; } + | static_scalar '?' ':' static_scalar { $$ = Expr\Ternary[$1, null, $4]; } + | static_scalar '[' static_scalar ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } + | '(' static_scalar ')' { $$ = $2; } +; + +constant: + name { $$ = Expr\ConstFetch[$1]; } + | class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_ex + { $$ = Expr\ClassConstFetch[$1, $3]; } +; + +scalar: + common_scalar { $$ = $1; } + | constant { $$ = $1; } + | '"' encaps_list '"' + { $attrs = attributes(); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED; + parseEncapsed($2, '"', true); $$ = new Scalar\Encapsed($2, $attrs); } + | T_START_HEREDOC encaps_list T_END_HEREDOC + { $$ = $this->parseDocString($1, $2, $3, attributes(), stackAttributes(#3), true); } +; + +static_array_pair_list: + /* empty */ { $$ = array(); } + | non_empty_static_array_pair_list optional_comma { $$ = $1; } +; + +optional_comma: + /* empty */ + | ',' +; + +non_empty_static_array_pair_list: + non_empty_static_array_pair_list ',' static_array_pair { push($1, $3); } + | static_array_pair { init($1); } +; + +static_array_pair: + static_scalar T_DOUBLE_ARROW static_scalar { $$ = Expr\ArrayItem[$3, $1, false]; } + | static_scalar { $$ = Expr\ArrayItem[$1, null, false]; } +; + +variable: + object_access { $$ = $1; } + | base_variable { $$ = $1; } + | function_call { $$ = $1; } + | new_expr_array_deref { $$ = $1; } +; + +new_expr_array_deref: + '(' new_expr ')' '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$2, $5]; } + | new_expr_array_deref '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } + /* alternative array syntax missing intentionally */ +; + +object_access: + variable_or_new_expr T_OBJECT_OPERATOR object_property + { $$ = Expr\PropertyFetch[$1, $3]; } + | variable_or_new_expr T_OBJECT_OPERATOR object_property argument_list + { $$ = Expr\MethodCall[$1, $3, $4]; } + | object_access argument_list { $$ = Expr\FuncCall[$1, $2]; } + | object_access '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } + | object_access '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; } +; + +variable_or_new_expr: + variable { $$ = $1; } + | '(' new_expr ')' { $$ = $2; } +; + +variable_without_objects: + reference_variable { $$ = $1; } + | '$' variable_without_objects { $$ = Expr\Variable[$2]; } +; + +base_variable: + variable_without_objects { $$ = $1; } + | static_property { $$ = $1; } +; + +static_property: + class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '$' reference_variable + { $$ = Expr\StaticPropertyFetch[$1, $4]; } + | static_property_with_arrays { $$ = $1; } +; + +static_property_simple_name: + T_VARIABLE + { $var = parseVar($1); $$ = \is_string($var) ? Node\VarLikeIdentifier[$var] : $var; } +; + +static_property_with_arrays: + class_name_or_var T_PAAMAYIM_NEKUDOTAYIM static_property_simple_name + { $$ = Expr\StaticPropertyFetch[$1, $3]; } + | class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '$' '{' expr '}' + { $$ = Expr\StaticPropertyFetch[$1, $5]; } + | static_property_with_arrays '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } + | static_property_with_arrays '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; } +; + +reference_variable: + reference_variable '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } + | reference_variable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; } + | plain_variable { $$ = $1; } + | '$' '{' expr '}' { $$ = Expr\Variable[$3]; } +; + +dim_offset: + /* empty */ { $$ = null; } + | expr { $$ = $1; } +; + +object_property: + identifier { $$ = $1; } + | '{' expr '}' { $$ = $2; } + | variable_without_objects { $$ = $1; } + | error { $$ = Expr\Error[]; $this->errorState = 2; } +; + +list_expr: + T_LIST '(' list_expr_elements ')' { $$ = Expr\List_[$3]; } +; + +list_expr_elements: + list_expr_elements ',' list_expr_element { push($1, $3); } + | list_expr_element { init($1); } +; + +list_expr_element: + variable { $$ = Expr\ArrayItem[$1, null, false]; } + | list_expr { $$ = Expr\ArrayItem[$1, null, false]; } + | /* empty */ { $$ = null; } +; + +array_pair_list: + /* empty */ { $$ = array(); } + | non_empty_array_pair_list optional_comma { $$ = $1; } +; + +non_empty_array_pair_list: + non_empty_array_pair_list ',' array_pair { push($1, $3); } + | array_pair { init($1); } +; + +array_pair: + expr T_DOUBLE_ARROW expr { $$ = Expr\ArrayItem[$3, $1, false]; } + | expr { $$ = Expr\ArrayItem[$1, null, false]; } + | expr T_DOUBLE_ARROW ampersand variable { $$ = Expr\ArrayItem[$4, $1, true]; } + | ampersand variable { $$ = Expr\ArrayItem[$2, null, true]; } + | T_ELLIPSIS expr { $$ = Expr\ArrayItem[$2, null, false, attributes(), true]; } +; + +encaps_list: + encaps_list encaps_var { push($1, $2); } + | encaps_list encaps_string_part { push($1, $2); } + | encaps_var { init($1); } + | encaps_string_part encaps_var { init($1, $2); } +; + +encaps_string_part: + T_ENCAPSED_AND_WHITESPACE { $$ = Scalar\EncapsedStringPart[$1]; } +; + +encaps_str_varname: + T_STRING_VARNAME { $$ = Expr\Variable[$1]; } +; + +encaps_var: + plain_variable { $$ = $1; } + | plain_variable '[' encaps_var_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } + | plain_variable T_OBJECT_OPERATOR identifier { $$ = Expr\PropertyFetch[$1, $3]; } + | T_DOLLAR_OPEN_CURLY_BRACES expr '}' { $$ = Expr\Variable[$2]; } + | T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '}' { $$ = Expr\Variable[$2]; } + | T_DOLLAR_OPEN_CURLY_BRACES encaps_str_varname '[' expr ']' '}' + { $$ = Expr\ArrayDimFetch[$2, $4]; } + | T_CURLY_OPEN variable '}' { $$ = $2; } +; + +encaps_var_offset: + T_STRING { $$ = Scalar\String_[$1]; } + | T_NUM_STRING { $$ = $this->parseNumString($1, attributes()); } + | plain_variable { $$ = $1; } +; + +%% diff --git a/vendor/nikic/php-parser/grammar/php7.y b/vendor/nikic/php-parser/grammar/php7.y new file mode 100644 index 0000000000..087bc7392e --- /dev/null +++ b/vendor/nikic/php-parser/grammar/php7.y @@ -0,0 +1,1204 @@ +%pure_parser +%expect 2 + +%tokens + +%% + +start: + top_statement_list { $$ = $this->handleNamespaces($1); } +; + +top_statement_list_ex: + top_statement_list_ex top_statement { pushNormalizing($1, $2); } + | /* empty */ { init(); } +; + +top_statement_list: + top_statement_list_ex + { makeZeroLengthNop($nop, $this->lookaheadStartAttributes); + if ($nop !== null) { $1[] = $nop; } $$ = $1; } +; + +ampersand: + T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG + | T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG +; + +reserved_non_modifiers: + T_INCLUDE | T_INCLUDE_ONCE | T_EVAL | T_REQUIRE | T_REQUIRE_ONCE | T_LOGICAL_OR | T_LOGICAL_XOR | T_LOGICAL_AND + | T_INSTANCEOF | T_NEW | T_CLONE | T_EXIT | T_IF | T_ELSEIF | T_ELSE | T_ENDIF | T_ECHO | T_DO | T_WHILE + | T_ENDWHILE | T_FOR | T_ENDFOR | T_FOREACH | T_ENDFOREACH | T_DECLARE | T_ENDDECLARE | T_AS | T_TRY | T_CATCH + | T_FINALLY | T_THROW | T_USE | T_INSTEADOF | T_GLOBAL | T_VAR | T_UNSET | T_ISSET | T_EMPTY | T_CONTINUE | T_GOTO + | T_FUNCTION | T_CONST | T_RETURN | T_PRINT | T_YIELD | T_LIST | T_SWITCH | T_ENDSWITCH | T_CASE | T_DEFAULT + | T_BREAK | T_ARRAY | T_CALLABLE | T_EXTENDS | T_IMPLEMENTS | T_NAMESPACE | T_TRAIT | T_INTERFACE | T_CLASS + | T_CLASS_C | T_TRAIT_C | T_FUNC_C | T_METHOD_C | T_LINE | T_FILE | T_DIR | T_NS_C | T_HALT_COMPILER | T_FN + | T_MATCH | T_ENUM +; + +semi_reserved: + reserved_non_modifiers + | T_STATIC | T_ABSTRACT | T_FINAL | T_PRIVATE | T_PROTECTED | T_PUBLIC | T_READONLY +; + +identifier_maybe_reserved: + T_STRING { $$ = Node\Identifier[$1]; } + | semi_reserved { $$ = Node\Identifier[$1]; } +; + +identifier_not_reserved: + T_STRING { $$ = Node\Identifier[$1]; } +; + +reserved_non_modifiers_identifier: + reserved_non_modifiers { $$ = Node\Identifier[$1]; } +; + +namespace_declaration_name: + T_STRING { $$ = Name[$1]; } + | semi_reserved { $$ = Name[$1]; } + | T_NAME_QUALIFIED { $$ = Name[$1]; } +; + +namespace_name: + T_STRING { $$ = Name[$1]; } + | T_NAME_QUALIFIED { $$ = Name[$1]; } +; + +legacy_namespace_name: + namespace_name { $$ = $1; } + | T_NAME_FULLY_QUALIFIED { $$ = Name[substr($1, 1)]; } +; + +plain_variable: + T_VARIABLE { $$ = Expr\Variable[parseVar($1)]; } +; + +semi: + ';' { /* nothing */ } + | error { /* nothing */ } +; + +no_comma: + /* empty */ { /* nothing */ } + | ',' { $this->emitError(new Error('A trailing comma is not allowed here', attributes())); } +; + +optional_comma: + /* empty */ + | ',' +; + +attribute_decl: + class_name { $$ = Node\Attribute[$1, []]; } + | class_name argument_list { $$ = Node\Attribute[$1, $2]; } +; + +attribute_group: + attribute_decl { init($1); } + | attribute_group ',' attribute_decl { push($1, $3); } +; + +attribute: + T_ATTRIBUTE attribute_group optional_comma ']' { $$ = Node\AttributeGroup[$2]; } +; + +attributes: + attribute { init($1); } + | attributes attribute { push($1, $2); } +; + +optional_attributes: + /* empty */ { $$ = []; } + | attributes { $$ = $1; } +; + +top_statement: + statement { $$ = $1; } + | function_declaration_statement { $$ = $1; } + | class_declaration_statement { $$ = $1; } + | T_HALT_COMPILER + { $$ = Stmt\HaltCompiler[$this->lexer->handleHaltCompiler()]; } + | T_NAMESPACE namespace_declaration_name semi + { $$ = Stmt\Namespace_[$2, null]; + $$->setAttribute('kind', Stmt\Namespace_::KIND_SEMICOLON); + $this->checkNamespace($$); } + | T_NAMESPACE namespace_declaration_name '{' top_statement_list '}' + { $$ = Stmt\Namespace_[$2, $4]; + $$->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); + $this->checkNamespace($$); } + | T_NAMESPACE '{' top_statement_list '}' + { $$ = Stmt\Namespace_[null, $3]; + $$->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); + $this->checkNamespace($$); } + | T_USE use_declarations semi { $$ = Stmt\Use_[$2, Stmt\Use_::TYPE_NORMAL]; } + | T_USE use_type use_declarations semi { $$ = Stmt\Use_[$3, $2]; } + | group_use_declaration semi { $$ = $1; } + | T_CONST constant_declaration_list semi { $$ = Stmt\Const_[$2]; } +; + +use_type: + T_FUNCTION { $$ = Stmt\Use_::TYPE_FUNCTION; } + | T_CONST { $$ = Stmt\Use_::TYPE_CONSTANT; } +; + +group_use_declaration: + T_USE use_type legacy_namespace_name T_NS_SEPARATOR '{' unprefixed_use_declarations '}' + { $$ = Stmt\GroupUse[$3, $6, $2]; } + | T_USE legacy_namespace_name T_NS_SEPARATOR '{' inline_use_declarations '}' + { $$ = Stmt\GroupUse[$2, $5, Stmt\Use_::TYPE_UNKNOWN]; } +; + +unprefixed_use_declarations: + non_empty_unprefixed_use_declarations optional_comma { $$ = $1; } +; + +non_empty_unprefixed_use_declarations: + non_empty_unprefixed_use_declarations ',' unprefixed_use_declaration + { push($1, $3); } + | unprefixed_use_declaration { init($1); } +; + +use_declarations: + non_empty_use_declarations no_comma { $$ = $1; } +; + +non_empty_use_declarations: + non_empty_use_declarations ',' use_declaration { push($1, $3); } + | use_declaration { init($1); } +; + +inline_use_declarations: + non_empty_inline_use_declarations optional_comma { $$ = $1; } +; + +non_empty_inline_use_declarations: + non_empty_inline_use_declarations ',' inline_use_declaration + { push($1, $3); } + | inline_use_declaration { init($1); } +; + +unprefixed_use_declaration: + namespace_name + { $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); } + | namespace_name T_AS identifier_not_reserved + { $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); } +; + +use_declaration: + legacy_namespace_name + { $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); } + | legacy_namespace_name T_AS identifier_not_reserved + { $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); } +; + +inline_use_declaration: + unprefixed_use_declaration { $$ = $1; $$->type = Stmt\Use_::TYPE_NORMAL; } + | use_type unprefixed_use_declaration { $$ = $2; $$->type = $1; } +; + +constant_declaration_list: + non_empty_constant_declaration_list no_comma { $$ = $1; } +; + +non_empty_constant_declaration_list: + non_empty_constant_declaration_list ',' constant_declaration + { push($1, $3); } + | constant_declaration { init($1); } +; + +constant_declaration: + identifier_not_reserved '=' expr { $$ = Node\Const_[$1, $3]; } +; + +class_const_list: + non_empty_class_const_list no_comma { $$ = $1; } +; + +non_empty_class_const_list: + non_empty_class_const_list ',' class_const { push($1, $3); } + | class_const { init($1); } +; + +class_const: + identifier_maybe_reserved '=' expr { $$ = Node\Const_[$1, $3]; } +; + +inner_statement_list_ex: + inner_statement_list_ex inner_statement { pushNormalizing($1, $2); } + | /* empty */ { init(); } +; + +inner_statement_list: + inner_statement_list_ex + { makeZeroLengthNop($nop, $this->lookaheadStartAttributes); + if ($nop !== null) { $1[] = $nop; } $$ = $1; } +; + +inner_statement: + statement { $$ = $1; } + | function_declaration_statement { $$ = $1; } + | class_declaration_statement { $$ = $1; } + | T_HALT_COMPILER + { throw new Error('__HALT_COMPILER() can only be used from the outermost scope', attributes()); } +; + +non_empty_statement: + '{' inner_statement_list '}' + { + if ($2) { + $$ = $2; prependLeadingComments($$); + } else { + makeNop($$, $this->startAttributeStack[#1], $this->endAttributes); + if (null === $$) { $$ = array(); } + } + } + | T_IF '(' expr ')' statement elseif_list else_single + { $$ = Stmt\If_[$3, ['stmts' => toArray($5), 'elseifs' => $6, 'else' => $7]]; } + | T_IF '(' expr ')' ':' inner_statement_list new_elseif_list new_else_single T_ENDIF ';' + { $$ = Stmt\If_[$3, ['stmts' => $6, 'elseifs' => $7, 'else' => $8]]; } + | T_WHILE '(' expr ')' while_statement { $$ = Stmt\While_[$3, $5]; } + | T_DO statement T_WHILE '(' expr ')' ';' { $$ = Stmt\Do_ [$5, toArray($2)]; } + | T_FOR '(' for_expr ';' for_expr ';' for_expr ')' for_statement + { $$ = Stmt\For_[['init' => $3, 'cond' => $5, 'loop' => $7, 'stmts' => $9]]; } + | T_SWITCH '(' expr ')' switch_case_list { $$ = Stmt\Switch_[$3, $5]; } + | T_BREAK optional_expr semi { $$ = Stmt\Break_[$2]; } + | T_CONTINUE optional_expr semi { $$ = Stmt\Continue_[$2]; } + | T_RETURN optional_expr semi { $$ = Stmt\Return_[$2]; } + | T_GLOBAL global_var_list semi { $$ = Stmt\Global_[$2]; } + | T_STATIC static_var_list semi { $$ = Stmt\Static_[$2]; } + | T_ECHO expr_list_forbid_comma semi { $$ = Stmt\Echo_[$2]; } + | T_INLINE_HTML { $$ = Stmt\InlineHTML[$1]; } + | expr semi { + $e = $1; + if ($e instanceof Expr\Throw_) { + // For backwards-compatibility reasons, convert throw in statement position into + // Stmt\Throw_ rather than Stmt\Expression(Expr\Throw_). + $$ = Stmt\Throw_[$e->expr]; + } else { + $$ = Stmt\Expression[$e]; + } + } + | T_UNSET '(' variables_list ')' semi { $$ = Stmt\Unset_[$3]; } + | T_FOREACH '(' expr T_AS foreach_variable ')' foreach_statement + { $$ = Stmt\Foreach_[$3, $5[0], ['keyVar' => null, 'byRef' => $5[1], 'stmts' => $7]]; } + | T_FOREACH '(' expr T_AS variable T_DOUBLE_ARROW foreach_variable ')' foreach_statement + { $$ = Stmt\Foreach_[$3, $7[0], ['keyVar' => $5, 'byRef' => $7[1], 'stmts' => $9]]; } + | T_FOREACH '(' expr error ')' foreach_statement + { $$ = Stmt\Foreach_[$3, new Expr\Error(stackAttributes(#4)), ['stmts' => $6]]; } + | T_DECLARE '(' declare_list ')' declare_statement { $$ = Stmt\Declare_[$3, $5]; } + | T_TRY '{' inner_statement_list '}' catches optional_finally + { $$ = Stmt\TryCatch[$3, $5, $6]; $this->checkTryCatch($$); } + | T_GOTO identifier_not_reserved semi { $$ = Stmt\Goto_[$2]; } + | identifier_not_reserved ':' { $$ = Stmt\Label[$1]; } + | error { $$ = array(); /* means: no statement */ } +; + +statement: + non_empty_statement { $$ = $1; } + | ';' + { makeNop($$, $this->startAttributeStack[#1], $this->endAttributes); + if ($$ === null) $$ = array(); /* means: no statement */ } +; + +catches: + /* empty */ { init(); } + | catches catch { push($1, $2); } +; + +name_union: + name { init($1); } + | name_union '|' name { push($1, $3); } +; + +catch: + T_CATCH '(' name_union optional_plain_variable ')' '{' inner_statement_list '}' + { $$ = Stmt\Catch_[$3, $4, $7]; } +; + +optional_finally: + /* empty */ { $$ = null; } + | T_FINALLY '{' inner_statement_list '}' { $$ = Stmt\Finally_[$3]; } +; + +variables_list: + non_empty_variables_list optional_comma { $$ = $1; } +; + +non_empty_variables_list: + variable { init($1); } + | non_empty_variables_list ',' variable { push($1, $3); } +; + +optional_ref: + /* empty */ { $$ = false; } + | ampersand { $$ = true; } +; + +optional_arg_ref: + /* empty */ { $$ = false; } + | T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG { $$ = true; } +; + +optional_ellipsis: + /* empty */ { $$ = false; } + | T_ELLIPSIS { $$ = true; } +; + +block_or_error: + '{' inner_statement_list '}' { $$ = $2; } + | error { $$ = []; } +; + +function_declaration_statement: + T_FUNCTION optional_ref identifier_not_reserved '(' parameter_list ')' optional_return_type block_or_error + { $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $8, 'attrGroups' => []]]; } + | attributes T_FUNCTION optional_ref identifier_not_reserved '(' parameter_list ')' optional_return_type block_or_error + { $$ = Stmt\Function_[$4, ['byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => $1]]; } +; + +class_declaration_statement: + optional_attributes class_entry_type identifier_not_reserved extends_from implements_list '{' class_statement_list '}' + { $$ = Stmt\Class_[$3, ['type' => $2, 'extends' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]]; + $this->checkClass($$, #3); } + | optional_attributes T_INTERFACE identifier_not_reserved interface_extends_list '{' class_statement_list '}' + { $$ = Stmt\Interface_[$3, ['extends' => $4, 'stmts' => $6, 'attrGroups' => $1]]; + $this->checkInterface($$, #3); } + | optional_attributes T_TRAIT identifier_not_reserved '{' class_statement_list '}' + { $$ = Stmt\Trait_[$3, ['stmts' => $5, 'attrGroups' => $1]]; } + | optional_attributes T_ENUM identifier_not_reserved enum_scalar_type implements_list '{' class_statement_list '}' + { $$ = Stmt\Enum_[$3, ['scalarType' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]]; + $this->checkEnum($$, #3); } +; + +enum_scalar_type: + /* empty */ { $$ = null; } + | ':' type { $$ = $2; } + +enum_case_expr: + /* empty */ { $$ = null; } + | '=' expr { $$ = $2; } +; + +class_entry_type: + T_CLASS { $$ = 0; } + | class_modifiers T_CLASS { $$ = $1; } +; + +class_modifiers: + class_modifier { $$ = $1; } + | class_modifiers class_modifier { $this->checkClassModifier($1, $2, #2); $$ = $1 | $2; } +; + +class_modifier: + T_ABSTRACT { $$ = Stmt\Class_::MODIFIER_ABSTRACT; } + | T_FINAL { $$ = Stmt\Class_::MODIFIER_FINAL; } + | T_READONLY { $$ = Stmt\Class_::MODIFIER_READONLY; } +; + +extends_from: + /* empty */ { $$ = null; } + | T_EXTENDS class_name { $$ = $2; } +; + +interface_extends_list: + /* empty */ { $$ = array(); } + | T_EXTENDS class_name_list { $$ = $2; } +; + +implements_list: + /* empty */ { $$ = array(); } + | T_IMPLEMENTS class_name_list { $$ = $2; } +; + +class_name_list: + non_empty_class_name_list no_comma { $$ = $1; } +; + +non_empty_class_name_list: + class_name { init($1); } + | non_empty_class_name_list ',' class_name { push($1, $3); } +; + +for_statement: + statement { $$ = toArray($1); } + | ':' inner_statement_list T_ENDFOR ';' { $$ = $2; } +; + +foreach_statement: + statement { $$ = toArray($1); } + | ':' inner_statement_list T_ENDFOREACH ';' { $$ = $2; } +; + +declare_statement: + non_empty_statement { $$ = toArray($1); } + | ';' { $$ = null; } + | ':' inner_statement_list T_ENDDECLARE ';' { $$ = $2; } +; + +declare_list: + non_empty_declare_list no_comma { $$ = $1; } +; + +non_empty_declare_list: + declare_list_element { init($1); } + | non_empty_declare_list ',' declare_list_element { push($1, $3); } +; + +declare_list_element: + identifier_not_reserved '=' expr { $$ = Stmt\DeclareDeclare[$1, $3]; } +; + +switch_case_list: + '{' case_list '}' { $$ = $2; } + | '{' ';' case_list '}' { $$ = $3; } + | ':' case_list T_ENDSWITCH ';' { $$ = $2; } + | ':' ';' case_list T_ENDSWITCH ';' { $$ = $3; } +; + +case_list: + /* empty */ { init(); } + | case_list case { push($1, $2); } +; + +case: + T_CASE expr case_separator inner_statement_list_ex { $$ = Stmt\Case_[$2, $4]; } + | T_DEFAULT case_separator inner_statement_list_ex { $$ = Stmt\Case_[null, $3]; } +; + +case_separator: + ':' + | ';' +; + +match: + T_MATCH '(' expr ')' '{' match_arm_list '}' { $$ = Expr\Match_[$3, $6]; } +; + +match_arm_list: + /* empty */ { $$ = []; } + | non_empty_match_arm_list optional_comma { $$ = $1; } +; + +non_empty_match_arm_list: + match_arm { init($1); } + | non_empty_match_arm_list ',' match_arm { push($1, $3); } +; + +match_arm: + expr_list_allow_comma T_DOUBLE_ARROW expr { $$ = Node\MatchArm[$1, $3]; } + | T_DEFAULT optional_comma T_DOUBLE_ARROW expr { $$ = Node\MatchArm[null, $4]; } +; + +while_statement: + statement { $$ = toArray($1); } + | ':' inner_statement_list T_ENDWHILE ';' { $$ = $2; } +; + +elseif_list: + /* empty */ { init(); } + | elseif_list elseif { push($1, $2); } +; + +elseif: + T_ELSEIF '(' expr ')' statement { $$ = Stmt\ElseIf_[$3, toArray($5)]; } +; + +new_elseif_list: + /* empty */ { init(); } + | new_elseif_list new_elseif { push($1, $2); } +; + +new_elseif: + T_ELSEIF '(' expr ')' ':' inner_statement_list { $$ = Stmt\ElseIf_[$3, $6]; } +; + +else_single: + /* empty */ { $$ = null; } + | T_ELSE statement { $$ = Stmt\Else_[toArray($2)]; } +; + +new_else_single: + /* empty */ { $$ = null; } + | T_ELSE ':' inner_statement_list { $$ = Stmt\Else_[$3]; } +; + +foreach_variable: + variable { $$ = array($1, false); } + | ampersand variable { $$ = array($2, true); } + | list_expr { $$ = array($1, false); } + | array_short_syntax { $$ = array($1, false); } +; + +parameter_list: + non_empty_parameter_list optional_comma { $$ = $1; } + | /* empty */ { $$ = array(); } +; + +non_empty_parameter_list: + parameter { init($1); } + | non_empty_parameter_list ',' parameter { push($1, $3); } +; + +optional_property_modifiers: + /* empty */ { $$ = 0; } + | optional_property_modifiers property_modifier + { $this->checkModifier($1, $2, #2); $$ = $1 | $2; } +; + +property_modifier: + T_PUBLIC { $$ = Stmt\Class_::MODIFIER_PUBLIC; } + | T_PROTECTED { $$ = Stmt\Class_::MODIFIER_PROTECTED; } + | T_PRIVATE { $$ = Stmt\Class_::MODIFIER_PRIVATE; } + | T_READONLY { $$ = Stmt\Class_::MODIFIER_READONLY; } +; + +parameter: + optional_attributes optional_property_modifiers optional_type_without_static + optional_arg_ref optional_ellipsis plain_variable + { $$ = new Node\Param($6, null, $3, $4, $5, attributes(), $2, $1); + $this->checkParam($$); } + | optional_attributes optional_property_modifiers optional_type_without_static + optional_arg_ref optional_ellipsis plain_variable '=' expr + { $$ = new Node\Param($6, $8, $3, $4, $5, attributes(), $2, $1); + $this->checkParam($$); } + | optional_attributes optional_property_modifiers optional_type_without_static + optional_arg_ref optional_ellipsis error + { $$ = new Node\Param(Expr\Error[], null, $3, $4, $5, attributes(), $2, $1); } +; + +type_expr: + type { $$ = $1; } + | '?' type { $$ = Node\NullableType[$2]; } + | union_type { $$ = Node\UnionType[$1]; } + | intersection_type { $$ = Node\IntersectionType[$1]; } +; + +type: + type_without_static { $$ = $1; } + | T_STATIC { $$ = Node\Name['static']; } +; + +type_without_static: + name { $$ = $this->handleBuiltinTypes($1); } + | T_ARRAY { $$ = Node\Identifier['array']; } + | T_CALLABLE { $$ = Node\Identifier['callable']; } +; + +union_type: + type '|' type { init($1, $3); } + | union_type '|' type { push($1, $3); } +; + +union_type_without_static: + type_without_static '|' type_without_static { init($1, $3); } + | union_type_without_static '|' type_without_static { push($1, $3); } +; + +intersection_type: + type T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type { init($1, $3); } + | intersection_type T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type + { push($1, $3); } +; + +intersection_type_without_static: + type_without_static T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type_without_static + { init($1, $3); } + | intersection_type_without_static T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type_without_static + { push($1, $3); } +; + +type_expr_without_static: + type_without_static { $$ = $1; } + | '?' type_without_static { $$ = Node\NullableType[$2]; } + | union_type_without_static { $$ = Node\UnionType[$1]; } + | intersection_type_without_static { $$ = Node\IntersectionType[$1]; } +; + +optional_type_without_static: + /* empty */ { $$ = null; } + | type_expr_without_static { $$ = $1; } +; + +optional_return_type: + /* empty */ { $$ = null; } + | ':' type_expr { $$ = $2; } + | ':' error { $$ = null; } +; + +argument_list: + '(' ')' { $$ = array(); } + | '(' non_empty_argument_list optional_comma ')' { $$ = $2; } + | '(' variadic_placeholder ')' { init($2); } +; + +variadic_placeholder: + T_ELLIPSIS { $$ = Node\VariadicPlaceholder[]; } +; + +non_empty_argument_list: + argument { init($1); } + | non_empty_argument_list ',' argument { push($1, $3); } +; + +argument: + expr { $$ = Node\Arg[$1, false, false]; } + | ampersand variable { $$ = Node\Arg[$2, true, false]; } + | T_ELLIPSIS expr { $$ = Node\Arg[$2, false, true]; } + | identifier_maybe_reserved ':' expr + { $$ = new Node\Arg($3, false, false, attributes(), $1); } +; + +global_var_list: + non_empty_global_var_list no_comma { $$ = $1; } +; + +non_empty_global_var_list: + non_empty_global_var_list ',' global_var { push($1, $3); } + | global_var { init($1); } +; + +global_var: + simple_variable { $$ = $1; } +; + +static_var_list: + non_empty_static_var_list no_comma { $$ = $1; } +; + +non_empty_static_var_list: + non_empty_static_var_list ',' static_var { push($1, $3); } + | static_var { init($1); } +; + +static_var: + plain_variable { $$ = Stmt\StaticVar[$1, null]; } + | plain_variable '=' expr { $$ = Stmt\StaticVar[$1, $3]; } +; + +class_statement_list_ex: + class_statement_list_ex class_statement { if ($2 !== null) { push($1, $2); } } + | /* empty */ { init(); } +; + +class_statement_list: + class_statement_list_ex + { makeZeroLengthNop($nop, $this->lookaheadStartAttributes); + if ($nop !== null) { $1[] = $nop; } $$ = $1; } +; + +class_statement: + optional_attributes variable_modifiers optional_type_without_static property_declaration_list semi + { $$ = new Stmt\Property($2, $4, attributes(), $3, $1); + $this->checkProperty($$, #2); } + | optional_attributes method_modifiers T_CONST class_const_list semi + { $$ = new Stmt\ClassConst($4, $2, attributes(), $1); + $this->checkClassConst($$, #2); } + | optional_attributes method_modifiers T_FUNCTION optional_ref identifier_maybe_reserved '(' parameter_list ')' + optional_return_type method_body + { $$ = Stmt\ClassMethod[$5, ['type' => $2, 'byRef' => $4, 'params' => $7, 'returnType' => $9, 'stmts' => $10, 'attrGroups' => $1]]; + $this->checkClassMethod($$, #2); } + | T_USE class_name_list trait_adaptations { $$ = Stmt\TraitUse[$2, $3]; } + | optional_attributes T_CASE identifier_maybe_reserved enum_case_expr semi + { $$ = Stmt\EnumCase[$3, $4, $1]; } + | error { $$ = null; /* will be skipped */ } +; + +trait_adaptations: + ';' { $$ = array(); } + | '{' trait_adaptation_list '}' { $$ = $2; } +; + +trait_adaptation_list: + /* empty */ { init(); } + | trait_adaptation_list trait_adaptation { push($1, $2); } +; + +trait_adaptation: + trait_method_reference_fully_qualified T_INSTEADOF class_name_list ';' + { $$ = Stmt\TraitUseAdaptation\Precedence[$1[0], $1[1], $3]; } + | trait_method_reference T_AS member_modifier identifier_maybe_reserved ';' + { $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, $4]; } + | trait_method_reference T_AS member_modifier ';' + { $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, null]; } + | trait_method_reference T_AS identifier_not_reserved ';' + { $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; } + | trait_method_reference T_AS reserved_non_modifiers_identifier ';' + { $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; } +; + +trait_method_reference_fully_qualified: + name T_PAAMAYIM_NEKUDOTAYIM identifier_maybe_reserved { $$ = array($1, $3); } +; +trait_method_reference: + trait_method_reference_fully_qualified { $$ = $1; } + | identifier_maybe_reserved { $$ = array(null, $1); } +; + +method_body: + ';' /* abstract method */ { $$ = null; } + | block_or_error { $$ = $1; } +; + +variable_modifiers: + non_empty_member_modifiers { $$ = $1; } + | T_VAR { $$ = 0; } +; + +method_modifiers: + /* empty */ { $$ = 0; } + | non_empty_member_modifiers { $$ = $1; } +; + +non_empty_member_modifiers: + member_modifier { $$ = $1; } + | non_empty_member_modifiers member_modifier { $this->checkModifier($1, $2, #2); $$ = $1 | $2; } +; + +member_modifier: + T_PUBLIC { $$ = Stmt\Class_::MODIFIER_PUBLIC; } + | T_PROTECTED { $$ = Stmt\Class_::MODIFIER_PROTECTED; } + | T_PRIVATE { $$ = Stmt\Class_::MODIFIER_PRIVATE; } + | T_STATIC { $$ = Stmt\Class_::MODIFIER_STATIC; } + | T_ABSTRACT { $$ = Stmt\Class_::MODIFIER_ABSTRACT; } + | T_FINAL { $$ = Stmt\Class_::MODIFIER_FINAL; } + | T_READONLY { $$ = Stmt\Class_::MODIFIER_READONLY; } +; + +property_declaration_list: + non_empty_property_declaration_list no_comma { $$ = $1; } +; + +non_empty_property_declaration_list: + property_declaration { init($1); } + | non_empty_property_declaration_list ',' property_declaration + { push($1, $3); } +; + +property_decl_name: + T_VARIABLE { $$ = Node\VarLikeIdentifier[parseVar($1)]; } +; + +property_declaration: + property_decl_name { $$ = Stmt\PropertyProperty[$1, null]; } + | property_decl_name '=' expr { $$ = Stmt\PropertyProperty[$1, $3]; } +; + +expr_list_forbid_comma: + non_empty_expr_list no_comma { $$ = $1; } +; + +expr_list_allow_comma: + non_empty_expr_list optional_comma { $$ = $1; } +; + +non_empty_expr_list: + non_empty_expr_list ',' expr { push($1, $3); } + | expr { init($1); } +; + +for_expr: + /* empty */ { $$ = array(); } + | expr_list_forbid_comma { $$ = $1; } +; + +expr: + variable { $$ = $1; } + | list_expr '=' expr { $$ = Expr\Assign[$1, $3]; } + | array_short_syntax '=' expr { $$ = Expr\Assign[$1, $3]; } + | variable '=' expr { $$ = Expr\Assign[$1, $3]; } + | variable '=' ampersand variable { $$ = Expr\AssignRef[$1, $4]; } + | new_expr { $$ = $1; } + | match { $$ = $1; } + | T_CLONE expr { $$ = Expr\Clone_[$2]; } + | variable T_PLUS_EQUAL expr { $$ = Expr\AssignOp\Plus [$1, $3]; } + | variable T_MINUS_EQUAL expr { $$ = Expr\AssignOp\Minus [$1, $3]; } + | variable T_MUL_EQUAL expr { $$ = Expr\AssignOp\Mul [$1, $3]; } + | variable T_DIV_EQUAL expr { $$ = Expr\AssignOp\Div [$1, $3]; } + | variable T_CONCAT_EQUAL expr { $$ = Expr\AssignOp\Concat [$1, $3]; } + | variable T_MOD_EQUAL expr { $$ = Expr\AssignOp\Mod [$1, $3]; } + | variable T_AND_EQUAL expr { $$ = Expr\AssignOp\BitwiseAnd[$1, $3]; } + | variable T_OR_EQUAL expr { $$ = Expr\AssignOp\BitwiseOr [$1, $3]; } + | variable T_XOR_EQUAL expr { $$ = Expr\AssignOp\BitwiseXor[$1, $3]; } + | variable T_SL_EQUAL expr { $$ = Expr\AssignOp\ShiftLeft [$1, $3]; } + | variable T_SR_EQUAL expr { $$ = Expr\AssignOp\ShiftRight[$1, $3]; } + | variable T_POW_EQUAL expr { $$ = Expr\AssignOp\Pow [$1, $3]; } + | variable T_COALESCE_EQUAL expr { $$ = Expr\AssignOp\Coalesce [$1, $3]; } + | variable T_INC { $$ = Expr\PostInc[$1]; } + | T_INC variable { $$ = Expr\PreInc [$2]; } + | variable T_DEC { $$ = Expr\PostDec[$1]; } + | T_DEC variable { $$ = Expr\PreDec [$2]; } + | expr T_BOOLEAN_OR expr { $$ = Expr\BinaryOp\BooleanOr [$1, $3]; } + | expr T_BOOLEAN_AND expr { $$ = Expr\BinaryOp\BooleanAnd[$1, $3]; } + | expr T_LOGICAL_OR expr { $$ = Expr\BinaryOp\LogicalOr [$1, $3]; } + | expr T_LOGICAL_AND expr { $$ = Expr\BinaryOp\LogicalAnd[$1, $3]; } + | expr T_LOGICAL_XOR expr { $$ = Expr\BinaryOp\LogicalXor[$1, $3]; } + | expr '|' expr { $$ = Expr\BinaryOp\BitwiseOr [$1, $3]; } + | expr T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG expr { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; } + | expr T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG expr { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; } + | expr '^' expr { $$ = Expr\BinaryOp\BitwiseXor[$1, $3]; } + | expr '.' expr { $$ = Expr\BinaryOp\Concat [$1, $3]; } + | expr '+' expr { $$ = Expr\BinaryOp\Plus [$1, $3]; } + | expr '-' expr { $$ = Expr\BinaryOp\Minus [$1, $3]; } + | expr '*' expr { $$ = Expr\BinaryOp\Mul [$1, $3]; } + | expr '/' expr { $$ = Expr\BinaryOp\Div [$1, $3]; } + | expr '%' expr { $$ = Expr\BinaryOp\Mod [$1, $3]; } + | expr T_SL expr { $$ = Expr\BinaryOp\ShiftLeft [$1, $3]; } + | expr T_SR expr { $$ = Expr\BinaryOp\ShiftRight[$1, $3]; } + | expr T_POW expr { $$ = Expr\BinaryOp\Pow [$1, $3]; } + | '+' expr %prec T_INC { $$ = Expr\UnaryPlus [$2]; } + | '-' expr %prec T_INC { $$ = Expr\UnaryMinus[$2]; } + | '!' expr { $$ = Expr\BooleanNot[$2]; } + | '~' expr { $$ = Expr\BitwiseNot[$2]; } + | expr T_IS_IDENTICAL expr { $$ = Expr\BinaryOp\Identical [$1, $3]; } + | expr T_IS_NOT_IDENTICAL expr { $$ = Expr\BinaryOp\NotIdentical [$1, $3]; } + | expr T_IS_EQUAL expr { $$ = Expr\BinaryOp\Equal [$1, $3]; } + | expr T_IS_NOT_EQUAL expr { $$ = Expr\BinaryOp\NotEqual [$1, $3]; } + | expr T_SPACESHIP expr { $$ = Expr\BinaryOp\Spaceship [$1, $3]; } + | expr '<' expr { $$ = Expr\BinaryOp\Smaller [$1, $3]; } + | expr T_IS_SMALLER_OR_EQUAL expr { $$ = Expr\BinaryOp\SmallerOrEqual[$1, $3]; } + | expr '>' expr { $$ = Expr\BinaryOp\Greater [$1, $3]; } + | expr T_IS_GREATER_OR_EQUAL expr { $$ = Expr\BinaryOp\GreaterOrEqual[$1, $3]; } + | expr T_INSTANCEOF class_name_reference { $$ = Expr\Instanceof_[$1, $3]; } + | '(' expr ')' { $$ = $2; } + | expr '?' expr ':' expr { $$ = Expr\Ternary[$1, $3, $5]; } + | expr '?' ':' expr { $$ = Expr\Ternary[$1, null, $4]; } + | expr T_COALESCE expr { $$ = Expr\BinaryOp\Coalesce[$1, $3]; } + | T_ISSET '(' expr_list_allow_comma ')' { $$ = Expr\Isset_[$3]; } + | T_EMPTY '(' expr ')' { $$ = Expr\Empty_[$3]; } + | T_INCLUDE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_INCLUDE]; } + | T_INCLUDE_ONCE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_INCLUDE_ONCE]; } + | T_EVAL '(' expr ')' { $$ = Expr\Eval_[$3]; } + | T_REQUIRE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_REQUIRE]; } + | T_REQUIRE_ONCE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_REQUIRE_ONCE]; } + | T_INT_CAST expr { $$ = Expr\Cast\Int_ [$2]; } + | T_DOUBLE_CAST expr + { $attrs = attributes(); + $attrs['kind'] = $this->getFloatCastKind($1); + $$ = new Expr\Cast\Double($2, $attrs); } + | T_STRING_CAST expr { $$ = Expr\Cast\String_ [$2]; } + | T_ARRAY_CAST expr { $$ = Expr\Cast\Array_ [$2]; } + | T_OBJECT_CAST expr { $$ = Expr\Cast\Object_ [$2]; } + | T_BOOL_CAST expr { $$ = Expr\Cast\Bool_ [$2]; } + | T_UNSET_CAST expr { $$ = Expr\Cast\Unset_ [$2]; } + | T_EXIT exit_expr + { $attrs = attributes(); + $attrs['kind'] = strtolower($1) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE; + $$ = new Expr\Exit_($2, $attrs); } + | '@' expr { $$ = Expr\ErrorSuppress[$2]; } + | scalar { $$ = $1; } + | '`' backticks_expr '`' { $$ = Expr\ShellExec[$2]; } + | T_PRINT expr { $$ = Expr\Print_[$2]; } + | T_YIELD { $$ = Expr\Yield_[null, null]; } + | T_YIELD expr { $$ = Expr\Yield_[$2, null]; } + | T_YIELD expr T_DOUBLE_ARROW expr { $$ = Expr\Yield_[$4, $2]; } + | T_YIELD_FROM expr { $$ = Expr\YieldFrom[$2]; } + | T_THROW expr { $$ = Expr\Throw_[$2]; } + + | T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr %prec T_THROW + { $$ = Expr\ArrowFunction[['static' => false, 'byRef' => $2, 'params' => $4, 'returnType' => $6, 'expr' => $8, 'attrGroups' => []]]; } + | T_STATIC T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr %prec T_THROW + { $$ = Expr\ArrowFunction[['static' => true, 'byRef' => $3, 'params' => $5, 'returnType' => $7, 'expr' => $9, 'attrGroups' => []]]; } + | T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type block_or_error + { $$ = Expr\Closure[['static' => false, 'byRef' => $2, 'params' => $4, 'uses' => $6, 'returnType' => $7, 'stmts' => $8, 'attrGroups' => []]]; } + | T_STATIC T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type block_or_error + { $$ = Expr\Closure[['static' => true, 'byRef' => $3, 'params' => $5, 'uses' => $7, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => []]]; } + + | attributes T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr %prec T_THROW + { $$ = Expr\ArrowFunction[['static' => false, 'byRef' => $3, 'params' => $5, 'returnType' => $7, 'expr' => $9, 'attrGroups' => $1]]; } + | attributes T_STATIC T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr %prec T_THROW + { $$ = Expr\ArrowFunction[['static' => true, 'byRef' => $4, 'params' => $6, 'returnType' => $8, 'expr' => $10, 'attrGroups' => $1]]; } + | attributes T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type block_or_error + { $$ = Expr\Closure[['static' => false, 'byRef' => $3, 'params' => $5, 'uses' => $7, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => $1]]; } + | attributes T_STATIC T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type block_or_error + { $$ = Expr\Closure[['static' => true, 'byRef' => $4, 'params' => $6, 'uses' => $8, 'returnType' => $9, 'stmts' => $10, 'attrGroups' => $1]]; } +; + +anonymous_class: + optional_attributes T_CLASS ctor_arguments extends_from implements_list '{' class_statement_list '}' + { $$ = array(Stmt\Class_[null, ['type' => 0, 'extends' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]], $3); + $this->checkClass($$[0], -1); } +; + +new_expr: + T_NEW class_name_reference ctor_arguments { $$ = Expr\New_[$2, $3]; } + | T_NEW anonymous_class + { list($class, $ctorArgs) = $2; $$ = Expr\New_[$class, $ctorArgs]; } +; + +lexical_vars: + /* empty */ { $$ = array(); } + | T_USE '(' lexical_var_list ')' { $$ = $3; } +; + +lexical_var_list: + non_empty_lexical_var_list optional_comma { $$ = $1; } +; + +non_empty_lexical_var_list: + lexical_var { init($1); } + | non_empty_lexical_var_list ',' lexical_var { push($1, $3); } +; + +lexical_var: + optional_ref plain_variable { $$ = Expr\ClosureUse[$2, $1]; } +; + +function_call: + name argument_list { $$ = Expr\FuncCall[$1, $2]; } + | callable_expr argument_list { $$ = Expr\FuncCall[$1, $2]; } + | class_name_or_var T_PAAMAYIM_NEKUDOTAYIM member_name argument_list + { $$ = Expr\StaticCall[$1, $3, $4]; } +; + +class_name: + T_STATIC { $$ = Name[$1]; } + | name { $$ = $1; } +; + +name: + T_STRING { $$ = Name[$1]; } + | T_NAME_QUALIFIED { $$ = Name[$1]; } + | T_NAME_FULLY_QUALIFIED { $$ = Name\FullyQualified[substr($1, 1)]; } + | T_NAME_RELATIVE { $$ = Name\Relative[substr($1, 10)]; } +; + +class_name_reference: + class_name { $$ = $1; } + | new_variable { $$ = $1; } + | '(' expr ')' { $$ = $2; } + | error { $$ = Expr\Error[]; $this->errorState = 2; } +; + +class_name_or_var: + class_name { $$ = $1; } + | fully_dereferencable { $$ = $1; } +; + +exit_expr: + /* empty */ { $$ = null; } + | '(' optional_expr ')' { $$ = $2; } +; + +backticks_expr: + /* empty */ { $$ = array(); } + | T_ENCAPSED_AND_WHITESPACE + { $$ = array(Scalar\EncapsedStringPart[Scalar\String_::parseEscapeSequences($1, '`')]); } + | encaps_list { parseEncapsed($1, '`', true); $$ = $1; } +; + +ctor_arguments: + /* empty */ { $$ = array(); } + | argument_list { $$ = $1; } +; + +constant: + name { $$ = Expr\ConstFetch[$1]; } + | T_LINE { $$ = Scalar\MagicConst\Line[]; } + | T_FILE { $$ = Scalar\MagicConst\File[]; } + | T_DIR { $$ = Scalar\MagicConst\Dir[]; } + | T_CLASS_C { $$ = Scalar\MagicConst\Class_[]; } + | T_TRAIT_C { $$ = Scalar\MagicConst\Trait_[]; } + | T_METHOD_C { $$ = Scalar\MagicConst\Method[]; } + | T_FUNC_C { $$ = Scalar\MagicConst\Function_[]; } + | T_NS_C { $$ = Scalar\MagicConst\Namespace_[]; } +; + +class_constant: + class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_maybe_reserved + { $$ = Expr\ClassConstFetch[$1, $3]; } + /* We interpret an isolated FOO:: as an unfinished class constant fetch. It could also be + an unfinished static property fetch or unfinished scoped call. */ + | class_name_or_var T_PAAMAYIM_NEKUDOTAYIM error + { $$ = Expr\ClassConstFetch[$1, new Expr\Error(stackAttributes(#3))]; $this->errorState = 2; } +; + +array_short_syntax: + '[' array_pair_list ']' + { $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_SHORT; + $$ = new Expr\Array_($2, $attrs); } +; + +dereferencable_scalar: + T_ARRAY '(' array_pair_list ')' + { $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_LONG; + $$ = new Expr\Array_($3, $attrs); } + | array_short_syntax { $$ = $1; } + | T_CONSTANT_ENCAPSED_STRING { $$ = Scalar\String_::fromString($1, attributes()); } + | '"' encaps_list '"' + { $attrs = attributes(); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED; + parseEncapsed($2, '"', true); $$ = new Scalar\Encapsed($2, $attrs); } +; + +scalar: + T_LNUMBER { $$ = $this->parseLNumber($1, attributes()); } + | T_DNUMBER { $$ = Scalar\DNumber::fromString($1, attributes()); } + | dereferencable_scalar { $$ = $1; } + | constant { $$ = $1; } + | class_constant { $$ = $1; } + | T_START_HEREDOC T_ENCAPSED_AND_WHITESPACE T_END_HEREDOC + { $$ = $this->parseDocString($1, $2, $3, attributes(), stackAttributes(#3), true); } + | T_START_HEREDOC T_END_HEREDOC + { $$ = $this->parseDocString($1, '', $2, attributes(), stackAttributes(#2), true); } + | T_START_HEREDOC encaps_list T_END_HEREDOC + { $$ = $this->parseDocString($1, $2, $3, attributes(), stackAttributes(#3), true); } +; + +optional_expr: + /* empty */ { $$ = null; } + | expr { $$ = $1; } +; + +fully_dereferencable: + variable { $$ = $1; } + | '(' expr ')' { $$ = $2; } + | dereferencable_scalar { $$ = $1; } + | class_constant { $$ = $1; } +; + +array_object_dereferencable: + fully_dereferencable { $$ = $1; } + | constant { $$ = $1; } +; + +callable_expr: + callable_variable { $$ = $1; } + | '(' expr ')' { $$ = $2; } + | dereferencable_scalar { $$ = $1; } +; + +callable_variable: + simple_variable { $$ = $1; } + | array_object_dereferencable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } + | array_object_dereferencable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; } + | function_call { $$ = $1; } + | array_object_dereferencable T_OBJECT_OPERATOR property_name argument_list + { $$ = Expr\MethodCall[$1, $3, $4]; } + | array_object_dereferencable T_NULLSAFE_OBJECT_OPERATOR property_name argument_list + { $$ = Expr\NullsafeMethodCall[$1, $3, $4]; } +; + +optional_plain_variable: + /* empty */ { $$ = null; } + | plain_variable { $$ = $1; } +; + +variable: + callable_variable { $$ = $1; } + | static_member { $$ = $1; } + | array_object_dereferencable T_OBJECT_OPERATOR property_name + { $$ = Expr\PropertyFetch[$1, $3]; } + | array_object_dereferencable T_NULLSAFE_OBJECT_OPERATOR property_name + { $$ = Expr\NullsafePropertyFetch[$1, $3]; } +; + +simple_variable: + plain_variable { $$ = $1; } + | '$' '{' expr '}' { $$ = Expr\Variable[$3]; } + | '$' simple_variable { $$ = Expr\Variable[$2]; } + | '$' error { $$ = Expr\Variable[Expr\Error[]]; $this->errorState = 2; } +; + +static_member_prop_name: + simple_variable + { $var = $1->name; $$ = \is_string($var) ? Node\VarLikeIdentifier[$var] : $var; } +; + +static_member: + class_name_or_var T_PAAMAYIM_NEKUDOTAYIM static_member_prop_name + { $$ = Expr\StaticPropertyFetch[$1, $3]; } +; + +new_variable: + simple_variable { $$ = $1; } + | new_variable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } + | new_variable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; } + | new_variable T_OBJECT_OPERATOR property_name { $$ = Expr\PropertyFetch[$1, $3]; } + | new_variable T_NULLSAFE_OBJECT_OPERATOR property_name { $$ = Expr\NullsafePropertyFetch[$1, $3]; } + | class_name T_PAAMAYIM_NEKUDOTAYIM static_member_prop_name + { $$ = Expr\StaticPropertyFetch[$1, $3]; } + | new_variable T_PAAMAYIM_NEKUDOTAYIM static_member_prop_name + { $$ = Expr\StaticPropertyFetch[$1, $3]; } +; + +member_name: + identifier_maybe_reserved { $$ = $1; } + | '{' expr '}' { $$ = $2; } + | simple_variable { $$ = $1; } +; + +property_name: + identifier_not_reserved { $$ = $1; } + | '{' expr '}' { $$ = $2; } + | simple_variable { $$ = $1; } + | error { $$ = Expr\Error[]; $this->errorState = 2; } +; + +list_expr: + T_LIST '(' inner_array_pair_list ')' { $$ = Expr\List_[$3]; } +; + +array_pair_list: + inner_array_pair_list + { $$ = $1; $end = count($$)-1; if ($$[$end] === null) array_pop($$); } +; + +comma_or_error: + ',' + | error + { /* do nothing -- prevent default action of $$=$1. See #551. */ } +; + +inner_array_pair_list: + inner_array_pair_list comma_or_error array_pair { push($1, $3); } + | array_pair { init($1); } +; + +array_pair: + expr { $$ = Expr\ArrayItem[$1, null, false]; } + | ampersand variable { $$ = Expr\ArrayItem[$2, null, true]; } + | list_expr { $$ = Expr\ArrayItem[$1, null, false]; } + | expr T_DOUBLE_ARROW expr { $$ = Expr\ArrayItem[$3, $1, false]; } + | expr T_DOUBLE_ARROW ampersand variable { $$ = Expr\ArrayItem[$4, $1, true]; } + | expr T_DOUBLE_ARROW list_expr { $$ = Expr\ArrayItem[$3, $1, false]; } + | T_ELLIPSIS expr { $$ = Expr\ArrayItem[$2, null, false, attributes(), true]; } + | /* empty */ { $$ = null; } +; + +encaps_list: + encaps_list encaps_var { push($1, $2); } + | encaps_list encaps_string_part { push($1, $2); } + | encaps_var { init($1); } + | encaps_string_part encaps_var { init($1, $2); } +; + +encaps_string_part: + T_ENCAPSED_AND_WHITESPACE { $$ = Scalar\EncapsedStringPart[$1]; } +; + +encaps_str_varname: + T_STRING_VARNAME { $$ = Expr\Variable[$1]; } +; + +encaps_var: + plain_variable { $$ = $1; } + | plain_variable '[' encaps_var_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } + | plain_variable T_OBJECT_OPERATOR identifier_not_reserved + { $$ = Expr\PropertyFetch[$1, $3]; } + | plain_variable T_NULLSAFE_OBJECT_OPERATOR identifier_not_reserved + { $$ = Expr\NullsafePropertyFetch[$1, $3]; } + | T_DOLLAR_OPEN_CURLY_BRACES expr '}' { $$ = Expr\Variable[$2]; } + | T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '}' { $$ = Expr\Variable[$2]; } + | T_DOLLAR_OPEN_CURLY_BRACES encaps_str_varname '[' expr ']' '}' + { $$ = Expr\ArrayDimFetch[$2, $4]; } + | T_CURLY_OPEN variable '}' { $$ = $2; } +; + +encaps_var_offset: + T_STRING { $$ = Scalar\String_[$1]; } + | T_NUM_STRING { $$ = $this->parseNumString($1, attributes()); } + | '-' T_NUM_STRING { $$ = $this->parseNumString('-' . $2, attributes()); } + | plain_variable { $$ = $1; } +; + +%% diff --git a/vendor/nikic/php-parser/grammar/phpyLang.php b/vendor/nikic/php-parser/grammar/phpyLang.php new file mode 100644 index 0000000000..663c2a144c --- /dev/null +++ b/vendor/nikic/php-parser/grammar/phpyLang.php @@ -0,0 +1,184 @@ +\'[^\\\\\']*+(?:\\\\.[^\\\\\']*+)*+\') + (?"[^\\\\"]*+(?:\\\\.[^\\\\"]*+)*+") + (?(?&singleQuotedString)|(?&doubleQuotedString)) + (?/\*[^*]*+(?:\*(?!/)[^*]*+)*+\*/) + (?\{[^\'"/{}]*+(?:(?:(?&string)|(?&comment)|(?&code)|/)[^\'"/{}]*+)*+}) +)'; + +const PARAMS = '\[(?[^[\]]*+(?:\[(?¶ms)\][^[\]]*+)*+)\]'; +const ARGS = '\((?[^()]*+(?:\((?&args)\)[^()]*+)*+)\)'; + +/////////////////////////////// +/// Preprocessing functions /// +/////////////////////////////// + +function preprocessGrammar($code) { + $code = resolveNodes($code); + $code = resolveMacros($code); + $code = resolveStackAccess($code); + + return $code; +} + +function resolveNodes($code) { + return preg_replace_callback( + '~\b(?[A-Z][a-zA-Z_\\\\]++)\s*' . PARAMS . '~', + function($matches) { + // recurse + $matches['params'] = resolveNodes($matches['params']); + + $params = magicSplit( + '(?:' . PARAMS . '|' . ARGS . ')(*SKIP)(*FAIL)|,', + $matches['params'] + ); + + $paramCode = ''; + foreach ($params as $param) { + $paramCode .= $param . ', '; + } + + return 'new ' . $matches['name'] . '(' . $paramCode . 'attributes())'; + }, + $code + ); +} + +function resolveMacros($code) { + return preg_replace_callback( + '~\b(?)(?!array\()(?[a-z][A-Za-z]++)' . ARGS . '~', + function($matches) { + // recurse + $matches['args'] = resolveMacros($matches['args']); + + $name = $matches['name']; + $args = magicSplit( + '(?:' . PARAMS . '|' . ARGS . ')(*SKIP)(*FAIL)|,', + $matches['args'] + ); + + if ('attributes' === $name) { + assertArgs(0, $args, $name); + return '$this->startAttributeStack[#1] + $this->endAttributes'; + } + + if ('stackAttributes' === $name) { + assertArgs(1, $args, $name); + return '$this->startAttributeStack[' . $args[0] . ']' + . ' + $this->endAttributeStack[' . $args[0] . ']'; + } + + if ('init' === $name) { + return '$$ = array(' . implode(', ', $args) . ')'; + } + + if ('push' === $name) { + assertArgs(2, $args, $name); + + return $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0]; + } + + if ('pushNormalizing' === $name) { + assertArgs(2, $args, $name); + + return 'if (is_array(' . $args[1] . ')) { $$ = array_merge(' . $args[0] . ', ' . $args[1] . '); }' + . ' else { ' . $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0] . '; }'; + } + + if ('toArray' == $name) { + assertArgs(1, $args, $name); + + return 'is_array(' . $args[0] . ') ? ' . $args[0] . ' : array(' . $args[0] . ')'; + } + + if ('parseVar' === $name) { + assertArgs(1, $args, $name); + + return 'substr(' . $args[0] . ', 1)'; + } + + if ('parseEncapsed' === $name) { + assertArgs(3, $args, $name); + + return 'foreach (' . $args[0] . ' as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) {' + . ' $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, ' . $args[1] . ', ' . $args[2] . '); } }'; + } + + if ('makeNop' === $name) { + assertArgs(3, $args, $name); + + return '$startAttributes = ' . $args[1] . ';' + . ' if (isset($startAttributes[\'comments\']))' + . ' { ' . $args[0] . ' = new Stmt\Nop($startAttributes + ' . $args[2] . '); }' + . ' else { ' . $args[0] . ' = null; }'; + } + + if ('makeZeroLengthNop' == $name) { + assertArgs(2, $args, $name); + + return '$startAttributes = ' . $args[1] . ';' + . ' if (isset($startAttributes[\'comments\']))' + . ' { ' . $args[0] . ' = new Stmt\Nop($this->createCommentNopAttributes($startAttributes[\'comments\'])); }' + . ' else { ' . $args[0] . ' = null; }'; + } + + if ('prependLeadingComments' === $name) { + assertArgs(1, $args, $name); + + return '$attrs = $this->startAttributeStack[#1]; $stmts = ' . $args[0] . '; ' + . 'if (!empty($attrs[\'comments\'])) {' + . '$stmts[0]->setAttribute(\'comments\', ' + . 'array_merge($attrs[\'comments\'], $stmts[0]->getAttribute(\'comments\', []))); }'; + } + + return $matches[0]; + }, + $code + ); +} + +function assertArgs($num, $args, $name) { + if ($num != count($args)) { + die('Wrong argument count for ' . $name . '().'); + } +} + +function resolveStackAccess($code) { + $code = preg_replace('/\$\d+/', '$this->semStack[$0]', $code); + $code = preg_replace('/#(\d+)/', '$$1', $code); + return $code; +} + +function removeTrailingWhitespace($code) { + $lines = explode("\n", $code); + $lines = array_map('rtrim', $lines); + return implode("\n", $lines); +} + +////////////////////////////// +/// Regex helper functions /// +////////////////////////////// + +function regex($regex) { + return '~' . LIB . '(?:' . str_replace('~', '\~', $regex) . ')~'; +} + +function magicSplit($regex, $string) { + $pieces = preg_split(regex('(?:(?&string)|(?&comment)|(?&code))(*SKIP)(*FAIL)|' . $regex), $string); + + foreach ($pieces as &$piece) { + $piece = trim($piece); + } + + if ($pieces === ['']) { + return []; + } + + return $pieces; +} diff --git a/vendor/nikic/php-parser/grammar/rebuildParsers.php b/vendor/nikic/php-parser/grammar/rebuildParsers.php new file mode 100644 index 0000000000..2d0c6b14d3 --- /dev/null +++ b/vendor/nikic/php-parser/grammar/rebuildParsers.php @@ -0,0 +1,81 @@ + 'Php5', + __DIR__ . '/php7.y' => 'Php7', +]; + +$tokensFile = __DIR__ . '/tokens.y'; +$tokensTemplate = __DIR__ . '/tokens.template'; +$skeletonFile = __DIR__ . '/parser.template'; +$tmpGrammarFile = __DIR__ . '/tmp_parser.phpy'; +$tmpResultFile = __DIR__ . '/tmp_parser.php'; +$resultDir = __DIR__ . '/../lib/PhpParser/Parser'; +$tokensResultsFile = $resultDir . '/Tokens.php'; + +$kmyacc = getenv('KMYACC'); +if (!$kmyacc) { + // Use phpyacc from dev dependencies by default. + $kmyacc = __DIR__ . '/../vendor/bin/phpyacc'; +} + +$options = array_flip($argv); +$optionDebug = isset($options['--debug']); +$optionKeepTmpGrammar = isset($options['--keep-tmp-grammar']); + +/////////////////// +/// Main script /// +/////////////////// + +$tokens = file_get_contents($tokensFile); + +foreach ($grammarFileToName as $grammarFile => $name) { + echo "Building temporary $name grammar file.\n"; + + $grammarCode = file_get_contents($grammarFile); + $grammarCode = str_replace('%tokens', $tokens, $grammarCode); + $grammarCode = preprocessGrammar($grammarCode); + + file_put_contents($tmpGrammarFile, $grammarCode); + + $additionalArgs = $optionDebug ? '-t -v' : ''; + + echo "Building $name parser.\n"; + $output = execCmd("$kmyacc $additionalArgs -m $skeletonFile -p $name $tmpGrammarFile"); + + $resultCode = file_get_contents($tmpResultFile); + $resultCode = removeTrailingWhitespace($resultCode); + + ensureDirExists($resultDir); + file_put_contents("$resultDir/$name.php", $resultCode); + unlink($tmpResultFile); + + echo "Building token definition.\n"; + $output = execCmd("$kmyacc -m $tokensTemplate $tmpGrammarFile"); + rename($tmpResultFile, $tokensResultsFile); + + if (!$optionKeepTmpGrammar) { + unlink($tmpGrammarFile); + } +} + +//////////////////////////////// +/// Utility helper functions /// +//////////////////////////////// + +function ensureDirExists($dir) { + if (!is_dir($dir)) { + mkdir($dir, 0777, true); + } +} + +function execCmd($cmd) { + $output = trim(shell_exec("$cmd 2>&1")); + if ($output !== "") { + echo "> " . $cmd . "\n"; + echo $output; + } + return $output; +} diff --git a/vendor/nikic/php-parser/grammar/tokens.template b/vendor/nikic/php-parser/grammar/tokens.template new file mode 100644 index 0000000000..ba4e4901c0 --- /dev/null +++ b/vendor/nikic/php-parser/grammar/tokens.template @@ -0,0 +1,17 @@ +semValue +#semval($,%t) $this->semValue +#semval(%n) $this->stackPos-(%l-%n) +#semval(%n,%t) $this->stackPos-(%l-%n) + +namespace PhpParser\Parser; +#include; + +/* GENERATED file based on grammar/tokens.y */ +final class Tokens +{ +#tokenval + const %s = %n; +#endtokenval +} diff --git a/vendor/nikic/php-parser/grammar/tokens.y b/vendor/nikic/php-parser/grammar/tokens.y new file mode 100644 index 0000000000..8f0b217254 --- /dev/null +++ b/vendor/nikic/php-parser/grammar/tokens.y @@ -0,0 +1,115 @@ +/* We currently rely on the token ID mapping to be the same between PHP 5 and PHP 7 - so the same lexer can be used for + * both. This is enforced by sharing this token file. */ + +%right T_THROW +%left T_INCLUDE T_INCLUDE_ONCE T_EVAL T_REQUIRE T_REQUIRE_ONCE +%left ',' +%left T_LOGICAL_OR +%left T_LOGICAL_XOR +%left T_LOGICAL_AND +%right T_PRINT +%right T_YIELD +%right T_DOUBLE_ARROW +%right T_YIELD_FROM +%left '=' T_PLUS_EQUAL T_MINUS_EQUAL T_MUL_EQUAL T_DIV_EQUAL T_CONCAT_EQUAL T_MOD_EQUAL T_AND_EQUAL T_OR_EQUAL T_XOR_EQUAL T_SL_EQUAL T_SR_EQUAL T_POW_EQUAL T_COALESCE_EQUAL +%left '?' ':' +%right T_COALESCE +%left T_BOOLEAN_OR +%left T_BOOLEAN_AND +%left '|' +%left '^' +%left T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG +%nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL T_SPACESHIP +%nonassoc '<' T_IS_SMALLER_OR_EQUAL '>' T_IS_GREATER_OR_EQUAL +%left T_SL T_SR +%left '+' '-' '.' +%left '*' '/' '%' +%right '!' +%nonassoc T_INSTANCEOF +%right '~' T_INC T_DEC T_INT_CAST T_DOUBLE_CAST T_STRING_CAST T_ARRAY_CAST T_OBJECT_CAST T_BOOL_CAST T_UNSET_CAST '@' +%right T_POW +%right '[' +%nonassoc T_NEW T_CLONE +%token T_EXIT +%token T_IF +%left T_ELSEIF +%left T_ELSE +%left T_ENDIF +%token T_LNUMBER +%token T_DNUMBER +%token T_STRING +%token T_STRING_VARNAME +%token T_VARIABLE +%token T_NUM_STRING +%token T_INLINE_HTML +%token T_ENCAPSED_AND_WHITESPACE +%token T_CONSTANT_ENCAPSED_STRING +%token T_ECHO +%token T_DO +%token T_WHILE +%token T_ENDWHILE +%token T_FOR +%token T_ENDFOR +%token T_FOREACH +%token T_ENDFOREACH +%token T_DECLARE +%token T_ENDDECLARE +%token T_AS +%token T_SWITCH +%token T_MATCH +%token T_ENDSWITCH +%token T_CASE +%token T_DEFAULT +%token T_BREAK +%token T_CONTINUE +%token T_GOTO +%token T_FUNCTION +%token T_FN +%token T_CONST +%token T_RETURN +%token T_TRY +%token T_CATCH +%token T_FINALLY +%token T_THROW +%token T_USE +%token T_INSTEADOF +%token T_GLOBAL +%right T_STATIC T_ABSTRACT T_FINAL T_PRIVATE T_PROTECTED T_PUBLIC T_READONLY +%token T_VAR +%token T_UNSET +%token T_ISSET +%token T_EMPTY +%token T_HALT_COMPILER +%token T_CLASS +%token T_TRAIT +%token T_INTERFACE +%token T_ENUM +%token T_EXTENDS +%token T_IMPLEMENTS +%token T_OBJECT_OPERATOR +%token T_NULLSAFE_OBJECT_OPERATOR +%token T_DOUBLE_ARROW +%token T_LIST +%token T_ARRAY +%token T_CALLABLE +%token T_CLASS_C +%token T_TRAIT_C +%token T_METHOD_C +%token T_FUNC_C +%token T_LINE +%token T_FILE +%token T_START_HEREDOC +%token T_END_HEREDOC +%token T_DOLLAR_OPEN_CURLY_BRACES +%token T_CURLY_OPEN +%token T_PAAMAYIM_NEKUDOTAYIM +%token T_NAMESPACE +%token T_NS_C +%token T_DIR +%token T_NS_SEPARATOR +%token T_ELLIPSIS +%token T_NAME_FULLY_QUALIFIED +%token T_NAME_QUALIFIED +%token T_NAME_RELATIVE +%token T_ATTRIBUTE +%token T_ENUM diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder.php b/vendor/nikic/php-parser/lib/PhpParser/Builder.php new file mode 100644 index 0000000000..26d8921efc --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder.php @@ -0,0 +1,13 @@ +constants = [new Const_($name, BuilderHelpers::normalizeValue($value))]; + } + + /** + * Add another constant to const group + * + * @param string|Identifier $name Name + * @param Node\Expr|bool|null|int|float|string|array $value Value + * + * @return $this The builder instance (for fluid interface) + */ + public function addConst($name, $value) { + $this->constants[] = new Const_($name, BuilderHelpers::normalizeValue($value)); + + return $this; + } + + /** + * Makes the constant public. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePublic() { + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PUBLIC); + + return $this; + } + + /** + * Makes the constant protected. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeProtected() { + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PROTECTED); + + return $this; + } + + /** + * Makes the constant private. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePrivate() { + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PRIVATE); + + return $this; + } + + /** + * Makes the constant final. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeFinal() { + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_FINAL); + + return $this; + } + + /** + * Sets doc comment for the constant. + * + * @param PhpParser\Comment\Doc|string $docComment Doc comment to set + * + * @return $this The builder instance (for fluid interface) + */ + public function setDocComment($docComment) { + $this->attributes = [ + 'comments' => [BuilderHelpers::normalizeDocComment($docComment)] + ]; + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built class node. + * + * @return Stmt\ClassConst The built constant node + */ + public function getNode(): PhpParser\Node { + return new Stmt\ClassConst( + $this->constants, + $this->flags, + $this->attributes, + $this->attributeGroups + ); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Class_.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Class_.php new file mode 100644 index 0000000000..35b54d0418 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Class_.php @@ -0,0 +1,146 @@ +name = $name; + } + + /** + * Extends a class. + * + * @param Name|string $class Name of class to extend + * + * @return $this The builder instance (for fluid interface) + */ + public function extend($class) { + $this->extends = BuilderHelpers::normalizeName($class); + + return $this; + } + + /** + * Implements one or more interfaces. + * + * @param Name|string ...$interfaces Names of interfaces to implement + * + * @return $this The builder instance (for fluid interface) + */ + public function implement(...$interfaces) { + foreach ($interfaces as $interface) { + $this->implements[] = BuilderHelpers::normalizeName($interface); + } + + return $this; + } + + /** + * Makes the class abstract. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeAbstract() { + $this->flags = BuilderHelpers::addClassModifier($this->flags, Stmt\Class_::MODIFIER_ABSTRACT); + + return $this; + } + + /** + * Makes the class final. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeFinal() { + $this->flags = BuilderHelpers::addClassModifier($this->flags, Stmt\Class_::MODIFIER_FINAL); + + return $this; + } + + public function makeReadonly() { + $this->flags = BuilderHelpers::addClassModifier($this->flags, Stmt\Class_::MODIFIER_READONLY); + + return $this; + } + + /** + * Adds a statement. + * + * @param Stmt|PhpParser\Builder $stmt The statement to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addStmt($stmt) { + $stmt = BuilderHelpers::normalizeNode($stmt); + + $targets = [ + Stmt\TraitUse::class => &$this->uses, + Stmt\ClassConst::class => &$this->constants, + Stmt\Property::class => &$this->properties, + Stmt\ClassMethod::class => &$this->methods, + ]; + + $class = \get_class($stmt); + if (!isset($targets[$class])) { + throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType())); + } + + $targets[$class][] = $stmt; + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built class node. + * + * @return Stmt\Class_ The built class node + */ + public function getNode() : PhpParser\Node { + return new Stmt\Class_($this->name, [ + 'flags' => $this->flags, + 'extends' => $this->extends, + 'implements' => $this->implements, + 'stmts' => array_merge($this->uses, $this->constants, $this->properties, $this->methods), + 'attrGroups' => $this->attributeGroups, + ], $this->attributes); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Declaration.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Declaration.php new file mode 100644 index 0000000000..830949928a --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Declaration.php @@ -0,0 +1,43 @@ +addStmt($stmt); + } + + return $this; + } + + /** + * Sets doc comment for the declaration. + * + * @param PhpParser\Comment\Doc|string $docComment Doc comment to set + * + * @return $this The builder instance (for fluid interface) + */ + public function setDocComment($docComment) { + $this->attributes['comments'] = [ + BuilderHelpers::normalizeDocComment($docComment) + ]; + + return $this; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/EnumCase.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/EnumCase.php new file mode 100644 index 0000000000..02fa83e624 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/EnumCase.php @@ -0,0 +1,85 @@ +name = $name; + } + + /** + * Sets the value. + * + * @param Node\Expr|string|int $value + * + * @return $this + */ + public function setValue($value) { + $this->value = BuilderHelpers::normalizeValue($value); + + return $this; + } + + /** + * Sets doc comment for the constant. + * + * @param PhpParser\Comment\Doc|string $docComment Doc comment to set + * + * @return $this The builder instance (for fluid interface) + */ + public function setDocComment($docComment) { + $this->attributes = [ + 'comments' => [BuilderHelpers::normalizeDocComment($docComment)] + ]; + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built enum case node. + * + * @return Stmt\EnumCase The built constant node + */ + public function getNode(): PhpParser\Node { + return new Stmt\EnumCase( + $this->name, + $this->value, + $this->attributes, + $this->attributeGroups + ); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Enum_.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Enum_.php new file mode 100644 index 0000000000..be7eef95f5 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Enum_.php @@ -0,0 +1,117 @@ +name = $name; + } + + /** + * Sets the scalar type. + * + * @param string|Identifier $type + * + * @return $this + */ + public function setScalarType($scalarType) { + $this->scalarType = BuilderHelpers::normalizeType($scalarType); + + return $this; + } + + /** + * Implements one or more interfaces. + * + * @param Name|string ...$interfaces Names of interfaces to implement + * + * @return $this The builder instance (for fluid interface) + */ + public function implement(...$interfaces) { + foreach ($interfaces as $interface) { + $this->implements[] = BuilderHelpers::normalizeName($interface); + } + + return $this; + } + + /** + * Adds a statement. + * + * @param Stmt|PhpParser\Builder $stmt The statement to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addStmt($stmt) { + $stmt = BuilderHelpers::normalizeNode($stmt); + + $targets = [ + Stmt\TraitUse::class => &$this->uses, + Stmt\EnumCase::class => &$this->enumCases, + Stmt\ClassConst::class => &$this->constants, + Stmt\ClassMethod::class => &$this->methods, + ]; + + $class = \get_class($stmt); + if (!isset($targets[$class])) { + throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType())); + } + + $targets[$class][] = $stmt; + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built class node. + * + * @return Stmt\Enum_ The built enum node + */ + public function getNode() : PhpParser\Node { + return new Stmt\Enum_($this->name, [ + 'scalarType' => $this->scalarType, + 'implements' => $this->implements, + 'stmts' => array_merge($this->uses, $this->enumCases, $this->constants, $this->methods), + 'attrGroups' => $this->attributeGroups, + ], $this->attributes); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.php new file mode 100644 index 0000000000..98ea9d3366 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.php @@ -0,0 +1,73 @@ +returnByRef = true; + + return $this; + } + + /** + * Adds a parameter. + * + * @param Node\Param|Param $param The parameter to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addParam($param) { + $param = BuilderHelpers::normalizeNode($param); + + if (!$param instanceof Node\Param) { + throw new \LogicException(sprintf('Expected parameter node, got "%s"', $param->getType())); + } + + $this->params[] = $param; + + return $this; + } + + /** + * Adds multiple parameters. + * + * @param array $params The parameters to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addParams(array $params) { + foreach ($params as $param) { + $this->addParam($param); + } + + return $this; + } + + /** + * Sets the return type for PHP 7. + * + * @param string|Node\Name|Node\Identifier|Node\ComplexType $type + * + * @return $this The builder instance (for fluid interface) + */ + public function setReturnType($type) { + $this->returnType = BuilderHelpers::normalizeType($type); + + return $this; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Function_.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Function_.php new file mode 100644 index 0000000000..1cd73c0d3b --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Function_.php @@ -0,0 +1,67 @@ +name = $name; + } + + /** + * Adds a statement. + * + * @param Node|PhpParser\Builder $stmt The statement to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addStmt($stmt) { + $this->stmts[] = BuilderHelpers::normalizeStmt($stmt); + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built function node. + * + * @return Stmt\Function_ The built function node + */ + public function getNode() : Node { + return new Stmt\Function_($this->name, [ + 'byRef' => $this->returnByRef, + 'params' => $this->params, + 'returnType' => $this->returnType, + 'stmts' => $this->stmts, + 'attrGroups' => $this->attributeGroups, + ], $this->attributes); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Interface_.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Interface_.php new file mode 100644 index 0000000000..7806e85fce --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Interface_.php @@ -0,0 +1,93 @@ +name = $name; + } + + /** + * Extends one or more interfaces. + * + * @param Name|string ...$interfaces Names of interfaces to extend + * + * @return $this The builder instance (for fluid interface) + */ + public function extend(...$interfaces) { + foreach ($interfaces as $interface) { + $this->extends[] = BuilderHelpers::normalizeName($interface); + } + + return $this; + } + + /** + * Adds a statement. + * + * @param Stmt|PhpParser\Builder $stmt The statement to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addStmt($stmt) { + $stmt = BuilderHelpers::normalizeNode($stmt); + + if ($stmt instanceof Stmt\ClassConst) { + $this->constants[] = $stmt; + } elseif ($stmt instanceof Stmt\ClassMethod) { + // we erase all statements in the body of an interface method + $stmt->stmts = null; + $this->methods[] = $stmt; + } else { + throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType())); + } + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built interface node. + * + * @return Stmt\Interface_ The built interface node + */ + public function getNode() : PhpParser\Node { + return new Stmt\Interface_($this->name, [ + 'extends' => $this->extends, + 'stmts' => array_merge($this->constants, $this->methods), + 'attrGroups' => $this->attributeGroups, + ], $this->attributes); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Method.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Method.php new file mode 100644 index 0000000000..232d7cb874 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Method.php @@ -0,0 +1,146 @@ +name = $name; + } + + /** + * Makes the method public. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePublic() { + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PUBLIC); + + return $this; + } + + /** + * Makes the method protected. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeProtected() { + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PROTECTED); + + return $this; + } + + /** + * Makes the method private. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePrivate() { + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PRIVATE); + + return $this; + } + + /** + * Makes the method static. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeStatic() { + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_STATIC); + + return $this; + } + + /** + * Makes the method abstract. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeAbstract() { + if (!empty($this->stmts)) { + throw new \LogicException('Cannot make method with statements abstract'); + } + + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_ABSTRACT); + $this->stmts = null; // abstract methods don't have statements + + return $this; + } + + /** + * Makes the method final. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeFinal() { + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_FINAL); + + return $this; + } + + /** + * Adds a statement. + * + * @param Node|PhpParser\Builder $stmt The statement to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addStmt($stmt) { + if (null === $this->stmts) { + throw new \LogicException('Cannot add statements to an abstract method'); + } + + $this->stmts[] = BuilderHelpers::normalizeStmt($stmt); + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built method node. + * + * @return Stmt\ClassMethod The built method node + */ + public function getNode() : Node { + return new Stmt\ClassMethod($this->name, [ + 'flags' => $this->flags, + 'byRef' => $this->returnByRef, + 'params' => $this->params, + 'returnType' => $this->returnType, + 'stmts' => $this->stmts, + 'attrGroups' => $this->attributeGroups, + ], $this->attributes); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Namespace_.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Namespace_.php new file mode 100644 index 0000000000..1c751e163a --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Namespace_.php @@ -0,0 +1,45 @@ +name = null !== $name ? BuilderHelpers::normalizeName($name) : null; + } + + /** + * Adds a statement. + * + * @param Node|PhpParser\Builder $stmt The statement to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addStmt($stmt) { + $this->stmts[] = BuilderHelpers::normalizeStmt($stmt); + + return $this; + } + + /** + * Returns the built node. + * + * @return Stmt\Namespace_ The built node + */ + public function getNode() : Node { + return new Stmt\Namespace_($this->name, $this->stmts, $this->attributes); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Param.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Param.php new file mode 100644 index 0000000000..de9aae7e5e --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Param.php @@ -0,0 +1,122 @@ +name = $name; + } + + /** + * Sets default value for the parameter. + * + * @param mixed $value Default value to use + * + * @return $this The builder instance (for fluid interface) + */ + public function setDefault($value) { + $this->default = BuilderHelpers::normalizeValue($value); + + return $this; + } + + /** + * Sets type for the parameter. + * + * @param string|Node\Name|Node\Identifier|Node\ComplexType $type Parameter type + * + * @return $this The builder instance (for fluid interface) + */ + public function setType($type) { + $this->type = BuilderHelpers::normalizeType($type); + if ($this->type == 'void') { + throw new \LogicException('Parameter type cannot be void'); + } + + return $this; + } + + /** + * Sets type for the parameter. + * + * @param string|Node\Name|Node\Identifier|Node\ComplexType $type Parameter type + * + * @return $this The builder instance (for fluid interface) + * + * @deprecated Use setType() instead + */ + public function setTypeHint($type) { + return $this->setType($type); + } + + /** + * Make the parameter accept the value by reference. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeByRef() { + $this->byRef = true; + + return $this; + } + + /** + * Make the parameter variadic + * + * @return $this The builder instance (for fluid interface) + */ + public function makeVariadic() { + $this->variadic = true; + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built parameter node. + * + * @return Node\Param The built parameter node + */ + public function getNode() : Node { + return new Node\Param( + new Node\Expr\Variable($this->name), + $this->default, $this->type, $this->byRef, $this->variadic, [], 0, $this->attributeGroups + ); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Property.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Property.php new file mode 100644 index 0000000000..68e318565e --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Property.php @@ -0,0 +1,161 @@ +name = $name; + } + + /** + * Makes the property public. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePublic() { + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PUBLIC); + + return $this; + } + + /** + * Makes the property protected. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeProtected() { + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PROTECTED); + + return $this; + } + + /** + * Makes the property private. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePrivate() { + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PRIVATE); + + return $this; + } + + /** + * Makes the property static. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeStatic() { + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_STATIC); + + return $this; + } + + /** + * Makes the property readonly. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeReadonly() { + $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_READONLY); + + return $this; + } + + /** + * Sets default value for the property. + * + * @param mixed $value Default value to use + * + * @return $this The builder instance (for fluid interface) + */ + public function setDefault($value) { + $this->default = BuilderHelpers::normalizeValue($value); + + return $this; + } + + /** + * Sets doc comment for the property. + * + * @param PhpParser\Comment\Doc|string $docComment Doc comment to set + * + * @return $this The builder instance (for fluid interface) + */ + public function setDocComment($docComment) { + $this->attributes = [ + 'comments' => [BuilderHelpers::normalizeDocComment($docComment)] + ]; + + return $this; + } + + /** + * Sets the property type for PHP 7.4+. + * + * @param string|Name|Identifier|ComplexType $type + * + * @return $this + */ + public function setType($type) { + $this->type = BuilderHelpers::normalizeType($type); + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built class node. + * + * @return Stmt\Property The built property node + */ + public function getNode() : PhpParser\Node { + return new Stmt\Property( + $this->flags !== 0 ? $this->flags : Stmt\Class_::MODIFIER_PUBLIC, + [ + new Stmt\PropertyProperty($this->name, $this->default) + ], + $this->attributes, + $this->type, + $this->attributeGroups + ); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/TraitUse.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/TraitUse.php new file mode 100644 index 0000000000..311e8cd7b6 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/TraitUse.php @@ -0,0 +1,64 @@ +and($trait); + } + } + + /** + * Adds used trait. + * + * @param Node\Name|string $trait Trait name + * + * @return $this The builder instance (for fluid interface) + */ + public function and($trait) { + $this->traits[] = BuilderHelpers::normalizeName($trait); + return $this; + } + + /** + * Adds trait adaptation. + * + * @param Stmt\TraitUseAdaptation|Builder\TraitUseAdaptation $adaptation Trait adaptation + * + * @return $this The builder instance (for fluid interface) + */ + public function with($adaptation) { + $adaptation = BuilderHelpers::normalizeNode($adaptation); + + if (!$adaptation instanceof Stmt\TraitUseAdaptation) { + throw new \LogicException('Adaptation must have type TraitUseAdaptation'); + } + + $this->adaptations[] = $adaptation; + return $this; + } + + /** + * Returns the built node. + * + * @return Node The built node + */ + public function getNode() : Node { + return new Stmt\TraitUse($this->traits, $this->adaptations); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/TraitUseAdaptation.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/TraitUseAdaptation.php new file mode 100644 index 0000000000..eb6c0b622d --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/TraitUseAdaptation.php @@ -0,0 +1,148 @@ +type = self::TYPE_UNDEFINED; + + $this->trait = is_null($trait)? null: BuilderHelpers::normalizeName($trait); + $this->method = BuilderHelpers::normalizeIdentifier($method); + } + + /** + * Sets alias of method. + * + * @param Node\Identifier|string $alias Alias for adaptated method + * + * @return $this The builder instance (for fluid interface) + */ + public function as($alias) { + if ($this->type === self::TYPE_UNDEFINED) { + $this->type = self::TYPE_ALIAS; + } + + if ($this->type !== self::TYPE_ALIAS) { + throw new \LogicException('Cannot set alias for not alias adaptation buider'); + } + + $this->alias = $alias; + return $this; + } + + /** + * Sets adaptated method public. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePublic() { + $this->setModifier(Stmt\Class_::MODIFIER_PUBLIC); + return $this; + } + + /** + * Sets adaptated method protected. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeProtected() { + $this->setModifier(Stmt\Class_::MODIFIER_PROTECTED); + return $this; + } + + /** + * Sets adaptated method private. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePrivate() { + $this->setModifier(Stmt\Class_::MODIFIER_PRIVATE); + return $this; + } + + /** + * Adds overwritten traits. + * + * @param Node\Name|string ...$traits Traits for overwrite + * + * @return $this The builder instance (for fluid interface) + */ + public function insteadof(...$traits) { + if ($this->type === self::TYPE_UNDEFINED) { + if (is_null($this->trait)) { + throw new \LogicException('Precedence adaptation must have trait'); + } + + $this->type = self::TYPE_PRECEDENCE; + } + + if ($this->type !== self::TYPE_PRECEDENCE) { + throw new \LogicException('Cannot add overwritten traits for not precedence adaptation buider'); + } + + foreach ($traits as $trait) { + $this->insteadof[] = BuilderHelpers::normalizeName($trait); + } + + return $this; + } + + protected function setModifier(int $modifier) { + if ($this->type === self::TYPE_UNDEFINED) { + $this->type = self::TYPE_ALIAS; + } + + if ($this->type !== self::TYPE_ALIAS) { + throw new \LogicException('Cannot set access modifier for not alias adaptation buider'); + } + + if (is_null($this->modifier)) { + $this->modifier = $modifier; + } else { + throw new \LogicException('Multiple access type modifiers are not allowed'); + } + } + + /** + * Returns the built node. + * + * @return Node The built node + */ + public function getNode() : Node { + switch ($this->type) { + case self::TYPE_ALIAS: + return new Stmt\TraitUseAdaptation\Alias($this->trait, $this->method, $this->modifier, $this->alias); + case self::TYPE_PRECEDENCE: + return new Stmt\TraitUseAdaptation\Precedence($this->trait, $this->method, $this->insteadof); + default: + throw new \LogicException('Type of adaptation is not defined'); + } + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Trait_.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Trait_.php new file mode 100644 index 0000000000..97f32f98d6 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Trait_.php @@ -0,0 +1,78 @@ +name = $name; + } + + /** + * Adds a statement. + * + * @param Stmt|PhpParser\Builder $stmt The statement to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addStmt($stmt) { + $stmt = BuilderHelpers::normalizeNode($stmt); + + if ($stmt instanceof Stmt\Property) { + $this->properties[] = $stmt; + } elseif ($stmt instanceof Stmt\ClassMethod) { + $this->methods[] = $stmt; + } elseif ($stmt instanceof Stmt\TraitUse) { + $this->uses[] = $stmt; + } else { + throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType())); + } + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built trait node. + * + * @return Stmt\Trait_ The built interface node + */ + public function getNode() : PhpParser\Node { + return new Stmt\Trait_( + $this->name, [ + 'stmts' => array_merge($this->uses, $this->properties, $this->methods), + 'attrGroups' => $this->attributeGroups, + ], $this->attributes + ); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Use_.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Use_.php new file mode 100644 index 0000000000..4bd3d12df0 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Use_.php @@ -0,0 +1,49 @@ +name = BuilderHelpers::normalizeName($name); + $this->type = $type; + } + + /** + * Sets alias for used name. + * + * @param string $alias Alias to use (last component of full name by default) + * + * @return $this The builder instance (for fluid interface) + */ + public function as(string $alias) { + $this->alias = $alias; + return $this; + } + + /** + * Returns the built node. + * + * @return Stmt\Use_ The built node + */ + public function getNode() : Node { + return new Stmt\Use_([ + new Stmt\UseUse($this->name, $this->alias) + ], $this->type); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/BuilderFactory.php b/vendor/nikic/php-parser/lib/PhpParser/BuilderFactory.php new file mode 100644 index 0000000000..fef2579b3e --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/BuilderFactory.php @@ -0,0 +1,399 @@ +args($args) + ); + } + + /** + * Creates a namespace builder. + * + * @param null|string|Node\Name $name Name of the namespace + * + * @return Builder\Namespace_ The created namespace builder + */ + public function namespace($name) : Builder\Namespace_ { + return new Builder\Namespace_($name); + } + + /** + * Creates a class builder. + * + * @param string $name Name of the class + * + * @return Builder\Class_ The created class builder + */ + public function class(string $name) : Builder\Class_ { + return new Builder\Class_($name); + } + + /** + * Creates an interface builder. + * + * @param string $name Name of the interface + * + * @return Builder\Interface_ The created interface builder + */ + public function interface(string $name) : Builder\Interface_ { + return new Builder\Interface_($name); + } + + /** + * Creates a trait builder. + * + * @param string $name Name of the trait + * + * @return Builder\Trait_ The created trait builder + */ + public function trait(string $name) : Builder\Trait_ { + return new Builder\Trait_($name); + } + + /** + * Creates an enum builder. + * + * @param string $name Name of the enum + * + * @return Builder\Enum_ The created enum builder + */ + public function enum(string $name) : Builder\Enum_ { + return new Builder\Enum_($name); + } + + /** + * Creates a trait use builder. + * + * @param Node\Name|string ...$traits Trait names + * + * @return Builder\TraitUse The create trait use builder + */ + public function useTrait(...$traits) : Builder\TraitUse { + return new Builder\TraitUse(...$traits); + } + + /** + * Creates a trait use adaptation builder. + * + * @param Node\Name|string|null $trait Trait name + * @param Node\Identifier|string $method Method name + * + * @return Builder\TraitUseAdaptation The create trait use adaptation builder + */ + public function traitUseAdaptation($trait, $method = null) : Builder\TraitUseAdaptation { + if ($method === null) { + $method = $trait; + $trait = null; + } + + return new Builder\TraitUseAdaptation($trait, $method); + } + + /** + * Creates a method builder. + * + * @param string $name Name of the method + * + * @return Builder\Method The created method builder + */ + public function method(string $name) : Builder\Method { + return new Builder\Method($name); + } + + /** + * Creates a parameter builder. + * + * @param string $name Name of the parameter + * + * @return Builder\Param The created parameter builder + */ + public function param(string $name) : Builder\Param { + return new Builder\Param($name); + } + + /** + * Creates a property builder. + * + * @param string $name Name of the property + * + * @return Builder\Property The created property builder + */ + public function property(string $name) : Builder\Property { + return new Builder\Property($name); + } + + /** + * Creates a function builder. + * + * @param string $name Name of the function + * + * @return Builder\Function_ The created function builder + */ + public function function(string $name) : Builder\Function_ { + return new Builder\Function_($name); + } + + /** + * Creates a namespace/class use builder. + * + * @param Node\Name|string $name Name of the entity (namespace or class) to alias + * + * @return Builder\Use_ The created use builder + */ + public function use($name) : Builder\Use_ { + return new Builder\Use_($name, Use_::TYPE_NORMAL); + } + + /** + * Creates a function use builder. + * + * @param Node\Name|string $name Name of the function to alias + * + * @return Builder\Use_ The created use function builder + */ + public function useFunction($name) : Builder\Use_ { + return new Builder\Use_($name, Use_::TYPE_FUNCTION); + } + + /** + * Creates a constant use builder. + * + * @param Node\Name|string $name Name of the const to alias + * + * @return Builder\Use_ The created use const builder + */ + public function useConst($name) : Builder\Use_ { + return new Builder\Use_($name, Use_::TYPE_CONSTANT); + } + + /** + * Creates a class constant builder. + * + * @param string|Identifier $name Name + * @param Node\Expr|bool|null|int|float|string|array $value Value + * + * @return Builder\ClassConst The created use const builder + */ + public function classConst($name, $value) : Builder\ClassConst { + return new Builder\ClassConst($name, $value); + } + + /** + * Creates an enum case builder. + * + * @param string|Identifier $name Name + * + * @return Builder\EnumCase The created use const builder + */ + public function enumCase($name) : Builder\EnumCase { + return new Builder\EnumCase($name); + } + + /** + * Creates node a for a literal value. + * + * @param Expr|bool|null|int|float|string|array $value $value + * + * @return Expr + */ + public function val($value) : Expr { + return BuilderHelpers::normalizeValue($value); + } + + /** + * Creates variable node. + * + * @param string|Expr $name Name + * + * @return Expr\Variable + */ + public function var($name) : Expr\Variable { + if (!\is_string($name) && !$name instanceof Expr) { + throw new \LogicException('Variable name must be string or Expr'); + } + + return new Expr\Variable($name); + } + + /** + * Normalizes an argument list. + * + * Creates Arg nodes for all arguments and converts literal values to expressions. + * + * @param array $args List of arguments to normalize + * + * @return Arg[] + */ + public function args(array $args) : array { + $normalizedArgs = []; + foreach ($args as $key => $arg) { + if (!($arg instanceof Arg)) { + $arg = new Arg(BuilderHelpers::normalizeValue($arg)); + } + if (\is_string($key)) { + $arg->name = BuilderHelpers::normalizeIdentifier($key); + } + $normalizedArgs[] = $arg; + } + return $normalizedArgs; + } + + /** + * Creates a function call node. + * + * @param string|Name|Expr $name Function name + * @param array $args Function arguments + * + * @return Expr\FuncCall + */ + public function funcCall($name, array $args = []) : Expr\FuncCall { + return new Expr\FuncCall( + BuilderHelpers::normalizeNameOrExpr($name), + $this->args($args) + ); + } + + /** + * Creates a method call node. + * + * @param Expr $var Variable the method is called on + * @param string|Identifier|Expr $name Method name + * @param array $args Method arguments + * + * @return Expr\MethodCall + */ + public function methodCall(Expr $var, $name, array $args = []) : Expr\MethodCall { + return new Expr\MethodCall( + $var, + BuilderHelpers::normalizeIdentifierOrExpr($name), + $this->args($args) + ); + } + + /** + * Creates a static method call node. + * + * @param string|Name|Expr $class Class name + * @param string|Identifier|Expr $name Method name + * @param array $args Method arguments + * + * @return Expr\StaticCall + */ + public function staticCall($class, $name, array $args = []) : Expr\StaticCall { + return new Expr\StaticCall( + BuilderHelpers::normalizeNameOrExpr($class), + BuilderHelpers::normalizeIdentifierOrExpr($name), + $this->args($args) + ); + } + + /** + * Creates an object creation node. + * + * @param string|Name|Expr $class Class name + * @param array $args Constructor arguments + * + * @return Expr\New_ + */ + public function new($class, array $args = []) : Expr\New_ { + return new Expr\New_( + BuilderHelpers::normalizeNameOrExpr($class), + $this->args($args) + ); + } + + /** + * Creates a constant fetch node. + * + * @param string|Name $name Constant name + * + * @return Expr\ConstFetch + */ + public function constFetch($name) : Expr\ConstFetch { + return new Expr\ConstFetch(BuilderHelpers::normalizeName($name)); + } + + /** + * Creates a property fetch node. + * + * @param Expr $var Variable holding object + * @param string|Identifier|Expr $name Property name + * + * @return Expr\PropertyFetch + */ + public function propertyFetch(Expr $var, $name) : Expr\PropertyFetch { + return new Expr\PropertyFetch($var, BuilderHelpers::normalizeIdentifierOrExpr($name)); + } + + /** + * Creates a class constant fetch node. + * + * @param string|Name|Expr $class Class name + * @param string|Identifier $name Constant name + * + * @return Expr\ClassConstFetch + */ + public function classConstFetch($class, $name): Expr\ClassConstFetch { + return new Expr\ClassConstFetch( + BuilderHelpers::normalizeNameOrExpr($class), + BuilderHelpers::normalizeIdentifier($name) + ); + } + + /** + * Creates nested Concat nodes from a list of expressions. + * + * @param Expr|string ...$exprs Expressions or literal strings + * + * @return Concat + */ + public function concat(...$exprs) : Concat { + $numExprs = count($exprs); + if ($numExprs < 2) { + throw new \LogicException('Expected at least two expressions'); + } + + $lastConcat = $this->normalizeStringExpr($exprs[0]); + for ($i = 1; $i < $numExprs; $i++) { + $lastConcat = new Concat($lastConcat, $this->normalizeStringExpr($exprs[$i])); + } + return $lastConcat; + } + + /** + * @param string|Expr $expr + * @return Expr + */ + private function normalizeStringExpr($expr) : Expr { + if ($expr instanceof Expr) { + return $expr; + } + + if (\is_string($expr)) { + return new String_($expr); + } + + throw new \LogicException('Expected string or Expr'); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/BuilderHelpers.php b/vendor/nikic/php-parser/lib/PhpParser/BuilderHelpers.php new file mode 100644 index 0000000000..b8839db322 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/BuilderHelpers.php @@ -0,0 +1,322 @@ +getNode(); + } + + if ($node instanceof Node) { + return $node; + } + + throw new \LogicException('Expected node or builder object'); + } + + /** + * Normalizes a node to a statement. + * + * Expressions are wrapped in a Stmt\Expression node. + * + * @param Node|Builder $node The node to normalize + * + * @return Stmt The normalized statement node + */ + public static function normalizeStmt($node) : Stmt { + $node = self::normalizeNode($node); + if ($node instanceof Stmt) { + return $node; + } + + if ($node instanceof Expr) { + return new Stmt\Expression($node); + } + + throw new \LogicException('Expected statement or expression node'); + } + + /** + * Normalizes strings to Identifier. + * + * @param string|Identifier $name The identifier to normalize + * + * @return Identifier The normalized identifier + */ + public static function normalizeIdentifier($name) : Identifier { + if ($name instanceof Identifier) { + return $name; + } + + if (\is_string($name)) { + return new Identifier($name); + } + + throw new \LogicException('Expected string or instance of Node\Identifier'); + } + + /** + * Normalizes strings to Identifier, also allowing expressions. + * + * @param string|Identifier|Expr $name The identifier to normalize + * + * @return Identifier|Expr The normalized identifier or expression + */ + public static function normalizeIdentifierOrExpr($name) { + if ($name instanceof Identifier || $name instanceof Expr) { + return $name; + } + + if (\is_string($name)) { + return new Identifier($name); + } + + throw new \LogicException('Expected string or instance of Node\Identifier or Node\Expr'); + } + + /** + * Normalizes a name: Converts string names to Name nodes. + * + * @param Name|string $name The name to normalize + * + * @return Name The normalized name + */ + public static function normalizeName($name) : Name { + if ($name instanceof Name) { + return $name; + } + + if (is_string($name)) { + if (!$name) { + throw new \LogicException('Name cannot be empty'); + } + + if ($name[0] === '\\') { + return new Name\FullyQualified(substr($name, 1)); + } + + if (0 === strpos($name, 'namespace\\')) { + return new Name\Relative(substr($name, strlen('namespace\\'))); + } + + return new Name($name); + } + + throw new \LogicException('Name must be a string or an instance of Node\Name'); + } + + /** + * Normalizes a name: Converts string names to Name nodes, while also allowing expressions. + * + * @param Expr|Name|string $name The name to normalize + * + * @return Name|Expr The normalized name or expression + */ + public static function normalizeNameOrExpr($name) { + if ($name instanceof Expr) { + return $name; + } + + if (!is_string($name) && !($name instanceof Name)) { + throw new \LogicException( + 'Name must be a string or an instance of Node\Name or Node\Expr' + ); + } + + return self::normalizeName($name); + } + + /** + * Normalizes a type: Converts plain-text type names into proper AST representation. + * + * In particular, builtin types become Identifiers, custom types become Names and nullables + * are wrapped in NullableType nodes. + * + * @param string|Name|Identifier|ComplexType $type The type to normalize + * + * @return Name|Identifier|ComplexType The normalized type + */ + public static function normalizeType($type) { + if (!is_string($type)) { + if ( + !$type instanceof Name && !$type instanceof Identifier && + !$type instanceof ComplexType + ) { + throw new \LogicException( + 'Type must be a string, or an instance of Name, Identifier or ComplexType' + ); + } + return $type; + } + + $nullable = false; + if (strlen($type) > 0 && $type[0] === '?') { + $nullable = true; + $type = substr($type, 1); + } + + $builtinTypes = [ + 'array', 'callable', 'string', 'int', 'float', 'bool', 'iterable', 'void', 'object', 'mixed', 'never', + ]; + + $lowerType = strtolower($type); + if (in_array($lowerType, $builtinTypes)) { + $type = new Identifier($lowerType); + } else { + $type = self::normalizeName($type); + } + + $notNullableTypes = [ + 'void', 'mixed', 'never', + ]; + if ($nullable && in_array((string) $type, $notNullableTypes)) { + throw new \LogicException(sprintf('%s type cannot be nullable', $type)); + } + + return $nullable ? new NullableType($type) : $type; + } + + /** + * Normalizes a value: Converts nulls, booleans, integers, + * floats, strings and arrays into their respective nodes + * + * @param Node\Expr|bool|null|int|float|string|array $value The value to normalize + * + * @return Expr The normalized value + */ + public static function normalizeValue($value) : Expr { + if ($value instanceof Node\Expr) { + return $value; + } + + if (is_null($value)) { + return new Expr\ConstFetch( + new Name('null') + ); + } + + if (is_bool($value)) { + return new Expr\ConstFetch( + new Name($value ? 'true' : 'false') + ); + } + + if (is_int($value)) { + return new Scalar\LNumber($value); + } + + if (is_float($value)) { + return new Scalar\DNumber($value); + } + + if (is_string($value)) { + return new Scalar\String_($value); + } + + if (is_array($value)) { + $items = []; + $lastKey = -1; + foreach ($value as $itemKey => $itemValue) { + // for consecutive, numeric keys don't generate keys + if (null !== $lastKey && ++$lastKey === $itemKey) { + $items[] = new Expr\ArrayItem( + self::normalizeValue($itemValue) + ); + } else { + $lastKey = null; + $items[] = new Expr\ArrayItem( + self::normalizeValue($itemValue), + self::normalizeValue($itemKey) + ); + } + } + + return new Expr\Array_($items); + } + + throw new \LogicException('Invalid value'); + } + + /** + * Normalizes a doc comment: Converts plain strings to PhpParser\Comment\Doc. + * + * @param Comment\Doc|string $docComment The doc comment to normalize + * + * @return Comment\Doc The normalized doc comment + */ + public static function normalizeDocComment($docComment) : Comment\Doc { + if ($docComment instanceof Comment\Doc) { + return $docComment; + } + + if (is_string($docComment)) { + return new Comment\Doc($docComment); + } + + throw new \LogicException('Doc comment must be a string or an instance of PhpParser\Comment\Doc'); + } + + /** + * Normalizes a attribute: Converts attribute to the Attribute Group if needed. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return Node\AttributeGroup The Attribute Group + */ + public static function normalizeAttribute($attribute) : Node\AttributeGroup + { + if ($attribute instanceof Node\AttributeGroup) { + return $attribute; + } + + if (!($attribute instanceof Node\Attribute)) { + throw new \LogicException('Attribute must be an instance of PhpParser\Node\Attribute or PhpParser\Node\AttributeGroup'); + } + + return new Node\AttributeGroup([$attribute]); + } + + /** + * Adds a modifier and returns new modifier bitmask. + * + * @param int $modifiers Existing modifiers + * @param int $modifier Modifier to set + * + * @return int New modifiers + */ + public static function addModifier(int $modifiers, int $modifier) : int { + Stmt\Class_::verifyModifier($modifiers, $modifier); + return $modifiers | $modifier; + } + + /** + * Adds a modifier and returns new modifier bitmask. + * @return int New modifiers + */ + public static function addClassModifier(int $existingModifiers, int $modifierToSet) : int { + Stmt\Class_::verifyClassModifier($existingModifiers, $modifierToSet); + return $existingModifiers | $modifierToSet; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Comment.php b/vendor/nikic/php-parser/lib/PhpParser/Comment.php new file mode 100644 index 0000000000..61e98d3dca --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Comment.php @@ -0,0 +1,239 @@ +text = $text; + $this->startLine = $startLine; + $this->startFilePos = $startFilePos; + $this->startTokenPos = $startTokenPos; + $this->endLine = $endLine; + $this->endFilePos = $endFilePos; + $this->endTokenPos = $endTokenPos; + } + + /** + * Gets the comment text. + * + * @return string The comment text (including comment delimiters like /*) + */ + public function getText() : string { + return $this->text; + } + + /** + * Gets the line number the comment started on. + * + * @return int Line number (or -1 if not available) + */ + public function getStartLine() : int { + return $this->startLine; + } + + /** + * Gets the file offset the comment started on. + * + * @return int File offset (or -1 if not available) + */ + public function getStartFilePos() : int { + return $this->startFilePos; + } + + /** + * Gets the token offset the comment started on. + * + * @return int Token offset (or -1 if not available) + */ + public function getStartTokenPos() : int { + return $this->startTokenPos; + } + + /** + * Gets the line number the comment ends on. + * + * @return int Line number (or -1 if not available) + */ + public function getEndLine() : int { + return $this->endLine; + } + + /** + * Gets the file offset the comment ends on. + * + * @return int File offset (or -1 if not available) + */ + public function getEndFilePos() : int { + return $this->endFilePos; + } + + /** + * Gets the token offset the comment ends on. + * + * @return int Token offset (or -1 if not available) + */ + public function getEndTokenPos() : int { + return $this->endTokenPos; + } + + /** + * Gets the line number the comment started on. + * + * @deprecated Use getStartLine() instead + * + * @return int Line number + */ + public function getLine() : int { + return $this->startLine; + } + + /** + * Gets the file offset the comment started on. + * + * @deprecated Use getStartFilePos() instead + * + * @return int File offset + */ + public function getFilePos() : int { + return $this->startFilePos; + } + + /** + * Gets the token offset the comment started on. + * + * @deprecated Use getStartTokenPos() instead + * + * @return int Token offset + */ + public function getTokenPos() : int { + return $this->startTokenPos; + } + + /** + * Gets the comment text. + * + * @return string The comment text (including comment delimiters like /*) + */ + public function __toString() : string { + return $this->text; + } + + /** + * Gets the reformatted comment text. + * + * "Reformatted" here means that we try to clean up the whitespace at the + * starts of the lines. This is necessary because we receive the comments + * without trailing whitespace on the first line, but with trailing whitespace + * on all subsequent lines. + * + * @return mixed|string + */ + public function getReformattedText() { + $text = trim($this->text); + $newlinePos = strpos($text, "\n"); + if (false === $newlinePos) { + // Single line comments don't need further processing + return $text; + } elseif (preg_match('((*BSR_ANYCRLF)(*ANYCRLF)^.*(?:\R\s+\*.*)+$)', $text)) { + // Multi line comment of the type + // + // /* + // * Some text. + // * Some more text. + // */ + // + // is handled by replacing the whitespace sequences before the * by a single space + return preg_replace('(^\s+\*)m', ' *', $this->text); + } elseif (preg_match('(^/\*\*?\s*[\r\n])', $text) && preg_match('(\n(\s*)\*/$)', $text, $matches)) { + // Multi line comment of the type + // + // /* + // Some text. + // Some more text. + // */ + // + // is handled by removing the whitespace sequence on the line before the closing + // */ on all lines. So if the last line is " */", then " " is removed at the + // start of all lines. + return preg_replace('(^' . preg_quote($matches[1]) . ')m', '', $text); + } elseif (preg_match('(^/\*\*?\s*(?!\s))', $text, $matches)) { + // Multi line comment of the type + // + // /* Some text. + // Some more text. + // Indented text. + // Even more text. */ + // + // is handled by removing the difference between the shortest whitespace prefix on all + // lines and the length of the "/* " opening sequence. + $prefixLen = $this->getShortestWhitespacePrefixLen(substr($text, $newlinePos + 1)); + $removeLen = $prefixLen - strlen($matches[0]); + return preg_replace('(^\s{' . $removeLen . '})m', '', $text); + } + + // No idea how to format this comment, so simply return as is + return $text; + } + + /** + * Get length of shortest whitespace prefix (at the start of a line). + * + * If there is a line with no prefix whitespace, 0 is a valid return value. + * + * @param string $str String to check + * @return int Length in characters. Tabs count as single characters. + */ + private function getShortestWhitespacePrefixLen(string $str) : int { + $lines = explode("\n", $str); + $shortestPrefixLen = \INF; + foreach ($lines as $line) { + preg_match('(^\s*)', $line, $matches); + $prefixLen = strlen($matches[0]); + if ($prefixLen < $shortestPrefixLen) { + $shortestPrefixLen = $prefixLen; + } + } + return $shortestPrefixLen; + } + + /** + * @return array + * @psalm-return array{nodeType:string, text:mixed, line:mixed, filePos:mixed} + */ + public function jsonSerialize() : array { + // Technically not a node, but we make it look like one anyway + $type = $this instanceof Comment\Doc ? 'Comment_Doc' : 'Comment'; + return [ + 'nodeType' => $type, + 'text' => $this->text, + // TODO: Rename these to include "start". + 'line' => $this->startLine, + 'filePos' => $this->startFilePos, + 'tokenPos' => $this->startTokenPos, + 'endLine' => $this->endLine, + 'endFilePos' => $this->endFilePos, + 'endTokenPos' => $this->endTokenPos, + ]; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Comment/Doc.php b/vendor/nikic/php-parser/lib/PhpParser/Comment/Doc.php new file mode 100644 index 0000000000..a9db6128f4 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Comment/Doc.php @@ -0,0 +1,7 @@ +fallbackEvaluator = $fallbackEvaluator ?? function(Expr $expr) { + throw new ConstExprEvaluationException( + "Expression of type {$expr->getType()} cannot be evaluated" + ); + }; + } + + /** + * Silently evaluates a constant expression into a PHP value. + * + * Thrown Errors, warnings or notices will be converted into a ConstExprEvaluationException. + * The original source of the exception is available through getPrevious(). + * + * If some part of the expression cannot be evaluated, the fallback evaluator passed to the + * constructor will be invoked. By default, if no fallback is provided, an exception of type + * ConstExprEvaluationException is thrown. + * + * See class doc comment for caveats and limitations. + * + * @param Expr $expr Constant expression to evaluate + * @return mixed Result of evaluation + * + * @throws ConstExprEvaluationException if the expression cannot be evaluated or an error occurred + */ + public function evaluateSilently(Expr $expr) { + set_error_handler(function($num, $str, $file, $line) { + throw new \ErrorException($str, 0, $num, $file, $line); + }); + + try { + return $this->evaluate($expr); + } catch (\Throwable $e) { + if (!$e instanceof ConstExprEvaluationException) { + $e = new ConstExprEvaluationException( + "An error occurred during constant expression evaluation", 0, $e); + } + throw $e; + } finally { + restore_error_handler(); + } + } + + /** + * Directly evaluates a constant expression into a PHP value. + * + * May generate Error exceptions, warnings or notices. Use evaluateSilently() to convert these + * into a ConstExprEvaluationException. + * + * If some part of the expression cannot be evaluated, the fallback evaluator passed to the + * constructor will be invoked. By default, if no fallback is provided, an exception of type + * ConstExprEvaluationException is thrown. + * + * See class doc comment for caveats and limitations. + * + * @param Expr $expr Constant expression to evaluate + * @return mixed Result of evaluation + * + * @throws ConstExprEvaluationException if the expression cannot be evaluated + */ + public function evaluateDirectly(Expr $expr) { + return $this->evaluate($expr); + } + + private function evaluate(Expr $expr) { + if ($expr instanceof Scalar\LNumber + || $expr instanceof Scalar\DNumber + || $expr instanceof Scalar\String_ + ) { + return $expr->value; + } + + if ($expr instanceof Expr\Array_) { + return $this->evaluateArray($expr); + } + + // Unary operators + if ($expr instanceof Expr\UnaryPlus) { + return +$this->evaluate($expr->expr); + } + if ($expr instanceof Expr\UnaryMinus) { + return -$this->evaluate($expr->expr); + } + if ($expr instanceof Expr\BooleanNot) { + return !$this->evaluate($expr->expr); + } + if ($expr instanceof Expr\BitwiseNot) { + return ~$this->evaluate($expr->expr); + } + + if ($expr instanceof Expr\BinaryOp) { + return $this->evaluateBinaryOp($expr); + } + + if ($expr instanceof Expr\Ternary) { + return $this->evaluateTernary($expr); + } + + if ($expr instanceof Expr\ArrayDimFetch && null !== $expr->dim) { + return $this->evaluate($expr->var)[$this->evaluate($expr->dim)]; + } + + if ($expr instanceof Expr\ConstFetch) { + return $this->evaluateConstFetch($expr); + } + + return ($this->fallbackEvaluator)($expr); + } + + private function evaluateArray(Expr\Array_ $expr) { + $array = []; + foreach ($expr->items as $item) { + if (null !== $item->key) { + $array[$this->evaluate($item->key)] = $this->evaluate($item->value); + } elseif ($item->unpack) { + $array = array_merge($array, $this->evaluate($item->value)); + } else { + $array[] = $this->evaluate($item->value); + } + } + return $array; + } + + private function evaluateTernary(Expr\Ternary $expr) { + if (null === $expr->if) { + return $this->evaluate($expr->cond) ?: $this->evaluate($expr->else); + } + + return $this->evaluate($expr->cond) + ? $this->evaluate($expr->if) + : $this->evaluate($expr->else); + } + + private function evaluateBinaryOp(Expr\BinaryOp $expr) { + if ($expr instanceof Expr\BinaryOp\Coalesce + && $expr->left instanceof Expr\ArrayDimFetch + ) { + // This needs to be special cased to respect BP_VAR_IS fetch semantics + return $this->evaluate($expr->left->var)[$this->evaluate($expr->left->dim)] + ?? $this->evaluate($expr->right); + } + + // The evaluate() calls are repeated in each branch, because some of the operators are + // short-circuiting and evaluating the RHS in advance may be illegal in that case + $l = $expr->left; + $r = $expr->right; + switch ($expr->getOperatorSigil()) { + case '&': return $this->evaluate($l) & $this->evaluate($r); + case '|': return $this->evaluate($l) | $this->evaluate($r); + case '^': return $this->evaluate($l) ^ $this->evaluate($r); + case '&&': return $this->evaluate($l) && $this->evaluate($r); + case '||': return $this->evaluate($l) || $this->evaluate($r); + case '??': return $this->evaluate($l) ?? $this->evaluate($r); + case '.': return $this->evaluate($l) . $this->evaluate($r); + case '/': return $this->evaluate($l) / $this->evaluate($r); + case '==': return $this->evaluate($l) == $this->evaluate($r); + case '>': return $this->evaluate($l) > $this->evaluate($r); + case '>=': return $this->evaluate($l) >= $this->evaluate($r); + case '===': return $this->evaluate($l) === $this->evaluate($r); + case 'and': return $this->evaluate($l) and $this->evaluate($r); + case 'or': return $this->evaluate($l) or $this->evaluate($r); + case 'xor': return $this->evaluate($l) xor $this->evaluate($r); + case '-': return $this->evaluate($l) - $this->evaluate($r); + case '%': return $this->evaluate($l) % $this->evaluate($r); + case '*': return $this->evaluate($l) * $this->evaluate($r); + case '!=': return $this->evaluate($l) != $this->evaluate($r); + case '!==': return $this->evaluate($l) !== $this->evaluate($r); + case '+': return $this->evaluate($l) + $this->evaluate($r); + case '**': return $this->evaluate($l) ** $this->evaluate($r); + case '<<': return $this->evaluate($l) << $this->evaluate($r); + case '>>': return $this->evaluate($l) >> $this->evaluate($r); + case '<': return $this->evaluate($l) < $this->evaluate($r); + case '<=': return $this->evaluate($l) <= $this->evaluate($r); + case '<=>': return $this->evaluate($l) <=> $this->evaluate($r); + } + + throw new \Exception('Should not happen'); + } + + private function evaluateConstFetch(Expr\ConstFetch $expr) { + $name = $expr->name->toLowerString(); + switch ($name) { + case 'null': return null; + case 'false': return false; + case 'true': return true; + } + + return ($this->fallbackEvaluator)($expr); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Error.php b/vendor/nikic/php-parser/lib/PhpParser/Error.php new file mode 100644 index 0000000000..d1fb959d19 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Error.php @@ -0,0 +1,180 @@ +rawMessage = $message; + if (is_array($attributes)) { + $this->attributes = $attributes; + } else { + $this->attributes = ['startLine' => $attributes]; + } + $this->updateMessage(); + } + + /** + * Gets the error message + * + * @return string Error message + */ + public function getRawMessage() : string { + return $this->rawMessage; + } + + /** + * Gets the line the error starts in. + * + * @return int Error start line + */ + public function getStartLine() : int { + return $this->attributes['startLine'] ?? -1; + } + + /** + * Gets the line the error ends in. + * + * @return int Error end line + */ + public function getEndLine() : int { + return $this->attributes['endLine'] ?? -1; + } + + /** + * Gets the attributes of the node/token the error occurred at. + * + * @return array + */ + public function getAttributes() : array { + return $this->attributes; + } + + /** + * Sets the attributes of the node/token the error occurred at. + * + * @param array $attributes + */ + public function setAttributes(array $attributes) { + $this->attributes = $attributes; + $this->updateMessage(); + } + + /** + * Sets the line of the PHP file the error occurred in. + * + * @param string $message Error message + */ + public function setRawMessage(string $message) { + $this->rawMessage = $message; + $this->updateMessage(); + } + + /** + * Sets the line the error starts in. + * + * @param int $line Error start line + */ + public function setStartLine(int $line) { + $this->attributes['startLine'] = $line; + $this->updateMessage(); + } + + /** + * Returns whether the error has start and end column information. + * + * For column information enable the startFilePos and endFilePos in the lexer options. + * + * @return bool + */ + public function hasColumnInfo() : bool { + return isset($this->attributes['startFilePos'], $this->attributes['endFilePos']); + } + + /** + * Gets the start column (1-based) into the line where the error started. + * + * @param string $code Source code of the file + * @return int + */ + public function getStartColumn(string $code) : int { + if (!$this->hasColumnInfo()) { + throw new \RuntimeException('Error does not have column information'); + } + + return $this->toColumn($code, $this->attributes['startFilePos']); + } + + /** + * Gets the end column (1-based) into the line where the error ended. + * + * @param string $code Source code of the file + * @return int + */ + public function getEndColumn(string $code) : int { + if (!$this->hasColumnInfo()) { + throw new \RuntimeException('Error does not have column information'); + } + + return $this->toColumn($code, $this->attributes['endFilePos']); + } + + /** + * Formats message including line and column information. + * + * @param string $code Source code associated with the error, for calculation of the columns + * + * @return string Formatted message + */ + public function getMessageWithColumnInfo(string $code) : string { + return sprintf( + '%s from %d:%d to %d:%d', $this->getRawMessage(), + $this->getStartLine(), $this->getStartColumn($code), + $this->getEndLine(), $this->getEndColumn($code) + ); + } + + /** + * Converts a file offset into a column. + * + * @param string $code Source code that $pos indexes into + * @param int $pos 0-based position in $code + * + * @return int 1-based column (relative to start of line) + */ + private function toColumn(string $code, int $pos) : int { + if ($pos > strlen($code)) { + throw new \RuntimeException('Invalid position information'); + } + + $lineStartPos = strrpos($code, "\n", $pos - strlen($code)); + if (false === $lineStartPos) { + $lineStartPos = -1; + } + + return $pos - $lineStartPos; + } + + /** + * Updates the exception message after a change to rawMessage or rawLine. + */ + protected function updateMessage() { + $this->message = $this->rawMessage; + + if (-1 === $this->getStartLine()) { + $this->message .= ' on unknown line'; + } else { + $this->message .= ' on line ' . $this->getStartLine(); + } + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/ErrorHandler.php b/vendor/nikic/php-parser/lib/PhpParser/ErrorHandler.php new file mode 100644 index 0000000000..d620e74536 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/ErrorHandler.php @@ -0,0 +1,13 @@ +errors[] = $error; + } + + /** + * Get collected errors. + * + * @return Error[] + */ + public function getErrors() : array { + return $this->errors; + } + + /** + * Check whether there are any errors. + * + * @return bool + */ + public function hasErrors() : bool { + return !empty($this->errors); + } + + /** + * Reset/clear collected errors. + */ + public function clearErrors() { + $this->errors = []; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/ErrorHandler/Throwing.php b/vendor/nikic/php-parser/lib/PhpParser/ErrorHandler/Throwing.php new file mode 100644 index 0000000000..aeee989b1a --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/ErrorHandler/Throwing.php @@ -0,0 +1,18 @@ +type = $type; + $this->old = $old; + $this->new = $new; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Internal/Differ.php b/vendor/nikic/php-parser/lib/PhpParser/Internal/Differ.php new file mode 100644 index 0000000000..7f218c74fe --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Internal/Differ.php @@ -0,0 +1,164 @@ +isEqual = $isEqual; + } + + /** + * Calculate diff (edit script) from $old to $new. + * + * @param array $old Original array + * @param array $new New array + * + * @return DiffElem[] Diff (edit script) + */ + public function diff(array $old, array $new) { + list($trace, $x, $y) = $this->calculateTrace($old, $new); + return $this->extractDiff($trace, $x, $y, $old, $new); + } + + /** + * Calculate diff, including "replace" operations. + * + * If a sequence of remove operations is followed by the same number of add operations, these + * will be coalesced into replace operations. + * + * @param array $old Original array + * @param array $new New array + * + * @return DiffElem[] Diff (edit script), including replace operations + */ + public function diffWithReplacements(array $old, array $new) { + return $this->coalesceReplacements($this->diff($old, $new)); + } + + private function calculateTrace(array $a, array $b) { + $n = \count($a); + $m = \count($b); + $max = $n + $m; + $v = [1 => 0]; + $trace = []; + for ($d = 0; $d <= $max; $d++) { + $trace[] = $v; + for ($k = -$d; $k <= $d; $k += 2) { + if ($k === -$d || ($k !== $d && $v[$k-1] < $v[$k+1])) { + $x = $v[$k+1]; + } else { + $x = $v[$k-1] + 1; + } + + $y = $x - $k; + while ($x < $n && $y < $m && ($this->isEqual)($a[$x], $b[$y])) { + $x++; + $y++; + } + + $v[$k] = $x; + if ($x >= $n && $y >= $m) { + return [$trace, $x, $y]; + } + } + } + throw new \Exception('Should not happen'); + } + + private function extractDiff(array $trace, int $x, int $y, array $a, array $b) { + $result = []; + for ($d = \count($trace) - 1; $d >= 0; $d--) { + $v = $trace[$d]; + $k = $x - $y; + + if ($k === -$d || ($k !== $d && $v[$k-1] < $v[$k+1])) { + $prevK = $k + 1; + } else { + $prevK = $k - 1; + } + + $prevX = $v[$prevK]; + $prevY = $prevX - $prevK; + + while ($x > $prevX && $y > $prevY) { + $result[] = new DiffElem(DiffElem::TYPE_KEEP, $a[$x-1], $b[$y-1]); + $x--; + $y--; + } + + if ($d === 0) { + break; + } + + while ($x > $prevX) { + $result[] = new DiffElem(DiffElem::TYPE_REMOVE, $a[$x-1], null); + $x--; + } + + while ($y > $prevY) { + $result[] = new DiffElem(DiffElem::TYPE_ADD, null, $b[$y-1]); + $y--; + } + } + return array_reverse($result); + } + + /** + * Coalesce equal-length sequences of remove+add into a replace operation. + * + * @param DiffElem[] $diff + * @return DiffElem[] + */ + private function coalesceReplacements(array $diff) { + $newDiff = []; + $c = \count($diff); + for ($i = 0; $i < $c; $i++) { + $diffType = $diff[$i]->type; + if ($diffType !== DiffElem::TYPE_REMOVE) { + $newDiff[] = $diff[$i]; + continue; + } + + $j = $i; + while ($j < $c && $diff[$j]->type === DiffElem::TYPE_REMOVE) { + $j++; + } + + $k = $j; + while ($k < $c && $diff[$k]->type === DiffElem::TYPE_ADD) { + $k++; + } + + if ($j - $i === $k - $j) { + $len = $j - $i; + for ($n = 0; $n < $len; $n++) { + $newDiff[] = new DiffElem( + DiffElem::TYPE_REPLACE, $diff[$i + $n]->old, $diff[$j + $n]->new + ); + } + } else { + for (; $i < $k; $i++) { + $newDiff[] = $diff[$i]; + } + } + $i = $k - 1; + } + return $newDiff; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Internal/PrintableNewAnonClassNode.php b/vendor/nikic/php-parser/lib/PhpParser/Internal/PrintableNewAnonClassNode.php new file mode 100644 index 0000000000..3eeac04a41 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Internal/PrintableNewAnonClassNode.php @@ -0,0 +1,61 @@ +attrGroups = $attrGroups; + $this->args = $args; + $this->extends = $extends; + $this->implements = $implements; + $this->stmts = $stmts; + } + + public static function fromNewNode(Expr\New_ $newNode) { + $class = $newNode->class; + assert($class instanceof Node\Stmt\Class_); + // We don't assert that $class->name is null here, to allow consumers to assign unique names + // to anonymous classes for their own purposes. We simplify ignore the name here. + return new self( + $class->attrGroups, $newNode->args, $class->extends, $class->implements, + $class->stmts, $newNode->getAttributes() + ); + } + + public function getType() : string { + return 'Expr_PrintableNewAnonClass'; + } + + public function getSubNodeNames() : array { + return ['attrGroups', 'args', 'extends', 'implements', 'stmts']; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Internal/TokenStream.php b/vendor/nikic/php-parser/lib/PhpParser/Internal/TokenStream.php new file mode 100644 index 0000000000..84c0175ec5 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Internal/TokenStream.php @@ -0,0 +1,281 @@ +tokens = $tokens; + $this->indentMap = $this->calcIndentMap(); + } + + /** + * Whether the given position is immediately surrounded by parenthesis. + * + * @param int $startPos Start position + * @param int $endPos End position + * + * @return bool + */ + public function haveParens(int $startPos, int $endPos) : bool { + return $this->haveTokenImmediatelyBefore($startPos, '(') + && $this->haveTokenImmediatelyAfter($endPos, ')'); + } + + /** + * Whether the given position is immediately surrounded by braces. + * + * @param int $startPos Start position + * @param int $endPos End position + * + * @return bool + */ + public function haveBraces(int $startPos, int $endPos) : bool { + return ($this->haveTokenImmediatelyBefore($startPos, '{') + || $this->haveTokenImmediatelyBefore($startPos, T_CURLY_OPEN)) + && $this->haveTokenImmediatelyAfter($endPos, '}'); + } + + /** + * Check whether the position is directly preceded by a certain token type. + * + * During this check whitespace and comments are skipped. + * + * @param int $pos Position before which the token should occur + * @param int|string $expectedTokenType Token to check for + * + * @return bool Whether the expected token was found + */ + public function haveTokenImmediatelyBefore(int $pos, $expectedTokenType) : bool { + $tokens = $this->tokens; + $pos--; + for (; $pos >= 0; $pos--) { + $tokenType = $tokens[$pos][0]; + if ($tokenType === $expectedTokenType) { + return true; + } + if ($tokenType !== \T_WHITESPACE + && $tokenType !== \T_COMMENT && $tokenType !== \T_DOC_COMMENT) { + break; + } + } + return false; + } + + /** + * Check whether the position is directly followed by a certain token type. + * + * During this check whitespace and comments are skipped. + * + * @param int $pos Position after which the token should occur + * @param int|string $expectedTokenType Token to check for + * + * @return bool Whether the expected token was found + */ + public function haveTokenImmediatelyAfter(int $pos, $expectedTokenType) : bool { + $tokens = $this->tokens; + $pos++; + for (; $pos < \count($tokens); $pos++) { + $tokenType = $tokens[$pos][0]; + if ($tokenType === $expectedTokenType) { + return true; + } + if ($tokenType !== \T_WHITESPACE + && $tokenType !== \T_COMMENT && $tokenType !== \T_DOC_COMMENT) { + break; + } + } + return false; + } + + public function skipLeft(int $pos, $skipTokenType) { + $tokens = $this->tokens; + + $pos = $this->skipLeftWhitespace($pos); + if ($skipTokenType === \T_WHITESPACE) { + return $pos; + } + + if ($tokens[$pos][0] !== $skipTokenType) { + // Shouldn't happen. The skip token MUST be there + throw new \Exception('Encountered unexpected token'); + } + $pos--; + + return $this->skipLeftWhitespace($pos); + } + + public function skipRight(int $pos, $skipTokenType) { + $tokens = $this->tokens; + + $pos = $this->skipRightWhitespace($pos); + if ($skipTokenType === \T_WHITESPACE) { + return $pos; + } + + if ($tokens[$pos][0] !== $skipTokenType) { + // Shouldn't happen. The skip token MUST be there + throw new \Exception('Encountered unexpected token'); + } + $pos++; + + return $this->skipRightWhitespace($pos); + } + + /** + * Return first non-whitespace token position smaller or equal to passed position. + * + * @param int $pos Token position + * @return int Non-whitespace token position + */ + public function skipLeftWhitespace(int $pos) { + $tokens = $this->tokens; + for (; $pos >= 0; $pos--) { + $type = $tokens[$pos][0]; + if ($type !== \T_WHITESPACE && $type !== \T_COMMENT && $type !== \T_DOC_COMMENT) { + break; + } + } + return $pos; + } + + /** + * Return first non-whitespace position greater or equal to passed position. + * + * @param int $pos Token position + * @return int Non-whitespace token position + */ + public function skipRightWhitespace(int $pos) { + $tokens = $this->tokens; + for ($count = \count($tokens); $pos < $count; $pos++) { + $type = $tokens[$pos][0]; + if ($type !== \T_WHITESPACE && $type !== \T_COMMENT && $type !== \T_DOC_COMMENT) { + break; + } + } + return $pos; + } + + public function findRight(int $pos, $findTokenType) { + $tokens = $this->tokens; + for ($count = \count($tokens); $pos < $count; $pos++) { + $type = $tokens[$pos][0]; + if ($type === $findTokenType) { + return $pos; + } + } + return -1; + } + + /** + * Whether the given position range contains a certain token type. + * + * @param int $startPos Starting position (inclusive) + * @param int $endPos Ending position (exclusive) + * @param int|string $tokenType Token type to look for + * @return bool Whether the token occurs in the given range + */ + public function haveTokenInRange(int $startPos, int $endPos, $tokenType) { + $tokens = $this->tokens; + for ($pos = $startPos; $pos < $endPos; $pos++) { + if ($tokens[$pos][0] === $tokenType) { + return true; + } + } + return false; + } + + public function haveBracesInRange(int $startPos, int $endPos) { + return $this->haveTokenInRange($startPos, $endPos, '{') + || $this->haveTokenInRange($startPos, $endPos, T_CURLY_OPEN) + || $this->haveTokenInRange($startPos, $endPos, '}'); + } + + /** + * Get indentation before token position. + * + * @param int $pos Token position + * + * @return int Indentation depth (in spaces) + */ + public function getIndentationBefore(int $pos) : int { + return $this->indentMap[$pos]; + } + + /** + * Get the code corresponding to a token offset range, optionally adjusted for indentation. + * + * @param int $from Token start position (inclusive) + * @param int $to Token end position (exclusive) + * @param int $indent By how much the code should be indented (can be negative as well) + * + * @return string Code corresponding to token range, adjusted for indentation + */ + public function getTokenCode(int $from, int $to, int $indent) : string { + $tokens = $this->tokens; + $result = ''; + for ($pos = $from; $pos < $to; $pos++) { + $token = $tokens[$pos]; + if (\is_array($token)) { + $type = $token[0]; + $content = $token[1]; + if ($type === \T_CONSTANT_ENCAPSED_STRING || $type === \T_ENCAPSED_AND_WHITESPACE) { + $result .= $content; + } else { + // TODO Handle non-space indentation + if ($indent < 0) { + $result .= str_replace("\n" . str_repeat(" ", -$indent), "\n", $content); + } elseif ($indent > 0) { + $result .= str_replace("\n", "\n" . str_repeat(" ", $indent), $content); + } else { + $result .= $content; + } + } + } else { + $result .= $token; + } + } + return $result; + } + + /** + * Precalculate the indentation at every token position. + * + * @return int[] Token position to indentation map + */ + private function calcIndentMap() { + $indentMap = []; + $indent = 0; + foreach ($this->tokens as $token) { + $indentMap[] = $indent; + + if ($token[0] === \T_WHITESPACE) { + $content = $token[1]; + $newlinePos = \strrpos($content, "\n"); + if (false !== $newlinePos) { + $indent = \strlen($content) - $newlinePos - 1; + } + } + } + + // Add a sentinel for one past end of the file + $indentMap[] = $indent; + + return $indentMap; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/JsonDecoder.php b/vendor/nikic/php-parser/lib/PhpParser/JsonDecoder.php new file mode 100644 index 0000000000..47d2003d4b --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/JsonDecoder.php @@ -0,0 +1,103 @@ +decodeRecursive($value); + } + + private function decodeRecursive($value) { + if (\is_array($value)) { + if (isset($value['nodeType'])) { + if ($value['nodeType'] === 'Comment' || $value['nodeType'] === 'Comment_Doc') { + return $this->decodeComment($value); + } + return $this->decodeNode($value); + } + return $this->decodeArray($value); + } + return $value; + } + + private function decodeArray(array $array) : array { + $decodedArray = []; + foreach ($array as $key => $value) { + $decodedArray[$key] = $this->decodeRecursive($value); + } + return $decodedArray; + } + + private function decodeNode(array $value) : Node { + $nodeType = $value['nodeType']; + if (!\is_string($nodeType)) { + throw new \RuntimeException('Node type must be a string'); + } + + $reflectionClass = $this->reflectionClassFromNodeType($nodeType); + /** @var Node $node */ + $node = $reflectionClass->newInstanceWithoutConstructor(); + + if (isset($value['attributes'])) { + if (!\is_array($value['attributes'])) { + throw new \RuntimeException('Attributes must be an array'); + } + + $node->setAttributes($this->decodeArray($value['attributes'])); + } + + foreach ($value as $name => $subNode) { + if ($name === 'nodeType' || $name === 'attributes') { + continue; + } + + $node->$name = $this->decodeRecursive($subNode); + } + + return $node; + } + + private function decodeComment(array $value) : Comment { + $className = $value['nodeType'] === 'Comment' ? Comment::class : Comment\Doc::class; + if (!isset($value['text'])) { + throw new \RuntimeException('Comment must have text'); + } + + return new $className( + $value['text'], + $value['line'] ?? -1, $value['filePos'] ?? -1, $value['tokenPos'] ?? -1, + $value['endLine'] ?? -1, $value['endFilePos'] ?? -1, $value['endTokenPos'] ?? -1 + ); + } + + private function reflectionClassFromNodeType(string $nodeType) : \ReflectionClass { + if (!isset($this->reflectionClassCache[$nodeType])) { + $className = $this->classNameFromNodeType($nodeType); + $this->reflectionClassCache[$nodeType] = new \ReflectionClass($className); + } + return $this->reflectionClassCache[$nodeType]; + } + + private function classNameFromNodeType(string $nodeType) : string { + $className = 'PhpParser\\Node\\' . strtr($nodeType, '_', '\\'); + if (class_exists($className)) { + return $className; + } + + $className .= '_'; + if (class_exists($className)) { + return $className; + } + + throw new \RuntimeException("Unknown node type \"$nodeType\""); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer.php new file mode 100644 index 0000000000..e15dd0a5d2 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer.php @@ -0,0 +1,560 @@ +defineCompatibilityTokens(); + $this->tokenMap = $this->createTokenMap(); + $this->identifierTokens = $this->createIdentifierTokenMap(); + + // map of tokens to drop while lexing (the map is only used for isset lookup, + // that's why the value is simply set to 1; the value is never actually used.) + $this->dropTokens = array_fill_keys( + [\T_WHITESPACE, \T_OPEN_TAG, \T_COMMENT, \T_DOC_COMMENT, \T_BAD_CHARACTER], 1 + ); + + $defaultAttributes = ['comments', 'startLine', 'endLine']; + $usedAttributes = array_fill_keys($options['usedAttributes'] ?? $defaultAttributes, true); + + // Create individual boolean properties to make these checks faster. + $this->attributeStartLineUsed = isset($usedAttributes['startLine']); + $this->attributeEndLineUsed = isset($usedAttributes['endLine']); + $this->attributeStartTokenPosUsed = isset($usedAttributes['startTokenPos']); + $this->attributeEndTokenPosUsed = isset($usedAttributes['endTokenPos']); + $this->attributeStartFilePosUsed = isset($usedAttributes['startFilePos']); + $this->attributeEndFilePosUsed = isset($usedAttributes['endFilePos']); + $this->attributeCommentsUsed = isset($usedAttributes['comments']); + } + + /** + * Initializes the lexer for lexing the provided source code. + * + * This function does not throw if lexing errors occur. Instead, errors may be retrieved using + * the getErrors() method. + * + * @param string $code The source code to lex + * @param ErrorHandler|null $errorHandler Error handler to use for lexing errors. Defaults to + * ErrorHandler\Throwing + */ + public function startLexing(string $code, ErrorHandler $errorHandler = null) { + if (null === $errorHandler) { + $errorHandler = new ErrorHandler\Throwing(); + } + + $this->code = $code; // keep the code around for __halt_compiler() handling + $this->pos = -1; + $this->line = 1; + $this->filePos = 0; + + // If inline HTML occurs without preceding code, treat it as if it had a leading newline. + // This ensures proper composability, because having a newline is the "safe" assumption. + $this->prevCloseTagHasNewline = true; + + $scream = ini_set('xdebug.scream', '0'); + + $this->tokens = @token_get_all($code); + $this->postprocessTokens($errorHandler); + + if (false !== $scream) { + ini_set('xdebug.scream', $scream); + } + } + + private function handleInvalidCharacterRange($start, $end, $line, ErrorHandler $errorHandler) { + $tokens = []; + for ($i = $start; $i < $end; $i++) { + $chr = $this->code[$i]; + if ($chr === "\0") { + // PHP cuts error message after null byte, so need special case + $errorMsg = 'Unexpected null byte'; + } else { + $errorMsg = sprintf( + 'Unexpected character "%s" (ASCII %d)', $chr, ord($chr) + ); + } + + $tokens[] = [\T_BAD_CHARACTER, $chr, $line]; + $errorHandler->handleError(new Error($errorMsg, [ + 'startLine' => $line, + 'endLine' => $line, + 'startFilePos' => $i, + 'endFilePos' => $i, + ])); + } + return $tokens; + } + + /** + * Check whether comment token is unterminated. + * + * @return bool + */ + private function isUnterminatedComment($token) : bool { + return ($token[0] === \T_COMMENT || $token[0] === \T_DOC_COMMENT) + && substr($token[1], 0, 2) === '/*' + && substr($token[1], -2) !== '*/'; + } + + protected function postprocessTokens(ErrorHandler $errorHandler) { + // PHP's error handling for token_get_all() is rather bad, so if we want detailed + // error information we need to compute it ourselves. Invalid character errors are + // detected by finding "gaps" in the token array. Unterminated comments are detected + // by checking if a trailing comment has a "*/" at the end. + // + // Additionally, we perform a number of canonicalizations here: + // * Use the PHP 8.0 comment format, which does not include trailing whitespace anymore. + // * Use PHP 8.0 T_NAME_* tokens. + // * Use PHP 8.1 T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG and + // T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG tokens used to disambiguate intersection types. + + $filePos = 0; + $line = 1; + $numTokens = \count($this->tokens); + for ($i = 0; $i < $numTokens; $i++) { + $token = $this->tokens[$i]; + + // Since PHP 7.4 invalid characters are represented by a T_BAD_CHARACTER token. + // In this case we only need to emit an error. + if ($token[0] === \T_BAD_CHARACTER) { + $this->handleInvalidCharacterRange($filePos, $filePos + 1, $line, $errorHandler); + } + + if ($token[0] === \T_COMMENT && substr($token[1], 0, 2) !== '/*' + && preg_match('/(\r\n|\n|\r)$/D', $token[1], $matches)) { + $trailingNewline = $matches[0]; + $token[1] = substr($token[1], 0, -strlen($trailingNewline)); + $this->tokens[$i] = $token; + if (isset($this->tokens[$i + 1]) && $this->tokens[$i + 1][0] === \T_WHITESPACE) { + // Move trailing newline into following T_WHITESPACE token, if it already exists. + $this->tokens[$i + 1][1] = $trailingNewline . $this->tokens[$i + 1][1]; + $this->tokens[$i + 1][2]--; + } else { + // Otherwise, we need to create a new T_WHITESPACE token. + array_splice($this->tokens, $i + 1, 0, [ + [\T_WHITESPACE, $trailingNewline, $line], + ]); + $numTokens++; + } + } + + // Emulate PHP 8 T_NAME_* tokens, by combining sequences of T_NS_SEPARATOR and T_STRING + // into a single token. + if (\is_array($token) + && ($token[0] === \T_NS_SEPARATOR || isset($this->identifierTokens[$token[0]]))) { + $lastWasSeparator = $token[0] === \T_NS_SEPARATOR; + $text = $token[1]; + for ($j = $i + 1; isset($this->tokens[$j]); $j++) { + if ($lastWasSeparator) { + if (!isset($this->identifierTokens[$this->tokens[$j][0]])) { + break; + } + $lastWasSeparator = false; + } else { + if ($this->tokens[$j][0] !== \T_NS_SEPARATOR) { + break; + } + $lastWasSeparator = true; + } + $text .= $this->tokens[$j][1]; + } + if ($lastWasSeparator) { + // Trailing separator is not part of the name. + $j--; + $text = substr($text, 0, -1); + } + if ($j > $i + 1) { + if ($token[0] === \T_NS_SEPARATOR) { + $type = \T_NAME_FULLY_QUALIFIED; + } else if ($token[0] === \T_NAMESPACE) { + $type = \T_NAME_RELATIVE; + } else { + $type = \T_NAME_QUALIFIED; + } + $token = [$type, $text, $line]; + array_splice($this->tokens, $i, $j - $i, [$token]); + $numTokens -= $j - $i - 1; + } + } + + if ($token === '&') { + $next = $i + 1; + while (isset($this->tokens[$next]) && $this->tokens[$next][0] === \T_WHITESPACE) { + $next++; + } + $followedByVarOrVarArg = isset($this->tokens[$next]) && + ($this->tokens[$next][0] === \T_VARIABLE || $this->tokens[$next][0] === \T_ELLIPSIS); + $this->tokens[$i] = $token = [ + $followedByVarOrVarArg + ? \T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG + : \T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG, + '&', + $line, + ]; + } + + $tokenValue = \is_string($token) ? $token : $token[1]; + $tokenLen = \strlen($tokenValue); + + if (substr($this->code, $filePos, $tokenLen) !== $tokenValue) { + // Something is missing, must be an invalid character + $nextFilePos = strpos($this->code, $tokenValue, $filePos); + $badCharTokens = $this->handleInvalidCharacterRange( + $filePos, $nextFilePos, $line, $errorHandler); + $filePos = (int) $nextFilePos; + + array_splice($this->tokens, $i, 0, $badCharTokens); + $numTokens += \count($badCharTokens); + $i += \count($badCharTokens); + } + + $filePos += $tokenLen; + $line += substr_count($tokenValue, "\n"); + } + + if ($filePos !== \strlen($this->code)) { + if (substr($this->code, $filePos, 2) === '/*') { + // Unlike PHP, HHVM will drop unterminated comments entirely + $comment = substr($this->code, $filePos); + $errorHandler->handleError(new Error('Unterminated comment', [ + 'startLine' => $line, + 'endLine' => $line + substr_count($comment, "\n"), + 'startFilePos' => $filePos, + 'endFilePos' => $filePos + \strlen($comment), + ])); + + // Emulate the PHP behavior + $isDocComment = isset($comment[3]) && $comment[3] === '*'; + $this->tokens[] = [$isDocComment ? \T_DOC_COMMENT : \T_COMMENT, $comment, $line]; + } else { + // Invalid characters at the end of the input + $badCharTokens = $this->handleInvalidCharacterRange( + $filePos, \strlen($this->code), $line, $errorHandler); + $this->tokens = array_merge($this->tokens, $badCharTokens); + } + return; + } + + if (count($this->tokens) > 0) { + // Check for unterminated comment + $lastToken = $this->tokens[count($this->tokens) - 1]; + if ($this->isUnterminatedComment($lastToken)) { + $errorHandler->handleError(new Error('Unterminated comment', [ + 'startLine' => $line - substr_count($lastToken[1], "\n"), + 'endLine' => $line, + 'startFilePos' => $filePos - \strlen($lastToken[1]), + 'endFilePos' => $filePos, + ])); + } + } + } + + /** + * Fetches the next token. + * + * The available attributes are determined by the 'usedAttributes' option, which can + * be specified in the constructor. The following attributes are supported: + * + * * 'comments' => Array of PhpParser\Comment or PhpParser\Comment\Doc instances, + * representing all comments that occurred between the previous + * non-discarded token and the current one. + * * 'startLine' => Line in which the node starts. + * * 'endLine' => Line in which the node ends. + * * 'startTokenPos' => Offset into the token array of the first token in the node. + * * 'endTokenPos' => Offset into the token array of the last token in the node. + * * 'startFilePos' => Offset into the code string of the first character that is part of the node. + * * 'endFilePos' => Offset into the code string of the last character that is part of the node. + * + * @param mixed $value Variable to store token content in + * @param mixed $startAttributes Variable to store start attributes in + * @param mixed $endAttributes Variable to store end attributes in + * + * @return int Token id + */ + public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) : int { + $startAttributes = []; + $endAttributes = []; + + while (1) { + if (isset($this->tokens[++$this->pos])) { + $token = $this->tokens[$this->pos]; + } else { + // EOF token with ID 0 + $token = "\0"; + } + + if ($this->attributeStartLineUsed) { + $startAttributes['startLine'] = $this->line; + } + if ($this->attributeStartTokenPosUsed) { + $startAttributes['startTokenPos'] = $this->pos; + } + if ($this->attributeStartFilePosUsed) { + $startAttributes['startFilePos'] = $this->filePos; + } + + if (\is_string($token)) { + $value = $token; + if (isset($token[1])) { + // bug in token_get_all + $this->filePos += 2; + $id = ord('"'); + } else { + $this->filePos += 1; + $id = ord($token); + } + } elseif (!isset($this->dropTokens[$token[0]])) { + $value = $token[1]; + $id = $this->tokenMap[$token[0]]; + if (\T_CLOSE_TAG === $token[0]) { + $this->prevCloseTagHasNewline = false !== strpos($token[1], "\n") + || false !== strpos($token[1], "\r"); + } elseif (\T_INLINE_HTML === $token[0]) { + $startAttributes['hasLeadingNewline'] = $this->prevCloseTagHasNewline; + } + + $this->line += substr_count($value, "\n"); + $this->filePos += \strlen($value); + } else { + $origLine = $this->line; + $origFilePos = $this->filePos; + $this->line += substr_count($token[1], "\n"); + $this->filePos += \strlen($token[1]); + + if (\T_COMMENT === $token[0] || \T_DOC_COMMENT === $token[0]) { + if ($this->attributeCommentsUsed) { + $comment = \T_DOC_COMMENT === $token[0] + ? new Comment\Doc($token[1], + $origLine, $origFilePos, $this->pos, + $this->line, $this->filePos - 1, $this->pos) + : new Comment($token[1], + $origLine, $origFilePos, $this->pos, + $this->line, $this->filePos - 1, $this->pos); + $startAttributes['comments'][] = $comment; + } + } + continue; + } + + if ($this->attributeEndLineUsed) { + $endAttributes['endLine'] = $this->line; + } + if ($this->attributeEndTokenPosUsed) { + $endAttributes['endTokenPos'] = $this->pos; + } + if ($this->attributeEndFilePosUsed) { + $endAttributes['endFilePos'] = $this->filePos - 1; + } + + return $id; + } + + throw new \RuntimeException('Reached end of lexer loop'); + } + + /** + * Returns the token array for current code. + * + * The token array is in the same format as provided by the + * token_get_all() function and does not discard tokens (i.e. + * whitespace and comments are included). The token position + * attributes are against this token array. + * + * @return array Array of tokens in token_get_all() format + */ + public function getTokens() : array { + return $this->tokens; + } + + /** + * Handles __halt_compiler() by returning the text after it. + * + * @return string Remaining text + */ + public function handleHaltCompiler() : string { + // text after T_HALT_COMPILER, still including (); + $textAfter = substr($this->code, $this->filePos); + + // ensure that it is followed by (); + // this simplifies the situation, by not allowing any comments + // in between of the tokens. + if (!preg_match('~^\s*\(\s*\)\s*(?:;|\?>\r?\n?)~', $textAfter, $matches)) { + throw new Error('__HALT_COMPILER must be followed by "();"'); + } + + // prevent the lexer from returning any further tokens + $this->pos = count($this->tokens); + + // return with (); removed + return substr($textAfter, strlen($matches[0])); + } + + private function defineCompatibilityTokens() { + static $compatTokensDefined = false; + if ($compatTokensDefined) { + return; + } + + $compatTokens = [ + // PHP 7.4 + 'T_BAD_CHARACTER', + 'T_FN', + 'T_COALESCE_EQUAL', + // PHP 8.0 + 'T_NAME_QUALIFIED', + 'T_NAME_FULLY_QUALIFIED', + 'T_NAME_RELATIVE', + 'T_MATCH', + 'T_NULLSAFE_OBJECT_OPERATOR', + 'T_ATTRIBUTE', + // PHP 8.1 + 'T_ENUM', + 'T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG', + 'T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG', + 'T_READONLY', + ]; + + // PHP-Parser might be used together with another library that also emulates some or all + // of these tokens. Perform a sanity-check that all already defined tokens have been + // assigned a unique ID. + $usedTokenIds = []; + foreach ($compatTokens as $token) { + if (\defined($token)) { + $tokenId = \constant($token); + $clashingToken = $usedTokenIds[$tokenId] ?? null; + if ($clashingToken !== null) { + throw new \Error(sprintf( + 'Token %s has same ID as token %s, ' . + 'you may be using a library with broken token emulation', + $token, $clashingToken + )); + } + $usedTokenIds[$tokenId] = $token; + } + } + + // Now define any tokens that have not yet been emulated. Try to assign IDs from -1 + // downwards, but skip any IDs that may already be in use. + $newTokenId = -1; + foreach ($compatTokens as $token) { + if (!\defined($token)) { + while (isset($usedTokenIds[$newTokenId])) { + $newTokenId--; + } + \define($token, $newTokenId); + $newTokenId--; + } + } + + $compatTokensDefined = true; + } + + /** + * Creates the token map. + * + * The token map maps the PHP internal token identifiers + * to the identifiers used by the Parser. Additionally it + * maps T_OPEN_TAG_WITH_ECHO to T_ECHO and T_CLOSE_TAG to ';'. + * + * @return array The token map + */ + protected function createTokenMap() : array { + $tokenMap = []; + + // 256 is the minimum possible token number, as everything below + // it is an ASCII value + for ($i = 256; $i < 1000; ++$i) { + if (\T_DOUBLE_COLON === $i) { + // T_DOUBLE_COLON is equivalent to T_PAAMAYIM_NEKUDOTAYIM + $tokenMap[$i] = Tokens::T_PAAMAYIM_NEKUDOTAYIM; + } elseif(\T_OPEN_TAG_WITH_ECHO === $i) { + // T_OPEN_TAG_WITH_ECHO with dropped T_OPEN_TAG results in T_ECHO + $tokenMap[$i] = Tokens::T_ECHO; + } elseif(\T_CLOSE_TAG === $i) { + // T_CLOSE_TAG is equivalent to ';' + $tokenMap[$i] = ord(';'); + } elseif ('UNKNOWN' !== $name = token_name($i)) { + if ('T_HASHBANG' === $name) { + // HHVM uses a special token for #! hashbang lines + $tokenMap[$i] = Tokens::T_INLINE_HTML; + } elseif (defined($name = Tokens::class . '::' . $name)) { + // Other tokens can be mapped directly + $tokenMap[$i] = constant($name); + } + } + } + + // HHVM uses a special token for numbers that overflow to double + if (defined('T_ONUMBER')) { + $tokenMap[\T_ONUMBER] = Tokens::T_DNUMBER; + } + // HHVM also has a separate token for the __COMPILER_HALT_OFFSET__ constant + if (defined('T_COMPILER_HALT_OFFSET')) { + $tokenMap[\T_COMPILER_HALT_OFFSET] = Tokens::T_STRING; + } + + // Assign tokens for which we define compatibility constants, as token_name() does not know them. + $tokenMap[\T_FN] = Tokens::T_FN; + $tokenMap[\T_COALESCE_EQUAL] = Tokens::T_COALESCE_EQUAL; + $tokenMap[\T_NAME_QUALIFIED] = Tokens::T_NAME_QUALIFIED; + $tokenMap[\T_NAME_FULLY_QUALIFIED] = Tokens::T_NAME_FULLY_QUALIFIED; + $tokenMap[\T_NAME_RELATIVE] = Tokens::T_NAME_RELATIVE; + $tokenMap[\T_MATCH] = Tokens::T_MATCH; + $tokenMap[\T_NULLSAFE_OBJECT_OPERATOR] = Tokens::T_NULLSAFE_OBJECT_OPERATOR; + $tokenMap[\T_ATTRIBUTE] = Tokens::T_ATTRIBUTE; + $tokenMap[\T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG] = Tokens::T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG; + $tokenMap[\T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG] = Tokens::T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG; + $tokenMap[\T_ENUM] = Tokens::T_ENUM; + $tokenMap[\T_READONLY] = Tokens::T_READONLY; + + return $tokenMap; + } + + private function createIdentifierTokenMap(): array { + // Based on semi_reserved production. + return array_fill_keys([ + \T_STRING, + \T_STATIC, \T_ABSTRACT, \T_FINAL, \T_PRIVATE, \T_PROTECTED, \T_PUBLIC, \T_READONLY, + \T_INCLUDE, \T_INCLUDE_ONCE, \T_EVAL, \T_REQUIRE, \T_REQUIRE_ONCE, \T_LOGICAL_OR, \T_LOGICAL_XOR, \T_LOGICAL_AND, + \T_INSTANCEOF, \T_NEW, \T_CLONE, \T_EXIT, \T_IF, \T_ELSEIF, \T_ELSE, \T_ENDIF, \T_ECHO, \T_DO, \T_WHILE, + \T_ENDWHILE, \T_FOR, \T_ENDFOR, \T_FOREACH, \T_ENDFOREACH, \T_DECLARE, \T_ENDDECLARE, \T_AS, \T_TRY, \T_CATCH, + \T_FINALLY, \T_THROW, \T_USE, \T_INSTEADOF, \T_GLOBAL, \T_VAR, \T_UNSET, \T_ISSET, \T_EMPTY, \T_CONTINUE, \T_GOTO, + \T_FUNCTION, \T_CONST, \T_RETURN, \T_PRINT, \T_YIELD, \T_LIST, \T_SWITCH, \T_ENDSWITCH, \T_CASE, \T_DEFAULT, + \T_BREAK, \T_ARRAY, \T_CALLABLE, \T_EXTENDS, \T_IMPLEMENTS, \T_NAMESPACE, \T_TRAIT, \T_INTERFACE, \T_CLASS, + \T_CLASS_C, \T_TRAIT_C, \T_FUNC_C, \T_METHOD_C, \T_LINE, \T_FILE, \T_DIR, \T_NS_C, \T_HALT_COMPILER, \T_FN, + \T_MATCH, + ], true); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php new file mode 100644 index 0000000000..5c56e026bb --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php @@ -0,0 +1,248 @@ +targetPhpVersion = $options['phpVersion'] ?? Emulative::PHP_8_1; + unset($options['phpVersion']); + + parent::__construct($options); + + $emulators = [ + new FlexibleDocStringEmulator(), + new FnTokenEmulator(), + new MatchTokenEmulator(), + new CoaleseEqualTokenEmulator(), + new NumericLiteralSeparatorEmulator(), + new NullsafeTokenEmulator(), + new AttributeEmulator(), + new EnumTokenEmulator(), + new ReadonlyTokenEmulator(), + new ExplicitOctalEmulator(), + ]; + + // Collect emulators that are relevant for the PHP version we're running + // and the PHP version we're targeting for emulation. + foreach ($emulators as $emulator) { + $emulatorPhpVersion = $emulator->getPhpVersion(); + if ($this->isForwardEmulationNeeded($emulatorPhpVersion)) { + $this->emulators[] = $emulator; + } else if ($this->isReverseEmulationNeeded($emulatorPhpVersion)) { + $this->emulators[] = new ReverseEmulator($emulator); + } + } + } + + public function startLexing(string $code, ErrorHandler $errorHandler = null) { + $emulators = array_filter($this->emulators, function($emulator) use($code) { + return $emulator->isEmulationNeeded($code); + }); + + if (empty($emulators)) { + // Nothing to emulate, yay + parent::startLexing($code, $errorHandler); + return; + } + + $this->patches = []; + foreach ($emulators as $emulator) { + $code = $emulator->preprocessCode($code, $this->patches); + } + + $collector = new ErrorHandler\Collecting(); + parent::startLexing($code, $collector); + $this->sortPatches(); + $this->fixupTokens(); + + $errors = $collector->getErrors(); + if (!empty($errors)) { + $this->fixupErrors($errors); + foreach ($errors as $error) { + $errorHandler->handleError($error); + } + } + + foreach ($emulators as $emulator) { + $this->tokens = $emulator->emulate($code, $this->tokens); + } + } + + private function isForwardEmulationNeeded(string $emulatorPhpVersion): bool { + return version_compare(\PHP_VERSION, $emulatorPhpVersion, '<') + && version_compare($this->targetPhpVersion, $emulatorPhpVersion, '>='); + } + + private function isReverseEmulationNeeded(string $emulatorPhpVersion): bool { + return version_compare(\PHP_VERSION, $emulatorPhpVersion, '>=') + && version_compare($this->targetPhpVersion, $emulatorPhpVersion, '<'); + } + + private function sortPatches() + { + // Patches may be contributed by different emulators. + // Make sure they are sorted by increasing patch position. + usort($this->patches, function($p1, $p2) { + return $p1[0] <=> $p2[0]; + }); + } + + private function fixupTokens() + { + if (\count($this->patches) === 0) { + return; + } + + // Load first patch + $patchIdx = 0; + + list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx]; + + // We use a manual loop over the tokens, because we modify the array on the fly + $pos = 0; + for ($i = 0, $c = \count($this->tokens); $i < $c; $i++) { + $token = $this->tokens[$i]; + if (\is_string($token)) { + if ($patchPos === $pos) { + // Only support replacement for string tokens. + assert($patchType === 'replace'); + $this->tokens[$i] = $patchText; + + // Fetch the next patch + $patchIdx++; + if ($patchIdx >= \count($this->patches)) { + // No more patches, we're done + return; + } + list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx]; + } + + $pos += \strlen($token); + continue; + } + + $len = \strlen($token[1]); + $posDelta = 0; + while ($patchPos >= $pos && $patchPos < $pos + $len) { + $patchTextLen = \strlen($patchText); + if ($patchType === 'remove') { + if ($patchPos === $pos && $patchTextLen === $len) { + // Remove token entirely + array_splice($this->tokens, $i, 1, []); + $i--; + $c--; + } else { + // Remove from token string + $this->tokens[$i][1] = substr_replace( + $token[1], '', $patchPos - $pos + $posDelta, $patchTextLen + ); + $posDelta -= $patchTextLen; + } + } elseif ($patchType === 'add') { + // Insert into the token string + $this->tokens[$i][1] = substr_replace( + $token[1], $patchText, $patchPos - $pos + $posDelta, 0 + ); + $posDelta += $patchTextLen; + } else if ($patchType === 'replace') { + // Replace inside the token string + $this->tokens[$i][1] = substr_replace( + $token[1], $patchText, $patchPos - $pos + $posDelta, $patchTextLen + ); + } else { + assert(false); + } + + // Fetch the next patch + $patchIdx++; + if ($patchIdx >= \count($this->patches)) { + // No more patches, we're done + return; + } + + list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx]; + + // Multiple patches may apply to the same token. Reload the current one to check + // If the new patch applies + $token = $this->tokens[$i]; + } + + $pos += $len; + } + + // A patch did not apply + assert(false); + } + + /** + * Fixup line and position information in errors. + * + * @param Error[] $errors + */ + private function fixupErrors(array $errors) { + foreach ($errors as $error) { + $attrs = $error->getAttributes(); + + $posDelta = 0; + $lineDelta = 0; + foreach ($this->patches as $patch) { + list($patchPos, $patchType, $patchText) = $patch; + if ($patchPos >= $attrs['startFilePos']) { + // No longer relevant + break; + } + + if ($patchType === 'add') { + $posDelta += strlen($patchText); + $lineDelta += substr_count($patchText, "\n"); + } else if ($patchType === 'remove') { + $posDelta -= strlen($patchText); + $lineDelta -= substr_count($patchText, "\n"); + } + } + + $attrs['startFilePos'] += $posDelta; + $attrs['endFilePos'] += $posDelta; + $attrs['startLine'] += $lineDelta; + $attrs['endLine'] += $lineDelta; + $error->setAttributes($attrs); + } + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/AttributeEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/AttributeEmulator.php new file mode 100644 index 0000000000..6776a51975 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/AttributeEmulator.php @@ -0,0 +1,56 @@ +resolveIntegerOrFloatToken($tokens[$i + 1][1]); + array_splice($tokens, $i, 2, [ + [$tokenKind, '0' . $tokens[$i + 1][1], $tokens[$i][2]], + ]); + $c--; + } + } + return $tokens; + } + + private function resolveIntegerOrFloatToken(string $str): int + { + $str = substr($str, 1); + $str = str_replace('_', '', $str); + $num = octdec($str); + return is_float($num) ? \T_DNUMBER : \T_LNUMBER; + } + + public function reverseEmulate(string $code, array $tokens): array { + // Explicit octals were not legal code previously, don't bother. + return $tokens; + } +} \ No newline at end of file diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/FlexibleDocStringEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/FlexibleDocStringEmulator.php new file mode 100644 index 0000000000..c15d6271fc --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/FlexibleDocStringEmulator.php @@ -0,0 +1,76 @@ +\h*)\2(?![a-zA-Z0-9_\x80-\xff])(?(?:;?[\r\n])?)/x +REGEX; + + public function getPhpVersion(): string + { + return Emulative::PHP_7_3; + } + + public function isEmulationNeeded(string $code) : bool + { + return strpos($code, '<<<') !== false; + } + + public function emulate(string $code, array $tokens): array + { + // Handled by preprocessing + fixup. + return $tokens; + } + + public function reverseEmulate(string $code, array $tokens): array + { + // Not supported. + return $tokens; + } + + public function preprocessCode(string $code, array &$patches): string { + if (!preg_match_all(self::FLEXIBLE_DOC_STRING_REGEX, $code, $matches, PREG_SET_ORDER|PREG_OFFSET_CAPTURE)) { + // No heredoc/nowdoc found + return $code; + } + + // Keep track of how much we need to adjust string offsets due to the modifications we + // already made + $posDelta = 0; + foreach ($matches as $match) { + $indentation = $match['indentation'][0]; + $indentationStart = $match['indentation'][1]; + + $separator = $match['separator'][0]; + $separatorStart = $match['separator'][1]; + + if ($indentation === '' && $separator !== '') { + // Ordinary heredoc/nowdoc + continue; + } + + if ($indentation !== '') { + // Remove indentation + $indentationLen = strlen($indentation); + $code = substr_replace($code, '', $indentationStart + $posDelta, $indentationLen); + $patches[] = [$indentationStart + $posDelta, 'add', $indentation]; + $posDelta -= $indentationLen; + } + + if ($separator === '') { + // Insert newline as separator + $code = substr_replace($code, "\n", $separatorStart + $posDelta, 0); + $patches[] = [$separatorStart + $posDelta, 'remove', "\n"]; + $posDelta += 1; + } + } + + return $code; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/FnTokenEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/FnTokenEmulator.php new file mode 100644 index 0000000000..eb7e49634a --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/FnTokenEmulator.php @@ -0,0 +1,23 @@ +getKeywordString()) !== false; + } + + protected function isKeywordContext(array $tokens, int $pos): bool + { + $previousNonSpaceToken = $this->getPreviousNonSpaceToken($tokens, $pos); + return $previousNonSpaceToken === null || $previousNonSpaceToken[0] !== \T_OBJECT_OPERATOR; + } + + public function emulate(string $code, array $tokens): array + { + $keywordString = $this->getKeywordString(); + foreach ($tokens as $i => $token) { + if ($token[0] === T_STRING && strtolower($token[1]) === $keywordString + && $this->isKeywordContext($tokens, $i)) { + $tokens[$i][0] = $this->getKeywordToken(); + } + } + + return $tokens; + } + + /** + * @param mixed[] $tokens + * @return mixed[]|null + */ + private function getPreviousNonSpaceToken(array $tokens, int $start) + { + for ($i = $start - 1; $i >= 0; --$i) { + if ($tokens[$i][0] === T_WHITESPACE) { + continue; + } + + return $tokens[$i]; + } + + return null; + } + + public function reverseEmulate(string $code, array $tokens): array + { + $keywordToken = $this->getKeywordToken(); + foreach ($tokens as $i => $token) { + if ($token[0] === $keywordToken) { + $tokens[$i][0] = \T_STRING; + } + } + + return $tokens; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/MatchTokenEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/MatchTokenEmulator.php new file mode 100644 index 0000000000..902a46dfcb --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/MatchTokenEmulator.php @@ -0,0 +1,23 @@ +') !== false; + } + + public function emulate(string $code, array $tokens): array + { + // We need to manually iterate and manage a count because we'll change + // the tokens array on the way + $line = 1; + for ($i = 0, $c = count($tokens); $i < $c; ++$i) { + if ($tokens[$i] === '?' && isset($tokens[$i + 1]) && $tokens[$i + 1][0] === \T_OBJECT_OPERATOR) { + array_splice($tokens, $i, 2, [ + [\T_NULLSAFE_OBJECT_OPERATOR, '?->', $line] + ]); + $c--; + continue; + } + + // Handle ?-> inside encapsed string. + if ($tokens[$i][0] === \T_ENCAPSED_AND_WHITESPACE && isset($tokens[$i - 1]) + && $tokens[$i - 1][0] === \T_VARIABLE + && preg_match('/^\?->([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)/', $tokens[$i][1], $matches) + ) { + $replacement = [ + [\T_NULLSAFE_OBJECT_OPERATOR, '?->', $line], + [\T_STRING, $matches[1], $line], + ]; + if (\strlen($matches[0]) !== \strlen($tokens[$i][1])) { + $replacement[] = [ + \T_ENCAPSED_AND_WHITESPACE, + \substr($tokens[$i][1], \strlen($matches[0])), + $line + ]; + } + array_splice($tokens, $i, 1, $replacement); + $c += \count($replacement) - 1; + continue; + } + + if (\is_array($tokens[$i])) { + $line += substr_count($tokens[$i][1], "\n"); + } + } + + return $tokens; + } + + public function reverseEmulate(string $code, array $tokens): array + { + // ?-> was not valid code previously, don't bother. + return $tokens; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/NumericLiteralSeparatorEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/NumericLiteralSeparatorEmulator.php new file mode 100644 index 0000000000..cdf793e46e --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/NumericLiteralSeparatorEmulator.php @@ -0,0 +1,105 @@ +resolveIntegerOrFloatToken($match); + $newTokens = [[$tokenKind, $match, $token[2]]]; + + $numTokens = 1; + $len = $tokenLen; + while ($matchLen > $len) { + $nextToken = $tokens[$i + $numTokens]; + $nextTokenText = \is_array($nextToken) ? $nextToken[1] : $nextToken; + $nextTokenLen = \strlen($nextTokenText); + + $numTokens++; + if ($matchLen < $len + $nextTokenLen) { + // Split trailing characters into a partial token. + assert(is_array($nextToken), "Partial token should be an array token"); + $partialText = substr($nextTokenText, $matchLen - $len); + $newTokens[] = [$nextToken[0], $partialText, $nextToken[2]]; + break; + } + + $len += $nextTokenLen; + } + + array_splice($tokens, $i, $numTokens, $newTokens); + $c -= $numTokens - \count($newTokens); + $codeOffset += $matchLen; + } + + return $tokens; + } + + private function resolveIntegerOrFloatToken(string $str): int + { + $str = str_replace('_', '', $str); + + if (stripos($str, '0b') === 0) { + $num = bindec($str); + } elseif (stripos($str, '0x') === 0) { + $num = hexdec($str); + } elseif (stripos($str, '0') === 0 && ctype_digit($str)) { + $num = octdec($str); + } else { + $num = +$str; + } + + return is_float($num) ? T_DNUMBER : T_LNUMBER; + } + + public function reverseEmulate(string $code, array $tokens): array + { + // Numeric separators were not legal code previously, don't bother. + return $tokens; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReadonlyTokenEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReadonlyTokenEmulator.php new file mode 100644 index 0000000000..b97f8d1126 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReadonlyTokenEmulator.php @@ -0,0 +1,23 @@ +emulator = $emulator; + } + + public function getPhpVersion(): string { + return $this->emulator->getPhpVersion(); + } + + public function isEmulationNeeded(string $code): bool { + return $this->emulator->isEmulationNeeded($code); + } + + public function emulate(string $code, array $tokens): array { + return $this->emulator->reverseEmulate($code, $tokens); + } + + public function reverseEmulate(string $code, array $tokens): array { + return $this->emulator->emulate($code, $tokens); + } + + public function preprocessCode(string $code, array &$patches): string { + return $code; + } +} \ No newline at end of file diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/TokenEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/TokenEmulator.php new file mode 100644 index 0000000000..a020bc0ff4 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/TokenEmulator.php @@ -0,0 +1,25 @@ + [aliasName => originalName]] */ + protected $aliases = []; + + /** @var Name[][] Same as $aliases but preserving original case */ + protected $origAliases = []; + + /** @var ErrorHandler Error handler */ + protected $errorHandler; + + /** + * Create a name context. + * + * @param ErrorHandler $errorHandler Error handling used to report errors + */ + public function __construct(ErrorHandler $errorHandler) { + $this->errorHandler = $errorHandler; + } + + /** + * Start a new namespace. + * + * This also resets the alias table. + * + * @param Name|null $namespace Null is the global namespace + */ + public function startNamespace(Name $namespace = null) { + $this->namespace = $namespace; + $this->origAliases = $this->aliases = [ + Stmt\Use_::TYPE_NORMAL => [], + Stmt\Use_::TYPE_FUNCTION => [], + Stmt\Use_::TYPE_CONSTANT => [], + ]; + } + + /** + * Add an alias / import. + * + * @param Name $name Original name + * @param string $aliasName Aliased name + * @param int $type One of Stmt\Use_::TYPE_* + * @param array $errorAttrs Attributes to use to report an error + */ + public function addAlias(Name $name, string $aliasName, int $type, array $errorAttrs = []) { + // Constant names are case sensitive, everything else case insensitive + if ($type === Stmt\Use_::TYPE_CONSTANT) { + $aliasLookupName = $aliasName; + } else { + $aliasLookupName = strtolower($aliasName); + } + + if (isset($this->aliases[$type][$aliasLookupName])) { + $typeStringMap = [ + Stmt\Use_::TYPE_NORMAL => '', + Stmt\Use_::TYPE_FUNCTION => 'function ', + Stmt\Use_::TYPE_CONSTANT => 'const ', + ]; + + $this->errorHandler->handleError(new Error( + sprintf( + 'Cannot use %s%s as %s because the name is already in use', + $typeStringMap[$type], $name, $aliasName + ), + $errorAttrs + )); + return; + } + + $this->aliases[$type][$aliasLookupName] = $name; + $this->origAliases[$type][$aliasName] = $name; + } + + /** + * Get current namespace. + * + * @return null|Name Namespace (or null if global namespace) + */ + public function getNamespace() { + return $this->namespace; + } + + /** + * Get resolved name. + * + * @param Name $name Name to resolve + * @param int $type One of Stmt\Use_::TYPE_{FUNCTION|CONSTANT} + * + * @return null|Name Resolved name, or null if static resolution is not possible + */ + public function getResolvedName(Name $name, int $type) { + // don't resolve special class names + if ($type === Stmt\Use_::TYPE_NORMAL && $name->isSpecialClassName()) { + if (!$name->isUnqualified()) { + $this->errorHandler->handleError(new Error( + sprintf("'\\%s' is an invalid class name", $name->toString()), + $name->getAttributes() + )); + } + return $name; + } + + // fully qualified names are already resolved + if ($name->isFullyQualified()) { + return $name; + } + + // Try to resolve aliases + if (null !== $resolvedName = $this->resolveAlias($name, $type)) { + return $resolvedName; + } + + if ($type !== Stmt\Use_::TYPE_NORMAL && $name->isUnqualified()) { + if (null === $this->namespace) { + // outside of a namespace unaliased unqualified is same as fully qualified + return new FullyQualified($name, $name->getAttributes()); + } + + // Cannot resolve statically + return null; + } + + // if no alias exists prepend current namespace + return FullyQualified::concat($this->namespace, $name, $name->getAttributes()); + } + + /** + * Get resolved class name. + * + * @param Name $name Class ame to resolve + * + * @return Name Resolved name + */ + public function getResolvedClassName(Name $name) : Name { + return $this->getResolvedName($name, Stmt\Use_::TYPE_NORMAL); + } + + /** + * Get possible ways of writing a fully qualified name (e.g., by making use of aliases). + * + * @param string $name Fully-qualified name (without leading namespace separator) + * @param int $type One of Stmt\Use_::TYPE_* + * + * @return Name[] Possible representations of the name + */ + public function getPossibleNames(string $name, int $type) : array { + $lcName = strtolower($name); + + if ($type === Stmt\Use_::TYPE_NORMAL) { + // self, parent and static must always be unqualified + if ($lcName === "self" || $lcName === "parent" || $lcName === "static") { + return [new Name($name)]; + } + } + + // Collect possible ways to write this name, starting with the fully-qualified name + $possibleNames = [new FullyQualified($name)]; + + if (null !== $nsRelativeName = $this->getNamespaceRelativeName($name, $lcName, $type)) { + // Make sure there is no alias that makes the normally namespace-relative name + // into something else + if (null === $this->resolveAlias($nsRelativeName, $type)) { + $possibleNames[] = $nsRelativeName; + } + } + + // Check for relevant namespace use statements + foreach ($this->origAliases[Stmt\Use_::TYPE_NORMAL] as $alias => $orig) { + $lcOrig = $orig->toLowerString(); + if (0 === strpos($lcName, $lcOrig . '\\')) { + $possibleNames[] = new Name($alias . substr($name, strlen($lcOrig))); + } + } + + // Check for relevant type-specific use statements + foreach ($this->origAliases[$type] as $alias => $orig) { + if ($type === Stmt\Use_::TYPE_CONSTANT) { + // Constants are are complicated-sensitive + $normalizedOrig = $this->normalizeConstName($orig->toString()); + if ($normalizedOrig === $this->normalizeConstName($name)) { + $possibleNames[] = new Name($alias); + } + } else { + // Everything else is case-insensitive + if ($orig->toLowerString() === $lcName) { + $possibleNames[] = new Name($alias); + } + } + } + + return $possibleNames; + } + + /** + * Get shortest representation of this fully-qualified name. + * + * @param string $name Fully-qualified name (without leading namespace separator) + * @param int $type One of Stmt\Use_::TYPE_* + * + * @return Name Shortest representation + */ + public function getShortName(string $name, int $type) : Name { + $possibleNames = $this->getPossibleNames($name, $type); + + // Find shortest name + $shortestName = null; + $shortestLength = \INF; + foreach ($possibleNames as $possibleName) { + $length = strlen($possibleName->toCodeString()); + if ($length < $shortestLength) { + $shortestName = $possibleName; + $shortestLength = $length; + } + } + + return $shortestName; + } + + private function resolveAlias(Name $name, $type) { + $firstPart = $name->getFirst(); + + if ($name->isQualified()) { + // resolve aliases for qualified names, always against class alias table + $checkName = strtolower($firstPart); + if (isset($this->aliases[Stmt\Use_::TYPE_NORMAL][$checkName])) { + $alias = $this->aliases[Stmt\Use_::TYPE_NORMAL][$checkName]; + return FullyQualified::concat($alias, $name->slice(1), $name->getAttributes()); + } + } elseif ($name->isUnqualified()) { + // constant aliases are case-sensitive, function aliases case-insensitive + $checkName = $type === Stmt\Use_::TYPE_CONSTANT ? $firstPart : strtolower($firstPart); + if (isset($this->aliases[$type][$checkName])) { + // resolve unqualified aliases + return new FullyQualified($this->aliases[$type][$checkName], $name->getAttributes()); + } + } + + // No applicable aliases + return null; + } + + private function getNamespaceRelativeName(string $name, string $lcName, int $type) { + if (null === $this->namespace) { + return new Name($name); + } + + if ($type === Stmt\Use_::TYPE_CONSTANT) { + // The constants true/false/null always resolve to the global symbols, even inside a + // namespace, so they may be used without qualification + if ($lcName === "true" || $lcName === "false" || $lcName === "null") { + return new Name($name); + } + } + + $namespacePrefix = strtolower($this->namespace . '\\'); + if (0 === strpos($lcName, $namespacePrefix)) { + return new Name(substr($name, strlen($namespacePrefix))); + } + + return null; + } + + private function normalizeConstName(string $name) { + $nsSep = strrpos($name, '\\'); + if (false === $nsSep) { + return $name; + } + + // Constants have case-insensitive namespace and case-sensitive short-name + $ns = substr($name, 0, $nsSep); + $shortName = substr($name, $nsSep + 1); + return strtolower($ns) . '\\' . $shortName; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node.php b/vendor/nikic/php-parser/lib/PhpParser/Node.php new file mode 100644 index 0000000000..befb256504 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node.php @@ -0,0 +1,151 @@ +attributes = $attributes; + $this->name = $name; + $this->value = $value; + $this->byRef = $byRef; + $this->unpack = $unpack; + } + + public function getSubNodeNames() : array { + return ['name', 'value', 'byRef', 'unpack']; + } + + public function getType() : string { + return 'Arg'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Attribute.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Attribute.php new file mode 100644 index 0000000000..c96f66e514 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Attribute.php @@ -0,0 +1,34 @@ +attributes = $attributes; + $this->name = $name; + $this->args = $args; + } + + public function getSubNodeNames() : array { + return ['name', 'args']; + } + + public function getType() : string { + return 'Attribute'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/AttributeGroup.php b/vendor/nikic/php-parser/lib/PhpParser/Node/AttributeGroup.php new file mode 100644 index 0000000000..613bfc4134 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/AttributeGroup.php @@ -0,0 +1,29 @@ +attributes = $attributes; + $this->attrs = $attrs; + } + + public function getSubNodeNames() : array { + return ['attrs']; + } + + public function getType() : string { + return 'AttributeGroup'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/ComplexType.php b/vendor/nikic/php-parser/lib/PhpParser/Node/ComplexType.php new file mode 100644 index 0000000000..9505532ae9 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/ComplexType.php @@ -0,0 +1,14 @@ +attributes = $attributes; + $this->name = \is_string($name) ? new Identifier($name) : $name; + $this->value = $value; + } + + public function getSubNodeNames() : array { + return ['name', 'value']; + } + + public function getType() : string { + return 'Const'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr.php new file mode 100644 index 0000000000..6cf4df2233 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr.php @@ -0,0 +1,9 @@ +attributes = $attributes; + $this->var = $var; + $this->dim = $dim; + } + + public function getSubNodeNames() : array { + return ['var', 'dim']; + } + + public function getType() : string { + return 'Expr_ArrayDimFetch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayItem.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayItem.php new file mode 100644 index 0000000000..1b078f8218 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayItem.php @@ -0,0 +1,41 @@ +attributes = $attributes; + $this->key = $key; + $this->value = $value; + $this->byRef = $byRef; + $this->unpack = $unpack; + } + + public function getSubNodeNames() : array { + return ['key', 'value', 'byRef', 'unpack']; + } + + public function getType() : string { + return 'Expr_ArrayItem'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Array_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Array_.php new file mode 100644 index 0000000000..e6eaa2834d --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Array_.php @@ -0,0 +1,34 @@ +attributes = $attributes; + $this->items = $items; + } + + public function getSubNodeNames() : array { + return ['items']; + } + + public function getType() : string { + return 'Expr_Array'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrowFunction.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrowFunction.php new file mode 100644 index 0000000000..c273fb7ee8 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrowFunction.php @@ -0,0 +1,79 @@ + false : Whether the closure is static + * 'byRef' => false : Whether to return by reference + * 'params' => array() : Parameters + * 'returnType' => null : Return type + * 'expr' => Expr : Expression body + * 'attrGroups' => array() : PHP attribute groups + * @param array $attributes Additional attributes + */ + public function __construct(array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->static = $subNodes['static'] ?? false; + $this->byRef = $subNodes['byRef'] ?? false; + $this->params = $subNodes['params'] ?? []; + $returnType = $subNodes['returnType'] ?? null; + $this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType; + $this->expr = $subNodes['expr']; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + } + + public function getSubNodeNames() : array { + return ['attrGroups', 'static', 'byRef', 'params', 'returnType', 'expr']; + } + + public function returnsByRef() : bool { + return $this->byRef; + } + + public function getParams() : array { + return $this->params; + } + + public function getReturnType() { + return $this->returnType; + } + + public function getAttrGroups() : array { + return $this->attrGroups; + } + + /** + * @return Node\Stmt\Return_[] + */ + public function getStmts() : array { + return [new Node\Stmt\Return_($this->expr)]; + } + + public function getType() : string { + return 'Expr_ArrowFunction'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Assign.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Assign.php new file mode 100644 index 0000000000..cf9e6e82b4 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Assign.php @@ -0,0 +1,34 @@ +attributes = $attributes; + $this->var = $var; + $this->expr = $expr; + } + + public function getSubNodeNames() : array { + return ['var', 'expr']; + } + + public function getType() : string { + return 'Expr_Assign'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp.php new file mode 100644 index 0000000000..bce8604f14 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->var = $var; + $this->expr = $expr; + } + + public function getSubNodeNames() : array { + return ['var', 'expr']; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php new file mode 100644 index 0000000000..420284cdc1 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php @@ -0,0 +1,12 @@ +attributes = $attributes; + $this->var = $var; + $this->expr = $expr; + } + + public function getSubNodeNames() : array { + return ['var', 'expr']; + } + + public function getType() : string { + return 'Expr_AssignRef'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp.php new file mode 100644 index 0000000000..d9c582b0d2 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp.php @@ -0,0 +1,40 @@ +attributes = $attributes; + $this->left = $left; + $this->right = $right; + } + + public function getSubNodeNames() : array { + return ['left', 'right']; + } + + /** + * Get the operator sigil for this binary operation. + * + * In the case there are multiple possible sigils for an operator, this method does not + * necessarily return the one used in the parsed code. + * + * @return string + */ + abstract public function getOperatorSigil() : string; +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php new file mode 100644 index 0000000000..d907393bfa --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php @@ -0,0 +1,16 @@ +'; + } + + public function getType() : string { + return 'Expr_BinaryOp_Greater'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php new file mode 100644 index 0000000000..d677502cf5 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php @@ -0,0 +1,16 @@ +='; + } + + public function getType() : string { + return 'Expr_BinaryOp_GreaterOrEqual'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Identical.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Identical.php new file mode 100644 index 0000000000..3d96285c64 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Identical.php @@ -0,0 +1,16 @@ +>'; + } + + public function getType() : string { + return 'Expr_BinaryOp_ShiftRight'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Smaller.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Smaller.php new file mode 100644 index 0000000000..3cb8e7e0d1 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Smaller.php @@ -0,0 +1,16 @@ +'; + } + + public function getType() : string { + return 'Expr_BinaryOp_Spaceship'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BitwiseNot.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BitwiseNot.php new file mode 100644 index 0000000000..ed44984bea --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BitwiseNot.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames() : array { + return ['expr']; + } + + public function getType() : string { + return 'Expr_BitwiseNot'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BooleanNot.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BooleanNot.php new file mode 100644 index 0000000000..bf27e9f657 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BooleanNot.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames() : array { + return ['expr']; + } + + public function getType() : string { + return 'Expr_BooleanNot'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/CallLike.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/CallLike.php new file mode 100644 index 0000000000..78e1cf3494 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/CallLike.php @@ -0,0 +1,39 @@ + + */ + abstract public function getRawArgs(): array; + + /** + * Returns whether this call expression is actually a first class callable. + */ + public function isFirstClassCallable(): bool { + foreach ($this->getRawArgs() as $arg) { + if ($arg instanceof VariadicPlaceholder) { + return true; + } + } + return false; + } + + /** + * Assert that this is not a first-class callable and return only ordinary Args. + * + * @return Arg[] + */ + public function getArgs(): array { + assert(!$this->isFirstClassCallable()); + return $this->getRawArgs(); + } +} \ No newline at end of file diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast.php new file mode 100644 index 0000000000..36769d4fc6 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast.php @@ -0,0 +1,26 @@ +attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames() : array { + return ['expr']; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Array_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Array_.php new file mode 100644 index 0000000000..57cc473b62 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Array_.php @@ -0,0 +1,12 @@ +attributes = $attributes; + $this->class = $class; + $this->name = \is_string($name) ? new Identifier($name) : $name; + } + + public function getSubNodeNames() : array { + return ['class', 'name']; + } + + public function getType() : string { + return 'Expr_ClassConstFetch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Clone_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Clone_.php new file mode 100644 index 0000000000..db216b8f84 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Clone_.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames() : array { + return ['expr']; + } + + public function getType() : string { + return 'Expr_Clone'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Closure.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Closure.php new file mode 100644 index 0000000000..56ddea6aa5 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Closure.php @@ -0,0 +1,79 @@ + false : Whether the closure is static + * 'byRef' => false : Whether to return by reference + * 'params' => array(): Parameters + * 'uses' => array(): use()s + * 'returnType' => null : Return type + * 'stmts' => array(): Statements + * 'attrGroups' => array(): PHP attributes groups + * @param array $attributes Additional attributes + */ + public function __construct(array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->static = $subNodes['static'] ?? false; + $this->byRef = $subNodes['byRef'] ?? false; + $this->params = $subNodes['params'] ?? []; + $this->uses = $subNodes['uses'] ?? []; + $returnType = $subNodes['returnType'] ?? null; + $this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType; + $this->stmts = $subNodes['stmts'] ?? []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + } + + public function getSubNodeNames() : array { + return ['attrGroups', 'static', 'byRef', 'params', 'uses', 'returnType', 'stmts']; + } + + public function returnsByRef() : bool { + return $this->byRef; + } + + public function getParams() : array { + return $this->params; + } + + public function getReturnType() { + return $this->returnType; + } + + /** @return Node\Stmt[] */ + public function getStmts() : array { + return $this->stmts; + } + + public function getAttrGroups() : array { + return $this->attrGroups; + } + + public function getType() : string { + return 'Expr_Closure'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ClosureUse.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ClosureUse.php new file mode 100644 index 0000000000..2b8a096666 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ClosureUse.php @@ -0,0 +1,34 @@ +attributes = $attributes; + $this->var = $var; + $this->byRef = $byRef; + } + + public function getSubNodeNames() : array { + return ['var', 'byRef']; + } + + public function getType() : string { + return 'Expr_ClosureUse'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ConstFetch.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ConstFetch.php new file mode 100644 index 0000000000..14ebd16bd8 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ConstFetch.php @@ -0,0 +1,31 @@ +attributes = $attributes; + $this->name = $name; + } + + public function getSubNodeNames() : array { + return ['name']; + } + + public function getType() : string { + return 'Expr_ConstFetch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Empty_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Empty_.php new file mode 100644 index 0000000000..4042ec93ca --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Empty_.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames() : array { + return ['expr']; + } + + public function getType() : string { + return 'Expr_Empty'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Error.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Error.php new file mode 100644 index 0000000000..1637f3aeae --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Error.php @@ -0,0 +1,31 @@ +attributes = $attributes; + } + + public function getSubNodeNames() : array { + return []; + } + + public function getType() : string { + return 'Expr_Error'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ErrorSuppress.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ErrorSuppress.php new file mode 100644 index 0000000000..c44ff6f931 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ErrorSuppress.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames() : array { + return ['expr']; + } + + public function getType() : string { + return 'Expr_ErrorSuppress'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Eval_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Eval_.php new file mode 100644 index 0000000000..8568547438 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Eval_.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames() : array { + return ['expr']; + } + + public function getType() : string { + return 'Expr_Eval'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Exit_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Exit_.php new file mode 100644 index 0000000000..b88a8f7e6f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Exit_.php @@ -0,0 +1,34 @@ +attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames() : array { + return ['expr']; + } + + public function getType() : string { + return 'Expr_Exit'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/FuncCall.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/FuncCall.php new file mode 100644 index 0000000000..2de4d0dd57 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/FuncCall.php @@ -0,0 +1,39 @@ + Arguments */ + public $args; + + /** + * Constructs a function call node. + * + * @param Node\Name|Expr $name Function name + * @param array $args Arguments + * @param array $attributes Additional attributes + */ + public function __construct($name, array $args = [], array $attributes = []) { + $this->attributes = $attributes; + $this->name = $name; + $this->args = $args; + } + + public function getSubNodeNames() : array { + return ['name', 'args']; + } + + public function getType() : string { + return 'Expr_FuncCall'; + } + + public function getRawArgs(): array { + return $this->args; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Include_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Include_.php new file mode 100644 index 0000000000..07ce5968e4 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Include_.php @@ -0,0 +1,39 @@ +attributes = $attributes; + $this->expr = $expr; + $this->type = $type; + } + + public function getSubNodeNames() : array { + return ['expr', 'type']; + } + + public function getType() : string { + return 'Expr_Include'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Instanceof_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Instanceof_.php new file mode 100644 index 0000000000..9000d47bb1 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Instanceof_.php @@ -0,0 +1,35 @@ +attributes = $attributes; + $this->expr = $expr; + $this->class = $class; + } + + public function getSubNodeNames() : array { + return ['expr', 'class']; + } + + public function getType() : string { + return 'Expr_Instanceof'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Isset_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Isset_.php new file mode 100644 index 0000000000..76b7387587 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Isset_.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->vars = $vars; + } + + public function getSubNodeNames() : array { + return ['vars']; + } + + public function getType() : string { + return 'Expr_Isset'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/List_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/List_.php new file mode 100644 index 0000000000..c27a27b957 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/List_.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->items = $items; + } + + public function getSubNodeNames() : array { + return ['items']; + } + + public function getType() : string { + return 'Expr_List'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Match_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Match_.php new file mode 100644 index 0000000000..2455a30264 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Match_.php @@ -0,0 +1,31 @@ +attributes = $attributes; + $this->cond = $cond; + $this->arms = $arms; + } + + public function getSubNodeNames() : array { + return ['cond', 'arms']; + } + + public function getType() : string { + return 'Expr_Match'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/MethodCall.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/MethodCall.php new file mode 100644 index 0000000000..49ca483565 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/MethodCall.php @@ -0,0 +1,45 @@ + Arguments */ + public $args; + + /** + * Constructs a function call node. + * + * @param Expr $var Variable holding object + * @param string|Identifier|Expr $name Method name + * @param array $args Arguments + * @param array $attributes Additional attributes + */ + public function __construct(Expr $var, $name, array $args = [], array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + $this->name = \is_string($name) ? new Identifier($name) : $name; + $this->args = $args; + } + + public function getSubNodeNames() : array { + return ['var', 'name', 'args']; + } + + public function getType() : string { + return 'Expr_MethodCall'; + } + + public function getRawArgs(): array { + return $this->args; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/New_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/New_.php new file mode 100644 index 0000000000..e2bb64928d --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/New_.php @@ -0,0 +1,41 @@ + Arguments */ + public $args; + + /** + * Constructs a function call node. + * + * @param Node\Name|Expr|Node\Stmt\Class_ $class Class name (or class node for anonymous classes) + * @param array $args Arguments + * @param array $attributes Additional attributes + */ + public function __construct($class, array $args = [], array $attributes = []) { + $this->attributes = $attributes; + $this->class = $class; + $this->args = $args; + } + + public function getSubNodeNames() : array { + return ['class', 'args']; + } + + public function getType() : string { + return 'Expr_New'; + } + + public function getRawArgs(): array { + return $this->args; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafeMethodCall.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafeMethodCall.php new file mode 100644 index 0000000000..07a571fd8f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafeMethodCall.php @@ -0,0 +1,45 @@ + Arguments */ + public $args; + + /** + * Constructs a nullsafe method call node. + * + * @param Expr $var Variable holding object + * @param string|Identifier|Expr $name Method name + * @param array $args Arguments + * @param array $attributes Additional attributes + */ + public function __construct(Expr $var, $name, array $args = [], array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + $this->name = \is_string($name) ? new Identifier($name) : $name; + $this->args = $args; + } + + public function getSubNodeNames() : array { + return ['var', 'name', 'args']; + } + + public function getType() : string { + return 'Expr_NullsafeMethodCall'; + } + + public function getRawArgs(): array { + return $this->args; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafePropertyFetch.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafePropertyFetch.php new file mode 100644 index 0000000000..9317eb3b91 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafePropertyFetch.php @@ -0,0 +1,35 @@ +attributes = $attributes; + $this->var = $var; + $this->name = \is_string($name) ? new Identifier($name) : $name; + } + + public function getSubNodeNames() : array { + return ['var', 'name']; + } + + public function getType() : string { + return 'Expr_NullsafePropertyFetch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostDec.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostDec.php new file mode 100644 index 0000000000..94d6c296d8 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostDec.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->var = $var; + } + + public function getSubNodeNames() : array { + return ['var']; + } + + public function getType() : string { + return 'Expr_PostDec'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostInc.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostInc.php new file mode 100644 index 0000000000..005c443a2d --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostInc.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->var = $var; + } + + public function getSubNodeNames() : array { + return ['var']; + } + + public function getType() : string { + return 'Expr_PostInc'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreDec.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreDec.php new file mode 100644 index 0000000000..a5ca685a8a --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreDec.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->var = $var; + } + + public function getSubNodeNames() : array { + return ['var']; + } + + public function getType() : string { + return 'Expr_PreDec'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreInc.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreInc.php new file mode 100644 index 0000000000..0986c44748 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreInc.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->var = $var; + } + + public function getSubNodeNames() : array { + return ['var']; + } + + public function getType() : string { + return 'Expr_PreInc'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Print_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Print_.php new file mode 100644 index 0000000000..2d43c2ac82 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Print_.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames() : array { + return ['expr']; + } + + public function getType() : string { + return 'Expr_Print'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PropertyFetch.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PropertyFetch.php new file mode 100644 index 0000000000..4281f31ccf --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PropertyFetch.php @@ -0,0 +1,35 @@ +attributes = $attributes; + $this->var = $var; + $this->name = \is_string($name) ? new Identifier($name) : $name; + } + + public function getSubNodeNames() : array { + return ['var', 'name']; + } + + public function getType() : string { + return 'Expr_PropertyFetch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ShellExec.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ShellExec.php new file mode 100644 index 0000000000..537a7cc809 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ShellExec.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->parts = $parts; + } + + public function getSubNodeNames() : array { + return ['parts']; + } + + public function getType() : string { + return 'Expr_ShellExec'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php new file mode 100644 index 0000000000..d0d099c472 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php @@ -0,0 +1,46 @@ + Arguments */ + public $args; + + /** + * Constructs a static method call node. + * + * @param Node\Name|Expr $class Class name + * @param string|Identifier|Expr $name Method name + * @param array $args Arguments + * @param array $attributes Additional attributes + */ + public function __construct($class, $name, array $args = [], array $attributes = []) { + $this->attributes = $attributes; + $this->class = $class; + $this->name = \is_string($name) ? new Identifier($name) : $name; + $this->args = $args; + } + + public function getSubNodeNames() : array { + return ['class', 'name', 'args']; + } + + public function getType() : string { + return 'Expr_StaticCall'; + } + + public function getRawArgs(): array { + return $this->args; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticPropertyFetch.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticPropertyFetch.php new file mode 100644 index 0000000000..1ee1a25e50 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticPropertyFetch.php @@ -0,0 +1,36 @@ +attributes = $attributes; + $this->class = $class; + $this->name = \is_string($name) ? new VarLikeIdentifier($name) : $name; + } + + public function getSubNodeNames() : array { + return ['class', 'name']; + } + + public function getType() : string { + return 'Expr_StaticPropertyFetch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Ternary.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Ternary.php new file mode 100644 index 0000000000..9316f47d4d --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Ternary.php @@ -0,0 +1,38 @@ +attributes = $attributes; + $this->cond = $cond; + $this->if = $if; + $this->else = $else; + } + + public function getSubNodeNames() : array { + return ['cond', 'if', 'else']; + } + + public function getType() : string { + return 'Expr_Ternary'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Throw_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Throw_.php new file mode 100644 index 0000000000..5c97f0e2b4 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Throw_.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames() : array { + return ['expr']; + } + + public function getType() : string { + return 'Expr_Throw'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryMinus.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryMinus.php new file mode 100644 index 0000000000..ce8808bc64 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryMinus.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames() : array { + return ['expr']; + } + + public function getType() : string { + return 'Expr_UnaryMinus'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryPlus.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryPlus.php new file mode 100644 index 0000000000..d23047e54e --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryPlus.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames() : array { + return ['expr']; + } + + public function getType() : string { + return 'Expr_UnaryPlus'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Variable.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Variable.php new file mode 100644 index 0000000000..b47d38e934 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Variable.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->name = $name; + } + + public function getSubNodeNames() : array { + return ['name']; + } + + public function getType() : string { + return 'Expr_Variable'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/YieldFrom.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/YieldFrom.php new file mode 100644 index 0000000000..a3efce618c --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/YieldFrom.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames() : array { + return ['expr']; + } + + public function getType() : string { + return 'Expr_YieldFrom'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Yield_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Yield_.php new file mode 100644 index 0000000000..aef8fc333d --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Yield_.php @@ -0,0 +1,34 @@ +attributes = $attributes; + $this->key = $key; + $this->value = $value; + } + + public function getSubNodeNames() : array { + return ['key', 'value']; + } + + public function getType() : string { + return 'Expr_Yield'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/FunctionLike.php b/vendor/nikic/php-parser/lib/PhpParser/Node/FunctionLike.php new file mode 100644 index 0000000000..5a825e7311 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/FunctionLike.php @@ -0,0 +1,43 @@ + true, + 'parent' => true, + 'static' => true, + ]; + + /** + * Constructs an identifier node. + * + * @param string $name Identifier as string + * @param array $attributes Additional attributes + */ + public function __construct(string $name, array $attributes = []) { + $this->attributes = $attributes; + $this->name = $name; + } + + public function getSubNodeNames() : array { + return ['name']; + } + + /** + * Get identifier as string. + * + * @return string Identifier as string. + */ + public function toString() : string { + return $this->name; + } + + /** + * Get lowercased identifier as string. + * + * @return string Lowercased identifier as string + */ + public function toLowerString() : string { + return strtolower($this->name); + } + + /** + * Checks whether the identifier is a special class name (self, parent or static). + * + * @return bool Whether identifier is a special class name + */ + public function isSpecialClassName() : bool { + return isset(self::$specialClassNames[strtolower($this->name)]); + } + + /** + * Get identifier as string. + * + * @return string Identifier as string + */ + public function __toString() : string { + return $this->name; + } + + public function getType() : string { + return 'Identifier'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/IntersectionType.php b/vendor/nikic/php-parser/lib/PhpParser/Node/IntersectionType.php new file mode 100644 index 0000000000..9208e1392d --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/IntersectionType.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->types = $types; + } + + public function getSubNodeNames() : array { + return ['types']; + } + + public function getType() : string { + return 'IntersectionType'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/MatchArm.php b/vendor/nikic/php-parser/lib/PhpParser/Node/MatchArm.php new file mode 100644 index 0000000000..2ae1c86b85 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/MatchArm.php @@ -0,0 +1,31 @@ +conds = $conds; + $this->body = $body; + $this->attributes = $attributes; + } + + public function getSubNodeNames() : array { + return ['conds', 'body']; + } + + public function getType() : string { + return 'MatchArm'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Name.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Name.php new file mode 100644 index 0000000000..6b1cc9f8ed --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Name.php @@ -0,0 +1,242 @@ + true, + 'parent' => true, + 'static' => true, + ]; + + /** + * Constructs a name node. + * + * @param string|string[]|self $name Name as string, part array or Name instance (copy ctor) + * @param array $attributes Additional attributes + */ + public function __construct($name, array $attributes = []) { + $this->attributes = $attributes; + $this->parts = self::prepareName($name); + } + + public function getSubNodeNames() : array { + return ['parts']; + } + + /** + * Gets the first part of the name, i.e. everything before the first namespace separator. + * + * @return string First part of the name + */ + public function getFirst() : string { + return $this->parts[0]; + } + + /** + * Gets the last part of the name, i.e. everything after the last namespace separator. + * + * @return string Last part of the name + */ + public function getLast() : string { + return $this->parts[count($this->parts) - 1]; + } + + /** + * Checks whether the name is unqualified. (E.g. Name) + * + * @return bool Whether the name is unqualified + */ + public function isUnqualified() : bool { + return 1 === count($this->parts); + } + + /** + * Checks whether the name is qualified. (E.g. Name\Name) + * + * @return bool Whether the name is qualified + */ + public function isQualified() : bool { + return 1 < count($this->parts); + } + + /** + * Checks whether the name is fully qualified. (E.g. \Name) + * + * @return bool Whether the name is fully qualified + */ + public function isFullyQualified() : bool { + return false; + } + + /** + * Checks whether the name is explicitly relative to the current namespace. (E.g. namespace\Name) + * + * @return bool Whether the name is relative + */ + public function isRelative() : bool { + return false; + } + + /** + * Returns a string representation of the name itself, without taking the name type into + * account (e.g., not including a leading backslash for fully qualified names). + * + * @return string String representation + */ + public function toString() : string { + return implode('\\', $this->parts); + } + + /** + * Returns a string representation of the name as it would occur in code (e.g., including + * leading backslash for fully qualified names. + * + * @return string String representation + */ + public function toCodeString() : string { + return $this->toString(); + } + + /** + * Returns lowercased string representation of the name, without taking the name type into + * account (e.g., no leading backslash for fully qualified names). + * + * @return string Lowercased string representation + */ + public function toLowerString() : string { + return strtolower(implode('\\', $this->parts)); + } + + /** + * Checks whether the identifier is a special class name (self, parent or static). + * + * @return bool Whether identifier is a special class name + */ + public function isSpecialClassName() : bool { + return count($this->parts) === 1 + && isset(self::$specialClassNames[strtolower($this->parts[0])]); + } + + /** + * Returns a string representation of the name by imploding the namespace parts with the + * namespace separator. + * + * @return string String representation + */ + public function __toString() : string { + return implode('\\', $this->parts); + } + + /** + * Gets a slice of a name (similar to array_slice). + * + * This method returns a new instance of the same type as the original and with the same + * attributes. + * + * If the slice is empty, null is returned. The null value will be correctly handled in + * concatenations using concat(). + * + * Offset and length have the same meaning as in array_slice(). + * + * @param int $offset Offset to start the slice at (may be negative) + * @param int|null $length Length of the slice (may be negative) + * + * @return static|null Sliced name + */ + public function slice(int $offset, int $length = null) { + $numParts = count($this->parts); + + $realOffset = $offset < 0 ? $offset + $numParts : $offset; + if ($realOffset < 0 || $realOffset > $numParts) { + throw new \OutOfBoundsException(sprintf('Offset %d is out of bounds', $offset)); + } + + if (null === $length) { + $realLength = $numParts - $realOffset; + } else { + $realLength = $length < 0 ? $length + $numParts - $realOffset : $length; + if ($realLength < 0 || $realLength > $numParts) { + throw new \OutOfBoundsException(sprintf('Length %d is out of bounds', $length)); + } + } + + if ($realLength === 0) { + // Empty slice is represented as null + return null; + } + + return new static(array_slice($this->parts, $realOffset, $realLength), $this->attributes); + } + + /** + * Concatenate two names, yielding a new Name instance. + * + * The type of the generated instance depends on which class this method is called on, for + * example Name\FullyQualified::concat() will yield a Name\FullyQualified instance. + * + * If one of the arguments is null, a new instance of the other name will be returned. If both + * arguments are null, null will be returned. As such, writing + * Name::concat($namespace, $shortName) + * where $namespace is a Name node or null will work as expected. + * + * @param string|string[]|self|null $name1 The first name + * @param string|string[]|self|null $name2 The second name + * @param array $attributes Attributes to assign to concatenated name + * + * @return static|null Concatenated name + */ + public static function concat($name1, $name2, array $attributes = []) { + if (null === $name1 && null === $name2) { + return null; + } elseif (null === $name1) { + return new static(self::prepareName($name2), $attributes); + } elseif (null === $name2) { + return new static(self::prepareName($name1), $attributes); + } else { + return new static( + array_merge(self::prepareName($name1), self::prepareName($name2)), $attributes + ); + } + } + + /** + * Prepares a (string, array or Name node) name for use in name changing methods by converting + * it to an array. + * + * @param string|string[]|self $name Name to prepare + * + * @return string[] Prepared name + */ + private static function prepareName($name) : array { + if (\is_string($name)) { + if ('' === $name) { + throw new \InvalidArgumentException('Name cannot be empty'); + } + + return explode('\\', $name); + } elseif (\is_array($name)) { + if (empty($name)) { + throw new \InvalidArgumentException('Name cannot be empty'); + } + + return $name; + } elseif ($name instanceof self) { + return $name->parts; + } + + throw new \InvalidArgumentException( + 'Expected string, array of parts or Name instance' + ); + } + + public function getType() : string { + return 'Name'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Name/FullyQualified.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Name/FullyQualified.php new file mode 100644 index 0000000000..1df93a56b6 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Name/FullyQualified.php @@ -0,0 +1,50 @@ +toString(); + } + + public function getType() : string { + return 'Name_FullyQualified'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Name/Relative.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Name/Relative.php new file mode 100644 index 0000000000..57bf7af2b2 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Name/Relative.php @@ -0,0 +1,50 @@ +toString(); + } + + public function getType() : string { + return 'Name_Relative'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/NullableType.php b/vendor/nikic/php-parser/lib/PhpParser/Node/NullableType.php new file mode 100644 index 0000000000..d68e26a38f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/NullableType.php @@ -0,0 +1,28 @@ +attributes = $attributes; + $this->type = \is_string($type) ? new Identifier($type) : $type; + } + + public function getSubNodeNames() : array { + return ['type']; + } + + public function getType() : string { + return 'NullableType'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Param.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Param.php new file mode 100644 index 0000000000..1e90b79441 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Param.php @@ -0,0 +1,60 @@ +attributes = $attributes; + $this->type = \is_string($type) ? new Identifier($type) : $type; + $this->byRef = $byRef; + $this->variadic = $variadic; + $this->var = $var; + $this->default = $default; + $this->flags = $flags; + $this->attrGroups = $attrGroups; + } + + public function getSubNodeNames() : array { + return ['attrGroups', 'flags', 'type', 'byRef', 'variadic', 'var', 'default']; + } + + public function getType() : string { + return 'Param'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar.php new file mode 100644 index 0000000000..8117909b65 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar.php @@ -0,0 +1,7 @@ +attributes = $attributes; + $this->value = $value; + } + + public function getSubNodeNames() : array { + return ['value']; + } + + /** + * @param mixed[] $attributes + */ + public static function fromString(string $str, array $attributes = []): DNumber + { + $attributes['rawValue'] = $str; + $float = self::parse($str); + + return new DNumber($float, $attributes); + } + + /** + * @internal + * + * Parses a DNUMBER token like PHP would. + * + * @param string $str A string number + * + * @return float The parsed number + */ + public static function parse(string $str) : float { + $str = str_replace('_', '', $str); + + // if string contains any of .eE just cast it to float + if (false !== strpbrk($str, '.eE')) { + return (float) $str; + } + + // otherwise it's an integer notation that overflowed into a float + // if it starts with 0 it's one of the special integer notations + if ('0' === $str[0]) { + // hex + if ('x' === $str[1] || 'X' === $str[1]) { + return hexdec($str); + } + + // bin + if ('b' === $str[1] || 'B' === $str[1]) { + return bindec($str); + } + + // oct + // substr($str, 0, strcspn($str, '89')) cuts the string at the first invalid digit (8 or 9) + // so that only the digits before that are used + return octdec(substr($str, 0, strcspn($str, '89'))); + } + + // dec + return (float) $str; + } + + public function getType() : string { + return 'Scalar_DNumber'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/Encapsed.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/Encapsed.php new file mode 100644 index 0000000000..fa5d2e2681 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/Encapsed.php @@ -0,0 +1,31 @@ +attributes = $attributes; + $this->parts = $parts; + } + + public function getSubNodeNames() : array { + return ['parts']; + } + + public function getType() : string { + return 'Scalar_Encapsed'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/EncapsedStringPart.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/EncapsedStringPart.php new file mode 100644 index 0000000000..bb3194c1d7 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/EncapsedStringPart.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->value = $value; + } + + public function getSubNodeNames() : array { + return ['value']; + } + + public function getType() : string { + return 'Scalar_EncapsedStringPart'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php new file mode 100644 index 0000000000..2cc2b22c8e --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php @@ -0,0 +1,80 @@ +attributes = $attributes; + $this->value = $value; + } + + public function getSubNodeNames() : array { + return ['value']; + } + + /** + * Constructs an LNumber node from a string number literal. + * + * @param string $str String number literal (decimal, octal, hex or binary) + * @param array $attributes Additional attributes + * @param bool $allowInvalidOctal Whether to allow invalid octal numbers (PHP 5) + * + * @return LNumber The constructed LNumber, including kind attribute + */ + public static function fromString(string $str, array $attributes = [], bool $allowInvalidOctal = false) : LNumber { + $attributes['rawValue'] = $str; + + $str = str_replace('_', '', $str); + + if ('0' !== $str[0] || '0' === $str) { + $attributes['kind'] = LNumber::KIND_DEC; + return new LNumber((int) $str, $attributes); + } + + if ('x' === $str[1] || 'X' === $str[1]) { + $attributes['kind'] = LNumber::KIND_HEX; + return new LNumber(hexdec($str), $attributes); + } + + if ('b' === $str[1] || 'B' === $str[1]) { + $attributes['kind'] = LNumber::KIND_BIN; + return new LNumber(bindec($str), $attributes); + } + + if (!$allowInvalidOctal && strpbrk($str, '89')) { + throw new Error('Invalid numeric literal', $attributes); + } + + // Strip optional explicit octal prefix. + if ('o' === $str[1] || 'O' === $str[1]) { + $str = substr($str, 2); + } + + // use intval instead of octdec to get proper cutting behavior with malformed numbers + $attributes['kind'] = LNumber::KIND_OCT; + return new LNumber(intval($str, 8), $attributes); + } + + public function getType() : string { + return 'Scalar_LNumber'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst.php new file mode 100644 index 0000000000..941f0c7620 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst.php @@ -0,0 +1,28 @@ +attributes = $attributes; + } + + public function getSubNodeNames() : array { + return []; + } + + /** + * Get name of magic constant. + * + * @return string Name of magic constant + */ + abstract public function getName() : string; +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Class_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Class_.php new file mode 100644 index 0000000000..244328476d --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Class_.php @@ -0,0 +1,16 @@ + '\\', + '$' => '$', + 'n' => "\n", + 'r' => "\r", + 't' => "\t", + 'f' => "\f", + 'v' => "\v", + 'e' => "\x1B", + ]; + + /** + * Constructs a string scalar node. + * + * @param string $value Value of the string + * @param array $attributes Additional attributes + */ + public function __construct(string $value, array $attributes = []) { + $this->attributes = $attributes; + $this->value = $value; + } + + public function getSubNodeNames() : array { + return ['value']; + } + + /** + * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes + */ + public static function fromString(string $str, array $attributes = [], bool $parseUnicodeEscape = true): self + { + $attributes['kind'] = ($str[0] === "'" || ($str[1] === "'" && ($str[0] === 'b' || $str[0] === 'B'))) + ? Scalar\String_::KIND_SINGLE_QUOTED + : Scalar\String_::KIND_DOUBLE_QUOTED; + + $attributes['rawValue'] = $str; + + $string = self::parse($str, $parseUnicodeEscape); + + return new self($string, $attributes); + } + + /** + * @internal + * + * Parses a string token. + * + * @param string $str String token content + * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes + * + * @return string The parsed string + */ + public static function parse(string $str, bool $parseUnicodeEscape = true) : string { + $bLength = 0; + if ('b' === $str[0] || 'B' === $str[0]) { + $bLength = 1; + } + + if ('\'' === $str[$bLength]) { + return str_replace( + ['\\\\', '\\\''], + ['\\', '\''], + substr($str, $bLength + 1, -1) + ); + } else { + return self::parseEscapeSequences( + substr($str, $bLength + 1, -1), '"', $parseUnicodeEscape + ); + } + } + + /** + * @internal + * + * Parses escape sequences in strings (all string types apart from single quoted). + * + * @param string $str String without quotes + * @param null|string $quote Quote type + * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes + * + * @return string String with escape sequences parsed + */ + public static function parseEscapeSequences(string $str, $quote, bool $parseUnicodeEscape = true) : string { + if (null !== $quote) { + $str = str_replace('\\' . $quote, $quote, $str); + } + + $extra = ''; + if ($parseUnicodeEscape) { + $extra = '|u\{([0-9a-fA-F]+)\}'; + } + + return preg_replace_callback( + '~\\\\([\\\\$nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3}' . $extra . ')~', + function($matches) { + $str = $matches[1]; + + if (isset(self::$replacements[$str])) { + return self::$replacements[$str]; + } elseif ('x' === $str[0] || 'X' === $str[0]) { + return chr(hexdec(substr($str, 1))); + } elseif ('u' === $str[0]) { + return self::codePointToUtf8(hexdec($matches[2])); + } else { + return chr(octdec($str)); + } + }, + $str + ); + } + + /** + * Converts a Unicode code point to its UTF-8 encoded representation. + * + * @param int $num Code point + * + * @return string UTF-8 representation of code point + */ + private static function codePointToUtf8(int $num) : string { + if ($num <= 0x7F) { + return chr($num); + } + if ($num <= 0x7FF) { + return chr(($num>>6) + 0xC0) . chr(($num&0x3F) + 0x80); + } + if ($num <= 0xFFFF) { + return chr(($num>>12) + 0xE0) . chr((($num>>6)&0x3F) + 0x80) . chr(($num&0x3F) + 0x80); + } + if ($num <= 0x1FFFFF) { + return chr(($num>>18) + 0xF0) . chr((($num>>12)&0x3F) + 0x80) + . chr((($num>>6)&0x3F) + 0x80) . chr(($num&0x3F) + 0x80); + } + throw new Error('Invalid UTF-8 codepoint escape sequence: Codepoint too large'); + } + + public function getType() : string { + return 'Scalar_String'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt.php new file mode 100644 index 0000000000..69d33e5796 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt.php @@ -0,0 +1,9 @@ +attributes = $attributes; + $this->num = $num; + } + + public function getSubNodeNames() : array { + return ['num']; + } + + public function getType() : string { + return 'Stmt_Break'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Case_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Case_.php new file mode 100644 index 0000000000..2bf044c900 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Case_.php @@ -0,0 +1,34 @@ +attributes = $attributes; + $this->cond = $cond; + $this->stmts = $stmts; + } + + public function getSubNodeNames() : array { + return ['cond', 'stmts']; + } + + public function getType() : string { + return 'Stmt_Case'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Catch_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Catch_.php new file mode 100644 index 0000000000..9b9c094782 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Catch_.php @@ -0,0 +1,41 @@ +attributes = $attributes; + $this->types = $types; + $this->var = $var; + $this->stmts = $stmts; + } + + public function getSubNodeNames() : array { + return ['types', 'var', 'stmts']; + } + + public function getType() : string { + return 'Stmt_Catch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassConst.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassConst.php new file mode 100644 index 0000000000..1fc7f3362a --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassConst.php @@ -0,0 +1,80 @@ +attributes = $attributes; + $this->flags = $flags; + $this->consts = $consts; + $this->attrGroups = $attrGroups; + } + + public function getSubNodeNames() : array { + return ['attrGroups', 'flags', 'consts']; + } + + /** + * Whether constant is explicitly or implicitly public. + * + * @return bool + */ + public function isPublic() : bool { + return ($this->flags & Class_::MODIFIER_PUBLIC) !== 0 + || ($this->flags & Class_::VISIBILITY_MODIFIER_MASK) === 0; + } + + /** + * Whether constant is protected. + * + * @return bool + */ + public function isProtected() : bool { + return (bool) ($this->flags & Class_::MODIFIER_PROTECTED); + } + + /** + * Whether constant is private. + * + * @return bool + */ + public function isPrivate() : bool { + return (bool) ($this->flags & Class_::MODIFIER_PRIVATE); + } + + /** + * Whether constant is final. + * + * @return bool + */ + public function isFinal() : bool { + return (bool) ($this->flags & Class_::MODIFIER_FINAL); + } + + public function getType() : string { + return 'Stmt_ClassConst'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassLike.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassLike.php new file mode 100644 index 0000000000..2fa4e861b3 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassLike.php @@ -0,0 +1,109 @@ +stmts as $stmt) { + if ($stmt instanceof TraitUse) { + $traitUses[] = $stmt; + } + } + return $traitUses; + } + + /** + * @return ClassConst[] + */ + public function getConstants() : array { + $constants = []; + foreach ($this->stmts as $stmt) { + if ($stmt instanceof ClassConst) { + $constants[] = $stmt; + } + } + return $constants; + } + + /** + * @return Property[] + */ + public function getProperties() : array { + $properties = []; + foreach ($this->stmts as $stmt) { + if ($stmt instanceof Property) { + $properties[] = $stmt; + } + } + return $properties; + } + + /** + * Gets property with the given name defined directly in this class/interface/trait. + * + * @param string $name Name of the property + * + * @return Property|null Property node or null if the property does not exist + */ + public function getProperty(string $name) { + foreach ($this->stmts as $stmt) { + if ($stmt instanceof Property) { + foreach ($stmt->props as $prop) { + if ($prop instanceof PropertyProperty && $name === $prop->name->toString()) { + return $stmt; + } + } + } + } + return null; + } + + /** + * Gets all methods defined directly in this class/interface/trait + * + * @return ClassMethod[] + */ + public function getMethods() : array { + $methods = []; + foreach ($this->stmts as $stmt) { + if ($stmt instanceof ClassMethod) { + $methods[] = $stmt; + } + } + return $methods; + } + + /** + * Gets method with the given name defined directly in this class/interface/trait. + * + * @param string $name Name of the method (compared case-insensitively) + * + * @return ClassMethod|null Method node or null if the method does not exist + */ + public function getMethod(string $name) { + $lowerName = strtolower($name); + foreach ($this->stmts as $stmt) { + if ($stmt instanceof ClassMethod && $lowerName === $stmt->name->toLowerString()) { + return $stmt; + } + } + return null; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassMethod.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassMethod.php new file mode 100644 index 0000000000..09b877a929 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassMethod.php @@ -0,0 +1,159 @@ + true, + '__destruct' => true, + '__call' => true, + '__callstatic' => true, + '__get' => true, + '__set' => true, + '__isset' => true, + '__unset' => true, + '__sleep' => true, + '__wakeup' => true, + '__tostring' => true, + '__set_state' => true, + '__clone' => true, + '__invoke' => true, + '__debuginfo' => true, + ]; + + /** + * Constructs a class method node. + * + * @param string|Node\Identifier $name Name + * @param array $subNodes Array of the following optional subnodes: + * 'flags => MODIFIER_PUBLIC: Flags + * 'byRef' => false : Whether to return by reference + * 'params' => array() : Parameters + * 'returnType' => null : Return type + * 'stmts' => array() : Statements + * 'attrGroups' => array() : PHP attribute groups + * @param array $attributes Additional attributes + */ + public function __construct($name, array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->flags = $subNodes['flags'] ?? $subNodes['type'] ?? 0; + $this->byRef = $subNodes['byRef'] ?? false; + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->params = $subNodes['params'] ?? []; + $returnType = $subNodes['returnType'] ?? null; + $this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType; + $this->stmts = array_key_exists('stmts', $subNodes) ? $subNodes['stmts'] : []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + } + + public function getSubNodeNames() : array { + return ['attrGroups', 'flags', 'byRef', 'name', 'params', 'returnType', 'stmts']; + } + + public function returnsByRef() : bool { + return $this->byRef; + } + + public function getParams() : array { + return $this->params; + } + + public function getReturnType() { + return $this->returnType; + } + + public function getStmts() { + return $this->stmts; + } + + public function getAttrGroups() : array { + return $this->attrGroups; + } + + /** + * Whether the method is explicitly or implicitly public. + * + * @return bool + */ + public function isPublic() : bool { + return ($this->flags & Class_::MODIFIER_PUBLIC) !== 0 + || ($this->flags & Class_::VISIBILITY_MODIFIER_MASK) === 0; + } + + /** + * Whether the method is protected. + * + * @return bool + */ + public function isProtected() : bool { + return (bool) ($this->flags & Class_::MODIFIER_PROTECTED); + } + + /** + * Whether the method is private. + * + * @return bool + */ + public function isPrivate() : bool { + return (bool) ($this->flags & Class_::MODIFIER_PRIVATE); + } + + /** + * Whether the method is abstract. + * + * @return bool + */ + public function isAbstract() : bool { + return (bool) ($this->flags & Class_::MODIFIER_ABSTRACT); + } + + /** + * Whether the method is final. + * + * @return bool + */ + public function isFinal() : bool { + return (bool) ($this->flags & Class_::MODIFIER_FINAL); + } + + /** + * Whether the method is static. + * + * @return bool + */ + public function isStatic() : bool { + return (bool) ($this->flags & Class_::MODIFIER_STATIC); + } + + /** + * Whether the method is magic. + * + * @return bool + */ + public function isMagic() : bool { + return isset(self::$magicNames[$this->name->toLowerString()]); + } + + public function getType() : string { + return 'Stmt_ClassMethod'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Class_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Class_.php new file mode 100644 index 0000000000..52ed6c6cd6 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Class_.php @@ -0,0 +1,137 @@ + 0 : Flags + * 'extends' => null : Name of extended class + * 'implements' => array(): Names of implemented interfaces + * 'stmts' => array(): Statements + * 'attrGroups' => array(): PHP attribute groups + * @param array $attributes Additional attributes + */ + public function __construct($name, array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->flags = $subNodes['flags'] ?? $subNodes['type'] ?? 0; + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->extends = $subNodes['extends'] ?? null; + $this->implements = $subNodes['implements'] ?? []; + $this->stmts = $subNodes['stmts'] ?? []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + } + + public function getSubNodeNames() : array { + return ['attrGroups', 'flags', 'name', 'extends', 'implements', 'stmts']; + } + + /** + * Whether the class is explicitly abstract. + * + * @return bool + */ + public function isAbstract() : bool { + return (bool) ($this->flags & self::MODIFIER_ABSTRACT); + } + + /** + * Whether the class is final. + * + * @return bool + */ + public function isFinal() : bool { + return (bool) ($this->flags & self::MODIFIER_FINAL); + } + + public function isReadonly() : bool { + return (bool) ($this->flags & self::MODIFIER_READONLY); + } + + /** + * Whether the class is anonymous. + * + * @return bool + */ + public function isAnonymous() : bool { + return null === $this->name; + } + + /** + * @internal + */ + public static function verifyClassModifier($a, $b) { + if ($a & self::MODIFIER_ABSTRACT && $b & self::MODIFIER_ABSTRACT) { + throw new Error('Multiple abstract modifiers are not allowed'); + } + + if ($a & self::MODIFIER_FINAL && $b & self::MODIFIER_FINAL) { + throw new Error('Multiple final modifiers are not allowed'); + } + + if ($a & self::MODIFIER_READONLY && $b & self::MODIFIER_READONLY) { + throw new Error('Multiple readonly modifiers are not allowed'); + } + + if ($a & 48 && $b & 48) { + throw new Error('Cannot use the final modifier on an abstract class'); + } + } + + /** + * @internal + */ + public static function verifyModifier($a, $b) { + if ($a & self::VISIBILITY_MODIFIER_MASK && $b & self::VISIBILITY_MODIFIER_MASK) { + throw new Error('Multiple access type modifiers are not allowed'); + } + + if ($a & self::MODIFIER_ABSTRACT && $b & self::MODIFIER_ABSTRACT) { + throw new Error('Multiple abstract modifiers are not allowed'); + } + + if ($a & self::MODIFIER_STATIC && $b & self::MODIFIER_STATIC) { + throw new Error('Multiple static modifiers are not allowed'); + } + + if ($a & self::MODIFIER_FINAL && $b & self::MODIFIER_FINAL) { + throw new Error('Multiple final modifiers are not allowed'); + } + + if ($a & self::MODIFIER_READONLY && $b & self::MODIFIER_READONLY) { + throw new Error('Multiple readonly modifiers are not allowed'); + } + + if ($a & 48 && $b & 48) { + throw new Error('Cannot use the final modifier on an abstract class member'); + } + } + + public function getType() : string { + return 'Stmt_Class'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Const_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Const_.php new file mode 100644 index 0000000000..e6316345ee --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Const_.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->consts = $consts; + } + + public function getSubNodeNames() : array { + return ['consts']; + } + + public function getType() : string { + return 'Stmt_Const'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Continue_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Continue_.php new file mode 100644 index 0000000000..24882683b3 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Continue_.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->num = $num; + } + + public function getSubNodeNames() : array { + return ['num']; + } + + public function getType() : string { + return 'Stmt_Continue'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/DeclareDeclare.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/DeclareDeclare.php new file mode 100644 index 0000000000..ac07f30c78 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/DeclareDeclare.php @@ -0,0 +1,34 @@ +value pair node. + * + * @param string|Node\Identifier $key Key + * @param Node\Expr $value Value + * @param array $attributes Additional attributes + */ + public function __construct($key, Node\Expr $value, array $attributes = []) { + $this->attributes = $attributes; + $this->key = \is_string($key) ? new Node\Identifier($key) : $key; + $this->value = $value; + } + + public function getSubNodeNames() : array { + return ['key', 'value']; + } + + public function getType() : string { + return 'Stmt_DeclareDeclare'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Declare_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Declare_.php new file mode 100644 index 0000000000..f46ff0bafd --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Declare_.php @@ -0,0 +1,34 @@ +attributes = $attributes; + $this->declares = $declares; + $this->stmts = $stmts; + } + + public function getSubNodeNames() : array { + return ['declares', 'stmts']; + } + + public function getType() : string { + return 'Stmt_Declare'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Do_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Do_.php new file mode 100644 index 0000000000..78e90da03a --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Do_.php @@ -0,0 +1,34 @@ +attributes = $attributes; + $this->cond = $cond; + $this->stmts = $stmts; + } + + public function getSubNodeNames() : array { + return ['stmts', 'cond']; + } + + public function getType() : string { + return 'Stmt_Do'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Echo_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Echo_.php new file mode 100644 index 0000000000..7cc50d5d6e --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Echo_.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->exprs = $exprs; + } + + public function getSubNodeNames() : array { + return ['exprs']; + } + + public function getType() : string { + return 'Stmt_Echo'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ElseIf_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ElseIf_.php new file mode 100644 index 0000000000..eef1ece324 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ElseIf_.php @@ -0,0 +1,34 @@ +attributes = $attributes; + $this->cond = $cond; + $this->stmts = $stmts; + } + + public function getSubNodeNames() : array { + return ['cond', 'stmts']; + } + + public function getType() : string { + return 'Stmt_ElseIf'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Else_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Else_.php new file mode 100644 index 0000000000..0e61778e26 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Else_.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->stmts = $stmts; + } + + public function getSubNodeNames() : array { + return ['stmts']; + } + + public function getType() : string { + return 'Stmt_Else'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/EnumCase.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/EnumCase.php new file mode 100644 index 0000000000..5beff8b39f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/EnumCase.php @@ -0,0 +1,37 @@ +name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->expr = $expr; + $this->attrGroups = $attrGroups; + } + + public function getSubNodeNames() : array { + return ['attrGroups', 'name', 'expr']; + } + + public function getType() : string { + return 'Stmt_EnumCase'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Enum_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Enum_.php new file mode 100644 index 0000000000..3a50c225db --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Enum_.php @@ -0,0 +1,40 @@ + null : Scalar type + * 'implements' => array() : Names of implemented interfaces + * 'stmts' => array() : Statements + * 'attrGroups' => array() : PHP attribute groups + * @param array $attributes Additional attributes + */ + public function __construct($name, array $subNodes = [], array $attributes = []) { + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->scalarType = $subNodes['scalarType'] ?? null; + $this->implements = $subNodes['implements'] ?? []; + $this->stmts = $subNodes['stmts'] ?? []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + + parent::__construct($attributes); + } + + public function getSubNodeNames() : array { + return ['attrGroups', 'name', 'scalarType', 'implements', 'stmts']; + } + + public function getType() : string { + return 'Stmt_Enum'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Expression.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Expression.php new file mode 100644 index 0000000000..99d1687ded --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Expression.php @@ -0,0 +1,33 @@ +attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames() : array { + return ['expr']; + } + + public function getType() : string { + return 'Stmt_Expression'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Finally_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Finally_.php new file mode 100644 index 0000000000..d55b8b6872 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Finally_.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->stmts = $stmts; + } + + public function getSubNodeNames() : array { + return ['stmts']; + } + + public function getType() : string { + return 'Stmt_Finally'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/For_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/For_.php new file mode 100644 index 0000000000..1323d37cf3 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/For_.php @@ -0,0 +1,43 @@ + array(): Init expressions + * 'cond' => array(): Loop conditions + * 'loop' => array(): Loop expressions + * 'stmts' => array(): Statements + * @param array $attributes Additional attributes + */ + public function __construct(array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->init = $subNodes['init'] ?? []; + $this->cond = $subNodes['cond'] ?? []; + $this->loop = $subNodes['loop'] ?? []; + $this->stmts = $subNodes['stmts'] ?? []; + } + + public function getSubNodeNames() : array { + return ['init', 'cond', 'loop', 'stmts']; + } + + public function getType() : string { + return 'Stmt_For'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Foreach_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Foreach_.php new file mode 100644 index 0000000000..0556a7ce5f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Foreach_.php @@ -0,0 +1,47 @@ + null : Variable to assign key to + * 'byRef' => false : Whether to assign value by reference + * 'stmts' => array(): Statements + * @param array $attributes Additional attributes + */ + public function __construct(Node\Expr $expr, Node\Expr $valueVar, array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + $this->keyVar = $subNodes['keyVar'] ?? null; + $this->byRef = $subNodes['byRef'] ?? false; + $this->valueVar = $valueVar; + $this->stmts = $subNodes['stmts'] ?? []; + } + + public function getSubNodeNames() : array { + return ['expr', 'keyVar', 'byRef', 'valueVar', 'stmts']; + } + + public function getType() : string { + return 'Stmt_Foreach'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Function_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Function_.php new file mode 100644 index 0000000000..c2ccae24ee --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Function_.php @@ -0,0 +1,77 @@ + false : Whether to return by reference + * 'params' => array(): Parameters + * 'returnType' => null : Return type + * 'stmts' => array(): Statements + * 'attrGroups' => array(): PHP attribute groups + * @param array $attributes Additional attributes + */ + public function __construct($name, array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->byRef = $subNodes['byRef'] ?? false; + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->params = $subNodes['params'] ?? []; + $returnType = $subNodes['returnType'] ?? null; + $this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType; + $this->stmts = $subNodes['stmts'] ?? []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + } + + public function getSubNodeNames() : array { + return ['attrGroups', 'byRef', 'name', 'params', 'returnType', 'stmts']; + } + + public function returnsByRef() : bool { + return $this->byRef; + } + + public function getParams() : array { + return $this->params; + } + + public function getReturnType() { + return $this->returnType; + } + + public function getAttrGroups() : array { + return $this->attrGroups; + } + + /** @return Node\Stmt[] */ + public function getStmts() : array { + return $this->stmts; + } + + public function getType() : string { + return 'Stmt_Function'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Global_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Global_.php new file mode 100644 index 0000000000..a0022ad932 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Global_.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->vars = $vars; + } + + public function getSubNodeNames() : array { + return ['vars']; + } + + public function getType() : string { + return 'Stmt_Global'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Goto_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Goto_.php new file mode 100644 index 0000000000..24a57f7807 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Goto_.php @@ -0,0 +1,31 @@ +attributes = $attributes; + $this->name = \is_string($name) ? new Identifier($name) : $name; + } + + public function getSubNodeNames() : array { + return ['name']; + } + + public function getType() : string { + return 'Stmt_Goto'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/GroupUse.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/GroupUse.php new file mode 100644 index 0000000000..24520d2233 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/GroupUse.php @@ -0,0 +1,39 @@ +attributes = $attributes; + $this->type = $type; + $this->prefix = $prefix; + $this->uses = $uses; + } + + public function getSubNodeNames() : array { + return ['type', 'prefix', 'uses']; + } + + public function getType() : string { + return 'Stmt_GroupUse'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/HaltCompiler.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/HaltCompiler.php new file mode 100644 index 0000000000..8e624e0f1f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/HaltCompiler.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->remaining = $remaining; + } + + public function getSubNodeNames() : array { + return ['remaining']; + } + + public function getType() : string { + return 'Stmt_HaltCompiler'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/If_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/If_.php new file mode 100644 index 0000000000..a1bae4bf89 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/If_.php @@ -0,0 +1,43 @@ + array(): Statements + * 'elseifs' => array(): Elseif clauses + * 'else' => null : Else clause + * @param array $attributes Additional attributes + */ + public function __construct(Node\Expr $cond, array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->cond = $cond; + $this->stmts = $subNodes['stmts'] ?? []; + $this->elseifs = $subNodes['elseifs'] ?? []; + $this->else = $subNodes['else'] ?? null; + } + + public function getSubNodeNames() : array { + return ['cond', 'stmts', 'elseifs', 'else']; + } + + public function getType() : string { + return 'Stmt_If'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/InlineHTML.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/InlineHTML.php new file mode 100644 index 0000000000..0711d2842c --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/InlineHTML.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->value = $value; + } + + public function getSubNodeNames() : array { + return ['value']; + } + + public function getType() : string { + return 'Stmt_InlineHTML'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Interface_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Interface_.php new file mode 100644 index 0000000000..4d587dd484 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Interface_.php @@ -0,0 +1,37 @@ + array(): Name of extended interfaces + * 'stmts' => array(): Statements + * 'attrGroups' => array(): PHP attribute groups + * @param array $attributes Additional attributes + */ + public function __construct($name, array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->extends = $subNodes['extends'] ?? []; + $this->stmts = $subNodes['stmts'] ?? []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + } + + public function getSubNodeNames() : array { + return ['attrGroups', 'name', 'extends', 'stmts']; + } + + public function getType() : string { + return 'Stmt_Interface'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Label.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Label.php new file mode 100644 index 0000000000..3edcb3be7e --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Label.php @@ -0,0 +1,31 @@ +attributes = $attributes; + $this->name = \is_string($name) ? new Identifier($name) : $name; + } + + public function getSubNodeNames() : array { + return ['name']; + } + + public function getType() : string { + return 'Stmt_Label'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Namespace_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Namespace_.php new file mode 100644 index 0000000000..c63204577c --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Namespace_.php @@ -0,0 +1,38 @@ +attributes = $attributes; + $this->name = $name; + $this->stmts = $stmts; + } + + public function getSubNodeNames() : array { + return ['name', 'stmts']; + } + + public function getType() : string { + return 'Stmt_Namespace'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Nop.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Nop.php new file mode 100644 index 0000000000..f86f8df7d3 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Nop.php @@ -0,0 +1,17 @@ +attributes = $attributes; + $this->flags = $flags; + $this->props = $props; + $this->type = \is_string($type) ? new Identifier($type) : $type; + $this->attrGroups = $attrGroups; + } + + public function getSubNodeNames() : array { + return ['attrGroups', 'flags', 'type', 'props']; + } + + /** + * Whether the property is explicitly or implicitly public. + * + * @return bool + */ + public function isPublic() : bool { + return ($this->flags & Class_::MODIFIER_PUBLIC) !== 0 + || ($this->flags & Class_::VISIBILITY_MODIFIER_MASK) === 0; + } + + /** + * Whether the property is protected. + * + * @return bool + */ + public function isProtected() : bool { + return (bool) ($this->flags & Class_::MODIFIER_PROTECTED); + } + + /** + * Whether the property is private. + * + * @return bool + */ + public function isPrivate() : bool { + return (bool) ($this->flags & Class_::MODIFIER_PRIVATE); + } + + /** + * Whether the property is static. + * + * @return bool + */ + public function isStatic() : bool { + return (bool) ($this->flags & Class_::MODIFIER_STATIC); + } + + /** + * Whether the property is readonly. + * + * @return bool + */ + public function isReadonly() : bool { + return (bool) ($this->flags & Class_::MODIFIER_READONLY); + } + + public function getType() : string { + return 'Stmt_Property'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/PropertyProperty.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/PropertyProperty.php new file mode 100644 index 0000000000..205731e20e --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/PropertyProperty.php @@ -0,0 +1,34 @@ +attributes = $attributes; + $this->name = \is_string($name) ? new Node\VarLikeIdentifier($name) : $name; + $this->default = $default; + } + + public function getSubNodeNames() : array { + return ['name', 'default']; + } + + public function getType() : string { + return 'Stmt_PropertyProperty'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Return_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Return_.php new file mode 100644 index 0000000000..efc578c58f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Return_.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames() : array { + return ['expr']; + } + + public function getType() : string { + return 'Stmt_Return'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/StaticVar.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/StaticVar.php new file mode 100644 index 0000000000..29584560d3 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/StaticVar.php @@ -0,0 +1,37 @@ +attributes = $attributes; + $this->var = $var; + $this->default = $default; + } + + public function getSubNodeNames() : array { + return ['var', 'default']; + } + + public function getType() : string { + return 'Stmt_StaticVar'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Static_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Static_.php new file mode 100644 index 0000000000..464898ffa6 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Static_.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->vars = $vars; + } + + public function getSubNodeNames() : array { + return ['vars']; + } + + public function getType() : string { + return 'Stmt_Static'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Switch_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Switch_.php new file mode 100644 index 0000000000..2c8dae0221 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Switch_.php @@ -0,0 +1,34 @@ +attributes = $attributes; + $this->cond = $cond; + $this->cases = $cases; + } + + public function getSubNodeNames() : array { + return ['cond', 'cases']; + } + + public function getType() : string { + return 'Stmt_Switch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Throw_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Throw_.php new file mode 100644 index 0000000000..a34e2b3624 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Throw_.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames() : array { + return ['expr']; + } + + public function getType() : string { + return 'Stmt_Throw'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUse.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUse.php new file mode 100644 index 0000000000..9e97053b40 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUse.php @@ -0,0 +1,34 @@ +attributes = $attributes; + $this->traits = $traits; + $this->adaptations = $adaptations; + } + + public function getSubNodeNames() : array { + return ['traits', 'adaptations']; + } + + public function getType() : string { + return 'Stmt_TraitUse'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation.php new file mode 100644 index 0000000000..8bdd2c041f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation.php @@ -0,0 +1,13 @@ +attributes = $attributes; + $this->trait = $trait; + $this->method = \is_string($method) ? new Node\Identifier($method) : $method; + $this->newModifier = $newModifier; + $this->newName = \is_string($newName) ? new Node\Identifier($newName) : $newName; + } + + public function getSubNodeNames() : array { + return ['trait', 'method', 'newModifier', 'newName']; + } + + public function getType() : string { + return 'Stmt_TraitUseAdaptation_Alias'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php new file mode 100644 index 0000000000..80385f64e3 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php @@ -0,0 +1,34 @@ +attributes = $attributes; + $this->trait = $trait; + $this->method = \is_string($method) ? new Node\Identifier($method) : $method; + $this->insteadof = $insteadof; + } + + public function getSubNodeNames() : array { + return ['trait', 'method', 'insteadof']; + } + + public function getType() : string { + return 'Stmt_TraitUseAdaptation_Precedence'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Trait_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Trait_.php new file mode 100644 index 0000000000..0cec203ac7 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Trait_.php @@ -0,0 +1,32 @@ + array(): Statements + * 'attrGroups' => array(): PHP attribute groups + * @param array $attributes Additional attributes + */ + public function __construct($name, array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->stmts = $subNodes['stmts'] ?? []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + } + + public function getSubNodeNames() : array { + return ['attrGroups', 'name', 'stmts']; + } + + public function getType() : string { + return 'Stmt_Trait'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TryCatch.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TryCatch.php new file mode 100644 index 0000000000..7fc158c570 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TryCatch.php @@ -0,0 +1,38 @@ +attributes = $attributes; + $this->stmts = $stmts; + $this->catches = $catches; + $this->finally = $finally; + } + + public function getSubNodeNames() : array { + return ['stmts', 'catches', 'finally']; + } + + public function getType() : string { + return 'Stmt_TryCatch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Unset_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Unset_.php new file mode 100644 index 0000000000..310e427aa2 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Unset_.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->vars = $vars; + } + + public function getSubNodeNames() : array { + return ['vars']; + } + + public function getType() : string { + return 'Stmt_Unset'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/UseUse.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/UseUse.php new file mode 100644 index 0000000000..32bd7847da --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/UseUse.php @@ -0,0 +1,52 @@ +attributes = $attributes; + $this->type = $type; + $this->name = $name; + $this->alias = \is_string($alias) ? new Identifier($alias) : $alias; + } + + public function getSubNodeNames() : array { + return ['type', 'name', 'alias']; + } + + /** + * Get alias. If not explicitly given this is the last component of the used name. + * + * @return Identifier + */ + public function getAlias() : Identifier { + if (null !== $this->alias) { + return $this->alias; + } + + return new Identifier($this->name->getLast()); + } + + public function getType() : string { + return 'Stmt_UseUse'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Use_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Use_.php new file mode 100644 index 0000000000..8753da313d --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Use_.php @@ -0,0 +1,47 @@ +attributes = $attributes; + $this->type = $type; + $this->uses = $uses; + } + + public function getSubNodeNames() : array { + return ['type', 'uses']; + } + + public function getType() : string { + return 'Stmt_Use'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/While_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/While_.php new file mode 100644 index 0000000000..f41034f8c2 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/While_.php @@ -0,0 +1,34 @@ +attributes = $attributes; + $this->cond = $cond; + $this->stmts = $stmts; + } + + public function getSubNodeNames() : array { + return ['cond', 'stmts']; + } + + public function getType() : string { + return 'Stmt_While'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/UnionType.php b/vendor/nikic/php-parser/lib/PhpParser/Node/UnionType.php new file mode 100644 index 0000000000..61c2d81062 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/UnionType.php @@ -0,0 +1,28 @@ +attributes = $attributes; + $this->types = $types; + } + + public function getSubNodeNames() : array { + return ['types']; + } + + public function getType() : string { + return 'UnionType'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/VarLikeIdentifier.php b/vendor/nikic/php-parser/lib/PhpParser/Node/VarLikeIdentifier.php new file mode 100644 index 0000000000..a30807a6d5 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/VarLikeIdentifier.php @@ -0,0 +1,17 @@ +attributes = $attributes; + } + + public function getType(): string { + return 'VariadicPlaceholder'; + } + + public function getSubNodeNames(): array { + return []; + } +} \ No newline at end of file diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeAbstract.php b/vendor/nikic/php-parser/lib/PhpParser/NodeAbstract.php new file mode 100644 index 0000000000..04514da116 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeAbstract.php @@ -0,0 +1,178 @@ +attributes = $attributes; + } + + /** + * Gets line the node started in (alias of getStartLine). + * + * @return int Start line (or -1 if not available) + */ + public function getLine() : int { + return $this->attributes['startLine'] ?? -1; + } + + /** + * Gets line the node started in. + * + * Requires the 'startLine' attribute to be enabled in the lexer (enabled by default). + * + * @return int Start line (or -1 if not available) + */ + public function getStartLine() : int { + return $this->attributes['startLine'] ?? -1; + } + + /** + * Gets the line the node ended in. + * + * Requires the 'endLine' attribute to be enabled in the lexer (enabled by default). + * + * @return int End line (or -1 if not available) + */ + public function getEndLine() : int { + return $this->attributes['endLine'] ?? -1; + } + + /** + * Gets the token offset of the first token that is part of this node. + * + * The offset is an index into the array returned by Lexer::getTokens(). + * + * Requires the 'startTokenPos' attribute to be enabled in the lexer (DISABLED by default). + * + * @return int Token start position (or -1 if not available) + */ + public function getStartTokenPos() : int { + return $this->attributes['startTokenPos'] ?? -1; + } + + /** + * Gets the token offset of the last token that is part of this node. + * + * The offset is an index into the array returned by Lexer::getTokens(). + * + * Requires the 'endTokenPos' attribute to be enabled in the lexer (DISABLED by default). + * + * @return int Token end position (or -1 if not available) + */ + public function getEndTokenPos() : int { + return $this->attributes['endTokenPos'] ?? -1; + } + + /** + * Gets the file offset of the first character that is part of this node. + * + * Requires the 'startFilePos' attribute to be enabled in the lexer (DISABLED by default). + * + * @return int File start position (or -1 if not available) + */ + public function getStartFilePos() : int { + return $this->attributes['startFilePos'] ?? -1; + } + + /** + * Gets the file offset of the last character that is part of this node. + * + * Requires the 'endFilePos' attribute to be enabled in the lexer (DISABLED by default). + * + * @return int File end position (or -1 if not available) + */ + public function getEndFilePos() : int { + return $this->attributes['endFilePos'] ?? -1; + } + + /** + * Gets all comments directly preceding this node. + * + * The comments are also available through the "comments" attribute. + * + * @return Comment[] + */ + public function getComments() : array { + return $this->attributes['comments'] ?? []; + } + + /** + * Gets the doc comment of the node. + * + * @return null|Comment\Doc Doc comment object or null + */ + public function getDocComment() { + $comments = $this->getComments(); + for ($i = count($comments) - 1; $i >= 0; $i--) { + $comment = $comments[$i]; + if ($comment instanceof Comment\Doc) { + return $comment; + } + } + + return null; + } + + /** + * Sets the doc comment of the node. + * + * This will either replace an existing doc comment or add it to the comments array. + * + * @param Comment\Doc $docComment Doc comment to set + */ + public function setDocComment(Comment\Doc $docComment) { + $comments = $this->getComments(); + for ($i = count($comments) - 1; $i >= 0; $i--) { + if ($comments[$i] instanceof Comment\Doc) { + // Replace existing doc comment. + $comments[$i] = $docComment; + $this->setAttribute('comments', $comments); + return; + } + } + + // Append new doc comment. + $comments[] = $docComment; + $this->setAttribute('comments', $comments); + } + + public function setAttribute(string $key, $value) { + $this->attributes[$key] = $value; + } + + public function hasAttribute(string $key) : bool { + return array_key_exists($key, $this->attributes); + } + + public function getAttribute(string $key, $default = null) { + if (array_key_exists($key, $this->attributes)) { + return $this->attributes[$key]; + } + + return $default; + } + + public function getAttributes() : array { + return $this->attributes; + } + + public function setAttributes(array $attributes) { + $this->attributes = $attributes; + } + + /** + * @return array + */ + public function jsonSerialize() : array { + return ['nodeType' => $this->getType()] + get_object_vars($this); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeDumper.php b/vendor/nikic/php-parser/lib/PhpParser/NodeDumper.php new file mode 100644 index 0000000000..ba622efd12 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeDumper.php @@ -0,0 +1,206 @@ +dumpComments = !empty($options['dumpComments']); + $this->dumpPositions = !empty($options['dumpPositions']); + } + + /** + * Dumps a node or array. + * + * @param array|Node $node Node or array to dump + * @param string|null $code Code corresponding to dumped AST. This only needs to be passed if + * the dumpPositions option is enabled and the dumping of node offsets + * is desired. + * + * @return string Dumped value + */ + public function dump($node, string $code = null) : string { + $this->code = $code; + return $this->dumpRecursive($node); + } + + protected function dumpRecursive($node) { + if ($node instanceof Node) { + $r = $node->getType(); + if ($this->dumpPositions && null !== $p = $this->dumpPosition($node)) { + $r .= $p; + } + $r .= '('; + + foreach ($node->getSubNodeNames() as $key) { + $r .= "\n " . $key . ': '; + + $value = $node->$key; + if (null === $value) { + $r .= 'null'; + } elseif (false === $value) { + $r .= 'false'; + } elseif (true === $value) { + $r .= 'true'; + } elseif (is_scalar($value)) { + if ('flags' === $key || 'newModifier' === $key) { + $r .= $this->dumpFlags($value); + } elseif ('type' === $key && $node instanceof Include_) { + $r .= $this->dumpIncludeType($value); + } elseif ('type' === $key + && ($node instanceof Use_ || $node instanceof UseUse || $node instanceof GroupUse)) { + $r .= $this->dumpUseType($value); + } else { + $r .= $value; + } + } else { + $r .= str_replace("\n", "\n ", $this->dumpRecursive($value)); + } + } + + if ($this->dumpComments && $comments = $node->getComments()) { + $r .= "\n comments: " . str_replace("\n", "\n ", $this->dumpRecursive($comments)); + } + } elseif (is_array($node)) { + $r = 'array('; + + foreach ($node as $key => $value) { + $r .= "\n " . $key . ': '; + + if (null === $value) { + $r .= 'null'; + } elseif (false === $value) { + $r .= 'false'; + } elseif (true === $value) { + $r .= 'true'; + } elseif (is_scalar($value)) { + $r .= $value; + } else { + $r .= str_replace("\n", "\n ", $this->dumpRecursive($value)); + } + } + } elseif ($node instanceof Comment) { + return $node->getReformattedText(); + } else { + throw new \InvalidArgumentException('Can only dump nodes and arrays.'); + } + + return $r . "\n)"; + } + + protected function dumpFlags($flags) { + $strs = []; + if ($flags & Class_::MODIFIER_PUBLIC) { + $strs[] = 'MODIFIER_PUBLIC'; + } + if ($flags & Class_::MODIFIER_PROTECTED) { + $strs[] = 'MODIFIER_PROTECTED'; + } + if ($flags & Class_::MODIFIER_PRIVATE) { + $strs[] = 'MODIFIER_PRIVATE'; + } + if ($flags & Class_::MODIFIER_ABSTRACT) { + $strs[] = 'MODIFIER_ABSTRACT'; + } + if ($flags & Class_::MODIFIER_STATIC) { + $strs[] = 'MODIFIER_STATIC'; + } + if ($flags & Class_::MODIFIER_FINAL) { + $strs[] = 'MODIFIER_FINAL'; + } + if ($flags & Class_::MODIFIER_READONLY) { + $strs[] = 'MODIFIER_READONLY'; + } + + if ($strs) { + return implode(' | ', $strs) . ' (' . $flags . ')'; + } else { + return $flags; + } + } + + protected function dumpIncludeType($type) { + $map = [ + Include_::TYPE_INCLUDE => 'TYPE_INCLUDE', + Include_::TYPE_INCLUDE_ONCE => 'TYPE_INCLUDE_ONCE', + Include_::TYPE_REQUIRE => 'TYPE_REQUIRE', + Include_::TYPE_REQUIRE_ONCE => 'TYPE_REQUIRE_ONCE', + ]; + + if (!isset($map[$type])) { + return $type; + } + return $map[$type] . ' (' . $type . ')'; + } + + protected function dumpUseType($type) { + $map = [ + Use_::TYPE_UNKNOWN => 'TYPE_UNKNOWN', + Use_::TYPE_NORMAL => 'TYPE_NORMAL', + Use_::TYPE_FUNCTION => 'TYPE_FUNCTION', + Use_::TYPE_CONSTANT => 'TYPE_CONSTANT', + ]; + + if (!isset($map[$type])) { + return $type; + } + return $map[$type] . ' (' . $type . ')'; + } + + /** + * Dump node position, if possible. + * + * @param Node $node Node for which to dump position + * + * @return string|null Dump of position, or null if position information not available + */ + protected function dumpPosition(Node $node) { + if (!$node->hasAttribute('startLine') || !$node->hasAttribute('endLine')) { + return null; + } + + $start = $node->getStartLine(); + $end = $node->getEndLine(); + if ($node->hasAttribute('startFilePos') && $node->hasAttribute('endFilePos') + && null !== $this->code + ) { + $start .= ':' . $this->toColumn($this->code, $node->getStartFilePos()); + $end .= ':' . $this->toColumn($this->code, $node->getEndFilePos()); + } + return "[$start - $end]"; + } + + // Copied from Error class + private function toColumn($code, $pos) { + if ($pos > strlen($code)) { + throw new \RuntimeException('Invalid position information'); + } + + $lineStartPos = strrpos($code, "\n", $pos - strlen($code)); + if (false === $lineStartPos) { + $lineStartPos = -1; + } + + return $pos - $lineStartPos; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeFinder.php b/vendor/nikic/php-parser/lib/PhpParser/NodeFinder.php new file mode 100644 index 0000000000..2e7cfdad4d --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeFinder.php @@ -0,0 +1,81 @@ +addVisitor($visitor); + $traverser->traverse($nodes); + + return $visitor->getFoundNodes(); + } + + /** + * Find all nodes that are instances of a certain class. + * + * @param Node|Node[] $nodes Single node or array of nodes to search in + * @param string $class Class name + * + * @return Node[] Found nodes (all instances of $class) + */ + public function findInstanceOf($nodes, string $class) : array { + return $this->find($nodes, function ($node) use ($class) { + return $node instanceof $class; + }); + } + + /** + * Find first node satisfying a filter callback. + * + * @param Node|Node[] $nodes Single node or array of nodes to search in + * @param callable $filter Filter callback: function(Node $node) : bool + * + * @return null|Node Found node (or null if none found) + */ + public function findFirst($nodes, callable $filter) { + if (!is_array($nodes)) { + $nodes = [$nodes]; + } + + $visitor = new FirstFindingVisitor($filter); + + $traverser = new NodeTraverser; + $traverser->addVisitor($visitor); + $traverser->traverse($nodes); + + return $visitor->getFoundNode(); + } + + /** + * Find first node that is an instance of a certain class. + * + * @param Node|Node[] $nodes Single node or array of nodes to search in + * @param string $class Class name + * + * @return null|Node Found node, which is an instance of $class (or null if none found) + */ + public function findFirstInstanceOf($nodes, string $class) { + return $this->findFirst($nodes, function ($node) use ($class) { + return $node instanceof $class; + }); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php b/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php new file mode 100644 index 0000000000..97d45bdaaa --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php @@ -0,0 +1,291 @@ +visitors[] = $visitor; + } + + /** + * Removes an added visitor. + * + * @param NodeVisitor $visitor + */ + public function removeVisitor(NodeVisitor $visitor) { + foreach ($this->visitors as $index => $storedVisitor) { + if ($storedVisitor === $visitor) { + unset($this->visitors[$index]); + break; + } + } + } + + /** + * Traverses an array of nodes using the registered visitors. + * + * @param Node[] $nodes Array of nodes + * + * @return Node[] Traversed array of nodes + */ + public function traverse(array $nodes) : array { + $this->stopTraversal = false; + + foreach ($this->visitors as $visitor) { + if (null !== $return = $visitor->beforeTraverse($nodes)) { + $nodes = $return; + } + } + + $nodes = $this->traverseArray($nodes); + + foreach ($this->visitors as $visitor) { + if (null !== $return = $visitor->afterTraverse($nodes)) { + $nodes = $return; + } + } + + return $nodes; + } + + /** + * Recursively traverse a node. + * + * @param Node $node Node to traverse. + * + * @return Node Result of traversal (may be original node or new one) + */ + protected function traverseNode(Node $node) : Node { + foreach ($node->getSubNodeNames() as $name) { + $subNode =& $node->$name; + + if (\is_array($subNode)) { + $subNode = $this->traverseArray($subNode); + if ($this->stopTraversal) { + break; + } + } elseif ($subNode instanceof Node) { + $traverseChildren = true; + $breakVisitorIndex = null; + + foreach ($this->visitors as $visitorIndex => $visitor) { + $return = $visitor->enterNode($subNode); + if (null !== $return) { + if ($return instanceof Node) { + $this->ensureReplacementReasonable($subNode, $return); + $subNode = $return; + } elseif (self::DONT_TRAVERSE_CHILDREN === $return) { + $traverseChildren = false; + } elseif (self::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) { + $traverseChildren = false; + $breakVisitorIndex = $visitorIndex; + break; + } elseif (self::STOP_TRAVERSAL === $return) { + $this->stopTraversal = true; + break 2; + } else { + throw new \LogicException( + 'enterNode() returned invalid value of type ' . gettype($return) + ); + } + } + } + + if ($traverseChildren) { + $subNode = $this->traverseNode($subNode); + if ($this->stopTraversal) { + break; + } + } + + foreach ($this->visitors as $visitorIndex => $visitor) { + $return = $visitor->leaveNode($subNode); + + if (null !== $return) { + if ($return instanceof Node) { + $this->ensureReplacementReasonable($subNode, $return); + $subNode = $return; + } elseif (self::STOP_TRAVERSAL === $return) { + $this->stopTraversal = true; + break 2; + } elseif (\is_array($return)) { + throw new \LogicException( + 'leaveNode() may only return an array ' . + 'if the parent structure is an array' + ); + } else { + throw new \LogicException( + 'leaveNode() returned invalid value of type ' . gettype($return) + ); + } + } + + if ($breakVisitorIndex === $visitorIndex) { + break; + } + } + } + } + + return $node; + } + + /** + * Recursively traverse array (usually of nodes). + * + * @param array $nodes Array to traverse + * + * @return array Result of traversal (may be original array or changed one) + */ + protected function traverseArray(array $nodes) : array { + $doNodes = []; + + foreach ($nodes as $i => &$node) { + if ($node instanceof Node) { + $traverseChildren = true; + $breakVisitorIndex = null; + + foreach ($this->visitors as $visitorIndex => $visitor) { + $return = $visitor->enterNode($node); + if (null !== $return) { + if ($return instanceof Node) { + $this->ensureReplacementReasonable($node, $return); + $node = $return; + } elseif (self::DONT_TRAVERSE_CHILDREN === $return) { + $traverseChildren = false; + } elseif (self::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) { + $traverseChildren = false; + $breakVisitorIndex = $visitorIndex; + break; + } elseif (self::STOP_TRAVERSAL === $return) { + $this->stopTraversal = true; + break 2; + } else { + throw new \LogicException( + 'enterNode() returned invalid value of type ' . gettype($return) + ); + } + } + } + + if ($traverseChildren) { + $node = $this->traverseNode($node); + if ($this->stopTraversal) { + break; + } + } + + foreach ($this->visitors as $visitorIndex => $visitor) { + $return = $visitor->leaveNode($node); + + if (null !== $return) { + if ($return instanceof Node) { + $this->ensureReplacementReasonable($node, $return); + $node = $return; + } elseif (\is_array($return)) { + $doNodes[] = [$i, $return]; + break; + } elseif (self::REMOVE_NODE === $return) { + $doNodes[] = [$i, []]; + break; + } elseif (self::STOP_TRAVERSAL === $return) { + $this->stopTraversal = true; + break 2; + } elseif (false === $return) { + throw new \LogicException( + 'bool(false) return from leaveNode() no longer supported. ' . + 'Return NodeTraverser::REMOVE_NODE instead' + ); + } else { + throw new \LogicException( + 'leaveNode() returned invalid value of type ' . gettype($return) + ); + } + } + + if ($breakVisitorIndex === $visitorIndex) { + break; + } + } + } elseif (\is_array($node)) { + throw new \LogicException('Invalid node structure: Contains nested arrays'); + } + } + + if (!empty($doNodes)) { + while (list($i, $replace) = array_pop($doNodes)) { + array_splice($nodes, $i, 1, $replace); + } + } + + return $nodes; + } + + private function ensureReplacementReasonable($old, $new) { + if ($old instanceof Node\Stmt && $new instanceof Node\Expr) { + throw new \LogicException( + "Trying to replace statement ({$old->getType()}) " . + "with expression ({$new->getType()}). Are you missing a " . + "Stmt_Expression wrapper?" + ); + } + + if ($old instanceof Node\Expr && $new instanceof Node\Stmt) { + throw new \LogicException( + "Trying to replace expression ({$old->getType()}) " . + "with statement ({$new->getType()})" + ); + } + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeTraverserInterface.php b/vendor/nikic/php-parser/lib/PhpParser/NodeTraverserInterface.php new file mode 100644 index 0000000000..77ff3d27f6 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeTraverserInterface.php @@ -0,0 +1,29 @@ + $node stays as-is + * * NodeTraverser::DONT_TRAVERSE_CHILDREN + * => Children of $node are not traversed. $node stays as-is + * * NodeTraverser::STOP_TRAVERSAL + * => Traversal is aborted. $node stays as-is + * * otherwise + * => $node is set to the return value + * + * @param Node $node Node + * + * @return null|int|Node Replacement node (or special return value) + */ + public function enterNode(Node $node); + + /** + * Called when leaving a node. + * + * Return value semantics: + * * null + * => $node stays as-is + * * NodeTraverser::REMOVE_NODE + * => $node is removed from the parent array + * * NodeTraverser::STOP_TRAVERSAL + * => Traversal is aborted. $node stays as-is + * * array (of Nodes) + * => The return value is merged into the parent array (at the position of the $node) + * * otherwise + * => $node is set to the return value + * + * @param Node $node Node + * + * @return null|int|Node|Node[] Replacement node (or special return value) + */ + public function leaveNode(Node $node); + + /** + * Called once after traversal. + * + * Return value semantics: + * * null: $nodes stays as-is + * * otherwise: $nodes is set to the return value + * + * @param Node[] $nodes Array of nodes + * + * @return null|Node[] Array of nodes + */ + public function afterTraverse(array $nodes); +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/CloningVisitor.php b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/CloningVisitor.php new file mode 100644 index 0000000000..a85fa493b0 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/CloningVisitor.php @@ -0,0 +1,20 @@ +setAttribute('origNode', $origNode); + return $node; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/FindingVisitor.php b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/FindingVisitor.php new file mode 100644 index 0000000000..9531edbce7 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/FindingVisitor.php @@ -0,0 +1,48 @@ +filterCallback = $filterCallback; + } + + /** + * Get found nodes satisfying the filter callback. + * + * Nodes are returned in pre-order. + * + * @return Node[] Found nodes + */ + public function getFoundNodes() : array { + return $this->foundNodes; + } + + public function beforeTraverse(array $nodes) { + $this->foundNodes = []; + + return null; + } + + public function enterNode(Node $node) { + $filterCallback = $this->filterCallback; + if ($filterCallback($node)) { + $this->foundNodes[] = $node; + } + + return null; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/FirstFindingVisitor.php b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/FirstFindingVisitor.php new file mode 100644 index 0000000000..596a7d7fd5 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/FirstFindingVisitor.php @@ -0,0 +1,50 @@ +filterCallback = $filterCallback; + } + + /** + * Get found node satisfying the filter callback. + * + * Returns null if no node satisfies the filter callback. + * + * @return null|Node Found node (or null if not found) + */ + public function getFoundNode() { + return $this->foundNode; + } + + public function beforeTraverse(array $nodes) { + $this->foundNode = null; + + return null; + } + + public function enterNode(Node $node) { + $filterCallback = $this->filterCallback; + if ($filterCallback($node)) { + $this->foundNode = $node; + return NodeTraverser::STOP_TRAVERSAL; + } + + return null; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/NameResolver.php b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/NameResolver.php new file mode 100644 index 0000000000..8e259c57b6 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/NameResolver.php @@ -0,0 +1,257 @@ +nameContext = new NameContext($errorHandler ?? new ErrorHandler\Throwing); + $this->preserveOriginalNames = $options['preserveOriginalNames'] ?? false; + $this->replaceNodes = $options['replaceNodes'] ?? true; + } + + /** + * Get name resolution context. + * + * @return NameContext + */ + public function getNameContext() : NameContext { + return $this->nameContext; + } + + public function beforeTraverse(array $nodes) { + $this->nameContext->startNamespace(); + return null; + } + + public function enterNode(Node $node) { + if ($node instanceof Stmt\Namespace_) { + $this->nameContext->startNamespace($node->name); + } elseif ($node instanceof Stmt\Use_) { + foreach ($node->uses as $use) { + $this->addAlias($use, $node->type, null); + } + } elseif ($node instanceof Stmt\GroupUse) { + foreach ($node->uses as $use) { + $this->addAlias($use, $node->type, $node->prefix); + } + } elseif ($node instanceof Stmt\Class_) { + if (null !== $node->extends) { + $node->extends = $this->resolveClassName($node->extends); + } + + foreach ($node->implements as &$interface) { + $interface = $this->resolveClassName($interface); + } + + $this->resolveAttrGroups($node); + if (null !== $node->name) { + $this->addNamespacedName($node); + } + } elseif ($node instanceof Stmt\Interface_) { + foreach ($node->extends as &$interface) { + $interface = $this->resolveClassName($interface); + } + + $this->resolveAttrGroups($node); + $this->addNamespacedName($node); + } elseif ($node instanceof Stmt\Enum_) { + foreach ($node->implements as &$interface) { + $interface = $this->resolveClassName($interface); + } + + $this->resolveAttrGroups($node); + if (null !== $node->name) { + $this->addNamespacedName($node); + } + } elseif ($node instanceof Stmt\Trait_) { + $this->resolveAttrGroups($node); + $this->addNamespacedName($node); + } elseif ($node instanceof Stmt\Function_) { + $this->resolveSignature($node); + $this->resolveAttrGroups($node); + $this->addNamespacedName($node); + } elseif ($node instanceof Stmt\ClassMethod + || $node instanceof Expr\Closure + || $node instanceof Expr\ArrowFunction + ) { + $this->resolveSignature($node); + $this->resolveAttrGroups($node); + } elseif ($node instanceof Stmt\Property) { + if (null !== $node->type) { + $node->type = $this->resolveType($node->type); + } + $this->resolveAttrGroups($node); + } elseif ($node instanceof Stmt\Const_) { + foreach ($node->consts as $const) { + $this->addNamespacedName($const); + } + } else if ($node instanceof Stmt\ClassConst) { + $this->resolveAttrGroups($node); + } else if ($node instanceof Stmt\EnumCase) { + $this->resolveAttrGroups($node); + } elseif ($node instanceof Expr\StaticCall + || $node instanceof Expr\StaticPropertyFetch + || $node instanceof Expr\ClassConstFetch + || $node instanceof Expr\New_ + || $node instanceof Expr\Instanceof_ + ) { + if ($node->class instanceof Name) { + $node->class = $this->resolveClassName($node->class); + } + } elseif ($node instanceof Stmt\Catch_) { + foreach ($node->types as &$type) { + $type = $this->resolveClassName($type); + } + } elseif ($node instanceof Expr\FuncCall) { + if ($node->name instanceof Name) { + $node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_FUNCTION); + } + } elseif ($node instanceof Expr\ConstFetch) { + $node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_CONSTANT); + } elseif ($node instanceof Stmt\TraitUse) { + foreach ($node->traits as &$trait) { + $trait = $this->resolveClassName($trait); + } + + foreach ($node->adaptations as $adaptation) { + if (null !== $adaptation->trait) { + $adaptation->trait = $this->resolveClassName($adaptation->trait); + } + + if ($adaptation instanceof Stmt\TraitUseAdaptation\Precedence) { + foreach ($adaptation->insteadof as &$insteadof) { + $insteadof = $this->resolveClassName($insteadof); + } + } + } + } + + return null; + } + + private function addAlias(Stmt\UseUse $use, $type, Name $prefix = null) { + // Add prefix for group uses + $name = $prefix ? Name::concat($prefix, $use->name) : $use->name; + // Type is determined either by individual element or whole use declaration + $type |= $use->type; + + $this->nameContext->addAlias( + $name, (string) $use->getAlias(), $type, $use->getAttributes() + ); + } + + /** @param Stmt\Function_|Stmt\ClassMethod|Expr\Closure $node */ + private function resolveSignature($node) { + foreach ($node->params as $param) { + $param->type = $this->resolveType($param->type); + $this->resolveAttrGroups($param); + } + $node->returnType = $this->resolveType($node->returnType); + } + + private function resolveType($node) { + if ($node instanceof Name) { + return $this->resolveClassName($node); + } + if ($node instanceof Node\NullableType) { + $node->type = $this->resolveType($node->type); + return $node; + } + if ($node instanceof Node\UnionType || $node instanceof Node\IntersectionType) { + foreach ($node->types as &$type) { + $type = $this->resolveType($type); + } + return $node; + } + return $node; + } + + /** + * Resolve name, according to name resolver options. + * + * @param Name $name Function or constant name to resolve + * @param int $type One of Stmt\Use_::TYPE_* + * + * @return Name Resolved name, or original name with attribute + */ + protected function resolveName(Name $name, int $type) : Name { + if (!$this->replaceNodes) { + $resolvedName = $this->nameContext->getResolvedName($name, $type); + if (null !== $resolvedName) { + $name->setAttribute('resolvedName', $resolvedName); + } else { + $name->setAttribute('namespacedName', FullyQualified::concat( + $this->nameContext->getNamespace(), $name, $name->getAttributes())); + } + return $name; + } + + if ($this->preserveOriginalNames) { + // Save the original name + $originalName = $name; + $name = clone $originalName; + $name->setAttribute('originalName', $originalName); + } + + $resolvedName = $this->nameContext->getResolvedName($name, $type); + if (null !== $resolvedName) { + return $resolvedName; + } + + // unqualified names inside a namespace cannot be resolved at compile-time + // add the namespaced version of the name as an attribute + $name->setAttribute('namespacedName', FullyQualified::concat( + $this->nameContext->getNamespace(), $name, $name->getAttributes())); + return $name; + } + + protected function resolveClassName(Name $name) { + return $this->resolveName($name, Stmt\Use_::TYPE_NORMAL); + } + + protected function addNamespacedName(Node $node) { + $node->namespacedName = Name::concat( + $this->nameContext->getNamespace(), (string) $node->name); + } + + protected function resolveAttrGroups(Node $node) + { + foreach ($node->attrGroups as $attrGroup) { + foreach ($attrGroup->attrs as $attr) { + $attr->name = $this->resolveClassName($attr->name); + } + } + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php new file mode 100644 index 0000000000..ea372e5b99 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php @@ -0,0 +1,52 @@ +$node->getAttribute('parent'), the previous + * node can be accessed through $node->getAttribute('previous'), + * and the next node can be accessed through $node->getAttribute('next'). + */ +final class NodeConnectingVisitor extends NodeVisitorAbstract +{ + /** + * @var Node[] + */ + private $stack = []; + + /** + * @var ?Node + */ + private $previous; + + public function beforeTraverse(array $nodes) { + $this->stack = []; + $this->previous = null; + } + + public function enterNode(Node $node) { + if (!empty($this->stack)) { + $node->setAttribute('parent', $this->stack[count($this->stack) - 1]); + } + + if ($this->previous !== null && $this->previous->getAttribute('parent') === $node->getAttribute('parent')) { + $node->setAttribute('previous', $this->previous); + $this->previous->setAttribute('next', $node); + } + + $this->stack[] = $node; + } + + public function leaveNode(Node $node) { + $this->previous = $node; + + array_pop($this->stack); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php new file mode 100644 index 0000000000..b98d2bfa6f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php @@ -0,0 +1,41 @@ +$node->getAttribute('parent'). + */ +final class ParentConnectingVisitor extends NodeVisitorAbstract +{ + /** + * @var Node[] + */ + private $stack = []; + + public function beforeTraverse(array $nodes) + { + $this->stack = []; + } + + public function enterNode(Node $node) + { + if (!empty($this->stack)) { + $node->setAttribute('parent', $this->stack[count($this->stack) - 1]); + } + + $this->stack[] = $node; + } + + public function leaveNode(Node $node) + { + array_pop($this->stack); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeVisitorAbstract.php b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitorAbstract.php new file mode 100644 index 0000000000..d378d67096 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitorAbstract.php @@ -0,0 +1,25 @@ +parsers = $parsers; + } + + public function parse(string $code, ErrorHandler $errorHandler = null) { + if (null === $errorHandler) { + $errorHandler = new ErrorHandler\Throwing; + } + + list($firstStmts, $firstError) = $this->tryParse($this->parsers[0], $errorHandler, $code); + if ($firstError === null) { + return $firstStmts; + } + + for ($i = 1, $c = count($this->parsers); $i < $c; ++$i) { + list($stmts, $error) = $this->tryParse($this->parsers[$i], $errorHandler, $code); + if ($error === null) { + return $stmts; + } + } + + throw $firstError; + } + + private function tryParse(Parser $parser, ErrorHandler $errorHandler, $code) { + $stmts = null; + $error = null; + try { + $stmts = $parser->parse($code, $errorHandler); + } catch (Error $error) {} + return [$stmts, $error]; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Parser/Php5.php b/vendor/nikic/php-parser/lib/PhpParser/Parser/Php5.php new file mode 100644 index 0000000000..d9c8fe0494 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Parser/Php5.php @@ -0,0 +1,2672 @@ +'", + "T_IS_GREATER_OR_EQUAL", + "T_SL", + "T_SR", + "'+'", + "'-'", + "'.'", + "'*'", + "'/'", + "'%'", + "'!'", + "T_INSTANCEOF", + "'~'", + "T_INC", + "T_DEC", + "T_INT_CAST", + "T_DOUBLE_CAST", + "T_STRING_CAST", + "T_ARRAY_CAST", + "T_OBJECT_CAST", + "T_BOOL_CAST", + "T_UNSET_CAST", + "'@'", + "T_POW", + "'['", + "T_NEW", + "T_CLONE", + "T_EXIT", + "T_IF", + "T_ELSEIF", + "T_ELSE", + "T_ENDIF", + "T_LNUMBER", + "T_DNUMBER", + "T_STRING", + "T_STRING_VARNAME", + "T_VARIABLE", + "T_NUM_STRING", + "T_INLINE_HTML", + "T_ENCAPSED_AND_WHITESPACE", + "T_CONSTANT_ENCAPSED_STRING", + "T_ECHO", + "T_DO", + "T_WHILE", + "T_ENDWHILE", + "T_FOR", + "T_ENDFOR", + "T_FOREACH", + "T_ENDFOREACH", + "T_DECLARE", + "T_ENDDECLARE", + "T_AS", + "T_SWITCH", + "T_MATCH", + "T_ENDSWITCH", + "T_CASE", + "T_DEFAULT", + "T_BREAK", + "T_CONTINUE", + "T_GOTO", + "T_FUNCTION", + "T_FN", + "T_CONST", + "T_RETURN", + "T_TRY", + "T_CATCH", + "T_FINALLY", + "T_USE", + "T_INSTEADOF", + "T_GLOBAL", + "T_STATIC", + "T_ABSTRACT", + "T_FINAL", + "T_PRIVATE", + "T_PROTECTED", + "T_PUBLIC", + "T_VAR", + "T_UNSET", + "T_ISSET", + "T_EMPTY", + "T_HALT_COMPILER", + "T_CLASS", + "T_TRAIT", + "T_INTERFACE", + "T_EXTENDS", + "T_IMPLEMENTS", + "T_OBJECT_OPERATOR", + "T_LIST", + "T_ARRAY", + "T_CALLABLE", + "T_CLASS_C", + "T_TRAIT_C", + "T_METHOD_C", + "T_FUNC_C", + "T_LINE", + "T_FILE", + "T_START_HEREDOC", + "T_END_HEREDOC", + "T_DOLLAR_OPEN_CURLY_BRACES", + "T_CURLY_OPEN", + "T_PAAMAYIM_NEKUDOTAYIM", + "T_NAMESPACE", + "T_NS_C", + "T_DIR", + "T_NS_SEPARATOR", + "T_ELLIPSIS", + "T_NAME_FULLY_QUALIFIED", + "T_NAME_QUALIFIED", + "T_NAME_RELATIVE", + "';'", + "'{'", + "'}'", + "'('", + "')'", + "'$'", + "'`'", + "']'", + "'\"'", + "T_READONLY", + "T_ENUM", + "T_NULLSAFE_OBJECT_OPERATOR", + "T_ATTRIBUTE" + ); + + protected $tokenToSymbol = array( + 0, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 56, 163, 168, 160, 55, 168, 168, + 158, 159, 53, 50, 8, 51, 52, 54, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 31, 155, + 44, 16, 46, 30, 68, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 70, 168, 162, 36, 168, 161, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 156, 35, 157, 58, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 1, 2, 3, 4, + 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 32, 33, 34, 37, 38, 39, 40, + 41, 42, 43, 45, 47, 48, 49, 57, 59, 60, + 61, 62, 63, 64, 65, 66, 67, 69, 71, 72, + 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, + 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, + 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, + 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, + 113, 114, 115, 116, 117, 118, 119, 120, 121, 164, + 122, 123, 124, 125, 126, 127, 128, 129, 165, 130, + 131, 132, 166, 133, 134, 135, 136, 137, 138, 139, + 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, + 150, 151, 152, 153, 154, 167 + ); + + protected $action = array( + 699, 669, 670, 671, 672, 673, 286, 674, 675, 676, + 712, 713, 223, 224, 225, 226, 227, 228, 229, 230, + 231, 232, 0, 233, 234, 235, 236, 237, 238, 239, + 240, 241, 242, 243, 244,-32766,-32766,-32766,-32766,-32766, + -32766,-32766,-32766,-32766,-32767,-32767,-32767,-32767, 245, 246, + 242, 243, 244,-32766,-32766, 677,-32766, 750,-32766,-32766, + -32766,-32766,-32766,-32766,-32766, 1224, 245, 246, 1225, 678, + 679, 680, 681, 682, 683, 684,-32766, 48, 746,-32766, + -32766,-32766,-32766,-32766,-32766, 685, 686, 687, 688, 689, + 690, 691, 692, 693, 694, 695, 715, 738, 716, 717, + 718, 719, 707, 708, 709, 737, 710, 711, 696, 697, + 698, 700, 701, 702, 740, 741, 742, 743, 744, 745, + 703, 704, 705, 706, 736, 727, 725, 726, 722, 723, + 751, 714, 720, 721, 728, 729, 731, 730, 732, 733, + 55, 56, 425, 57, 58, 724, 735, 734, 1073, 59, + 60, -224, 61,-32766,-32766,-32766,-32766,-32766,-32766,-32766, + -32766,-32766,-32766, 121,-32767,-32767,-32767,-32767, 29, 107, + 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, + 118, 119, 1043, 766, 1071, 767, 580, 62, 63,-32766, + -32766,-32766,-32766, 64, 516, 65, 294, 295, 66, 67, + 68, 69, 70, 71, 72, 73, 822, 25, 302, 74, + 418, 981, 983, 1043, 1181, 1095, 1096, 1073, 748, 754, + 1075, 1074, 1076, 469,-32766,-32766,-32766, 337, 823, 54, + -32767,-32767,-32767,-32767, 98, 99, 100, 101, 102, 220, + 221, 222, 78, 361, 1107,-32766, 341,-32766,-32766,-32766, + -32766,-32766, 1107, 492, 949, 950, 951, 948, 947, 946, + 207, 477, 478, 949, 950, 951, 948, 947, 946, 1043, + 479, 480, 52, 1101, 1102, 1103, 1104, 1098, 1099, 319, + 872, 668, 667, 27, -511, 1105, 1100,-32766, 130, 1075, + 1074, 1076, 345, 668, 667, 41, 126, 341, 334, 369, + 336, 426, -128, -128, -128, 896, 897, 468, 220, 221, + 222, 811, 1195, 619, 40, 21, 427, -128, 470, -128, + 471, -128, 472, -128, 802, 428, -4, 823, 54, 207, + 33, 34, 429, 360, 317, 28, 35, 473,-32766,-32766, + -32766, 211, 356, 357, 474, 475,-32766,-32766,-32766, 754, + 476, 49, 313, 794, 843, 430, 431, 289, 125,-32766, + 813,-32766,-32766,-32766,-32766,-32766,-32766,-32766,-32767,-32767, + -32767,-32767,-32767,-32766,-32766,-32766, 769, 103, 104, 105, + 327, 307, 825, 633, -128, 1075, 1074, 1076, 221, 222, + 927, 748, 1146, 106,-32766, 129,-32766,-32766,-32766,-32766, + 426, 823, 54, 902, 873, 302, 468, 75, 207, 359, + 811, 668, 667, 40, 21, 427, 754, 470, 754, 471, + 423, 472, 1043, 127, 428, 435, 1043, 341, 1043, 33, + 34, 429, 360, 1181, 415, 35, 473, 122, 10, 315, + 128, 356, 357, 474, 475,-32766,-32766,-32766, 768, 476, + 668, 667, 758, 843, 430, 431, 754, 1043, 1147,-32766, + -32766,-32766, 754, 419, 342, 1215,-32766, 131,-32766,-32766, + -32766, 341, 363, 346, 426, 823, 54, 100, 101, 102, + 468, 825, 633, -4, 811, 442, 903, 40, 21, 427, + 754, 470, 435, 471, 341, 472, 341, 766, 428, 767, + -209, -209, -209, 33, 34, 429, 360, 479, 1196, 35, + 473, 345,-32766,-32766,-32766, 356, 357, 474, 475, 220, + 221, 222, 421, 476, 32, 297, 794, 843, 430, 431, + 754, 754, 435,-32766, 341,-32766,-32766, 9, 300, 51, + 207, 249, 324, 753, 120, 220, 221, 222, 426, 30, + 247, 941, 422, 424, 468, 825, 633, -209, 811, 1043, + 1061, 40, 21, 427, 129, 470, 207, 471, 341, 472, + 804, 20, 428, 124, -208, -208, -208, 33, 34, 429, + 360, 479, 212, 35, 473, 923, -259, 823, 54, 356, + 357, 474, 475,-32766,-32766,-32766, 1043, 476, 213, 806, + 794, 843, 430, 431,-32766,-32766, 435, 435, 341, 341, + 443, 79, 80, 81,-32766, 668, 667, 636, 344, 808, + 668, 667, 239, 240, 241, 123, 214, 538, 250, 825, + 633, -208, 36, 251, 82, 83, 84, 85, 86, 87, + 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, + 98, 99, 100, 101, 102, 103, 104, 105, 252, 307, + 426, 220, 221, 222, 823, 54, 468,-32766, 222, 765, + 811, 106, 134, 40, 21, 427, 571, 470, 207, 471, + 445, 472, 207,-32766, 428, 896, 897, 207, 307, 33, + 34, 429, 245, 246, 637, 35, 473, 452, 22, 809, + 922, 356, 357, 457, 588, 135, 374, 595, 596, 476, + -228, 759, 639, 938, 653, 926, 661, -86, 823, 54, + 314, 644, 647, 821, 133, 836, 43, 106, 603, 44, + 45, 46, 47, 748, 50, 53, 132, 426, 302,-32766, + 520, 825, 633, 468, -84, 607, 577, 811, 641, 362, + 40, 21, 427, -278, 470, 754, 471, 954, 472, 441, + 627, 428, 823, 54, 574, 844, 33, 34, 429, 11, + 615, 845, 35, 473, 444, 461, 285, -511, 356, 357, + 592, -419, 593, 1106, 1153, -410, 476, 368, 838, 38, + 658, 426, 645, 795, 1052, 0, 325, 468, 0,-32766, + 0, 811, 0, 0, 40, 21, 427, 0, 470, 0, + 471, 0, 472, 0, 322, 428, 823, 54, 825, 633, + 33, 34, 429, 0, 326, 0, 35, 473, 323, 0, + 316, 318, 356, 357, -512, 426, 0, 753, 531, 0, + 476, 468, 6, 0, 0, 811, 650, 7, 40, 21, + 427, 12, 470, 14, 471, 373, 472, -420, 562, 428, + 823, 54, 78, -225, 33, 34, 429, 39, 656, 657, + 35, 473, 859, 633, 764, 812, 356, 357, 820, 799, + 814, 875, 866, 867, 476, 797, 860, 857, 855, 426, + 933, 934, 931, 819, 803, 468, 805, 807, 810, 811, + 930, 762, 40, 21, 427, 763, 470, 932, 471, 335, + 472, 358, 634, 428, 638, 640, 825, 633, 33, 34, + 429, 642, 643, 646, 35, 473, 648, 649, 651, 652, + 356, 357, 635, 426, 1221, 1223, 761, 842, 476, 468, + 248, 760, 841, 811, 1222, 840, 40, 21, 427, 1057, + 470, 830, 471, 1045, 472, 839, 1046, 428, 828, 215, + 216, 939, 33, 34, 429, 217, 864, 218, 35, 473, + 825, 633, 24, 865, 356, 357, 456, 1220, 1189, 209, + 1187, 1172, 476, 1185, 215, 216, 1086, 1095, 1096, 914, + 217, 1193, 218, 1183, -224, 1097, 26, 31, 37, 42, + 76, 77, 210, 288, 209, 292, 293, 308, 309, 310, + 311, 339, 1095, 1096, 825, 633, 355, 291, 416, 1152, + 1097, 16, 17, 18, 393, 453, 460, 462, 466, 552, + 624, 1048, 1051, 904, 1111, 1047, 1023, 563, 1022, 1088, + 0, 0, -429, 558, 1041, 1101, 1102, 1103, 1104, 1098, + 1099, 398, 1054, 1053, 1056, 1055, 1070, 1105, 1100, 1186, + 1171, 1167, 1184, 1085, 1218, 1112, 1166, 219, 558, 599, + 1101, 1102, 1103, 1104, 1098, 1099, 398, 0, 0, 0, + 0, 0, 1105, 1100, 0, 0, 0, 0, 0, 0, + 0, 0, 219 + ); + + protected $actionCheck = array( + 2, 3, 4, 5, 6, 7, 14, 9, 10, 11, + 12, 13, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 0, 44, 45, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55, 9, 10, 11, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 42, 69, 70, + 53, 54, 55, 9, 10, 57, 30, 80, 32, 33, + 34, 35, 36, 37, 38, 80, 69, 70, 83, 71, + 72, 73, 74, 75, 76, 77, 9, 70, 80, 33, + 34, 35, 36, 37, 38, 87, 88, 89, 90, 91, + 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, + 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, + 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, + 153, 133, 134, 135, 136, 137, 138, 139, 140, 141, + 3, 4, 5, 6, 7, 147, 148, 149, 80, 12, + 13, 159, 15, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 156, 44, 45, 46, 47, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, + 28, 29, 13, 106, 116, 108, 85, 50, 51, 33, + 34, 35, 36, 56, 85, 58, 59, 60, 61, 62, + 63, 64, 65, 66, 67, 68, 1, 70, 71, 72, + 73, 59, 60, 13, 82, 78, 79, 80, 80, 82, + 152, 153, 154, 86, 9, 10, 11, 8, 1, 2, + 44, 45, 46, 47, 48, 49, 50, 51, 52, 9, + 10, 11, 156, 106, 143, 30, 160, 32, 33, 34, + 35, 36, 143, 116, 116, 117, 118, 119, 120, 121, + 30, 124, 125, 116, 117, 118, 119, 120, 121, 13, + 133, 134, 70, 136, 137, 138, 139, 140, 141, 142, + 31, 37, 38, 8, 132, 148, 149, 116, 156, 152, + 153, 154, 160, 37, 38, 158, 8, 160, 161, 8, + 163, 74, 75, 76, 77, 134, 135, 80, 9, 10, + 11, 84, 1, 80, 87, 88, 89, 90, 91, 92, + 93, 94, 95, 96, 155, 98, 0, 1, 2, 30, + 103, 104, 105, 106, 132, 8, 109, 110, 9, 10, + 11, 8, 115, 116, 117, 118, 9, 10, 11, 82, + 123, 70, 8, 126, 127, 128, 129, 8, 156, 30, + 155, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 9, 10, 11, 157, 53, 54, 55, + 8, 57, 155, 156, 157, 152, 153, 154, 10, 11, + 157, 80, 162, 69, 30, 151, 32, 33, 34, 35, + 74, 1, 2, 159, 155, 71, 80, 151, 30, 8, + 84, 37, 38, 87, 88, 89, 82, 91, 82, 93, + 8, 95, 13, 156, 98, 158, 13, 160, 13, 103, + 104, 105, 106, 82, 108, 109, 110, 156, 8, 113, + 31, 115, 116, 117, 118, 9, 10, 11, 157, 123, + 37, 38, 126, 127, 128, 129, 82, 13, 159, 33, + 34, 35, 82, 127, 8, 85, 30, 156, 32, 33, + 34, 160, 8, 147, 74, 1, 2, 50, 51, 52, + 80, 155, 156, 157, 84, 31, 159, 87, 88, 89, + 82, 91, 158, 93, 160, 95, 160, 106, 98, 108, + 100, 101, 102, 103, 104, 105, 106, 133, 159, 109, + 110, 160, 9, 10, 11, 115, 116, 117, 118, 9, + 10, 11, 8, 123, 144, 145, 126, 127, 128, 129, + 82, 82, 158, 30, 160, 32, 33, 108, 8, 70, + 30, 31, 113, 152, 16, 9, 10, 11, 74, 14, + 14, 122, 8, 8, 80, 155, 156, 157, 84, 13, + 159, 87, 88, 89, 151, 91, 30, 93, 160, 95, + 155, 159, 98, 14, 100, 101, 102, 103, 104, 105, + 106, 133, 16, 109, 110, 155, 157, 1, 2, 115, + 116, 117, 118, 9, 10, 11, 13, 123, 16, 155, + 126, 127, 128, 129, 33, 34, 158, 158, 160, 160, + 156, 9, 10, 11, 30, 37, 38, 31, 70, 155, + 37, 38, 50, 51, 52, 156, 16, 81, 16, 155, + 156, 157, 30, 16, 32, 33, 34, 35, 36, 37, + 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 16, 57, + 74, 9, 10, 11, 1, 2, 80, 116, 11, 155, + 84, 69, 156, 87, 88, 89, 160, 91, 30, 93, + 132, 95, 30, 33, 98, 134, 135, 30, 57, 103, + 104, 105, 69, 70, 31, 109, 110, 75, 76, 155, + 155, 115, 116, 75, 76, 101, 102, 111, 112, 123, + 159, 155, 156, 155, 156, 155, 156, 31, 1, 2, + 31, 31, 31, 31, 31, 38, 70, 69, 77, 70, + 70, 70, 70, 80, 70, 70, 70, 74, 71, 85, + 85, 155, 156, 80, 97, 96, 100, 84, 31, 106, + 87, 88, 89, 82, 91, 82, 93, 82, 95, 89, + 92, 98, 1, 2, 90, 127, 103, 104, 105, 97, + 94, 127, 109, 110, 97, 97, 97, 132, 115, 116, + 100, 146, 113, 143, 143, 146, 123, 106, 151, 155, + 157, 74, 31, 157, 162, -1, 114, 80, -1, 116, + -1, 84, -1, -1, 87, 88, 89, -1, 91, -1, + 93, -1, 95, -1, 130, 98, 1, 2, 155, 156, + 103, 104, 105, -1, 130, -1, 109, 110, 131, -1, + 132, 132, 115, 116, 132, 74, -1, 152, 150, -1, + 123, 80, 146, -1, -1, 84, 31, 146, 87, 88, + 89, 146, 91, 146, 93, 146, 95, 146, 150, 98, + 1, 2, 156, 159, 103, 104, 105, 155, 155, 155, + 109, 110, 155, 156, 155, 155, 115, 116, 155, 155, + 155, 155, 155, 155, 123, 155, 155, 155, 155, 74, + 155, 155, 155, 155, 155, 80, 155, 155, 155, 84, + 155, 155, 87, 88, 89, 155, 91, 155, 93, 156, + 95, 156, 156, 98, 156, 156, 155, 156, 103, 104, + 105, 156, 156, 156, 109, 110, 156, 156, 156, 156, + 115, 116, 156, 74, 157, 157, 157, 157, 123, 80, + 31, 157, 157, 84, 157, 157, 87, 88, 89, 157, + 91, 157, 93, 157, 95, 157, 157, 98, 157, 50, + 51, 157, 103, 104, 105, 56, 157, 58, 109, 110, + 155, 156, 158, 157, 115, 116, 157, 157, 157, 70, + 157, 157, 123, 157, 50, 51, 157, 78, 79, 157, + 56, 157, 58, 157, 159, 86, 158, 158, 158, 158, + 158, 158, 158, 158, 70, 158, 158, 158, 158, 158, + 158, 158, 78, 79, 155, 156, 158, 160, 158, 163, + 86, 159, 159, 159, 159, 159, 159, 159, 159, 159, + 159, 159, 159, 159, 159, 159, 159, 159, 159, 159, + -1, -1, 161, 134, 161, 136, 137, 138, 139, 140, + 141, 142, 162, 162, 162, 162, 162, 148, 149, 162, + 162, 162, 162, 162, 162, 162, 162, 158, 134, 162, + 136, 137, 138, 139, 140, 141, 142, -1, -1, -1, + -1, -1, 148, 149, -1, -1, -1, -1, -1, -1, + -1, -1, 158 + ); + + protected $actionBase = array( + 0, 227, 326, 400, 474, 233, 132, 132, 752, -2, + -2, 138, -2, -2, -2, 663, 761, 815, 761, 586, + 717, 859, 859, 859, 244, 256, 256, 256, 413, 583, + 583, 880, 546, 169, 415, 444, 409, 200, 200, 200, + 200, 137, 137, 200, 200, 200, 200, 200, 200, 200, + 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, + 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, + 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, + 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, + 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, + 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, + 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, + 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, + 200, 200, 200, 200, 200, 200, 249, 205, 738, 559, + 535, 739, 741, 742, 876, 679, 877, 820, 821, 693, + 823, 824, 826, 829, 832, 819, 834, 907, 836, 602, + 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, + 602, 67, 536, 299, 510, 230, 44, 652, 652, 652, + 652, 652, 652, 652, 337, 337, 337, 337, 337, 337, + 337, 337, 337, 337, 337, 337, 337, 337, 337, 337, + 337, 337, 378, 584, 584, 584, 657, 909, 648, 934, + 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, + 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, + 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, + 934, 934, 934, 934, 934, 934, 934, 934, 934, 934, + 934, 934, 934, 503, -21, -21, 436, 650, 364, 571, + 215, 426, 156, 26, 26, 329, 329, 329, 329, 329, + 46, 46, 5, 5, 5, 5, 152, 186, 186, 186, + 186, 120, 120, 120, 120, 374, 374, 429, 448, 448, + 334, 267, 449, 449, 449, 449, 449, 449, 449, 449, + 449, 449, 336, 427, 427, 572, 572, 408, 551, 551, + 551, 551, 671, 171, 171, 391, 311, 311, 311, 109, + 641, 856, 68, 68, 68, 68, 68, 68, 324, 324, + 324, -3, -3, -3, 655, 77, 380, 77, 380, 683, + 685, 86, 685, 654, -15, 516, 776, 281, 646, 809, + 680, 816, 560, 711, 202, 578, 857, 643, -23, 578, + 578, 578, 578, 857, 622, 628, 596, -23, 578, -23, + 639, 454, 849, 351, 249, 558, 469, 631, 743, 514, + 688, 746, 464, 544, 548, 556, 7, 412, 708, 750, + 878, 879, 349, 702, 631, 631, 631, 327, 101, 7, + -8, 623, 623, 623, 623, 219, 623, 623, 623, 623, + 291, 430, 545, 401, 745, 653, 653, 675, 839, 814, + 814, 653, 673, 653, 675, 841, 841, 841, 841, 653, + 653, 653, 653, 814, 814, 667, 814, 275, 684, 694, + 694, 841, 713, 714, 653, 653, 697, 814, 814, 814, + 697, 687, 841, 669, 637, 333, 814, 841, 689, 673, + 689, 653, 669, 689, 673, 673, 689, 22, 686, 656, + 840, 842, 860, 756, 638, 644, 847, 848, 843, 845, + 838, 692, 719, 720, 528, 659, 660, 661, 662, 696, + 664, 698, 643, 658, 658, 658, 645, 701, 645, 658, + 658, 658, 658, 658, 658, 658, 658, 632, 635, 709, + 699, 670, 723, 566, 582, 758, 640, 636, 872, 865, + 881, 883, 849, 870, 645, 890, 634, 288, 610, 850, + 633, 753, 645, 851, 645, 759, 645, 873, 777, 666, + 778, 779, 658, 874, 891, 892, 893, 894, 897, 898, + 899, 900, 665, 901, 724, 674, 866, 344, 844, 639, + 705, 677, 755, 725, 780, 372, 902, 784, 645, 645, + 765, 706, 645, 766, 726, 712, 862, 727, 867, 903, + 640, 678, 868, 645, 681, 785, 904, 372, 690, 651, + 704, 649, 728, 858, 875, 853, 767, 612, 617, 787, + 788, 792, 691, 730, 863, 864, 835, 731, 770, 642, + 771, 676, 794, 772, 852, 732, 796, 798, 871, 647, + 707, 682, 672, 668, 773, 799, 869, 733, 735, 736, + 801, 737, 804, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 137, 137, 137, 137, -2, -2, -2, + -2, 0, 0, -2, 0, 0, 0, 137, 137, 137, + 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, + 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, + 137, 137, 137, 0, 0, 137, 137, 137, 137, 137, + 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, + 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, + 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, + 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, + 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, + 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, + 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, + 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, + 137, 137, 137, 137, 137, 137, 137, 137, 602, 602, + 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, + 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, + 602, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 602, -21, -21, -21, -21, 602, -21, + -21, -21, -21, -21, -21, -21, 602, 602, 602, 602, + 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, + 602, 602, 602, 602, -21, 602, 602, 602, -21, 68, + -21, 68, 68, 68, 68, 68, 68, 68, 68, 68, + 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, + 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, + 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, + 68, 68, 68, 68, 68, 602, 0, 0, 602, -21, + 602, -21, 602, -21, -21, 602, 602, 602, 602, 602, + 602, 602, -21, -21, -21, -21, -21, -21, 0, 324, + 324, 324, 324, -21, -21, -21, -21, 68, 68, 147, + 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, + 68, 68, 68, 68, 68, 324, 324, -3, -3, 68, + 68, 68, 68, 68, 147, 68, 68, -23, 673, 673, + 673, 380, 380, 380, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 380, -23, 0, -23, + 0, 68, -23, 673, -23, 380, 673, 673, -23, 814, + 604, 604, 604, 604, 372, 7, 0, 0, 673, 673, + 0, 0, 0, 0, 0, 673, 0, 0, 0, 0, + 0, 0, 814, 0, 653, 0, 0, 0, 0, 658, + 288, 0, 677, 456, 0, 0, 0, 0, 0, 0, + 677, 456, 530, 530, 0, 665, 658, 658, 658, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 372 + ); + + protected $actionDefault = array( + 3,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767, 540, 540, 495,32767,32767, + 32767,32767,32767,32767,32767,32767,32767, 297, 297, 297, + 32767,32767,32767, 528, 528, 528, 528, 528, 528, 528, + 528, 528, 528, 528,32767,32767,32767,32767,32767,32767, + 381,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767, 387, + 545,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767, 362, + 363, 365, 366, 296, 548, 529, 245, 388, 544, 295, + 247, 325, 499,32767,32767,32767, 327, 122, 256, 201, + 498, 125, 294, 232, 380, 382, 326, 301, 306, 307, + 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, + 318, 300, 454, 359, 358, 357, 456,32767, 455, 492, + 492, 495,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767, 323, 483, 482, 324, 452, 328, 453, + 331, 457, 460, 329, 330, 347, 348, 345, 346, 349, + 458, 459, 476, 477, 474, 475, 299, 350, 351, 352, + 353, 478, 479, 480, 481,32767,32767, 280, 539, 539, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767, 338, 339, 467, 468,32767, 236, 236, + 236, 236, 281, 236,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767, 333, 334, + 332, 462, 463, 461, 428,32767,32767,32767, 430,32767, + 32767,32767,32767,32767,32767,32767,32767, 500,32767,32767, + 32767,32767,32767, 513, 417, 171,32767, 409,32767, 171, + 171, 171, 171,32767, 220, 222, 167,32767, 171,32767, + 486,32767,32767,32767,32767,32767, 518, 343,32767,32767, + 116,32767,32767,32767, 555,32767, 513,32767, 116,32767, + 32767,32767,32767, 356, 335, 336, 337,32767,32767, 517, + 511, 470, 471, 472, 473,32767, 464, 465, 466, 469, + 32767,32767,32767,32767,32767,32767,32767,32767, 425, 431, + 431,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767, 516, 515,32767, 410, 494, 186, 184, + 184,32767, 206, 206,32767,32767, 188, 487, 506,32767, + 188, 173,32767, 398, 175, 494,32767,32767, 238,32767, + 238,32767, 398, 238,32767,32767, 238,32767, 411, 435, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767, 377, 378, 489, 502,32767, + 503,32767, 409, 341, 342, 344, 320,32767, 322, 367, + 368, 369, 370, 371, 372, 373, 375,32767, 415,32767, + 418,32767,32767,32767, 255,32767, 553,32767,32767, 304, + 553,32767,32767,32767, 547,32767,32767, 298,32767,32767, + 32767,32767, 251,32767, 169,32767, 537,32767, 554,32767, + 511,32767, 340,32767,32767,32767,32767,32767,32767,32767, + 32767,32767, 512,32767,32767,32767,32767, 227,32767, 448, + 32767, 116,32767,32767,32767, 187,32767,32767, 302, 246, + 32767,32767, 546,32767,32767,32767,32767,32767,32767,32767, + 32767, 114,32767, 170,32767,32767,32767, 189,32767,32767, + 511,32767,32767,32767,32767,32767,32767,32767, 293,32767, + 32767,32767,32767,32767,32767,32767, 511,32767,32767, 231, + 32767,32767,32767,32767,32767,32767,32767,32767,32767, 411, + 32767, 274,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767, 127, 127, 3, 127, 127, 258, 3, + 258, 127, 258, 258, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 214, 217, 206, 206, 164, 127, + 127, 266 + ); + + protected $goto = array( + 166, 140, 140, 140, 166, 187, 168, 144, 147, 141, + 142, 143, 149, 163, 163, 163, 163, 144, 144, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 138, 159, 160, 161, 162, 184, 139, 185, 493, 494, + 377, 495, 499, 500, 501, 502, 503, 504, 505, 506, + 967, 164, 145, 146, 148, 171, 176, 186, 203, 253, + 256, 258, 260, 263, 264, 265, 266, 267, 268, 269, + 277, 278, 279, 280, 303, 304, 328, 329, 330, 394, + 395, 396, 542, 188, 189, 190, 191, 192, 193, 194, + 195, 196, 197, 198, 199, 200, 201, 150, 151, 152, + 167, 153, 169, 154, 204, 170, 155, 156, 157, 205, + 158, 136, 620, 560, 756, 560, 560, 560, 560, 560, + 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, + 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, + 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, + 560, 560, 560, 560, 560, 560, 560, 560, 560, 1108, + 628, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, + 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, + 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, + 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1108, + 1108, 1108, 1108, 1108, 1108, 757, 888, 888, 508, 1200, + 1200, 400, 606, 508, 536, 536, 568, 532, 534, 534, + 496, 498, 524, 540, 569, 572, 583, 590, 852, 852, + 852, 852, 847, 853, 174, 585, 519, 600, 601, 177, + 178, 179, 401, 402, 403, 404, 173, 202, 206, 208, + 257, 259, 261, 262, 270, 271, 272, 273, 274, 275, + 281, 282, 283, 284, 305, 306, 331, 332, 333, 406, + 407, 408, 409, 175, 180, 254, 255, 181, 182, 183, + 497, 497, 785, 497, 497, 497, 497, 497, 497, 497, + 497, 497, 497, 497, 497, 497, 497, 509, 578, 582, + 626, 749, 509, 544, 545, 546, 547, 548, 549, 550, + 551, 553, 586, 338, 559, 321, 559, 559, 559, 559, + 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, + 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, + 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, + 559, 559, 559, 559, 559, 559, 559, 559, 559, 559, + 530, 349, 655, 555, 587, 352, 414, 591, 575, 604, + 885, 611, 612, 881, 616, 617, 623, 625, 630, 632, + 298, 296, 296, 296, 298, 290, 299, 944, 610, 816, + 1170, 613, 436, 436, 375, 436, 436, 436, 436, 436, + 436, 436, 436, 436, 436, 436, 436, 436, 436, 1072, + 1084, 1083, 945, 1065, 1072, 895, 895, 895, 895, 1178, + 895, 895, 1212, 1212, 1178, 388, 858, 561, 755, 1072, + 1072, 1072, 1072, 1072, 1072, 3, 4, 384, 384, 384, + 1212, 874, 856, 854, 856, 654, 465, 511, 883, 878, + 1089, 541, 384, 537, 384, 567, 384, 1026, 19, 15, + 371, 384, 1226, 510, 1204, 1192, 1192, 1192, 510, 906, + 372, 522, 533, 554, 912, 514, 1068, 1069, 13, 1065, + 378, 912, 1158, 594, 23, 965, 386, 386, 386, 602, + 1066, 1169, 1066, 937, 447, 449, 631, 752, 1177, 1067, + 1109, 614, 935, 1177, 605, 1197, 391, 1211, 1211, 543, + 892, 386, 1194, 1194, 1194, 399, 518, 1016, 901, 389, + 771, 529, 752, 340, 752, 1211, 518, 518, 385, 781, + 1214, 770, 772, 1063, 910, 774, 1058, 1176, 659, 953, + 514, 782, 862, 915, 450, 573, 1155, 0, 463, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 513, 528, 0, 0, 0, 0, + 513, 0, 528, 0, 350, 351, 0, 609, 512, 515, + 438, 439, 1064, 618, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 779, 1219, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 777, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 523, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 301, 301 + ); + + protected $gotoCheck = array( + 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, + 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, + 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, + 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, + 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, + 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, + 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, + 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, + 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, + 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, + 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, + 43, 43, 57, 68, 15, 68, 68, 68, 68, 68, + 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, + 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, + 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, + 68, 68, 68, 68, 68, 68, 68, 68, 68, 126, + 9, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 126, 126, 126, 126, 126, 16, 76, 76, 68, 76, + 76, 51, 51, 68, 51, 51, 51, 51, 51, 51, + 51, 51, 51, 51, 51, 51, 51, 51, 68, 68, + 68, 68, 68, 68, 27, 66, 101, 66, 66, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 117, 117, 29, 117, 117, 117, 117, 117, 117, 117, + 117, 117, 117, 117, 117, 117, 117, 117, 61, 61, + 61, 6, 117, 110, 110, 110, 110, 110, 110, 110, + 110, 110, 110, 125, 57, 125, 57, 57, 57, 57, + 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, + 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, + 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, + 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, + 32, 71, 32, 32, 69, 69, 69, 32, 40, 40, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 5, 5, 5, 5, 5, 5, 5, 97, 62, 50, + 81, 62, 57, 57, 62, 57, 57, 57, 57, 57, + 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, + 124, 124, 97, 81, 57, 57, 57, 57, 57, 118, + 57, 57, 142, 142, 118, 12, 33, 12, 14, 57, + 57, 57, 57, 57, 57, 30, 30, 13, 13, 13, + 142, 14, 14, 14, 14, 14, 57, 14, 14, 14, + 34, 2, 13, 109, 13, 2, 13, 34, 34, 34, + 34, 13, 13, 122, 140, 9, 9, 9, 122, 83, + 58, 58, 58, 34, 13, 13, 81, 81, 58, 81, + 46, 13, 131, 127, 34, 101, 123, 123, 123, 34, + 81, 81, 81, 8, 8, 8, 8, 11, 119, 81, + 8, 8, 8, 119, 49, 138, 48, 141, 141, 47, + 78, 123, 119, 119, 119, 123, 47, 102, 80, 17, + 23, 9, 11, 18, 11, 141, 47, 47, 11, 23, + 141, 23, 24, 115, 84, 25, 113, 119, 73, 99, + 13, 26, 70, 85, 64, 65, 130, -1, 108, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 9, 9, -1, -1, -1, -1, + 9, -1, 9, -1, 71, 71, -1, 13, 9, 9, + 9, 9, 13, 13, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 9, 9, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 101, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 5, 5 + ); + + protected $gotoBase = array( + 0, 0, -184, 0, 0, 356, 290, 0, 488, 149, + 0, 182, 85, 118, 426, 112, 203, 179, 208, 0, + 0, 0, 0, 162, 190, 198, 120, 27, 0, 272, + -224, 0, -274, 406, 32, 0, 0, 0, 0, 0, + 330, 0, 0, -24, 0, 0, 440, 485, 213, 218, + 371, -74, 0, 0, 0, 0, 0, 107, 110, 0, + 0, -11, -72, 0, 104, 95, -405, 0, -94, 41, + 119, -82, 0, 164, 0, 0, -79, 0, 197, 0, + 204, 43, 0, 441, 171, 121, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 100, 0, 115, + 0, 195, 210, 0, 0, 0, 0, 0, 86, 427, + 259, 0, 0, 116, 0, 174, 0, -5, 117, 196, + 0, 0, 161, 170, 93, -21, -48, 273, 0, 0, + 91, 271, 0, 0, 0, 0, 0, 0, 216, 0, + 437, 187, 102, 0, 0 + ); + + protected $gotoDefault = array( + -32768, 467, 663, 2, 664, 834, 739, 747, 597, 481, + 629, 581, 380, 1188, 791, 792, 793, 381, 367, 482, + 379, 410, 405, 780, 773, 775, 783, 172, 411, 786, + 1, 788, 517, 824, 1017, 364, 796, 365, 589, 798, + 526, 800, 801, 137, 382, 383, 527, 483, 390, 576, + 815, 276, 387, 817, 366, 818, 827, 370, 464, 454, + 459, 556, 608, 432, 446, 570, 564, 535, 1081, 565, + 861, 348, 869, 660, 877, 880, 484, 557, 891, 451, + 899, 1094, 397, 905, 911, 916, 287, 919, 417, 412, + 584, 924, 925, 5, 929, 621, 622, 8, 312, 952, + 598, 966, 420, 1036, 1038, 485, 486, 521, 458, 507, + 525, 487, 1059, 440, 413, 1062, 488, 489, 433, 434, + 1078, 354, 1163, 353, 448, 320, 1150, 579, 1113, 455, + 1203, 1159, 347, 490, 491, 376, 1182, 392, 1198, 437, + 1205, 1213, 343, 539, 566 + ); + + protected $ruleToNonTerminal = array( + 0, 1, 3, 3, 2, 5, 5, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, + 7, 7, 7, 7, 8, 8, 9, 10, 11, 11, + 12, 12, 13, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 18, 18, 19, 19, 21, 21, + 17, 17, 22, 22, 23, 23, 24, 24, 25, 25, + 20, 20, 26, 28, 28, 29, 30, 30, 32, 31, + 31, 31, 31, 33, 33, 33, 33, 33, 33, 33, + 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, + 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, + 33, 33, 14, 14, 54, 54, 56, 55, 55, 48, + 48, 58, 58, 59, 59, 60, 60, 15, 16, 16, + 16, 63, 63, 63, 64, 64, 67, 67, 65, 65, + 69, 69, 41, 41, 50, 50, 53, 53, 53, 52, + 52, 70, 42, 42, 42, 42, 71, 71, 72, 72, + 73, 73, 39, 39, 35, 35, 74, 37, 37, 75, + 36, 36, 38, 38, 49, 49, 49, 61, 61, 77, + 77, 78, 78, 80, 80, 80, 79, 79, 62, 62, + 81, 81, 81, 82, 82, 83, 83, 83, 44, 44, + 84, 84, 84, 45, 45, 85, 85, 86, 86, 66, + 87, 87, 87, 87, 92, 92, 93, 93, 94, 94, + 94, 94, 94, 95, 96, 96, 91, 91, 88, 88, + 90, 90, 98, 98, 97, 97, 97, 97, 97, 97, + 89, 89, 100, 99, 99, 46, 46, 40, 40, 43, + 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, + 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, + 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, + 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, + 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, + 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, + 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, + 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, + 43, 43, 43, 43, 43, 34, 34, 47, 47, 105, + 105, 106, 106, 106, 106, 112, 101, 101, 108, 108, + 114, 114, 115, 116, 116, 116, 116, 116, 116, 68, + 68, 57, 57, 57, 57, 102, 102, 120, 120, 117, + 117, 121, 121, 121, 121, 103, 103, 103, 107, 107, + 107, 113, 113, 126, 126, 126, 126, 126, 126, 126, + 126, 126, 126, 126, 126, 126, 27, 27, 27, 27, + 27, 27, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 111, 111, 104, 104, + 104, 104, 127, 127, 130, 130, 129, 129, 131, 131, + 51, 51, 51, 51, 133, 133, 132, 132, 132, 132, + 132, 134, 134, 119, 119, 122, 122, 118, 118, 136, + 135, 135, 135, 135, 123, 123, 123, 123, 110, 110, + 124, 124, 124, 124, 76, 137, 137, 138, 138, 138, + 109, 109, 139, 139, 140, 140, 140, 140, 140, 125, + 125, 125, 125, 142, 143, 141, 141, 141, 141, 141, + 141, 141, 144, 144, 144 + ); + + protected $ruleToLength = array( + 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 3, 5, 4, + 3, 4, 2, 3, 1, 1, 7, 6, 3, 1, + 3, 1, 3, 1, 1, 3, 1, 3, 1, 2, + 3, 1, 3, 3, 1, 3, 2, 0, 1, 1, + 1, 1, 1, 3, 5, 8, 3, 5, 9, 3, + 2, 3, 2, 3, 2, 3, 3, 3, 3, 1, + 2, 2, 5, 7, 9, 5, 6, 3, 3, 2, + 2, 1, 1, 1, 0, 2, 8, 0, 4, 1, + 3, 0, 1, 0, 1, 0, 1, 10, 7, 6, + 5, 1, 2, 2, 0, 2, 0, 2, 0, 2, + 1, 3, 1, 4, 1, 4, 1, 1, 4, 1, + 3, 3, 3, 4, 4, 5, 0, 2, 4, 3, + 1, 1, 1, 4, 0, 2, 3, 0, 2, 4, + 0, 2, 0, 3, 1, 2, 1, 1, 0, 1, + 3, 4, 6, 1, 1, 1, 0, 1, 0, 2, + 2, 3, 3, 1, 3, 1, 2, 2, 3, 1, + 1, 2, 4, 3, 1, 1, 3, 2, 0, 1, + 3, 3, 9, 3, 1, 3, 0, 2, 4, 5, + 4, 4, 4, 3, 1, 1, 1, 3, 1, 1, + 0, 1, 1, 2, 1, 1, 1, 1, 1, 1, + 1, 3, 1, 1, 3, 3, 1, 0, 1, 1, + 3, 3, 4, 4, 1, 2, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, + 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 2, 2, 2, 2, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 1, 3, 5, 4, 3, + 4, 4, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 1, 1, 3, + 2, 1, 2, 10, 11, 3, 3, 2, 4, 4, + 3, 4, 4, 4, 4, 7, 3, 2, 0, 4, + 1, 3, 2, 2, 4, 6, 2, 2, 4, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 3, 3, 4, 4, 0, 2, 1, 0, 1, + 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 3, 2, 1, 3, 1, 4, + 3, 1, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, + 3, 3, 5, 4, 4, 3, 1, 3, 1, 1, + 3, 3, 0, 2, 0, 1, 3, 1, 3, 1, + 1, 1, 1, 1, 6, 4, 3, 4, 2, 4, + 4, 1, 3, 1, 2, 1, 1, 4, 1, 1, + 3, 6, 4, 4, 4, 4, 1, 4, 0, 1, + 1, 3, 1, 1, 4, 3, 1, 1, 1, 0, + 0, 2, 3, 1, 3, 1, 4, 2, 2, 2, + 2, 1, 2, 1, 1, 1, 4, 3, 3, 3, + 6, 3, 1, 1, 1 + ); + + protected function initReduceCallbacks() { + $this->reduceCallbacks = [ + 0 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 1 => function ($stackPos) { + $this->semValue = $this->handleNamespaces($this->semStack[$stackPos-(1-1)]); + }, + 2 => function ($stackPos) { + if (is_array($this->semStack[$stackPos-(2-2)])) { $this->semValue = array_merge($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)]); } else { $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; }; + }, + 3 => function ($stackPos) { + $this->semValue = array(); + }, + 4 => function ($stackPos) { + $startAttributes = $this->lookaheadStartAttributes; if (isset($startAttributes['comments'])) { $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); } else { $nop = null; }; + if ($nop !== null) { $this->semStack[$stackPos-(1-1)][] = $nop; } $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 5 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 6 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 7 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 8 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 9 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 10 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 11 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 12 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 13 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 14 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 15 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 16 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 17 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 18 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 19 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 20 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 21 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 22 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 23 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 24 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 25 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 26 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 27 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 28 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 29 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 30 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 31 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 32 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 33 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 34 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 35 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 36 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 37 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 38 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 39 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 40 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 41 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 42 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 43 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 44 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 45 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 46 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 47 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 48 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 49 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 50 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 51 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 52 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 53 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 54 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 55 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 56 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 57 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 58 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 59 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 60 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 61 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 62 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 63 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 64 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 65 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 66 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 67 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 68 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 69 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 70 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 71 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 72 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 73 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 74 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 75 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 76 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 77 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 78 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 79 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 80 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 81 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 82 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 83 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 84 => function ($stackPos) { + $this->semValue = new Node\Identifier($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 85 => function ($stackPos) { + $this->semValue = new Node\Identifier($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 86 => function ($stackPos) { + $this->semValue = new Node\Identifier($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 87 => function ($stackPos) { + $this->semValue = new Node\Identifier($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 88 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 89 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 90 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 91 => function ($stackPos) { + $this->semValue = new Name(substr($this->semStack[$stackPos-(1-1)], 1), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 92 => function ($stackPos) { + $this->semValue = new Expr\Variable(substr($this->semStack[$stackPos-(1-1)], 1), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 93 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 94 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 95 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 96 => function ($stackPos) { + $this->semValue = new Stmt\HaltCompiler($this->lexer->handleHaltCompiler(), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 97 => function ($stackPos) { + $this->semValue = new Stmt\Namespace_($this->semStack[$stackPos-(3-2)], null, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + $this->semValue->setAttribute('kind', Stmt\Namespace_::KIND_SEMICOLON); + $this->checkNamespace($this->semValue); + }, + 98 => function ($stackPos) { + $this->semValue = new Stmt\Namespace_($this->semStack[$stackPos-(5-2)], $this->semStack[$stackPos-(5-4)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + $this->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); + $this->checkNamespace($this->semValue); + }, + 99 => function ($stackPos) { + $this->semValue = new Stmt\Namespace_(null, $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + $this->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); + $this->checkNamespace($this->semValue); + }, + 100 => function ($stackPos) { + $this->semValue = new Stmt\Use_($this->semStack[$stackPos-(3-2)], Stmt\Use_::TYPE_NORMAL, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 101 => function ($stackPos) { + $this->semValue = new Stmt\Use_($this->semStack[$stackPos-(4-3)], $this->semStack[$stackPos-(4-2)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 102 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 103 => function ($stackPos) { + $this->semValue = new Stmt\Const_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 104 => function ($stackPos) { + $this->semValue = Stmt\Use_::TYPE_FUNCTION; + }, + 105 => function ($stackPos) { + $this->semValue = Stmt\Use_::TYPE_CONSTANT; + }, + 106 => function ($stackPos) { + $this->semValue = new Stmt\GroupUse($this->semStack[$stackPos-(7-3)], $this->semStack[$stackPos-(7-6)], $this->semStack[$stackPos-(7-2)], $this->startAttributeStack[$stackPos-(7-1)] + $this->endAttributes); + }, + 107 => function ($stackPos) { + $this->semValue = new Stmt\GroupUse($this->semStack[$stackPos-(6-2)], $this->semStack[$stackPos-(6-5)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); + }, + 108 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 109 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 110 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 111 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 112 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 113 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 114 => function ($stackPos) { + $this->semValue = new Stmt\UseUse($this->semStack[$stackPos-(1-1)], null, Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); $this->checkUseUse($this->semValue, $stackPos-(1-1)); + }, + 115 => function ($stackPos) { + $this->semValue = new Stmt\UseUse($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); $this->checkUseUse($this->semValue, $stackPos-(3-3)); + }, + 116 => function ($stackPos) { + $this->semValue = new Stmt\UseUse($this->semStack[$stackPos-(1-1)], null, Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); $this->checkUseUse($this->semValue, $stackPos-(1-1)); + }, + 117 => function ($stackPos) { + $this->semValue = new Stmt\UseUse($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); $this->checkUseUse($this->semValue, $stackPos-(3-3)); + }, + 118 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; $this->semValue->type = Stmt\Use_::TYPE_NORMAL; + }, + 119 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-2)]; $this->semValue->type = $this->semStack[$stackPos-(2-1)]; + }, + 120 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 121 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 122 => function ($stackPos) { + $this->semValue = new Node\Const_($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 123 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 124 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 125 => function ($stackPos) { + $this->semValue = new Node\Const_($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 126 => function ($stackPos) { + if (is_array($this->semStack[$stackPos-(2-2)])) { $this->semValue = array_merge($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)]); } else { $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; }; + }, + 127 => function ($stackPos) { + $this->semValue = array(); + }, + 128 => function ($stackPos) { + $startAttributes = $this->lookaheadStartAttributes; if (isset($startAttributes['comments'])) { $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); } else { $nop = null; }; + if ($nop !== null) { $this->semStack[$stackPos-(1-1)][] = $nop; } $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 129 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 130 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 131 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 132 => function ($stackPos) { + throw new Error('__HALT_COMPILER() can only be used from the outermost scope', $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 133 => function ($stackPos) { + + if ($this->semStack[$stackPos-(3-2)]) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; $attrs = $this->startAttributeStack[$stackPos-(3-1)]; $stmts = $this->semValue; if (!empty($attrs['comments'])) {$stmts[0]->setAttribute('comments', array_merge($attrs['comments'], $stmts[0]->getAttribute('comments', []))); }; + } else { + $startAttributes = $this->startAttributeStack[$stackPos-(3-1)]; if (isset($startAttributes['comments'])) { $this->semValue = new Stmt\Nop($startAttributes + $this->endAttributes); } else { $this->semValue = null; }; + if (null === $this->semValue) { $this->semValue = array(); } + } + + }, + 134 => function ($stackPos) { + $this->semValue = new Stmt\If_($this->semStack[$stackPos-(5-2)], ['stmts' => is_array($this->semStack[$stackPos-(5-3)]) ? $this->semStack[$stackPos-(5-3)] : array($this->semStack[$stackPos-(5-3)]), 'elseifs' => $this->semStack[$stackPos-(5-4)], 'else' => $this->semStack[$stackPos-(5-5)]], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 135 => function ($stackPos) { + $this->semValue = new Stmt\If_($this->semStack[$stackPos-(8-2)], ['stmts' => $this->semStack[$stackPos-(8-4)], 'elseifs' => $this->semStack[$stackPos-(8-5)], 'else' => $this->semStack[$stackPos-(8-6)]], $this->startAttributeStack[$stackPos-(8-1)] + $this->endAttributes); + }, + 136 => function ($stackPos) { + $this->semValue = new Stmt\While_($this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 137 => function ($stackPos) { + $this->semValue = new Stmt\Do_($this->semStack[$stackPos-(5-4)], is_array($this->semStack[$stackPos-(5-2)]) ? $this->semStack[$stackPos-(5-2)] : array($this->semStack[$stackPos-(5-2)]), $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 138 => function ($stackPos) { + $this->semValue = new Stmt\For_(['init' => $this->semStack[$stackPos-(9-3)], 'cond' => $this->semStack[$stackPos-(9-5)], 'loop' => $this->semStack[$stackPos-(9-7)], 'stmts' => $this->semStack[$stackPos-(9-9)]], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes); + }, + 139 => function ($stackPos) { + $this->semValue = new Stmt\Switch_($this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 140 => function ($stackPos) { + $this->semValue = new Stmt\Break_(null, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 141 => function ($stackPos) { + $this->semValue = new Stmt\Break_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 142 => function ($stackPos) { + $this->semValue = new Stmt\Continue_(null, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 143 => function ($stackPos) { + $this->semValue = new Stmt\Continue_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 144 => function ($stackPos) { + $this->semValue = new Stmt\Return_(null, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 145 => function ($stackPos) { + $this->semValue = new Stmt\Return_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 146 => function ($stackPos) { + $this->semValue = new Stmt\Global_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 147 => function ($stackPos) { + $this->semValue = new Stmt\Static_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 148 => function ($stackPos) { + $this->semValue = new Stmt\Echo_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 149 => function ($stackPos) { + $this->semValue = new Stmt\InlineHTML($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 150 => function ($stackPos) { + $this->semValue = new Stmt\Expression($this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 151 => function ($stackPos) { + $this->semValue = new Stmt\Expression($this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 152 => function ($stackPos) { + $this->semValue = new Stmt\Unset_($this->semStack[$stackPos-(5-3)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 153 => function ($stackPos) { + $this->semValue = new Stmt\Foreach_($this->semStack[$stackPos-(7-3)], $this->semStack[$stackPos-(7-5)][0], ['keyVar' => null, 'byRef' => $this->semStack[$stackPos-(7-5)][1], 'stmts' => $this->semStack[$stackPos-(7-7)]], $this->startAttributeStack[$stackPos-(7-1)] + $this->endAttributes); + }, + 154 => function ($stackPos) { + $this->semValue = new Stmt\Foreach_($this->semStack[$stackPos-(9-3)], $this->semStack[$stackPos-(9-7)][0], ['keyVar' => $this->semStack[$stackPos-(9-5)], 'byRef' => $this->semStack[$stackPos-(9-7)][1], 'stmts' => $this->semStack[$stackPos-(9-9)]], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes); + }, + 155 => function ($stackPos) { + $this->semValue = new Stmt\Declare_($this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-5)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 156 => function ($stackPos) { + $this->semValue = new Stmt\TryCatch($this->semStack[$stackPos-(6-3)], $this->semStack[$stackPos-(6-5)], $this->semStack[$stackPos-(6-6)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); $this->checkTryCatch($this->semValue); + }, + 157 => function ($stackPos) { + $this->semValue = new Stmt\Throw_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 158 => function ($stackPos) { + $this->semValue = new Stmt\Goto_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 159 => function ($stackPos) { + $this->semValue = new Stmt\Label($this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 160 => function ($stackPos) { + $this->semValue = new Stmt\Expression($this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 161 => function ($stackPos) { + $this->semValue = array(); /* means: no statement */ + }, + 162 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 163 => function ($stackPos) { + $startAttributes = $this->startAttributeStack[$stackPos-(1-1)]; if (isset($startAttributes['comments'])) { $this->semValue = new Stmt\Nop($startAttributes + $this->endAttributes); } else { $this->semValue = null; }; + if ($this->semValue === null) $this->semValue = array(); /* means: no statement */ + }, + 164 => function ($stackPos) { + $this->semValue = array(); + }, + 165 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 166 => function ($stackPos) { + $this->semValue = new Stmt\Catch_(array($this->semStack[$stackPos-(8-3)]), $this->semStack[$stackPos-(8-4)], $this->semStack[$stackPos-(8-7)], $this->startAttributeStack[$stackPos-(8-1)] + $this->endAttributes); + }, + 167 => function ($stackPos) { + $this->semValue = null; + }, + 168 => function ($stackPos) { + $this->semValue = new Stmt\Finally_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 169 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 170 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 171 => function ($stackPos) { + $this->semValue = false; + }, + 172 => function ($stackPos) { + $this->semValue = true; + }, + 173 => function ($stackPos) { + $this->semValue = false; + }, + 174 => function ($stackPos) { + $this->semValue = true; + }, + 175 => function ($stackPos) { + $this->semValue = false; + }, + 176 => function ($stackPos) { + $this->semValue = true; + }, + 177 => function ($stackPos) { + $this->semValue = new Stmt\Function_($this->semStack[$stackPos-(10-3)], ['byRef' => $this->semStack[$stackPos-(10-2)], 'params' => $this->semStack[$stackPos-(10-5)], 'returnType' => $this->semStack[$stackPos-(10-7)], 'stmts' => $this->semStack[$stackPos-(10-9)]], $this->startAttributeStack[$stackPos-(10-1)] + $this->endAttributes); + }, + 178 => function ($stackPos) { + $this->semValue = new Stmt\Class_($this->semStack[$stackPos-(7-2)], ['type' => $this->semStack[$stackPos-(7-1)], 'extends' => $this->semStack[$stackPos-(7-3)], 'implements' => $this->semStack[$stackPos-(7-4)], 'stmts' => $this->semStack[$stackPos-(7-6)]], $this->startAttributeStack[$stackPos-(7-1)] + $this->endAttributes); + $this->checkClass($this->semValue, $stackPos-(7-2)); + }, + 179 => function ($stackPos) { + $this->semValue = new Stmt\Interface_($this->semStack[$stackPos-(6-2)], ['extends' => $this->semStack[$stackPos-(6-3)], 'stmts' => $this->semStack[$stackPos-(6-5)]], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); + $this->checkInterface($this->semValue, $stackPos-(6-2)); + }, + 180 => function ($stackPos) { + $this->semValue = new Stmt\Trait_($this->semStack[$stackPos-(5-2)], ['stmts' => $this->semStack[$stackPos-(5-4)]], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 181 => function ($stackPos) { + $this->semValue = 0; + }, + 182 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_ABSTRACT; + }, + 183 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_FINAL; + }, + 184 => function ($stackPos) { + $this->semValue = null; + }, + 185 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-2)]; + }, + 186 => function ($stackPos) { + $this->semValue = array(); + }, + 187 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-2)]; + }, + 188 => function ($stackPos) { + $this->semValue = array(); + }, + 189 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-2)]; + }, + 190 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 191 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 192 => function ($stackPos) { + $this->semValue = is_array($this->semStack[$stackPos-(1-1)]) ? $this->semStack[$stackPos-(1-1)] : array($this->semStack[$stackPos-(1-1)]); + }, + 193 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-2)]; + }, + 194 => function ($stackPos) { + $this->semValue = is_array($this->semStack[$stackPos-(1-1)]) ? $this->semStack[$stackPos-(1-1)] : array($this->semStack[$stackPos-(1-1)]); + }, + 195 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-2)]; + }, + 196 => function ($stackPos) { + $this->semValue = is_array($this->semStack[$stackPos-(1-1)]) ? $this->semStack[$stackPos-(1-1)] : array($this->semStack[$stackPos-(1-1)]); + }, + 197 => function ($stackPos) { + $this->semValue = null; + }, + 198 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-2)]; + }, + 199 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 200 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 201 => function ($stackPos) { + $this->semValue = new Stmt\DeclareDeclare($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 202 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 203 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-3)]; + }, + 204 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-2)]; + }, + 205 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(5-3)]; + }, + 206 => function ($stackPos) { + $this->semValue = array(); + }, + 207 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 208 => function ($stackPos) { + $this->semValue = new Stmt\Case_($this->semStack[$stackPos-(4-2)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 209 => function ($stackPos) { + $this->semValue = new Stmt\Case_(null, $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 210 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 211 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 212 => function ($stackPos) { + $this->semValue = is_array($this->semStack[$stackPos-(1-1)]) ? $this->semStack[$stackPos-(1-1)] : array($this->semStack[$stackPos-(1-1)]); + }, + 213 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-2)]; + }, + 214 => function ($stackPos) { + $this->semValue = array(); + }, + 215 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 216 => function ($stackPos) { + $this->semValue = new Stmt\ElseIf_($this->semStack[$stackPos-(3-2)], is_array($this->semStack[$stackPos-(3-3)]) ? $this->semStack[$stackPos-(3-3)] : array($this->semStack[$stackPos-(3-3)]), $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 217 => function ($stackPos) { + $this->semValue = array(); + }, + 218 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 219 => function ($stackPos) { + $this->semValue = new Stmt\ElseIf_($this->semStack[$stackPos-(4-2)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 220 => function ($stackPos) { + $this->semValue = null; + }, + 221 => function ($stackPos) { + $this->semValue = new Stmt\Else_(is_array($this->semStack[$stackPos-(2-2)]) ? $this->semStack[$stackPos-(2-2)] : array($this->semStack[$stackPos-(2-2)]), $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 222 => function ($stackPos) { + $this->semValue = null; + }, + 223 => function ($stackPos) { + $this->semValue = new Stmt\Else_($this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 224 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)], false); + }, + 225 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(2-2)], true); + }, + 226 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)], false); + }, + 227 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 228 => function ($stackPos) { + $this->semValue = array(); + }, + 229 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 230 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 231 => function ($stackPos) { + $this->semValue = new Node\Param($this->semStack[$stackPos-(4-4)], null, $this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-2)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); $this->checkParam($this->semValue); + }, + 232 => function ($stackPos) { + $this->semValue = new Node\Param($this->semStack[$stackPos-(6-4)], $this->semStack[$stackPos-(6-6)], $this->semStack[$stackPos-(6-1)], $this->semStack[$stackPos-(6-2)], $this->semStack[$stackPos-(6-3)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); $this->checkParam($this->semValue); + }, + 233 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 234 => function ($stackPos) { + $this->semValue = new Node\Identifier('array', $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 235 => function ($stackPos) { + $this->semValue = new Node\Identifier('callable', $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 236 => function ($stackPos) { + $this->semValue = null; + }, + 237 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 238 => function ($stackPos) { + $this->semValue = null; + }, + 239 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-2)]; + }, + 240 => function ($stackPos) { + $this->semValue = array(); + }, + 241 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 242 => function ($stackPos) { + $this->semValue = array(new Node\Arg($this->semStack[$stackPos-(3-2)], false, false, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes)); + }, + 243 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 244 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 245 => function ($stackPos) { + $this->semValue = new Node\Arg($this->semStack[$stackPos-(1-1)], false, false, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 246 => function ($stackPos) { + $this->semValue = new Node\Arg($this->semStack[$stackPos-(2-2)], true, false, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 247 => function ($stackPos) { + $this->semValue = new Node\Arg($this->semStack[$stackPos-(2-2)], false, true, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 248 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 249 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 250 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 251 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 252 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 253 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 254 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 255 => function ($stackPos) { + $this->semValue = new Stmt\StaticVar($this->semStack[$stackPos-(1-1)], null, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 256 => function ($stackPos) { + $this->semValue = new Stmt\StaticVar($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 257 => function ($stackPos) { + if ($this->semStack[$stackPos-(2-2)] !== null) { $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; } + }, + 258 => function ($stackPos) { + $this->semValue = array(); + }, + 259 => function ($stackPos) { + $startAttributes = $this->lookaheadStartAttributes; if (isset($startAttributes['comments'])) { $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); } else { $nop = null; }; + if ($nop !== null) { $this->semStack[$stackPos-(1-1)][] = $nop; } $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 260 => function ($stackPos) { + $this->semValue = new Stmt\Property($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); $this->checkProperty($this->semValue, $stackPos-(3-1)); + }, + 261 => function ($stackPos) { + $this->semValue = new Stmt\ClassConst($this->semStack[$stackPos-(3-2)], 0, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 262 => function ($stackPos) { + $this->semValue = new Stmt\ClassMethod($this->semStack[$stackPos-(9-4)], ['type' => $this->semStack[$stackPos-(9-1)], 'byRef' => $this->semStack[$stackPos-(9-3)], 'params' => $this->semStack[$stackPos-(9-6)], 'returnType' => $this->semStack[$stackPos-(9-8)], 'stmts' => $this->semStack[$stackPos-(9-9)]], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes); + $this->checkClassMethod($this->semValue, $stackPos-(9-1)); + }, + 263 => function ($stackPos) { + $this->semValue = new Stmt\TraitUse($this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 264 => function ($stackPos) { + $this->semValue = array(); + }, + 265 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 266 => function ($stackPos) { + $this->semValue = array(); + }, + 267 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 268 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Precedence($this->semStack[$stackPos-(4-1)][0], $this->semStack[$stackPos-(4-1)][1], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 269 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos-(5-1)][0], $this->semStack[$stackPos-(5-1)][1], $this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-4)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 270 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos-(4-1)][0], $this->semStack[$stackPos-(4-1)][1], $this->semStack[$stackPos-(4-3)], null, $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 271 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos-(4-1)][0], $this->semStack[$stackPos-(4-1)][1], null, $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 272 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos-(4-1)][0], $this->semStack[$stackPos-(4-1)][1], null, $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 273 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)]); + }, + 274 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 275 => function ($stackPos) { + $this->semValue = array(null, $this->semStack[$stackPos-(1-1)]); + }, + 276 => function ($stackPos) { + $this->semValue = null; + }, + 277 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 278 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 279 => function ($stackPos) { + $this->semValue = 0; + }, + 280 => function ($stackPos) { + $this->semValue = 0; + }, + 281 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 282 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 283 => function ($stackPos) { + $this->checkModifier($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $stackPos-(2-2)); $this->semValue = $this->semStack[$stackPos-(2-1)] | $this->semStack[$stackPos-(2-2)]; + }, + 284 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_PUBLIC; + }, + 285 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_PROTECTED; + }, + 286 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_PRIVATE; + }, + 287 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_STATIC; + }, + 288 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_ABSTRACT; + }, + 289 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_FINAL; + }, + 290 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 291 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 292 => function ($stackPos) { + $this->semValue = new Node\VarLikeIdentifier(substr($this->semStack[$stackPos-(1-1)], 1), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 293 => function ($stackPos) { + $this->semValue = new Stmt\PropertyProperty($this->semStack[$stackPos-(1-1)], null, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 294 => function ($stackPos) { + $this->semValue = new Stmt\PropertyProperty($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 295 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 296 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 297 => function ($stackPos) { + $this->semValue = array(); + }, + 298 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 299 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 300 => function ($stackPos) { + $this->semValue = new Expr\Assign($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 301 => function ($stackPos) { + $this->semValue = new Expr\Assign($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 302 => function ($stackPos) { + $this->semValue = new Expr\AssignRef($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 303 => function ($stackPos) { + $this->semValue = new Expr\AssignRef($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 304 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 305 => function ($stackPos) { + $this->semValue = new Expr\Clone_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 306 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Plus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 307 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Minus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 308 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Mul($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 309 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Div($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 310 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Concat($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 311 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Mod($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 312 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\BitwiseAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 313 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\BitwiseOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 314 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\BitwiseXor($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 315 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\ShiftLeft($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 316 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\ShiftRight($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 317 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Pow($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 318 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Coalesce($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 319 => function ($stackPos) { + $this->semValue = new Expr\PostInc($this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 320 => function ($stackPos) { + $this->semValue = new Expr\PreInc($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 321 => function ($stackPos) { + $this->semValue = new Expr\PostDec($this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 322 => function ($stackPos) { + $this->semValue = new Expr\PreDec($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 323 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BooleanOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 324 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BooleanAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 325 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\LogicalOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 326 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\LogicalAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 327 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\LogicalXor($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 328 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 329 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 330 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 331 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseXor($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 332 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Concat($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 333 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Plus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 334 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Minus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 335 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Mul($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 336 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Div($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 337 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Mod($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 338 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\ShiftLeft($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 339 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\ShiftRight($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 340 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Pow($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 341 => function ($stackPos) { + $this->semValue = new Expr\UnaryPlus($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 342 => function ($stackPos) { + $this->semValue = new Expr\UnaryMinus($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 343 => function ($stackPos) { + $this->semValue = new Expr\BooleanNot($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 344 => function ($stackPos) { + $this->semValue = new Expr\BitwiseNot($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 345 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Identical($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 346 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\NotIdentical($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 347 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Equal($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 348 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\NotEqual($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 349 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Spaceship($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 350 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Smaller($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 351 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\SmallerOrEqual($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 352 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Greater($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 353 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\GreaterOrEqual($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 354 => function ($stackPos) { + $this->semValue = new Expr\Instanceof_($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 355 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 356 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 357 => function ($stackPos) { + $this->semValue = new Expr\Ternary($this->semStack[$stackPos-(5-1)], $this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-5)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 358 => function ($stackPos) { + $this->semValue = new Expr\Ternary($this->semStack[$stackPos-(4-1)], null, $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 359 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Coalesce($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 360 => function ($stackPos) { + $this->semValue = new Expr\Isset_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 361 => function ($stackPos) { + $this->semValue = new Expr\Empty_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 362 => function ($stackPos) { + $this->semValue = new Expr\Include_($this->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 363 => function ($stackPos) { + $this->semValue = new Expr\Include_($this->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE_ONCE, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 364 => function ($stackPos) { + $this->semValue = new Expr\Eval_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 365 => function ($stackPos) { + $this->semValue = new Expr\Include_($this->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 366 => function ($stackPos) { + $this->semValue = new Expr\Include_($this->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE_ONCE, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 367 => function ($stackPos) { + $this->semValue = new Expr\Cast\Int_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 368 => function ($stackPos) { + $attrs = $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes; + $attrs['kind'] = $this->getFloatCastKind($this->semStack[$stackPos-(2-1)]); + $this->semValue = new Expr\Cast\Double($this->semStack[$stackPos-(2-2)], $attrs); + }, + 369 => function ($stackPos) { + $this->semValue = new Expr\Cast\String_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 370 => function ($stackPos) { + $this->semValue = new Expr\Cast\Array_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 371 => function ($stackPos) { + $this->semValue = new Expr\Cast\Object_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 372 => function ($stackPos) { + $this->semValue = new Expr\Cast\Bool_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 373 => function ($stackPos) { + $this->semValue = new Expr\Cast\Unset_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 374 => function ($stackPos) { + $attrs = $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes; + $attrs['kind'] = strtolower($this->semStack[$stackPos-(2-1)]) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE; + $this->semValue = new Expr\Exit_($this->semStack[$stackPos-(2-2)], $attrs); + }, + 375 => function ($stackPos) { + $this->semValue = new Expr\ErrorSuppress($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 376 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 377 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 378 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 379 => function ($stackPos) { + $this->semValue = new Expr\ShellExec($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 380 => function ($stackPos) { + $this->semValue = new Expr\Print_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 381 => function ($stackPos) { + $this->semValue = new Expr\Yield_(null, null, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 382 => function ($stackPos) { + $this->semValue = new Expr\YieldFrom($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 383 => function ($stackPos) { + $this->semValue = new Expr\Closure(['static' => false, 'byRef' => $this->semStack[$stackPos-(10-2)], 'params' => $this->semStack[$stackPos-(10-4)], 'uses' => $this->semStack[$stackPos-(10-6)], 'returnType' => $this->semStack[$stackPos-(10-7)], 'stmts' => $this->semStack[$stackPos-(10-9)]], $this->startAttributeStack[$stackPos-(10-1)] + $this->endAttributes); + }, + 384 => function ($stackPos) { + $this->semValue = new Expr\Closure(['static' => true, 'byRef' => $this->semStack[$stackPos-(11-3)], 'params' => $this->semStack[$stackPos-(11-5)], 'uses' => $this->semStack[$stackPos-(11-7)], 'returnType' => $this->semStack[$stackPos-(11-8)], 'stmts' => $this->semStack[$stackPos-(11-10)]], $this->startAttributeStack[$stackPos-(11-1)] + $this->endAttributes); + }, + 385 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 386 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 387 => function ($stackPos) { + $this->semValue = new Expr\Yield_($this->semStack[$stackPos-(2-2)], null, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 388 => function ($stackPos) { + $this->semValue = new Expr\Yield_($this->semStack[$stackPos-(4-4)], $this->semStack[$stackPos-(4-2)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 389 => function ($stackPos) { + $attrs = $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes; $attrs['kind'] = Expr\Array_::KIND_LONG; + $this->semValue = new Expr\Array_($this->semStack[$stackPos-(4-3)], $attrs); + }, + 390 => function ($stackPos) { + $attrs = $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = Expr\Array_::KIND_SHORT; + $this->semValue = new Expr\Array_($this->semStack[$stackPos-(3-2)], $attrs); + }, + 391 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 392 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch(Scalar\String_::fromString($this->semStack[$stackPos-(4-1)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes), $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 393 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 394 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 395 => function ($stackPos) { + $this->semValue = array(new Stmt\Class_(null, ['type' => 0, 'extends' => $this->semStack[$stackPos-(7-3)], 'implements' => $this->semStack[$stackPos-(7-4)], 'stmts' => $this->semStack[$stackPos-(7-6)]], $this->startAttributeStack[$stackPos-(7-1)] + $this->endAttributes), $this->semStack[$stackPos-(7-2)]); + $this->checkClass($this->semValue[0], -1); + }, + 396 => function ($stackPos) { + $this->semValue = new Expr\New_($this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 397 => function ($stackPos) { + list($class, $ctorArgs) = $this->semStack[$stackPos-(2-2)]; $this->semValue = new Expr\New_($class, $ctorArgs, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 398 => function ($stackPos) { + $this->semValue = array(); + }, + 399 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-3)]; + }, + 400 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 401 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 402 => function ($stackPos) { + $this->semValue = new Expr\ClosureUse($this->semStack[$stackPos-(2-2)], $this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 403 => function ($stackPos) { + $this->semValue = new Expr\FuncCall($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 404 => function ($stackPos) { + $this->semValue = new Expr\StaticCall($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 405 => function ($stackPos) { + $this->semValue = new Expr\StaticCall($this->semStack[$stackPos-(6-1)], $this->semStack[$stackPos-(6-4)], $this->semStack[$stackPos-(6-6)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); + }, + 406 => function ($stackPos) { + $this->semValue = $this->fixupPhp5StaticPropCall($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 407 => function ($stackPos) { + $this->semValue = new Expr\FuncCall($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 408 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 409 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 410 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 411 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 412 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 413 => function ($stackPos) { + $this->semValue = new Name\FullyQualified(substr($this->semStack[$stackPos-(1-1)], 1), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 414 => function ($stackPos) { + $this->semValue = new Name\Relative(substr($this->semStack[$stackPos-(1-1)], 10), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 415 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 416 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 417 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 418 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 419 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 420 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 421 => function ($stackPos) { + $this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 422 => function ($stackPos) { + $this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 423 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 424 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 425 => function ($stackPos) { + $this->semValue = null; + }, + 426 => function ($stackPos) { + $this->semValue = null; + }, + 427 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 428 => function ($stackPos) { + $this->semValue = array(); + }, + 429 => function ($stackPos) { + $this->semValue = array(new Scalar\EncapsedStringPart(Scalar\String_::parseEscapeSequences($this->semStack[$stackPos-(1-1)], '`', false), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes)); + }, + 430 => function ($stackPos) { + foreach ($this->semStack[$stackPos-(1-1)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '`', false); } }; $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 431 => function ($stackPos) { + $this->semValue = array(); + }, + 432 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 433 => function ($stackPos) { + $this->semValue = $this->parseLNumber($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes, true); + }, + 434 => function ($stackPos) { + $this->semValue = Scalar\DNumber::fromString($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 435 => function ($stackPos) { + $this->semValue = Scalar\String_::fromString($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes, false); + }, + 436 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Line($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 437 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\File($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 438 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Dir($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 439 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Class_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 440 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Trait_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 441 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Method($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 442 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Function_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 443 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Namespace_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 444 => function ($stackPos) { + $this->semValue = $this->parseDocString($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(3-3)] + $this->endAttributeStack[$stackPos-(3-3)], false); + }, + 445 => function ($stackPos) { + $this->semValue = $this->parseDocString($this->semStack[$stackPos-(2-1)], '', $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(2-2)] + $this->endAttributeStack[$stackPos-(2-2)], false); + }, + 446 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 447 => function ($stackPos) { + $this->semValue = new Expr\ClassConstFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 448 => function ($stackPos) { + $this->semValue = new Expr\ConstFetch($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 449 => function ($stackPos) { + $this->semValue = new Expr\Array_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 450 => function ($stackPos) { + $this->semValue = new Expr\Array_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 451 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 452 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BooleanOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 453 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BooleanAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 454 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\LogicalOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 455 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\LogicalAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 456 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\LogicalXor($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 457 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 458 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 459 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 460 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseXor($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 461 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Concat($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 462 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Plus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 463 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Minus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 464 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Mul($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 465 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Div($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 466 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Mod($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 467 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\ShiftLeft($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 468 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\ShiftRight($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 469 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Pow($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 470 => function ($stackPos) { + $this->semValue = new Expr\UnaryPlus($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 471 => function ($stackPos) { + $this->semValue = new Expr\UnaryMinus($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 472 => function ($stackPos) { + $this->semValue = new Expr\BooleanNot($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 473 => function ($stackPos) { + $this->semValue = new Expr\BitwiseNot($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 474 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Identical($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 475 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\NotIdentical($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 476 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Equal($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 477 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\NotEqual($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 478 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Smaller($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 479 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\SmallerOrEqual($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 480 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Greater($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 481 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\GreaterOrEqual($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 482 => function ($stackPos) { + $this->semValue = new Expr\Ternary($this->semStack[$stackPos-(5-1)], $this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-5)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 483 => function ($stackPos) { + $this->semValue = new Expr\Ternary($this->semStack[$stackPos-(4-1)], null, $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 484 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 485 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 486 => function ($stackPos) { + $this->semValue = new Expr\ConstFetch($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 487 => function ($stackPos) { + $this->semValue = new Expr\ClassConstFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 488 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 489 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 490 => function ($stackPos) { + $attrs = $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED; + foreach ($this->semStack[$stackPos-(3-2)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '"', true); } }; $this->semValue = new Scalar\Encapsed($this->semStack[$stackPos-(3-2)], $attrs); + }, + 491 => function ($stackPos) { + $this->semValue = $this->parseDocString($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(3-3)] + $this->endAttributeStack[$stackPos-(3-3)], true); + }, + 492 => function ($stackPos) { + $this->semValue = array(); + }, + 493 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 494 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 495 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 496 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 497 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 498 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(3-3)], $this->semStack[$stackPos-(3-1)], false, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 499 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(1-1)], null, false, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 500 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 501 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 502 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 503 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 504 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(6-2)], $this->semStack[$stackPos-(6-5)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); + }, + 505 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 506 => function ($stackPos) { + $this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 507 => function ($stackPos) { + $this->semValue = new Expr\MethodCall($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 508 => function ($stackPos) { + $this->semValue = new Expr\FuncCall($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 509 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 510 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 511 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 512 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 513 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 514 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 515 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 516 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 517 => function ($stackPos) { + $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 518 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 519 => function ($stackPos) { + $var = substr($this->semStack[$stackPos-(1-1)], 1); $this->semValue = \is_string($var) ? new Node\VarLikeIdentifier($var, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes) : $var; + }, + 520 => function ($stackPos) { + $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 521 => function ($stackPos) { + $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos-(6-1)], $this->semStack[$stackPos-(6-5)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); + }, + 522 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 523 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 524 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 525 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 526 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 527 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 528 => function ($stackPos) { + $this->semValue = null; + }, + 529 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 530 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 531 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 532 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 533 => function ($stackPos) { + $this->semValue = new Expr\Error($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); $this->errorState = 2; + }, + 534 => function ($stackPos) { + $this->semValue = new Expr\List_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 535 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 536 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 537 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(1-1)], null, false, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 538 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(1-1)], null, false, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 539 => function ($stackPos) { + $this->semValue = null; + }, + 540 => function ($stackPos) { + $this->semValue = array(); + }, + 541 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 542 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 543 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 544 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(3-3)], $this->semStack[$stackPos-(3-1)], false, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 545 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(1-1)], null, false, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 546 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(4-4)], $this->semStack[$stackPos-(4-1)], true, $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 547 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(2-2)], null, true, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 548 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(2-2)], null, false, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes, true, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 549 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 550 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 551 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 552 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)]); + }, + 553 => function ($stackPos) { + $this->semValue = new Scalar\EncapsedStringPart($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 554 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 555 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 556 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 557 => function ($stackPos) { + $this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 558 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 559 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 560 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(6-2)], $this->semStack[$stackPos-(6-4)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); + }, + 561 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 562 => function ($stackPos) { + $this->semValue = new Scalar\String_($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 563 => function ($stackPos) { + $this->semValue = $this->parseNumString($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 564 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + ]; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Parser/Php7.php b/vendor/nikic/php-parser/lib/PhpParser/Parser/Php7.php new file mode 100644 index 0000000000..71ba0187ee --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Parser/Php7.php @@ -0,0 +1,2829 @@ +'", + "T_IS_GREATER_OR_EQUAL", + "T_SL", + "T_SR", + "'+'", + "'-'", + "'.'", + "'*'", + "'/'", + "'%'", + "'!'", + "T_INSTANCEOF", + "'~'", + "T_INC", + "T_DEC", + "T_INT_CAST", + "T_DOUBLE_CAST", + "T_STRING_CAST", + "T_ARRAY_CAST", + "T_OBJECT_CAST", + "T_BOOL_CAST", + "T_UNSET_CAST", + "'@'", + "T_POW", + "'['", + "T_NEW", + "T_CLONE", + "T_EXIT", + "T_IF", + "T_ELSEIF", + "T_ELSE", + "T_ENDIF", + "T_LNUMBER", + "T_DNUMBER", + "T_STRING", + "T_STRING_VARNAME", + "T_VARIABLE", + "T_NUM_STRING", + "T_INLINE_HTML", + "T_ENCAPSED_AND_WHITESPACE", + "T_CONSTANT_ENCAPSED_STRING", + "T_ECHO", + "T_DO", + "T_WHILE", + "T_ENDWHILE", + "T_FOR", + "T_ENDFOR", + "T_FOREACH", + "T_ENDFOREACH", + "T_DECLARE", + "T_ENDDECLARE", + "T_AS", + "T_SWITCH", + "T_MATCH", + "T_ENDSWITCH", + "T_CASE", + "T_DEFAULT", + "T_BREAK", + "T_CONTINUE", + "T_GOTO", + "T_FUNCTION", + "T_FN", + "T_CONST", + "T_RETURN", + "T_TRY", + "T_CATCH", + "T_FINALLY", + "T_USE", + "T_INSTEADOF", + "T_GLOBAL", + "T_STATIC", + "T_ABSTRACT", + "T_FINAL", + "T_PRIVATE", + "T_PROTECTED", + "T_PUBLIC", + "T_READONLY", + "T_VAR", + "T_UNSET", + "T_ISSET", + "T_EMPTY", + "T_HALT_COMPILER", + "T_CLASS", + "T_TRAIT", + "T_INTERFACE", + "T_ENUM", + "T_EXTENDS", + "T_IMPLEMENTS", + "T_OBJECT_OPERATOR", + "T_NULLSAFE_OBJECT_OPERATOR", + "T_LIST", + "T_ARRAY", + "T_CALLABLE", + "T_CLASS_C", + "T_TRAIT_C", + "T_METHOD_C", + "T_FUNC_C", + "T_LINE", + "T_FILE", + "T_START_HEREDOC", + "T_END_HEREDOC", + "T_DOLLAR_OPEN_CURLY_BRACES", + "T_CURLY_OPEN", + "T_PAAMAYIM_NEKUDOTAYIM", + "T_NAMESPACE", + "T_NS_C", + "T_DIR", + "T_NS_SEPARATOR", + "T_ELLIPSIS", + "T_NAME_FULLY_QUALIFIED", + "T_NAME_QUALIFIED", + "T_NAME_RELATIVE", + "T_ATTRIBUTE", + "';'", + "']'", + "'{'", + "'}'", + "'('", + "')'", + "'`'", + "'\"'", + "'$'" + ); + + protected $tokenToSymbol = array( + 0, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 56, 166, 168, 167, 55, 168, 168, + 163, 164, 53, 50, 8, 51, 52, 54, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 31, 159, + 44, 16, 46, 30, 68, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 70, 168, 160, 36, 168, 165, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 161, 35, 162, 58, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 1, 2, 3, 4, + 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 32, 33, 34, 37, 38, 39, 40, + 41, 42, 43, 45, 47, 48, 49, 57, 59, 60, + 61, 62, 63, 64, 65, 66, 67, 69, 71, 72, + 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, + 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, + 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, + 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, + 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, + 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, + 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, + 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, + 153, 154, 155, 156, 157, 158 + ); + + protected $action = array( + 132, 133, 134, 568, 135, 136, 0, 721, 722, 723, + 137, 37, 921, 448, 449, 450,-32766,-32766,-32766,-32767, + -32767,-32767,-32767, 101, 102, 103, 104, 105, 1071, 1072, + 1073, 1070, 1069, 1068, 1074, 715, 714,-32766,-32766,-32766, + -32766,-32766,-32766,-32766,-32766,-32766,-32767,-32767,-32767,-32767, + -32767, 371, 372, 240, 2, 724,-32766,-32766,-32766, 1001, + 1002, 415, 956,-32766,-32766,-32766, 373, 372, 12, 267, + 138, 397, 728, 729, 730, 731, 415,-32766, 421,-32766, + -32766,-32766,-32766,-32766,-32766, 732, 733, 734, 735, 736, + 737, 738, 739, 740, 741, 742, 762, 569, 763, 764, + 765, 766, 754, 755, 337, 338, 757, 758, 743, 744, + 745, 747, 748, 749, 347, 789, 790, 791, 792, 793, + 794, 750, 751, 570, 571, 783, 774, 772, 773, 786, + 769, 770, 284, 421, 572, 573, 768, 574, 575, 576, + 577, 578, 579, 597, -579,-32766,-32766, 797, 771, 580, + 581, -579, 139,-32766,-32766,-32766, 132, 133, 134, 568, + 135, 136, 1020, 721, 722, 723, 137, 37,-32766,-32766, + -32766, 542, 1306, 126,-32766, 1307,-32766,-32766,-32766,-32766, + -32766,-32766,-32766, 1071, 1072, 1073, 1070, 1069, 1068, 1074, + 957, 715, 714, -318, 993, 1261,-32766,-32766,-32766, -576, + 106, 107, 108, -268, 270, 890, -576, 910, 1196, 1195, + 1197, 724,-32766,-32766,-32766, 1049, 109,-32766,-32766,-32766, + -32766, 989, 988, 987, 990, 267, 138, 397, 728, 729, + 730, 731, 1233,-32766, 421,-32766,-32766,-32766,-32766, 1001, + 1002, 732, 733, 734, 735, 736, 737, 738, 739, 740, + 741, 742, 762, 569, 763, 764, 765, 766, 754, 755, + 337, 338, 757, 758, 743, 744, 745, 747, 748, 749, + 347, 789, 790, 791, 792, 793, 794, 750, 751, 570, + 571, 783, 774, 772, 773, 786, 769, 770, 880, 321, + 572, 573, 768, 574, 575, 576, 577, 578, 579,-32766, + 82, 83, 84, -579, 771, 580, 581, -579, 148, 746, + 716, 717, 718, 719, 720, 1281, 721, 722, 723, 759, + 760, 36, 1280, 85, 86, 87, 88, 89, 90, 91, + 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, + 102, 103, 104, 105, 106, 107, 108, 999, 270, -318, + -32766,-32766,-32766, 456, 457, 81, -193, 808, -576, 1019, + 109, 320, -576, 892, 724, 681, 802, 695, 1001, 1002, + 591,-32766, 1047,-32766,-32766,-32766, 715, 714, 725, 726, + 727, 728, 729, 730, 731, -192, -86, 795, 279, -530, + 284,-32766,-32766,-32766, 732, 733, 734, 735, 736, 737, + 738, 739, 740, 741, 742, 762, 785, 763, 764, 765, + 766, 754, 755, 756, 784, 757, 758, 743, 744, 745, + 747, 748, 749, 788, 789, 790, 791, 792, 793, 794, + 750, 751, 752, 753, 783, 774, 772, 773, 786, 769, + 770, 470, 803, 761, 767, 768, 775, 776, 778, 777, + 779, 780, -86, -530, -530, 637, 25, 771, 782, 781, + 49, 50, 51, 501, 52, 53, 239, 34, -530, 890, + 54, 55, -111, 56, 999, 128,-32766, -111, 1201, -111, + -530, -570, -536, 890, 300, -570, 144, -111, -111, -111, + -111, -111, -111, -111, -111, 1001, 1002, 1001, 1002, 686, + 1201, 925, 926, 1194, 806, 890, 927, 1296, 57, 58, + 799, 253, -193, 687, 59, 807, 60, 246, 247, 61, + 62, 63, 64, 65, 66, 67, 68, 304, 27, 268, + 69, 437, 502, -332, 306, 688, 1227, 1228, 503, 1192, + 806, -192, 318, 890, 1225, 41, 24, 504, 334, 505, + 14, 506, 880, 507, 653, 654, 508, 509, 280, 806, + 281, 43, 44, 438, 368, 367, 880, 45, 510, 35, + 249, 471, 1063, 359, 333, 103, 104, 105, 1196, 1195, + 1197, 806, 511, 512, 513, 335, 801, 1221, 880, 361, + 285, 683, 286, 365, 514, 515, 380, 1215, 1216, 1217, + 1218, 1212, 1213, 292, 433, -111, 715, 714, 434, 1219, + 1214, 149, 400, 1196, 1195, 1197, 293, -153, -153, -153, + -356, 70, -356, 316, 317, 320, 880, 892, -531, 681, + 435, 1048, -153, 707, -153, 293, -153, 1277, -153, 27, + 74, 892, 436, 681, 320, 369, 370, 833, 366, 834, + -529, 806, 382, 812, 11, 1225, 833, 150, 834, -111, + -111, 151, 74, 942, -111, 681, 320, 153, 806, 866, + -111, -111, -111, -111, 31, 110, 111, 112, 113, 114, + 115, 116, 117, 118, 119, 120, 121, 122, 715, 714, + 374, 375, -531, -531, 890, 154, 805, 155, -4, 890, + 157, 892, -88, 681, -153, 514, 515, -531, 1215, 1216, + 1217, 1218, 1212, 1213, -529, -529, 797, 1108, 1110, -531, + 1219, 1214, 715, 714, 690,-32766, 629, 630, -528, -529, + 32, 1194, 72, 123, 124, 317, 320, 129,-32766,-32766, + -32766, -529,-32766, -535,-32766, 130,-32766, 140, 143,-32766, + 158, 159, 160, 320,-32766,-32766,-32766, 161, -528,-32766, + -32766,-32766, -79, 282, -75, 1194,-32766, 412, -73, 27, + -72, -71,-32766,-32766,-32766,-32766,-32766, 880,-32766, 287, + -32766, 806, 880,-32766, -70, 1225, -69, -68,-32766,-32766, + -32766, -67, -528, -528,-32766,-32766, -66, 141, -47, -18, + -32766, 412, 147, 320, 366, 73, 428, -528, 271,-32766, + 278, 291, -51, 696, 699, -111, -111, 1201, -533, -528, + -111, 889, -528, -528, 48, 825, -111, -111, -111, -111, + 146, 327, 283, 270, 288, 109, 515, -528, 1215, 1216, + 1217, 1218, 1212, 1213, 131, 906, 661, -16, 9, -528, + 1219, 1214, 892, 797, 681,-32766, 145, 892, 1308, 681, + -4, 1194, 72,-32766, 638, 317, 320, 806,-32766,-32766, + -32766, 1078,-32766, 544,-32766, 627,-32766, 13, 656,-32766, + 548, 298, -533, -533,-32766,-32766,-32766,-32766, 296, 297, + -32766,-32766, 674, 1194, 643, 890,-32766, 412, 806, 453, + -32766,-32766,-32766, 364,-32766,-32766,-32766, 481,-32766, -533, + -32766,-32766, 47, -494, 890, 127,-32766,-32766,-32766,-32766, + 644, 657,-32766,-32766, 305, 1194, 890, 805,-32766, 412, + 1222, 301,-32766,-32766,-32766, 0,-32766,-32766,-32766, 432, + -32766, 299, 922,-32766, -111, 293, 554, 476,-32766,-32766, + -32766,-32766, 1232, -484,-32766,-32766, 697, 1194, 560, 908, + -32766, 412, 595, 817,-32766,-32766,-32766, 7,-32766,-32766, + -32766, 1234,-32766, 16, 293,-32766, 294, 295, 880, 74, + -32766,-32766,-32766, 320, 363, 39,-32766,-32766, 40, 704, + 705, 871,-32766, 412, -246, -246, -246, 880, 966, 943, + 366,-32766, 950, 125, 1247, 940, 951, 869, 938, 880, + 1052, -111, -111, -245, -245, -245, -111, 1055, 1056, 366, + 1053, 866, -111, -111, -111, -111, 1054, 1060, 701, 1265, + -111, -111, 1299, 632, -564, -111, 33, 315, -271, 362, + 866, -111, -111, -111, -111, 682, 685, 689, 691, 692, + 693, 694,-32766, 892, 698, 681, -246, 684, 1194, 867, + 1303, 1305, 828, 827, 836,-32766,-32766,-32766, 915,-32766, + 958,-32766, 892,-32766, 681, -245,-32766, 835, 1304, 914, + 916,-32766,-32766,-32766, 892, 913, 681,-32766,-32766, 1180, + 899, 909, 897,-32766, 412, 948, 949, 1302, 1259, 1248, + 1266, 1272,-32766, 1275, -269, -562, -536, -535, -534, 1, + 28, 29, 38, 42, 46, 71, 75, 76, 77, 78, + 79, 80, 142, 152, 156, 245, 322, 348, 349, 350, + 351, 352, 353, 354, 355, 356, 357, 358, 360, 429, + 0, -268, 0, 18, 19, 20, 21, 23, 399, 472, + 473, 480, 483, 484, 485, 486, 490, 491, 492, 499, + 668, 1205, 1148, 1223, 1022, 1021, 1184, -273, -103, 17, + 22, 26, 290, 398, 588, 592, 619, 673, 1152, 1200, + 1149, 1278, 0, -498, 1165, 0, 1226, 0, 320 + ); + + protected $actionCheck = array( + 2, 3, 4, 5, 6, 7, 0, 9, 10, 11, + 12, 13, 128, 129, 130, 131, 9, 10, 11, 44, + 45, 46, 47, 48, 49, 50, 51, 52, 116, 117, + 118, 119, 120, 121, 122, 37, 38, 30, 116, 32, + 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, + 43, 106, 107, 14, 8, 57, 9, 10, 11, 137, + 138, 116, 31, 9, 10, 11, 106, 107, 8, 71, + 72, 73, 74, 75, 76, 77, 116, 30, 80, 32, + 33, 34, 35, 36, 30, 87, 88, 89, 90, 91, + 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, + 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, + 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, + 132, 133, 30, 80, 136, 137, 138, 139, 140, 141, + 142, 143, 144, 51, 1, 9, 10, 80, 150, 151, + 152, 8, 154, 9, 10, 11, 2, 3, 4, 5, + 6, 7, 164, 9, 10, 11, 12, 13, 9, 10, + 11, 85, 80, 14, 30, 83, 32, 33, 34, 35, + 36, 37, 38, 116, 117, 118, 119, 120, 121, 122, + 159, 37, 38, 8, 1, 1, 9, 10, 11, 1, + 53, 54, 55, 164, 57, 1, 8, 1, 155, 156, + 157, 57, 9, 10, 11, 162, 69, 30, 116, 32, + 33, 119, 120, 121, 122, 71, 72, 73, 74, 75, + 76, 77, 146, 30, 80, 32, 33, 34, 35, 137, + 138, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, + 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, + 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, + 126, 127, 128, 129, 130, 131, 132, 133, 84, 70, + 136, 137, 138, 139, 140, 141, 142, 143, 144, 9, + 9, 10, 11, 160, 150, 151, 152, 164, 154, 2, + 3, 4, 5, 6, 7, 1, 9, 10, 11, 12, + 13, 30, 8, 32, 33, 34, 35, 36, 37, 38, + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55, 116, 57, 164, + 9, 10, 11, 134, 135, 161, 8, 1, 160, 1, + 69, 167, 164, 159, 57, 161, 80, 161, 137, 138, + 1, 30, 1, 32, 33, 34, 37, 38, 71, 72, + 73, 74, 75, 76, 77, 8, 31, 80, 30, 70, + 30, 9, 10, 11, 87, 88, 89, 90, 91, 92, + 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, + 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, + 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, + 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, + 133, 31, 156, 136, 137, 138, 139, 140, 141, 142, + 143, 144, 97, 134, 135, 75, 76, 150, 151, 152, + 2, 3, 4, 5, 6, 7, 97, 8, 149, 1, + 12, 13, 101, 15, 116, 8, 116, 106, 1, 108, + 161, 160, 163, 1, 113, 164, 8, 116, 117, 118, + 119, 120, 121, 122, 123, 137, 138, 137, 138, 31, + 1, 117, 118, 80, 82, 1, 122, 85, 50, 51, + 80, 8, 164, 31, 56, 159, 58, 59, 60, 61, + 62, 63, 64, 65, 66, 67, 68, 8, 70, 71, + 72, 73, 74, 162, 8, 31, 78, 79, 80, 116, + 82, 164, 8, 1, 86, 87, 88, 89, 8, 91, + 101, 93, 84, 95, 75, 76, 98, 99, 35, 82, + 37, 103, 104, 105, 106, 107, 84, 109, 110, 147, + 148, 161, 123, 115, 116, 50, 51, 52, 155, 156, + 157, 82, 124, 125, 126, 8, 156, 1, 84, 8, + 35, 161, 37, 8, 136, 137, 8, 139, 140, 141, + 142, 143, 144, 145, 8, 128, 37, 38, 8, 151, + 152, 101, 102, 155, 156, 157, 158, 75, 76, 77, + 106, 163, 108, 165, 166, 167, 84, 159, 70, 161, + 8, 159, 90, 161, 92, 158, 94, 1, 96, 70, + 163, 159, 8, 161, 167, 106, 107, 106, 106, 108, + 70, 82, 106, 8, 108, 86, 106, 14, 108, 117, + 118, 14, 163, 159, 122, 161, 167, 14, 82, 127, + 128, 129, 130, 131, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, 28, 29, 37, 38, + 106, 107, 134, 135, 1, 14, 155, 14, 0, 1, + 14, 159, 31, 161, 162, 136, 137, 149, 139, 140, + 141, 142, 143, 144, 134, 135, 80, 59, 60, 161, + 151, 152, 37, 38, 31, 74, 111, 112, 70, 149, + 14, 80, 163, 16, 16, 166, 167, 16, 87, 88, + 89, 161, 91, 163, 93, 16, 95, 161, 16, 98, + 16, 16, 16, 167, 103, 104, 105, 16, 70, 74, + 109, 110, 31, 35, 31, 80, 115, 116, 31, 70, + 31, 31, 87, 88, 89, 124, 91, 84, 93, 35, + 95, 82, 84, 98, 31, 86, 31, 31, 103, 104, + 105, 31, 134, 135, 109, 110, 31, 161, 31, 31, + 115, 116, 31, 167, 106, 154, 108, 149, 31, 124, + 31, 113, 31, 31, 31, 117, 118, 1, 70, 161, + 122, 31, 134, 135, 70, 127, 128, 129, 130, 131, + 31, 35, 37, 57, 37, 69, 137, 149, 139, 140, + 141, 142, 143, 144, 31, 38, 77, 31, 150, 161, + 151, 152, 159, 80, 161, 74, 70, 159, 83, 161, + 162, 80, 163, 85, 90, 166, 167, 82, 87, 88, + 89, 82, 91, 85, 93, 113, 95, 97, 94, 98, + 89, 132, 134, 135, 103, 104, 105, 74, 134, 135, + 109, 110, 92, 80, 96, 1, 115, 116, 82, 97, + 87, 88, 89, 149, 91, 124, 93, 97, 95, 161, + 116, 98, 70, 149, 1, 161, 103, 104, 105, 74, + 100, 100, 109, 110, 132, 80, 1, 155, 115, 116, + 160, 114, 87, 88, 89, -1, 91, 124, 93, 128, + 95, 133, 128, 98, 128, 158, 153, 102, 103, 104, + 105, 74, 146, 149, 109, 110, 31, 80, 81, 154, + 115, 116, 153, 160, 87, 88, 89, 149, 91, 124, + 93, 146, 95, 149, 158, 98, 134, 135, 84, 163, + 103, 104, 105, 167, 149, 159, 109, 110, 159, 159, + 159, 159, 115, 116, 100, 101, 102, 84, 159, 159, + 106, 124, 159, 161, 160, 159, 159, 159, 159, 84, + 159, 117, 118, 100, 101, 102, 122, 159, 159, 106, + 159, 127, 128, 129, 130, 131, 159, 159, 162, 160, + 117, 118, 160, 160, 163, 122, 161, 161, 164, 161, + 127, 128, 129, 130, 131, 161, 161, 161, 161, 161, + 161, 161, 74, 159, 161, 161, 162, 161, 80, 162, + 162, 162, 162, 162, 162, 87, 88, 89, 162, 91, + 162, 93, 159, 95, 161, 162, 98, 162, 162, 162, + 162, 103, 104, 105, 159, 162, 161, 109, 110, 162, + 162, 162, 162, 115, 116, 162, 162, 162, 162, 162, + 162, 162, 124, 162, 164, 163, 163, 163, 163, 163, + 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, + 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, + 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, + -1, 164, -1, 164, 164, 164, 164, 164, 164, 164, + 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, + 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, + 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, + 164, 164, -1, 165, 165, -1, 166, -1, 167 + ); + + protected $actionBase = array( + 0, -2, 154, 542, 698, 894, 913, 586, 53, 430, + 867, 307, 307, 67, 307, 307, 307, 482, 693, 693, + 925, 693, 468, 504, 204, 204, 204, 651, 651, 651, + 651, 685, 685, 845, 845, 877, 813, 781, 978, 978, + 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, + 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, + 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, + 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, + 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, + 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, + 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, + 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, + 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, + 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, + 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, + 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, + 978, 978, 356, 31, 369, 716, 1008, 1014, 1010, 1015, + 1006, 1005, 1009, 1011, 1016, 935, 936, 799, 937, 938, + 939, 941, 1012, 873, 1007, 1013, 291, 291, 291, 291, + 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, + 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, + 291, 291, 290, 159, 136, 382, 382, 382, 382, 382, + 382, 382, 382, 382, 382, 382, 382, 382, 382, 382, + 382, 382, 382, 382, 382, 54, 54, 54, 187, 569, + 569, 341, 203, 658, 47, 699, 699, 699, 699, 699, + 699, 699, 699, 699, 699, 144, 144, 7, 7, 7, + 7, 7, 371, -25, -25, -25, -25, 816, 477, 102, + 499, 358, 449, 514, 525, 525, 360, -116, 231, 231, + 231, 231, 231, 231, -78, -78, -78, -78, -78, 319, + 580, 541, 86, 423, 636, 636, 636, 636, 423, 423, + 423, 423, 825, 1020, 423, 423, 423, 558, 688, 688, + 754, 147, 147, 147, 688, 550, 788, 422, 550, 422, + 194, 92, 794, -55, -40, 321, 814, 794, 748, 842, + 198, 143, 772, 539, 772, 1004, 778, 767, 733, 868, + 896, 1017, 820, 933, 821, 934, 219, 731, 1003, 1003, + 1003, 1003, 1003, 1003, 1003, 1003, 1003, 1003, 1003, 1021, + 339, 1004, 286, 1021, 1021, 1021, 339, 339, 339, 339, + 339, 339, 339, 339, 339, 339, 615, 286, 380, 479, + 286, 796, 339, 356, 804, 356, 356, 356, 356, 964, + 356, 356, 356, 356, 356, 356, 969, 768, 410, 356, + 31, 206, 206, 472, 193, 206, 206, 206, 206, 356, + 356, 356, 539, 776, 793, 584, 809, 377, 776, 776, + 776, 355, 185, 39, 348, 555, 523, 546, 773, 773, + 789, 946, 946, 773, 785, 773, 789, 951, 773, 946, + 787, 467, 596, 540, 585, 600, 946, 519, 773, 773, + 773, 773, 622, 773, 503, 478, 773, 773, 749, 779, + 792, 46, 946, 946, 946, 792, 581, 808, 808, 808, + 830, 831, 762, 777, 534, 526, 645, 459, 807, 777, + 777, 773, 588, 762, 777, 762, 777, 805, 777, 777, + 777, 762, 777, 785, 577, 777, 734, 634, 60, 777, + 6, 952, 953, 671, 954, 949, 955, 976, 956, 957, + 884, 962, 950, 958, 948, 947, 790, 717, 718, 818, + 764, 945, 766, 766, 766, 943, 766, 766, 766, 766, + 766, 766, 766, 766, 717, 770, 835, 811, 791, 965, + 721, 729, 806, 897, 1018, 1019, 964, 997, 959, 826, + 732, 983, 966, 866, 876, 967, 968, 984, 998, 999, + 898, 786, 899, 900, 803, 970, 885, 766, 952, 957, + 950, 958, 948, 947, 765, 760, 755, 756, 753, 740, + 737, 739, 771, 1000, 942, 871, 844, 969, 944, 717, + 869, 979, 875, 985, 986, 878, 802, 775, 872, 901, + 971, 972, 973, 886, 1001, 829, 980, 874, 987, 810, + 902, 988, 989, 990, 991, 906, 887, 888, 889, 832, + 774, 940, 798, 908, 643, 744, 797, 975, 647, 963, + 890, 915, 916, 992, 993, 994, 917, 960, 839, 981, + 784, 982, 977, 840, 843, 653, 728, 795, 681, 683, + 918, 923, 927, 961, 782, 769, 846, 847, 1002, 928, + 686, 848, 735, 929, 996, 736, 741, 800, 893, 824, + 817, 780, 974, 783, 849, 930, 851, 858, 859, 995, + 861, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 458, 458, 458, 458, 458, 458, 307, 307, 307, 307, + 0, 0, 307, 0, 0, 0, 458, 458, 458, 458, + 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, + 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, + 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, + 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, + 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, + 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, + 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, + 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, + 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, + 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, + 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, + 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, + 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, + 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, + 458, 291, 291, 291, 291, 291, 291, 291, 291, 291, + 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, + 291, 291, 291, 291, 291, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 291, 291, 291, 291, 291, 291, 291, 291, 291, + 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, + 291, 291, 291, 291, 291, 291, 291, 291, 423, 423, + 291, 291, 0, 291, 423, 423, 423, 423, 423, 423, + 423, 423, 423, 423, 291, 291, 291, 291, 291, 291, + 291, 787, 147, 147, 147, 147, 423, 423, 423, 423, + 423, -88, -88, 147, 147, 423, 384, 423, 423, 423, + 423, 423, 423, 423, 423, 423, 423, 423, 0, 0, + 286, 422, 0, 785, 785, 785, 785, 0, 0, 0, + 0, 422, 422, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 286, 422, 0, 286, 0, 785, + 785, 423, 787, 787, 314, 384, 423, 0, 0, 0, + 0, 286, 785, 286, 339, 422, 339, 339, 206, 356, + 314, 510, 510, 510, 510, 0, 539, 787, 787, 787, + 787, 787, 787, 787, 787, 787, 787, 787, 785, 0, + 787, 0, 785, 785, 785, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 785, 0, 0, 946, 0, 0, 0, 0, 773, 0, + 0, 0, 0, 0, 0, 773, 951, 0, 0, 0, + 0, 0, 0, 785, 0, 0, 0, 0, 0, 0, + 0, 0, 766, 802, 0, 802, 0, 766, 766, 766 + ); + + protected $actionDefault = array( + 3,32767, 103,32767,32767,32767,32767,32767,32767,32767, + 32767,32767, 101,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767, 582, 582, 582, + 582,32767,32767, 250, 103,32767,32767, 458, 376, 376, + 376,32767,32767, 526, 526, 526, 526, 526, 526,32767, + 32767,32767,32767,32767,32767, 458,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767, 101,32767, + 32767,32767, 37, 7, 8, 10, 11, 50, 17, 314, + 32767,32767,32767,32767, 103,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767, 575,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767, 462, 441, 442, 444, + 445, 375, 527, 581, 317, 578, 374, 146, 329, 319, + 238, 320, 254, 463, 255, 464, 467, 468, 211, 283, + 371, 150, 405, 459, 407, 457, 461, 406, 381, 386, + 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, + 397, 398, 379, 380, 460, 438, 437, 436, 403,32767, + 32767, 404, 408, 378, 411,32767,32767,32767,32767,32767, + 32767,32767,32767, 103,32767, 409, 410, 427, 428, 425, + 426, 429,32767, 430, 431, 432, 433,32767,32767, 306, + 32767,32767, 355, 353, 418, 419, 306,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767, 520, + 435,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767, 103,32767, 101, 522, 400, 402, + 490, 413, 414, 412, 382,32767, 497,32767, 103, 499, + 32767,32767,32767, 112,32767,32767,32767,32767, 521,32767, + 528, 528,32767, 483, 101, 194,32767, 194, 194,32767, + 32767,32767,32767,32767,32767,32767, 589, 483, 111, 111, + 111, 111, 111, 111, 111, 111, 111, 111, 111,32767, + 194, 111,32767,32767,32767, 101, 194, 194, 194, 194, + 194, 194, 194, 194, 194, 194, 189,32767, 264, 266, + 103, 543, 194,32767, 502,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767, 495,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767, 483, 423, 139,32767, 139, 528, 415, 416, + 417, 485, 528, 528, 528, 302, 285,32767,32767,32767, + 32767, 500, 500, 101, 101, 101, 101, 495,32767,32767, + 112, 100, 100, 100, 100, 100, 104, 102,32767,32767, + 32767,32767, 100,32767, 102, 102,32767,32767, 221, 208, + 219, 102,32767, 547, 548, 219, 102, 223, 223, 223, + 243, 243, 474, 308, 102, 100, 102, 102, 196, 308, + 308,32767, 102, 474, 308, 474, 308, 198, 308, 308, + 308, 474, 308,32767, 102, 308, 210, 100, 100, 308, + 32767,32767,32767, 485,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767, 515,32767, + 532, 545, 421, 422, 424, 530, 446, 447, 448, 449, + 450, 451, 452, 454, 577,32767, 489,32767,32767,32767, + 32767, 328, 587,32767, 587,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 588,32767, 528,32767,32767,32767,32767, 420, 9, 76, + 43, 44, 52, 58, 506, 507, 508, 509, 503, 504, + 510, 505,32767,32767, 511, 553,32767,32767, 529, 580, + 32767,32767,32767,32767,32767,32767, 139,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767, 515,32767, 137, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767, 528,32767,32767,32767, 304, 305,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767, 528,32767,32767,32767, 287, 288,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767, 282,32767,32767, 370,32767,32767,32767,32767, + 349,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767, 152, 152, 3, 3, 331, 152, 152, 152, 331, + 152, 331, 331, 331, 152, 152, 152, 152, 152, 152, + 276, 184, 258, 261, 243, 243, 152, 341, 152 + ); + + protected $goto = array( + 194, 194, 669, 423, 642, 883, 839, 884, 1025, 417, + 308, 309, 330, 562, 314, 422, 331, 424, 621, 823, + 677, 851, 824, 585, 838, 857, 165, 165, 165, 165, + 218, 195, 191, 191, 175, 177, 213, 191, 191, 191, + 191, 191, 192, 192, 192, 192, 192, 192, 186, 187, + 188, 189, 190, 215, 213, 216, 522, 523, 413, 524, + 526, 527, 528, 529, 530, 531, 532, 533, 1094, 166, + 167, 168, 193, 169, 170, 171, 164, 172, 173, 174, + 176, 212, 214, 217, 235, 238, 241, 242, 244, 255, + 256, 257, 258, 259, 260, 261, 263, 264, 265, 266, + 274, 275, 311, 312, 313, 418, 419, 420, 567, 219, + 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, + 230, 231, 232, 233, 178, 234, 179, 196, 197, 198, + 236, 186, 187, 188, 189, 190, 215, 1094, 199, 180, + 181, 182, 200, 196, 183, 237, 201, 199, 163, 202, + 203, 184, 204, 205, 206, 185, 207, 208, 209, 210, + 211, 323, 323, 323, 323, 826, 607, 607, 800, 546, + 539, 1189, 1224, 1224, 1224, 1224, 1224, 1224, 1224, 1224, + 1224, 1224, 1242, 1242, 343, 464, 1267, 1268, 1242, 1242, + 1242, 1242, 1242, 1242, 1242, 1242, 1242, 1242, 389, 539, + 546, 555, 556, 396, 565, 587, 601, 602, 831, 798, + 879, 874, 875, 888, 15, 832, 876, 829, 877, 878, + 830, 455, 455, 941, 882, 804, 1190, 251, 251, 559, + 455, 1240, 1240, 814, 1046, 1042, 1043, 1240, 1240, 1240, + 1240, 1240, 1240, 1240, 1240, 1240, 1240, 605, 639, 1191, + 1250, 1251, 341, 248, 248, 248, 248, 250, 252, 819, + 819, 1193, 1193, 1000, 1193, 1000, 804, 416, 804, 596, + 1000, 1282, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, + 1000, 1000, 1000, 1264, 1264, 962, 1264, 1193, 488, 5, + 489, 6, 1193, 1193, 1193, 1193, 495, 385, 1193, 1193, + 1193, 1274, 1274, 1274, 1274, 277, 277, 277, 277, 558, + 1276, 1276, 1276, 1276, 1066, 1067, 895, 346, 553, 319, + 303, 896, 703, 620, 622, 641, 640, 346, 346, 1143, + 659, 663, 976, 667, 675, 972, 1260, 430, 1292, 1292, + 332, 346, 346, 816, 346, 636, 1309, 650, 651, 652, + 844, 536, 536, 924, 536, 1292, 525, 525, 541, 1269, + 1270, 346, 525, 525, 525, 525, 525, 525, 525, 525, + 525, 525, 1295, 617, 618, 1033, 819, 446, 395, 1262, + 1262, 1033, 935, 935, 935, 935, 563, 599, 446, 929, + 936, 933, 403, 676, 822, 1186, 552, 534, 534, 534, + 534, 841, 589, 600, 984, 1031, 1253, 965, 939, 939, + 937, 939, 702, 465, 538, 974, 969, 344, 345, 706, + 440, 900, 1082, 853, 946, 440, 440, 1035, 604, 662, + 469, 1293, 1293, 981, 1077, 540, 550, 0, 0, 0, + 540, 843, 550, 645, 960, 388, 1174, 911, 1293, 837, + 1175, 1178, 912, 1179, 0, 566, 458, 459, 460, 541, + 849, 1185, 0, 1300, 1301, 254, 254, 401, 402, 0, + 0, 0, 648, 0, 649, 0, 405, 406, 407, 0, + 660, 0, 0, 408, 0, 0, 0, 339, 847, 594, + 608, 611, 612, 613, 614, 633, 634, 635, 679, 918, + 995, 1003, 1007, 1004, 1008, 0, 440, 440, 440, 440, + 440, 440, 440, 440, 440, 440, 440, 0, 1188, 440, + 852, 840, 1030, 1034, 584, 1059, 0, 680, 666, 666, + 944, 496, 672, 1057, 387, 391, 547, 586, 590, 425, + 0, 0, 0, 0, 0, 0, 425, 0, 0, 0, + 0, 0, 0, 934, 1012, 1005, 1009, 1006, 1010, 0, + 0, 0, 0, 0, 272, 0, 0, 0, 0, 537, + 537, 0, 0, 0, 0, 1075, 856, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 979, + 979 + ); + + protected $gotoCheck = array( + 42, 42, 72, 65, 65, 64, 35, 64, 121, 65, + 65, 65, 65, 65, 65, 65, 65, 65, 65, 26, + 9, 35, 27, 124, 35, 45, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 23, 23, 23, 23, 15, 106, 106, 7, 75, + 75, 20, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 162, 162, 95, 168, 168, 168, 162, 162, + 162, 162, 162, 162, 162, 162, 162, 162, 75, 75, + 75, 75, 75, 75, 75, 75, 75, 75, 15, 6, + 15, 15, 15, 15, 75, 15, 15, 15, 15, 15, + 15, 143, 143, 49, 15, 12, 20, 5, 5, 164, + 143, 163, 163, 20, 15, 15, 15, 163, 163, 163, + 163, 163, 163, 163, 163, 163, 163, 55, 55, 20, + 20, 20, 171, 5, 5, 5, 5, 5, 5, 22, + 22, 72, 72, 72, 72, 72, 12, 13, 12, 13, + 72, 173, 72, 72, 72, 72, 72, 72, 72, 72, + 72, 72, 72, 124, 124, 101, 124, 72, 149, 46, + 149, 46, 72, 72, 72, 72, 149, 61, 72, 72, + 72, 9, 9, 9, 9, 24, 24, 24, 24, 102, + 124, 124, 124, 124, 138, 138, 72, 14, 48, 161, + 161, 72, 48, 48, 48, 63, 48, 14, 14, 145, + 48, 48, 48, 48, 48, 48, 124, 111, 174, 174, + 29, 14, 14, 18, 14, 84, 14, 84, 84, 84, + 39, 19, 19, 90, 19, 174, 165, 165, 14, 170, + 170, 14, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 174, 83, 83, 124, 22, 19, 28, 124, + 124, 124, 19, 19, 19, 19, 2, 2, 19, 19, + 19, 91, 91, 91, 25, 154, 9, 105, 105, 105, + 105, 37, 105, 9, 108, 123, 14, 25, 25, 25, + 25, 25, 25, 151, 25, 25, 25, 95, 95, 97, + 23, 17, 17, 41, 94, 23, 23, 126, 17, 14, + 82, 175, 175, 17, 141, 9, 9, -1, -1, -1, + 9, 17, 9, 17, 17, 9, 78, 78, 175, 17, + 78, 78, 78, 78, -1, 9, 9, 9, 9, 14, + 9, 17, -1, 9, 9, 5, 5, 80, 80, -1, + -1, -1, 80, -1, 80, -1, 80, 80, 80, -1, + 80, -1, -1, 80, -1, -1, -1, 80, 9, 79, + 79, 79, 79, 79, 79, 79, 79, 79, 79, 87, + 87, 87, 87, 87, 87, -1, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, -1, 14, 23, + 16, 16, 16, 16, 8, 8, -1, 8, 8, 8, + 16, 8, 8, 8, 58, 58, 58, 58, 58, 115, + -1, -1, -1, -1, -1, -1, 115, -1, -1, -1, + -1, -1, -1, 16, 115, 115, 115, 115, 115, -1, + -1, -1, -1, -1, 24, -1, -1, -1, -1, 24, + 24, -1, -1, -1, -1, 16, 16, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 105, + 105 + ); + + protected $gotoBase = array( + 0, 0, -297, 0, 0, 226, 196, 159, 517, 7, + 0, 0, -66, -65, 25, -175, 78, -33, 39, 84, + -213, 0, -64, 158, 302, 390, 15, 18, 46, 49, + 0, 0, 0, 0, 0, -356, 0, 67, 0, 32, + 0, -10, -1, 0, 0, 13, -417, 0, -364, 200, + 0, 0, 0, 0, 0, 208, 0, 0, 490, 0, + 0, 256, 0, 85, -14, -236, 0, 0, 0, 0, + 0, 0, -6, 0, 0, -168, 0, 0, 45, 140, + -12, 0, -35, -95, -344, 0, 0, 221, 0, 0, + 27, 92, 0, 0, -11, -287, 0, 19, 0, 0, + 0, 251, 267, 0, 0, 370, -73, 0, 43, 0, + 0, 61, 0, 0, 0, 270, 0, 0, 0, 0, + 0, 6, 0, 40, 16, 0, -7, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, + 0, -2, 0, 188, 0, 59, 0, 0, 0, -195, + 0, -19, 0, 0, 35, 0, 0, 0, 0, 0, + 0, 3, -57, -8, 201, 117, 0, 0, -110, 0, + -4, 223, 0, 241, 36, 129, 0, 0 + ); + + protected $gotoDefault = array( + -32768, 500, 710, 4, 711, 904, 787, 796, 582, 516, + 678, 340, 609, 414, 1258, 881, 1081, 564, 815, 1202, + 1210, 447, 818, 324, 700, 863, 864, 865, 392, 377, + 383, 390, 631, 610, 482, 850, 443, 842, 474, 845, + 442, 854, 162, 411, 498, 858, 3, 860, 543, 891, + 378, 868, 379, 655, 870, 549, 872, 873, 386, 393, + 394, 1086, 557, 606, 885, 243, 551, 886, 376, 887, + 894, 381, 384, 664, 454, 493, 487, 404, 1061, 593, + 628, 451, 468, 616, 615, 603, 467, 426, 409, 326, + 923, 931, 475, 452, 945, 342, 953, 708, 1093, 623, + 477, 961, 624, 968, 971, 517, 518, 466, 983, 269, + 986, 478, 1018, 646, 647, 998, 625, 626, 1016, 461, + 583, 1024, 444, 1032, 1246, 445, 1036, 262, 1039, 276, + 410, 427, 1044, 1045, 8, 1051, 670, 671, 10, 273, + 497, 1076, 665, 441, 1092, 431, 1162, 1164, 545, 479, + 1182, 1181, 658, 494, 1187, 1249, 439, 519, 462, 310, + 520, 302, 328, 307, 535, 289, 329, 521, 463, 1255, + 1263, 325, 30, 1283, 1294, 336, 561, 598 + ); + + protected $ruleToNonTerminal = array( + 0, 1, 3, 3, 2, 5, 5, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, + 7, 7, 7, 7, 7, 7, 8, 8, 9, 10, + 11, 11, 11, 12, 12, 13, 13, 14, 15, 15, + 16, 16, 17, 17, 18, 18, 21, 21, 22, 23, + 23, 24, 24, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 29, 29, 30, 30, 32, 34, + 34, 28, 36, 36, 33, 38, 38, 35, 35, 37, + 37, 39, 39, 31, 40, 40, 41, 43, 44, 44, + 45, 46, 46, 48, 47, 47, 47, 47, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 25, 25, 68, 68, 71, 71, 70, 69, + 69, 62, 74, 74, 75, 75, 76, 76, 77, 77, + 78, 78, 26, 26, 27, 27, 27, 27, 86, 86, + 88, 88, 81, 81, 89, 89, 90, 90, 90, 82, + 82, 85, 85, 83, 83, 91, 92, 92, 56, 56, + 64, 64, 67, 67, 67, 66, 93, 93, 94, 57, + 57, 57, 57, 95, 95, 96, 96, 97, 97, 98, + 99, 99, 100, 100, 101, 101, 54, 54, 50, 50, + 103, 52, 52, 104, 51, 51, 53, 53, 63, 63, + 63, 63, 79, 79, 107, 107, 109, 109, 110, 110, + 110, 110, 108, 108, 108, 112, 112, 112, 112, 87, + 87, 115, 115, 115, 113, 113, 116, 116, 114, 114, + 117, 117, 118, 118, 118, 118, 111, 111, 80, 80, + 80, 20, 20, 20, 120, 119, 119, 121, 121, 121, + 121, 59, 122, 122, 123, 60, 125, 125, 126, 126, + 127, 127, 84, 128, 128, 128, 128, 128, 128, 133, + 133, 134, 134, 135, 135, 135, 135, 135, 136, 137, + 137, 132, 132, 129, 129, 131, 131, 139, 139, 138, + 138, 138, 138, 138, 138, 138, 130, 140, 140, 142, + 141, 141, 61, 102, 143, 143, 55, 55, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 150, 144, 144, 149, 149, 152, 153, 153, 154, + 155, 155, 155, 19, 19, 72, 72, 72, 72, 145, + 145, 145, 145, 157, 157, 146, 146, 148, 148, 148, + 151, 151, 162, 162, 162, 162, 162, 162, 162, 162, + 162, 163, 163, 106, 165, 165, 165, 165, 147, 147, + 147, 147, 147, 147, 147, 147, 58, 58, 160, 160, + 160, 160, 166, 166, 156, 156, 156, 167, 167, 167, + 167, 167, 167, 73, 73, 65, 65, 65, 65, 124, + 124, 124, 124, 170, 169, 159, 159, 159, 159, 159, + 159, 159, 158, 158, 158, 168, 168, 168, 168, 105, + 164, 172, 172, 171, 171, 173, 173, 173, 173, 173, + 173, 173, 173, 161, 161, 161, 161, 175, 176, 174, + 174, 174, 174, 174, 174, 174, 174, 177, 177, 177, + 177 + ); + + protected $ruleToLength = array( + 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 1, 0, 1, 1, 2, 1, 3, 4, 1, + 2, 0, 1, 1, 1, 1, 1, 3, 5, 4, + 3, 4, 2, 3, 1, 1, 7, 6, 2, 3, + 1, 2, 3, 1, 2, 3, 1, 1, 3, 1, + 3, 1, 2, 2, 3, 1, 3, 2, 3, 1, + 3, 2, 0, 1, 1, 1, 1, 1, 3, 7, + 10, 5, 7, 9, 5, 3, 3, 3, 3, 3, + 3, 1, 2, 5, 7, 9, 6, 5, 6, 3, + 2, 1, 1, 1, 0, 2, 1, 3, 8, 0, + 4, 2, 1, 3, 0, 1, 0, 1, 0, 1, + 3, 1, 8, 9, 8, 7, 6, 8, 0, 2, + 0, 2, 1, 2, 1, 2, 1, 1, 1, 0, + 2, 0, 2, 0, 2, 2, 1, 3, 1, 4, + 1, 4, 1, 1, 4, 2, 1, 3, 3, 3, + 4, 4, 5, 0, 2, 4, 3, 1, 1, 7, + 0, 2, 1, 3, 3, 4, 1, 4, 0, 2, + 5, 0, 2, 6, 0, 2, 0, 3, 1, 2, + 1, 1, 2, 0, 1, 3, 0, 2, 1, 1, + 1, 1, 6, 8, 6, 1, 2, 1, 1, 1, + 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, + 3, 3, 1, 2, 1, 1, 0, 1, 0, 2, + 2, 2, 4, 3, 1, 1, 3, 1, 2, 2, + 3, 2, 3, 1, 1, 2, 3, 1, 1, 3, + 2, 0, 1, 5, 5, 10, 3, 5, 1, 1, + 3, 0, 2, 4, 5, 4, 4, 4, 3, 1, + 1, 1, 1, 1, 1, 0, 1, 1, 2, 1, + 1, 1, 1, 1, 1, 1, 2, 1, 3, 1, + 1, 3, 2, 2, 3, 1, 0, 1, 1, 3, + 3, 3, 4, 1, 1, 2, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, + 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 2, 2, 2, 2, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 5, 4, 3, 4, + 4, 2, 2, 4, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 1, 3, 2, 1, 2, + 4, 2, 2, 8, 9, 8, 9, 9, 10, 9, + 10, 8, 3, 2, 0, 4, 2, 1, 3, 2, + 2, 2, 4, 1, 1, 1, 1, 1, 1, 1, + 1, 3, 1, 1, 1, 0, 3, 0, 1, 1, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 3, 3, 3, 4, 1, 1, 3, 1, 1, + 1, 1, 1, 3, 2, 3, 0, 1, 1, 3, + 1, 1, 1, 1, 1, 3, 1, 1, 4, 4, + 1, 4, 4, 0, 1, 1, 1, 3, 3, 1, + 4, 2, 2, 1, 3, 1, 4, 4, 3, 3, + 3, 3, 1, 3, 1, 1, 3, 1, 1, 4, + 1, 1, 1, 3, 1, 1, 2, 1, 3, 4, + 3, 2, 0, 2, 2, 1, 2, 1, 1, 1, + 4, 3, 3, 3, 3, 6, 3, 1, 1, 2, + 1 + ); + + protected function initReduceCallbacks() { + $this->reduceCallbacks = [ + 0 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 1 => function ($stackPos) { + $this->semValue = $this->handleNamespaces($this->semStack[$stackPos-(1-1)]); + }, + 2 => function ($stackPos) { + if (is_array($this->semStack[$stackPos-(2-2)])) { $this->semValue = array_merge($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)]); } else { $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; }; + }, + 3 => function ($stackPos) { + $this->semValue = array(); + }, + 4 => function ($stackPos) { + $startAttributes = $this->lookaheadStartAttributes; if (isset($startAttributes['comments'])) { $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); } else { $nop = null; }; + if ($nop !== null) { $this->semStack[$stackPos-(1-1)][] = $nop; } $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 5 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 6 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 7 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 8 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 9 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 10 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 11 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 12 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 13 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 14 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 15 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 16 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 17 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 18 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 19 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 20 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 21 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 22 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 23 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 24 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 25 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 26 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 27 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 28 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 29 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 30 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 31 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 32 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 33 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 34 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 35 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 36 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 37 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 38 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 39 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 40 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 41 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 42 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 43 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 44 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 45 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 46 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 47 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 48 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 49 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 50 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 51 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 52 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 53 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 54 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 55 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 56 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 57 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 58 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 59 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 60 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 61 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 62 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 63 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 64 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 65 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 66 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 67 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 68 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 69 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 70 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 71 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 72 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 73 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 74 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 75 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 76 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 77 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 78 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 79 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 80 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 81 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 82 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 83 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 84 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 85 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 86 => function ($stackPos) { + $this->semValue = new Node\Identifier($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 87 => function ($stackPos) { + $this->semValue = new Node\Identifier($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 88 => function ($stackPos) { + $this->semValue = new Node\Identifier($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 89 => function ($stackPos) { + $this->semValue = new Node\Identifier($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 90 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 91 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 92 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 93 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 94 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 95 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 96 => function ($stackPos) { + $this->semValue = new Name(substr($this->semStack[$stackPos-(1-1)], 1), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 97 => function ($stackPos) { + $this->semValue = new Expr\Variable(substr($this->semStack[$stackPos-(1-1)], 1), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 98 => function ($stackPos) { + /* nothing */ + }, + 99 => function ($stackPos) { + /* nothing */ + }, + 100 => function ($stackPos) { + /* nothing */ + }, + 101 => function ($stackPos) { + $this->emitError(new Error('A trailing comma is not allowed here', $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes)); + }, + 102 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 103 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 104 => function ($stackPos) { + $this->semValue = new Node\Attribute($this->semStack[$stackPos-(1-1)], [], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 105 => function ($stackPos) { + $this->semValue = new Node\Attribute($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 106 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 107 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 108 => function ($stackPos) { + $this->semValue = new Node\AttributeGroup($this->semStack[$stackPos-(4-2)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 109 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 110 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 111 => function ($stackPos) { + $this->semValue = []; + }, + 112 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 113 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 114 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 115 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 116 => function ($stackPos) { + $this->semValue = new Stmt\HaltCompiler($this->lexer->handleHaltCompiler(), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 117 => function ($stackPos) { + $this->semValue = new Stmt\Namespace_($this->semStack[$stackPos-(3-2)], null, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + $this->semValue->setAttribute('kind', Stmt\Namespace_::KIND_SEMICOLON); + $this->checkNamespace($this->semValue); + }, + 118 => function ($stackPos) { + $this->semValue = new Stmt\Namespace_($this->semStack[$stackPos-(5-2)], $this->semStack[$stackPos-(5-4)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + $this->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); + $this->checkNamespace($this->semValue); + }, + 119 => function ($stackPos) { + $this->semValue = new Stmt\Namespace_(null, $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + $this->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); + $this->checkNamespace($this->semValue); + }, + 120 => function ($stackPos) { + $this->semValue = new Stmt\Use_($this->semStack[$stackPos-(3-2)], Stmt\Use_::TYPE_NORMAL, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 121 => function ($stackPos) { + $this->semValue = new Stmt\Use_($this->semStack[$stackPos-(4-3)], $this->semStack[$stackPos-(4-2)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 122 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 123 => function ($stackPos) { + $this->semValue = new Stmt\Const_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 124 => function ($stackPos) { + $this->semValue = Stmt\Use_::TYPE_FUNCTION; + }, + 125 => function ($stackPos) { + $this->semValue = Stmt\Use_::TYPE_CONSTANT; + }, + 126 => function ($stackPos) { + $this->semValue = new Stmt\GroupUse($this->semStack[$stackPos-(7-3)], $this->semStack[$stackPos-(7-6)], $this->semStack[$stackPos-(7-2)], $this->startAttributeStack[$stackPos-(7-1)] + $this->endAttributes); + }, + 127 => function ($stackPos) { + $this->semValue = new Stmt\GroupUse($this->semStack[$stackPos-(6-2)], $this->semStack[$stackPos-(6-5)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); + }, + 128 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 129 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 130 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 131 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 132 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 133 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 134 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 135 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 136 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 137 => function ($stackPos) { + $this->semValue = new Stmt\UseUse($this->semStack[$stackPos-(1-1)], null, Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); $this->checkUseUse($this->semValue, $stackPos-(1-1)); + }, + 138 => function ($stackPos) { + $this->semValue = new Stmt\UseUse($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); $this->checkUseUse($this->semValue, $stackPos-(3-3)); + }, + 139 => function ($stackPos) { + $this->semValue = new Stmt\UseUse($this->semStack[$stackPos-(1-1)], null, Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); $this->checkUseUse($this->semValue, $stackPos-(1-1)); + }, + 140 => function ($stackPos) { + $this->semValue = new Stmt\UseUse($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); $this->checkUseUse($this->semValue, $stackPos-(3-3)); + }, + 141 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; $this->semValue->type = Stmt\Use_::TYPE_NORMAL; + }, + 142 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-2)]; $this->semValue->type = $this->semStack[$stackPos-(2-1)]; + }, + 143 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 144 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 145 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 146 => function ($stackPos) { + $this->semValue = new Node\Const_($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 147 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 148 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 149 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 150 => function ($stackPos) { + $this->semValue = new Node\Const_($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 151 => function ($stackPos) { + if (is_array($this->semStack[$stackPos-(2-2)])) { $this->semValue = array_merge($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)]); } else { $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; }; + }, + 152 => function ($stackPos) { + $this->semValue = array(); + }, + 153 => function ($stackPos) { + $startAttributes = $this->lookaheadStartAttributes; if (isset($startAttributes['comments'])) { $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); } else { $nop = null; }; + if ($nop !== null) { $this->semStack[$stackPos-(1-1)][] = $nop; } $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 154 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 155 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 156 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 157 => function ($stackPos) { + throw new Error('__HALT_COMPILER() can only be used from the outermost scope', $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 158 => function ($stackPos) { + + if ($this->semStack[$stackPos-(3-2)]) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; $attrs = $this->startAttributeStack[$stackPos-(3-1)]; $stmts = $this->semValue; if (!empty($attrs['comments'])) {$stmts[0]->setAttribute('comments', array_merge($attrs['comments'], $stmts[0]->getAttribute('comments', []))); }; + } else { + $startAttributes = $this->startAttributeStack[$stackPos-(3-1)]; if (isset($startAttributes['comments'])) { $this->semValue = new Stmt\Nop($startAttributes + $this->endAttributes); } else { $this->semValue = null; }; + if (null === $this->semValue) { $this->semValue = array(); } + } + + }, + 159 => function ($stackPos) { + $this->semValue = new Stmt\If_($this->semStack[$stackPos-(7-3)], ['stmts' => is_array($this->semStack[$stackPos-(7-5)]) ? $this->semStack[$stackPos-(7-5)] : array($this->semStack[$stackPos-(7-5)]), 'elseifs' => $this->semStack[$stackPos-(7-6)], 'else' => $this->semStack[$stackPos-(7-7)]], $this->startAttributeStack[$stackPos-(7-1)] + $this->endAttributes); + }, + 160 => function ($stackPos) { + $this->semValue = new Stmt\If_($this->semStack[$stackPos-(10-3)], ['stmts' => $this->semStack[$stackPos-(10-6)], 'elseifs' => $this->semStack[$stackPos-(10-7)], 'else' => $this->semStack[$stackPos-(10-8)]], $this->startAttributeStack[$stackPos-(10-1)] + $this->endAttributes); + }, + 161 => function ($stackPos) { + $this->semValue = new Stmt\While_($this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-5)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 162 => function ($stackPos) { + $this->semValue = new Stmt\Do_($this->semStack[$stackPos-(7-5)], is_array($this->semStack[$stackPos-(7-2)]) ? $this->semStack[$stackPos-(7-2)] : array($this->semStack[$stackPos-(7-2)]), $this->startAttributeStack[$stackPos-(7-1)] + $this->endAttributes); + }, + 163 => function ($stackPos) { + $this->semValue = new Stmt\For_(['init' => $this->semStack[$stackPos-(9-3)], 'cond' => $this->semStack[$stackPos-(9-5)], 'loop' => $this->semStack[$stackPos-(9-7)], 'stmts' => $this->semStack[$stackPos-(9-9)]], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes); + }, + 164 => function ($stackPos) { + $this->semValue = new Stmt\Switch_($this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-5)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 165 => function ($stackPos) { + $this->semValue = new Stmt\Break_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 166 => function ($stackPos) { + $this->semValue = new Stmt\Continue_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 167 => function ($stackPos) { + $this->semValue = new Stmt\Return_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 168 => function ($stackPos) { + $this->semValue = new Stmt\Global_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 169 => function ($stackPos) { + $this->semValue = new Stmt\Static_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 170 => function ($stackPos) { + $this->semValue = new Stmt\Echo_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 171 => function ($stackPos) { + $this->semValue = new Stmt\InlineHTML($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 172 => function ($stackPos) { + + $e = $this->semStack[$stackPos-(2-1)]; + if ($e instanceof Expr\Throw_) { + // For backwards-compatibility reasons, convert throw in statement position into + // Stmt\Throw_ rather than Stmt\Expression(Expr\Throw_). + $this->semValue = new Stmt\Throw_($e->expr, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + } else { + $this->semValue = new Stmt\Expression($e, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + } + + }, + 173 => function ($stackPos) { + $this->semValue = new Stmt\Unset_($this->semStack[$stackPos-(5-3)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 174 => function ($stackPos) { + $this->semValue = new Stmt\Foreach_($this->semStack[$stackPos-(7-3)], $this->semStack[$stackPos-(7-5)][0], ['keyVar' => null, 'byRef' => $this->semStack[$stackPos-(7-5)][1], 'stmts' => $this->semStack[$stackPos-(7-7)]], $this->startAttributeStack[$stackPos-(7-1)] + $this->endAttributes); + }, + 175 => function ($stackPos) { + $this->semValue = new Stmt\Foreach_($this->semStack[$stackPos-(9-3)], $this->semStack[$stackPos-(9-7)][0], ['keyVar' => $this->semStack[$stackPos-(9-5)], 'byRef' => $this->semStack[$stackPos-(9-7)][1], 'stmts' => $this->semStack[$stackPos-(9-9)]], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes); + }, + 176 => function ($stackPos) { + $this->semValue = new Stmt\Foreach_($this->semStack[$stackPos-(6-3)], new Expr\Error($this->startAttributeStack[$stackPos-(6-4)] + $this->endAttributeStack[$stackPos-(6-4)]), ['stmts' => $this->semStack[$stackPos-(6-6)]], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); + }, + 177 => function ($stackPos) { + $this->semValue = new Stmt\Declare_($this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-5)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 178 => function ($stackPos) { + $this->semValue = new Stmt\TryCatch($this->semStack[$stackPos-(6-3)], $this->semStack[$stackPos-(6-5)], $this->semStack[$stackPos-(6-6)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); $this->checkTryCatch($this->semValue); + }, + 179 => function ($stackPos) { + $this->semValue = new Stmt\Goto_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 180 => function ($stackPos) { + $this->semValue = new Stmt\Label($this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 181 => function ($stackPos) { + $this->semValue = array(); /* means: no statement */ + }, + 182 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 183 => function ($stackPos) { + $startAttributes = $this->startAttributeStack[$stackPos-(1-1)]; if (isset($startAttributes['comments'])) { $this->semValue = new Stmt\Nop($startAttributes + $this->endAttributes); } else { $this->semValue = null; }; + if ($this->semValue === null) $this->semValue = array(); /* means: no statement */ + }, + 184 => function ($stackPos) { + $this->semValue = array(); + }, + 185 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 186 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 187 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 188 => function ($stackPos) { + $this->semValue = new Stmt\Catch_($this->semStack[$stackPos-(8-3)], $this->semStack[$stackPos-(8-4)], $this->semStack[$stackPos-(8-7)], $this->startAttributeStack[$stackPos-(8-1)] + $this->endAttributes); + }, + 189 => function ($stackPos) { + $this->semValue = null; + }, + 190 => function ($stackPos) { + $this->semValue = new Stmt\Finally_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 191 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 192 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 193 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 194 => function ($stackPos) { + $this->semValue = false; + }, + 195 => function ($stackPos) { + $this->semValue = true; + }, + 196 => function ($stackPos) { + $this->semValue = false; + }, + 197 => function ($stackPos) { + $this->semValue = true; + }, + 198 => function ($stackPos) { + $this->semValue = false; + }, + 199 => function ($stackPos) { + $this->semValue = true; + }, + 200 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 201 => function ($stackPos) { + $this->semValue = []; + }, + 202 => function ($stackPos) { + $this->semValue = new Stmt\Function_($this->semStack[$stackPos-(8-3)], ['byRef' => $this->semStack[$stackPos-(8-2)], 'params' => $this->semStack[$stackPos-(8-5)], 'returnType' => $this->semStack[$stackPos-(8-7)], 'stmts' => $this->semStack[$stackPos-(8-8)], 'attrGroups' => []], $this->startAttributeStack[$stackPos-(8-1)] + $this->endAttributes); + }, + 203 => function ($stackPos) { + $this->semValue = new Stmt\Function_($this->semStack[$stackPos-(9-4)], ['byRef' => $this->semStack[$stackPos-(9-3)], 'params' => $this->semStack[$stackPos-(9-6)], 'returnType' => $this->semStack[$stackPos-(9-8)], 'stmts' => $this->semStack[$stackPos-(9-9)], 'attrGroups' => $this->semStack[$stackPos-(9-1)]], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes); + }, + 204 => function ($stackPos) { + $this->semValue = new Stmt\Class_($this->semStack[$stackPos-(8-3)], ['type' => $this->semStack[$stackPos-(8-2)], 'extends' => $this->semStack[$stackPos-(8-4)], 'implements' => $this->semStack[$stackPos-(8-5)], 'stmts' => $this->semStack[$stackPos-(8-7)], 'attrGroups' => $this->semStack[$stackPos-(8-1)]], $this->startAttributeStack[$stackPos-(8-1)] + $this->endAttributes); + $this->checkClass($this->semValue, $stackPos-(8-3)); + }, + 205 => function ($stackPos) { + $this->semValue = new Stmt\Interface_($this->semStack[$stackPos-(7-3)], ['extends' => $this->semStack[$stackPos-(7-4)], 'stmts' => $this->semStack[$stackPos-(7-6)], 'attrGroups' => $this->semStack[$stackPos-(7-1)]], $this->startAttributeStack[$stackPos-(7-1)] + $this->endAttributes); + $this->checkInterface($this->semValue, $stackPos-(7-3)); + }, + 206 => function ($stackPos) { + $this->semValue = new Stmt\Trait_($this->semStack[$stackPos-(6-3)], ['stmts' => $this->semStack[$stackPos-(6-5)], 'attrGroups' => $this->semStack[$stackPos-(6-1)]], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); + }, + 207 => function ($stackPos) { + $this->semValue = new Stmt\Enum_($this->semStack[$stackPos-(8-3)], ['scalarType' => $this->semStack[$stackPos-(8-4)], 'implements' => $this->semStack[$stackPos-(8-5)], 'stmts' => $this->semStack[$stackPos-(8-7)], 'attrGroups' => $this->semStack[$stackPos-(8-1)]], $this->startAttributeStack[$stackPos-(8-1)] + $this->endAttributes); + $this->checkEnum($this->semValue, $stackPos-(8-3)); + }, + 208 => function ($stackPos) { + $this->semValue = null; + }, + 209 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-2)]; + }, + 210 => function ($stackPos) { + $this->semValue = null; + }, + 211 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-2)]; + }, + 212 => function ($stackPos) { + $this->semValue = 0; + }, + 213 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 214 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 215 => function ($stackPos) { + $this->checkClassModifier($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $stackPos-(2-2)); $this->semValue = $this->semStack[$stackPos-(2-1)] | $this->semStack[$stackPos-(2-2)]; + }, + 216 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_ABSTRACT; + }, + 217 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_FINAL; + }, + 218 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_READONLY; + }, + 219 => function ($stackPos) { + $this->semValue = null; + }, + 220 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-2)]; + }, + 221 => function ($stackPos) { + $this->semValue = array(); + }, + 222 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-2)]; + }, + 223 => function ($stackPos) { + $this->semValue = array(); + }, + 224 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-2)]; + }, + 225 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 226 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 227 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 228 => function ($stackPos) { + $this->semValue = is_array($this->semStack[$stackPos-(1-1)]) ? $this->semStack[$stackPos-(1-1)] : array($this->semStack[$stackPos-(1-1)]); + }, + 229 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-2)]; + }, + 230 => function ($stackPos) { + $this->semValue = is_array($this->semStack[$stackPos-(1-1)]) ? $this->semStack[$stackPos-(1-1)] : array($this->semStack[$stackPos-(1-1)]); + }, + 231 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-2)]; + }, + 232 => function ($stackPos) { + $this->semValue = is_array($this->semStack[$stackPos-(1-1)]) ? $this->semStack[$stackPos-(1-1)] : array($this->semStack[$stackPos-(1-1)]); + }, + 233 => function ($stackPos) { + $this->semValue = null; + }, + 234 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-2)]; + }, + 235 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 236 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 237 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 238 => function ($stackPos) { + $this->semValue = new Stmt\DeclareDeclare($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 239 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 240 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-3)]; + }, + 241 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-2)]; + }, + 242 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(5-3)]; + }, + 243 => function ($stackPos) { + $this->semValue = array(); + }, + 244 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 245 => function ($stackPos) { + $this->semValue = new Stmt\Case_($this->semStack[$stackPos-(4-2)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 246 => function ($stackPos) { + $this->semValue = new Stmt\Case_(null, $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 247 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 248 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 249 => function ($stackPos) { + $this->semValue = new Expr\Match_($this->semStack[$stackPos-(7-3)], $this->semStack[$stackPos-(7-6)], $this->startAttributeStack[$stackPos-(7-1)] + $this->endAttributes); + }, + 250 => function ($stackPos) { + $this->semValue = []; + }, + 251 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 252 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 253 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 254 => function ($stackPos) { + $this->semValue = new Node\MatchArm($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 255 => function ($stackPos) { + $this->semValue = new Node\MatchArm(null, $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 256 => function ($stackPos) { + $this->semValue = is_array($this->semStack[$stackPos-(1-1)]) ? $this->semStack[$stackPos-(1-1)] : array($this->semStack[$stackPos-(1-1)]); + }, + 257 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-2)]; + }, + 258 => function ($stackPos) { + $this->semValue = array(); + }, + 259 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 260 => function ($stackPos) { + $this->semValue = new Stmt\ElseIf_($this->semStack[$stackPos-(5-3)], is_array($this->semStack[$stackPos-(5-5)]) ? $this->semStack[$stackPos-(5-5)] : array($this->semStack[$stackPos-(5-5)]), $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 261 => function ($stackPos) { + $this->semValue = array(); + }, + 262 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 263 => function ($stackPos) { + $this->semValue = new Stmt\ElseIf_($this->semStack[$stackPos-(6-3)], $this->semStack[$stackPos-(6-6)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); + }, + 264 => function ($stackPos) { + $this->semValue = null; + }, + 265 => function ($stackPos) { + $this->semValue = new Stmt\Else_(is_array($this->semStack[$stackPos-(2-2)]) ? $this->semStack[$stackPos-(2-2)] : array($this->semStack[$stackPos-(2-2)]), $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 266 => function ($stackPos) { + $this->semValue = null; + }, + 267 => function ($stackPos) { + $this->semValue = new Stmt\Else_($this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 268 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)], false); + }, + 269 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(2-2)], true); + }, + 270 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)], false); + }, + 271 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)], false); + }, + 272 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 273 => function ($stackPos) { + $this->semValue = array(); + }, + 274 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 275 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 276 => function ($stackPos) { + $this->semValue = 0; + }, + 277 => function ($stackPos) { + $this->checkModifier($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $stackPos-(2-2)); $this->semValue = $this->semStack[$stackPos-(2-1)] | $this->semStack[$stackPos-(2-2)]; + }, + 278 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_PUBLIC; + }, + 279 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_PROTECTED; + }, + 280 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_PRIVATE; + }, + 281 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_READONLY; + }, + 282 => function ($stackPos) { + $this->semValue = new Node\Param($this->semStack[$stackPos-(6-6)], null, $this->semStack[$stackPos-(6-3)], $this->semStack[$stackPos-(6-4)], $this->semStack[$stackPos-(6-5)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes, $this->semStack[$stackPos-(6-2)], $this->semStack[$stackPos-(6-1)]); + $this->checkParam($this->semValue); + }, + 283 => function ($stackPos) { + $this->semValue = new Node\Param($this->semStack[$stackPos-(8-6)], $this->semStack[$stackPos-(8-8)], $this->semStack[$stackPos-(8-3)], $this->semStack[$stackPos-(8-4)], $this->semStack[$stackPos-(8-5)], $this->startAttributeStack[$stackPos-(8-1)] + $this->endAttributes, $this->semStack[$stackPos-(8-2)], $this->semStack[$stackPos-(8-1)]); + $this->checkParam($this->semValue); + }, + 284 => function ($stackPos) { + $this->semValue = new Node\Param(new Expr\Error($this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes), null, $this->semStack[$stackPos-(6-3)], $this->semStack[$stackPos-(6-4)], $this->semStack[$stackPos-(6-5)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes, $this->semStack[$stackPos-(6-2)], $this->semStack[$stackPos-(6-1)]); + }, + 285 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 286 => function ($stackPos) { + $this->semValue = new Node\NullableType($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 287 => function ($stackPos) { + $this->semValue = new Node\UnionType($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 288 => function ($stackPos) { + $this->semValue = new Node\IntersectionType($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 289 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 290 => function ($stackPos) { + $this->semValue = new Node\Name('static', $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 291 => function ($stackPos) { + $this->semValue = $this->handleBuiltinTypes($this->semStack[$stackPos-(1-1)]); + }, + 292 => function ($stackPos) { + $this->semValue = new Node\Identifier('array', $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 293 => function ($stackPos) { + $this->semValue = new Node\Identifier('callable', $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 294 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)]); + }, + 295 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 296 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)]); + }, + 297 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 298 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)]); + }, + 299 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 300 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)]); + }, + 301 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 302 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 303 => function ($stackPos) { + $this->semValue = new Node\NullableType($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 304 => function ($stackPos) { + $this->semValue = new Node\UnionType($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 305 => function ($stackPos) { + $this->semValue = new Node\IntersectionType($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 306 => function ($stackPos) { + $this->semValue = null; + }, + 307 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 308 => function ($stackPos) { + $this->semValue = null; + }, + 309 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-2)]; + }, + 310 => function ($stackPos) { + $this->semValue = null; + }, + 311 => function ($stackPos) { + $this->semValue = array(); + }, + 312 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-2)]; + }, + 313 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(3-2)]); + }, + 314 => function ($stackPos) { + $this->semValue = new Node\VariadicPlaceholder($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 315 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 316 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 317 => function ($stackPos) { + $this->semValue = new Node\Arg($this->semStack[$stackPos-(1-1)], false, false, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 318 => function ($stackPos) { + $this->semValue = new Node\Arg($this->semStack[$stackPos-(2-2)], true, false, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 319 => function ($stackPos) { + $this->semValue = new Node\Arg($this->semStack[$stackPos-(2-2)], false, true, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 320 => function ($stackPos) { + $this->semValue = new Node\Arg($this->semStack[$stackPos-(3-3)], false, false, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes, $this->semStack[$stackPos-(3-1)]); + }, + 321 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 322 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 323 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 324 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 325 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 326 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 327 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 328 => function ($stackPos) { + $this->semValue = new Stmt\StaticVar($this->semStack[$stackPos-(1-1)], null, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 329 => function ($stackPos) { + $this->semValue = new Stmt\StaticVar($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 330 => function ($stackPos) { + if ($this->semStack[$stackPos-(2-2)] !== null) { $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; } + }, + 331 => function ($stackPos) { + $this->semValue = array(); + }, + 332 => function ($stackPos) { + $startAttributes = $this->lookaheadStartAttributes; if (isset($startAttributes['comments'])) { $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); } else { $nop = null; }; + if ($nop !== null) { $this->semStack[$stackPos-(1-1)][] = $nop; } $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 333 => function ($stackPos) { + $this->semValue = new Stmt\Property($this->semStack[$stackPos-(5-2)], $this->semStack[$stackPos-(5-4)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes, $this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-1)]); + $this->checkProperty($this->semValue, $stackPos-(5-2)); + }, + 334 => function ($stackPos) { + $this->semValue = new Stmt\ClassConst($this->semStack[$stackPos-(5-4)], $this->semStack[$stackPos-(5-2)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes, $this->semStack[$stackPos-(5-1)]); + $this->checkClassConst($this->semValue, $stackPos-(5-2)); + }, + 335 => function ($stackPos) { + $this->semValue = new Stmt\ClassMethod($this->semStack[$stackPos-(10-5)], ['type' => $this->semStack[$stackPos-(10-2)], 'byRef' => $this->semStack[$stackPos-(10-4)], 'params' => $this->semStack[$stackPos-(10-7)], 'returnType' => $this->semStack[$stackPos-(10-9)], 'stmts' => $this->semStack[$stackPos-(10-10)], 'attrGroups' => $this->semStack[$stackPos-(10-1)]], $this->startAttributeStack[$stackPos-(10-1)] + $this->endAttributes); + $this->checkClassMethod($this->semValue, $stackPos-(10-2)); + }, + 336 => function ($stackPos) { + $this->semValue = new Stmt\TraitUse($this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 337 => function ($stackPos) { + $this->semValue = new Stmt\EnumCase($this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-4)], $this->semStack[$stackPos-(5-1)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 338 => function ($stackPos) { + $this->semValue = null; /* will be skipped */ + }, + 339 => function ($stackPos) { + $this->semValue = array(); + }, + 340 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 341 => function ($stackPos) { + $this->semValue = array(); + }, + 342 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 343 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Precedence($this->semStack[$stackPos-(4-1)][0], $this->semStack[$stackPos-(4-1)][1], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 344 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos-(5-1)][0], $this->semStack[$stackPos-(5-1)][1], $this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-4)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 345 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos-(4-1)][0], $this->semStack[$stackPos-(4-1)][1], $this->semStack[$stackPos-(4-3)], null, $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 346 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos-(4-1)][0], $this->semStack[$stackPos-(4-1)][1], null, $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 347 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos-(4-1)][0], $this->semStack[$stackPos-(4-1)][1], null, $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 348 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)]); + }, + 349 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 350 => function ($stackPos) { + $this->semValue = array(null, $this->semStack[$stackPos-(1-1)]); + }, + 351 => function ($stackPos) { + $this->semValue = null; + }, + 352 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 353 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 354 => function ($stackPos) { + $this->semValue = 0; + }, + 355 => function ($stackPos) { + $this->semValue = 0; + }, + 356 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 357 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 358 => function ($stackPos) { + $this->checkModifier($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $stackPos-(2-2)); $this->semValue = $this->semStack[$stackPos-(2-1)] | $this->semStack[$stackPos-(2-2)]; + }, + 359 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_PUBLIC; + }, + 360 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_PROTECTED; + }, + 361 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_PRIVATE; + }, + 362 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_STATIC; + }, + 363 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_ABSTRACT; + }, + 364 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_FINAL; + }, + 365 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_READONLY; + }, + 366 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 367 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 368 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 369 => function ($stackPos) { + $this->semValue = new Node\VarLikeIdentifier(substr($this->semStack[$stackPos-(1-1)], 1), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 370 => function ($stackPos) { + $this->semValue = new Stmt\PropertyProperty($this->semStack[$stackPos-(1-1)], null, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 371 => function ($stackPos) { + $this->semValue = new Stmt\PropertyProperty($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 372 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 373 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 374 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 375 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 376 => function ($stackPos) { + $this->semValue = array(); + }, + 377 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 378 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 379 => function ($stackPos) { + $this->semValue = new Expr\Assign($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 380 => function ($stackPos) { + $this->semValue = new Expr\Assign($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 381 => function ($stackPos) { + $this->semValue = new Expr\Assign($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 382 => function ($stackPos) { + $this->semValue = new Expr\AssignRef($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 383 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 384 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 385 => function ($stackPos) { + $this->semValue = new Expr\Clone_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 386 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Plus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 387 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Minus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 388 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Mul($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 389 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Div($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 390 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Concat($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 391 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Mod($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 392 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\BitwiseAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 393 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\BitwiseOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 394 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\BitwiseXor($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 395 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\ShiftLeft($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 396 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\ShiftRight($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 397 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Pow($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 398 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Coalesce($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 399 => function ($stackPos) { + $this->semValue = new Expr\PostInc($this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 400 => function ($stackPos) { + $this->semValue = new Expr\PreInc($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 401 => function ($stackPos) { + $this->semValue = new Expr\PostDec($this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 402 => function ($stackPos) { + $this->semValue = new Expr\PreDec($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 403 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BooleanOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 404 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BooleanAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 405 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\LogicalOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 406 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\LogicalAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 407 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\LogicalXor($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 408 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 409 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 410 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 411 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseXor($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 412 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Concat($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 413 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Plus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 414 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Minus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 415 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Mul($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 416 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Div($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 417 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Mod($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 418 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\ShiftLeft($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 419 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\ShiftRight($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 420 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Pow($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 421 => function ($stackPos) { + $this->semValue = new Expr\UnaryPlus($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 422 => function ($stackPos) { + $this->semValue = new Expr\UnaryMinus($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 423 => function ($stackPos) { + $this->semValue = new Expr\BooleanNot($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 424 => function ($stackPos) { + $this->semValue = new Expr\BitwiseNot($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 425 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Identical($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 426 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\NotIdentical($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 427 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Equal($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 428 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\NotEqual($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 429 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Spaceship($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 430 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Smaller($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 431 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\SmallerOrEqual($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 432 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Greater($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 433 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\GreaterOrEqual($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 434 => function ($stackPos) { + $this->semValue = new Expr\Instanceof_($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 435 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 436 => function ($stackPos) { + $this->semValue = new Expr\Ternary($this->semStack[$stackPos-(5-1)], $this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-5)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 437 => function ($stackPos) { + $this->semValue = new Expr\Ternary($this->semStack[$stackPos-(4-1)], null, $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 438 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Coalesce($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 439 => function ($stackPos) { + $this->semValue = new Expr\Isset_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 440 => function ($stackPos) { + $this->semValue = new Expr\Empty_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 441 => function ($stackPos) { + $this->semValue = new Expr\Include_($this->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 442 => function ($stackPos) { + $this->semValue = new Expr\Include_($this->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE_ONCE, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 443 => function ($stackPos) { + $this->semValue = new Expr\Eval_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 444 => function ($stackPos) { + $this->semValue = new Expr\Include_($this->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 445 => function ($stackPos) { + $this->semValue = new Expr\Include_($this->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE_ONCE, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 446 => function ($stackPos) { + $this->semValue = new Expr\Cast\Int_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 447 => function ($stackPos) { + $attrs = $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes; + $attrs['kind'] = $this->getFloatCastKind($this->semStack[$stackPos-(2-1)]); + $this->semValue = new Expr\Cast\Double($this->semStack[$stackPos-(2-2)], $attrs); + }, + 448 => function ($stackPos) { + $this->semValue = new Expr\Cast\String_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 449 => function ($stackPos) { + $this->semValue = new Expr\Cast\Array_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 450 => function ($stackPos) { + $this->semValue = new Expr\Cast\Object_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 451 => function ($stackPos) { + $this->semValue = new Expr\Cast\Bool_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 452 => function ($stackPos) { + $this->semValue = new Expr\Cast\Unset_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 453 => function ($stackPos) { + $attrs = $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes; + $attrs['kind'] = strtolower($this->semStack[$stackPos-(2-1)]) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE; + $this->semValue = new Expr\Exit_($this->semStack[$stackPos-(2-2)], $attrs); + }, + 454 => function ($stackPos) { + $this->semValue = new Expr\ErrorSuppress($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 455 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 456 => function ($stackPos) { + $this->semValue = new Expr\ShellExec($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 457 => function ($stackPos) { + $this->semValue = new Expr\Print_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 458 => function ($stackPos) { + $this->semValue = new Expr\Yield_(null, null, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 459 => function ($stackPos) { + $this->semValue = new Expr\Yield_($this->semStack[$stackPos-(2-2)], null, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 460 => function ($stackPos) { + $this->semValue = new Expr\Yield_($this->semStack[$stackPos-(4-4)], $this->semStack[$stackPos-(4-2)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 461 => function ($stackPos) { + $this->semValue = new Expr\YieldFrom($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 462 => function ($stackPos) { + $this->semValue = new Expr\Throw_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 463 => function ($stackPos) { + $this->semValue = new Expr\ArrowFunction(['static' => false, 'byRef' => $this->semStack[$stackPos-(8-2)], 'params' => $this->semStack[$stackPos-(8-4)], 'returnType' => $this->semStack[$stackPos-(8-6)], 'expr' => $this->semStack[$stackPos-(8-8)], 'attrGroups' => []], $this->startAttributeStack[$stackPos-(8-1)] + $this->endAttributes); + }, + 464 => function ($stackPos) { + $this->semValue = new Expr\ArrowFunction(['static' => true, 'byRef' => $this->semStack[$stackPos-(9-3)], 'params' => $this->semStack[$stackPos-(9-5)], 'returnType' => $this->semStack[$stackPos-(9-7)], 'expr' => $this->semStack[$stackPos-(9-9)], 'attrGroups' => []], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes); + }, + 465 => function ($stackPos) { + $this->semValue = new Expr\Closure(['static' => false, 'byRef' => $this->semStack[$stackPos-(8-2)], 'params' => $this->semStack[$stackPos-(8-4)], 'uses' => $this->semStack[$stackPos-(8-6)], 'returnType' => $this->semStack[$stackPos-(8-7)], 'stmts' => $this->semStack[$stackPos-(8-8)], 'attrGroups' => []], $this->startAttributeStack[$stackPos-(8-1)] + $this->endAttributes); + }, + 466 => function ($stackPos) { + $this->semValue = new Expr\Closure(['static' => true, 'byRef' => $this->semStack[$stackPos-(9-3)], 'params' => $this->semStack[$stackPos-(9-5)], 'uses' => $this->semStack[$stackPos-(9-7)], 'returnType' => $this->semStack[$stackPos-(9-8)], 'stmts' => $this->semStack[$stackPos-(9-9)], 'attrGroups' => []], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes); + }, + 467 => function ($stackPos) { + $this->semValue = new Expr\ArrowFunction(['static' => false, 'byRef' => $this->semStack[$stackPos-(9-3)], 'params' => $this->semStack[$stackPos-(9-5)], 'returnType' => $this->semStack[$stackPos-(9-7)], 'expr' => $this->semStack[$stackPos-(9-9)], 'attrGroups' => $this->semStack[$stackPos-(9-1)]], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes); + }, + 468 => function ($stackPos) { + $this->semValue = new Expr\ArrowFunction(['static' => true, 'byRef' => $this->semStack[$stackPos-(10-4)], 'params' => $this->semStack[$stackPos-(10-6)], 'returnType' => $this->semStack[$stackPos-(10-8)], 'expr' => $this->semStack[$stackPos-(10-10)], 'attrGroups' => $this->semStack[$stackPos-(10-1)]], $this->startAttributeStack[$stackPos-(10-1)] + $this->endAttributes); + }, + 469 => function ($stackPos) { + $this->semValue = new Expr\Closure(['static' => false, 'byRef' => $this->semStack[$stackPos-(9-3)], 'params' => $this->semStack[$stackPos-(9-5)], 'uses' => $this->semStack[$stackPos-(9-7)], 'returnType' => $this->semStack[$stackPos-(9-8)], 'stmts' => $this->semStack[$stackPos-(9-9)], 'attrGroups' => $this->semStack[$stackPos-(9-1)]], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes); + }, + 470 => function ($stackPos) { + $this->semValue = new Expr\Closure(['static' => true, 'byRef' => $this->semStack[$stackPos-(10-4)], 'params' => $this->semStack[$stackPos-(10-6)], 'uses' => $this->semStack[$stackPos-(10-8)], 'returnType' => $this->semStack[$stackPos-(10-9)], 'stmts' => $this->semStack[$stackPos-(10-10)], 'attrGroups' => $this->semStack[$stackPos-(10-1)]], $this->startAttributeStack[$stackPos-(10-1)] + $this->endAttributes); + }, + 471 => function ($stackPos) { + $this->semValue = array(new Stmt\Class_(null, ['type' => 0, 'extends' => $this->semStack[$stackPos-(8-4)], 'implements' => $this->semStack[$stackPos-(8-5)], 'stmts' => $this->semStack[$stackPos-(8-7)], 'attrGroups' => $this->semStack[$stackPos-(8-1)]], $this->startAttributeStack[$stackPos-(8-1)] + $this->endAttributes), $this->semStack[$stackPos-(8-3)]); + $this->checkClass($this->semValue[0], -1); + }, + 472 => function ($stackPos) { + $this->semValue = new Expr\New_($this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 473 => function ($stackPos) { + list($class, $ctorArgs) = $this->semStack[$stackPos-(2-2)]; $this->semValue = new Expr\New_($class, $ctorArgs, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 474 => function ($stackPos) { + $this->semValue = array(); + }, + 475 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-3)]; + }, + 476 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 477 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 478 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 479 => function ($stackPos) { + $this->semValue = new Expr\ClosureUse($this->semStack[$stackPos-(2-2)], $this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 480 => function ($stackPos) { + $this->semValue = new Expr\FuncCall($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 481 => function ($stackPos) { + $this->semValue = new Expr\FuncCall($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 482 => function ($stackPos) { + $this->semValue = new Expr\StaticCall($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 483 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 484 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 485 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 486 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 487 => function ($stackPos) { + $this->semValue = new Name\FullyQualified(substr($this->semStack[$stackPos-(1-1)], 1), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 488 => function ($stackPos) { + $this->semValue = new Name\Relative(substr($this->semStack[$stackPos-(1-1)], 10), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 489 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 490 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 491 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 492 => function ($stackPos) { + $this->semValue = new Expr\Error($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); $this->errorState = 2; + }, + 493 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 494 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 495 => function ($stackPos) { + $this->semValue = null; + }, + 496 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 497 => function ($stackPos) { + $this->semValue = array(); + }, + 498 => function ($stackPos) { + $this->semValue = array(new Scalar\EncapsedStringPart(Scalar\String_::parseEscapeSequences($this->semStack[$stackPos-(1-1)], '`'), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes)); + }, + 499 => function ($stackPos) { + foreach ($this->semStack[$stackPos-(1-1)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '`', true); } }; $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 500 => function ($stackPos) { + $this->semValue = array(); + }, + 501 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 502 => function ($stackPos) { + $this->semValue = new Expr\ConstFetch($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 503 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Line($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 504 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\File($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 505 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Dir($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 506 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Class_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 507 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Trait_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 508 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Method($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 509 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Function_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 510 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Namespace_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 511 => function ($stackPos) { + $this->semValue = new Expr\ClassConstFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 512 => function ($stackPos) { + $this->semValue = new Expr\ClassConstFetch($this->semStack[$stackPos-(3-1)], new Expr\Error($this->startAttributeStack[$stackPos-(3-3)] + $this->endAttributeStack[$stackPos-(3-3)]), $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); $this->errorState = 2; + }, + 513 => function ($stackPos) { + $attrs = $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = Expr\Array_::KIND_SHORT; + $this->semValue = new Expr\Array_($this->semStack[$stackPos-(3-2)], $attrs); + }, + 514 => function ($stackPos) { + $attrs = $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes; $attrs['kind'] = Expr\Array_::KIND_LONG; + $this->semValue = new Expr\Array_($this->semStack[$stackPos-(4-3)], $attrs); + }, + 515 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 516 => function ($stackPos) { + $this->semValue = Scalar\String_::fromString($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 517 => function ($stackPos) { + $attrs = $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED; + foreach ($this->semStack[$stackPos-(3-2)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '"', true); } }; $this->semValue = new Scalar\Encapsed($this->semStack[$stackPos-(3-2)], $attrs); + }, + 518 => function ($stackPos) { + $this->semValue = $this->parseLNumber($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 519 => function ($stackPos) { + $this->semValue = Scalar\DNumber::fromString($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 520 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 521 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 522 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 523 => function ($stackPos) { + $this->semValue = $this->parseDocString($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(3-3)] + $this->endAttributeStack[$stackPos-(3-3)], true); + }, + 524 => function ($stackPos) { + $this->semValue = $this->parseDocString($this->semStack[$stackPos-(2-1)], '', $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(2-2)] + $this->endAttributeStack[$stackPos-(2-2)], true); + }, + 525 => function ($stackPos) { + $this->semValue = $this->parseDocString($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(3-3)] + $this->endAttributeStack[$stackPos-(3-3)], true); + }, + 526 => function ($stackPos) { + $this->semValue = null; + }, + 527 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 528 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 529 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 530 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 531 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 532 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 533 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 534 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 535 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 536 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 537 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 538 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 539 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 540 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 541 => function ($stackPos) { + $this->semValue = new Expr\MethodCall($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 542 => function ($stackPos) { + $this->semValue = new Expr\NullsafeMethodCall($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 543 => function ($stackPos) { + $this->semValue = null; + }, + 544 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 545 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 546 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 547 => function ($stackPos) { + $this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 548 => function ($stackPos) { + $this->semValue = new Expr\NullsafePropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 549 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 550 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 551 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 552 => function ($stackPos) { + $this->semValue = new Expr\Variable(new Expr\Error($this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes), $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); $this->errorState = 2; + }, + 553 => function ($stackPos) { + $var = $this->semStack[$stackPos-(1-1)]->name; $this->semValue = \is_string($var) ? new Node\VarLikeIdentifier($var, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes) : $var; + }, + 554 => function ($stackPos) { + $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 555 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 556 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 557 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 558 => function ($stackPos) { + $this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 559 => function ($stackPos) { + $this->semValue = new Expr\NullsafePropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 560 => function ($stackPos) { + $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 561 => function ($stackPos) { + $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 562 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 563 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 564 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 565 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 566 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 567 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 568 => function ($stackPos) { + $this->semValue = new Expr\Error($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); $this->errorState = 2; + }, + 569 => function ($stackPos) { + $this->semValue = new Expr\List_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 570 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; $end = count($this->semValue)-1; if ($this->semValue[$end] === null) array_pop($this->semValue); + }, + 571 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 572 => function ($stackPos) { + /* do nothing -- prevent default action of $$=$this->semStack[$1]. See $551. */ + }, + 573 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 574 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 575 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(1-1)], null, false, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 576 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(2-2)], null, true, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 577 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(1-1)], null, false, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 578 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(3-3)], $this->semStack[$stackPos-(3-1)], false, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 579 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(4-4)], $this->semStack[$stackPos-(4-1)], true, $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 580 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(3-3)], $this->semStack[$stackPos-(3-1)], false, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 581 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(2-2)], null, false, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes, true, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 582 => function ($stackPos) { + $this->semValue = null; + }, + 583 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 584 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 585 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 586 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)]); + }, + 587 => function ($stackPos) { + $this->semValue = new Scalar\EncapsedStringPart($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 588 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 589 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 590 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 591 => function ($stackPos) { + $this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 592 => function ($stackPos) { + $this->semValue = new Expr\NullsafePropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 593 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 594 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 595 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(6-2)], $this->semStack[$stackPos-(6-4)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); + }, + 596 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 597 => function ($stackPos) { + $this->semValue = new Scalar\String_($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 598 => function ($stackPos) { + $this->semValue = $this->parseNumString($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 599 => function ($stackPos) { + $this->semValue = $this->parseNumString('-' . $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 600 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + ]; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Parser/Tokens.php b/vendor/nikic/php-parser/lib/PhpParser/Parser/Tokens.php new file mode 100644 index 0000000000..b76a5d94c8 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Parser/Tokens.php @@ -0,0 +1,148 @@ +lexer = $lexer; + + if (isset($options['throwOnError'])) { + throw new \LogicException( + '"throwOnError" is no longer supported, use "errorHandler" instead'); + } + + $this->initReduceCallbacks(); + } + + /** + * Parses PHP code into a node tree. + * + * If a non-throwing error handler is used, the parser will continue parsing after an error + * occurred and attempt to build a partial AST. + * + * @param string $code The source code to parse + * @param ErrorHandler|null $errorHandler Error handler to use for lexer/parser errors, defaults + * to ErrorHandler\Throwing. + * + * @return Node\Stmt[]|null Array of statements (or null non-throwing error handler is used and + * the parser was unable to recover from an error). + */ + public function parse(string $code, ErrorHandler $errorHandler = null) { + $this->errorHandler = $errorHandler ?: new ErrorHandler\Throwing; + + $this->lexer->startLexing($code, $this->errorHandler); + $result = $this->doParse(); + + // Clear out some of the interior state, so we don't hold onto unnecessary + // memory between uses of the parser + $this->startAttributeStack = []; + $this->endAttributeStack = []; + $this->semStack = []; + $this->semValue = null; + + return $result; + } + + protected function doParse() { + // We start off with no lookahead-token + $symbol = self::SYMBOL_NONE; + + // The attributes for a node are taken from the first and last token of the node. + // From the first token only the startAttributes are taken and from the last only + // the endAttributes. Both are merged using the array union operator (+). + $startAttributes = []; + $endAttributes = []; + $this->endAttributes = $endAttributes; + + // Keep stack of start and end attributes + $this->startAttributeStack = []; + $this->endAttributeStack = [$endAttributes]; + + // Start off in the initial state and keep a stack of previous states + $state = 0; + $stateStack = [$state]; + + // Semantic value stack (contains values of tokens and semantic action results) + $this->semStack = []; + + // Current position in the stack(s) + $stackPos = 0; + + $this->errorState = 0; + + for (;;) { + //$this->traceNewState($state, $symbol); + + if ($this->actionBase[$state] === 0) { + $rule = $this->actionDefault[$state]; + } else { + if ($symbol === self::SYMBOL_NONE) { + // Fetch the next token id from the lexer and fetch additional info by-ref. + // The end attributes are fetched into a temporary variable and only set once the token is really + // shifted (not during read). Otherwise you would sometimes get off-by-one errors, when a rule is + // reduced after a token was read but not yet shifted. + $tokenId = $this->lexer->getNextToken($tokenValue, $startAttributes, $endAttributes); + + // map the lexer token id to the internally used symbols + $symbol = $tokenId >= 0 && $tokenId < $this->tokenToSymbolMapSize + ? $this->tokenToSymbol[$tokenId] + : $this->invalidSymbol; + + if ($symbol === $this->invalidSymbol) { + throw new \RangeException(sprintf( + 'The lexer returned an invalid token (id=%d, value=%s)', + $tokenId, $tokenValue + )); + } + + // Allow productions to access the start attributes of the lookahead token. + $this->lookaheadStartAttributes = $startAttributes; + + //$this->traceRead($symbol); + } + + $idx = $this->actionBase[$state] + $symbol; + if ((($idx >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol) + || ($state < $this->YY2TBLSTATE + && ($idx = $this->actionBase[$state + $this->numNonLeafStates] + $symbol) >= 0 + && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol)) + && ($action = $this->action[$idx]) !== $this->defaultAction) { + /* + * >= numNonLeafStates: shift and reduce + * > 0: shift + * = 0: accept + * < 0: reduce + * = -YYUNEXPECTED: error + */ + if ($action > 0) { + /* shift */ + //$this->traceShift($symbol); + + ++$stackPos; + $stateStack[$stackPos] = $state = $action; + $this->semStack[$stackPos] = $tokenValue; + $this->startAttributeStack[$stackPos] = $startAttributes; + $this->endAttributeStack[$stackPos] = $endAttributes; + $this->endAttributes = $endAttributes; + $symbol = self::SYMBOL_NONE; + + if ($this->errorState) { + --$this->errorState; + } + + if ($action < $this->numNonLeafStates) { + continue; + } + + /* $yyn >= numNonLeafStates means shift-and-reduce */ + $rule = $action - $this->numNonLeafStates; + } else { + $rule = -$action; + } + } else { + $rule = $this->actionDefault[$state]; + } + } + + for (;;) { + if ($rule === 0) { + /* accept */ + //$this->traceAccept(); + return $this->semValue; + } elseif ($rule !== $this->unexpectedTokenRule) { + /* reduce */ + //$this->traceReduce($rule); + + try { + $this->reduceCallbacks[$rule]($stackPos); + } catch (Error $e) { + if (-1 === $e->getStartLine() && isset($startAttributes['startLine'])) { + $e->setStartLine($startAttributes['startLine']); + } + + $this->emitError($e); + // Can't recover from this type of error + return null; + } + + /* Goto - shift nonterminal */ + $lastEndAttributes = $this->endAttributeStack[$stackPos]; + $ruleLength = $this->ruleToLength[$rule]; + $stackPos -= $ruleLength; + $nonTerminal = $this->ruleToNonTerminal[$rule]; + $idx = $this->gotoBase[$nonTerminal] + $stateStack[$stackPos]; + if ($idx >= 0 && $idx < $this->gotoTableSize && $this->gotoCheck[$idx] === $nonTerminal) { + $state = $this->goto[$idx]; + } else { + $state = $this->gotoDefault[$nonTerminal]; + } + + ++$stackPos; + $stateStack[$stackPos] = $state; + $this->semStack[$stackPos] = $this->semValue; + $this->endAttributeStack[$stackPos] = $lastEndAttributes; + if ($ruleLength === 0) { + // Empty productions use the start attributes of the lookahead token. + $this->startAttributeStack[$stackPos] = $this->lookaheadStartAttributes; + } + } else { + /* error */ + switch ($this->errorState) { + case 0: + $msg = $this->getErrorMessage($symbol, $state); + $this->emitError(new Error($msg, $startAttributes + $endAttributes)); + // Break missing intentionally + case 1: + case 2: + $this->errorState = 3; + + // Pop until error-expecting state uncovered + while (!( + (($idx = $this->actionBase[$state] + $this->errorSymbol) >= 0 + && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $this->errorSymbol) + || ($state < $this->YY2TBLSTATE + && ($idx = $this->actionBase[$state + $this->numNonLeafStates] + $this->errorSymbol) >= 0 + && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $this->errorSymbol) + ) || ($action = $this->action[$idx]) === $this->defaultAction) { // Not totally sure about this + if ($stackPos <= 0) { + // Could not recover from error + return null; + } + $state = $stateStack[--$stackPos]; + //$this->tracePop($state); + } + + //$this->traceShift($this->errorSymbol); + ++$stackPos; + $stateStack[$stackPos] = $state = $action; + + // We treat the error symbol as being empty, so we reset the end attributes + // to the end attributes of the last non-error symbol + $this->startAttributeStack[$stackPos] = $this->lookaheadStartAttributes; + $this->endAttributeStack[$stackPos] = $this->endAttributeStack[$stackPos - 1]; + $this->endAttributes = $this->endAttributeStack[$stackPos - 1]; + break; + + case 3: + if ($symbol === 0) { + // Reached EOF without recovering from error + return null; + } + + //$this->traceDiscard($symbol); + $symbol = self::SYMBOL_NONE; + break 2; + } + } + + if ($state < $this->numNonLeafStates) { + break; + } + + /* >= numNonLeafStates means shift-and-reduce */ + $rule = $state - $this->numNonLeafStates; + } + } + + throw new \RuntimeException('Reached end of parser loop'); + } + + protected function emitError(Error $error) { + $this->errorHandler->handleError($error); + } + + /** + * Format error message including expected tokens. + * + * @param int $symbol Unexpected symbol + * @param int $state State at time of error + * + * @return string Formatted error message + */ + protected function getErrorMessage(int $symbol, int $state) : string { + $expectedString = ''; + if ($expected = $this->getExpectedTokens($state)) { + $expectedString = ', expecting ' . implode(' or ', $expected); + } + + return 'Syntax error, unexpected ' . $this->symbolToName[$symbol] . $expectedString; + } + + /** + * Get limited number of expected tokens in given state. + * + * @param int $state State + * + * @return string[] Expected tokens. If too many, an empty array is returned. + */ + protected function getExpectedTokens(int $state) : array { + $expected = []; + + $base = $this->actionBase[$state]; + foreach ($this->symbolToName as $symbol => $name) { + $idx = $base + $symbol; + if ($idx >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol + || $state < $this->YY2TBLSTATE + && ($idx = $this->actionBase[$state + $this->numNonLeafStates] + $symbol) >= 0 + && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol + ) { + if ($this->action[$idx] !== $this->unexpectedTokenRule + && $this->action[$idx] !== $this->defaultAction + && $symbol !== $this->errorSymbol + ) { + if (count($expected) === 4) { + /* Too many expected tokens */ + return []; + } + + $expected[] = $name; + } + } + } + + return $expected; + } + + /* + * Tracing functions used for debugging the parser. + */ + + /* + protected function traceNewState($state, $symbol) { + echo '% State ' . $state + . ', Lookahead ' . ($symbol == self::SYMBOL_NONE ? '--none--' : $this->symbolToName[$symbol]) . "\n"; + } + + protected function traceRead($symbol) { + echo '% Reading ' . $this->symbolToName[$symbol] . "\n"; + } + + protected function traceShift($symbol) { + echo '% Shift ' . $this->symbolToName[$symbol] . "\n"; + } + + protected function traceAccept() { + echo "% Accepted.\n"; + } + + protected function traceReduce($n) { + echo '% Reduce by (' . $n . ') ' . $this->productions[$n] . "\n"; + } + + protected function tracePop($state) { + echo '% Recovering, uncovered state ' . $state . "\n"; + } + + protected function traceDiscard($symbol) { + echo '% Discard ' . $this->symbolToName[$symbol] . "\n"; + } + */ + + /* + * Helper functions invoked by semantic actions + */ + + /** + * Moves statements of semicolon-style namespaces into $ns->stmts and checks various error conditions. + * + * @param Node\Stmt[] $stmts + * @return Node\Stmt[] + */ + protected function handleNamespaces(array $stmts) : array { + $hasErrored = false; + $style = $this->getNamespacingStyle($stmts); + if (null === $style) { + // not namespaced, nothing to do + return $stmts; + } elseif ('brace' === $style) { + // For braced namespaces we only have to check that there are no invalid statements between the namespaces + $afterFirstNamespace = false; + foreach ($stmts as $stmt) { + if ($stmt instanceof Node\Stmt\Namespace_) { + $afterFirstNamespace = true; + } elseif (!$stmt instanceof Node\Stmt\HaltCompiler + && !$stmt instanceof Node\Stmt\Nop + && $afterFirstNamespace && !$hasErrored) { + $this->emitError(new Error( + 'No code may exist outside of namespace {}', $stmt->getAttributes())); + $hasErrored = true; // Avoid one error for every statement + } + } + return $stmts; + } else { + // For semicolon namespaces we have to move the statements after a namespace declaration into ->stmts + $resultStmts = []; + $targetStmts =& $resultStmts; + $lastNs = null; + foreach ($stmts as $stmt) { + if ($stmt instanceof Node\Stmt\Namespace_) { + if ($lastNs !== null) { + $this->fixupNamespaceAttributes($lastNs); + } + if ($stmt->stmts === null) { + $stmt->stmts = []; + $targetStmts =& $stmt->stmts; + $resultStmts[] = $stmt; + } else { + // This handles the invalid case of mixed style namespaces + $resultStmts[] = $stmt; + $targetStmts =& $resultStmts; + } + $lastNs = $stmt; + } elseif ($stmt instanceof Node\Stmt\HaltCompiler) { + // __halt_compiler() is not moved into the namespace + $resultStmts[] = $stmt; + } else { + $targetStmts[] = $stmt; + } + } + if ($lastNs !== null) { + $this->fixupNamespaceAttributes($lastNs); + } + return $resultStmts; + } + } + + private function fixupNamespaceAttributes(Node\Stmt\Namespace_ $stmt) { + // We moved the statements into the namespace node, as such the end of the namespace node + // needs to be extended to the end of the statements. + if (empty($stmt->stmts)) { + return; + } + + // We only move the builtin end attributes here. This is the best we can do with the + // knowledge we have. + $endAttributes = ['endLine', 'endFilePos', 'endTokenPos']; + $lastStmt = $stmt->stmts[count($stmt->stmts) - 1]; + foreach ($endAttributes as $endAttribute) { + if ($lastStmt->hasAttribute($endAttribute)) { + $stmt->setAttribute($endAttribute, $lastStmt->getAttribute($endAttribute)); + } + } + } + + /** + * Determine namespacing style (semicolon or brace) + * + * @param Node[] $stmts Top-level statements. + * + * @return null|string One of "semicolon", "brace" or null (no namespaces) + */ + private function getNamespacingStyle(array $stmts) { + $style = null; + $hasNotAllowedStmts = false; + foreach ($stmts as $i => $stmt) { + if ($stmt instanceof Node\Stmt\Namespace_) { + $currentStyle = null === $stmt->stmts ? 'semicolon' : 'brace'; + if (null === $style) { + $style = $currentStyle; + if ($hasNotAllowedStmts) { + $this->emitError(new Error( + 'Namespace declaration statement has to be the very first statement in the script', + $stmt->getLine() // Avoid marking the entire namespace as an error + )); + } + } elseif ($style !== $currentStyle) { + $this->emitError(new Error( + 'Cannot mix bracketed namespace declarations with unbracketed namespace declarations', + $stmt->getLine() // Avoid marking the entire namespace as an error + )); + // Treat like semicolon style for namespace normalization + return 'semicolon'; + } + continue; + } + + /* declare(), __halt_compiler() and nops can be used before a namespace declaration */ + if ($stmt instanceof Node\Stmt\Declare_ + || $stmt instanceof Node\Stmt\HaltCompiler + || $stmt instanceof Node\Stmt\Nop) { + continue; + } + + /* There may be a hashbang line at the very start of the file */ + if ($i === 0 && $stmt instanceof Node\Stmt\InlineHTML && preg_match('/\A#!.*\r?\n\z/', $stmt->value)) { + continue; + } + + /* Everything else if forbidden before namespace declarations */ + $hasNotAllowedStmts = true; + } + return $style; + } + + /** + * Fix up parsing of static property calls in PHP 5. + * + * In PHP 5 A::$b[c][d] and A::$b[c][d]() have very different interpretation. The former is + * interpreted as (A::$b)[c][d], while the latter is the same as A::{$b[c][d]}(). We parse the + * latter as the former initially and this method fixes the AST into the correct form when we + * encounter the "()". + * + * @param Node\Expr\StaticPropertyFetch|Node\Expr\ArrayDimFetch $prop + * @param Node\Arg[] $args + * @param array $attributes + * + * @return Expr\StaticCall + */ + protected function fixupPhp5StaticPropCall($prop, array $args, array $attributes) : Expr\StaticCall { + if ($prop instanceof Node\Expr\StaticPropertyFetch) { + $name = $prop->name instanceof VarLikeIdentifier + ? $prop->name->toString() : $prop->name; + $var = new Expr\Variable($name, $prop->name->getAttributes()); + return new Expr\StaticCall($prop->class, $var, $args, $attributes); + } elseif ($prop instanceof Node\Expr\ArrayDimFetch) { + $tmp = $prop; + while ($tmp->var instanceof Node\Expr\ArrayDimFetch) { + $tmp = $tmp->var; + } + + /** @var Expr\StaticPropertyFetch $staticProp */ + $staticProp = $tmp->var; + + // Set start attributes to attributes of innermost node + $tmp = $prop; + $this->fixupStartAttributes($tmp, $staticProp->name); + while ($tmp->var instanceof Node\Expr\ArrayDimFetch) { + $tmp = $tmp->var; + $this->fixupStartAttributes($tmp, $staticProp->name); + } + + $name = $staticProp->name instanceof VarLikeIdentifier + ? $staticProp->name->toString() : $staticProp->name; + $tmp->var = new Expr\Variable($name, $staticProp->name->getAttributes()); + return new Expr\StaticCall($staticProp->class, $prop, $args, $attributes); + } else { + throw new \Exception; + } + } + + protected function fixupStartAttributes(Node $to, Node $from) { + $startAttributes = ['startLine', 'startFilePos', 'startTokenPos']; + foreach ($startAttributes as $startAttribute) { + if ($from->hasAttribute($startAttribute)) { + $to->setAttribute($startAttribute, $from->getAttribute($startAttribute)); + } + } + } + + protected function handleBuiltinTypes(Name $name) { + $builtinTypes = [ + 'bool' => true, + 'int' => true, + 'float' => true, + 'string' => true, + 'iterable' => true, + 'void' => true, + 'object' => true, + 'null' => true, + 'false' => true, + 'mixed' => true, + 'never' => true, + ]; + + if (!$name->isUnqualified()) { + return $name; + } + + $lowerName = $name->toLowerString(); + if (!isset($builtinTypes[$lowerName])) { + return $name; + } + + return new Node\Identifier($lowerName, $name->getAttributes()); + } + + /** + * Get combined start and end attributes at a stack location + * + * @param int $pos Stack location + * + * @return array Combined start and end attributes + */ + protected function getAttributesAt(int $pos) : array { + return $this->startAttributeStack[$pos] + $this->endAttributeStack[$pos]; + } + + protected function getFloatCastKind(string $cast): int + { + $cast = strtolower($cast); + if (strpos($cast, 'float') !== false) { + return Double::KIND_FLOAT; + } + + if (strpos($cast, 'real') !== false) { + return Double::KIND_REAL; + } + + return Double::KIND_DOUBLE; + } + + protected function parseLNumber($str, $attributes, $allowInvalidOctal = false) { + try { + return LNumber::fromString($str, $attributes, $allowInvalidOctal); + } catch (Error $error) { + $this->emitError($error); + // Use dummy value + return new LNumber(0, $attributes); + } + } + + /** + * Parse a T_NUM_STRING token into either an integer or string node. + * + * @param string $str Number string + * @param array $attributes Attributes + * + * @return LNumber|String_ Integer or string node. + */ + protected function parseNumString(string $str, array $attributes) { + if (!preg_match('/^(?:0|-?[1-9][0-9]*)$/', $str)) { + return new String_($str, $attributes); + } + + $num = +$str; + if (!is_int($num)) { + return new String_($str, $attributes); + } + + return new LNumber($num, $attributes); + } + + protected function stripIndentation( + string $string, int $indentLen, string $indentChar, + bool $newlineAtStart, bool $newlineAtEnd, array $attributes + ) { + if ($indentLen === 0) { + return $string; + } + + $start = $newlineAtStart ? '(?:(?<=\n)|\A)' : '(?<=\n)'; + $end = $newlineAtEnd ? '(?:(?=[\r\n])|\z)' : '(?=[\r\n])'; + $regex = '/' . $start . '([ \t]*)(' . $end . ')?/'; + return preg_replace_callback( + $regex, + function ($matches) use ($indentLen, $indentChar, $attributes) { + $prefix = substr($matches[1], 0, $indentLen); + if (false !== strpos($prefix, $indentChar === " " ? "\t" : " ")) { + $this->emitError(new Error( + 'Invalid indentation - tabs and spaces cannot be mixed', $attributes + )); + } elseif (strlen($prefix) < $indentLen && !isset($matches[2])) { + $this->emitError(new Error( + 'Invalid body indentation level ' . + '(expecting an indentation level of at least ' . $indentLen . ')', + $attributes + )); + } + return substr($matches[0], strlen($prefix)); + }, + $string + ); + } + + protected function parseDocString( + string $startToken, $contents, string $endToken, + array $attributes, array $endTokenAttributes, bool $parseUnicodeEscape + ) { + $kind = strpos($startToken, "'") === false + ? String_::KIND_HEREDOC : String_::KIND_NOWDOC; + + $regex = '/\A[bB]?<<<[ \t]*[\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\'"]?(?:\r\n|\n|\r)\z/'; + $result = preg_match($regex, $startToken, $matches); + assert($result === 1); + $label = $matches[1]; + + $result = preg_match('/\A[ \t]*/', $endToken, $matches); + assert($result === 1); + $indentation = $matches[0]; + + $attributes['kind'] = $kind; + $attributes['docLabel'] = $label; + $attributes['docIndentation'] = $indentation; + + $indentHasSpaces = false !== strpos($indentation, " "); + $indentHasTabs = false !== strpos($indentation, "\t"); + if ($indentHasSpaces && $indentHasTabs) { + $this->emitError(new Error( + 'Invalid indentation - tabs and spaces cannot be mixed', + $endTokenAttributes + )); + + // Proceed processing as if this doc string is not indented + $indentation = ''; + } + + $indentLen = \strlen($indentation); + $indentChar = $indentHasSpaces ? " " : "\t"; + + if (\is_string($contents)) { + if ($contents === '') { + return new String_('', $attributes); + } + + $contents = $this->stripIndentation( + $contents, $indentLen, $indentChar, true, true, $attributes + ); + $contents = preg_replace('~(\r\n|\n|\r)\z~', '', $contents); + + if ($kind === String_::KIND_HEREDOC) { + $contents = String_::parseEscapeSequences($contents, null, $parseUnicodeEscape); + } + + return new String_($contents, $attributes); + } else { + assert(count($contents) > 0); + if (!$contents[0] instanceof Node\Scalar\EncapsedStringPart) { + // If there is no leading encapsed string part, pretend there is an empty one + $this->stripIndentation( + '', $indentLen, $indentChar, true, false, $contents[0]->getAttributes() + ); + } + + $newContents = []; + foreach ($contents as $i => $part) { + if ($part instanceof Node\Scalar\EncapsedStringPart) { + $isLast = $i === \count($contents) - 1; + $part->value = $this->stripIndentation( + $part->value, $indentLen, $indentChar, + $i === 0, $isLast, $part->getAttributes() + ); + $part->value = String_::parseEscapeSequences($part->value, null, $parseUnicodeEscape); + if ($isLast) { + $part->value = preg_replace('~(\r\n|\n|\r)\z~', '', $part->value); + } + if ('' === $part->value) { + continue; + } + } + $newContents[] = $part; + } + return new Encapsed($newContents, $attributes); + } + } + + /** + * Create attributes for a zero-length common-capturing nop. + * + * @param Comment[] $comments + * @return array + */ + protected function createCommentNopAttributes(array $comments) { + $comment = $comments[count($comments) - 1]; + $commentEndLine = $comment->getEndLine(); + $commentEndFilePos = $comment->getEndFilePos(); + $commentEndTokenPos = $comment->getEndTokenPos(); + + $attributes = ['comments' => $comments]; + if (-1 !== $commentEndLine) { + $attributes['startLine'] = $commentEndLine; + $attributes['endLine'] = $commentEndLine; + } + if (-1 !== $commentEndFilePos) { + $attributes['startFilePos'] = $commentEndFilePos + 1; + $attributes['endFilePos'] = $commentEndFilePos; + } + if (-1 !== $commentEndTokenPos) { + $attributes['startTokenPos'] = $commentEndTokenPos + 1; + $attributes['endTokenPos'] = $commentEndTokenPos; + } + return $attributes; + } + + protected function checkClassModifier($a, $b, $modifierPos) { + try { + Class_::verifyClassModifier($a, $b); + } catch (Error $error) { + $error->setAttributes($this->getAttributesAt($modifierPos)); + $this->emitError($error); + } + } + + protected function checkModifier($a, $b, $modifierPos) { + // Jumping through some hoops here because verifyModifier() is also used elsewhere + try { + Class_::verifyModifier($a, $b); + } catch (Error $error) { + $error->setAttributes($this->getAttributesAt($modifierPos)); + $this->emitError($error); + } + } + + protected function checkParam(Param $node) { + if ($node->variadic && null !== $node->default) { + $this->emitError(new Error( + 'Variadic parameter cannot have a default value', + $node->default->getAttributes() + )); + } + } + + protected function checkTryCatch(TryCatch $node) { + if (empty($node->catches) && null === $node->finally) { + $this->emitError(new Error( + 'Cannot use try without catch or finally', $node->getAttributes() + )); + } + } + + protected function checkNamespace(Namespace_ $node) { + if (null !== $node->stmts) { + foreach ($node->stmts as $stmt) { + if ($stmt instanceof Namespace_) { + $this->emitError(new Error( + 'Namespace declarations cannot be nested', $stmt->getAttributes() + )); + } + } + } + } + + private function checkClassName($name, $namePos) { + if (null !== $name && $name->isSpecialClassName()) { + $this->emitError(new Error( + sprintf('Cannot use \'%s\' as class name as it is reserved', $name), + $this->getAttributesAt($namePos) + )); + } + } + + private function checkImplementedInterfaces(array $interfaces) { + foreach ($interfaces as $interface) { + if ($interface->isSpecialClassName()) { + $this->emitError(new Error( + sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface), + $interface->getAttributes() + )); + } + } + } + + protected function checkClass(Class_ $node, $namePos) { + $this->checkClassName($node->name, $namePos); + + if ($node->extends && $node->extends->isSpecialClassName()) { + $this->emitError(new Error( + sprintf('Cannot use \'%s\' as class name as it is reserved', $node->extends), + $node->extends->getAttributes() + )); + } + + $this->checkImplementedInterfaces($node->implements); + } + + protected function checkInterface(Interface_ $node, $namePos) { + $this->checkClassName($node->name, $namePos); + $this->checkImplementedInterfaces($node->extends); + } + + protected function checkEnum(Enum_ $node, $namePos) { + $this->checkClassName($node->name, $namePos); + $this->checkImplementedInterfaces($node->implements); + } + + protected function checkClassMethod(ClassMethod $node, $modifierPos) { + if ($node->flags & Class_::MODIFIER_STATIC) { + switch ($node->name->toLowerString()) { + case '__construct': + $this->emitError(new Error( + sprintf('Constructor %s() cannot be static', $node->name), + $this->getAttributesAt($modifierPos))); + break; + case '__destruct': + $this->emitError(new Error( + sprintf('Destructor %s() cannot be static', $node->name), + $this->getAttributesAt($modifierPos))); + break; + case '__clone': + $this->emitError(new Error( + sprintf('Clone method %s() cannot be static', $node->name), + $this->getAttributesAt($modifierPos))); + break; + } + } + + if ($node->flags & Class_::MODIFIER_READONLY) { + $this->emitError(new Error( + sprintf('Method %s() cannot be readonly', $node->name), + $this->getAttributesAt($modifierPos))); + } + } + + protected function checkClassConst(ClassConst $node, $modifierPos) { + if ($node->flags & Class_::MODIFIER_STATIC) { + $this->emitError(new Error( + "Cannot use 'static' as constant modifier", + $this->getAttributesAt($modifierPos))); + } + if ($node->flags & Class_::MODIFIER_ABSTRACT) { + $this->emitError(new Error( + "Cannot use 'abstract' as constant modifier", + $this->getAttributesAt($modifierPos))); + } + if ($node->flags & Class_::MODIFIER_READONLY) { + $this->emitError(new Error( + "Cannot use 'readonly' as constant modifier", + $this->getAttributesAt($modifierPos))); + } + } + + protected function checkProperty(Property $node, $modifierPos) { + if ($node->flags & Class_::MODIFIER_ABSTRACT) { + $this->emitError(new Error('Properties cannot be declared abstract', + $this->getAttributesAt($modifierPos))); + } + + if ($node->flags & Class_::MODIFIER_FINAL) { + $this->emitError(new Error('Properties cannot be declared final', + $this->getAttributesAt($modifierPos))); + } + } + + protected function checkUseUse(UseUse $node, $namePos) { + if ($node->alias && $node->alias->isSpecialClassName()) { + $this->emitError(new Error( + sprintf( + 'Cannot use %s as %s because \'%2$s\' is a special class name', + $node->name, $node->alias + ), + $this->getAttributesAt($namePos) + )); + } + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/ParserFactory.php b/vendor/nikic/php-parser/lib/PhpParser/ParserFactory.php new file mode 100644 index 0000000000..f041e7ffe3 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/ParserFactory.php @@ -0,0 +1,44 @@ +pAttrGroups($node->attrGroups, true) + . $this->pModifiers($node->flags) + . ($node->type ? $this->p($node->type) . ' ' : '') + . ($node->byRef ? '&' : '') + . ($node->variadic ? '...' : '') + . $this->p($node->var) + . ($node->default ? ' = ' . $this->p($node->default) : ''); + } + + protected function pArg(Node\Arg $node) { + return ($node->name ? $node->name->toString() . ': ' : '') + . ($node->byRef ? '&' : '') . ($node->unpack ? '...' : '') + . $this->p($node->value); + } + + protected function pVariadicPlaceholder(Node\VariadicPlaceholder $node) { + return '...'; + } + + protected function pConst(Node\Const_ $node) { + return $node->name . ' = ' . $this->p($node->value); + } + + protected function pNullableType(Node\NullableType $node) { + return '?' . $this->p($node->type); + } + + protected function pUnionType(Node\UnionType $node) { + return $this->pImplode($node->types, '|'); + } + + protected function pIntersectionType(Node\IntersectionType $node) { + return $this->pImplode($node->types, '&'); + } + + protected function pIdentifier(Node\Identifier $node) { + return $node->name; + } + + protected function pVarLikeIdentifier(Node\VarLikeIdentifier $node) { + return '$' . $node->name; + } + + protected function pAttribute(Node\Attribute $node) { + return $this->p($node->name) + . ($node->args ? '(' . $this->pCommaSeparated($node->args) . ')' : ''); + } + + protected function pAttributeGroup(Node\AttributeGroup $node) { + return '#[' . $this->pCommaSeparated($node->attrs) . ']'; + } + + // Names + + protected function pName(Name $node) { + return implode('\\', $node->parts); + } + + protected function pName_FullyQualified(Name\FullyQualified $node) { + return '\\' . implode('\\', $node->parts); + } + + protected function pName_Relative(Name\Relative $node) { + return 'namespace\\' . implode('\\', $node->parts); + } + + // Magic Constants + + protected function pScalar_MagicConst_Class(MagicConst\Class_ $node) { + return '__CLASS__'; + } + + protected function pScalar_MagicConst_Dir(MagicConst\Dir $node) { + return '__DIR__'; + } + + protected function pScalar_MagicConst_File(MagicConst\File $node) { + return '__FILE__'; + } + + protected function pScalar_MagicConst_Function(MagicConst\Function_ $node) { + return '__FUNCTION__'; + } + + protected function pScalar_MagicConst_Line(MagicConst\Line $node) { + return '__LINE__'; + } + + protected function pScalar_MagicConst_Method(MagicConst\Method $node) { + return '__METHOD__'; + } + + protected function pScalar_MagicConst_Namespace(MagicConst\Namespace_ $node) { + return '__NAMESPACE__'; + } + + protected function pScalar_MagicConst_Trait(MagicConst\Trait_ $node) { + return '__TRAIT__'; + } + + // Scalars + + protected function pScalar_String(Scalar\String_ $node) { + $kind = $node->getAttribute('kind', Scalar\String_::KIND_SINGLE_QUOTED); + switch ($kind) { + case Scalar\String_::KIND_NOWDOC: + $label = $node->getAttribute('docLabel'); + if ($label && !$this->containsEndLabel($node->value, $label)) { + if ($node->value === '') { + return "<<<'$label'\n$label" . $this->docStringEndToken; + } + + return "<<<'$label'\n$node->value\n$label" + . $this->docStringEndToken; + } + /* break missing intentionally */ + case Scalar\String_::KIND_SINGLE_QUOTED: + return $this->pSingleQuotedString($node->value); + case Scalar\String_::KIND_HEREDOC: + $label = $node->getAttribute('docLabel'); + if ($label && !$this->containsEndLabel($node->value, $label)) { + if ($node->value === '') { + return "<<<$label\n$label" . $this->docStringEndToken; + } + + $escaped = $this->escapeString($node->value, null); + return "<<<$label\n" . $escaped . "\n$label" + . $this->docStringEndToken; + } + /* break missing intentionally */ + case Scalar\String_::KIND_DOUBLE_QUOTED: + return '"' . $this->escapeString($node->value, '"') . '"'; + } + throw new \Exception('Invalid string kind'); + } + + protected function pScalar_Encapsed(Scalar\Encapsed $node) { + if ($node->getAttribute('kind') === Scalar\String_::KIND_HEREDOC) { + $label = $node->getAttribute('docLabel'); + if ($label && !$this->encapsedContainsEndLabel($node->parts, $label)) { + if (count($node->parts) === 1 + && $node->parts[0] instanceof Scalar\EncapsedStringPart + && $node->parts[0]->value === '' + ) { + return "<<<$label\n$label" . $this->docStringEndToken; + } + + return "<<<$label\n" . $this->pEncapsList($node->parts, null) . "\n$label" + . $this->docStringEndToken; + } + } + return '"' . $this->pEncapsList($node->parts, '"') . '"'; + } + + protected function pScalar_LNumber(Scalar\LNumber $node) { + if ($node->value === -\PHP_INT_MAX-1) { + // PHP_INT_MIN cannot be represented as a literal, + // because the sign is not part of the literal + return '(-' . \PHP_INT_MAX . '-1)'; + } + + $kind = $node->getAttribute('kind', Scalar\LNumber::KIND_DEC); + if (Scalar\LNumber::KIND_DEC === $kind) { + return (string) $node->value; + } + + if ($node->value < 0) { + $sign = '-'; + $str = (string) -$node->value; + } else { + $sign = ''; + $str = (string) $node->value; + } + switch ($kind) { + case Scalar\LNumber::KIND_BIN: + return $sign . '0b' . base_convert($str, 10, 2); + case Scalar\LNumber::KIND_OCT: + return $sign . '0' . base_convert($str, 10, 8); + case Scalar\LNumber::KIND_HEX: + return $sign . '0x' . base_convert($str, 10, 16); + } + throw new \Exception('Invalid number kind'); + } + + protected function pScalar_DNumber(Scalar\DNumber $node) { + if (!is_finite($node->value)) { + if ($node->value === \INF) { + return '\INF'; + } elseif ($node->value === -\INF) { + return '-\INF'; + } else { + return '\NAN'; + } + } + + // Try to find a short full-precision representation + $stringValue = sprintf('%.16G', $node->value); + if ($node->value !== (double) $stringValue) { + $stringValue = sprintf('%.17G', $node->value); + } + + // %G is locale dependent and there exists no locale-independent alternative. We don't want + // mess with switching locales here, so let's assume that a comma is the only non-standard + // decimal separator we may encounter... + $stringValue = str_replace(',', '.', $stringValue); + + // ensure that number is really printed as float + return preg_match('/^-?[0-9]+$/', $stringValue) ? $stringValue . '.0' : $stringValue; + } + + protected function pScalar_EncapsedStringPart(Scalar\EncapsedStringPart $node) { + throw new \LogicException('Cannot directly print EncapsedStringPart'); + } + + // Assignments + + protected function pExpr_Assign(Expr\Assign $node) { + return $this->pInfixOp(Expr\Assign::class, $node->var, ' = ', $node->expr); + } + + protected function pExpr_AssignRef(Expr\AssignRef $node) { + return $this->pInfixOp(Expr\AssignRef::class, $node->var, ' =& ', $node->expr); + } + + protected function pExpr_AssignOp_Plus(AssignOp\Plus $node) { + return $this->pInfixOp(AssignOp\Plus::class, $node->var, ' += ', $node->expr); + } + + protected function pExpr_AssignOp_Minus(AssignOp\Minus $node) { + return $this->pInfixOp(AssignOp\Minus::class, $node->var, ' -= ', $node->expr); + } + + protected function pExpr_AssignOp_Mul(AssignOp\Mul $node) { + return $this->pInfixOp(AssignOp\Mul::class, $node->var, ' *= ', $node->expr); + } + + protected function pExpr_AssignOp_Div(AssignOp\Div $node) { + return $this->pInfixOp(AssignOp\Div::class, $node->var, ' /= ', $node->expr); + } + + protected function pExpr_AssignOp_Concat(AssignOp\Concat $node) { + return $this->pInfixOp(AssignOp\Concat::class, $node->var, ' .= ', $node->expr); + } + + protected function pExpr_AssignOp_Mod(AssignOp\Mod $node) { + return $this->pInfixOp(AssignOp\Mod::class, $node->var, ' %= ', $node->expr); + } + + protected function pExpr_AssignOp_BitwiseAnd(AssignOp\BitwiseAnd $node) { + return $this->pInfixOp(AssignOp\BitwiseAnd::class, $node->var, ' &= ', $node->expr); + } + + protected function pExpr_AssignOp_BitwiseOr(AssignOp\BitwiseOr $node) { + return $this->pInfixOp(AssignOp\BitwiseOr::class, $node->var, ' |= ', $node->expr); + } + + protected function pExpr_AssignOp_BitwiseXor(AssignOp\BitwiseXor $node) { + return $this->pInfixOp(AssignOp\BitwiseXor::class, $node->var, ' ^= ', $node->expr); + } + + protected function pExpr_AssignOp_ShiftLeft(AssignOp\ShiftLeft $node) { + return $this->pInfixOp(AssignOp\ShiftLeft::class, $node->var, ' <<= ', $node->expr); + } + + protected function pExpr_AssignOp_ShiftRight(AssignOp\ShiftRight $node) { + return $this->pInfixOp(AssignOp\ShiftRight::class, $node->var, ' >>= ', $node->expr); + } + + protected function pExpr_AssignOp_Pow(AssignOp\Pow $node) { + return $this->pInfixOp(AssignOp\Pow::class, $node->var, ' **= ', $node->expr); + } + + protected function pExpr_AssignOp_Coalesce(AssignOp\Coalesce $node) { + return $this->pInfixOp(AssignOp\Coalesce::class, $node->var, ' ??= ', $node->expr); + } + + // Binary expressions + + protected function pExpr_BinaryOp_Plus(BinaryOp\Plus $node) { + return $this->pInfixOp(BinaryOp\Plus::class, $node->left, ' + ', $node->right); + } + + protected function pExpr_BinaryOp_Minus(BinaryOp\Minus $node) { + return $this->pInfixOp(BinaryOp\Minus::class, $node->left, ' - ', $node->right); + } + + protected function pExpr_BinaryOp_Mul(BinaryOp\Mul $node) { + return $this->pInfixOp(BinaryOp\Mul::class, $node->left, ' * ', $node->right); + } + + protected function pExpr_BinaryOp_Div(BinaryOp\Div $node) { + return $this->pInfixOp(BinaryOp\Div::class, $node->left, ' / ', $node->right); + } + + protected function pExpr_BinaryOp_Concat(BinaryOp\Concat $node) { + return $this->pInfixOp(BinaryOp\Concat::class, $node->left, ' . ', $node->right); + } + + protected function pExpr_BinaryOp_Mod(BinaryOp\Mod $node) { + return $this->pInfixOp(BinaryOp\Mod::class, $node->left, ' % ', $node->right); + } + + protected function pExpr_BinaryOp_BooleanAnd(BinaryOp\BooleanAnd $node) { + return $this->pInfixOp(BinaryOp\BooleanAnd::class, $node->left, ' && ', $node->right); + } + + protected function pExpr_BinaryOp_BooleanOr(BinaryOp\BooleanOr $node) { + return $this->pInfixOp(BinaryOp\BooleanOr::class, $node->left, ' || ', $node->right); + } + + protected function pExpr_BinaryOp_BitwiseAnd(BinaryOp\BitwiseAnd $node) { + return $this->pInfixOp(BinaryOp\BitwiseAnd::class, $node->left, ' & ', $node->right); + } + + protected function pExpr_BinaryOp_BitwiseOr(BinaryOp\BitwiseOr $node) { + return $this->pInfixOp(BinaryOp\BitwiseOr::class, $node->left, ' | ', $node->right); + } + + protected function pExpr_BinaryOp_BitwiseXor(BinaryOp\BitwiseXor $node) { + return $this->pInfixOp(BinaryOp\BitwiseXor::class, $node->left, ' ^ ', $node->right); + } + + protected function pExpr_BinaryOp_ShiftLeft(BinaryOp\ShiftLeft $node) { + return $this->pInfixOp(BinaryOp\ShiftLeft::class, $node->left, ' << ', $node->right); + } + + protected function pExpr_BinaryOp_ShiftRight(BinaryOp\ShiftRight $node) { + return $this->pInfixOp(BinaryOp\ShiftRight::class, $node->left, ' >> ', $node->right); + } + + protected function pExpr_BinaryOp_Pow(BinaryOp\Pow $node) { + return $this->pInfixOp(BinaryOp\Pow::class, $node->left, ' ** ', $node->right); + } + + protected function pExpr_BinaryOp_LogicalAnd(BinaryOp\LogicalAnd $node) { + return $this->pInfixOp(BinaryOp\LogicalAnd::class, $node->left, ' and ', $node->right); + } + + protected function pExpr_BinaryOp_LogicalOr(BinaryOp\LogicalOr $node) { + return $this->pInfixOp(BinaryOp\LogicalOr::class, $node->left, ' or ', $node->right); + } + + protected function pExpr_BinaryOp_LogicalXor(BinaryOp\LogicalXor $node) { + return $this->pInfixOp(BinaryOp\LogicalXor::class, $node->left, ' xor ', $node->right); + } + + protected function pExpr_BinaryOp_Equal(BinaryOp\Equal $node) { + return $this->pInfixOp(BinaryOp\Equal::class, $node->left, ' == ', $node->right); + } + + protected function pExpr_BinaryOp_NotEqual(BinaryOp\NotEqual $node) { + return $this->pInfixOp(BinaryOp\NotEqual::class, $node->left, ' != ', $node->right); + } + + protected function pExpr_BinaryOp_Identical(BinaryOp\Identical $node) { + return $this->pInfixOp(BinaryOp\Identical::class, $node->left, ' === ', $node->right); + } + + protected function pExpr_BinaryOp_NotIdentical(BinaryOp\NotIdentical $node) { + return $this->pInfixOp(BinaryOp\NotIdentical::class, $node->left, ' !== ', $node->right); + } + + protected function pExpr_BinaryOp_Spaceship(BinaryOp\Spaceship $node) { + return $this->pInfixOp(BinaryOp\Spaceship::class, $node->left, ' <=> ', $node->right); + } + + protected function pExpr_BinaryOp_Greater(BinaryOp\Greater $node) { + return $this->pInfixOp(BinaryOp\Greater::class, $node->left, ' > ', $node->right); + } + + protected function pExpr_BinaryOp_GreaterOrEqual(BinaryOp\GreaterOrEqual $node) { + return $this->pInfixOp(BinaryOp\GreaterOrEqual::class, $node->left, ' >= ', $node->right); + } + + protected function pExpr_BinaryOp_Smaller(BinaryOp\Smaller $node) { + return $this->pInfixOp(BinaryOp\Smaller::class, $node->left, ' < ', $node->right); + } + + protected function pExpr_BinaryOp_SmallerOrEqual(BinaryOp\SmallerOrEqual $node) { + return $this->pInfixOp(BinaryOp\SmallerOrEqual::class, $node->left, ' <= ', $node->right); + } + + protected function pExpr_BinaryOp_Coalesce(BinaryOp\Coalesce $node) { + return $this->pInfixOp(BinaryOp\Coalesce::class, $node->left, ' ?? ', $node->right); + } + + protected function pExpr_Instanceof(Expr\Instanceof_ $node) { + list($precedence, $associativity) = $this->precedenceMap[Expr\Instanceof_::class]; + return $this->pPrec($node->expr, $precedence, $associativity, -1) + . ' instanceof ' + . $this->pNewVariable($node->class); + } + + // Unary expressions + + protected function pExpr_BooleanNot(Expr\BooleanNot $node) { + return $this->pPrefixOp(Expr\BooleanNot::class, '!', $node->expr); + } + + protected function pExpr_BitwiseNot(Expr\BitwiseNot $node) { + return $this->pPrefixOp(Expr\BitwiseNot::class, '~', $node->expr); + } + + protected function pExpr_UnaryMinus(Expr\UnaryMinus $node) { + if ($node->expr instanceof Expr\UnaryMinus || $node->expr instanceof Expr\PreDec) { + // Enforce -(-$expr) instead of --$expr + return '-(' . $this->p($node->expr) . ')'; + } + return $this->pPrefixOp(Expr\UnaryMinus::class, '-', $node->expr); + } + + protected function pExpr_UnaryPlus(Expr\UnaryPlus $node) { + if ($node->expr instanceof Expr\UnaryPlus || $node->expr instanceof Expr\PreInc) { + // Enforce +(+$expr) instead of ++$expr + return '+(' . $this->p($node->expr) . ')'; + } + return $this->pPrefixOp(Expr\UnaryPlus::class, '+', $node->expr); + } + + protected function pExpr_PreInc(Expr\PreInc $node) { + return $this->pPrefixOp(Expr\PreInc::class, '++', $node->var); + } + + protected function pExpr_PreDec(Expr\PreDec $node) { + return $this->pPrefixOp(Expr\PreDec::class, '--', $node->var); + } + + protected function pExpr_PostInc(Expr\PostInc $node) { + return $this->pPostfixOp(Expr\PostInc::class, $node->var, '++'); + } + + protected function pExpr_PostDec(Expr\PostDec $node) { + return $this->pPostfixOp(Expr\PostDec::class, $node->var, '--'); + } + + protected function pExpr_ErrorSuppress(Expr\ErrorSuppress $node) { + return $this->pPrefixOp(Expr\ErrorSuppress::class, '@', $node->expr); + } + + protected function pExpr_YieldFrom(Expr\YieldFrom $node) { + return $this->pPrefixOp(Expr\YieldFrom::class, 'yield from ', $node->expr); + } + + protected function pExpr_Print(Expr\Print_ $node) { + return $this->pPrefixOp(Expr\Print_::class, 'print ', $node->expr); + } + + // Casts + + protected function pExpr_Cast_Int(Cast\Int_ $node) { + return $this->pPrefixOp(Cast\Int_::class, '(int) ', $node->expr); + } + + protected function pExpr_Cast_Double(Cast\Double $node) { + $kind = $node->getAttribute('kind', Cast\Double::KIND_DOUBLE); + if ($kind === Cast\Double::KIND_DOUBLE) { + $cast = '(double)'; + } elseif ($kind === Cast\Double::KIND_FLOAT) { + $cast = '(float)'; + } elseif ($kind === Cast\Double::KIND_REAL) { + $cast = '(real)'; + } + return $this->pPrefixOp(Cast\Double::class, $cast . ' ', $node->expr); + } + + protected function pExpr_Cast_String(Cast\String_ $node) { + return $this->pPrefixOp(Cast\String_::class, '(string) ', $node->expr); + } + + protected function pExpr_Cast_Array(Cast\Array_ $node) { + return $this->pPrefixOp(Cast\Array_::class, '(array) ', $node->expr); + } + + protected function pExpr_Cast_Object(Cast\Object_ $node) { + return $this->pPrefixOp(Cast\Object_::class, '(object) ', $node->expr); + } + + protected function pExpr_Cast_Bool(Cast\Bool_ $node) { + return $this->pPrefixOp(Cast\Bool_::class, '(bool) ', $node->expr); + } + + protected function pExpr_Cast_Unset(Cast\Unset_ $node) { + return $this->pPrefixOp(Cast\Unset_::class, '(unset) ', $node->expr); + } + + // Function calls and similar constructs + + protected function pExpr_FuncCall(Expr\FuncCall $node) { + return $this->pCallLhs($node->name) + . '(' . $this->pMaybeMultiline($node->args) . ')'; + } + + protected function pExpr_MethodCall(Expr\MethodCall $node) { + return $this->pDereferenceLhs($node->var) . '->' . $this->pObjectProperty($node->name) + . '(' . $this->pMaybeMultiline($node->args) . ')'; + } + + protected function pExpr_NullsafeMethodCall(Expr\NullsafeMethodCall $node) { + return $this->pDereferenceLhs($node->var) . '?->' . $this->pObjectProperty($node->name) + . '(' . $this->pMaybeMultiline($node->args) . ')'; + } + + protected function pExpr_StaticCall(Expr\StaticCall $node) { + return $this->pDereferenceLhs($node->class) . '::' + . ($node->name instanceof Expr + ? ($node->name instanceof Expr\Variable + ? $this->p($node->name) + : '{' . $this->p($node->name) . '}') + : $node->name) + . '(' . $this->pMaybeMultiline($node->args) . ')'; + } + + protected function pExpr_Empty(Expr\Empty_ $node) { + return 'empty(' . $this->p($node->expr) . ')'; + } + + protected function pExpr_Isset(Expr\Isset_ $node) { + return 'isset(' . $this->pCommaSeparated($node->vars) . ')'; + } + + protected function pExpr_Eval(Expr\Eval_ $node) { + return 'eval(' . $this->p($node->expr) . ')'; + } + + protected function pExpr_Include(Expr\Include_ $node) { + static $map = [ + Expr\Include_::TYPE_INCLUDE => 'include', + Expr\Include_::TYPE_INCLUDE_ONCE => 'include_once', + Expr\Include_::TYPE_REQUIRE => 'require', + Expr\Include_::TYPE_REQUIRE_ONCE => 'require_once', + ]; + + return $map[$node->type] . ' ' . $this->p($node->expr); + } + + protected function pExpr_List(Expr\List_ $node) { + return 'list(' . $this->pCommaSeparated($node->items) . ')'; + } + + // Other + + protected function pExpr_Error(Expr\Error $node) { + throw new \LogicException('Cannot pretty-print AST with Error nodes'); + } + + protected function pExpr_Variable(Expr\Variable $node) { + if ($node->name instanceof Expr) { + return '${' . $this->p($node->name) . '}'; + } else { + return '$' . $node->name; + } + } + + protected function pExpr_Array(Expr\Array_ $node) { + $syntax = $node->getAttribute('kind', + $this->options['shortArraySyntax'] ? Expr\Array_::KIND_SHORT : Expr\Array_::KIND_LONG); + if ($syntax === Expr\Array_::KIND_SHORT) { + return '[' . $this->pMaybeMultiline($node->items, true) . ']'; + } else { + return 'array(' . $this->pMaybeMultiline($node->items, true) . ')'; + } + } + + protected function pExpr_ArrayItem(Expr\ArrayItem $node) { + return (null !== $node->key ? $this->p($node->key) . ' => ' : '') + . ($node->byRef ? '&' : '') + . ($node->unpack ? '...' : '') + . $this->p($node->value); + } + + protected function pExpr_ArrayDimFetch(Expr\ArrayDimFetch $node) { + return $this->pDereferenceLhs($node->var) + . '[' . (null !== $node->dim ? $this->p($node->dim) : '') . ']'; + } + + protected function pExpr_ConstFetch(Expr\ConstFetch $node) { + return $this->p($node->name); + } + + protected function pExpr_ClassConstFetch(Expr\ClassConstFetch $node) { + return $this->pDereferenceLhs($node->class) . '::' . $this->p($node->name); + } + + protected function pExpr_PropertyFetch(Expr\PropertyFetch $node) { + return $this->pDereferenceLhs($node->var) . '->' . $this->pObjectProperty($node->name); + } + + protected function pExpr_NullsafePropertyFetch(Expr\NullsafePropertyFetch $node) { + return $this->pDereferenceLhs($node->var) . '?->' . $this->pObjectProperty($node->name); + } + + protected function pExpr_StaticPropertyFetch(Expr\StaticPropertyFetch $node) { + return $this->pDereferenceLhs($node->class) . '::$' . $this->pObjectProperty($node->name); + } + + protected function pExpr_ShellExec(Expr\ShellExec $node) { + return '`' . $this->pEncapsList($node->parts, '`') . '`'; + } + + protected function pExpr_Closure(Expr\Closure $node) { + return $this->pAttrGroups($node->attrGroups, true) + . ($node->static ? 'static ' : '') + . 'function ' . ($node->byRef ? '&' : '') + . '(' . $this->pCommaSeparated($node->params) . ')' + . (!empty($node->uses) ? ' use(' . $this->pCommaSeparated($node->uses) . ')' : '') + . (null !== $node->returnType ? ' : ' . $this->p($node->returnType) : '') + . ' {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pExpr_Match(Expr\Match_ $node) { + return 'match (' . $this->p($node->cond) . ') {' + . $this->pCommaSeparatedMultiline($node->arms, true) + . $this->nl + . '}'; + } + + protected function pMatchArm(Node\MatchArm $node) { + return ($node->conds ? $this->pCommaSeparated($node->conds) : 'default') + . ' => ' . $this->p($node->body); + } + + protected function pExpr_ArrowFunction(Expr\ArrowFunction $node) { + return $this->pAttrGroups($node->attrGroups, true) + . ($node->static ? 'static ' : '') + . 'fn' . ($node->byRef ? '&' : '') + . '(' . $this->pCommaSeparated($node->params) . ')' + . (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '') + . ' => ' + . $this->p($node->expr); + } + + protected function pExpr_ClosureUse(Expr\ClosureUse $node) { + return ($node->byRef ? '&' : '') . $this->p($node->var); + } + + protected function pExpr_New(Expr\New_ $node) { + if ($node->class instanceof Stmt\Class_) { + $args = $node->args ? '(' . $this->pMaybeMultiline($node->args) . ')' : ''; + return 'new ' . $this->pClassCommon($node->class, $args); + } + return 'new ' . $this->pNewVariable($node->class) + . '(' . $this->pMaybeMultiline($node->args) . ')'; + } + + protected function pExpr_Clone(Expr\Clone_ $node) { + return 'clone ' . $this->p($node->expr); + } + + protected function pExpr_Ternary(Expr\Ternary $node) { + // a bit of cheating: we treat the ternary as a binary op where the ?...: part is the operator. + // this is okay because the part between ? and : never needs parentheses. + return $this->pInfixOp(Expr\Ternary::class, + $node->cond, ' ?' . (null !== $node->if ? ' ' . $this->p($node->if) . ' ' : '') . ': ', $node->else + ); + } + + protected function pExpr_Exit(Expr\Exit_ $node) { + $kind = $node->getAttribute('kind', Expr\Exit_::KIND_DIE); + return ($kind === Expr\Exit_::KIND_EXIT ? 'exit' : 'die') + . (null !== $node->expr ? '(' . $this->p($node->expr) . ')' : ''); + } + + protected function pExpr_Throw(Expr\Throw_ $node) { + return 'throw ' . $this->p($node->expr); + } + + protected function pExpr_Yield(Expr\Yield_ $node) { + if ($node->value === null) { + return 'yield'; + } else { + // this is a bit ugly, but currently there is no way to detect whether the parentheses are necessary + return '(yield ' + . ($node->key !== null ? $this->p($node->key) . ' => ' : '') + . $this->p($node->value) + . ')'; + } + } + + // Declarations + + protected function pStmt_Namespace(Stmt\Namespace_ $node) { + if ($this->canUseSemicolonNamespaces) { + return 'namespace ' . $this->p($node->name) . ';' + . $this->nl . $this->pStmts($node->stmts, false); + } else { + return 'namespace' . (null !== $node->name ? ' ' . $this->p($node->name) : '') + . ' {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + } + + protected function pStmt_Use(Stmt\Use_ $node) { + return 'use ' . $this->pUseType($node->type) + . $this->pCommaSeparated($node->uses) . ';'; + } + + protected function pStmt_GroupUse(Stmt\GroupUse $node) { + return 'use ' . $this->pUseType($node->type) . $this->pName($node->prefix) + . '\{' . $this->pCommaSeparated($node->uses) . '};'; + } + + protected function pStmt_UseUse(Stmt\UseUse $node) { + return $this->pUseType($node->type) . $this->p($node->name) + . (null !== $node->alias ? ' as ' . $node->alias : ''); + } + + protected function pUseType($type) { + return $type === Stmt\Use_::TYPE_FUNCTION ? 'function ' + : ($type === Stmt\Use_::TYPE_CONSTANT ? 'const ' : ''); + } + + protected function pStmt_Interface(Stmt\Interface_ $node) { + return $this->pAttrGroups($node->attrGroups) + . 'interface ' . $node->name + . (!empty($node->extends) ? ' extends ' . $this->pCommaSeparated($node->extends) : '') + . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Enum(Stmt\Enum_ $node) { + return $this->pAttrGroups($node->attrGroups) + . 'enum ' . $node->name + . ($node->scalarType ? " : $node->scalarType" : '') + . (!empty($node->implements) ? ' implements ' . $this->pCommaSeparated($node->implements) : '') + . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Class(Stmt\Class_ $node) { + return $this->pClassCommon($node, ' ' . $node->name); + } + + protected function pStmt_Trait(Stmt\Trait_ $node) { + return $this->pAttrGroups($node->attrGroups) + . 'trait ' . $node->name + . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_EnumCase(Stmt\EnumCase $node) { + return $this->pAttrGroups($node->attrGroups) + . 'case ' . $node->name + . ($node->expr ? ' = ' . $this->p($node->expr) : '') + . ';'; + } + + protected function pStmt_TraitUse(Stmt\TraitUse $node) { + return 'use ' . $this->pCommaSeparated($node->traits) + . (empty($node->adaptations) + ? ';' + : ' {' . $this->pStmts($node->adaptations) . $this->nl . '}'); + } + + protected function pStmt_TraitUseAdaptation_Precedence(Stmt\TraitUseAdaptation\Precedence $node) { + return $this->p($node->trait) . '::' . $node->method + . ' insteadof ' . $this->pCommaSeparated($node->insteadof) . ';'; + } + + protected function pStmt_TraitUseAdaptation_Alias(Stmt\TraitUseAdaptation\Alias $node) { + return (null !== $node->trait ? $this->p($node->trait) . '::' : '') + . $node->method . ' as' + . (null !== $node->newModifier ? ' ' . rtrim($this->pModifiers($node->newModifier), ' ') : '') + . (null !== $node->newName ? ' ' . $node->newName : '') + . ';'; + } + + protected function pStmt_Property(Stmt\Property $node) { + return $this->pAttrGroups($node->attrGroups) + . (0 === $node->flags ? 'var ' : $this->pModifiers($node->flags)) + . ($node->type ? $this->p($node->type) . ' ' : '') + . $this->pCommaSeparated($node->props) . ';'; + } + + protected function pStmt_PropertyProperty(Stmt\PropertyProperty $node) { + return '$' . $node->name + . (null !== $node->default ? ' = ' . $this->p($node->default) : ''); + } + + protected function pStmt_ClassMethod(Stmt\ClassMethod $node) { + return $this->pAttrGroups($node->attrGroups) + . $this->pModifiers($node->flags) + . 'function ' . ($node->byRef ? '&' : '') . $node->name + . '(' . $this->pMaybeMultiline($node->params) . ')' + . (null !== $node->returnType ? ' : ' . $this->p($node->returnType) : '') + . (null !== $node->stmts + ? $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}' + : ';'); + } + + protected function pStmt_ClassConst(Stmt\ClassConst $node) { + return $this->pAttrGroups($node->attrGroups) + . $this->pModifiers($node->flags) + . 'const ' . $this->pCommaSeparated($node->consts) . ';'; + } + + protected function pStmt_Function(Stmt\Function_ $node) { + return $this->pAttrGroups($node->attrGroups) + . 'function ' . ($node->byRef ? '&' : '') . $node->name + . '(' . $this->pCommaSeparated($node->params) . ')' + . (null !== $node->returnType ? ' : ' . $this->p($node->returnType) : '') + . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Const(Stmt\Const_ $node) { + return 'const ' . $this->pCommaSeparated($node->consts) . ';'; + } + + protected function pStmt_Declare(Stmt\Declare_ $node) { + return 'declare (' . $this->pCommaSeparated($node->declares) . ')' + . (null !== $node->stmts ? ' {' . $this->pStmts($node->stmts) . $this->nl . '}' : ';'); + } + + protected function pStmt_DeclareDeclare(Stmt\DeclareDeclare $node) { + return $node->key . '=' . $this->p($node->value); + } + + // Control flow + + protected function pStmt_If(Stmt\If_ $node) { + return 'if (' . $this->p($node->cond) . ') {' + . $this->pStmts($node->stmts) . $this->nl . '}' + . ($node->elseifs ? ' ' . $this->pImplode($node->elseifs, ' ') : '') + . (null !== $node->else ? ' ' . $this->p($node->else) : ''); + } + + protected function pStmt_ElseIf(Stmt\ElseIf_ $node) { + return 'elseif (' . $this->p($node->cond) . ') {' + . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Else(Stmt\Else_ $node) { + return 'else {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_For(Stmt\For_ $node) { + return 'for (' + . $this->pCommaSeparated($node->init) . ';' . (!empty($node->cond) ? ' ' : '') + . $this->pCommaSeparated($node->cond) . ';' . (!empty($node->loop) ? ' ' : '') + . $this->pCommaSeparated($node->loop) + . ') {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Foreach(Stmt\Foreach_ $node) { + return 'foreach (' . $this->p($node->expr) . ' as ' + . (null !== $node->keyVar ? $this->p($node->keyVar) . ' => ' : '') + . ($node->byRef ? '&' : '') . $this->p($node->valueVar) . ') {' + . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_While(Stmt\While_ $node) { + return 'while (' . $this->p($node->cond) . ') {' + . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Do(Stmt\Do_ $node) { + return 'do {' . $this->pStmts($node->stmts) . $this->nl + . '} while (' . $this->p($node->cond) . ');'; + } + + protected function pStmt_Switch(Stmt\Switch_ $node) { + return 'switch (' . $this->p($node->cond) . ') {' + . $this->pStmts($node->cases) . $this->nl . '}'; + } + + protected function pStmt_Case(Stmt\Case_ $node) { + return (null !== $node->cond ? 'case ' . $this->p($node->cond) : 'default') . ':' + . $this->pStmts($node->stmts); + } + + protected function pStmt_TryCatch(Stmt\TryCatch $node) { + return 'try {' . $this->pStmts($node->stmts) . $this->nl . '}' + . ($node->catches ? ' ' . $this->pImplode($node->catches, ' ') : '') + . ($node->finally !== null ? ' ' . $this->p($node->finally) : ''); + } + + protected function pStmt_Catch(Stmt\Catch_ $node) { + return 'catch (' . $this->pImplode($node->types, '|') + . ($node->var !== null ? ' ' . $this->p($node->var) : '') + . ') {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Finally(Stmt\Finally_ $node) { + return 'finally {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Break(Stmt\Break_ $node) { + return 'break' . ($node->num !== null ? ' ' . $this->p($node->num) : '') . ';'; + } + + protected function pStmt_Continue(Stmt\Continue_ $node) { + return 'continue' . ($node->num !== null ? ' ' . $this->p($node->num) : '') . ';'; + } + + protected function pStmt_Return(Stmt\Return_ $node) { + return 'return' . (null !== $node->expr ? ' ' . $this->p($node->expr) : '') . ';'; + } + + protected function pStmt_Throw(Stmt\Throw_ $node) { + return 'throw ' . $this->p($node->expr) . ';'; + } + + protected function pStmt_Label(Stmt\Label $node) { + return $node->name . ':'; + } + + protected function pStmt_Goto(Stmt\Goto_ $node) { + return 'goto ' . $node->name . ';'; + } + + // Other + + protected function pStmt_Expression(Stmt\Expression $node) { + return $this->p($node->expr) . ';'; + } + + protected function pStmt_Echo(Stmt\Echo_ $node) { + return 'echo ' . $this->pCommaSeparated($node->exprs) . ';'; + } + + protected function pStmt_Static(Stmt\Static_ $node) { + return 'static ' . $this->pCommaSeparated($node->vars) . ';'; + } + + protected function pStmt_Global(Stmt\Global_ $node) { + return 'global ' . $this->pCommaSeparated($node->vars) . ';'; + } + + protected function pStmt_StaticVar(Stmt\StaticVar $node) { + return $this->p($node->var) + . (null !== $node->default ? ' = ' . $this->p($node->default) : ''); + } + + protected function pStmt_Unset(Stmt\Unset_ $node) { + return 'unset(' . $this->pCommaSeparated($node->vars) . ');'; + } + + protected function pStmt_InlineHTML(Stmt\InlineHTML $node) { + $newline = $node->getAttribute('hasLeadingNewline', true) ? "\n" : ''; + return '?>' . $newline . $node->value . 'remaining; + } + + protected function pStmt_Nop(Stmt\Nop $node) { + return ''; + } + + // Helpers + + protected function pClassCommon(Stmt\Class_ $node, $afterClassToken) { + return $this->pAttrGroups($node->attrGroups, $node->name === null) + . $this->pModifiers($node->flags) + . 'class' . $afterClassToken + . (null !== $node->extends ? ' extends ' . $this->p($node->extends) : '') + . (!empty($node->implements) ? ' implements ' . $this->pCommaSeparated($node->implements) : '') + . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pObjectProperty($node) { + if ($node instanceof Expr) { + return '{' . $this->p($node) . '}'; + } else { + return $node; + } + } + + protected function pEncapsList(array $encapsList, $quote) { + $return = ''; + foreach ($encapsList as $element) { + if ($element instanceof Scalar\EncapsedStringPart) { + $return .= $this->escapeString($element->value, $quote); + } else { + $return .= '{' . $this->p($element) . '}'; + } + } + + return $return; + } + + protected function pSingleQuotedString(string $string) { + return '\'' . addcslashes($string, '\'\\') . '\''; + } + + protected function escapeString($string, $quote) { + if (null === $quote) { + // For doc strings, don't escape newlines + $escaped = addcslashes($string, "\t\f\v$\\"); + } else { + $escaped = addcslashes($string, "\n\r\t\f\v$" . $quote . "\\"); + } + + // Escape control characters and non-UTF-8 characters. + // Regex based on https://stackoverflow.com/a/11709412/385378. + $regex = '/( + [\x00-\x08\x0E-\x1F] # Control characters + | [\xC0-\xC1] # Invalid UTF-8 Bytes + | [\xF5-\xFF] # Invalid UTF-8 Bytes + | \xE0(?=[\x80-\x9F]) # Overlong encoding of prior code point + | \xF0(?=[\x80-\x8F]) # Overlong encoding of prior code point + | [\xC2-\xDF](?![\x80-\xBF]) # Invalid UTF-8 Sequence Start + | [\xE0-\xEF](?![\x80-\xBF]{2}) # Invalid UTF-8 Sequence Start + | [\xF0-\xF4](?![\x80-\xBF]{3}) # Invalid UTF-8 Sequence Start + | (?<=[\x00-\x7F\xF5-\xFF])[\x80-\xBF] # Invalid UTF-8 Sequence Middle + | (? $part) { + $atStart = $i === 0; + $atEnd = $i === count($parts) - 1; + if ($part instanceof Scalar\EncapsedStringPart + && $this->containsEndLabel($part->value, $label, $atStart, $atEnd) + ) { + return true; + } + } + return false; + } + + protected function pDereferenceLhs(Node $node) { + if (!$this->dereferenceLhsRequiresParens($node)) { + return $this->p($node); + } else { + return '(' . $this->p($node) . ')'; + } + } + + protected function pCallLhs(Node $node) { + if (!$this->callLhsRequiresParens($node)) { + return $this->p($node); + } else { + return '(' . $this->p($node) . ')'; + } + } + + protected function pNewVariable(Node $node) { + // TODO: This is not fully accurate. + return $this->pDereferenceLhs($node); + } + + /** + * @param Node[] $nodes + * @return bool + */ + protected function hasNodeWithComments(array $nodes) { + foreach ($nodes as $node) { + if ($node && $node->getComments()) { + return true; + } + } + return false; + } + + protected function pMaybeMultiline(array $nodes, bool $trailingComma = false) { + if (!$this->hasNodeWithComments($nodes)) { + return $this->pCommaSeparated($nodes); + } else { + return $this->pCommaSeparatedMultiline($nodes, $trailingComma) . $this->nl; + } + } + + protected function pAttrGroups(array $nodes, bool $inline = false): string { + $result = ''; + $sep = $inline ? ' ' : $this->nl; + foreach ($nodes as $node) { + $result .= $this->p($node) . $sep; + } + + return $result; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.php b/vendor/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.php new file mode 100644 index 0000000000..2c7fc3070c --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.php @@ -0,0 +1,1506 @@ + [ 0, 1], + Expr\BitwiseNot::class => [ 10, 1], + Expr\PreInc::class => [ 10, 1], + Expr\PreDec::class => [ 10, 1], + Expr\PostInc::class => [ 10, -1], + Expr\PostDec::class => [ 10, -1], + Expr\UnaryPlus::class => [ 10, 1], + Expr\UnaryMinus::class => [ 10, 1], + Cast\Int_::class => [ 10, 1], + Cast\Double::class => [ 10, 1], + Cast\String_::class => [ 10, 1], + Cast\Array_::class => [ 10, 1], + Cast\Object_::class => [ 10, 1], + Cast\Bool_::class => [ 10, 1], + Cast\Unset_::class => [ 10, 1], + Expr\ErrorSuppress::class => [ 10, 1], + Expr\Instanceof_::class => [ 20, 0], + Expr\BooleanNot::class => [ 30, 1], + BinaryOp\Mul::class => [ 40, -1], + BinaryOp\Div::class => [ 40, -1], + BinaryOp\Mod::class => [ 40, -1], + BinaryOp\Plus::class => [ 50, -1], + BinaryOp\Minus::class => [ 50, -1], + BinaryOp\Concat::class => [ 50, -1], + BinaryOp\ShiftLeft::class => [ 60, -1], + BinaryOp\ShiftRight::class => [ 60, -1], + BinaryOp\Smaller::class => [ 70, 0], + BinaryOp\SmallerOrEqual::class => [ 70, 0], + BinaryOp\Greater::class => [ 70, 0], + BinaryOp\GreaterOrEqual::class => [ 70, 0], + BinaryOp\Equal::class => [ 80, 0], + BinaryOp\NotEqual::class => [ 80, 0], + BinaryOp\Identical::class => [ 80, 0], + BinaryOp\NotIdentical::class => [ 80, 0], + BinaryOp\Spaceship::class => [ 80, 0], + BinaryOp\BitwiseAnd::class => [ 90, -1], + BinaryOp\BitwiseXor::class => [100, -1], + BinaryOp\BitwiseOr::class => [110, -1], + BinaryOp\BooleanAnd::class => [120, -1], + BinaryOp\BooleanOr::class => [130, -1], + BinaryOp\Coalesce::class => [140, 1], + Expr\Ternary::class => [150, 0], + // parser uses %left for assignments, but they really behave as %right + Expr\Assign::class => [160, 1], + Expr\AssignRef::class => [160, 1], + AssignOp\Plus::class => [160, 1], + AssignOp\Minus::class => [160, 1], + AssignOp\Mul::class => [160, 1], + AssignOp\Div::class => [160, 1], + AssignOp\Concat::class => [160, 1], + AssignOp\Mod::class => [160, 1], + AssignOp\BitwiseAnd::class => [160, 1], + AssignOp\BitwiseOr::class => [160, 1], + AssignOp\BitwiseXor::class => [160, 1], + AssignOp\ShiftLeft::class => [160, 1], + AssignOp\ShiftRight::class => [160, 1], + AssignOp\Pow::class => [160, 1], + AssignOp\Coalesce::class => [160, 1], + Expr\YieldFrom::class => [165, 1], + Expr\Print_::class => [168, 1], + BinaryOp\LogicalAnd::class => [170, -1], + BinaryOp\LogicalXor::class => [180, -1], + BinaryOp\LogicalOr::class => [190, -1], + Expr\Include_::class => [200, -1], + ]; + + /** @var int Current indentation level. */ + protected $indentLevel; + /** @var string Newline including current indentation. */ + protected $nl; + /** @var string Token placed at end of doc string to ensure it is followed by a newline. */ + protected $docStringEndToken; + /** @var bool Whether semicolon namespaces can be used (i.e. no global namespace is used) */ + protected $canUseSemicolonNamespaces; + /** @var array Pretty printer options */ + protected $options; + + /** @var TokenStream Original tokens for use in format-preserving pretty print */ + protected $origTokens; + /** @var Internal\Differ Differ for node lists */ + protected $nodeListDiffer; + /** @var bool[] Map determining whether a certain character is a label character */ + protected $labelCharMap; + /** + * @var int[][] Map from token classes and subnode names to FIXUP_* constants. This is used + * during format-preserving prints to place additional parens/braces if necessary. + */ + protected $fixupMap; + /** + * @var int[][] Map from "{$node->getType()}->{$subNode}" to ['left' => $l, 'right' => $r], + * where $l and $r specify the token type that needs to be stripped when removing + * this node. + */ + protected $removalMap; + /** + * @var mixed[] Map from "{$node->getType()}->{$subNode}" to [$find, $beforeToken, $extraLeft, $extraRight]. + * $find is an optional token after which the insertion occurs. $extraLeft/Right + * are optionally added before/after the main insertions. + */ + protected $insertionMap; + /** + * @var string[] Map From "{$node->getType()}->{$subNode}" to string that should be inserted + * between elements of this list subnode. + */ + protected $listInsertionMap; + protected $emptyListInsertionMap; + /** @var int[] Map from "{$node->getType()}->{$subNode}" to token before which the modifiers + * should be reprinted. */ + protected $modifierChangeMap; + + /** + * Creates a pretty printer instance using the given options. + * + * Supported options: + * * bool $shortArraySyntax = false: Whether to use [] instead of array() as the default array + * syntax, if the node does not specify a format. + * + * @param array $options Dictionary of formatting options + */ + public function __construct(array $options = []) { + $this->docStringEndToken = '_DOC_STRING_END_' . mt_rand(); + + $defaultOptions = ['shortArraySyntax' => false]; + $this->options = $options + $defaultOptions; + } + + /** + * Reset pretty printing state. + */ + protected function resetState() { + $this->indentLevel = 0; + $this->nl = "\n"; + $this->origTokens = null; + } + + /** + * Set indentation level + * + * @param int $level Level in number of spaces + */ + protected function setIndentLevel(int $level) { + $this->indentLevel = $level; + $this->nl = "\n" . \str_repeat(' ', $level); + } + + /** + * Increase indentation level. + */ + protected function indent() { + $this->indentLevel += 4; + $this->nl .= ' '; + } + + /** + * Decrease indentation level. + */ + protected function outdent() { + assert($this->indentLevel >= 4); + $this->indentLevel -= 4; + $this->nl = "\n" . str_repeat(' ', $this->indentLevel); + } + + /** + * Pretty prints an array of statements. + * + * @param Node[] $stmts Array of statements + * + * @return string Pretty printed statements + */ + public function prettyPrint(array $stmts) : string { + $this->resetState(); + $this->preprocessNodes($stmts); + + return ltrim($this->handleMagicTokens($this->pStmts($stmts, false))); + } + + /** + * Pretty prints an expression. + * + * @param Expr $node Expression node + * + * @return string Pretty printed node + */ + public function prettyPrintExpr(Expr $node) : string { + $this->resetState(); + return $this->handleMagicTokens($this->p($node)); + } + + /** + * Pretty prints a file of statements (includes the opening prettyPrint($stmts); + + if ($stmts[0] instanceof Stmt\InlineHTML) { + $p = preg_replace('/^<\?php\s+\?>\n?/', '', $p); + } + if ($stmts[count($stmts) - 1] instanceof Stmt\InlineHTML) { + $p = preg_replace('/<\?php$/', '', rtrim($p)); + } + + return $p; + } + + /** + * Preprocesses the top-level nodes to initialize pretty printer state. + * + * @param Node[] $nodes Array of nodes + */ + protected function preprocessNodes(array $nodes) { + /* We can use semicolon-namespaces unless there is a global namespace declaration */ + $this->canUseSemicolonNamespaces = true; + foreach ($nodes as $node) { + if ($node instanceof Stmt\Namespace_ && null === $node->name) { + $this->canUseSemicolonNamespaces = false; + break; + } + } + } + + /** + * Handles (and removes) no-indent and doc-string-end tokens. + * + * @param string $str + * @return string + */ + protected function handleMagicTokens(string $str) : string { + // Replace doc-string-end tokens with nothing or a newline + $str = str_replace($this->docStringEndToken . ";\n", ";\n", $str); + $str = str_replace($this->docStringEndToken, "\n", $str); + + return $str; + } + + /** + * Pretty prints an array of nodes (statements) and indents them optionally. + * + * @param Node[] $nodes Array of nodes + * @param bool $indent Whether to indent the printed nodes + * + * @return string Pretty printed statements + */ + protected function pStmts(array $nodes, bool $indent = true) : string { + if ($indent) { + $this->indent(); + } + + $result = ''; + foreach ($nodes as $node) { + $comments = $node->getComments(); + if ($comments) { + $result .= $this->nl . $this->pComments($comments); + if ($node instanceof Stmt\Nop) { + continue; + } + } + + $result .= $this->nl . $this->p($node); + } + + if ($indent) { + $this->outdent(); + } + + return $result; + } + + /** + * Pretty-print an infix operation while taking precedence into account. + * + * @param string $class Node class of operator + * @param Node $leftNode Left-hand side node + * @param string $operatorString String representation of the operator + * @param Node $rightNode Right-hand side node + * + * @return string Pretty printed infix operation + */ + protected function pInfixOp(string $class, Node $leftNode, string $operatorString, Node $rightNode) : string { + list($precedence, $associativity) = $this->precedenceMap[$class]; + + return $this->pPrec($leftNode, $precedence, $associativity, -1) + . $operatorString + . $this->pPrec($rightNode, $precedence, $associativity, 1); + } + + /** + * Pretty-print a prefix operation while taking precedence into account. + * + * @param string $class Node class of operator + * @param string $operatorString String representation of the operator + * @param Node $node Node + * + * @return string Pretty printed prefix operation + */ + protected function pPrefixOp(string $class, string $operatorString, Node $node) : string { + list($precedence, $associativity) = $this->precedenceMap[$class]; + return $operatorString . $this->pPrec($node, $precedence, $associativity, 1); + } + + /** + * Pretty-print a postfix operation while taking precedence into account. + * + * @param string $class Node class of operator + * @param string $operatorString String representation of the operator + * @param Node $node Node + * + * @return string Pretty printed postfix operation + */ + protected function pPostfixOp(string $class, Node $node, string $operatorString) : string { + list($precedence, $associativity) = $this->precedenceMap[$class]; + return $this->pPrec($node, $precedence, $associativity, -1) . $operatorString; + } + + /** + * Prints an expression node with the least amount of parentheses necessary to preserve the meaning. + * + * @param Node $node Node to pretty print + * @param int $parentPrecedence Precedence of the parent operator + * @param int $parentAssociativity Associativity of parent operator + * (-1 is left, 0 is nonassoc, 1 is right) + * @param int $childPosition Position of the node relative to the operator + * (-1 is left, 1 is right) + * + * @return string The pretty printed node + */ + protected function pPrec(Node $node, int $parentPrecedence, int $parentAssociativity, int $childPosition) : string { + $class = \get_class($node); + if (isset($this->precedenceMap[$class])) { + $childPrecedence = $this->precedenceMap[$class][0]; + if ($childPrecedence > $parentPrecedence + || ($parentPrecedence === $childPrecedence && $parentAssociativity !== $childPosition) + ) { + return '(' . $this->p($node) . ')'; + } + } + + return $this->p($node); + } + + /** + * Pretty prints an array of nodes and implodes the printed values. + * + * @param Node[] $nodes Array of Nodes to be printed + * @param string $glue Character to implode with + * + * @return string Imploded pretty printed nodes + */ + protected function pImplode(array $nodes, string $glue = '') : string { + $pNodes = []; + foreach ($nodes as $node) { + if (null === $node) { + $pNodes[] = ''; + } else { + $pNodes[] = $this->p($node); + } + } + + return implode($glue, $pNodes); + } + + /** + * Pretty prints an array of nodes and implodes the printed values with commas. + * + * @param Node[] $nodes Array of Nodes to be printed + * + * @return string Comma separated pretty printed nodes + */ + protected function pCommaSeparated(array $nodes) : string { + return $this->pImplode($nodes, ', '); + } + + /** + * Pretty prints a comma-separated list of nodes in multiline style, including comments. + * + * The result includes a leading newline and one level of indentation (same as pStmts). + * + * @param Node[] $nodes Array of Nodes to be printed + * @param bool $trailingComma Whether to use a trailing comma + * + * @return string Comma separated pretty printed nodes in multiline style + */ + protected function pCommaSeparatedMultiline(array $nodes, bool $trailingComma) : string { + $this->indent(); + + $result = ''; + $lastIdx = count($nodes) - 1; + foreach ($nodes as $idx => $node) { + if ($node !== null) { + $comments = $node->getComments(); + if ($comments) { + $result .= $this->nl . $this->pComments($comments); + } + + $result .= $this->nl . $this->p($node); + } else { + $result .= $this->nl; + } + if ($trailingComma || $idx !== $lastIdx) { + $result .= ','; + } + } + + $this->outdent(); + return $result; + } + + /** + * Prints reformatted text of the passed comments. + * + * @param Comment[] $comments List of comments + * + * @return string Reformatted text of comments + */ + protected function pComments(array $comments) : string { + $formattedComments = []; + + foreach ($comments as $comment) { + $formattedComments[] = str_replace("\n", $this->nl, $comment->getReformattedText()); + } + + return implode($this->nl, $formattedComments); + } + + /** + * Perform a format-preserving pretty print of an AST. + * + * The format preservation is best effort. For some changes to the AST the formatting will not + * be preserved (at least not locally). + * + * In order to use this method a number of prerequisites must be satisfied: + * * The startTokenPos and endTokenPos attributes in the lexer must be enabled. + * * The CloningVisitor must be run on the AST prior to modification. + * * The original tokens must be provided, using the getTokens() method on the lexer. + * + * @param Node[] $stmts Modified AST with links to original AST + * @param Node[] $origStmts Original AST with token offset information + * @param array $origTokens Tokens of the original code + * + * @return string + */ + public function printFormatPreserving(array $stmts, array $origStmts, array $origTokens) : string { + $this->initializeNodeListDiffer(); + $this->initializeLabelCharMap(); + $this->initializeFixupMap(); + $this->initializeRemovalMap(); + $this->initializeInsertionMap(); + $this->initializeListInsertionMap(); + $this->initializeEmptyListInsertionMap(); + $this->initializeModifierChangeMap(); + + $this->resetState(); + $this->origTokens = new TokenStream($origTokens); + + $this->preprocessNodes($stmts); + + $pos = 0; + $result = $this->pArray($stmts, $origStmts, $pos, 0, 'File', 'stmts', null); + if (null !== $result) { + $result .= $this->origTokens->getTokenCode($pos, count($origTokens), 0); + } else { + // Fallback + // TODO Add pStmts($stmts, false); + } + + return ltrim($this->handleMagicTokens($result)); + } + + protected function pFallback(Node $node) { + return $this->{'p' . $node->getType()}($node); + } + + /** + * Pretty prints a node. + * + * This method also handles formatting preservation for nodes. + * + * @param Node $node Node to be pretty printed + * @param bool $parentFormatPreserved Whether parent node has preserved formatting + * + * @return string Pretty printed node + */ + protected function p(Node $node, $parentFormatPreserved = false) : string { + // No orig tokens means this is a normal pretty print without preservation of formatting + if (!$this->origTokens) { + return $this->{'p' . $node->getType()}($node); + } + + /** @var Node $origNode */ + $origNode = $node->getAttribute('origNode'); + if (null === $origNode) { + return $this->pFallback($node); + } + + $class = \get_class($node); + \assert($class === \get_class($origNode)); + + $startPos = $origNode->getStartTokenPos(); + $endPos = $origNode->getEndTokenPos(); + \assert($startPos >= 0 && $endPos >= 0); + + $fallbackNode = $node; + if ($node instanceof Expr\New_ && $node->class instanceof Stmt\Class_) { + // Normalize node structure of anonymous classes + $node = PrintableNewAnonClassNode::fromNewNode($node); + $origNode = PrintableNewAnonClassNode::fromNewNode($origNode); + } + + // InlineHTML node does not contain closing and opening PHP tags. If the parent formatting + // is not preserved, then we need to use the fallback code to make sure the tags are + // printed. + if ($node instanceof Stmt\InlineHTML && !$parentFormatPreserved) { + return $this->pFallback($fallbackNode); + } + + $indentAdjustment = $this->indentLevel - $this->origTokens->getIndentationBefore($startPos); + + $type = $node->getType(); + $fixupInfo = $this->fixupMap[$class] ?? null; + + $result = ''; + $pos = $startPos; + foreach ($node->getSubNodeNames() as $subNodeName) { + $subNode = $node->$subNodeName; + $origSubNode = $origNode->$subNodeName; + + if ((!$subNode instanceof Node && $subNode !== null) + || (!$origSubNode instanceof Node && $origSubNode !== null) + ) { + if ($subNode === $origSubNode) { + // Unchanged, can reuse old code + continue; + } + + if (is_array($subNode) && is_array($origSubNode)) { + // Array subnode changed, we might be able to reconstruct it + $listResult = $this->pArray( + $subNode, $origSubNode, $pos, $indentAdjustment, $type, $subNodeName, + $fixupInfo[$subNodeName] ?? null + ); + if (null === $listResult) { + return $this->pFallback($fallbackNode); + } + + $result .= $listResult; + continue; + } + + if (is_int($subNode) && is_int($origSubNode)) { + // Check if this is a modifier change + $key = $type . '->' . $subNodeName; + if (!isset($this->modifierChangeMap[$key])) { + return $this->pFallback($fallbackNode); + } + + $findToken = $this->modifierChangeMap[$key]; + $result .= $this->pModifiers($subNode); + $pos = $this->origTokens->findRight($pos, $findToken); + continue; + } + + // If a non-node, non-array subnode changed, we don't be able to do a partial + // reconstructions, as we don't have enough offset information. Pretty print the + // whole node instead. + return $this->pFallback($fallbackNode); + } + + $extraLeft = ''; + $extraRight = ''; + if ($origSubNode !== null) { + $subStartPos = $origSubNode->getStartTokenPos(); + $subEndPos = $origSubNode->getEndTokenPos(); + \assert($subStartPos >= 0 && $subEndPos >= 0); + } else { + if ($subNode === null) { + // Both null, nothing to do + continue; + } + + // A node has been inserted, check if we have insertion information for it + $key = $type . '->' . $subNodeName; + if (!isset($this->insertionMap[$key])) { + return $this->pFallback($fallbackNode); + } + + list($findToken, $beforeToken, $extraLeft, $extraRight) = $this->insertionMap[$key]; + if (null !== $findToken) { + $subStartPos = $this->origTokens->findRight($pos, $findToken) + + (int) !$beforeToken; + } else { + $subStartPos = $pos; + } + + if (null === $extraLeft && null !== $extraRight) { + // If inserting on the right only, skipping whitespace looks better + $subStartPos = $this->origTokens->skipRightWhitespace($subStartPos); + } + $subEndPos = $subStartPos - 1; + } + + if (null === $subNode) { + // A node has been removed, check if we have removal information for it + $key = $type . '->' . $subNodeName; + if (!isset($this->removalMap[$key])) { + return $this->pFallback($fallbackNode); + } + + // Adjust positions to account for additional tokens that must be skipped + $removalInfo = $this->removalMap[$key]; + if (isset($removalInfo['left'])) { + $subStartPos = $this->origTokens->skipLeft($subStartPos - 1, $removalInfo['left']) + 1; + } + if (isset($removalInfo['right'])) { + $subEndPos = $this->origTokens->skipRight($subEndPos + 1, $removalInfo['right']) - 1; + } + } + + $result .= $this->origTokens->getTokenCode($pos, $subStartPos, $indentAdjustment); + + if (null !== $subNode) { + $result .= $extraLeft; + + $origIndentLevel = $this->indentLevel; + $this->setIndentLevel($this->origTokens->getIndentationBefore($subStartPos) + $indentAdjustment); + + // If it's the same node that was previously in this position, it certainly doesn't + // need fixup. It's important to check this here, because our fixup checks are more + // conservative than strictly necessary. + if (isset($fixupInfo[$subNodeName]) + && $subNode->getAttribute('origNode') !== $origSubNode + ) { + $fixup = $fixupInfo[$subNodeName]; + $res = $this->pFixup($fixup, $subNode, $class, $subStartPos, $subEndPos); + } else { + $res = $this->p($subNode, true); + } + + $this->safeAppend($result, $res); + $this->setIndentLevel($origIndentLevel); + + $result .= $extraRight; + } + + $pos = $subEndPos + 1; + } + + $result .= $this->origTokens->getTokenCode($pos, $endPos + 1, $indentAdjustment); + return $result; + } + + /** + * Perform a format-preserving pretty print of an array. + * + * @param array $nodes New nodes + * @param array $origNodes Original nodes + * @param int $pos Current token position (updated by reference) + * @param int $indentAdjustment Adjustment for indentation + * @param string $parentNodeType Type of the containing node. + * @param string $subNodeName Name of array subnode. + * @param null|int $fixup Fixup information for array item nodes + * + * @return null|string Result of pretty print or null if cannot preserve formatting + */ + protected function pArray( + array $nodes, array $origNodes, int &$pos, int $indentAdjustment, + string $parentNodeType, string $subNodeName, $fixup + ) { + $diff = $this->nodeListDiffer->diffWithReplacements($origNodes, $nodes); + + $mapKey = $parentNodeType . '->' . $subNodeName; + $insertStr = $this->listInsertionMap[$mapKey] ?? null; + $isStmtList = $subNodeName === 'stmts'; + + $beforeFirstKeepOrReplace = true; + $skipRemovedNode = false; + $delayedAdd = []; + $lastElemIndentLevel = $this->indentLevel; + + $insertNewline = false; + if ($insertStr === "\n") { + $insertStr = ''; + $insertNewline = true; + } + + if ($isStmtList && \count($origNodes) === 1 && \count($nodes) !== 1) { + $startPos = $origNodes[0]->getStartTokenPos(); + $endPos = $origNodes[0]->getEndTokenPos(); + \assert($startPos >= 0 && $endPos >= 0); + if (!$this->origTokens->haveBraces($startPos, $endPos)) { + // This was a single statement without braces, but either additional statements + // have been added, or the single statement has been removed. This requires the + // addition of braces. For now fall back. + // TODO: Try to preserve formatting + return null; + } + } + + $result = ''; + foreach ($diff as $i => $diffElem) { + $diffType = $diffElem->type; + /** @var Node|null $arrItem */ + $arrItem = $diffElem->new; + /** @var Node|null $origArrItem */ + $origArrItem = $diffElem->old; + + if ($diffType === DiffElem::TYPE_KEEP || $diffType === DiffElem::TYPE_REPLACE) { + $beforeFirstKeepOrReplace = false; + + if ($origArrItem === null || $arrItem === null) { + // We can only handle the case where both are null + if ($origArrItem === $arrItem) { + continue; + } + return null; + } + + if (!$arrItem instanceof Node || !$origArrItem instanceof Node) { + // We can only deal with nodes. This can occur for Names, which use string arrays. + return null; + } + + $itemStartPos = $origArrItem->getStartTokenPos(); + $itemEndPos = $origArrItem->getEndTokenPos(); + \assert($itemStartPos >= 0 && $itemEndPos >= 0 && $itemStartPos >= $pos); + + $origIndentLevel = $this->indentLevel; + $lastElemIndentLevel = $this->origTokens->getIndentationBefore($itemStartPos) + $indentAdjustment; + $this->setIndentLevel($lastElemIndentLevel); + + $comments = $arrItem->getComments(); + $origComments = $origArrItem->getComments(); + $commentStartPos = $origComments ? $origComments[0]->getStartTokenPos() : $itemStartPos; + \assert($commentStartPos >= 0); + + if ($commentStartPos < $pos) { + // Comments may be assigned to multiple nodes if they start at the same position. + // Make sure we don't try to print them multiple times. + $commentStartPos = $itemStartPos; + } + + if ($skipRemovedNode) { + if ($isStmtList && $this->origTokens->haveBracesInRange($pos, $itemStartPos)) { + // We'd remove the brace of a code block. + // TODO: Preserve formatting. + $this->setIndentLevel($origIndentLevel); + return null; + } + } else { + $result .= $this->origTokens->getTokenCode( + $pos, $commentStartPos, $indentAdjustment); + } + + if (!empty($delayedAdd)) { + /** @var Node $delayedAddNode */ + foreach ($delayedAdd as $delayedAddNode) { + if ($insertNewline) { + $delayedAddComments = $delayedAddNode->getComments(); + if ($delayedAddComments) { + $result .= $this->pComments($delayedAddComments) . $this->nl; + } + } + + $this->safeAppend($result, $this->p($delayedAddNode, true)); + + if ($insertNewline) { + $result .= $insertStr . $this->nl; + } else { + $result .= $insertStr; + } + } + + $delayedAdd = []; + } + + if ($comments !== $origComments) { + if ($comments) { + $result .= $this->pComments($comments) . $this->nl; + } + } else { + $result .= $this->origTokens->getTokenCode( + $commentStartPos, $itemStartPos, $indentAdjustment); + } + + // If we had to remove anything, we have done so now. + $skipRemovedNode = false; + } elseif ($diffType === DiffElem::TYPE_ADD) { + if (null === $insertStr) { + // We don't have insertion information for this list type + return null; + } + + // We go multiline if the original code was multiline, + // or if it's an array item with a comment above it. + if ($insertStr === ', ' && + ($this->isMultiline($origNodes) || $arrItem->getComments()) + ) { + $insertStr = ','; + $insertNewline = true; + } + + if ($beforeFirstKeepOrReplace) { + // Will be inserted at the next "replace" or "keep" element + $delayedAdd[] = $arrItem; + continue; + } + + $itemStartPos = $pos; + $itemEndPos = $pos - 1; + + $origIndentLevel = $this->indentLevel; + $this->setIndentLevel($lastElemIndentLevel); + + if ($insertNewline) { + $result .= $insertStr . $this->nl; + $comments = $arrItem->getComments(); + if ($comments) { + $result .= $this->pComments($comments) . $this->nl; + } + } else { + $result .= $insertStr; + } + } elseif ($diffType === DiffElem::TYPE_REMOVE) { + if (!$origArrItem instanceof Node) { + // We only support removal for nodes + return null; + } + + $itemStartPos = $origArrItem->getStartTokenPos(); + $itemEndPos = $origArrItem->getEndTokenPos(); + \assert($itemStartPos >= 0 && $itemEndPos >= 0); + + // Consider comments part of the node. + $origComments = $origArrItem->getComments(); + if ($origComments) { + $itemStartPos = $origComments[0]->getStartTokenPos(); + } + + if ($i === 0) { + // If we're removing from the start, keep the tokens before the node and drop those after it, + // instead of the other way around. + $result .= $this->origTokens->getTokenCode( + $pos, $itemStartPos, $indentAdjustment); + $skipRemovedNode = true; + } else { + if ($isStmtList && $this->origTokens->haveBracesInRange($pos, $itemStartPos)) { + // We'd remove the brace of a code block. + // TODO: Preserve formatting. + return null; + } + } + + $pos = $itemEndPos + 1; + continue; + } else { + throw new \Exception("Shouldn't happen"); + } + + if (null !== $fixup && $arrItem->getAttribute('origNode') !== $origArrItem) { + $res = $this->pFixup($fixup, $arrItem, null, $itemStartPos, $itemEndPos); + } else { + $res = $this->p($arrItem, true); + } + $this->safeAppend($result, $res); + + $this->setIndentLevel($origIndentLevel); + $pos = $itemEndPos + 1; + } + + if ($skipRemovedNode) { + // TODO: Support removing single node. + return null; + } + + if (!empty($delayedAdd)) { + if (!isset($this->emptyListInsertionMap[$mapKey])) { + return null; + } + + list($findToken, $extraLeft, $extraRight) = $this->emptyListInsertionMap[$mapKey]; + if (null !== $findToken) { + $insertPos = $this->origTokens->findRight($pos, $findToken) + 1; + $result .= $this->origTokens->getTokenCode($pos, $insertPos, $indentAdjustment); + $pos = $insertPos; + } + + $first = true; + $result .= $extraLeft; + foreach ($delayedAdd as $delayedAddNode) { + if (!$first) { + $result .= $insertStr; + } + $result .= $this->p($delayedAddNode, true); + $first = false; + } + $result .= $extraRight; + } + + return $result; + } + + /** + * Print node with fixups. + * + * Fixups here refer to the addition of extra parentheses, braces or other characters, that + * are required to preserve program semantics in a certain context (e.g. to maintain precedence + * or because only certain expressions are allowed in certain places). + * + * @param int $fixup Fixup type + * @param Node $subNode Subnode to print + * @param string|null $parentClass Class of parent node + * @param int $subStartPos Original start pos of subnode + * @param int $subEndPos Original end pos of subnode + * + * @return string Result of fixed-up print of subnode + */ + protected function pFixup(int $fixup, Node $subNode, $parentClass, int $subStartPos, int $subEndPos) : string { + switch ($fixup) { + case self::FIXUP_PREC_LEFT: + case self::FIXUP_PREC_RIGHT: + if (!$this->origTokens->haveParens($subStartPos, $subEndPos)) { + list($precedence, $associativity) = $this->precedenceMap[$parentClass]; + return $this->pPrec($subNode, $precedence, $associativity, + $fixup === self::FIXUP_PREC_LEFT ? -1 : 1); + } + break; + case self::FIXUP_CALL_LHS: + if ($this->callLhsRequiresParens($subNode) + && !$this->origTokens->haveParens($subStartPos, $subEndPos) + ) { + return '(' . $this->p($subNode) . ')'; + } + break; + case self::FIXUP_DEREF_LHS: + if ($this->dereferenceLhsRequiresParens($subNode) + && !$this->origTokens->haveParens($subStartPos, $subEndPos) + ) { + return '(' . $this->p($subNode) . ')'; + } + break; + case self::FIXUP_BRACED_NAME: + case self::FIXUP_VAR_BRACED_NAME: + if ($subNode instanceof Expr + && !$this->origTokens->haveBraces($subStartPos, $subEndPos) + ) { + return ($fixup === self::FIXUP_VAR_BRACED_NAME ? '$' : '') + . '{' . $this->p($subNode) . '}'; + } + break; + case self::FIXUP_ENCAPSED: + if (!$subNode instanceof Scalar\EncapsedStringPart + && !$this->origTokens->haveBraces($subStartPos, $subEndPos) + ) { + return '{' . $this->p($subNode) . '}'; + } + break; + default: + throw new \Exception('Cannot happen'); + } + + // Nothing special to do + return $this->p($subNode); + } + + /** + * Appends to a string, ensuring whitespace between label characters. + * + * Example: "echo" and "$x" result in "echo$x", but "echo" and "x" result in "echo x". + * Without safeAppend the result would be "echox", which does not preserve semantics. + * + * @param string $str + * @param string $append + */ + protected function safeAppend(string &$str, string $append) { + if ($str === "") { + $str = $append; + return; + } + + if ($append === "") { + return; + } + + if (!$this->labelCharMap[$append[0]] + || !$this->labelCharMap[$str[\strlen($str) - 1]]) { + $str .= $append; + } else { + $str .= " " . $append; + } + } + + /** + * Determines whether the LHS of a call must be wrapped in parenthesis. + * + * @param Node $node LHS of a call + * + * @return bool Whether parentheses are required + */ + protected function callLhsRequiresParens(Node $node) : bool { + return !($node instanceof Node\Name + || $node instanceof Expr\Variable + || $node instanceof Expr\ArrayDimFetch + || $node instanceof Expr\FuncCall + || $node instanceof Expr\MethodCall + || $node instanceof Expr\NullsafeMethodCall + || $node instanceof Expr\StaticCall + || $node instanceof Expr\Array_); + } + + /** + * Determines whether the LHS of a dereferencing operation must be wrapped in parenthesis. + * + * @param Node $node LHS of dereferencing operation + * + * @return bool Whether parentheses are required + */ + protected function dereferenceLhsRequiresParens(Node $node) : bool { + return !($node instanceof Expr\Variable + || $node instanceof Node\Name + || $node instanceof Expr\ArrayDimFetch + || $node instanceof Expr\PropertyFetch + || $node instanceof Expr\NullsafePropertyFetch + || $node instanceof Expr\StaticPropertyFetch + || $node instanceof Expr\FuncCall + || $node instanceof Expr\MethodCall + || $node instanceof Expr\NullsafeMethodCall + || $node instanceof Expr\StaticCall + || $node instanceof Expr\Array_ + || $node instanceof Scalar\String_ + || $node instanceof Expr\ConstFetch + || $node instanceof Expr\ClassConstFetch); + } + + /** + * Print modifiers, including trailing whitespace. + * + * @param int $modifiers Modifier mask to print + * + * @return string Printed modifiers + */ + protected function pModifiers(int $modifiers) { + return ($modifiers & Stmt\Class_::MODIFIER_PUBLIC ? 'public ' : '') + . ($modifiers & Stmt\Class_::MODIFIER_PROTECTED ? 'protected ' : '') + . ($modifiers & Stmt\Class_::MODIFIER_PRIVATE ? 'private ' : '') + . ($modifiers & Stmt\Class_::MODIFIER_STATIC ? 'static ' : '') + . ($modifiers & Stmt\Class_::MODIFIER_ABSTRACT ? 'abstract ' : '') + . ($modifiers & Stmt\Class_::MODIFIER_FINAL ? 'final ' : '') + . ($modifiers & Stmt\Class_::MODIFIER_READONLY ? 'readonly ' : ''); + } + + /** + * Determine whether a list of nodes uses multiline formatting. + * + * @param (Node|null)[] $nodes Node list + * + * @return bool Whether multiline formatting is used + */ + protected function isMultiline(array $nodes) : bool { + if (\count($nodes) < 2) { + return false; + } + + $pos = -1; + foreach ($nodes as $node) { + if (null === $node) { + continue; + } + + $endPos = $node->getEndTokenPos() + 1; + if ($pos >= 0) { + $text = $this->origTokens->getTokenCode($pos, $endPos, 0); + if (false === strpos($text, "\n")) { + // We require that a newline is present between *every* item. If the formatting + // is inconsistent, with only some items having newlines, we don't consider it + // as multiline + return false; + } + } + $pos = $endPos; + } + + return true; + } + + /** + * Lazily initializes label char map. + * + * The label char map determines whether a certain character may occur in a label. + */ + protected function initializeLabelCharMap() { + if ($this->labelCharMap) return; + + $this->labelCharMap = []; + for ($i = 0; $i < 256; $i++) { + // Since PHP 7.1 The lower range is 0x80. However, we also want to support code for + // older versions. + $chr = chr($i); + $this->labelCharMap[$chr] = $i >= 0x7f || ctype_alnum($chr); + } + } + + /** + * Lazily initializes node list differ. + * + * The node list differ is used to determine differences between two array subnodes. + */ + protected function initializeNodeListDiffer() { + if ($this->nodeListDiffer) return; + + $this->nodeListDiffer = new Internal\Differ(function ($a, $b) { + if ($a instanceof Node && $b instanceof Node) { + return $a === $b->getAttribute('origNode'); + } + // Can happen for array destructuring + return $a === null && $b === null; + }); + } + + /** + * Lazily initializes fixup map. + * + * The fixup map is used to determine whether a certain subnode of a certain node may require + * some kind of "fixup" operation, e.g. the addition of parenthesis or braces. + */ + protected function initializeFixupMap() { + if ($this->fixupMap) return; + + $this->fixupMap = [ + Expr\PreInc::class => ['var' => self::FIXUP_PREC_RIGHT], + Expr\PreDec::class => ['var' => self::FIXUP_PREC_RIGHT], + Expr\PostInc::class => ['var' => self::FIXUP_PREC_LEFT], + Expr\PostDec::class => ['var' => self::FIXUP_PREC_LEFT], + Expr\Instanceof_::class => [ + 'expr' => self::FIXUP_PREC_LEFT, + 'class' => self::FIXUP_PREC_RIGHT, // TODO: FIXUP_NEW_VARIABLE + ], + Expr\Ternary::class => [ + 'cond' => self::FIXUP_PREC_LEFT, + 'else' => self::FIXUP_PREC_RIGHT, + ], + + Expr\FuncCall::class => ['name' => self::FIXUP_CALL_LHS], + Expr\StaticCall::class => ['class' => self::FIXUP_DEREF_LHS], + Expr\ArrayDimFetch::class => ['var' => self::FIXUP_DEREF_LHS], + Expr\ClassConstFetch::class => ['var' => self::FIXUP_DEREF_LHS], + Expr\New_::class => ['class' => self::FIXUP_DEREF_LHS], // TODO: FIXUP_NEW_VARIABLE + Expr\MethodCall::class => [ + 'var' => self::FIXUP_DEREF_LHS, + 'name' => self::FIXUP_BRACED_NAME, + ], + Expr\NullsafeMethodCall::class => [ + 'var' => self::FIXUP_DEREF_LHS, + 'name' => self::FIXUP_BRACED_NAME, + ], + Expr\StaticPropertyFetch::class => [ + 'class' => self::FIXUP_DEREF_LHS, + 'name' => self::FIXUP_VAR_BRACED_NAME, + ], + Expr\PropertyFetch::class => [ + 'var' => self::FIXUP_DEREF_LHS, + 'name' => self::FIXUP_BRACED_NAME, + ], + Expr\NullsafePropertyFetch::class => [ + 'var' => self::FIXUP_DEREF_LHS, + 'name' => self::FIXUP_BRACED_NAME, + ], + Scalar\Encapsed::class => [ + 'parts' => self::FIXUP_ENCAPSED, + ], + ]; + + $binaryOps = [ + BinaryOp\Pow::class, BinaryOp\Mul::class, BinaryOp\Div::class, BinaryOp\Mod::class, + BinaryOp\Plus::class, BinaryOp\Minus::class, BinaryOp\Concat::class, + BinaryOp\ShiftLeft::class, BinaryOp\ShiftRight::class, BinaryOp\Smaller::class, + BinaryOp\SmallerOrEqual::class, BinaryOp\Greater::class, BinaryOp\GreaterOrEqual::class, + BinaryOp\Equal::class, BinaryOp\NotEqual::class, BinaryOp\Identical::class, + BinaryOp\NotIdentical::class, BinaryOp\Spaceship::class, BinaryOp\BitwiseAnd::class, + BinaryOp\BitwiseXor::class, BinaryOp\BitwiseOr::class, BinaryOp\BooleanAnd::class, + BinaryOp\BooleanOr::class, BinaryOp\Coalesce::class, BinaryOp\LogicalAnd::class, + BinaryOp\LogicalXor::class, BinaryOp\LogicalOr::class, + ]; + foreach ($binaryOps as $binaryOp) { + $this->fixupMap[$binaryOp] = [ + 'left' => self::FIXUP_PREC_LEFT, + 'right' => self::FIXUP_PREC_RIGHT + ]; + } + + $assignOps = [ + Expr\Assign::class, Expr\AssignRef::class, AssignOp\Plus::class, AssignOp\Minus::class, + AssignOp\Mul::class, AssignOp\Div::class, AssignOp\Concat::class, AssignOp\Mod::class, + AssignOp\BitwiseAnd::class, AssignOp\BitwiseOr::class, AssignOp\BitwiseXor::class, + AssignOp\ShiftLeft::class, AssignOp\ShiftRight::class, AssignOp\Pow::class, AssignOp\Coalesce::class + ]; + foreach ($assignOps as $assignOp) { + $this->fixupMap[$assignOp] = [ + 'var' => self::FIXUP_PREC_LEFT, + 'expr' => self::FIXUP_PREC_RIGHT, + ]; + } + + $prefixOps = [ + Expr\BitwiseNot::class, Expr\BooleanNot::class, Expr\UnaryPlus::class, Expr\UnaryMinus::class, + Cast\Int_::class, Cast\Double::class, Cast\String_::class, Cast\Array_::class, + Cast\Object_::class, Cast\Bool_::class, Cast\Unset_::class, Expr\ErrorSuppress::class, + Expr\YieldFrom::class, Expr\Print_::class, Expr\Include_::class, + ]; + foreach ($prefixOps as $prefixOp) { + $this->fixupMap[$prefixOp] = ['expr' => self::FIXUP_PREC_RIGHT]; + } + } + + /** + * Lazily initializes the removal map. + * + * The removal map is used to determine which additional tokens should be removed when a + * certain node is replaced by null. + */ + protected function initializeRemovalMap() { + if ($this->removalMap) return; + + $stripBoth = ['left' => \T_WHITESPACE, 'right' => \T_WHITESPACE]; + $stripLeft = ['left' => \T_WHITESPACE]; + $stripRight = ['right' => \T_WHITESPACE]; + $stripDoubleArrow = ['right' => \T_DOUBLE_ARROW]; + $stripColon = ['left' => ':']; + $stripEquals = ['left' => '=']; + $this->removalMap = [ + 'Expr_ArrayDimFetch->dim' => $stripBoth, + 'Expr_ArrayItem->key' => $stripDoubleArrow, + 'Expr_ArrowFunction->returnType' => $stripColon, + 'Expr_Closure->returnType' => $stripColon, + 'Expr_Exit->expr' => $stripBoth, + 'Expr_Ternary->if' => $stripBoth, + 'Expr_Yield->key' => $stripDoubleArrow, + 'Expr_Yield->value' => $stripBoth, + 'Param->type' => $stripRight, + 'Param->default' => $stripEquals, + 'Stmt_Break->num' => $stripBoth, + 'Stmt_Catch->var' => $stripLeft, + 'Stmt_ClassMethod->returnType' => $stripColon, + 'Stmt_Class->extends' => ['left' => \T_EXTENDS], + 'Stmt_Enum->scalarType' => $stripColon, + 'Stmt_EnumCase->expr' => $stripEquals, + 'Expr_PrintableNewAnonClass->extends' => ['left' => \T_EXTENDS], + 'Stmt_Continue->num' => $stripBoth, + 'Stmt_Foreach->keyVar' => $stripDoubleArrow, + 'Stmt_Function->returnType' => $stripColon, + 'Stmt_If->else' => $stripLeft, + 'Stmt_Namespace->name' => $stripLeft, + 'Stmt_Property->type' => $stripRight, + 'Stmt_PropertyProperty->default' => $stripEquals, + 'Stmt_Return->expr' => $stripBoth, + 'Stmt_StaticVar->default' => $stripEquals, + 'Stmt_TraitUseAdaptation_Alias->newName' => $stripLeft, + 'Stmt_TryCatch->finally' => $stripLeft, + // 'Stmt_Case->cond': Replace with "default" + // 'Stmt_Class->name': Unclear what to do + // 'Stmt_Declare->stmts': Not a plain node + // 'Stmt_TraitUseAdaptation_Alias->newModifier': Not a plain node + ]; + } + + protected function initializeInsertionMap() { + if ($this->insertionMap) return; + + // TODO: "yield" where both key and value are inserted doesn't work + // [$find, $beforeToken, $extraLeft, $extraRight] + $this->insertionMap = [ + 'Expr_ArrayDimFetch->dim' => ['[', false, null, null], + 'Expr_ArrayItem->key' => [null, false, null, ' => '], + 'Expr_ArrowFunction->returnType' => [')', false, ' : ', null], + 'Expr_Closure->returnType' => [')', false, ' : ', null], + 'Expr_Ternary->if' => ['?', false, ' ', ' '], + 'Expr_Yield->key' => [\T_YIELD, false, null, ' => '], + 'Expr_Yield->value' => [\T_YIELD, false, ' ', null], + 'Param->type' => [null, false, null, ' '], + 'Param->default' => [null, false, ' = ', null], + 'Stmt_Break->num' => [\T_BREAK, false, ' ', null], + 'Stmt_Catch->var' => [null, false, ' ', null], + 'Stmt_ClassMethod->returnType' => [')', false, ' : ', null], + 'Stmt_Class->extends' => [null, false, ' extends ', null], + 'Stmt_Enum->scalarType' => [null, false, ' : ', null], + 'Stmt_EnumCase->expr' => [null, false, ' = ', null], + 'Expr_PrintableNewAnonClass->extends' => [null, ' extends ', null], + 'Stmt_Continue->num' => [\T_CONTINUE, false, ' ', null], + 'Stmt_Foreach->keyVar' => [\T_AS, false, null, ' => '], + 'Stmt_Function->returnType' => [')', false, ' : ', null], + 'Stmt_If->else' => [null, false, ' ', null], + 'Stmt_Namespace->name' => [\T_NAMESPACE, false, ' ', null], + 'Stmt_Property->type' => [\T_VARIABLE, true, null, ' '], + 'Stmt_PropertyProperty->default' => [null, false, ' = ', null], + 'Stmt_Return->expr' => [\T_RETURN, false, ' ', null], + 'Stmt_StaticVar->default' => [null, false, ' = ', null], + //'Stmt_TraitUseAdaptation_Alias->newName' => [T_AS, false, ' ', null], // TODO + 'Stmt_TryCatch->finally' => [null, false, ' ', null], + + // 'Expr_Exit->expr': Complicated due to optional () + // 'Stmt_Case->cond': Conversion from default to case + // 'Stmt_Class->name': Unclear + // 'Stmt_Declare->stmts': Not a proper node + // 'Stmt_TraitUseAdaptation_Alias->newModifier': Not a proper node + ]; + } + + protected function initializeListInsertionMap() { + if ($this->listInsertionMap) return; + + $this->listInsertionMap = [ + // special + //'Expr_ShellExec->parts' => '', // TODO These need to be treated more carefully + //'Scalar_Encapsed->parts' => '', + 'Stmt_Catch->types' => '|', + 'UnionType->types' => '|', + 'IntersectionType->types' => '&', + 'Stmt_If->elseifs' => ' ', + 'Stmt_TryCatch->catches' => ' ', + + // comma-separated lists + 'Expr_Array->items' => ', ', + 'Expr_ArrowFunction->params' => ', ', + 'Expr_Closure->params' => ', ', + 'Expr_Closure->uses' => ', ', + 'Expr_FuncCall->args' => ', ', + 'Expr_Isset->vars' => ', ', + 'Expr_List->items' => ', ', + 'Expr_MethodCall->args' => ', ', + 'Expr_NullsafeMethodCall->args' => ', ', + 'Expr_New->args' => ', ', + 'Expr_PrintableNewAnonClass->args' => ', ', + 'Expr_StaticCall->args' => ', ', + 'Stmt_ClassConst->consts' => ', ', + 'Stmt_ClassMethod->params' => ', ', + 'Stmt_Class->implements' => ', ', + 'Stmt_Enum->implements' => ', ', + 'Expr_PrintableNewAnonClass->implements' => ', ', + 'Stmt_Const->consts' => ', ', + 'Stmt_Declare->declares' => ', ', + 'Stmt_Echo->exprs' => ', ', + 'Stmt_For->init' => ', ', + 'Stmt_For->cond' => ', ', + 'Stmt_For->loop' => ', ', + 'Stmt_Function->params' => ', ', + 'Stmt_Global->vars' => ', ', + 'Stmt_GroupUse->uses' => ', ', + 'Stmt_Interface->extends' => ', ', + 'Stmt_Match->arms' => ', ', + 'Stmt_Property->props' => ', ', + 'Stmt_StaticVar->vars' => ', ', + 'Stmt_TraitUse->traits' => ', ', + 'Stmt_TraitUseAdaptation_Precedence->insteadof' => ', ', + 'Stmt_Unset->vars' => ', ', + 'Stmt_Use->uses' => ', ', + 'MatchArm->conds' => ', ', + 'AttributeGroup->attrs' => ', ', + + // statement lists + 'Expr_Closure->stmts' => "\n", + 'Stmt_Case->stmts' => "\n", + 'Stmt_Catch->stmts' => "\n", + 'Stmt_Class->stmts' => "\n", + 'Stmt_Enum->stmts' => "\n", + 'Expr_PrintableNewAnonClass->stmts' => "\n", + 'Stmt_Interface->stmts' => "\n", + 'Stmt_Trait->stmts' => "\n", + 'Stmt_ClassMethod->stmts' => "\n", + 'Stmt_Declare->stmts' => "\n", + 'Stmt_Do->stmts' => "\n", + 'Stmt_ElseIf->stmts' => "\n", + 'Stmt_Else->stmts' => "\n", + 'Stmt_Finally->stmts' => "\n", + 'Stmt_Foreach->stmts' => "\n", + 'Stmt_For->stmts' => "\n", + 'Stmt_Function->stmts' => "\n", + 'Stmt_If->stmts' => "\n", + 'Stmt_Namespace->stmts' => "\n", + 'Stmt_Class->attrGroups' => "\n", + 'Stmt_Enum->attrGroups' => "\n", + 'Stmt_EnumCase->attrGroups' => "\n", + 'Stmt_Interface->attrGroups' => "\n", + 'Stmt_Trait->attrGroups' => "\n", + 'Stmt_Function->attrGroups' => "\n", + 'Stmt_ClassMethod->attrGroups' => "\n", + 'Stmt_ClassConst->attrGroups' => "\n", + 'Stmt_Property->attrGroups' => "\n", + 'Expr_PrintableNewAnonClass->attrGroups' => ' ', + 'Expr_Closure->attrGroups' => ' ', + 'Expr_ArrowFunction->attrGroups' => ' ', + 'Param->attrGroups' => ' ', + 'Stmt_Switch->cases' => "\n", + 'Stmt_TraitUse->adaptations' => "\n", + 'Stmt_TryCatch->stmts' => "\n", + 'Stmt_While->stmts' => "\n", + + // dummy for top-level context + 'File->stmts' => "\n", + ]; + } + + protected function initializeEmptyListInsertionMap() { + if ($this->emptyListInsertionMap) return; + + // TODO Insertion into empty statement lists. + + // [$find, $extraLeft, $extraRight] + $this->emptyListInsertionMap = [ + 'Expr_ArrowFunction->params' => ['(', '', ''], + 'Expr_Closure->uses' => [')', ' use(', ')'], + 'Expr_Closure->params' => ['(', '', ''], + 'Expr_FuncCall->args' => ['(', '', ''], + 'Expr_MethodCall->args' => ['(', '', ''], + 'Expr_NullsafeMethodCall->args' => ['(', '', ''], + 'Expr_New->args' => ['(', '', ''], + 'Expr_PrintableNewAnonClass->args' => ['(', '', ''], + 'Expr_PrintableNewAnonClass->implements' => [null, ' implements ', ''], + 'Expr_StaticCall->args' => ['(', '', ''], + 'Stmt_Class->implements' => [null, ' implements ', ''], + 'Stmt_Enum->implements' => [null, ' implements ', ''], + 'Stmt_ClassMethod->params' => ['(', '', ''], + 'Stmt_Interface->extends' => [null, ' extends ', ''], + 'Stmt_Function->params' => ['(', '', ''], + + /* These cannot be empty to start with: + * Expr_Isset->vars + * Stmt_Catch->types + * Stmt_Const->consts + * Stmt_ClassConst->consts + * Stmt_Declare->declares + * Stmt_Echo->exprs + * Stmt_Global->vars + * Stmt_GroupUse->uses + * Stmt_Property->props + * Stmt_StaticVar->vars + * Stmt_TraitUse->traits + * Stmt_TraitUseAdaptation_Precedence->insteadof + * Stmt_Unset->vars + * Stmt_Use->uses + * UnionType->types + */ + + /* TODO + * Stmt_If->elseifs + * Stmt_TryCatch->catches + * Expr_Array->items + * Expr_List->items + * Stmt_For->init + * Stmt_For->cond + * Stmt_For->loop + */ + ]; + } + + protected function initializeModifierChangeMap() { + if ($this->modifierChangeMap) return; + + $this->modifierChangeMap = [ + 'Stmt_ClassConst->flags' => \T_CONST, + 'Stmt_ClassMethod->flags' => \T_FUNCTION, + 'Stmt_Class->flags' => \T_CLASS, + 'Stmt_Property->flags' => \T_VARIABLE, + 'Param->flags' => \T_VARIABLE, + //'Stmt_TraitUseAdaptation_Alias->newModifier' => 0, // TODO + ]; + + // List of integer subnodes that are not modifiers: + // Expr_Include->type + // Stmt_GroupUse->type + // Stmt_Use->type + // Stmt_UseUse->type + } +} diff --git a/vendor/pear/console_table/.travis.yml b/vendor/pear/console_table/.travis.yml new file mode 100644 index 0000000000..46d986dc9e --- /dev/null +++ b/vendor/pear/console_table/.travis.yml @@ -0,0 +1,11 @@ +language: php +install: + - pear install Console_Color2-alpha +php: + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - 7.1 + - 7.2 +script: cd tests && pear run-tests . diff --git a/vendor/pear/console_table/Table.php b/vendor/pear/console_table/Table.php new file mode 100755 index 0000000000..dbfec73f1b --- /dev/null +++ b/vendor/pear/console_table/Table.php @@ -0,0 +1,978 @@ + + * @author Jan Schneider + * @copyright 2002-2005 Richard Heyes + * @copyright 2006-2008 Jan Schneider + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Console_Table + */ + +define('CONSOLE_TABLE_HORIZONTAL_RULE', 1); +define('CONSOLE_TABLE_ALIGN_LEFT', -1); +define('CONSOLE_TABLE_ALIGN_CENTER', 0); +define('CONSOLE_TABLE_ALIGN_RIGHT', 1); +define('CONSOLE_TABLE_BORDER_ASCII', -1); + +/** + * The main class. + * + * @category Console + * @package Console_Table + * @author Jan Schneider + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @link http://pear.php.net/package/Console_Table + */ +class Console_Table +{ + /** + * The table headers. + * + * @var array + */ + var $_headers = array(); + + /** + * The data of the table. + * + * @var array + */ + var $_data = array(); + + /** + * The maximum number of columns in a row. + * + * @var integer + */ + var $_max_cols = 0; + + /** + * The maximum number of rows in the table. + * + * @var integer + */ + var $_max_rows = 0; + + /** + * Lengths of the columns, calculated when rows are added to the table. + * + * @var array + */ + var $_cell_lengths = array(); + + /** + * Heights of the rows. + * + * @var array + */ + var $_row_heights = array(); + + /** + * How many spaces to use to pad the table. + * + * @var integer + */ + var $_padding = 1; + + /** + * Column filters. + * + * @var array + */ + var $_filters = array(); + + /** + * Columns to calculate totals for. + * + * @var array + */ + var $_calculateTotals; + + /** + * Alignment of the columns. + * + * @var array + */ + var $_col_align = array(); + + /** + * Default alignment of columns. + * + * @var integer + */ + var $_defaultAlign; + + /** + * Character set of the data. + * + * @var string + */ + var $_charset = 'utf-8'; + + /** + * Border characters. + * Allowed keys: + * - intersection - intersection ("+") + * - horizontal - horizontal rule character ("-") + * - vertical - vertical rule character ("|") + * + * @var array + */ + var $_border = array( + 'intersection' => '+', + 'horizontal' => '-', + 'vertical' => '|', + ); + + /** + * If borders are shown or not + * Allowed keys: top, right, bottom, left, inner: true and false + * + * @var array + */ + var $_borderVisibility = array( + 'top' => true, + 'right' => true, + 'bottom' => true, + 'left' => true, + 'inner' => true + ); + + /** + * Whether the data has ANSI colors. + * + * @var Console_Color2 + */ + var $_ansiColor = false; + + /** + * Constructor. + * + * @param integer $align Default alignment. One of + * CONSOLE_TABLE_ALIGN_LEFT, + * CONSOLE_TABLE_ALIGN_CENTER or + * CONSOLE_TABLE_ALIGN_RIGHT. + * @param string $border The character used for table borders or + * CONSOLE_TABLE_BORDER_ASCII. + * @param integer $padding How many spaces to use to pad the table. + * @param string $charset A charset supported by the mbstring PHP + * extension. + * @param boolean $color Whether the data contains ansi color codes. + */ + function __construct($align = CONSOLE_TABLE_ALIGN_LEFT, + $border = CONSOLE_TABLE_BORDER_ASCII, $padding = 1, + $charset = null, $color = false) + { + $this->_defaultAlign = $align; + $this->setBorder($border); + $this->_padding = $padding; + if ($color) { + if (!class_exists('Console_Color2')) { + include_once 'Console/Color2.php'; + } + $this->_ansiColor = new Console_Color2(); + } + if (!empty($charset)) { + $this->setCharset($charset); + } + } + + /** + * Converts an array to a table. + * + * @param array $headers Headers for the table. + * @param array $data A two dimensional array with the table + * data. + * @param boolean $returnObject Whether to return the Console_Table object + * instead of the rendered table. + * + * @static + * + * @return Console_Table|string A Console_Table object or the generated + * table. + */ + function fromArray($headers, $data, $returnObject = false) + { + if (!is_array($headers) || !is_array($data)) { + return false; + } + + $table = new Console_Table(); + $table->setHeaders($headers); + + foreach ($data as $row) { + $table->addRow($row); + } + + return $returnObject ? $table : $table->getTable(); + } + + /** + * Adds a filter to a column. + * + * Filters are standard PHP callbacks which are run on the data before + * table generation is performed. Filters are applied in the order they + * are added. The callback function must accept a single argument, which + * is a single table cell. + * + * @param integer $col Column to apply filter to. + * @param mixed &$callback PHP callback to apply. + * + * @return void + */ + function addFilter($col, &$callback) + { + $this->_filters[] = array($col, &$callback); + } + + /** + * Sets the charset of the provided table data. + * + * @param string $charset A charset supported by the mbstring PHP + * extension. + * + * @return void + */ + function setCharset($charset) + { + $locale = setlocale(LC_CTYPE, 0); + setlocale(LC_CTYPE, 'en_US'); + $this->_charset = strtolower($charset); + setlocale(LC_CTYPE, $locale); + } + + /** + * Set the table border settings + * + * Border definition modes: + * - CONSOLE_TABLE_BORDER_ASCII: Default border with +, - and | + * - array with keys "intersection", "horizontal" and "vertical" + * - single character string that sets all three of the array keys + * + * @param mixed $border Border definition + * + * @return void + * @see $_border + */ + function setBorder($border) + { + if ($border === CONSOLE_TABLE_BORDER_ASCII) { + $intersection = '+'; + $horizontal = '-'; + $vertical = '|'; + } else if (is_string($border)) { + $intersection = $horizontal = $vertical = $border; + } else if ($border == '') { + $intersection = $horizontal = $vertical = ''; + } else { + extract($border); + } + + $this->_border = array( + 'intersection' => $intersection, + 'horizontal' => $horizontal, + 'vertical' => $vertical, + ); + } + + /** + * Set which borders shall be shown. + * + * @param array $visibility Visibility settings. + * Allowed keys: left, right, top, bottom, inner + * + * @return void + * @see $_borderVisibility + */ + function setBorderVisibility($visibility) + { + $this->_borderVisibility = array_merge( + $this->_borderVisibility, + array_intersect_key( + $visibility, + $this->_borderVisibility + ) + ); + } + + /** + * Sets the alignment for the columns. + * + * @param integer $col_id The column number. + * @param integer $align Alignment to set for this column. One of + * CONSOLE_TABLE_ALIGN_LEFT + * CONSOLE_TABLE_ALIGN_CENTER + * CONSOLE_TABLE_ALIGN_RIGHT. + * + * @return void + */ + function setAlign($col_id, $align = CONSOLE_TABLE_ALIGN_LEFT) + { + switch ($align) { + case CONSOLE_TABLE_ALIGN_CENTER: + $pad = STR_PAD_BOTH; + break; + case CONSOLE_TABLE_ALIGN_RIGHT: + $pad = STR_PAD_LEFT; + break; + default: + $pad = STR_PAD_RIGHT; + break; + } + $this->_col_align[$col_id] = $pad; + } + + /** + * Specifies which columns are to have totals calculated for them and + * added as a new row at the bottom. + * + * @param array $cols Array of column numbers (starting with 0). + * + * @return void + */ + function calculateTotalsFor($cols) + { + $this->_calculateTotals = $cols; + } + + /** + * Sets the headers for the columns. + * + * @param array $headers The column headers. + * + * @return void + */ + function setHeaders($headers) + { + $this->_headers = array(array_values($headers)); + $this->_updateRowsCols($headers); + } + + /** + * Adds a row to the table. + * + * @param array $row The row data to add. + * @param boolean $append Whether to append or prepend the row. + * + * @return void + */ + function addRow($row, $append = true) + { + if ($append) { + $this->_data[] = array_values($row); + } else { + array_unshift($this->_data, array_values($row)); + } + + $this->_updateRowsCols($row); + } + + /** + * Inserts a row after a given row number in the table. + * + * If $row_id is not given it will prepend the row. + * + * @param array $row The data to insert. + * @param integer $row_id Row number to insert before. + * + * @return void + */ + function insertRow($row, $row_id = 0) + { + array_splice($this->_data, $row_id, 0, array($row)); + + $this->_updateRowsCols($row); + } + + /** + * Adds a column to the table. + * + * @param array $col_data The data of the column. + * @param integer $col_id The column index to populate. + * @param integer $row_id If starting row is not zero, specify it here. + * + * @return void + */ + function addCol($col_data, $col_id = 0, $row_id = 0) + { + foreach ($col_data as $col_cell) { + $this->_data[$row_id++][$col_id] = $col_cell; + } + + $this->_updateRowsCols(); + $this->_max_cols = max($this->_max_cols, $col_id + 1); + } + + /** + * Adds data to the table. + * + * @param array $data A two dimensional array with the table data. + * @param integer $col_id Starting column number. + * @param integer $row_id Starting row number. + * + * @return void + */ + function addData($data, $col_id = 0, $row_id = 0) + { + foreach ($data as $row) { + if ($row === CONSOLE_TABLE_HORIZONTAL_RULE) { + $this->_data[$row_id] = CONSOLE_TABLE_HORIZONTAL_RULE; + $row_id++; + continue; + } + $starting_col = $col_id; + foreach ($row as $cell) { + $this->_data[$row_id][$starting_col++] = $cell; + } + $this->_updateRowsCols(); + $this->_max_cols = max($this->_max_cols, $starting_col); + $row_id++; + } + } + + /** + * Adds a horizontal seperator to the table. + * + * @return void + */ + function addSeparator() + { + $this->_data[] = CONSOLE_TABLE_HORIZONTAL_RULE; + } + + /** + * Returns the generated table. + * + * @return string The generated table. + */ + function getTable() + { + $this->_applyFilters(); + $this->_calculateTotals(); + $this->_validateTable(); + + return $this->_buildTable(); + } + + /** + * Calculates totals for columns. + * + * @return void + */ + function _calculateTotals() + { + if (empty($this->_calculateTotals)) { + return; + } + + $this->addSeparator(); + + $totals = array(); + foreach ($this->_data as $row) { + if (is_array($row)) { + foreach ($this->_calculateTotals as $columnID) { + $totals[$columnID] += $row[$columnID]; + } + } + } + + $this->_data[] = $totals; + $this->_updateRowsCols(); + } + + /** + * Applies any column filters to the data. + * + * @return void + */ + function _applyFilters() + { + if (empty($this->_filters)) { + return; + } + + foreach ($this->_filters as $filter) { + $column = $filter[0]; + $callback = $filter[1]; + + foreach ($this->_data as $row_id => $row_data) { + if ($row_data !== CONSOLE_TABLE_HORIZONTAL_RULE) { + $this->_data[$row_id][$column] = + call_user_func($callback, $row_data[$column]); + } + } + } + } + + /** + * Ensures that column and row counts are correct. + * + * @return void + */ + function _validateTable() + { + if (!empty($this->_headers)) { + $this->_calculateRowHeight(-1, $this->_headers[0]); + } + + for ($i = 0; $i < $this->_max_rows; $i++) { + for ($j = 0; $j < $this->_max_cols; $j++) { + if (!isset($this->_data[$i][$j]) && + (!isset($this->_data[$i]) || + $this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE)) { + $this->_data[$i][$j] = ''; + } + + } + $this->_calculateRowHeight($i, $this->_data[$i]); + + if ($this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE) { + ksort($this->_data[$i]); + } + + } + + $this->_splitMultilineRows(); + + // Update cell lengths. + for ($i = 0; $i < count($this->_headers); $i++) { + $this->_calculateCellLengths($this->_headers[$i]); + } + for ($i = 0; $i < $this->_max_rows; $i++) { + $this->_calculateCellLengths($this->_data[$i]); + } + + ksort($this->_data); + } + + /** + * Splits multiline rows into many smaller one-line rows. + * + * @return void + */ + function _splitMultilineRows() + { + ksort($this->_data); + $sections = array(&$this->_headers, &$this->_data); + $max_rows = array(count($this->_headers), $this->_max_rows); + $row_height_offset = array(-1, 0); + + for ($s = 0; $s <= 1; $s++) { + $inserted = 0; + $new_data = $sections[$s]; + + for ($i = 0; $i < $max_rows[$s]; $i++) { + // Process only rows that have many lines. + $height = $this->_row_heights[$i + $row_height_offset[$s]]; + if ($height > 1) { + // Split column data into one-liners. + $split = array(); + for ($j = 0; $j < $this->_max_cols; $j++) { + $split[$j] = preg_split('/\r?\n|\r/', + $sections[$s][$i][$j]); + } + + $new_rows = array(); + // Construct new 'virtual' rows - insert empty strings for + // columns that have less lines that the highest one. + for ($i2 = 0; $i2 < $height; $i2++) { + for ($j = 0; $j < $this->_max_cols; $j++) { + $new_rows[$i2][$j] = !isset($split[$j][$i2]) + ? '' + : $split[$j][$i2]; + } + } + + // Replace current row with smaller rows. $inserted is + // used to take account of bigger array because of already + // inserted rows. + array_splice($new_data, $i + $inserted, 1, $new_rows); + $inserted += count($new_rows) - 1; + } + } + + // Has the data been modified? + if ($inserted > 0) { + $sections[$s] = $new_data; + $this->_updateRowsCols(); + } + } + } + + /** + * Builds the table. + * + * @return string The generated table string. + */ + function _buildTable() + { + if (!count($this->_data)) { + return ''; + } + + $vertical = $this->_border['vertical']; + $separator = $this->_getSeparator(); + + $return = array(); + for ($i = 0; $i < count($this->_data); $i++) { + if (is_array($this->_data[$i])) { + for ($j = 0; $j < count($this->_data[$i]); $j++) { + if ($this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE && + $this->_strlen($this->_data[$i][$j]) < + $this->_cell_lengths[$j]) { + $this->_data[$i][$j] = $this->_strpad($this->_data[$i][$j], + $this->_cell_lengths[$j], + ' ', + $this->_col_align[$j]); + } + } + } + + if ($this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE) { + $row_begin = $this->_borderVisibility['left'] + ? $vertical . str_repeat(' ', $this->_padding) + : ''; + $row_end = $this->_borderVisibility['right'] + ? str_repeat(' ', $this->_padding) . $vertical + : ''; + $implode_char = str_repeat(' ', $this->_padding) . $vertical + . str_repeat(' ', $this->_padding); + $return[] = $row_begin + . implode($implode_char, $this->_data[$i]) . $row_end; + } elseif (!empty($separator)) { + $return[] = $separator; + } + + } + + $return = implode(PHP_EOL, $return); + if (!empty($separator)) { + if ($this->_borderVisibility['inner']) { + $return = $separator . PHP_EOL . $return; + } + if ($this->_borderVisibility['bottom']) { + $return .= PHP_EOL . $separator; + } + } + $return .= PHP_EOL; + + if (!empty($this->_headers)) { + $return = $this->_getHeaderLine() . PHP_EOL . $return; + } + + return $return; + } + + /** + * Creates a horizontal separator for header separation and table + * start/end etc. + * + * @return string The horizontal separator. + */ + function _getSeparator() + { + if (!$this->_border) { + return; + } + + $horizontal = $this->_border['horizontal']; + $intersection = $this->_border['intersection']; + + $return = array(); + foreach ($this->_cell_lengths as $cl) { + $return[] = str_repeat($horizontal, $cl); + } + + $row_begin = $this->_borderVisibility['left'] + ? $intersection . str_repeat($horizontal, $this->_padding) + : ''; + $row_end = $this->_borderVisibility['right'] + ? str_repeat($horizontal, $this->_padding) . $intersection + : ''; + $implode_char = str_repeat($horizontal, $this->_padding) . $intersection + . str_repeat($horizontal, $this->_padding); + + return $row_begin . implode($implode_char, $return) . $row_end; + } + + /** + * Returns the header line for the table. + * + * @return string The header line of the table. + */ + function _getHeaderLine() + { + // Make sure column count is correct + for ($j = 0; $j < count($this->_headers); $j++) { + for ($i = 0; $i < $this->_max_cols; $i++) { + if (!isset($this->_headers[$j][$i])) { + $this->_headers[$j][$i] = ''; + } + } + } + + for ($j = 0; $j < count($this->_headers); $j++) { + for ($i = 0; $i < count($this->_headers[$j]); $i++) { + if ($this->_strlen($this->_headers[$j][$i]) < + $this->_cell_lengths[$i]) { + $this->_headers[$j][$i] = + $this->_strpad($this->_headers[$j][$i], + $this->_cell_lengths[$i], + ' ', + $this->_col_align[$i]); + } + } + } + + $vertical = $this->_border['vertical']; + $row_begin = $this->_borderVisibility['left'] + ? $vertical . str_repeat(' ', $this->_padding) + : ''; + $row_end = $this->_borderVisibility['right'] + ? str_repeat(' ', $this->_padding) . $vertical + : ''; + $implode_char = str_repeat(' ', $this->_padding) . $vertical + . str_repeat(' ', $this->_padding); + + $separator = $this->_getSeparator(); + if (!empty($separator) && $this->_borderVisibility['top']) { + $return[] = $separator; + } + for ($j = 0; $j < count($this->_headers); $j++) { + $return[] = $row_begin + . implode($implode_char, $this->_headers[$j]) . $row_end; + } + + return implode(PHP_EOL, $return); + } + + /** + * Updates values for maximum columns and rows. + * + * @param array $rowdata Data array of a single row. + * + * @return void + */ + function _updateRowsCols($rowdata = null) + { + // Update maximum columns. + $this->_max_cols = max($this->_max_cols, is_array($rowdata) ? count($rowdata) : 0); + + // Update maximum rows. + ksort($this->_data); + $keys = array_keys($this->_data); + $this->_max_rows = end($keys) + 1; + + switch ($this->_defaultAlign) { + case CONSOLE_TABLE_ALIGN_CENTER: + $pad = STR_PAD_BOTH; + break; + case CONSOLE_TABLE_ALIGN_RIGHT: + $pad = STR_PAD_LEFT; + break; + default: + $pad = STR_PAD_RIGHT; + break; + } + + // Set default column alignments + for ($i = 0; $i < $this->_max_cols; $i++) { + if (!isset($this->_col_align[$i])) { + $this->_col_align[$i] = $pad; + } + } + } + + /** + * Calculates the maximum length for each column of a row. + * + * @param array $row The row data. + * + * @return void + */ + function _calculateCellLengths($row) + { + if (is_array($row)) { + for ($i = 0; $i < count($row); $i++) { + if (!isset($this->_cell_lengths[$i])) { + $this->_cell_lengths[$i] = 0; + } + $this->_cell_lengths[$i] = max($this->_cell_lengths[$i], + $this->_strlen($row[$i])); + } + } + } + + /** + * Calculates the maximum height for all columns of a row. + * + * @param integer $row_number The row number. + * @param array $row The row data. + * + * @return void + */ + function _calculateRowHeight($row_number, $row) + { + if (!isset($this->_row_heights[$row_number])) { + $this->_row_heights[$row_number] = 1; + } + + // Do not process horizontal rule rows. + if ($row === CONSOLE_TABLE_HORIZONTAL_RULE) { + return; + } + + for ($i = 0, $c = count($row); $i < $c; ++$i) { + $lines = preg_split('/\r?\n|\r/', $row[$i]); + $this->_row_heights[$row_number] = max($this->_row_heights[$row_number], + count($lines)); + } + } + + /** + * Returns the character length of a string. + * + * @param string $str A multibyte or singlebyte string. + * + * @return integer The string length. + */ + function _strlen($str) + { + static $mbstring; + + // Strip ANSI color codes if requested. + if ($this->_ansiColor) { + $str = $this->_ansiColor->strip($str); + } + + // Cache expensive function_exists() calls. + if (!isset($mbstring)) { + $mbstring = function_exists('mb_strwidth'); + } + + if ($mbstring) { + return mb_strwidth($str, $this->_charset); + } + + return strlen($str); + } + + /** + * Returns part of a string. + * + * @param string $string The string to be converted. + * @param integer $start The part's start position, zero based. + * @param integer $length The part's length. + * + * @return string The string's part. + */ + function _substr($string, $start, $length = null) + { + static $mbstring; + + // Cache expensive function_exists() calls. + if (!isset($mbstring)) { + $mbstring = function_exists('mb_substr'); + } + + if (is_null($length)) { + $length = $this->_strlen($string); + } + if ($mbstring) { + $ret = @mb_substr($string, $start, $length, $this->_charset); + if (!empty($ret)) { + return $ret; + } + } + return substr($string, $start, $length); + } + + /** + * Returns a string padded to a certain length with another string. + * + * This method behaves exactly like str_pad but is multibyte safe. + * + * @param string $input The string to be padded. + * @param integer $length The length of the resulting string. + * @param string $pad The string to pad the input string with. Must + * be in the same charset like the input string. + * @param const $type The padding type. One of STR_PAD_LEFT, + * STR_PAD_RIGHT, or STR_PAD_BOTH. + * + * @return string The padded string. + */ + function _strpad($input, $length, $pad = ' ', $type = STR_PAD_RIGHT) + { + $mb_length = $this->_strlen($input); + $sb_length = strlen($input); + $pad_length = $this->_strlen($pad); + + /* Return if we already have the length. */ + if ($mb_length >= $length) { + return $input; + } + + /* Shortcut for single byte strings. */ + if ($mb_length == $sb_length && $pad_length == strlen($pad)) { + return str_pad($input, $length, $pad, $type); + } + + switch ($type) { + case STR_PAD_LEFT: + $left = $length - $mb_length; + $output = $this->_substr(str_repeat($pad, ceil($left / $pad_length)), + 0, $left, $this->_charset) . $input; + break; + case STR_PAD_BOTH: + $left = floor(($length - $mb_length) / 2); + $right = ceil(($length - $mb_length) / 2); + $output = $this->_substr(str_repeat($pad, ceil($left / $pad_length)), + 0, $left, $this->_charset) . + $input . + $this->_substr(str_repeat($pad, ceil($right / $pad_length)), + 0, $right, $this->_charset); + break; + case STR_PAD_RIGHT: + $right = $length - $mb_length; + $output = $input . + $this->_substr(str_repeat($pad, ceil($right / $pad_length)), + 0, $right, $this->_charset); + break; + } + + return $output; + } + +} diff --git a/vendor/pear/console_table/composer.json b/vendor/pear/console_table/composer.json new file mode 100644 index 0000000000..cfc37a3387 --- /dev/null +++ b/vendor/pear/console_table/composer.json @@ -0,0 +1,43 @@ +{ + "name": "pear/console_table", + "type": "library", + "description": "Library that makes it easy to build console style tables.", + "keywords": [ + "console" + ], + "homepage": "http://pear.php.net/package/Console_Table/", + "license": "BSD-2-Clause", + "authors": [ + { + "name": "Jan Schneider", + "homepage": "http://pear.php.net/user/yunosh" + }, + { + "name": "Tal Peer", + "homepage": "http://pear.php.net/user/tal" + }, + { + "name": "Xavier Noguer", + "homepage": "http://pear.php.net/user/xnoguer" + }, + { + "name": "Richard Heyes", + "homepage": "http://pear.php.net/user/richard" + } + ], + "require": { + "php": ">=5.2.0" + }, + "suggest": { + "pear/Console_Color2": ">=0.1.2" + }, + "autoload": { + "classmap": [ + "Table.php" + ] + }, + "support": { + "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Console_Table", + "source": "https://github.com/pear/Console_Table" + } +} diff --git a/vendor/pear/console_table/package.xml b/vendor/pear/console_table/package.xml new file mode 100644 index 0000000000..2fef9dff8c --- /dev/null +++ b/vendor/pear/console_table/package.xml @@ -0,0 +1,416 @@ + + + Console_Table + pear.php.net + Library that makes it easy to build console style tables + Provides a Console_Table class with methods such as addRow(), insertRow(), addCol() etc. to build console tables with or without headers and with user defined table rules, padding, and alignment. + + Jan Schneider + yunosh + jan@horde.org + yes + + + Richard Heyes + richard + richard@phpguru.org + no + + + Tal Peer + tal + tal@php.net + no + + + Xavier Noguer + xnoguer + xnoguer@php.net + no + + 2018-01-25 + + 1.3.1 + 1.3.0 + + + stable + stable + + BSD + +* Fix warning with PHP 7.2 when passing non-array data (Remi Collet <remi@remirepo.net>, PR #14). + + + + + + + + + + + + + + + + + + + + + + + + + + + 5.2.0 + + + 1.4.0b1 + + + + + Console_Color2 + pear.php.net + 0.1.2 + + + + + + + + 0.8 + 0.8 + + + beta + beta + + 2002-09-02 + BSD + +* Initial release. + + + + + 1.0 + 1.0.0 + + + stable + stable + + 2003-01-24 + BSD + +* Removed a few options and added addData() method. + + + + + 1.0.1 + 1.0.0 + + + stable + stable + + 2003-03-03 + BSD + +* Fixed a caching bug. + + + + + 1.0.2 + 1.0.0 + + + stable + stable + + 2005-07-16 + BSD + +* Added support for column alignment (Michael Richter). + + + + + 1.0.3 + 1.0.0 + + + stable + stable + + 2006-03-13 + BSD + +* Fix cell width calculation if setting header with associative array (Bug #4299). +* Fix fatal reference error with some PHP versions (Bug #5309). +* Fix notice if no data has been provided (Bug #5851). +* Added multibyte support (Requests #2934, Request #7014). + + + + + 1.0.4 + 1.0.0 + + + stable + stable + + 2006-04-08 + BSD + +* Add support for multi-line cells (koto at webworkers dot pl, Request #7017). + + + + + 1.0.5 + 1.0.0 + + + stable + stable + + 2006-08-28 + BSD + +* Allow to specify separator rules in addData(). +* Fix warnings when combining separator rules and callback filters (Bug #8566). + + + + + 1.0.6 + 1.0.0 + + + stable + stable + + 2007-01-19 + BSD + +* Add support for multi-line headers (Request #8615). + + + + 2007-05-17 + + 1.0.7 + 1.0.0 + + + stable + stable + + BSD + +* Fix header height if first data row has more than one line (Bug #11064). +* Fix notice if header is not set. + + + + 2008-01-09 + + 1.0.8 + 1.0.0 + + + stable + stable + + BSD + +* Fix cell padding with multibyte strings under certain circumstances (Bug #12853). + + + + 2008-03-28 + + 1.1.0 + 1.1.0 + + + stable + stable + + BSD + +* Add option to set table border character. +* Extend constructor to set table borders, padding, and charset on instantiation. + + + + 2008-04-09 + + 1.1.1 + 1.1.0 + + + stable + stable + + BSD + +* Fix rendering of multiline rows with cells that contain zeros (Bug #13629). + + + + 2008-07-27 + + 1.1.2 + 1.1.0 + + + stable + stable + + BSD + +* Don't render anything if no data has been provided (Bug #14405). + + + + 2008-10-20 + + 1.1.3 + 1.1.1 + + + stable + stable + + BSD + +* Add option to render data with ANSI color codes (Igor Feghali, Request #14835). + + + + + 1.1.4 + 1.1.1 + + + stable + stable + + 2010-10-25 + BSD + +* Automatically built QA release. +* Add Console_Color support (Request #14835). + +* Improve documentation (Christian Weiske, Bug #15006). + + + + 2012-12-07 + + + 1.1.5 + 1.1.1 + + + stable + stable + + BSD + +* Use mb_strwidth() instead of mb_strlen() to determine lengths of multi-byte strings (Bug #19423). + + + + 2013-10-12 + + 1.1.6 + 1.1.1 + + + stable + stable + + BSD + +* Use line breaks dependent on the current operating system (Bug #20092). + + + + 2014-02-17 + + 1.2.0 + 1.2.0 + + + stable + stable + + BSD + +* Make border visibility configurable (Christian Weiske, Request #20186). +* Allow to customize all border characters (Christian Weiske, Request #20182). +* Fix notice when using setAlign() on other than first column (Christian Weiske, Bug #20181). +* Use Console_Color2 to avoid notices from PHP 4 code (Christian Weiske, Bug #20188) + + + + 2014-10-27 + + 1.2.1 + 1.2.0 + + + stable + stable + + BSD + +* Add travis configuration (Christian Weiske). +* Try to autoload Console_Color2 first (Jurgen Rutten, PR #11). +* Fix Composer definition syntax (Rob Loach, PR #9). + + + + 2016-01-21 + + 1.3.0 + 1.3.0 + + + stable + stable + + BSD + +* Fix warning with PHP 7 and bump required PHP version to 5.2.0 (Pieter Frenssen PR #13). + + + + 2018-01-25 + + 1.3.1 + 1.3.0 + + + stable + stable + + BSD + +* Fix warning with PHP 7.2 when passing non-array data (Remi Collet <remi@remirepo.net>, PR #14). + + + + diff --git a/vendor/pear/console_table/tests/assoziative_arrays.phpt b/vendor/pear/console_table/tests/assoziative_arrays.phpt new file mode 100644 index 0000000000..91bc49b753 --- /dev/null +++ b/vendor/pear/console_table/tests/assoziative_arrays.phpt @@ -0,0 +1,35 @@ +--TEST-- +Header and data as associative arrays. +--FILE-- + 'foo', + 'two' => 'bar' +); + +$data = array( + array( + 'x' => 'baz', + ) +); + +$table = new Console_Table(); +$table->setHeaders($headers); +$table->addData($data); + +echo $table->getTable(); + +?> +--EXPECT-- ++-----+-----+ +| foo | bar | ++-----+-----+ +| baz | | ++-----+-----+ diff --git a/vendor/pear/console_table/tests/border-ascii.phpt b/vendor/pear/console_table/tests/border-ascii.phpt new file mode 100644 index 0000000000..e8ca63100b --- /dev/null +++ b/vendor/pear/console_table/tests/border-ascii.phpt @@ -0,0 +1,24 @@ +--TEST-- +Border: default ASCII mode +--FILE-- +setHeaders(array('City', 'Mayor')); +$table->addRow(array('Leipzig', 'Major Tom')); +$table->addRow(array('New York', 'Towerhouse')); + +echo $table->getTable(); +?> +--EXPECT-- ++----------+------------+ +| City | Mayor | ++----------+------------+ +| Leipzig | Major Tom | +| New York | Towerhouse | ++----------+------------+ diff --git a/vendor/pear/console_table/tests/border-custom.phpt b/vendor/pear/console_table/tests/border-custom.phpt new file mode 100644 index 0000000000..62be737a84 --- /dev/null +++ b/vendor/pear/console_table/tests/border-custom.phpt @@ -0,0 +1,27 @@ +--TEST-- +Border: new custom mode +--FILE-- + '=', 'vertical' => ':', 'intersection' => '*') +); +$table->setHeaders(array('City', 'Mayor')); +$table->addRow(array('Leipzig', 'Major Tom')); +$table->addRow(array('New York', 'Towerhouse')); + +echo $table->getTable(); +?> +--EXPECT-- +*==========*============* +: City : Mayor : +*==========*============* +: Leipzig : Major Tom : +: New York : Towerhouse : +*==========*============* diff --git a/vendor/pear/console_table/tests/border-custom2.phpt b/vendor/pear/console_table/tests/border-custom2.phpt new file mode 100644 index 0000000000..c129c8d68b --- /dev/null +++ b/vendor/pear/console_table/tests/border-custom2.phpt @@ -0,0 +1,27 @@ +--TEST-- +Border: new custom mode, alternative style +--FILE-- + '=', 'vertical' => '', 'intersection' => '') +); +$table->setHeaders(array('City', 'Mayor')); +$table->addRow(array('Leipzig', 'Major Tom')); +$table->addRow(array('New York', 'Towerhouse')); + +echo $table->getTable(); +?> +--EXPECT-- +====================== + City Mayor +====================== + Leipzig Major Tom + New York Towerhouse +====================== diff --git a/vendor/pear/console_table/tests/border-disable.phpt b/vendor/pear/console_table/tests/border-disable.phpt new file mode 100644 index 0000000000..c753e98ba8 --- /dev/null +++ b/vendor/pear/console_table/tests/border-disable.phpt @@ -0,0 +1,68 @@ +--TEST-- +Border: disable it +--FILE-- +setHeaders(array('City', 'Mayor')); +$table->addRow(array('Leipzig', 'Major Tom')); +$table->addRow(array('New York', 'Towerhouse')); + +$table->setBorderVisibility( + array( + 'left' => false, + 'right' => false, + ) +); +echo "Horizontal borders only:\n"; +echo $table->getTable() . "\n"; + +$table->setBorderVisibility( + array( + 'top' => false, + 'right' => false, + 'bottom' => false, + 'left' => false, + 'inner' => false, + ) +); +echo "No borders:\n"; +echo $table->getTable() . "\n"; + +$table->setBorderVisibility( + array( + 'top' => false, + 'right' => true, + 'bottom' => false, + 'left' => true, + 'inner' => true, + ) +); +echo "Vertical and inner only:\n"; +echo $table->getTable() . "\n"; +?> +--EXPECT-- +Horizontal borders only: +---------+----------- +City | Mayor +---------+----------- +Leipzig | Major Tom +New York | Towerhouse +---------+----------- + +No borders: +City | Mayor +Leipzig | Major Tom +New York | Towerhouse + +Vertical and inner only: +| City | Mayor | ++----------+------------+ +| Leipzig | Major Tom | +| New York | Towerhouse | + diff --git a/vendor/pear/console_table/tests/border-dot.phpt b/vendor/pear/console_table/tests/border-dot.phpt new file mode 100644 index 0000000000..8d6f1033ce --- /dev/null +++ b/vendor/pear/console_table/tests/border-dot.phpt @@ -0,0 +1,24 @@ +--TEST-- +Border: custom border character +--FILE-- +setHeaders(array('City', 'Mayor')); +$table->addRow(array('Leipzig', 'Major Tom')); +$table->addRow(array('New York', 'Towerhouse')); + +echo $table->getTable(); +?> +--EXPECT-- +......................... +. City . Mayor . +......................... +. Leipzig . Major Tom . +. New York . Towerhouse . +......................... diff --git a/vendor/pear/console_table/tests/border-empty.phpt b/vendor/pear/console_table/tests/border-empty.phpt new file mode 100644 index 0000000000..a9c635b21f --- /dev/null +++ b/vendor/pear/console_table/tests/border-empty.phpt @@ -0,0 +1,21 @@ +--TEST-- +Border: empty character +--FILE-- +setHeaders(array('City', 'Mayor')); +$table->addRow(array('Leipzig', 'Major Tom')); +$table->addRow(array('New York', 'Towerhouse')); + +echo $table->getTable() . "\n"; +?> +--EXPECT-- + City Mayor + Leipzig Major Tom + New York Towerhouse diff --git a/vendor/pear/console_table/tests/bug20181.phpt b/vendor/pear/console_table/tests/bug20181.phpt new file mode 100644 index 0000000000..2a604b3036 --- /dev/null +++ b/vendor/pear/console_table/tests/bug20181.phpt @@ -0,0 +1,23 @@ +--TEST-- +Bug #20181: setAlign() on non-zero column +--FILE-- +setAlign(1, CONSOLE_TABLE_ALIGN_RIGHT); +$table->setHeaders(array('f', 'bar')); +$table->addRow(array('baz', 'b')); + +echo $table->getTable(); +?> +--EXPECT-- ++-----+-----+ +| f | bar | ++-----+-----+ +| baz | b | ++-----+-----+ diff --git a/vendor/pear/console_table/tests/colors.phpt b/vendor/pear/console_table/tests/colors.phpt new file mode 100644 index 0000000000..ac7f923fc2 --- /dev/null +++ b/vendor/pear/console_table/tests/colors.phpt @@ -0,0 +1,28 @@ +--TEST-- +Data with ANSI color codes +--SKIPIF-- + +--FILE-- +setHeaders(array('foo', 'bar')); +$table->addRow(array('baz', $cc->convert("%bblue%n"))); + +echo $table->getTable(); + +?> +--EXPECT-- ++-----+------+ +| foo | bar | ++-----+------+ +| baz | blue | ++-----+------+ diff --git a/vendor/pear/console_table/tests/filters.phpt b/vendor/pear/console_table/tests/filters.phpt new file mode 100644 index 0000000000..f96e78a75f --- /dev/null +++ b/vendor/pear/console_table/tests/filters.phpt @@ -0,0 +1,38 @@ +--TEST-- +Callback filters +--FILE-- +setHeaders(array('foo', 'bar')); +$table->addData($data); +$table->addFilter(0, $filter); + +echo $table->getTable(); + +?> +--EXPECT-- ++-------+-------+ +| foo | bar | ++-------+-------+ +| ONE | two | +| THREE | four | ++-------+-------+ +| FIVE | six | +| SEVEN | eight | ++-------+-------+ diff --git a/vendor/pear/console_table/tests/multibyte.phpt b/vendor/pear/console_table/tests/multibyte.phpt new file mode 100644 index 0000000000..0ac7723e20 --- /dev/null +++ b/vendor/pear/console_table/tests/multibyte.phpt @@ -0,0 +1,35 @@ +--TEST-- +Multibyte strings +--FILE-- +setHeaders(array('Schön', 'Häßlich')); +$table->addData(array(array('Ich', 'Du'), array('Ä', 'Ü'))); +echo $table->getTable(); + +$table = new Console_Table(); +$table->addRow(array("I'm from 中国")); +$table->addRow(array("我是中国人")); +$table->addRow(array("I'm from China")); +echo $table->getTable(); + +?> +--EXPECT-- ++-------+---------+ +| Schön | Häßlich | ++-------+---------+ +| Ich | Du | +| Ä | Ü | ++-------+---------+ ++----------------+ +| I'm from 中国 | +| 我是中国人 | +| I'm from China | ++----------------+ diff --git a/vendor/pear/console_table/tests/multiline.phpt b/vendor/pear/console_table/tests/multiline.phpt new file mode 100644 index 0000000000..1e72e58a21 --- /dev/null +++ b/vendor/pear/console_table/tests/multiline.phpt @@ -0,0 +1,51 @@ +--TEST-- +Multiline table cells +--FILE-- +setHeaders(array("h1\nmultiline", 'h2', "h3", 'h4')); +$table->addData($data); +echo $table->getTable(); + +echo Console_Table::fromArray(array('one line header'), + array(array("multiple\nlines"), + array('one line'))); + +?> +--EXPECT-- ++-----------+--------+-----------+--------+ +| h1 | h2 | h3 | h4 | +| multiline | | | | ++-----------+--------+-----------+--------+ +| col1 | 0 | col3 | col4 | +| | | multiline | | +| r2col1 | r2col2 | r2col3 | r2col4 | +| | | multiline | | +| r3col1 | r3col2 | r3col3 | r3col4 | +| | | multiline | | +| | | verymuch | | +| r4col1 | r4col2 | r4col3 | r4col4 | +| r5col1 | r5col2 | r5col3 | r5col4 | ++-----------+--------+-----------+--------+ ++-----------------+ +| one line header | ++-----------------+ +| multiple | +| lines | +| one line | ++-----------------+ diff --git a/vendor/pear/console_table/tests/no_header.phpt b/vendor/pear/console_table/tests/no_header.phpt new file mode 100644 index 0000000000..8d4b61bec4 --- /dev/null +++ b/vendor/pear/console_table/tests/no_header.phpt @@ -0,0 +1,21 @@ +--TEST-- +Table without header +--FILE-- +addData(array(array('foo', 'bar'))); + +echo $table->getTable(); + +?> +--EXPECT-- ++-----+-----+ +| foo | bar | ++-----+-----+ diff --git a/vendor/pear/console_table/tests/no_rows.phpt b/vendor/pear/console_table/tests/no_rows.phpt new file mode 100644 index 0000000000..386da343bd --- /dev/null +++ b/vendor/pear/console_table/tests/no_rows.phpt @@ -0,0 +1,25 @@ +--TEST-- +Table without data +--FILE-- +setHeaders(array('foo', 'bar')); +echo $table->getTable(); + +$table = new Console_Table(); +echo $table->getTable(); + +?> +--EXPECT-- ++-----+-----+ +| foo | bar | ++-----+-----+ +| | | ++-----+-----+ diff --git a/vendor/pear/console_table/tests/rules.phpt b/vendor/pear/console_table/tests/rules.phpt new file mode 100644 index 0000000000..ddbbc3fc6b --- /dev/null +++ b/vendor/pear/console_table/tests/rules.phpt @@ -0,0 +1,74 @@ +--TEST-- +Horizontal rules +--FILE-- +setHeaders(array('foo', 'bar')); +$table->addData($data); +$table->addSeparator(); +echo $table->getTable(); +echo "=========================\n"; + +$table = new Console_Table(CONSOLE_TABLE_ALIGN_LEFT, ''); +$table->setHeaders(array('foo', 'bar')); +$table->addData($data); +$table->addSeparator(); +echo $table->getTable(); +echo "=========================\n"; + +$table = new Console_Table(CONSOLE_TABLE_ALIGN_LEFT, '#', 0); +$table->setHeaders(array('foo', 'bar')); +$table->addData($data); +$table->addSeparator(); +echo $table->getTable(); + +?> +--EXPECT-- ++-------+-------+ +| foo | bar | ++-------+-------+ +| one | two | ++-------+-------+ +| three | four | ++-------+-------+ ++-------+-------+ +| five | six | +| seven | eight | ++-------+-------+ ++-------+-------+ +========================= + foo bar + one two + three four + five six + seven eight +========================= +############# +#foo #bar # +############# +#one #two # +############# +#three#four # +############# +############# +#five #six # +#seven#eight# +############# +############# diff --git a/vendor/psr/container/.gitignore b/vendor/psr/container/.gitignore new file mode 100644 index 0000000000..b2395aa055 --- /dev/null +++ b/vendor/psr/container/.gitignore @@ -0,0 +1,3 @@ +composer.lock +composer.phar +/vendor/ diff --git a/vendor/psr/container/LICENSE b/vendor/psr/container/LICENSE new file mode 100644 index 0000000000..2877a4894e --- /dev/null +++ b/vendor/psr/container/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013-2016 container-interop +Copyright (c) 2016 PHP Framework Interoperability Group + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/psr/container/README.md b/vendor/psr/container/README.md new file mode 100644 index 0000000000..1b9d9e5708 --- /dev/null +++ b/vendor/psr/container/README.md @@ -0,0 +1,13 @@ +Container interface +============== + +This repository holds all interfaces related to [PSR-11 (Container Interface)][psr-url]. + +Note that this is not a Container implementation of its own. It is merely abstractions that describe the components of a Dependency Injection Container. + +The installable [package][package-url] and [implementations][implementation-url] are listed on Packagist. + +[psr-url]: https://www.php-fig.org/psr/psr-11/ +[package-url]: https://packagist.org/packages/psr/container +[implementation-url]: https://packagist.org/providers/psr/container-implementation + diff --git a/vendor/psr/container/composer.json b/vendor/psr/container/composer.json new file mode 100644 index 0000000000..017f41ea69 --- /dev/null +++ b/vendor/psr/container/composer.json @@ -0,0 +1,22 @@ +{ + "name": "psr/container", + "type": "library", + "description": "Common Container Interface (PHP FIG PSR-11)", + "keywords": ["psr", "psr-11", "container", "container-interop", "container-interface"], + "homepage": "https://github.com/php-fig/container", + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "require": { + "php": ">=7.4.0" + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + } +} diff --git a/vendor/psr/container/src/ContainerExceptionInterface.php b/vendor/psr/container/src/ContainerExceptionInterface.php new file mode 100644 index 0000000000..0f213f2fed --- /dev/null +++ b/vendor/psr/container/src/ContainerExceptionInterface.php @@ -0,0 +1,12 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } +} diff --git a/vendor/psr/log/Psr/Log/InvalidArgumentException.php b/vendor/psr/log/Psr/Log/InvalidArgumentException.php new file mode 100644 index 0000000000..67f852d1db --- /dev/null +++ b/vendor/psr/log/Psr/Log/InvalidArgumentException.php @@ -0,0 +1,7 @@ +logger = $logger; + } +} diff --git a/vendor/psr/log/Psr/Log/LoggerInterface.php b/vendor/psr/log/Psr/Log/LoggerInterface.php new file mode 100644 index 0000000000..2206cfde41 --- /dev/null +++ b/vendor/psr/log/Psr/Log/LoggerInterface.php @@ -0,0 +1,125 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + * + * @throws \Psr\Log\InvalidArgumentException + */ + abstract public function log($level, $message, array $context = array()); +} diff --git a/vendor/psr/log/Psr/Log/NullLogger.php b/vendor/psr/log/Psr/Log/NullLogger.php new file mode 100644 index 0000000000..c8f7293b1c --- /dev/null +++ b/vendor/psr/log/Psr/Log/NullLogger.php @@ -0,0 +1,30 @@ +logger) { }` + * blocks. + */ +class NullLogger extends AbstractLogger +{ + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + * + * @throws \Psr\Log\InvalidArgumentException + */ + public function log($level, $message, array $context = array()) + { + // noop + } +} diff --git a/vendor/psr/log/Psr/Log/Test/DummyTest.php b/vendor/psr/log/Psr/Log/Test/DummyTest.php new file mode 100644 index 0000000000..9638c11018 --- /dev/null +++ b/vendor/psr/log/Psr/Log/Test/DummyTest.php @@ -0,0 +1,18 @@ + ". + * + * Example ->error('Foo') would yield "error Foo". + * + * @return string[] + */ + abstract public function getLogs(); + + public function testImplements() + { + $this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger()); + } + + /** + * @dataProvider provideLevelsAndMessages + */ + public function testLogsAtAllLevels($level, $message) + { + $logger = $this->getLogger(); + $logger->{$level}($message, array('user' => 'Bob')); + $logger->log($level, $message, array('user' => 'Bob')); + + $expected = array( + $level.' message of level '.$level.' with context: Bob', + $level.' message of level '.$level.' with context: Bob', + ); + $this->assertEquals($expected, $this->getLogs()); + } + + public function provideLevelsAndMessages() + { + return array( + LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'), + LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'), + LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'), + LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'), + LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'), + LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'), + LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'), + LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'), + ); + } + + /** + * @expectedException \Psr\Log\InvalidArgumentException + */ + public function testThrowsOnInvalidLevel() + { + $logger = $this->getLogger(); + $logger->log('invalid level', 'Foo'); + } + + public function testContextReplacement() + { + $logger = $this->getLogger(); + $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar')); + + $expected = array('info {Message {nothing} Bob Bar a}'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testObjectCastToString() + { + if (method_exists($this, 'createPartialMock')) { + $dummy = $this->createPartialMock('Psr\Log\Test\DummyTest', array('__toString')); + } else { + $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString')); + } + $dummy->expects($this->once()) + ->method('__toString') + ->will($this->returnValue('DUMMY')); + + $this->getLogger()->warning($dummy); + + $expected = array('warning DUMMY'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testContextCanContainAnything() + { + $closed = fopen('php://memory', 'r'); + fclose($closed); + + $context = array( + 'bool' => true, + 'null' => null, + 'string' => 'Foo', + 'int' => 0, + 'float' => 0.5, + 'nested' => array('with object' => new DummyTest), + 'object' => new \DateTime, + 'resource' => fopen('php://memory', 'r'), + 'closed' => $closed, + ); + + $this->getLogger()->warning('Crazy context data', $context); + + $expected = array('warning Crazy context data'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testContextExceptionKeyCanBeExceptionOrOtherValues() + { + $logger = $this->getLogger(); + $logger->warning('Random message', array('exception' => 'oops')); + $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail'))); + + $expected = array( + 'warning Random message', + 'critical Uncaught Exception!' + ); + $this->assertEquals($expected, $this->getLogs()); + } +} diff --git a/vendor/psr/log/Psr/Log/Test/TestLogger.php b/vendor/psr/log/Psr/Log/Test/TestLogger.php new file mode 100644 index 0000000000..1be3230496 --- /dev/null +++ b/vendor/psr/log/Psr/Log/Test/TestLogger.php @@ -0,0 +1,147 @@ + $level, + 'message' => $message, + 'context' => $context, + ]; + + $this->recordsByLevel[$record['level']][] = $record; + $this->records[] = $record; + } + + public function hasRecords($level) + { + return isset($this->recordsByLevel[$level]); + } + + public function hasRecord($record, $level) + { + if (is_string($record)) { + $record = ['message' => $record]; + } + return $this->hasRecordThatPasses(function ($rec) use ($record) { + if ($rec['message'] !== $record['message']) { + return false; + } + if (isset($record['context']) && $rec['context'] !== $record['context']) { + return false; + } + return true; + }, $level); + } + + public function hasRecordThatContains($message, $level) + { + return $this->hasRecordThatPasses(function ($rec) use ($message) { + return strpos($rec['message'], $message) !== false; + }, $level); + } + + public function hasRecordThatMatches($regex, $level) + { + return $this->hasRecordThatPasses(function ($rec) use ($regex) { + return preg_match($regex, $rec['message']) > 0; + }, $level); + } + + public function hasRecordThatPasses(callable $predicate, $level) + { + if (!isset($this->recordsByLevel[$level])) { + return false; + } + foreach ($this->recordsByLevel[$level] as $i => $rec) { + if (call_user_func($predicate, $rec, $i)) { + return true; + } + } + return false; + } + + public function __call($method, $args) + { + if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) { + $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3]; + $level = strtolower($matches[2]); + if (method_exists($this, $genericMethod)) { + $args[] = $level; + return call_user_func_array([$this, $genericMethod], $args); + } + } + throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()'); + } + + public function reset() + { + $this->records = []; + $this->recordsByLevel = []; + } +} diff --git a/vendor/psr/log/README.md b/vendor/psr/log/README.md new file mode 100644 index 0000000000..a9f20c437b --- /dev/null +++ b/vendor/psr/log/README.md @@ -0,0 +1,58 @@ +PSR Log +======= + +This repository holds all interfaces/classes/traits related to +[PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md). + +Note that this is not a logger of its own. It is merely an interface that +describes a logger. See the specification for more details. + +Installation +------------ + +```bash +composer require psr/log +``` + +Usage +----- + +If you need a logger, you can use the interface like this: + +```php +logger = $logger; + } + + public function doSomething() + { + if ($this->logger) { + $this->logger->info('Doing work'); + } + + try { + $this->doSomethingElse(); + } catch (Exception $exception) { + $this->logger->error('Oh no!', array('exception' => $exception)); + } + + // do something useful + } +} +``` + +You can then pick one of the implementations of the interface to get a logger. + +If you want to implement the interface, you can require this package and +implement `Psr\Log\LoggerInterface` in your code. Please read the +[specification text](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) +for details. diff --git a/vendor/psr/log/composer.json b/vendor/psr/log/composer.json new file mode 100644 index 0000000000..ca05695377 --- /dev/null +++ b/vendor/psr/log/composer.json @@ -0,0 +1,26 @@ +{ + "name": "psr/log", + "description": "Common interface for logging libraries", + "keywords": ["psr", "psr-3", "log"], + "homepage": "https://github.com/php-fig/log", + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + } +} diff --git a/vendor/psy/psysh/LICENSE b/vendor/psy/psysh/LICENSE new file mode 100644 index 0000000000..f2afd1c2ce --- /dev/null +++ b/vendor/psy/psysh/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2012-2022 Justin Hileman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE +OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/psy/psysh/README.md b/vendor/psy/psysh/README.md new file mode 100644 index 0000000000..0be99d7a6b --- /dev/null +++ b/vendor/psy/psysh/README.md @@ -0,0 +1,35 @@ +# PsySH + +PsySH is a runtime developer console, interactive debugger and [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop) for PHP. Learn more at [psysh.org](http://psysh.org/) and [in the manual](https://github.com/bobthecow/psysh/wiki/Home). + + +[![Package version](https://img.shields.io/packagist/v/psy/psysh.svg?style=flat-square)](https://packagist.org/packages/psy/psysh) +[![Monthly downloads](http://img.shields.io/packagist/dm/psy/psysh.svg?style=flat-square)](https://packagist.org/packages/psy/psysh) +[![Made out of awesome](https://img.shields.io/badge/made_out_of_awesome-✓-brightgreen.svg?style=flat-square)](http://psysh.org) + +[![Build status](https://img.shields.io/github/workflow/status/bobthecow/psysh/Tests/main.svg?style=flat-square)](https://github.com/bobthecow/psysh/actions?query=branch:main) +[![StyleCI](https://styleci.io/repos/4549925/shield)](https://styleci.io/repos/4549925) + + + + +## [PsySH manual](https://github.com/bobthecow/psysh/wiki/Home) + +### [💾 Installation](https://github.com/bobthecow/psysh/wiki/Installation) + * [📕 PHP manual installation](https://github.com/bobthecow/psysh/wiki/PHP-manual) + * Windows + +### [🖥 Usage](https://github.com/bobthecow/psysh/wiki/Usage) + * [✨ Magic variables](https://github.com/bobthecow/psysh/wiki/Magic-variables) + * [⏳ Managing history](https://github.com/bobthecow/psysh/wiki/History) + * [💲 System shell integration](https://github.com/bobthecow/psysh/wiki/Shell-integration) + * [🎥 Tutorials & guides](https://github.com/bobthecow/psysh/wiki/Tutorials) + * [🐛 Troubleshooting](https://github.com/bobthecow/psysh/wiki/Troubleshooting) + +### [📢 Commands](https://github.com/bobthecow/psysh/wiki/Commands) + +### [🛠 Configuration](https://github.com/bobthecow/psysh/wiki/Configuration) + * [🎛 Config options](https://github.com/bobthecow/psysh/wiki/Config-options) + * [📄 Sample config file](https://github.com/bobthecow/psysh/wiki/Sample-config) + +### [🔌 Integrations](https://github.com/bobthecow/psysh/wiki/Integrations) diff --git a/vendor/psy/psysh/bin/psysh b/vendor/psy/psysh/bin/psysh new file mode 100755 index 0000000000..1205b63160 --- /dev/null +++ b/vendor/psy/psysh/bin/psysh @@ -0,0 +1,148 @@ +#!/usr/bin/env php + $arg) { + if ($arg === '--cwd') { + if ($i >= count($argv) - 1) { + fwrite(STDERR, 'Missing --cwd argument.' . PHP_EOL); + exit(1); + } + $cwd = $argv[$i + 1]; + break; + } + + if (preg_match('/^--cwd=/', $arg)) { + $cwd = substr($arg, 6); + break; + } + } + + // Or fall back to the actual cwd + if (!isset($cwd)) { + $cwd = getcwd(); + } + + $cwd = str_replace('\\', '/', $cwd); + + $chunks = explode('/', $cwd); + while (!empty($chunks)) { + $path = implode('/', $chunks); + $prettyPath = $path; + if (isset($_SERVER['HOME']) && $_SERVER['HOME']) { + $prettyPath = preg_replace('/^' . preg_quote($_SERVER['HOME'], '/') . '/', '~', $path); + } + + // Find composer.json + if (is_file($path . '/composer.json')) { + if ($cfg = json_decode(file_get_contents($path . '/composer.json'), true)) { + if (isset($cfg['name']) && $cfg['name'] === 'psy/psysh') { + // We're inside the psysh project. Let's use the local Composer autoload. + if (is_file($path . '/vendor/autoload.php')) { + if (realpath($path) !== realpath(__DIR__ . '/..')) { + fwrite(STDERR, 'Using local PsySH version at ' . $prettyPath . PHP_EOL); + } + + require $path . '/vendor/autoload.php'; + } + + return; + } + } + } + + // Or a composer.lock + if (is_file($path . '/composer.lock')) { + if ($cfg = json_decode(file_get_contents($path . '/composer.lock'), true)) { + foreach (array_merge($cfg['packages'], $cfg['packages-dev']) as $pkg) { + if (isset($pkg['name']) && $pkg['name'] === 'psy/psysh') { + // We're inside a project which requires psysh. We'll use the local Composer autoload. + if (is_file($path . '/vendor/autoload.php')) { + if (realpath($path . '/vendor') !== realpath(__DIR__ . '/../../..')) { + fwrite(STDERR, 'Using local PsySH version at ' . $prettyPath . PHP_EOL); + } + + require $path . '/vendor/autoload.php'; + } + + return; + } + } + } + } + + array_pop($chunks); + } +}); + +// We didn't find an autoloader for a local version, so use the autoloader that +// came with this script. +if (!class_exists('Psy\Shell')) { +/* <<< */ + if (is_file(__DIR__ . '/../vendor/autoload.php')) { + require __DIR__ . '/../vendor/autoload.php'; + } elseif (is_file(__DIR__ . '/../../../autoload.php')) { + require __DIR__ . '/../../../autoload.php'; + } else { + fwrite(STDERR, 'PsySH dependencies not found, be sure to run `composer install`.' . PHP_EOL); + fwrite(STDERR, 'See https://getcomposer.org to get Composer.' . PHP_EOL); + exit(1); + } +/* >>> */ +} + +// If the psysh binary was included directly, assume they just wanted an +// autoloader and bail early. +// +// Keep this PHP 5.3 and 5.4 code around for a while in case someone is using a +// globally installed psysh as a bin launcher for older local versions. +if (version_compare(PHP_VERSION, '5.3.6', '<')) { + $trace = debug_backtrace(); +} elseif (version_compare(PHP_VERSION, '5.4.0', '<')) { + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); +} else { + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); +} + +if (Psy\Shell::isIncluded($trace)) { + unset($trace); + + return; +} + +// Clean up after ourselves. +unset($trace); + +// If the local version is too old, we can't do this +if (!function_exists('Psy\bin')) { + $argv = isset($_SERVER['argv']) ? $_SERVER['argv'] : array(); + $first = array_shift($argv); + if (preg_match('/php(\.exe)?$/', $first)) { + array_shift($argv); + } + array_unshift($argv, 'vendor/bin/psysh'); + + fwrite(STDERR, 'A local PsySH dependency was found, but it cannot be loaded. Please update to' . PHP_EOL); + fwrite(STDERR, 'the latest version, or run the local copy directly, e.g.:' . PHP_EOL); + fwrite(STDERR, PHP_EOL); + fwrite(STDERR, ' ' . implode(' ', $argv) . PHP_EOL); + exit(1); +} + +// And go! +call_user_func(Psy\bin()); diff --git a/vendor/psy/psysh/composer.json b/vendor/psy/psysh/composer.json new file mode 100644 index 0000000000..c076d041f9 --- /dev/null +++ b/vendor/psy/psysh/composer.json @@ -0,0 +1,57 @@ +{ + "name": "psy/psysh", + "description": "An interactive shell for modern PHP.", + "type": "library", + "keywords": ["console", "interactive", "shell", "repl"], + "homepage": "http://psysh.org", + "license": "MIT", + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "require": { + "php": "^8.0 || ^7.0.8", + "ext-json": "*", + "ext-tokenizer": "*", + "symfony/console": "^6.0 || ^5.0 || ^4.0 || ^3.4", + "symfony/var-dumper": "^6.0 || ^5.0 || ^4.0 || ^3.4", + "nikic/php-parser": "^4.0 || ^3.1" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.2" + }, + "suggest": { + "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", + "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well.", + "ext-readline": "Enables support for arrow-key history navigation, and showing and manipulating command history.", + "ext-pdo-sqlite": "The doc command requires SQLite to work." + }, + "autoload": { + "files": ["src/functions.php"], + "psr-4": { + "Psy\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Psy\\Test\\": "test/" + } + }, + "bin": ["bin/psysh"], + "config": { + "allow-plugins": { + "bamarni/composer-bin-plugin": true + } + }, + "extra": { + "branch-alias": { + "dev-main": "0.11.x-dev" + } + }, + "conflict": { + "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4" + } +} diff --git a/vendor/psy/psysh/src/CodeCleaner.php b/vendor/psy/psysh/src/CodeCleaner.php new file mode 100644 index 0000000000..60fe888f71 --- /dev/null +++ b/vendor/psy/psysh/src/CodeCleaner.php @@ -0,0 +1,399 @@ +yolo = $yolo; + + if ($parser === null) { + $parserFactory = new ParserFactory(); + $parser = $parserFactory->createParser(); + } + + $this->parser = $parser; + $this->printer = $printer ?: new Printer(); + $this->traverser = $traverser ?: new NodeTraverser(); + + foreach ($this->getDefaultPasses() as $pass) { + $this->traverser->addVisitor($pass); + } + } + + /** + * Check whether this CodeCleaner is in YOLO mode. + * + * @return bool + */ + public function yolo(): bool + { + return $this->yolo; + } + + /** + * Get default CodeCleaner passes. + * + * @return array + */ + private function getDefaultPasses(): array + { + if ($this->yolo) { + return $this->getYoloPasses(); + } + + $useStatementPass = new UseStatementPass(); + $namespacePass = new NamespacePass($this); + + // Try to add implicit `use` statements and an implicit namespace, + // based on the file in which the `debug` call was made. + $this->addImplicitDebugContext([$useStatementPass, $namespacePass]); + + return [ + // Validation passes + new AbstractClassPass(), + new AssignThisVariablePass(), + new CalledClassPass(), + new CallTimePassByReferencePass(), + new FinalClassPass(), + new FunctionContextPass(), + new FunctionReturnInWriteContextPass(), + new InstanceOfPass(), + new IssetPass(), + new LabelContextPass(), + new LeavePsyshAlonePass(), + new ListPass(), + new LoopContextPass(), + new PassableByReferencePass(), + new ReturnTypePass(), + new EmptyArrayDimFetchPass(), + new ValidConstructorPass(), + + // Rewriting shenanigans + $useStatementPass, // must run before the namespace pass + new ExitPass(), + new ImplicitReturnPass(), + new MagicConstantsPass(), + $namespacePass, // must run after the implicit return pass + new RequirePass(), + new StrictTypesPass(), + + // Namespace-aware validation (which depends on aforementioned shenanigans) + new ValidClassNamePass(), + new ValidFunctionNamePass(), + ]; + } + + /** + * A set of code cleaner passes that don't try to do any validation, and + * only do minimal rewriting to make things work inside the REPL. + * + * This list should stay in sync with the "rewriting shenanigans" in + * getDefaultPasses above. + * + * @return array + */ + private function getYoloPasses(): array + { + $useStatementPass = new UseStatementPass(); + $namespacePass = new NamespacePass($this); + + // Try to add implicit `use` statements and an implicit namespace, + // based on the file in which the `debug` call was made. + $this->addImplicitDebugContext([$useStatementPass, $namespacePass]); + + return [ + new LeavePsyshAlonePass(), + $useStatementPass, // must run before the namespace pass + new ExitPass(), + new ImplicitReturnPass(), + new MagicConstantsPass(), + $namespacePass, // must run after the implicit return pass + new RequirePass(), + new StrictTypesPass(), + ]; + } + + /** + * "Warm up" code cleaner passes when we're coming from a debug call. + * + * This is useful, for example, for `UseStatementPass` and `NamespacePass` + * which keep track of state between calls, to maintain the current + * namespace and a map of use statements. + * + * @param array $passes + */ + private function addImplicitDebugContext(array $passes) + { + $file = $this->getDebugFile(); + if ($file === null) { + return; + } + + try { + $code = @\file_get_contents($file); + if (!$code) { + return; + } + + $stmts = $this->parse($code, true); + if ($stmts === false) { + return; + } + + // Set up a clean traverser for just these code cleaner passes + $traverser = new NodeTraverser(); + foreach ($passes as $pass) { + $traverser->addVisitor($pass); + } + + $traverser->traverse($stmts); + } catch (\Throwable $e) { + // Don't care. + } + } + + /** + * Search the stack trace for a file in which the user called Psy\debug. + * + * @return string|null + */ + private static function getDebugFile() + { + $trace = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS); + + foreach (\array_reverse($trace) as $stackFrame) { + if (!self::isDebugCall($stackFrame)) { + continue; + } + + if (\preg_match('/eval\(/', $stackFrame['file'])) { + \preg_match_all('/([^\(]+)\((\d+)/', $stackFrame['file'], $matches); + + return $matches[1][0]; + } + + return $stackFrame['file']; + } + } + + /** + * Check whether a given backtrace frame is a call to Psy\debug. + * + * @param array $stackFrame + * + * @return bool + */ + private static function isDebugCall(array $stackFrame): bool + { + $class = isset($stackFrame['class']) ? $stackFrame['class'] : null; + $function = isset($stackFrame['function']) ? $stackFrame['function'] : null; + + return ($class === null && $function === 'Psy\\debug') || + ($class === Shell::class && $function === 'debug'); + } + + /** + * Clean the given array of code. + * + * @throws ParseErrorException if the code is invalid PHP, and cannot be coerced into valid PHP + * + * @param array $codeLines + * @param bool $requireSemicolons + * + * @return string|false Cleaned PHP code, False if the input is incomplete + */ + public function clean(array $codeLines, bool $requireSemicolons = false) + { + $stmts = $this->parse('traverser->traverse($stmts); + + // Work around https://github.com/nikic/PHP-Parser/issues/399 + $oldLocale = \setlocale(\LC_NUMERIC, 0); + \setlocale(\LC_NUMERIC, 'C'); + + $code = $this->printer->prettyPrint($stmts); + + // Now put the locale back + \setlocale(\LC_NUMERIC, $oldLocale); + + return $code; + } + + /** + * Set the current local namespace. + * + * @param array|null $namespace (default: null) + * + * @return array|null + */ + public function setNamespace(array $namespace = null) + { + $this->namespace = $namespace; + } + + /** + * Get the current local namespace. + * + * @return array|null + */ + public function getNamespace() + { + return $this->namespace; + } + + /** + * Lex and parse a block of code. + * + * @see Parser::parse + * + * @throws ParseErrorException for parse errors that can't be resolved by + * waiting a line to see what comes next + * + * @param string $code + * @param bool $requireSemicolons + * + * @return array|false A set of statements, or false if incomplete + */ + protected function parse(string $code, bool $requireSemicolons = false) + { + try { + return $this->parser->parse($code); + } catch (\PhpParser\Error $e) { + if ($this->parseErrorIsUnclosedString($e, $code)) { + return false; + } + + if ($this->parseErrorIsUnterminatedComment($e, $code)) { + return false; + } + + if ($this->parseErrorIsTrailingComma($e, $code)) { + return false; + } + + if (!$this->parseErrorIsEOF($e)) { + throw ParseErrorException::fromParseError($e); + } + + if ($requireSemicolons) { + return false; + } + + try { + // Unexpected EOF, try again with an implicit semicolon + return $this->parser->parse($code.';'); + } catch (\PhpParser\Error $e) { + return false; + } + } + } + + private function parseErrorIsEOF(\PhpParser\Error $e): bool + { + $msg = $e->getRawMessage(); + + return ($msg === 'Unexpected token EOF') || (\strpos($msg, 'Syntax error, unexpected EOF') !== false); + } + + /** + * A special test for unclosed single-quoted strings. + * + * Unlike (all?) other unclosed statements, single quoted strings have + * their own special beautiful snowflake syntax error just for + * themselves. + * + * @param \PhpParser\Error $e + * @param string $code + * + * @return bool + */ + private function parseErrorIsUnclosedString(\PhpParser\Error $e, string $code): bool + { + if ($e->getRawMessage() !== 'Syntax error, unexpected T_ENCAPSED_AND_WHITESPACE') { + return false; + } + + try { + $this->parser->parse($code."';"); + } catch (\Throwable $e) { + return false; + } + + return true; + } + + private function parseErrorIsUnterminatedComment(\PhpParser\Error $e, $code): bool + { + return $e->getRawMessage() === 'Unterminated comment'; + } + + private function parseErrorIsTrailingComma(\PhpParser\Error $e, $code): bool + { + return ($e->getRawMessage() === 'A trailing comma is not allowed here') && (\substr(\rtrim($code), -1) === ','); + } +} diff --git a/vendor/psy/psysh/src/CodeCleaner/AbstractClassPass.php b/vendor/psy/psysh/src/CodeCleaner/AbstractClassPass.php new file mode 100644 index 0000000000..3358bb5408 --- /dev/null +++ b/vendor/psy/psysh/src/CodeCleaner/AbstractClassPass.php @@ -0,0 +1,71 @@ +class = $node; + $this->abstractMethods = []; + } elseif ($node instanceof ClassMethod) { + if ($node->isAbstract()) { + $name = \sprintf('%s::%s', $this->class->name, $node->name); + $this->abstractMethods[] = $name; + + if ($node->stmts !== null) { + $msg = \sprintf('Abstract function %s cannot contain body', $name); + throw new FatalErrorException($msg, 0, \E_ERROR, null, $node->getLine()); + } + } + } + } + + /** + * @throws FatalErrorException if the node is a non-abstract class with abstract methods + * + * @param Node $node + */ + public function leaveNode(Node $node) + { + if ($node instanceof Class_) { + $count = \count($this->abstractMethods); + if ($count > 0 && !$node->isAbstract()) { + $msg = \sprintf( + 'Class %s contains %d abstract method%s must therefore be declared abstract or implement the remaining methods (%s)', + $node->name, + $count, + ($count === 1) ? '' : 's', + \implode(', ', $this->abstractMethods) + ); + throw new FatalErrorException($msg, 0, \E_ERROR, null, $node->getLine()); + } + } + } +} diff --git a/vendor/psy/psysh/src/CodeCleaner/AssignThisVariablePass.php b/vendor/psy/psysh/src/CodeCleaner/AssignThisVariablePass.php new file mode 100644 index 0000000000..3f81632c9b --- /dev/null +++ b/vendor/psy/psysh/src/CodeCleaner/AssignThisVariablePass.php @@ -0,0 +1,39 @@ + + */ +class AssignThisVariablePass extends CodeCleanerPass +{ + /** + * Validate that the user input does not assign the `$this` variable. + * + * @throws FatalErrorException if the user assign the `$this` variable + * + * @param Node $node + */ + public function enterNode(Node $node) + { + if ($node instanceof Assign && $node->var instanceof Variable && $node->var->name === 'this') { + throw new FatalErrorException('Cannot re-assign $this', 0, \E_ERROR, null, $node->getLine()); + } + } +} diff --git a/vendor/psy/psysh/src/CodeCleaner/CallTimePassByReferencePass.php b/vendor/psy/psysh/src/CodeCleaner/CallTimePassByReferencePass.php new file mode 100644 index 0000000000..ccfe8df032 --- /dev/null +++ b/vendor/psy/psysh/src/CodeCleaner/CallTimePassByReferencePass.php @@ -0,0 +1,55 @@ + + */ +class CallTimePassByReferencePass extends CodeCleanerPass +{ + const EXCEPTION_MESSAGE = 'Call-time pass-by-reference has been removed'; + + /** + * Validate of use call-time pass-by-reference. + * + * @throws FatalErrorException if the user used call-time pass-by-reference + * + * @param Node $node + */ + public function enterNode(Node $node) + { + if (!$node instanceof FuncCall && !$node instanceof MethodCall && !$node instanceof StaticCall) { + return; + } + + foreach ($node->args as $arg) { + if ($arg instanceof VariadicPlaceholder) { + continue; + } + + if ($arg->byRef) { + throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, \E_ERROR, null, $node->getLine()); + } + } + } +} diff --git a/vendor/psy/psysh/src/CodeCleaner/CalledClassPass.php b/vendor/psy/psysh/src/CodeCleaner/CalledClassPass.php new file mode 100644 index 0000000000..1bea26fd5d --- /dev/null +++ b/vendor/psy/psysh/src/CodeCleaner/CalledClassPass.php @@ -0,0 +1,88 @@ +inClass = false; + } + + /** + * @throws ErrorException if get_class or get_called_class is called without an object from outside a class + * + * @param Node $node + */ + public function enterNode(Node $node) + { + if ($node instanceof Class_ || $node instanceof Trait_) { + $this->inClass = true; + } elseif ($node instanceof FuncCall && !$this->inClass) { + // We'll give any args at all (besides null) a pass. + // Technically we should be checking whether the args are objects, but this will do for now. + // + // @todo switch this to actually validate args when we get context-aware code cleaner passes. + if (!empty($node->args) && !$this->isNull($node->args[0])) { + return; + } + + // We'll ignore name expressions as well (things like `$foo()`) + if (!($node->name instanceof Name)) { + return; + } + + $name = \strtolower($node->name); + if (\in_array($name, ['get_class', 'get_called_class'])) { + $msg = \sprintf('%s() called without object from outside a class', $name); + throw new ErrorException($msg, 0, \E_USER_WARNING, null, $node->getLine()); + } + } + } + + /** + * @param Node $node + */ + public function leaveNode(Node $node) + { + if ($node instanceof Class_) { + $this->inClass = false; + } + } + + private function isNull(Node $node): bool + { + if ($node instanceof VariadicPlaceholder) { + return false; + } + + return $node->value instanceof ConstFetch && \strtolower($node->value->name) === 'null'; + } +} diff --git a/vendor/psy/psysh/src/CodeCleaner/CodeCleanerPass.php b/vendor/psy/psysh/src/CodeCleaner/CodeCleanerPass.php new file mode 100644 index 0000000000..9b3bd7f888 --- /dev/null +++ b/vendor/psy/psysh/src/CodeCleaner/CodeCleanerPass.php @@ -0,0 +1,22 @@ +theseOnesAreFine = []; + } + + /** + * @throws FatalErrorException if the user used empty empty array dim fetch outside of assignment + * + * @param Node $node + */ + public function enterNode(Node $node) + { + if ($node instanceof Assign && $node->var instanceof ArrayDimFetch) { + $this->theseOnesAreFine[] = $node->var; + } + + if ($node instanceof ArrayDimFetch && $node->dim === null) { + if (!\in_array($node, $this->theseOnesAreFine)) { + throw new FatalErrorException(self::EXCEPTION_MESSAGE, $node->getLine()); + } + } + } +} diff --git a/vendor/psy/psysh/src/CodeCleaner/ExitPass.php b/vendor/psy/psysh/src/CodeCleaner/ExitPass.php new file mode 100644 index 0000000000..5e2639d954 --- /dev/null +++ b/vendor/psy/psysh/src/CodeCleaner/ExitPass.php @@ -0,0 +1,33 @@ +finalClasses = []; + } + + /** + * @throws FatalErrorException if the node is a class that extends a final class + * + * @param Node $node + */ + public function enterNode(Node $node) + { + if ($node instanceof Class_) { + if ($node->extends) { + $extends = (string) $node->extends; + if ($this->isFinalClass($extends)) { + $msg = \sprintf('Class %s may not inherit from final class (%s)', $node->name, $extends); + throw new FatalErrorException($msg, 0, \E_ERROR, null, $node->getLine()); + } + } + + if ($node->isFinal()) { + $this->finalClasses[\strtolower($node->name)] = true; + } + } + } + + /** + * @param string $name Class name + * + * @return bool + */ + private function isFinalClass(string $name): bool + { + if (!\class_exists($name)) { + return isset($this->finalClasses[\strtolower($name)]); + } + + $refl = new \ReflectionClass($name); + + return $refl->isFinal(); + } +} diff --git a/vendor/psy/psysh/src/CodeCleaner/FunctionContextPass.php b/vendor/psy/psysh/src/CodeCleaner/FunctionContextPass.php new file mode 100644 index 0000000000..e372c65e72 --- /dev/null +++ b/vendor/psy/psysh/src/CodeCleaner/FunctionContextPass.php @@ -0,0 +1,61 @@ +functionDepth = 0; + } + + public function enterNode(Node $node) + { + if ($node instanceof FunctionLike) { + $this->functionDepth++; + + return; + } + + // node is inside function context + if ($this->functionDepth !== 0) { + return; + } + + // It causes fatal error. + if ($node instanceof Yield_) { + $msg = 'The "yield" expression can only be used inside a function'; + throw new FatalErrorException($msg, 0, \E_ERROR, null, $node->getLine()); + } + } + + /** + * @param \PhpParser\Node $node + */ + public function leaveNode(Node $node) + { + if ($node instanceof FunctionLike) { + $this->functionDepth--; + } + } +} diff --git a/vendor/psy/psysh/src/CodeCleaner/FunctionReturnInWriteContextPass.php b/vendor/psy/psysh/src/CodeCleaner/FunctionReturnInWriteContextPass.php new file mode 100644 index 0000000000..d49fa16762 --- /dev/null +++ b/vendor/psy/psysh/src/CodeCleaner/FunctionReturnInWriteContextPass.php @@ -0,0 +1,75 @@ + + */ +class FunctionReturnInWriteContextPass extends CodeCleanerPass +{ + const ISSET_MESSAGE = 'Cannot use isset() on the result of an expression (you can use "null !== expression" instead)'; + const EXCEPTION_MESSAGE = "Can't use function return value in write context"; + + /** + * Validate that the functions are used correctly. + * + * @throws FatalErrorException if a function is passed as an argument reference + * @throws FatalErrorException if a function is used as an argument in the isset + * @throws FatalErrorException if a value is assigned to a function + * + * @param Node $node + */ + public function enterNode(Node $node) + { + if ($node instanceof Array_ || $this->isCallNode($node)) { + $items = $node instanceof Array_ ? $node->items : $node->args; + foreach ($items as $item) { + if ($item instanceof VariadicPlaceholder) { + continue; + } + + if ($item && $item->byRef && $this->isCallNode($item->value)) { + throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, \E_ERROR, null, $node->getLine()); + } + } + } elseif ($node instanceof Isset_ || $node instanceof Unset_) { + foreach ($node->vars as $var) { + if (!$this->isCallNode($var)) { + continue; + } + + $msg = $node instanceof Isset_ ? self::ISSET_MESSAGE : self::EXCEPTION_MESSAGE; + throw new FatalErrorException($msg, 0, \E_ERROR, null, $node->getLine()); + } + } elseif ($node instanceof Assign && $this->isCallNode($node->var)) { + throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, \E_ERROR, null, $node->getLine()); + } + } + + private function isCallNode(Node $node): bool + { + return $node instanceof FuncCall || $node instanceof MethodCall || $node instanceof StaticCall; + } +} diff --git a/vendor/psy/psysh/src/CodeCleaner/ImplicitReturnPass.php b/vendor/psy/psysh/src/CodeCleaner/ImplicitReturnPass.php new file mode 100644 index 0000000000..0b405cf918 --- /dev/null +++ b/vendor/psy/psysh/src/CodeCleaner/ImplicitReturnPass.php @@ -0,0 +1,128 @@ +addImplicitReturn($nodes); + } + + /** + * @param array $nodes + * + * @return array + */ + private function addImplicitReturn(array $nodes): array + { + // If nodes is empty, it can't have a return value. + if (empty($nodes)) { + return [new Return_(NoReturnValue::create())]; + } + + $last = \end($nodes); + + // Special case a few types of statements to add an implicit return + // value (even though they technically don't have any return value) + // because showing a return value in these instances is useful and not + // very surprising. + if ($last instanceof If_) { + $last->stmts = $this->addImplicitReturn($last->stmts); + + foreach ($last->elseifs as $elseif) { + $elseif->stmts = $this->addImplicitReturn($elseif->stmts); + } + + if ($last->else) { + $last->else->stmts = $this->addImplicitReturn($last->else->stmts); + } + } elseif ($last instanceof Switch_) { + foreach ($last->cases as $case) { + // only add an implicit return to cases which end in break + $caseLast = \end($case->stmts); + if ($caseLast instanceof Break_) { + $case->stmts = $this->addImplicitReturn(\array_slice($case->stmts, 0, -1)); + $case->stmts[] = $caseLast; + } + } + } elseif ($last instanceof Expr && !($last instanceof Exit_)) { + // @codeCoverageIgnoreStart + $nodes[\count($nodes) - 1] = new Return_($last, [ + 'startLine' => $last->getLine(), + 'endLine' => $last->getLine(), + ]); + // @codeCoverageIgnoreEnd + } elseif ($last instanceof Expression && !($last->expr instanceof Exit_)) { + // For PHP Parser 4.x + $nodes[\count($nodes) - 1] = new Return_($last->expr, [ + 'startLine' => $last->getLine(), + 'endLine' => $last->getLine(), + ]); + } elseif ($last instanceof Namespace_) { + $last->stmts = $this->addImplicitReturn($last->stmts); + } + + // Return a "no return value" for all non-expression statements, so that + // PsySH can suppress the `null` that `eval()` returns otherwise. + // + // Note that statements special cased above (if/elseif/else, switch) + // _might_ implicitly return a value before this catch-all return is + // reached. + // + // We're not adding a fallback return after namespace statements, + // because code outside namespace statements doesn't really work, and + // there's already an implicit return in the namespace statement anyway. + if (self::isNonExpressionStmt($last)) { + $nodes[] = new Return_(NoReturnValue::create()); + } + + return $nodes; + } + + /** + * Check whether a given node is a non-expression statement. + * + * As of PHP Parser 4.x, Expressions are now instances of Stmt as well, so + * we'll exclude them here. + * + * @param Node $node + * + * @return bool + */ + private static function isNonExpressionStmt(Node $node): bool + { + return $node instanceof Stmt && + !$node instanceof Expression && + !$node instanceof Return_ && + !$node instanceof Namespace_; + } +} diff --git a/vendor/psy/psysh/src/CodeCleaner/InstanceOfPass.php b/vendor/psy/psysh/src/CodeCleaner/InstanceOfPass.php new file mode 100644 index 0000000000..e6c10bc6af --- /dev/null +++ b/vendor/psy/psysh/src/CodeCleaner/InstanceOfPass.php @@ -0,0 +1,67 @@ + + */ +class InstanceOfPass extends CodeCleanerPass +{ + const EXCEPTION_MSG = 'instanceof expects an object instance, constant given'; + + private $atLeastPhp73; + + public function __construct() + { + $this->atLeastPhp73 = \version_compare(\PHP_VERSION, '7.3', '>='); + } + + /** + * Validate that the instanceof statement does not receive a scalar value or a non-class constant. + * + * @throws FatalErrorException if a scalar or a non-class constant is given + * + * @param Node $node + */ + public function enterNode(Node $node) + { + // Basically everything is allowed in PHP 7.3 :) + if ($this->atLeastPhp73) { + return; + } + + if (!$node instanceof Instanceof_) { + return; + } + + if (($node->expr instanceof Scalar && !$node->expr instanceof Encapsed) || + $node->expr instanceof BinaryOp || + $node->expr instanceof Array_ || + $node->expr instanceof ConstFetch || + $node->expr instanceof ClassConstFetch + ) { + throw new FatalErrorException(self::EXCEPTION_MSG, 0, \E_ERROR, null, $node->getLine()); + } + } +} diff --git a/vendor/psy/psysh/src/CodeCleaner/IssetPass.php b/vendor/psy/psysh/src/CodeCleaner/IssetPass.php new file mode 100644 index 0000000000..ddb5d17e68 --- /dev/null +++ b/vendor/psy/psysh/src/CodeCleaner/IssetPass.php @@ -0,0 +1,47 @@ +vars as $var) { + if (!$var instanceof Variable && !$var instanceof ArrayDimFetch && !$var instanceof PropertyFetch && !$var instanceof NullsafePropertyFetch) { + throw new FatalErrorException(self::EXCEPTION_MSG, 0, \E_ERROR, null, $node->getLine()); + } + } + } +} diff --git a/vendor/psy/psysh/src/CodeCleaner/LabelContextPass.php b/vendor/psy/psysh/src/CodeCleaner/LabelContextPass.php new file mode 100644 index 0000000000..daa028c2c2 --- /dev/null +++ b/vendor/psy/psysh/src/CodeCleaner/LabelContextPass.php @@ -0,0 +1,91 @@ +functionDepth = 0; + $this->labelDeclarations = []; + $this->labelGotos = []; + } + + public function enterNode(Node $node) + { + if ($node instanceof FunctionLike) { + $this->functionDepth++; + + return; + } + + // node is inside function context + if ($this->functionDepth !== 0) { + return; + } + + if ($node instanceof Goto_) { + $this->labelGotos[\strtolower($node->name)] = $node->getLine(); + } elseif ($node instanceof Label) { + $this->labelDeclarations[\strtolower($node->name)] = $node->getLine(); + } + } + + /** + * @param \PhpParser\Node $node + */ + public function leaveNode(Node $node) + { + if ($node instanceof FunctionLike) { + $this->functionDepth--; + } + } + + public function afterTraverse(array $nodes) + { + foreach ($this->labelGotos as $name => $line) { + if (!isset($this->labelDeclarations[$name])) { + $msg = "'goto' to undefined label '{$name}'"; + throw new FatalErrorException($msg, 0, \E_ERROR, null, $line); + } + } + } +} diff --git a/vendor/psy/psysh/src/CodeCleaner/LeavePsyshAlonePass.php b/vendor/psy/psysh/src/CodeCleaner/LeavePsyshAlonePass.php new file mode 100644 index 0000000000..9af0ff7332 --- /dev/null +++ b/vendor/psy/psysh/src/CodeCleaner/LeavePsyshAlonePass.php @@ -0,0 +1,36 @@ +name === '__psysh__') { + throw new RuntimeException('Don\'t mess with $__psysh__; bad things will happen'); + } + } +} diff --git a/vendor/psy/psysh/src/CodeCleaner/ListPass.php b/vendor/psy/psysh/src/CodeCleaner/ListPass.php new file mode 100644 index 0000000000..8587c50cbb --- /dev/null +++ b/vendor/psy/psysh/src/CodeCleaner/ListPass.php @@ -0,0 +1,112 @@ +atLeastPhp71 = \version_compare(\PHP_VERSION, '7.1', '>='); + } + + /** + * Validate use of list assignment. + * + * @throws ParseErrorException if the user used empty with anything but a variable + * + * @param Node $node + */ + public function enterNode(Node $node) + { + if (!$node instanceof Assign) { + return; + } + + if (!$node->var instanceof Array_ && !$node->var instanceof List_) { + return; + } + + if (!$this->atLeastPhp71 && $node->var instanceof Array_) { + $msg = "syntax error, unexpected '='"; + throw new ParseErrorException($msg, $node->expr->getLine()); + } + + // Polyfill for PHP-Parser 2.x + $items = isset($node->var->items) ? $node->var->items : $node->var->vars; + + if ($items === [] || $items === [null]) { + throw new ParseErrorException('Cannot use empty list', $node->var->getLine()); + } + + $itemFound = false; + foreach ($items as $item) { + if ($item === null) { + continue; + } + + $itemFound = true; + + // List_->$vars in PHP-Parser 2.x is Variable instead of ArrayItem. + if (!$this->atLeastPhp71 && $item instanceof ArrayItem && $item->key !== null) { + $msg = 'Syntax error, unexpected T_CONSTANT_ENCAPSED_STRING, expecting \',\' or \')\''; + throw new ParseErrorException($msg, $item->key->getLine()); + } + + if (!self::isValidArrayItem($item)) { + $msg = 'Assignments can only happen to writable values'; + throw new ParseErrorException($msg, $item->getLine()); + } + } + + if (!$itemFound) { + throw new ParseErrorException('Cannot use empty list'); + } + } + + /** + * Validate whether a given item in an array is valid for short assignment. + * + * @param Expr $item + * + * @return bool + */ + private static function isValidArrayItem(Expr $item): bool + { + $value = ($item instanceof ArrayItem) ? $item->value : $item; + + while ($value instanceof ArrayDimFetch || $value instanceof PropertyFetch) { + $value = $value->var; + } + + // We just kind of give up if it's a method call. We can't tell if it's + // valid via static analysis. + return $value instanceof Variable || $value instanceof MethodCall || $value instanceof FuncCall; + } +} diff --git a/vendor/psy/psysh/src/CodeCleaner/LoopContextPass.php b/vendor/psy/psysh/src/CodeCleaner/LoopContextPass.php new file mode 100644 index 0000000000..0e6e7ddfcc --- /dev/null +++ b/vendor/psy/psysh/src/CodeCleaner/LoopContextPass.php @@ -0,0 +1,103 @@ +loopDepth = 0; + } + + /** + * @throws FatalErrorException if the node is a break or continue in a non-loop or switch context + * @throws FatalErrorException if the node is trying to break out of more nested structures than exist + * @throws FatalErrorException if the node is a break or continue and has a non-numeric argument + * @throws FatalErrorException if the node is a break or continue and has an argument less than 1 + * + * @param Node $node + */ + public function enterNode(Node $node) + { + switch (true) { + case $node instanceof Do_: + case $node instanceof For_: + case $node instanceof Foreach_: + case $node instanceof Switch_: + case $node instanceof While_: + $this->loopDepth++; + break; + + case $node instanceof Break_: + case $node instanceof Continue_: + $operator = $node instanceof Break_ ? 'break' : 'continue'; + + if ($this->loopDepth === 0) { + $msg = \sprintf("'%s' not in the 'loop' or 'switch' context", $operator); + throw new FatalErrorException($msg, 0, \E_ERROR, null, $node->getLine()); + } + + if ($node->num instanceof LNumber || $node->num instanceof DNumber) { + $num = $node->num->value; + if ($node->num instanceof DNumber || $num < 1) { + $msg = \sprintf("'%s' operator accepts only positive numbers", $operator); + throw new FatalErrorException($msg, 0, \E_ERROR, null, $node->getLine()); + } + + if ($num > $this->loopDepth) { + $msg = \sprintf("Cannot '%s' %d levels", $operator, $num); + throw new FatalErrorException($msg, 0, \E_ERROR, null, $node->getLine()); + } + } elseif ($node->num) { + $msg = \sprintf("'%s' operator with non-constant operand is no longer supported", $operator); + throw new FatalErrorException($msg, 0, \E_ERROR, null, $node->getLine()); + } + break; + } + } + + /** + * @param Node $node + */ + public function leaveNode(Node $node) + { + switch (true) { + case $node instanceof Do_: + case $node instanceof For_: + case $node instanceof Foreach_: + case $node instanceof Switch_: + case $node instanceof While_: + $this->loopDepth--; + break; + } + } +} diff --git a/vendor/psy/psysh/src/CodeCleaner/MagicConstantsPass.php b/vendor/psy/psysh/src/CodeCleaner/MagicConstantsPass.php new file mode 100644 index 0000000000..ea1f60c47d --- /dev/null +++ b/vendor/psy/psysh/src/CodeCleaner/MagicConstantsPass.php @@ -0,0 +1,42 @@ +getAttributes()); + } elseif ($node instanceof File) { + return new String_('', $node->getAttributes()); + } + } +} diff --git a/vendor/psy/psysh/src/CodeCleaner/NamespaceAwarePass.php b/vendor/psy/psysh/src/CodeCleaner/NamespaceAwarePass.php new file mode 100644 index 0000000000..063147185a --- /dev/null +++ b/vendor/psy/psysh/src/CodeCleaner/NamespaceAwarePass.php @@ -0,0 +1,71 @@ +namespace = []; + $this->currentScope = []; + } + + /** + * @todo should this be final? Extending classes should be sure to either use + * leaveNode or call parent::enterNode() when overloading + * + * @param Node $node + */ + public function enterNode(Node $node) + { + if ($node instanceof Namespace_) { + $this->namespace = isset($node->name) ? $node->name->parts : []; + } + } + + /** + * Get a fully-qualified name (class, function, interface, etc). + * + * @param mixed $name + * + * @return string + */ + protected function getFullyQualifiedName($name): string + { + if ($name instanceof FullyQualifiedName) { + return \implode('\\', $name->parts); + } elseif ($name instanceof Name) { + $name = $name->parts; + } elseif (!\is_array($name)) { + $name = [$name]; + } + + return \implode('\\', \array_merge($this->namespace, $name)); + } +} diff --git a/vendor/psy/psysh/src/CodeCleaner/NamespacePass.php b/vendor/psy/psysh/src/CodeCleaner/NamespacePass.php new file mode 100644 index 0000000000..78c625bfaf --- /dev/null +++ b/vendor/psy/psysh/src/CodeCleaner/NamespacePass.php @@ -0,0 +1,88 @@ +cleaner = $cleaner; + } + + /** + * If this is a standalone namespace line, remember it for later. + * + * Otherwise, apply remembered namespaces to the code until a new namespace + * is encountered. + * + * @param array $nodes + */ + public function beforeTraverse(array $nodes) + { + if (empty($nodes)) { + return $nodes; + } + + $last = \end($nodes); + + if ($last instanceof Namespace_) { + $kind = $last->getAttribute('kind'); + + // Treat all namespace statements pre-PHP-Parser v3.1.2 as "open", + // even though we really have no way of knowing. + if ($kind === null || $kind === Namespace_::KIND_SEMICOLON) { + // Save the current namespace for open namespaces + $this->setNamespace($last->name); + } else { + // Clear the current namespace after a braced namespace + $this->setNamespace(null); + } + + return $nodes; + } + + return $this->namespace ? [new Namespace_($this->namespace, $nodes)] : $nodes; + } + + /** + * Remember the namespace and (re)set the namespace on the CodeCleaner as + * well. + * + * @param Name|null $namespace + */ + private function setNamespace($namespace) + { + $this->namespace = $namespace; + $this->cleaner->setNamespace($namespace === null ? null : $namespace->parts); + } +} diff --git a/vendor/psy/psysh/src/CodeCleaner/NoReturnValue.php b/vendor/psy/psysh/src/CodeCleaner/NoReturnValue.php new file mode 100644 index 0000000000..26cce8de94 --- /dev/null +++ b/vendor/psy/psysh/src/CodeCleaner/NoReturnValue.php @@ -0,0 +1,35 @@ +name instanceof Expr || $node->name instanceof Variable) { + return; + } + + $name = (string) $node->name; + + if ($name === 'array_multisort') { + return $this->validateArrayMultisort($node); + } + + try { + $refl = new \ReflectionFunction($name); + } catch (\ReflectionException $e) { + // Well, we gave it a shot! + return; + } + + foreach ($refl->getParameters() as $key => $param) { + if (\array_key_exists($key, $node->args)) { + $arg = $node->args[$key]; + if ($param->isPassedByReference() && !$this->isPassableByReference($arg)) { + throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, \E_ERROR, null, $node->getLine()); + } + } + } + } + } + + private function isPassableByReference(Node $arg): bool + { + // Unpacked arrays can be passed by reference + if ($arg->value instanceof Array_) { + return $arg->unpack; + } + + // FuncCall, MethodCall and StaticCall are all PHP _warnings_ not fatal errors, so we'll let + // PHP handle those ones :) + return $arg->value instanceof ClassConstFetch || + $arg->value instanceof PropertyFetch || + $arg->value instanceof Variable || + $arg->value instanceof FuncCall || + $arg->value instanceof MethodCall || + $arg->value instanceof StaticCall || + $arg->value instanceof ArrayDimFetch; + } + + /** + * Because array_multisort has a problematic signature... + * + * The argument order is all sorts of wonky, and whether something is passed + * by reference or not depends on the values of the two arguments before it. + * We'll do a good faith attempt at validating this, but err on the side of + * permissive. + * + * This is why you don't design languages where core code and extensions can + * implement APIs that wouldn't be possible in userland code. + * + * @throws FatalErrorException for clearly invalid arguments + * + * @param Node $node + */ + private function validateArrayMultisort(Node $node) + { + $nonPassable = 2; // start with 2 because the first one has to be passable by reference + foreach ($node->args as $arg) { + if ($this->isPassableByReference($arg)) { + $nonPassable = 0; + } elseif (++$nonPassable > 2) { + // There can be *at most* two non-passable-by-reference args in a row. This is about + // as close as we can get to validating the arguments for this function :-/ + throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, \E_ERROR, null, $node->getLine()); + } + } + } +} diff --git a/vendor/psy/psysh/src/CodeCleaner/RequirePass.php b/vendor/psy/psysh/src/CodeCleaner/RequirePass.php new file mode 100644 index 0000000000..b4d5e28f9a --- /dev/null +++ b/vendor/psy/psysh/src/CodeCleaner/RequirePass.php @@ -0,0 +1,131 @@ +isRequireNode($origNode)) { + return; + } + + $node = clone $origNode; + + /* + * rewrite + * + * $foo = require $bar + * + * to + * + * $foo = require \Psy\CodeCleaner\RequirePass::resolve($bar) + */ + $node->expr = new StaticCall( + new FullyQualifiedName(self::class), + 'resolve', + [new Arg($origNode->expr), new Arg(new LNumber($origNode->getLine()))], + $origNode->getAttributes() + ); + + return $node; + } + + /** + * Runtime validation that $file can be resolved as an include path. + * + * If $file can be resolved, return $file. Otherwise throw a fatal error exception. + * + * If $file collides with a path in the currently running PsySH phar, it will be resolved + * relative to the include path, to prevent PHP from grabbing the phar version of the file. + * + * @throws FatalErrorException when unable to resolve include path for $file + * @throws ErrorException if $file is empty and E_WARNING is included in error_reporting level + * + * @param string $file + * @param int $lineNumber Line number of the original require expression + * + * @return string Exactly the same as $file, unless $file collides with a path in the currently running phar + */ + public static function resolve($file, $lineNumber = null): string + { + $file = (string) $file; + + if ($file === '') { + // @todo Shell::handleError would be better here, because we could + // fake the file and line number, but we can't call it statically. + // So we're duplicating some of the logics here. + if (\E_WARNING & \error_reporting()) { + ErrorException::throwException(\E_WARNING, 'Filename cannot be empty', null, $lineNumber); + } + // @todo trigger an error as fallback? this is pretty ugly… + // trigger_error('Filename cannot be empty', E_USER_WARNING); + } + + $resolvedPath = \stream_resolve_include_path($file); + if ($file === '' || !$resolvedPath) { + $msg = \sprintf("Failed opening required '%s'", $file); + throw new FatalErrorException($msg, 0, \E_ERROR, null, $lineNumber); + } + + // Special case: if the path is not already relative or absolute, and it would resolve to + // something inside the currently running phar (e.g. `vendor/autoload.php`), we'll resolve + // it relative to the include path so PHP won't grab the phar version. + // + // Note that this only works if the phar has `psysh` in the path. We might want to lift this + // restriction and special case paths that would collide with any running phar? + if ($resolvedPath !== $file && $file[0] !== '.') { + $runningPhar = \Phar::running(); + if (\strpos($runningPhar, 'psysh') !== false && \is_file($runningPhar.\DIRECTORY_SEPARATOR.$file)) { + foreach (self::getIncludePath() as $prefix) { + $resolvedPath = $prefix.\DIRECTORY_SEPARATOR.$file; + if (\is_file($resolvedPath)) { + return $resolvedPath; + } + } + } + } + + return $file; + } + + private function isRequireNode(Node $node): bool + { + return $node instanceof Include_ && \in_array($node->type, self::$requireTypes); + } + + private static function getIncludePath(): array + { + if (\PATH_SEPARATOR === ':') { + return \preg_split('#:(?!//)#', \get_include_path()); + } + + return \explode(\PATH_SEPARATOR, \get_include_path()); + } +} diff --git a/vendor/psy/psysh/src/CodeCleaner/ReturnTypePass.php b/vendor/psy/psysh/src/CodeCleaner/ReturnTypePass.php new file mode 100644 index 0000000000..9a1bba9e5e --- /dev/null +++ b/vendor/psy/psysh/src/CodeCleaner/ReturnTypePass.php @@ -0,0 +1,123 @@ +atLeastPhp71 = \version_compare(\PHP_VERSION, '7.1', '>='); + } + + /** + * {@inheritdoc} + */ + public function enterNode(Node $node) + { + if (!$this->atLeastPhp71) { + return; // @codeCoverageIgnore + } + + if ($this->isFunctionNode($node)) { + $this->returnTypeStack[] = $node->returnType; + + return; + } + + if (!empty($this->returnTypeStack) && $node instanceof Return_) { + $expectedType = \end($this->returnTypeStack); + if ($expectedType === null) { + return; + } + + $msg = null; + + if ($this->typeName($expectedType) === 'void') { + // Void functions + if ($expectedType instanceof NullableType) { + $msg = self::NULLABLE_VOID_MESSAGE; + } elseif ($node->expr instanceof ConstFetch && \strtolower($node->expr->name) === 'null') { + $msg = self::VOID_NULL_MESSAGE; + } elseif ($node->expr !== null) { + $msg = self::VOID_MESSAGE; + } + } else { + // Everything else + if ($node->expr === null) { + $msg = $expectedType instanceof NullableType ? self::NULLABLE_MESSAGE : self::MESSAGE; + } + } + + if ($msg !== null) { + throw new FatalErrorException($msg, 0, \E_ERROR, null, $node->getLine()); + } + } + } + + /** + * {@inheritdoc} + */ + public function leaveNode(Node $node) + { + if (!$this->atLeastPhp71) { + return; // @codeCoverageIgnore + } + + if (!empty($this->returnTypeStack) && $this->isFunctionNode($node)) { + \array_pop($this->returnTypeStack); + } + } + + private function isFunctionNode(Node $node): bool + { + return $node instanceof Function_ || $node instanceof Closure; + } + + private function typeName(Node $node): string + { + if ($node instanceof UnionType) { + return \implode('|', \array_map([$this, 'typeName'], $node->types)); + } + + if ($node instanceof NullableType) { + return \strtolower($node->type->name); + } + + if ($node instanceof Identifier) { + return \strtolower($node->name); + } + + throw new \InvalidArgumentException('Unable to find type name'); + } +} diff --git a/vendor/psy/psysh/src/CodeCleaner/StrictTypesPass.php b/vendor/psy/psysh/src/CodeCleaner/StrictTypesPass.php new file mode 100644 index 0000000000..08ec5e68af --- /dev/null +++ b/vendor/psy/psysh/src/CodeCleaner/StrictTypesPass.php @@ -0,0 +1,77 @@ +strictTypes; + + foreach ($nodes as $node) { + if ($node instanceof Declare_) { + foreach ($node->declares as $declare) { + // For PHP Parser 4.x + $declareKey = $declare->key instanceof Identifier ? $declare->key->toString() : $declare->key; + if ($declareKey === 'strict_types') { + $value = $declare->value; + if (!$value instanceof LNumber || ($value->value !== 0 && $value->value !== 1)) { + throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, \E_ERROR, null, $node->getLine()); + } + + $this->strictTypes = $value->value === 1; + } + } + } + } + + if ($prependStrictTypes) { + $first = \reset($nodes); + if (!$first instanceof Declare_) { + $declare = new Declare_([new DeclareDeclare('strict_types', new LNumber(1))]); + \array_unshift($nodes, $declare); + } + } + + return $nodes; + } +} diff --git a/vendor/psy/psysh/src/CodeCleaner/UseStatementPass.php b/vendor/psy/psysh/src/CodeCleaner/UseStatementPass.php new file mode 100644 index 0000000000..e36a1b5568 --- /dev/null +++ b/vendor/psy/psysh/src/CodeCleaner/UseStatementPass.php @@ -0,0 +1,136 @@ +name ?: '') === \strtolower($this->lastNamespace ?: '')) { + $this->aliases = $this->lastAliases; + } + } + } + + /** + * If this statement is a namespace, forget all the aliases we had. + * + * If it's a use statement, remember the alias for later. Otherwise, apply + * remembered aliases to the code. + * + * @param Node $node + */ + public function leaveNode(Node $node) + { + // Store a reference to every "use" statement, because we'll need them in a bit. + if ($node instanceof Use_) { + foreach ($node->uses as $use) { + $alias = $use->alias ?: \end($use->name->parts); + $this->aliases[\strtolower($alias)] = $use->name; + } + + return NodeTraverser::REMOVE_NODE; + } + + // Expand every "use" statement in the group into a full, standalone "use" and store 'em with the others. + if ($node instanceof GroupUse) { + foreach ($node->uses as $use) { + $alias = $use->alias ?: \end($use->name->parts); + $this->aliases[\strtolower($alias)] = Name::concat($node->prefix, $use->name, [ + 'startLine' => $node->prefix->getAttribute('startLine'), + 'endLine' => $use->name->getAttribute('endLine'), + ]); + } + + return NodeTraverser::REMOVE_NODE; + } + + // Start fresh, since we're done with this namespace. + if ($node instanceof Namespace_) { + $this->lastNamespace = $node->name; + $this->lastAliases = $this->aliases; + $this->aliases = []; + + return; + } + + // Do nothing with UseUse; this an entry in the list of uses in the use statement. + if ($node instanceof UseUse) { + return; + } + + // For everything else, we'll implicitly thunk all aliases into fully-qualified names. + foreach ($node as $name => $subNode) { + if ($subNode instanceof Name) { + if ($replacement = $this->findAlias($subNode)) { + $node->$name = $replacement; + } + } + } + + return $node; + } + + /** + * Find class/namespace aliases. + * + * @param Name $name + * + * @return FullyQualifiedName|null + */ + private function findAlias(Name $name) + { + $that = \strtolower($name); + foreach ($this->aliases as $alias => $prefix) { + if ($that === $alias) { + return new FullyQualifiedName($prefix->toString()); + } elseif (\substr($that, 0, \strlen($alias) + 1) === $alias.'\\') { + return new FullyQualifiedName($prefix->toString().\substr($name, \strlen($alias))); + } + } + } +} diff --git a/vendor/psy/psysh/src/CodeCleaner/ValidClassNamePass.php b/vendor/psy/psysh/src/CodeCleaner/ValidClassNamePass.php new file mode 100644 index 0000000000..d077dea7cc --- /dev/null +++ b/vendor/psy/psysh/src/CodeCleaner/ValidClassNamePass.php @@ -0,0 +1,353 @@ +conditionalScopes++; + + return; + } + + if ($this->conditionalScopes === 0) { + if ($node instanceof Class_) { + $this->validateClassStatement($node); + } elseif ($node instanceof Interface_) { + $this->validateInterfaceStatement($node); + } elseif ($node instanceof Trait_) { + $this->validateTraitStatement($node); + } + } + } + + /** + * @param Node $node + */ + public function leaveNode(Node $node) + { + if (self::isConditional($node)) { + $this->conditionalScopes--; + + return; + } + } + + private static function isConditional(Node $node): bool + { + return $node instanceof If_ || + $node instanceof While_ || + $node instanceof Do_ || + $node instanceof Switch_ || + $node instanceof Ternary; + } + + /** + * Validate a class definition statement. + * + * @param Class_ $stmt + */ + protected function validateClassStatement(Class_ $stmt) + { + $this->ensureCanDefine($stmt, self::CLASS_TYPE); + if (isset($stmt->extends)) { + $this->ensureClassExists($this->getFullyQualifiedName($stmt->extends), $stmt); + } + $this->ensureInterfacesExist($stmt->implements, $stmt); + } + + /** + * Validate an interface definition statement. + * + * @param Interface_ $stmt + */ + protected function validateInterfaceStatement(Interface_ $stmt) + { + $this->ensureCanDefine($stmt, self::INTERFACE_TYPE); + $this->ensureInterfacesExist($stmt->extends, $stmt); + } + + /** + * Validate a trait definition statement. + * + * @param Trait_ $stmt + */ + protected function validateTraitStatement(Trait_ $stmt) + { + $this->ensureCanDefine($stmt, self::TRAIT_TYPE); + } + + /** + * Ensure that no class, interface or trait name collides with a new definition. + * + * @throws FatalErrorException + * + * @param Stmt $stmt + * @param string $scopeType + */ + protected function ensureCanDefine(Stmt $stmt, string $scopeType = self::CLASS_TYPE) + { + // Anonymous classes don't have a name, and uniqueness shouldn't be enforced. + if ($stmt->name === null) { + return; + } + + $name = $this->getFullyQualifiedName($stmt->name); + + // check for name collisions + $errorType = null; + if ($this->classExists($name)) { + $errorType = self::CLASS_TYPE; + } elseif ($this->interfaceExists($name)) { + $errorType = self::INTERFACE_TYPE; + } elseif ($this->traitExists($name)) { + $errorType = self::TRAIT_TYPE; + } + + if ($errorType !== null) { + throw $this->createError(\sprintf('%s named %s already exists', \ucfirst($errorType), $name), $stmt); + } + + // Store creation for the rest of this code snippet so we can find local + // issue too + $this->currentScope[\strtolower($name)] = $scopeType; + } + + /** + * Ensure that a referenced class exists. + * + * @throws FatalErrorException + * + * @param string $name + * @param Stmt $stmt + */ + protected function ensureClassExists(string $name, Stmt $stmt) + { + if (!$this->classExists($name)) { + throw $this->createError(\sprintf('Class \'%s\' not found', $name), $stmt); + } + } + + /** + * Ensure that a referenced class _or interface_ exists. + * + * @throws FatalErrorException + * + * @param string $name + * @param Stmt $stmt + */ + protected function ensureClassOrInterfaceExists(string $name, Stmt $stmt) + { + if (!$this->classExists($name) && !$this->interfaceExists($name)) { + throw $this->createError(\sprintf('Class \'%s\' not found', $name), $stmt); + } + } + + /** + * Ensure that a referenced class _or trait_ exists. + * + * @throws FatalErrorException + * + * @param string $name + * @param Stmt $stmt + */ + protected function ensureClassOrTraitExists(string $name, Stmt $stmt) + { + if (!$this->classExists($name) && !$this->traitExists($name)) { + throw $this->createError(\sprintf('Class \'%s\' not found', $name), $stmt); + } + } + + /** + * Ensure that a statically called method exists. + * + * @throws FatalErrorException + * + * @param string $class + * @param string $name + * @param Stmt $stmt + */ + protected function ensureMethodExists(string $class, string $name, Stmt $stmt) + { + $this->ensureClassOrTraitExists($class, $stmt); + + // let's pretend all calls to self, parent and static are valid + if (\in_array(\strtolower($class), ['self', 'parent', 'static'])) { + return; + } + + // ... and all calls to classes defined right now + if ($this->findInScope($class) === self::CLASS_TYPE) { + return; + } + + // if method name is an expression, give it a pass for now + if ($name instanceof Expr) { + return; + } + + if (!\method_exists($class, $name) && !\method_exists($class, '__callStatic')) { + throw $this->createError(\sprintf('Call to undefined method %s::%s()', $class, $name), $stmt); + } + } + + /** + * Ensure that a referenced interface exists. + * + * @throws FatalErrorException + * + * @param Interface_[] $interfaces + * @param Stmt $stmt + */ + protected function ensureInterfacesExist(array $interfaces, Stmt $stmt) + { + foreach ($interfaces as $interface) { + /** @var string $name */ + $name = $this->getFullyQualifiedName($interface); + if (!$this->interfaceExists($name)) { + throw $this->createError(\sprintf('Interface \'%s\' not found', $name), $stmt); + } + } + } + + /** + * Get a symbol type key for storing in the scope name cache. + * + * @deprecated No longer used. Scope type should be passed into ensureCanDefine directly. + * @codeCoverageIgnore + * + * @param Stmt $stmt + * + * @return string + */ + protected function getScopeType(Stmt $stmt): string + { + if ($stmt instanceof Class_) { + return self::CLASS_TYPE; + } elseif ($stmt instanceof Interface_) { + return self::INTERFACE_TYPE; + } elseif ($stmt instanceof Trait_) { + return self::TRAIT_TYPE; + } + } + + /** + * Check whether a class exists, or has been defined in the current code snippet. + * + * Gives `self`, `static` and `parent` a free pass. + * + * @param string $name + * + * @return bool + */ + protected function classExists(string $name): bool + { + // Give `self`, `static` and `parent` a pass. This will actually let + // some errors through, since we're not checking whether the keyword is + // being used in a class scope. + if (\in_array(\strtolower($name), ['self', 'static', 'parent'])) { + return true; + } + + return \class_exists($name) || $this->findInScope($name) === self::CLASS_TYPE; + } + + /** + * Check whether an interface exists, or has been defined in the current code snippet. + * + * @param string $name + * + * @return bool + */ + protected function interfaceExists(string $name): bool + { + return \interface_exists($name) || $this->findInScope($name) === self::INTERFACE_TYPE; + } + + /** + * Check whether a trait exists, or has been defined in the current code snippet. + * + * @param string $name + * + * @return bool + */ + protected function traitExists(string $name): bool + { + return \trait_exists($name) || $this->findInScope($name) === self::TRAIT_TYPE; + } + + /** + * Find a symbol in the current code snippet scope. + * + * @param string $name + * + * @return string|null + */ + protected function findInScope(string $name) + { + $name = \strtolower($name); + if (isset($this->currentScope[$name])) { + return $this->currentScope[$name]; + } + } + + /** + * Error creation factory. + * + * @param string $msg + * @param Stmt $stmt + * + * @return FatalErrorException + */ + protected function createError(string $msg, Stmt $stmt): FatalErrorException + { + return new FatalErrorException($msg, 0, \E_ERROR, null, $stmt->getLine()); + } +} diff --git a/vendor/psy/psysh/src/CodeCleaner/ValidConstructorPass.php b/vendor/psy/psysh/src/CodeCleaner/ValidConstructorPass.php new file mode 100644 index 0000000000..fac59103e9 --- /dev/null +++ b/vendor/psy/psysh/src/CodeCleaner/ValidConstructorPass.php @@ -0,0 +1,112 @@ + + */ +class ValidConstructorPass extends CodeCleanerPass +{ + private $namespace; + + public function beforeTraverse(array $nodes) + { + $this->namespace = []; + } + + /** + * Validate that the constructor is not static and does not have a return type. + * + * @throws FatalErrorException the constructor function is static + * @throws FatalErrorException the constructor function has a return type + * + * @param Node $node + */ + public function enterNode(Node $node) + { + if ($node instanceof Namespace_) { + $this->namespace = isset($node->name) ? $node->name->parts : []; + } elseif ($node instanceof Class_) { + $constructor = null; + foreach ($node->stmts as $stmt) { + if ($stmt instanceof ClassMethod) { + // If we find a new-style constructor, no need to look for the old-style + if ('__construct' === \strtolower($stmt->name)) { + $this->validateConstructor($stmt, $node); + + return; + } + + // We found a possible old-style constructor (unless there is also a __construct method) + if (empty($this->namespace) && \strtolower($node->name) === \strtolower($stmt->name)) { + $constructor = $stmt; + } + } + } + + if ($constructor) { + $this->validateConstructor($constructor, $node); + } + } + } + + /** + * @throws FatalErrorException the constructor function is static + * @throws FatalErrorException the constructor function has a return type + * + * @param Node $constructor + * @param Node $classNode + */ + private function validateConstructor(Node $constructor, Node $classNode) + { + if ($constructor->isStatic()) { + // For PHP Parser 4.x + $className = $classNode->name instanceof Identifier ? $classNode->name->toString() : $classNode->name; + + $msg = \sprintf( + 'Constructor %s::%s() cannot be static', + \implode('\\', \array_merge($this->namespace, (array) $className)), + $constructor->name + ); + throw new FatalErrorException($msg, 0, \E_ERROR, null, $classNode->getLine()); + } + + if (\method_exists($constructor, 'getReturnType') && $constructor->getReturnType()) { + // For PHP Parser 4.x + $className = $classNode->name instanceof Identifier ? $classNode->name->toString() : $classNode->name; + + $msg = \sprintf( + 'Constructor %s::%s() cannot declare a return type', + \implode('\\', \array_merge($this->namespace, (array) $className)), + $constructor->name + ); + throw new FatalErrorException($msg, 0, \E_ERROR, null, $classNode->getLine()); + } + } +} diff --git a/vendor/psy/psysh/src/CodeCleaner/ValidFunctionNamePass.php b/vendor/psy/psysh/src/CodeCleaner/ValidFunctionNamePass.php new file mode 100644 index 0000000000..2e35c7f823 --- /dev/null +++ b/vendor/psy/psysh/src/CodeCleaner/ValidFunctionNamePass.php @@ -0,0 +1,79 @@ +conditionalScopes++; + } elseif ($node instanceof Function_) { + $name = $this->getFullyQualifiedName($node->name); + + // @todo add an "else" here which adds a runtime check for instances where we can't tell + // whether a function is being redefined by static analysis alone. + if ($this->conditionalScopes === 0) { + if (\function_exists($name) || + isset($this->currentScope[\strtolower($name)])) { + $msg = \sprintf('Cannot redeclare %s()', $name); + throw new FatalErrorException($msg, 0, \E_ERROR, null, $node->getLine()); + } + } + + $this->currentScope[\strtolower($name)] = true; + } + } + + /** + * @param Node $node + */ + public function leaveNode(Node $node) + { + if (self::isConditional($node)) { + $this->conditionalScopes--; + } + } + + private static function isConditional(Node $node) + { + return $node instanceof If_ || + $node instanceof While_ || + $node instanceof Do_ || + $node instanceof Switch_; + } +} diff --git a/vendor/psy/psysh/src/Command/BufferCommand.php b/vendor/psy/psysh/src/Command/BufferCommand.php new file mode 100644 index 0000000000..f563fb19bd --- /dev/null +++ b/vendor/psy/psysh/src/Command/BufferCommand.php @@ -0,0 +1,79 @@ +setName('buffer') + ->setAliases(['buf']) + ->setDefinition([ + new InputOption('clear', '', InputOption::VALUE_NONE, 'Clear the current buffer.'), + ]) + ->setDescription('Show (or clear) the contents of the code input buffer.') + ->setHelp( + <<<'HELP' +Show the contents of the code buffer for the current multi-line expression. + +Optionally, clear the buffer by passing the --clear option. +HELP + ); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $buf = $this->getApplication()->getCodeBuffer(); + if ($input->getOption('clear')) { + $this->getApplication()->resetCodeBuffer(); + $output->writeln($this->formatLines($buf, 'urgent'), ShellOutput::NUMBER_LINES); + } else { + $output->writeln($this->formatLines($buf), ShellOutput::NUMBER_LINES); + } + + return 0; + } + + /** + * A helper method for wrapping buffer lines in `` and `` formatter strings. + * + * @param array $lines + * @param string $type (default: 'return') + * + * @return array Formatted strings + */ + protected function formatLines(array $lines, string $type = 'return'): array + { + $template = \sprintf('<%s>%%s', $type, $type); + + return \array_map(function ($line) use ($template) { + return \sprintf($template, $line); + }, $lines); + } +} diff --git a/vendor/psy/psysh/src/Command/ClearCommand.php b/vendor/psy/psysh/src/Command/ClearCommand.php new file mode 100644 index 0000000000..72901cd08d --- /dev/null +++ b/vendor/psy/psysh/src/Command/ClearCommand.php @@ -0,0 +1,51 @@ +setName('clear') + ->setDefinition([]) + ->setDescription('Clear the Psy Shell screen.') + ->setHelp( + <<<'HELP' +Clear the Psy Shell screen. + +Pro Tip: If your PHP has readline support, you should be able to use ctrl+l too! +HELP + ); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $output->write(\sprintf('%c[2J%c[0;0f', 27, 27)); + + return 0; + } +} diff --git a/vendor/psy/psysh/src/Command/Command.php b/vendor/psy/psysh/src/Command/Command.php new file mode 100644 index 0000000000..ec219ecb37 --- /dev/null +++ b/vendor/psy/psysh/src/Command/Command.php @@ -0,0 +1,289 @@ +Usage:', + ' '.$this->getSynopsis(), + '', + ]; + + if ($this->getAliases()) { + $messages[] = $this->aliasesAsText(); + } + + if ($this->getArguments()) { + $messages[] = $this->argumentsAsText(); + } + + if ($this->getOptions()) { + $messages[] = $this->optionsAsText(); + } + + if ($help = $this->getProcessedHelp()) { + $messages[] = 'Help:'; + $messages[] = ' '.\str_replace("\n", "\n ", $help)."\n"; + } + + return \implode("\n", $messages); + } + + /** + * {@inheritdoc} + */ + private function getArguments(): array + { + $hidden = $this->getHiddenArguments(); + + return \array_filter($this->getNativeDefinition()->getArguments(), function ($argument) use ($hidden) { + return !\in_array($argument->getName(), $hidden); + }); + } + + /** + * These arguments will be excluded from help output. + * + * @return array + */ + protected function getHiddenArguments(): array + { + return ['command']; + } + + /** + * {@inheritdoc} + */ + private function getOptions(): array + { + $hidden = $this->getHiddenOptions(); + + return \array_filter($this->getNativeDefinition()->getOptions(), function ($option) use ($hidden) { + return !\in_array($option->getName(), $hidden); + }); + } + + /** + * These options will be excluded from help output. + * + * @return array + */ + protected function getHiddenOptions(): array + { + return ['verbose']; + } + + /** + * Format command aliases as text.. + * + * @return string + */ + private function aliasesAsText(): string + { + return 'Aliases: '.\implode(', ', $this->getAliases()).''.\PHP_EOL; + } + + /** + * Format command arguments as text. + * + * @return string + */ + private function argumentsAsText(): string + { + $max = $this->getMaxWidth(); + $messages = []; + + $arguments = $this->getArguments(); + if (!empty($arguments)) { + $messages[] = 'Arguments:'; + foreach ($arguments as $argument) { + if (null !== $argument->getDefault() && (!\is_array($argument->getDefault()) || \count($argument->getDefault()))) { + $default = \sprintf(' (default: %s)', $this->formatDefaultValue($argument->getDefault())); + } else { + $default = ''; + } + + $description = \str_replace("\n", "\n".\str_pad('', $max + 2, ' '), $argument->getDescription()); + + $messages[] = \sprintf(" %-{$max}s %s%s", $argument->getName(), $description, $default); + } + + $messages[] = ''; + } + + return \implode(\PHP_EOL, $messages); + } + + /** + * Format options as text. + * + * @return string + */ + private function optionsAsText(): string + { + $max = $this->getMaxWidth(); + $messages = []; + + $options = $this->getOptions(); + if ($options) { + $messages[] = 'Options:'; + + foreach ($options as $option) { + if ($option->acceptValue() && null !== $option->getDefault() && (!\is_array($option->getDefault()) || \count($option->getDefault()))) { + $default = \sprintf(' (default: %s)', $this->formatDefaultValue($option->getDefault())); + } else { + $default = ''; + } + + $multiple = $option->isArray() ? ' (multiple values allowed)' : ''; + $description = \str_replace("\n", "\n".\str_pad('', $max + 2, ' '), $option->getDescription()); + + $optionMax = $max - \strlen($option->getName()) - 2; + $messages[] = \sprintf( + " %s %-{$optionMax}s%s%s%s", + '--'.$option->getName(), + $option->getShortcut() ? \sprintf('(-%s) ', $option->getShortcut()) : '', + $description, + $default, + $multiple + ); + } + + $messages[] = ''; + } + + return \implode(\PHP_EOL, $messages); + } + + /** + * Calculate the maximum padding width for a set of lines. + * + * @return int + */ + private function getMaxWidth(): int + { + $max = 0; + + foreach ($this->getOptions() as $option) { + $nameLength = \strlen($option->getName()) + 2; + if ($option->getShortcut()) { + $nameLength += \strlen($option->getShortcut()) + 3; + } + + $max = \max($max, $nameLength); + } + + foreach ($this->getArguments() as $argument) { + $max = \max($max, \strlen($argument->getName())); + } + + return ++$max; + } + + /** + * Format an option default as text. + * + * @param mixed $default + * + * @return string + */ + private function formatDefaultValue($default): string + { + if (\is_array($default) && $default === \array_values($default)) { + return \sprintf("['%s']", \implode("', '", $default)); + } + + return \str_replace("\n", '', \var_export($default, true)); + } + + /** + * Get a Table instance. + * + * Falls back to legacy TableHelper. + * + * @return Table|TableHelper + */ + protected function getTable(OutputInterface $output) + { + if (!\class_exists(Table::class)) { + return $this->getTableHelper(); + } + + $style = new TableStyle(); + + // Symfony 4.1 deprecated single-argument style setters. + if (\method_exists($style, 'setVerticalBorderChars')) { + $style->setVerticalBorderChars(' '); + $style->setHorizontalBorderChars(''); + $style->setCrossingChars('', '', '', '', '', '', '', '', ''); + } else { + $style->setVerticalBorderChar(' '); + $style->setHorizontalBorderChar(''); + $style->setCrossingChar(''); + } + + $table = new Table($output); + + return $table + ->setRows([]) + ->setStyle($style); + } + + /** + * Legacy fallback for getTable. + * + * @return TableHelper + */ + protected function getTableHelper(): TableHelper + { + $table = $this->getApplication()->getHelperSet()->get('table'); + + return $table + ->setRows([]) + ->setLayout(TableHelper::LAYOUT_BORDERLESS) + ->setHorizontalBorderChar('') + ->setCrossingChar(''); + } +} diff --git a/vendor/psy/psysh/src/Command/DocCommand.php b/vendor/psy/psysh/src/Command/DocCommand.php new file mode 100644 index 0000000000..c50790f28a --- /dev/null +++ b/vendor/psy/psysh/src/Command/DocCommand.php @@ -0,0 +1,250 @@ +setName('doc') + ->setAliases(['rtfm', 'man']) + ->setDefinition([ + new InputOption('all', 'a', InputOption::VALUE_NONE, 'Show documentation for superclasses as well as the current class.'), + new CodeArgument('target', CodeArgument::REQUIRED, 'Function, class, instance, constant, method or property to document.'), + ]) + ->setDescription('Read the documentation for an object, class, constant, method or property.') + ->setHelp( + <<>>> doc preg_replace +>>> doc Psy\Shell +>>> doc Psy\Shell::debug +>>> \$s = new Psy\Shell +>>> doc \$s->run +HELP + ); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $value = $input->getArgument('target'); + if (ReflectionLanguageConstruct::isLanguageConstruct($value)) { + $reflector = new ReflectionLanguageConstruct($value); + $doc = $this->getManualDocById($value); + } else { + list($target, $reflector) = $this->getTargetAndReflector($value); + $doc = $this->getManualDoc($reflector) ?: DocblockFormatter::format($reflector); + } + + $db = $this->getApplication()->getManualDb(); + + if ($output instanceof ShellOutput) { + $output->startPaging(); + } + + // Maybe include the declaring class + if ($reflector instanceof \ReflectionMethod || $reflector instanceof \ReflectionProperty) { + $output->writeln(SignatureFormatter::format($reflector->getDeclaringClass())); + } + + $output->writeln(SignatureFormatter::format($reflector)); + $output->writeln(''); + + if (empty($doc) && !$db) { + $output->writeln('PHP manual not found'); + $output->writeln(' To document core PHP functionality, download the PHP reference manual:'); + $output->writeln(' https://github.com/bobthecow/psysh/wiki/PHP-manual'); + } else { + $output->writeln($doc); + } + + // Implicit --all if the original docblock has an {@inheritdoc} tag. + if ($input->getOption('all') || \stripos($doc, self::INHERIT_DOC_TAG) !== false) { + $parent = $reflector; + foreach ($this->getParentReflectors($reflector) as $parent) { + $output->writeln(''); + $output->writeln('---'); + $output->writeln(''); + + // Maybe include the declaring class + if ($parent instanceof \ReflectionMethod || $parent instanceof \ReflectionProperty) { + $output->writeln(SignatureFormatter::format($parent->getDeclaringClass())); + } + + $output->writeln(SignatureFormatter::format($parent)); + $output->writeln(''); + + if ($doc = $this->getManualDoc($parent) ?: DocblockFormatter::format($parent)) { + $output->writeln($doc); + } + } + } + + if ($output instanceof ShellOutput) { + $output->stopPaging(); + } + + // Set some magic local variables + $this->setCommandScopeVariables($reflector); + + return 0; + } + + private function getManualDoc($reflector) + { + switch (\get_class($reflector)) { + case \ReflectionClass::class: + case \ReflectionObject::class: + case \ReflectionFunction::class: + $id = $reflector->name; + break; + + case \ReflectionMethod::class: + $id = $reflector->class.'::'.$reflector->name; + break; + + case \ReflectionProperty::class: + $id = $reflector->class.'::$'.$reflector->name; + break; + + case \ReflectionClassConstant::class: + case ReflectionClassConstant::class: + // @todo this is going to collide with ReflectionMethod ids + // someday... start running the query by id + type if the DB + // supports it. + $id = $reflector->class.'::'.$reflector->name; + break; + + case ReflectionConstant_::class: + $id = $reflector->name; + break; + + default: + return false; + } + + return $this->getManualDocById($id); + } + + /** + * Get all all parent Reflectors for a given Reflector. + * + * For example, passing a Class, Object or TraitReflector will yield all + * traits and parent classes. Passing a Method or PropertyReflector will + * yield Reflectors for the same-named method or property on all traits and + * parent classes. + * + * @return \Generator a whole bunch of \Reflector instances + */ + private function getParentReflectors($reflector): \Generator + { + $seenClasses = []; + + switch (\get_class($reflector)) { + case \ReflectionClass::class: + case \ReflectionObject::class: + foreach ($reflector->getTraits() as $trait) { + if (!\in_array($trait->getName(), $seenClasses)) { + $seenClasses[] = $trait->getName(); + yield $trait; + } + } + + foreach ($reflector->getInterfaces() as $interface) { + if (!\in_array($interface->getName(), $seenClasses)) { + $seenClasses[] = $interface->getName(); + yield $interface; + } + } + + while ($reflector = $reflector->getParentClass()) { + yield $reflector; + + foreach ($reflector->getTraits() as $trait) { + if (!\in_array($trait->getName(), $seenClasses)) { + $seenClasses[] = $trait->getName(); + yield $trait; + } + } + + foreach ($reflector->getInterfaces() as $interface) { + if (!\in_array($interface->getName(), $seenClasses)) { + $seenClasses[] = $interface->getName(); + yield $interface; + } + } + } + + return; + + case \ReflectionMethod::class: + foreach ($this->getParentReflectors($reflector->getDeclaringClass()) as $parent) { + if ($parent->hasMethod($reflector->getName())) { + $parentMethod = $parent->getMethod($reflector->getName()); + if (!\in_array($parentMethod->getDeclaringClass()->getName(), $seenClasses)) { + $seenClasses[] = $parentMethod->getDeclaringClass()->getName(); + yield $parentMethod; + } + } + } + + return; + + case \ReflectionProperty::class: + foreach ($this->getParentReflectors($reflector->getDeclaringClass()) as $parent) { + if ($parent->hasProperty($reflector->getName())) { + $parentProperty = $parent->getProperty($reflector->getName()); + if (!\in_array($parentProperty->getDeclaringClass()->getName(), $seenClasses)) { + $seenClasses[] = $parentProperty->getDeclaringClass()->getName(); + yield $parentProperty; + } + } + } + break; + } + } + + private function getManualDocById($id) + { + if ($db = $this->getApplication()->getManualDb()) { + return $db + ->query(\sprintf('SELECT doc FROM php_manual WHERE id = %s', $db->quote($id))) + ->fetchColumn(0); + } + } +} diff --git a/vendor/psy/psysh/src/Command/DumpCommand.php b/vendor/psy/psysh/src/Command/DumpCommand.php new file mode 100644 index 0000000000..24e7c55481 --- /dev/null +++ b/vendor/psy/psysh/src/Command/DumpCommand.php @@ -0,0 +1,96 @@ +presenter = $presenter; + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('dump') + ->setDefinition([ + new CodeArgument('target', CodeArgument::REQUIRED, 'A target object or primitive to dump.'), + new InputOption('depth', '', InputOption::VALUE_REQUIRED, 'Depth to parse.', 10), + new InputOption('all', 'a', InputOption::VALUE_NONE, 'Include private and protected methods and properties.'), + ]) + ->setDescription('Dump an object or primitive.') + ->setHelp( + <<<'HELP' +Dump an object or primitive. + +This is like var_dump but way awesomer. + +e.g. +>>> dump $_ +>>> dump $someVar +>>> dump $stuff->getAll() +HELP + ); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $depth = $input->getOption('depth'); + $target = $this->resolveCode($input->getArgument('target')); + $output->page($this->presenter->present($target, $depth, $input->getOption('all') ? Presenter::VERBOSE : 0)); + + if (\is_object($target)) { + $this->setCommandScopeVariables(new \ReflectionObject($target)); + } + + return 0; + } + + /** + * @deprecated Use `resolveCode` instead + * + * @param string $name + * + * @return mixed + */ + protected function resolveTarget(string $name) + { + @\trigger_error('`resolveTarget` is deprecated; use `resolveCode` instead.', \E_USER_DEPRECATED); + + return $this->resolveCode($name); + } +} diff --git a/vendor/psy/psysh/src/Command/EditCommand.php b/vendor/psy/psysh/src/Command/EditCommand.php new file mode 100644 index 0000000000..96854e7b0c --- /dev/null +++ b/vendor/psy/psysh/src/Command/EditCommand.php @@ -0,0 +1,190 @@ +runtimeDir = $runtimeDir; + } + + protected function configure() + { + $this + ->setName('edit') + ->setDefinition([ + new InputArgument('file', InputArgument::OPTIONAL, 'The file to open for editing. If this is not given, edits a temporary file.', null), + new InputOption( + 'exec', + 'e', + InputOption::VALUE_NONE, + 'Execute the file content after editing. This is the default when a file name argument is not given.', + null + ), + new InputOption( + 'no-exec', + 'E', + InputOption::VALUE_NONE, + 'Do not execute the file content after editing. This is the default when a file name argument is given.', + null + ), + ]) + ->setDescription('Open an external editor. Afterwards, get produced code in input buffer.') + ->setHelp('Set the EDITOR environment variable to something you\'d like to use.'); + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * + * @throws \InvalidArgumentException when both exec and no-exec flags are given or if a given variable is not found in the current context + * @throws \UnexpectedValueException if file_get_contents on the edited file returns false instead of a string + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + if ($input->getOption('exec') && + $input->getOption('no-exec')) { + throw new \InvalidArgumentException('The --exec and --no-exec flags are mutually exclusive'); + } + + $filePath = $this->extractFilePath($input->getArgument('file')); + + $execute = $this->shouldExecuteFile( + $input->getOption('exec'), + $input->getOption('no-exec'), + $filePath + ); + + $shouldRemoveFile = false; + + if ($filePath === null) { + $filePath = \tempnam($this->runtimeDir, 'psysh-edit-command'); + $shouldRemoveFile = true; + } + + $editedContent = $this->editFile($filePath, $shouldRemoveFile); + + if ($execute) { + $this->getApplication()->addInput($editedContent); + } + + return 0; + } + + /** + * @param bool $execOption + * @param bool $noExecOption + * @param string|null $filePath + * + * @return bool + */ + private function shouldExecuteFile(bool $execOption, bool $noExecOption, string $filePath = null): bool + { + if ($execOption) { + return true; + } + + if ($noExecOption) { + return false; + } + + // By default, code that is edited is executed if there was no given input file path + return $filePath === null; + } + + /** + * @param string|null $fileArgument + * + * @return string|null The file path to edit, null if the input was null, or the value of the referenced variable + * + * @throws \InvalidArgumentException If the variable is not found in the current context + */ + private function extractFilePath(string $fileArgument = null) + { + // If the file argument was a variable, get it from the context + if ($fileArgument !== null && + $fileArgument !== '' && + $fileArgument[0] === '$') { + $fileArgument = $this->context->get(\preg_replace('/^\$/', '', $fileArgument)); + } + + return $fileArgument; + } + + /** + * @param string $filePath + * @param bool $shouldRemoveFile + * + * @return string + * + * @throws \UnexpectedValueException if file_get_contents on $filePath returns false instead of a string + */ + private function editFile(string $filePath, bool $shouldRemoveFile): string + { + $escapedFilePath = \escapeshellarg($filePath); + $editor = (isset($_SERVER['EDITOR']) && $_SERVER['EDITOR']) ? $_SERVER['EDITOR'] : 'nano'; + + $pipes = []; + $proc = \proc_open("{$editor} {$escapedFilePath}", [\STDIN, \STDOUT, \STDERR], $pipes); + \proc_close($proc); + + $editedContent = @\file_get_contents($filePath); + + if ($shouldRemoveFile) { + @\unlink($filePath); + } + + if ($editedContent === false) { + throw new \UnexpectedValueException("Reading {$filePath} returned false"); + } + + return $editedContent; + } + + /** + * Set the Context reference. + * + * @param Context $context + */ + public function setContext(Context $context) + { + $this->context = $context; + } +} diff --git a/vendor/psy/psysh/src/Command/ExitCommand.php b/vendor/psy/psysh/src/Command/ExitCommand.php new file mode 100644 index 0000000000..183438f951 --- /dev/null +++ b/vendor/psy/psysh/src/Command/ExitCommand.php @@ -0,0 +1,52 @@ +setName('exit') + ->setAliases(['quit', 'q']) + ->setDefinition([]) + ->setDescription('End the current session and return to caller.') + ->setHelp( + <<<'HELP' +End the current session and return to caller. + +e.g. +>>> exit +HELP + ); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + throw new BreakException('Goodbye'); + } +} diff --git a/vendor/psy/psysh/src/Command/HelpCommand.php b/vendor/psy/psysh/src/Command/HelpCommand.php new file mode 100644 index 0000000000..d15cc4f40d --- /dev/null +++ b/vendor/psy/psysh/src/Command/HelpCommand.php @@ -0,0 +1,107 @@ +setName('help') + ->setAliases(['?']) + ->setDefinition([ + new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name.', null), + ]) + ->setDescription('Show a list of commands. Type `help [foo]` for information about [foo].') + ->setHelp('My. How meta.'); + } + + /** + * Helper for setting a subcommand to retrieve help for. + * + * @param Command $command + */ + public function setCommand(Command $command) + { + $this->command = $command; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + if ($this->command !== null) { + // help for an individual command + $output->page($this->command->asText()); + $this->command = null; + } elseif ($name = $input->getArgument('command_name')) { + // help for an individual command + $output->page($this->getApplication()->get($name)->asText()); + } else { + // list available commands + $commands = $this->getApplication()->all(); + + $table = $this->getTable($output); + + foreach ($commands as $name => $command) { + if ($name !== $command->getName()) { + continue; + } + + if ($command->getAliases()) { + $aliases = \sprintf('Aliases: %s', \implode(', ', $command->getAliases())); + } else { + $aliases = ''; + } + + $table->addRow([ + \sprintf('%s', $name), + $command->getDescription(), + $aliases, + ]); + } + + if ($output instanceof ShellOutput) { + $output->startPaging(); + } + + if ($table instanceof TableHelper) { + $table->render($output); + } else { + $table->render(); + } + + if ($output instanceof ShellOutput) { + $output->stopPaging(); + } + } + + return 0; + } +} diff --git a/vendor/psy/psysh/src/Command/HistoryCommand.php b/vendor/psy/psysh/src/Command/HistoryCommand.php new file mode 100644 index 0000000000..52fe88ed27 --- /dev/null +++ b/vendor/psy/psysh/src/Command/HistoryCommand.php @@ -0,0 +1,248 @@ +filter = new FilterOptions(); + + parent::__construct($name); + } + + /** + * Set the Shell's Readline service. + * + * @param Readline $readline + */ + public function setReadline(Readline $readline) + { + $this->readline = $readline; + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + list($grep, $insensitive, $invert) = FilterOptions::getOptions(); + + $this + ->setName('history') + ->setAliases(['hist']) + ->setDefinition([ + new InputOption('show', 's', InputOption::VALUE_REQUIRED, 'Show the given range of lines.'), + new InputOption('head', 'H', InputOption::VALUE_REQUIRED, 'Display the first N items.'), + new InputOption('tail', 'T', InputOption::VALUE_REQUIRED, 'Display the last N items.'), + + $grep, + $insensitive, + $invert, + + new InputOption('no-numbers', 'N', InputOption::VALUE_NONE, 'Omit line numbers.'), + + new InputOption('save', '', InputOption::VALUE_REQUIRED, 'Save history to a file.'), + new InputOption('replay', '', InputOption::VALUE_NONE, 'Replay.'), + new InputOption('clear', '', InputOption::VALUE_NONE, 'Clear the history.'), + ]) + ->setDescription('Show the Psy Shell history.') + ->setHelp( + <<<'HELP' +Show, search, save or replay the Psy Shell history. + +e.g. +>>> history --grep /[bB]acon/ +>>> history --show 0..10 --replay +>>> history --clear +>>> history --tail 1000 --save somefile.txt +HELP + ); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->validateOnlyOne($input, ['show', 'head', 'tail']); + $this->validateOnlyOne($input, ['save', 'replay', 'clear']); + + $history = $this->getHistorySlice( + $input->getOption('show'), + $input->getOption('head'), + $input->getOption('tail') + ); + $highlighted = false; + + $this->filter->bind($input); + if ($this->filter->hasFilter()) { + $matches = []; + $highlighted = []; + foreach ($history as $i => $line) { + if ($this->filter->match($line, $matches)) { + if (isset($matches[0])) { + $chunks = \explode($matches[0], $history[$i]); + $chunks = \array_map([__CLASS__, 'escape'], $chunks); + $glue = \sprintf('%s', self::escape($matches[0])); + + $highlighted[$i] = \implode($glue, $chunks); + } + } else { + unset($history[$i]); + } + } + } + + if ($save = $input->getOption('save')) { + $output->writeln(\sprintf('Saving history in %s...', $save)); + \file_put_contents($save, \implode(\PHP_EOL, $history).\PHP_EOL); + $output->writeln('History saved.'); + } elseif ($input->getOption('replay')) { + if (!($input->getOption('show') || $input->getOption('head') || $input->getOption('tail'))) { + throw new \InvalidArgumentException('You must limit history via --head, --tail or --show before replaying'); + } + + $count = \count($history); + $output->writeln(\sprintf('Replaying %d line%s of history', $count, ($count !== 1) ? 's' : '')); + $this->getApplication()->addInput($history); + } elseif ($input->getOption('clear')) { + $this->clearHistory(); + $output->writeln('History cleared.'); + } else { + $type = $input->getOption('no-numbers') ? 0 : ShellOutput::NUMBER_LINES; + if (!$highlighted) { + $type = $type | OutputInterface::OUTPUT_RAW; + } + + $output->page($highlighted ?: $history, $type); + } + + return 0; + } + + /** + * Extract a range from a string. + * + * @param string $range + * + * @return array [ start, end ] + */ + private function extractRange(string $range): array + { + if (\preg_match('/^\d+$/', $range)) { + return [$range, $range + 1]; + } + + $matches = []; + if ($range !== '..' && \preg_match('/^(\d*)\.\.(\d*)$/', $range, $matches)) { + $start = $matches[1] ? (int) $matches[1] : 0; + $end = $matches[2] ? (int) $matches[2] + 1 : \PHP_INT_MAX; + + return [$start, $end]; + } + + throw new \InvalidArgumentException('Unexpected range: '.$range); + } + + /** + * Retrieve a slice of the readline history. + * + * @param string|null $show + * @param string|null $head + * @param string|null $tail + * + * @return array A slice of history + */ + private function getHistorySlice($show, $head, $tail): array + { + $history = $this->readline->listHistory(); + + // don't show the current `history` invocation + \array_pop($history); + + if ($show) { + list($start, $end) = $this->extractRange($show); + $length = $end - $start; + } elseif ($head) { + if (!\preg_match('/^\d+$/', $head)) { + throw new \InvalidArgumentException('Please specify an integer argument for --head'); + } + + $start = 0; + $length = (int) $head; + } elseif ($tail) { + if (!\preg_match('/^\d+$/', $tail)) { + throw new \InvalidArgumentException('Please specify an integer argument for --tail'); + } + + $start = \count($history) - $tail; + $length = (int) $tail + 1; + } else { + return $history; + } + + return \array_slice($history, $start, $length, true); + } + + /** + * Validate that only one of the given $options is set. + * + * @param InputInterface $input + * @param array $options + */ + private function validateOnlyOne(InputInterface $input, array $options) + { + $count = 0; + foreach ($options as $opt) { + if ($input->getOption($opt)) { + $count++; + } + } + + if ($count > 1) { + throw new \InvalidArgumentException('Please specify only one of --'.\implode(', --', $options)); + } + } + + /** + * Clear the readline history. + */ + private function clearHistory() + { + $this->readline->clearHistory(); + } + + public static function escape(string $string): string + { + return OutputFormatter::escape($string); + } +} diff --git a/vendor/psy/psysh/src/Command/ListCommand.php b/vendor/psy/psysh/src/Command/ListCommand.php new file mode 100644 index 0000000000..675703bfa7 --- /dev/null +++ b/vendor/psy/psysh/src/Command/ListCommand.php @@ -0,0 +1,280 @@ +presenter = $presenter; + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + list($grep, $insensitive, $invert) = FilterOptions::getOptions(); + + $this + ->setName('ls') + ->setAliases(['dir']) + ->setDefinition([ + new CodeArgument('target', CodeArgument::OPTIONAL, 'A target class or object to list.'), + + new InputOption('vars', '', InputOption::VALUE_NONE, 'Display variables.'), + new InputOption('constants', 'c', InputOption::VALUE_NONE, 'Display defined constants.'), + new InputOption('functions', 'f', InputOption::VALUE_NONE, 'Display defined functions.'), + new InputOption('classes', 'k', InputOption::VALUE_NONE, 'Display declared classes.'), + new InputOption('interfaces', 'I', InputOption::VALUE_NONE, 'Display declared interfaces.'), + new InputOption('traits', 't', InputOption::VALUE_NONE, 'Display declared traits.'), + + new InputOption('no-inherit', '', InputOption::VALUE_NONE, 'Exclude inherited methods, properties and constants.'), + + new InputOption('properties', 'p', InputOption::VALUE_NONE, 'Display class or object properties (public properties by default).'), + new InputOption('methods', 'm', InputOption::VALUE_NONE, 'Display class or object methods (public methods by default).'), + + $grep, + $insensitive, + $invert, + + new InputOption('globals', 'g', InputOption::VALUE_NONE, 'Include global variables.'), + new InputOption('internal', 'n', InputOption::VALUE_NONE, 'Limit to internal functions and classes.'), + new InputOption('user', 'u', InputOption::VALUE_NONE, 'Limit to user-defined constants, functions and classes.'), + new InputOption('category', 'C', InputOption::VALUE_REQUIRED, 'Limit to constants in a specific category (e.g. "date").'), + + new InputOption('all', 'a', InputOption::VALUE_NONE, 'Include private and protected methods and properties.'), + new InputOption('long', 'l', InputOption::VALUE_NONE, 'List in long format: includes class names and method signatures.'), + ]) + ->setDescription('List local, instance or class variables, methods and constants.') + ->setHelp( + <<<'HELP' +List variables, constants, classes, interfaces, traits, functions, methods, +and properties. + +Called without options, this will return a list of variables currently in scope. + +If a target object is provided, list properties, constants and methods of that +target. If a class, interface or trait name is passed instead, list constants +and methods on that class. + +e.g. +>>> ls +>>> ls $foo +>>> ls -k --grep mongo -i +>>> ls -al ReflectionClass +>>> ls --constants --category date +>>> ls -l --functions --grep /^array_.*/ +>>> ls -l --properties new DateTime() +HELP + ); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->validateInput($input); + $this->initEnumerators(); + + $method = $input->getOption('long') ? 'writeLong' : 'write'; + + if ($target = $input->getArgument('target')) { + list($target, $reflector) = $this->getTargetAndReflector($target); + } else { + $reflector = null; + } + + // @todo something cleaner than this :-/ + if ($output instanceof ShellOutput && $input->getOption('long')) { + $output->startPaging(); + } + + foreach ($this->enumerators as $enumerator) { + $this->$method($output, $enumerator->enumerate($input, $reflector, $target)); + } + + if ($output instanceof ShellOutput && $input->getOption('long')) { + $output->stopPaging(); + } + + // Set some magic local variables + if ($reflector !== null) { + $this->setCommandScopeVariables($reflector); + } + + return 0; + } + + /** + * Initialize Enumerators. + */ + protected function initEnumerators() + { + if (!isset($this->enumerators)) { + $mgr = $this->presenter; + + $this->enumerators = [ + new ClassConstantEnumerator($mgr), + new ClassEnumerator($mgr), + new ConstantEnumerator($mgr), + new FunctionEnumerator($mgr), + new GlobalVariableEnumerator($mgr), + new PropertyEnumerator($mgr), + new MethodEnumerator($mgr), + new VariableEnumerator($mgr, $this->context), + ]; + } + } + + /** + * Write the list items to $output. + * + * @param OutputInterface $output + * @param array $result List of enumerated items + */ + protected function write(OutputInterface $output, array $result) + { + if (\count($result) === 0) { + return; + } + + foreach ($result as $label => $items) { + $names = \array_map([$this, 'formatItemName'], $items); + $output->writeln(\sprintf('%s: %s', $label, \implode(', ', $names))); + } + } + + /** + * Write the list items to $output. + * + * Items are listed one per line, and include the item signature. + * + * @param OutputInterface $output + * @param array $result List of enumerated items + */ + protected function writeLong(OutputInterface $output, array $result) + { + if (\count($result) === 0) { + return; + } + + $table = $this->getTable($output); + + foreach ($result as $label => $items) { + $output->writeln(''); + $output->writeln(\sprintf('%s:', $label)); + + $table->setRows([]); + foreach ($items as $item) { + $table->addRow([$this->formatItemName($item), $item['value']]); + } + + if ($table instanceof TableHelper) { + $table->render($output); + } else { + $table->render(); + } + } + } + + /** + * Format an item name given its visibility. + * + * @param array $item + * + * @return string + */ + private function formatItemName(array $item): string + { + return \sprintf('<%s>%s', $item['style'], OutputFormatter::escape($item['name']), $item['style']); + } + + /** + * Validate that input options make sense, provide defaults when called without options. + * + * @throws RuntimeException if options are inconsistent + * + * @param InputInterface $input + */ + private function validateInput(InputInterface $input) + { + if (!$input->getArgument('target')) { + // if no target is passed, there can be no properties or methods + foreach (['properties', 'methods', 'no-inherit'] as $option) { + if ($input->getOption($option)) { + throw new RuntimeException('--'.$option.' does not make sense without a specified target'); + } + } + + foreach (['globals', 'vars', 'constants', 'functions', 'classes', 'interfaces', 'traits'] as $option) { + if ($input->getOption($option)) { + return; + } + } + + // default to --vars if no other options are passed + $input->setOption('vars', true); + } else { + // if a target is passed, classes, functions, etc don't make sense + foreach (['vars', 'globals'] as $option) { + if ($input->getOption($option)) { + throw new RuntimeException('--'.$option.' does not make sense with a specified target'); + } + } + + // @todo ensure that 'functions', 'classes', 'interfaces', 'traits' only accept namespace target? + foreach (['constants', 'properties', 'methods', 'functions', 'classes', 'interfaces', 'traits'] as $option) { + if ($input->getOption($option)) { + return; + } + } + + // default to --constants --properties --methods if no other options are passed + $input->setOption('constants', true); + $input->setOption('properties', true); + $input->setOption('methods', true); + } + } +} diff --git a/vendor/psy/psysh/src/Command/ListCommand/ClassConstantEnumerator.php b/vendor/psy/psysh/src/Command/ListCommand/ClassConstantEnumerator.php new file mode 100644 index 0000000000..a634488118 --- /dev/null +++ b/vendor/psy/psysh/src/Command/ListCommand/ClassConstantEnumerator.php @@ -0,0 +1,124 @@ +getOption('constants')) { + return []; + } + + $noInherit = $input->getOption('no-inherit'); + $constants = $this->prepareConstants($this->getConstants($reflector, $noInherit)); + + if (empty($constants)) { + return []; + } + + $ret = []; + $ret[$this->getKindLabel($reflector)] = $constants; + + return $ret; + } + + /** + * Get defined constants for the given class or object Reflector. + * + * @param \Reflector $reflector + * @param bool $noInherit Exclude inherited constants + * + * @return array + */ + protected function getConstants(\Reflector $reflector, bool $noInherit = false): array + { + $className = $reflector->getName(); + + $constants = []; + foreach ($reflector->getConstants() as $name => $constant) { + $constReflector = ReflectionClassConstant::create($reflector->name, $name); + + if ($noInherit && $constReflector->getDeclaringClass()->getName() !== $className) { + continue; + } + + $constants[$name] = $constReflector; + } + + \ksort($constants, \SORT_NATURAL | \SORT_FLAG_CASE); + + return $constants; + } + + /** + * Prepare formatted constant array. + * + * @param array $constants + * + * @return array + */ + protected function prepareConstants(array $constants): array + { + // My kingdom for a generator. + $ret = []; + + foreach ($constants as $name => $constant) { + if ($this->showItem($name)) { + $ret[$name] = [ + 'name' => $name, + 'style' => self::IS_CONSTANT, + 'value' => $this->presentRef($constant->getValue()), + ]; + } + } + + return $ret; + } + + /** + * Get a label for the particular kind of "class" represented. + * + * @param \ReflectionClass $reflector + * + * @return string + */ + protected function getKindLabel(\ReflectionClass $reflector): string + { + if ($reflector->isInterface()) { + return 'Interface Constants'; + } else { + return 'Class Constants'; + } + } +} diff --git a/vendor/psy/psysh/src/Command/ListCommand/ClassEnumerator.php b/vendor/psy/psysh/src/Command/ListCommand/ClassEnumerator.php new file mode 100644 index 0000000000..c6a7c252bc --- /dev/null +++ b/vendor/psy/psysh/src/Command/ListCommand/ClassEnumerator.php @@ -0,0 +1,132 @@ +getOption('internal'); + $user = $input->getOption('user'); + $prefix = $reflector === null ? null : \strtolower($reflector->getName()).'\\'; + + $ret = []; + + // only list classes, interfaces and traits if we are specifically asked + + if ($input->getOption('classes')) { + $ret = \array_merge($ret, $this->filterClasses('Classes', \get_declared_classes(), $internal, $user, $prefix)); + } + + if ($input->getOption('interfaces')) { + $ret = \array_merge($ret, $this->filterClasses('Interfaces', \get_declared_interfaces(), $internal, $user, $prefix)); + } + + if ($input->getOption('traits')) { + $ret = \array_merge($ret, $this->filterClasses('Traits', \get_declared_traits(), $internal, $user, $prefix)); + } + + return \array_map([$this, 'prepareClasses'], \array_filter($ret)); + } + + /** + * Filter a list of classes, interfaces or traits. + * + * If $internal or $user is defined, results will be limited to internal or + * user-defined classes as appropriate. + * + * @param string $key + * @param array $classes + * @param bool $internal + * @param bool $user + * @param string $prefix + * + * @return array + */ + protected function filterClasses(string $key, array $classes, bool $internal, bool $user, string $prefix = null): array + { + $ret = []; + + if ($internal) { + $ret['Internal '.$key] = \array_filter($classes, function ($class) use ($prefix) { + if ($prefix !== null && \strpos(\strtolower($class), $prefix) !== 0) { + return false; + } + + $refl = new \ReflectionClass($class); + + return $refl->isInternal(); + }); + } + + if ($user) { + $ret['User '.$key] = \array_filter($classes, function ($class) use ($prefix) { + if ($prefix !== null && \strpos(\strtolower($class), $prefix) !== 0) { + return false; + } + + $refl = new \ReflectionClass($class); + + return !$refl->isInternal(); + }); + } + + if (!$user && !$internal) { + $ret[$key] = \array_filter($classes, function ($class) use ($prefix) { + return $prefix === null || \strpos(\strtolower($class), $prefix) === 0; + }); + } + + return $ret; + } + + /** + * Prepare formatted class array. + * + * @param array $classes + * + * @return array + */ + protected function prepareClasses(array $classes): array + { + \natcasesort($classes); + + // My kingdom for a generator. + $ret = []; + + foreach ($classes as $name) { + if ($this->showItem($name)) { + $ret[$name] = [ + 'name' => $name, + 'style' => self::IS_CLASS, + 'value' => $this->presentSignature($name), + ]; + } + } + + return $ret; + } +} diff --git a/vendor/psy/psysh/src/Command/ListCommand/ConstantEnumerator.php b/vendor/psy/psysh/src/Command/ListCommand/ConstantEnumerator.php new file mode 100644 index 0000000000..b66862b7b7 --- /dev/null +++ b/vendor/psy/psysh/src/Command/ListCommand/ConstantEnumerator.php @@ -0,0 +1,175 @@ + 'libxml', + 'openssl' => 'OpenSSL', + 'pcre' => 'PCRE', + 'sqlite3' => 'SQLite3', + 'curl' => 'cURL', + 'dom' => 'DOM', + 'ftp' => 'FTP', + 'gd' => 'GD', + 'gmp' => 'GMP', + 'iconv' => 'iconv', + 'json' => 'JSON', + 'ldap' => 'LDAP', + 'mbstring' => 'mbstring', + 'odbc' => 'ODBC', + 'pcntl' => 'PCNTL', + 'pgsql' => 'pgsql', + 'posix' => 'POSIX', + 'mysqli' => 'mysqli', + 'soap' => 'SOAP', + 'exif' => 'EXIF', + 'sysvmsg' => 'sysvmsg', + 'xml' => 'XML', + 'xsl' => 'XSL', + ]; + + /** + * {@inheritdoc} + */ + protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null): array + { + // if we have a reflector, ensure that it's a namespace reflector + if (($target !== null || $reflector !== null) && !$reflector instanceof ReflectionNamespace) { + return []; + } + + // only list constants if we are specifically asked + if (!$input->getOption('constants')) { + return []; + } + + $user = $input->getOption('user'); + $internal = $input->getOption('internal'); + $category = $input->getOption('category'); + + if ($category) { + $category = \strtolower($category); + + if ($category === 'internal') { + $internal = true; + $category = null; + } elseif ($category === 'user') { + $user = true; + $category = null; + } + } + + $ret = []; + + if ($user) { + $ret['User Constants'] = $this->getConstants('user'); + } + + if ($internal) { + $ret['Internal Constants'] = $this->getConstants('internal'); + } + + if ($category) { + $caseCategory = \array_key_exists($category, self::$categoryLabels) ? self::$categoryLabels[$category] : \ucfirst($category); + $label = $caseCategory.' Constants'; + $ret[$label] = $this->getConstants($category); + } + + if (!$user && !$internal && !$category) { + $ret['Constants'] = $this->getConstants(); + } + + if ($reflector !== null) { + $prefix = \strtolower($reflector->getName()).'\\'; + + foreach ($ret as $key => $names) { + foreach (\array_keys($names) as $name) { + if (\strpos(\strtolower($name), $prefix) !== 0) { + unset($ret[$key][$name]); + } + } + } + } + + return \array_map([$this, 'prepareConstants'], \array_filter($ret)); + } + + /** + * Get defined constants. + * + * Optionally restrict constants to a given category, e.g. "date". If the + * category is "internal", include all non-user-defined constants. + * + * @param string $category + * + * @return array + */ + protected function getConstants(string $category = null): array + { + if (!$category) { + return \get_defined_constants(); + } + + $consts = \get_defined_constants(true); + + if ($category === 'internal') { + unset($consts['user']); + + return \call_user_func_array('array_merge', \array_values($consts)); + } + + foreach ($consts as $key => $value) { + if (\strtolower($key) === $category) { + return $value; + } + } + + return []; + } + + /** + * Prepare formatted constant array. + * + * @param array $constants + * + * @return array + */ + protected function prepareConstants(array $constants): array + { + // My kingdom for a generator. + $ret = []; + + $names = \array_keys($constants); + \natcasesort($names); + + foreach ($names as $name) { + if ($this->showItem($name)) { + $ret[$name] = [ + 'name' => $name, + 'style' => self::IS_CONSTANT, + 'value' => $this->presentRef($constants[$name]), + ]; + } + } + + return $ret; + } +} diff --git a/vendor/psy/psysh/src/Command/ListCommand/Enumerator.php b/vendor/psy/psysh/src/Command/ListCommand/Enumerator.php new file mode 100644 index 0000000000..05322b9843 --- /dev/null +++ b/vendor/psy/psysh/src/Command/ListCommand/Enumerator.php @@ -0,0 +1,106 @@ +filter = new FilterOptions(); + $this->presenter = $presenter; + } + + /** + * Return a list of categorized things with the given input options and target. + * + * @param InputInterface $input + * @param \Reflector|null $reflector + * @param mixed $target + * + * @return array + */ + public function enumerate(InputInterface $input, \Reflector $reflector = null, $target = null): array + { + $this->filter->bind($input); + + return $this->listItems($input, $reflector, $target); + } + + /** + * Enumerate specific items with the given input options and target. + * + * Implementing classes should return an array of arrays: + * + * [ + * 'Constants' => [ + * 'FOO' => [ + * 'name' => 'FOO', + * 'style' => 'public', + * 'value' => '123', + * ], + * ], + * ] + * + * @param InputInterface $input + * @param \Reflector|null $reflector + * @param mixed $target + * + * @return array + */ + abstract protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null): array; + + protected function showItem($name) + { + return $this->filter->match($name); + } + + protected function presentRef($value) + { + return $this->presenter->presentRef($value); + } + + protected function presentSignature($target) + { + // This might get weird if the signature is actually for a reflector. Hrm. + if (!$target instanceof \Reflector) { + $target = Mirror::get($target); + } + + return SignatureFormatter::format($target); + } +} diff --git a/vendor/psy/psysh/src/Command/ListCommand/FunctionEnumerator.php b/vendor/psy/psysh/src/Command/ListCommand/FunctionEnumerator.php new file mode 100644 index 0000000000..f4323f2322 --- /dev/null +++ b/vendor/psy/psysh/src/Command/ListCommand/FunctionEnumerator.php @@ -0,0 +1,116 @@ +getOption('functions')) { + return []; + } + + if ($input->getOption('user')) { + $label = 'User Functions'; + $functions = $this->getFunctions('user'); + } elseif ($input->getOption('internal')) { + $label = 'Internal Functions'; + $functions = $this->getFunctions('internal'); + } else { + $label = 'Functions'; + $functions = $this->getFunctions(); + } + + $prefix = $reflector === null ? null : \strtolower($reflector->getName()).'\\'; + $functions = $this->prepareFunctions($functions, $prefix); + + if (empty($functions)) { + return []; + } + + $ret = []; + $ret[$label] = $functions; + + return $ret; + } + + /** + * Get defined functions. + * + * Optionally limit functions to "user" or "internal" functions. + * + * @param string|null $type "user" or "internal" (default: both) + * + * @return array + */ + protected function getFunctions(string $type = null): array + { + $funcs = \get_defined_functions(); + + if ($type) { + return $funcs[$type]; + } else { + return \array_merge($funcs['internal'], $funcs['user']); + } + } + + /** + * Prepare formatted function array. + * + * @param array $functions + * @param string $prefix + * + * @return array + */ + protected function prepareFunctions(array $functions, string $prefix = null): array + { + \natcasesort($functions); + + // My kingdom for a generator. + $ret = []; + + foreach ($functions as $name) { + if ($prefix !== null && \strpos(\strtolower($name), $prefix) !== 0) { + continue; + } + + if ($this->showItem($name)) { + try { + $ret[$name] = [ + 'name' => $name, + 'style' => self::IS_FUNCTION, + 'value' => $this->presentSignature($name), + ]; + } catch (\Throwable $e) { + // Ignore failures. + } + } + } + + return $ret; + } +} diff --git a/vendor/psy/psysh/src/Command/ListCommand/GlobalVariableEnumerator.php b/vendor/psy/psysh/src/Command/ListCommand/GlobalVariableEnumerator.php new file mode 100644 index 0000000000..a643cc1f99 --- /dev/null +++ b/vendor/psy/psysh/src/Command/ListCommand/GlobalVariableEnumerator.php @@ -0,0 +1,92 @@ +getOption('globals')) { + return []; + } + + $globals = $this->prepareGlobals($this->getGlobals()); + + if (empty($globals)) { + return []; + } + + return [ + 'Global Variables' => $globals, + ]; + } + + /** + * Get defined global variables. + * + * @return array + */ + protected function getGlobals(): array + { + global $GLOBALS; + + $names = \array_keys($GLOBALS); + \natcasesort($names); + + $ret = []; + foreach ($names as $name) { + $ret[$name] = $GLOBALS[$name]; + } + + return $ret; + } + + /** + * Prepare formatted global variable array. + * + * @param array $globals + * + * @return array + */ + protected function prepareGlobals(array $globals): array + { + // My kingdom for a generator. + $ret = []; + + foreach ($globals as $name => $value) { + if ($this->showItem($name)) { + $fname = '$'.$name; + $ret[$fname] = [ + 'name' => $fname, + 'style' => self::IS_GLOBAL, + 'value' => $this->presentRef($value), + ]; + } + } + + return $ret; + } +} diff --git a/vendor/psy/psysh/src/Command/ListCommand/MethodEnumerator.php b/vendor/psy/psysh/src/Command/ListCommand/MethodEnumerator.php new file mode 100644 index 0000000000..a2e3331755 --- /dev/null +++ b/vendor/psy/psysh/src/Command/ListCommand/MethodEnumerator.php @@ -0,0 +1,146 @@ +getOption('methods')) { + return []; + } + + $showAll = $input->getOption('all'); + $noInherit = $input->getOption('no-inherit'); + $methods = $this->prepareMethods($this->getMethods($showAll, $reflector, $noInherit)); + + if (empty($methods)) { + return []; + } + + $ret = []; + $ret[$this->getKindLabel($reflector)] = $methods; + + return $ret; + } + + /** + * Get defined methods for the given class or object Reflector. + * + * @param bool $showAll Include private and protected methods + * @param \Reflector $reflector + * @param bool $noInherit Exclude inherited methods + * + * @return array + */ + protected function getMethods(bool $showAll, \Reflector $reflector, bool $noInherit = false): array + { + $className = $reflector->getName(); + + $methods = []; + foreach ($reflector->getMethods() as $name => $method) { + // For some reason PHP reflection shows private methods from the parent class, even + // though they're effectively worthless. Let's suppress them here, like --no-inherit + if (($noInherit || $method->isPrivate()) && $method->getDeclaringClass()->getName() !== $className) { + continue; + } + + if ($showAll || $method->isPublic()) { + $methods[$method->getName()] = $method; + } + } + + \ksort($methods, \SORT_NATURAL | \SORT_FLAG_CASE); + + return $methods; + } + + /** + * Prepare formatted method array. + * + * @param array $methods + * + * @return array + */ + protected function prepareMethods(array $methods): array + { + // My kingdom for a generator. + $ret = []; + + foreach ($methods as $name => $method) { + if ($this->showItem($name)) { + $ret[$name] = [ + 'name' => $name, + 'style' => $this->getVisibilityStyle($method), + 'value' => $this->presentSignature($method), + ]; + } + } + + return $ret; + } + + /** + * Get a label for the particular kind of "class" represented. + * + * @param \ReflectionClass $reflector + * + * @return string + */ + protected function getKindLabel(\ReflectionClass $reflector): string + { + if ($reflector->isInterface()) { + return 'Interface Methods'; + } elseif (\method_exists($reflector, 'isTrait') && $reflector->isTrait()) { + return 'Trait Methods'; + } else { + return 'Class Methods'; + } + } + + /** + * Get output style for the given method's visibility. + * + * @param \ReflectionMethod $method + * + * @return string + */ + private function getVisibilityStyle(\ReflectionMethod $method): string + { + if ($method->isPublic()) { + return self::IS_PUBLIC; + } elseif ($method->isProtected()) { + return self::IS_PROTECTED; + } else { + return self::IS_PRIVATE; + } + } +} diff --git a/vendor/psy/psysh/src/Command/ListCommand/PropertyEnumerator.php b/vendor/psy/psysh/src/Command/ListCommand/PropertyEnumerator.php new file mode 100644 index 0000000000..1258448e3c --- /dev/null +++ b/vendor/psy/psysh/src/Command/ListCommand/PropertyEnumerator.php @@ -0,0 +1,182 @@ +getOption('properties')) { + return []; + } + + $showAll = $input->getOption('all'); + $noInherit = $input->getOption('no-inherit'); + $properties = $this->prepareProperties($this->getProperties($showAll, $reflector, $noInherit), $target); + + if (empty($properties)) { + return []; + } + + $ret = []; + $ret[$this->getKindLabel($reflector)] = $properties; + + return $ret; + } + + /** + * Get defined properties for the given class or object Reflector. + * + * @param bool $showAll Include private and protected properties + * @param \Reflector $reflector + * @param bool $noInherit Exclude inherited properties + * + * @return array + */ + protected function getProperties(bool $showAll, \Reflector $reflector, bool $noInherit = false): array + { + $className = $reflector->getName(); + + $properties = []; + foreach ($reflector->getProperties() as $property) { + if ($noInherit && $property->getDeclaringClass()->getName() !== $className) { + continue; + } + + if ($showAll || $property->isPublic()) { + $properties[$property->getName()] = $property; + } + } + + \ksort($properties, \SORT_NATURAL | \SORT_FLAG_CASE); + + return $properties; + } + + /** + * Prepare formatted property array. + * + * @param array $properties + * + * @return array + */ + protected function prepareProperties(array $properties, $target = null): array + { + // My kingdom for a generator. + $ret = []; + + foreach ($properties as $name => $property) { + if ($this->showItem($name)) { + $fname = '$'.$name; + $ret[$fname] = [ + 'name' => $fname, + 'style' => $this->getVisibilityStyle($property), + 'value' => $this->presentValue($property, $target), + ]; + } + } + + return $ret; + } + + /** + * Get a label for the particular kind of "class" represented. + * + * @param \ReflectionClass $reflector + * + * @return string + */ + protected function getKindLabel(\ReflectionClass $reflector): string + { + if (\method_exists($reflector, 'isTrait') && $reflector->isTrait()) { + return 'Trait Properties'; + } else { + return 'Class Properties'; + } + } + + /** + * Get output style for the given property's visibility. + * + * @param \ReflectionProperty $property + * + * @return string + */ + private function getVisibilityStyle(\ReflectionProperty $property): string + { + if ($property->isPublic()) { + return self::IS_PUBLIC; + } elseif ($property->isProtected()) { + return self::IS_PROTECTED; + } else { + return self::IS_PRIVATE; + } + } + + /** + * Present the $target's current value for a reflection property. + * + * @param \ReflectionProperty $property + * @param mixed $target + * + * @return string + */ + protected function presentValue(\ReflectionProperty $property, $target): string + { + if (!$target) { + return ''; + } + + // If $target is a class or trait (try to) get the default + // value for the property. + if (!\is_object($target)) { + try { + $refl = new \ReflectionClass($target); + $props = $refl->getDefaultProperties(); + if (\array_key_exists($property->name, $props)) { + $suffix = $property->isStatic() ? '' : ' '; + + return $this->presentRef($props[$property->name]).$suffix; + } + } catch (\Throwable $e) { + // Well, we gave it a shot. + } + + return ''; + } + + $property->setAccessible(true); + $value = $property->getValue($target); + + return $this->presentRef($value); + } +} diff --git a/vendor/psy/psysh/src/Command/ListCommand/VariableEnumerator.php b/vendor/psy/psysh/src/Command/ListCommand/VariableEnumerator.php new file mode 100644 index 0000000000..ecc69040f2 --- /dev/null +++ b/vendor/psy/psysh/src/Command/ListCommand/VariableEnumerator.php @@ -0,0 +1,137 @@ +context = $context; + parent::__construct($presenter); + } + + /** + * {@inheritdoc} + */ + protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null): array + { + // only list variables when no Reflector is present. + if ($reflector !== null || $target !== null) { + return []; + } + + // only list variables if we are specifically asked + if (!$input->getOption('vars')) { + return []; + } + + $showAll = $input->getOption('all'); + $variables = $this->prepareVariables($this->getVariables($showAll)); + + if (empty($variables)) { + return []; + } + + return [ + 'Variables' => $variables, + ]; + } + + /** + * Get scope variables. + * + * @param bool $showAll Include special variables (e.g. $_) + * + * @return array + */ + protected function getVariables(bool $showAll): array + { + $scopeVars = $this->context->getAll(); + \uksort($scopeVars, function ($a, $b) { + $aIndex = \array_search($a, self::$specialNames); + $bIndex = \array_search($b, self::$specialNames); + + if ($aIndex !== false) { + if ($bIndex !== false) { + return $aIndex - $bIndex; + } + + return 1; + } + + if ($bIndex !== false) { + return -1; + } + + return \strnatcasecmp($a, $b); + }); + + $ret = []; + foreach ($scopeVars as $name => $val) { + if (!$showAll && \in_array($name, self::$specialNames)) { + continue; + } + + $ret[$name] = $val; + } + + return $ret; + } + + /** + * Prepare formatted variable array. + * + * @param array $variables + * + * @return array + */ + protected function prepareVariables(array $variables): array + { + // My kingdom for a generator. + $ret = []; + foreach ($variables as $name => $val) { + if ($this->showItem($name)) { + $fname = '$'.$name; + $ret[$fname] = [ + 'name' => $fname, + 'style' => \in_array($name, self::$specialNames) ? self::IS_PRIVATE : self::IS_PUBLIC, + 'value' => $this->presentRef($val), + ]; + } + } + + return $ret; + } +} diff --git a/vendor/psy/psysh/src/Command/ParseCommand.php b/vendor/psy/psysh/src/Command/ParseCommand.php new file mode 100644 index 0000000000..c2f8ab0f45 --- /dev/null +++ b/vendor/psy/psysh/src/Command/ParseCommand.php @@ -0,0 +1,176 @@ +parserFactory = new ParserFactory(); + $this->parsers = []; + + parent::__construct($name); + } + + /** + * ContextAware interface. + * + * @param Context $context + */ + public function setContext(Context $context) + { + $this->context = $context; + } + + /** + * PresenterAware interface. + * + * @param Presenter $presenter + */ + public function setPresenter(Presenter $presenter) + { + $this->presenter = clone $presenter; + $this->presenter->addCasters([ + Node::class => function (Node $node, array $a) { + $a = [ + Caster::PREFIX_VIRTUAL.'type' => $node->getType(), + Caster::PREFIX_VIRTUAL.'attributes' => $node->getAttributes(), + ]; + + foreach ($node->getSubNodeNames() as $name) { + $a[Caster::PREFIX_VIRTUAL.$name] = $node->$name; + } + + return $a; + }, + ]); + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $kindMsg = 'One of PhpParser\\ParserFactory constants: ' + .\implode(', ', ParserFactory::getPossibleKinds()) + ." (default is based on current interpreter's version)."; + + $this + ->setName('parse') + ->setDefinition([ + new CodeArgument('code', CodeArgument::REQUIRED, 'PHP code to parse.'), + new InputOption('depth', '', InputOption::VALUE_REQUIRED, 'Depth to parse.', 10), + new InputOption('kind', '', InputOption::VALUE_REQUIRED, $kindMsg, $this->parserFactory->getDefaultKind()), + ]) + ->setDescription('Parse PHP code and show the abstract syntax tree.') + ->setHelp( + <<<'HELP' +Parse PHP code and show the abstract syntax tree. + +This command is used in the development of PsySH. Given a string of PHP code, +it pretty-prints the PHP Parser parse tree. + +See https://github.com/nikic/PHP-Parser + +It prolly won't be super useful for most of you, but it's here if you want to play. +HELP + ); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $code = $input->getArgument('code'); + if (\strpos($code, 'getOption('kind'); + $depth = $input->getOption('depth'); + $nodes = $this->parse($this->getParser($parserKind), $code); + $output->page($this->presenter->present($nodes, $depth)); + + $this->context->setReturnValue($nodes); + + return 0; + } + + /** + * Lex and parse a string of code into statements. + * + * @param Parser $parser + * @param string $code + * + * @return array Statements + */ + private function parse(Parser $parser, string $code): array + { + try { + return $parser->parse($code); + } catch (\PhpParser\Error $e) { + if (\strpos($e->getMessage(), 'unexpected EOF') === false) { + throw $e; + } + + // If we got an unexpected EOF, let's try it again with a semicolon. + return $parser->parse($code.';'); + } + } + + /** + * Get (or create) the Parser instance. + * + * @param string|null $kind One of Psy\ParserFactory constants (only for PHP parser 2.0 and above) + * + * @return Parser + */ + private function getParser(string $kind = null): Parser + { + if (!\array_key_exists($kind, $this->parsers)) { + $this->parsers[$kind] = $this->parserFactory->createParser($kind); + } + + return $this->parsers[$kind]; + } +} diff --git a/vendor/psy/psysh/src/Command/PsyVersionCommand.php b/vendor/psy/psysh/src/Command/PsyVersionCommand.php new file mode 100644 index 0000000000..c78889c8bd --- /dev/null +++ b/vendor/psy/psysh/src/Command/PsyVersionCommand.php @@ -0,0 +1,43 @@ +setName('version') + ->setDefinition([]) + ->setDescription('Show Psy Shell version.') + ->setHelp('Show Psy Shell version.'); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $output->writeln($this->getApplication()->getVersion()); + + return 0; + } +} diff --git a/vendor/psy/psysh/src/Command/ReflectingCommand.php b/vendor/psy/psysh/src/Command/ReflectingCommand.php new file mode 100644 index 0000000000..8068334260 --- /dev/null +++ b/vendor/psy/psysh/src/Command/ReflectingCommand.php @@ -0,0 +1,324 @@ +)(\w+)$/'; + + /** + * Context instance (for ContextAware interface). + * + * @var Context + */ + protected $context; + + /** + * ContextAware interface. + * + * @param Context $context + */ + public function setContext(Context $context) + { + $this->context = $context; + } + + /** + * Get the target for a value. + * + * @throws \InvalidArgumentException when the value specified can't be resolved + * + * @param string $valueName Function, class, variable, constant, method or property name + * + * @return array (class or instance name, member name, kind) + */ + protected function getTarget(string $valueName): array + { + $valueName = \trim($valueName); + $matches = []; + switch (true) { + case \preg_match(self::CLASS_OR_FUNC, $valueName, $matches): + return [$this->resolveName($matches[0], true), null, 0]; + + case \preg_match(self::CLASS_MEMBER, $valueName, $matches): + return [$this->resolveName($matches[1]), $matches[2], Mirror::CONSTANT | Mirror::METHOD]; + + case \preg_match(self::CLASS_STATIC, $valueName, $matches): + return [$this->resolveName($matches[1]), $matches[2], Mirror::STATIC_PROPERTY | Mirror::PROPERTY]; + + case \preg_match(self::INSTANCE_MEMBER, $valueName, $matches): + if ($matches[2] === '->') { + $kind = Mirror::METHOD | Mirror::PROPERTY; + } else { + $kind = Mirror::CONSTANT | Mirror::METHOD; + } + + return [$this->resolveObject($matches[1]), $matches[3], $kind]; + + default: + return [$this->resolveObject($valueName), null, 0]; + } + } + + /** + * Resolve a class or function name (with the current shell namespace). + * + * @throws ErrorException when `self` or `static` is used in a non-class scope + * + * @param string $name + * @param bool $includeFunctions (default: false) + * + * @return string + */ + protected function resolveName(string $name, bool $includeFunctions = false): string + { + $shell = $this->getApplication(); + + // While not *technically* 100% accurate, let's treat `self` and `static` as equivalent. + if (\in_array(\strtolower($name), ['self', 'static'])) { + if ($boundClass = $shell->getBoundClass()) { + return $boundClass; + } + + if ($boundObject = $shell->getBoundObject()) { + return \get_class($boundObject); + } + + $msg = \sprintf('Cannot use "%s" when no class scope is active', \strtolower($name)); + throw new ErrorException($msg, 0, \E_USER_ERROR, "eval()'d code", 1); + } + + if (\substr($name, 0, 1) === '\\') { + return $name; + } + + // Check $name against the current namespace and use statements. + if (self::couldBeClassName($name)) { + try { + $name = $this->resolveCode($name.'::class'); + } catch (RuntimeException $e) { + // /shrug + } + } + + if ($namespace = $shell->getNamespace()) { + $fullName = $namespace.'\\'.$name; + + if (\class_exists($fullName) || \interface_exists($fullName) || ($includeFunctions && \function_exists($fullName))) { + return $fullName; + } + } + + return $name; + } + + /** + * Check whether a given name could be a class name. + */ + protected function couldBeClassName(string $name): bool + { + // Regex based on https://www.php.net/manual/en/language.oop5.basic.php#language.oop5.basic.class + return \preg_match('/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*(\\\\[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)*$/', $name) === 1; + } + + /** + * Get a Reflector and documentation for a function, class or instance, constant, method or property. + * + * @param string $valueName Function, class, variable, constant, method or property name + * + * @return array (value, Reflector) + */ + protected function getTargetAndReflector(string $valueName): array + { + list($value, $member, $kind) = $this->getTarget($valueName); + + return [$value, Mirror::get($value, $member, $kind)]; + } + + /** + * Resolve code to a value in the current scope. + * + * @throws RuntimeException when the code does not return a value in the current scope + * + * @param string $code + * + * @return mixed Variable value + */ + protected function resolveCode(string $code) + { + try { + $value = $this->getApplication()->execute($code, true); + } catch (\Throwable $e) { + // Swallow all exceptions? + } + + if (!isset($value) || $value instanceof NoReturnValue) { + throw new RuntimeException('Unknown target: '.$code); + } + + return $value; + } + + /** + * Resolve code to an object in the current scope. + * + * @throws UnexpectedTargetException when the code resolves to a non-object value + * + * @param string $code + * + * @return object Variable instance + */ + private function resolveObject(string $code) + { + $value = $this->resolveCode($code); + + if (!\is_object($value)) { + throw new UnexpectedTargetException($value, 'Unable to inspect a non-object'); + } + + return $value; + } + + /** + * @deprecated Use `resolveCode` instead + * + * @param string $name + * + * @return mixed Variable instance + */ + protected function resolveInstance(string $name) + { + @\trigger_error('`resolveInstance` is deprecated; use `resolveCode` instead.', \E_USER_DEPRECATED); + + return $this->resolveCode($name); + } + + /** + * Get a variable from the current shell scope. + * + * @param string $name + * + * @return mixed + */ + protected function getScopeVariable(string $name) + { + return $this->context->get($name); + } + + /** + * Get all scope variables from the current shell scope. + * + * @return array + */ + protected function getScopeVariables(): array + { + return $this->context->getAll(); + } + + /** + * Given a Reflector instance, set command-scope variables in the shell + * execution context. This is used to inject magic $__class, $__method and + * $__file variables (as well as a handful of others). + * + * @param \Reflector $reflector + */ + protected function setCommandScopeVariables(\Reflector $reflector) + { + $vars = []; + + switch (\get_class($reflector)) { + case \ReflectionClass::class: + case \ReflectionObject::class: + $vars['__class'] = $reflector->name; + if ($reflector->inNamespace()) { + $vars['__namespace'] = $reflector->getNamespaceName(); + } + break; + + case \ReflectionMethod::class: + $vars['__method'] = \sprintf('%s::%s', $reflector->class, $reflector->name); + $vars['__class'] = $reflector->class; + $classReflector = $reflector->getDeclaringClass(); + if ($classReflector->inNamespace()) { + $vars['__namespace'] = $classReflector->getNamespaceName(); + } + break; + + case \ReflectionFunction::class: + $vars['__function'] = $reflector->name; + if ($reflector->inNamespace()) { + $vars['__namespace'] = $reflector->getNamespaceName(); + } + break; + + case \ReflectionGenerator::class: + $funcReflector = $reflector->getFunction(); + $vars['__function'] = $funcReflector->name; + if ($funcReflector->inNamespace()) { + $vars['__namespace'] = $funcReflector->getNamespaceName(); + } + if ($fileName = $reflector->getExecutingFile()) { + $vars['__file'] = $fileName; + $vars['__line'] = $reflector->getExecutingLine(); + $vars['__dir'] = \dirname($fileName); + } + break; + + case \ReflectionProperty::class: + case \ReflectionClassConstant::class: + case ReflectionClassConstant::class: + $classReflector = $reflector->getDeclaringClass(); + $vars['__class'] = $classReflector->name; + if ($classReflector->inNamespace()) { + $vars['__namespace'] = $classReflector->getNamespaceName(); + } + // no line for these, but this'll do + if ($fileName = $reflector->getDeclaringClass()->getFileName()) { + $vars['__file'] = $fileName; + $vars['__dir'] = \dirname($fileName); + } + break; + + case ReflectionConstant_::class: + if ($reflector->inNamespace()) { + $vars['__namespace'] = $reflector->getNamespaceName(); + } + break; + } + + if ($reflector instanceof \ReflectionClass || $reflector instanceof \ReflectionFunctionAbstract) { + if ($fileName = $reflector->getFileName()) { + $vars['__file'] = $fileName; + $vars['__line'] = $reflector->getStartLine(); + $vars['__dir'] = \dirname($fileName); + } + } + + $this->context->setCommandScopeVariables($vars); + } +} diff --git a/vendor/psy/psysh/src/Command/ShowCommand.php b/vendor/psy/psysh/src/Command/ShowCommand.php new file mode 100644 index 0000000000..1abb7cbcef --- /dev/null +++ b/vendor/psy/psysh/src/Command/ShowCommand.php @@ -0,0 +1,299 @@ +setName('show') + ->setDefinition([ + new CodeArgument('target', CodeArgument::OPTIONAL, 'Function, class, instance, constant, method or property to show.'), + new InputOption('ex', null, InputOption::VALUE_OPTIONAL, 'Show last exception context. Optionally specify a stack index.', 1), + ]) + ->setDescription('Show the code for an object, class, constant, method or property.') + ->setHelp( + <<cat --ex defaults to showing the lines surrounding the location of the last +exception. Invoking it more than once travels up the exception's stack trace, +and providing a number shows the context of the given index of the trace. + +e.g. +>>> show \$myObject +>>> show Psy\Shell::debug +>>> show --ex +>>> show --ex 3 +HELP + ); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + // n.b. As far as I can tell, InputInterface doesn't want to tell me + // whether an option with an optional value was actually passed. If you + // call `$input->getOption('ex')`, it will return the default, both when + // `--ex` is specified with no value, and when `--ex` isn't specified at + // all. + // + // So we're doing something sneaky here. If we call `getOptions`, it'll + // return the default value when `--ex` is not present, and `null` if + // `--ex` is passed with no value. /shrug + $opts = $input->getOptions(); + + // Strict comparison to `1` (the default value) here, because `--ex 1` + // will come in as `"1"`. Now we can tell the difference between + // "no --ex present", because it's the integer 1, "--ex with no value", + // because it's `null`, and "--ex 1", because it's the string "1". + if ($opts['ex'] !== 1) { + if ($input->getArgument('target')) { + throw new \InvalidArgumentException('Too many arguments (supply either "target" or "--ex")'); + } + + $this->writeExceptionContext($input, $output); + + return 0; + } + + if ($input->getArgument('target')) { + $this->writeCodeContext($input, $output); + + return 0; + } + + throw new RuntimeException('Not enough arguments (missing: "target")'); + } + + private function writeCodeContext(InputInterface $input, OutputInterface $output) + { + try { + list($target, $reflector) = $this->getTargetAndReflector($input->getArgument('target')); + } catch (UnexpectedTargetException $e) { + // If we didn't get a target and Reflector, maybe we got a filename? + $target = $e->getTarget(); + if (\is_string($target) && \is_file($target) && $code = @\file_get_contents($target)) { + $file = \realpath($target); + if ($file !== $this->context->get('__file')) { + $this->context->setCommandScopeVariables([ + '__file' => $file, + '__dir' => \dirname($file), + ]); + } + + $output->page(CodeFormatter::formatCode($code)); + + return; + } else { + throw $e; + } + } + + // Set some magic local variables + $this->setCommandScopeVariables($reflector); + + try { + $output->page(CodeFormatter::format($reflector)); + } catch (RuntimeException $e) { + $output->writeln(SignatureFormatter::format($reflector)); + throw $e; + } + } + + private function writeExceptionContext(InputInterface $input, OutputInterface $output) + { + $exception = $this->context->getLastException(); + if ($exception !== $this->lastException) { + $this->lastException = null; + $this->lastExceptionIndex = null; + } + + $opts = $input->getOptions(); + if ($opts['ex'] === null) { + if ($this->lastException && $this->lastExceptionIndex !== null) { + $index = $this->lastExceptionIndex + 1; + } else { + $index = 0; + } + } else { + $index = \max(0, (int) $input->getOption('ex') - 1); + } + + $trace = $exception->getTrace(); + \array_unshift($trace, [ + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + ]); + + if ($index >= \count($trace)) { + $index = 0; + } + + $this->lastException = $exception; + $this->lastExceptionIndex = $index; + + $output->writeln($this->getApplication()->formatException($exception)); + $output->writeln('--'); + $this->writeTraceLine($output, $trace, $index); + $this->writeTraceCodeSnippet($output, $trace, $index); + + $this->setCommandScopeVariablesFromContext($trace[$index]); + } + + private function writeTraceLine(OutputInterface $output, array $trace, $index) + { + $file = isset($trace[$index]['file']) ? $this->replaceCwd($trace[$index]['file']) : 'n/a'; + $line = isset($trace[$index]['line']) ? $trace[$index]['line'] : 'n/a'; + + $output->writeln(\sprintf( + 'From %s:%d at level %d of backtrace (of %d):', + OutputFormatter::escape($file), + OutputFormatter::escape($line), + $index + 1, + \count($trace) + )); + } + + private function replaceCwd(string $file): string + { + if ($cwd = \getcwd()) { + $cwd = \rtrim($cwd, \DIRECTORY_SEPARATOR).\DIRECTORY_SEPARATOR; + } + + if ($cwd === false) { + return $file; + } else { + return \preg_replace('/^'.\preg_quote($cwd, '/').'/', '', $file); + } + } + + private function writeTraceCodeSnippet(OutputInterface $output, array $trace, $index) + { + if (!isset($trace[$index]['file'])) { + return; + } + + $file = $trace[$index]['file']; + if ($fileAndLine = $this->extractEvalFileAndLine($file)) { + list($file, $line) = $fileAndLine; + } else { + if (!isset($trace[$index]['line'])) { + return; + } + + $line = $trace[$index]['line']; + } + + if (\is_file($file)) { + $code = @\file_get_contents($file); + } + + if (empty($code)) { + return; + } + + $startLine = \max($line - 5, 0); + $endLine = $line + 5; + + $output->write(CodeFormatter::formatCode($code, $startLine, $endLine, $line), false); + } + + private function setCommandScopeVariablesFromContext(array $context) + { + $vars = []; + + if (isset($context['class'])) { + $vars['__class'] = $context['class']; + if (isset($context['function'])) { + $vars['__method'] = $context['function']; + } + + try { + $refl = new \ReflectionClass($context['class']); + if ($namespace = $refl->getNamespaceName()) { + $vars['__namespace'] = $namespace; + } + } catch (\Throwable $e) { + // oh well + } + } elseif (isset($context['function'])) { + $vars['__function'] = $context['function']; + + try { + $refl = new \ReflectionFunction($context['function']); + if ($namespace = $refl->getNamespaceName()) { + $vars['__namespace'] = $namespace; + } + } catch (\Throwable $e) { + // oh well + } + } + + if (isset($context['file'])) { + $file = $context['file']; + if ($fileAndLine = $this->extractEvalFileAndLine($file)) { + list($file, $line) = $fileAndLine; + } elseif (isset($context['line'])) { + $line = $context['line']; + } + + if (\is_file($file)) { + $vars['__file'] = $file; + if (isset($line)) { + $vars['__line'] = $line; + } + $vars['__dir'] = \dirname($file); + } + } + + $this->context->setCommandScopeVariables($vars); + } + + private function extractEvalFileAndLine(string $file) + { + if (\preg_match('/(.*)\\((\\d+)\\) : eval\\(\\)\'d code$/', $file, $matches)) { + return [$matches[1], $matches[2]]; + } + } +} diff --git a/vendor/psy/psysh/src/Command/SudoCommand.php b/vendor/psy/psysh/src/Command/SudoCommand.php new file mode 100644 index 0000000000..7912a1d530 --- /dev/null +++ b/vendor/psy/psysh/src/Command/SudoCommand.php @@ -0,0 +1,145 @@ +parser = $parserFactory->createParser(); + + $this->traverser = new NodeTraverser(); + $this->traverser->addVisitor(new SudoVisitor()); + + $this->printer = new Printer(); + + parent::__construct($name); + } + + /** + * Set the Shell's Readline service. + * + * @param Readline $readline + */ + public function setReadline(Readline $readline) + { + $this->readline = $readline; + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('sudo') + ->setDefinition([ + new CodeArgument('code', CodeArgument::REQUIRED, 'Code to execute.'), + ]) + ->setDescription('Evaluate PHP code, bypassing visibility restrictions.') + ->setHelp( + <<<'HELP' +Evaluate PHP code, bypassing visibility restrictions. + +e.g. +>>> $sekret->whisper("hi") +PHP error: Call to private method Sekret::whisper() from context '' on line 1 + +>>> sudo $sekret->whisper("hi") +=> "hi" + +>>> $sekret->word +PHP error: Cannot access private property Sekret::$word on line 1 + +>>> sudo $sekret->word +=> "hi" + +>>> $sekret->word = "please" +PHP error: Cannot access private property Sekret::$word on line 1 + +>>> sudo $sekret->word = "please" +=> "please" +HELP + ); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $code = $input->getArgument('code'); + + // special case for !! + if ($code === '!!') { + $history = $this->readline->listHistory(); + if (\count($history) < 2) { + throw new \InvalidArgumentException('No previous command to replay'); + } + $code = $history[\count($history) - 2]; + } + + if (\strpos($code, 'traverser->traverse($this->parse($code)); + + $sudoCode = $this->printer->prettyPrint($nodes); + $shell = $this->getApplication(); + $shell->addCode($sudoCode, !$shell->hasCode()); + + return 0; + } + + /** + * Lex and parse a string of code into statements. + * + * @param string $code + * + * @return array Statements + */ + private function parse(string $code): array + { + try { + return $this->parser->parse($code); + } catch (\PhpParser\Error $e) { + if (\strpos($e->getMessage(), 'unexpected EOF') === false) { + throw $e; + } + + // If we got an unexpected EOF, let's try it again with a semicolon. + return $this->parser->parse($code.';'); + } + } +} diff --git a/vendor/psy/psysh/src/Command/ThrowUpCommand.php b/vendor/psy/psysh/src/Command/ThrowUpCommand.php new file mode 100644 index 0000000000..d7bf63b75e --- /dev/null +++ b/vendor/psy/psysh/src/Command/ThrowUpCommand.php @@ -0,0 +1,166 @@ +parser = $parserFactory->createParser(); + $this->printer = new Printer(); + + parent::__construct($name); + } + + /** + * @deprecated throwUp no longer needs to be ContextAware + * + * @param Context $context + */ + public function setContext(Context $context) + { + // Do nothing + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('throw-up') + ->setDefinition([ + new CodeArgument('exception', CodeArgument::OPTIONAL, 'Exception or Error to throw.'), + ]) + ->setDescription('Throw an exception or error out of the Psy Shell.') + ->setHelp( + <<<'HELP' +Throws an exception or error out of the current the Psy Shell instance. + +By default it throws the most recent exception. + +e.g. +>>> throw-up +>>> throw-up $e +>>> throw-up new Exception('WHEEEEEE!') +>>> throw-up "bye!" +HELP + ); + } + + /** + * {@inheritdoc} + * + * @throws \InvalidArgumentException if there is no exception to throw + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $args = $this->prepareArgs($input->getArgument('exception')); + $throwStmt = new Throw_(new StaticCall(new FullyQualifiedName(ThrowUpException::class), 'fromThrowable', $args)); + $throwCode = $this->printer->prettyPrint([$throwStmt]); + + $shell = $this->getApplication(); + $shell->addCode($throwCode, !$shell->hasCode()); + + return 0; + } + + /** + * Parse the supplied command argument. + * + * If no argument was given, this falls back to `$_e` + * + * @throws \InvalidArgumentException if there is no exception to throw + * + * @param string $code + * + * @return Arg[] + */ + private function prepareArgs(string $code = null): array + { + if (!$code) { + // Default to last exception if nothing else was supplied + return [new Arg(new Variable('_e'))]; + } + + if (\strpos($code, 'parse($code); + if (\count($nodes) !== 1) { + throw new \InvalidArgumentException('No idea how to throw this'); + } + + $node = $nodes[0]; + + // Make this work for PHP Parser v3.x + $expr = isset($node->expr) ? $node->expr : $node; + + $args = [new Arg($expr, false, false, $node->getAttributes())]; + + // Allow throwing via a string, e.g. `throw-up "SUP"` + if ($expr instanceof String_) { + return [new New_(new FullyQualifiedName(\Exception::class), $args)]; + } + + return $args; + } + + /** + * Lex and parse a string of code into statements. + * + * @param string $code + * + * @return array Statements + */ + private function parse(string $code): array + { + try { + return $this->parser->parse($code); + } catch (\PhpParser\Error $e) { + if (\strpos($e->getMessage(), 'unexpected EOF') === false) { + throw $e; + } + + // If we got an unexpected EOF, let's try it again with a semicolon. + return $this->parser->parse($code.';'); + } + } +} diff --git a/vendor/psy/psysh/src/Command/TimeitCommand.php b/vendor/psy/psysh/src/Command/TimeitCommand.php new file mode 100644 index 0000000000..4aa3967e97 --- /dev/null +++ b/vendor/psy/psysh/src/Command/TimeitCommand.php @@ -0,0 +1,197 @@ +Command took %.6f seconds to complete.'; + const AVG_RESULT_MSG = 'Command took %.6f seconds on average (%.6f median; %.6f total) to complete.'; + + private static $start = null; + private static $times = []; + + private $parser; + private $traverser; + private $printer; + + /** + * {@inheritdoc} + */ + public function __construct($name = null) + { + $parserFactory = new ParserFactory(); + $this->parser = $parserFactory->createParser(); + + $this->traverser = new NodeTraverser(); + $this->traverser->addVisitor(new TimeitVisitor()); + + $this->printer = new Printer(); + + parent::__construct($name); + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('timeit') + ->setDefinition([ + new InputOption('num', 'n', InputOption::VALUE_REQUIRED, 'Number of iterations.'), + new CodeArgument('code', CodeArgument::REQUIRED, 'Code to execute.'), + ]) + ->setDescription('Profiles with a timer.') + ->setHelp( + <<<'HELP' +Time profiling for functions and commands. + +e.g. +>>> timeit sleep(1) +>>> timeit -n1000 $closure() +HELP + ); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $code = $input->getArgument('code'); + $num = $input->getOption('num') ?: 1; + $shell = $this->getApplication(); + + $instrumentedCode = $this->instrumentCode($code); + + self::$times = []; + + for ($i = 0; $i < $num; $i++) { + $_ = $shell->execute($instrumentedCode); + $this->ensureEndMarked(); + } + + $shell->writeReturnValue($_); + + $times = self::$times; + self::$times = []; + + if ($num === 1) { + $output->writeln(\sprintf(self::RESULT_MSG, $times[0])); + } else { + $total = \array_sum($times); + \rsort($times); + $median = $times[\round($num / 2)]; + + $output->writeln(\sprintf(self::AVG_RESULT_MSG, $total / $num, $median, $total)); + } + + return 0; + } + + /** + * Internal method for marking the start of timeit execution. + * + * A static call to this method will be injected at the start of the timeit + * input code to instrument the call. We will use the saved start time to + * more accurately calculate time elapsed during execution. + */ + public static function markStart() + { + self::$start = \microtime(true); + } + + /** + * Internal method for marking the end of timeit execution. + * + * A static call to this method is injected by TimeitVisitor at the end + * of the timeit input code to instrument the call. + * + * Note that this accepts an optional $ret parameter, which is used to pass + * the return value of the last statement back out of timeit. This saves us + * a bunch of code rewriting shenanigans. + * + * @param mixed $ret + * + * @return mixed it just passes $ret right back + */ + public static function markEnd($ret = null) + { + self::$times[] = \microtime(true) - self::$start; + self::$start = null; + + return $ret; + } + + /** + * Ensure that the end of code execution was marked. + * + * The end *should* be marked in the instrumented code, but just in case + * we'll add a fallback here. + */ + private function ensureEndMarked() + { + if (self::$start !== null) { + self::markEnd(); + } + } + + /** + * Instrument code for timeit execution. + * + * This inserts `markStart` and `markEnd` calls to ensure that (reasonably) + * accurate times are recorded for just the code being executed. + * + * @param string $code + * + * @return string + */ + private function instrumentCode(string $code): string + { + return $this->printer->prettyPrint($this->traverser->traverse($this->parse($code))); + } + + /** + * Lex and parse a string of code into statements. + * + * @param string $code + * + * @return array Statements + */ + private function parse(string $code): array + { + $code = 'parser->parse($code); + } catch (\PhpParser\Error $e) { + if (\strpos($e->getMessage(), 'unexpected EOF') === false) { + throw $e; + } + + // If we got an unexpected EOF, let's try it again with a semicolon. + return $this->parser->parse($code.';'); + } + } +} diff --git a/vendor/psy/psysh/src/Command/TimeitCommand/TimeitVisitor.php b/vendor/psy/psysh/src/Command/TimeitCommand/TimeitVisitor.php new file mode 100644 index 0000000000..3f9bd8abd6 --- /dev/null +++ b/vendor/psy/psysh/src/Command/TimeitCommand/TimeitVisitor.php @@ -0,0 +1,140 @@ +functionDepth = 0; + } + + /** + * {@inheritdoc} + */ + public function enterNode(Node $node) + { + // keep track of nested function-like nodes, because they can have + // returns statements... and we don't want to call markEnd for those. + if ($node instanceof FunctionLike) { + $this->functionDepth++; + + return; + } + + // replace any top-level `return` statements with a `markEnd` call + if ($this->functionDepth === 0 && $node instanceof Return_) { + return new Return_($this->getEndCall($node->expr), $node->getAttributes()); + } + } + + /** + * {@inheritdoc} + */ + public function leaveNode(Node $node) + { + if ($node instanceof FunctionLike) { + $this->functionDepth--; + } + } + + /** + * {@inheritdoc} + */ + public function afterTraverse(array $nodes) + { + // prepend a `markStart` call + \array_unshift($nodes, $this->maybeExpression($this->getStartCall())); + + // append a `markEnd` call (wrapping the final node, if it's an expression) + $last = $nodes[\count($nodes) - 1]; + if ($last instanceof Expr) { + \array_pop($nodes); + $nodes[] = $this->getEndCall($last); + } elseif ($last instanceof Expression) { + \array_pop($nodes); + $nodes[] = new Expression($this->getEndCall($last->expr), $last->getAttributes()); + } elseif ($last instanceof Return_) { + // nothing to do here, we're already ending with a return call + } else { + $nodes[] = $this->maybeExpression($this->getEndCall()); + } + + return $nodes; + } + + /** + * Get PhpParser AST nodes for a `markStart` call. + * + * @return \PhpParser\Node\Expr\StaticCall + */ + private function getStartCall(): StaticCall + { + return new StaticCall(new FullyQualifiedName(TimeitCommand::class), 'markStart'); + } + + /** + * Get PhpParser AST nodes for a `markEnd` call. + * + * Optionally pass in a return value. + * + * @param Expr|null $arg + * + * @return \PhpParser\Node\Expr\StaticCall + */ + private function getEndCall(Expr $arg = null): StaticCall + { + if ($arg === null) { + $arg = NoReturnValue::create(); + } + + return new StaticCall(new FullyQualifiedName(TimeitCommand::class), 'markEnd', [new Arg($arg)]); + } + + /** + * Compatibility shim for PHP Parser 3.x. + * + * Wrap $expr in a PhpParser\Node\Stmt\Expression if the class exists. + * + * @param \PhpParser\Node $expr + * @param array $attrs + * + * @return \PhpParser\Node\Expr|\PhpParser\Node\Stmt\Expression + */ + private function maybeExpression(Node $expr, array $attrs = []) + { + return \class_exists(Expression::class) ? new Expression($expr, $attrs) : $expr; + } +} diff --git a/vendor/psy/psysh/src/Command/TraceCommand.php b/vendor/psy/psysh/src/Command/TraceCommand.php new file mode 100644 index 0000000000..09d9fc9174 --- /dev/null +++ b/vendor/psy/psysh/src/Command/TraceCommand.php @@ -0,0 +1,97 @@ +filter = new FilterOptions(); + + parent::__construct($name); + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + list($grep, $insensitive, $invert) = FilterOptions::getOptions(); + + $this + ->setName('trace') + ->setDefinition([ + new InputOption('include-psy', 'p', InputOption::VALUE_NONE, 'Include Psy in the call stack.'), + new InputOption('num', 'n', InputOption::VALUE_REQUIRED, 'Only include NUM lines.'), + + $grep, + $insensitive, + $invert, + ]) + ->setDescription('Show the current call stack.') + ->setHelp( + <<<'HELP' +Show the current call stack. + +Optionally, include PsySH in the call stack by passing the --include-psy option. + +e.g. +> trace -n10 +> trace --include-psy +HELP + ); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->filter->bind($input); + $trace = $this->getBacktrace(new \Exception(), $input->getOption('num'), $input->getOption('include-psy')); + $output->page($trace, ShellOutput::NUMBER_LINES); + + return 0; + } + + /** + * Get a backtrace for an exception. + * + * Optionally limit the number of rows to include with $count, and exclude + * Psy from the trace. + * + * @param \Exception $e The exception with a backtrace + * @param int $count (default: PHP_INT_MAX) + * @param bool $includePsy (default: true) + * + * @return array Formatted stacktrace lines + */ + protected function getBacktrace(\Exception $e, int $count = null, bool $includePsy = true): array + { + return TraceFormatter::formatTrace($e, $this->filter, $count, $includePsy); + } +} diff --git a/vendor/psy/psysh/src/Command/WhereamiCommand.php b/vendor/psy/psysh/src/Command/WhereamiCommand.php new file mode 100644 index 0000000000..ea91f2f081 --- /dev/null +++ b/vendor/psy/psysh/src/Command/WhereamiCommand.php @@ -0,0 +1,159 @@ +backtrace = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS); + + parent::__construct(); + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('whereami') + ->setDefinition([ + new InputOption('num', 'n', InputOption::VALUE_OPTIONAL, 'Number of lines before and after.', '5'), + new InputOption('file', 'f|a', InputOption::VALUE_NONE, 'Show the full source for the current file.'), + ]) + ->setDescription('Show where you are in the code.') + ->setHelp( + <<<'HELP' +Show where you are in the code. + +Optionally, include the number of lines before and after you want to display, +or --file for the whole file. + +e.g. +> whereami +> whereami -n10 +> whereami --file +HELP + ); + } + + /** + * Obtains the correct stack frame in the full backtrace. + * + * @return array + */ + protected function trace(): array + { + foreach (\array_reverse($this->backtrace) as $stackFrame) { + if ($this->isDebugCall($stackFrame)) { + return $stackFrame; + } + } + + return \end($this->backtrace); + } + + private static function isDebugCall(array $stackFrame): bool + { + $class = isset($stackFrame['class']) ? $stackFrame['class'] : null; + $function = isset($stackFrame['function']) ? $stackFrame['function'] : null; + + return ($class === null && $function === 'Psy\\debug') || + ($class === Shell::class && \in_array($function, ['__construct', 'debug'])); + } + + /** + * Determine the file and line based on the specific backtrace. + * + * @return array + */ + protected function fileInfo(): array + { + $stackFrame = $this->trace(); + if (\preg_match('/eval\(/', $stackFrame['file'])) { + \preg_match_all('/([^\(]+)\((\d+)/', $stackFrame['file'], $matches); + $file = $matches[1][0]; + $line = (int) $matches[2][0]; + } else { + $file = $stackFrame['file']; + $line = $stackFrame['line']; + } + + return \compact('file', 'line'); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $info = $this->fileInfo(); + $num = $input->getOption('num'); + $lineNum = $info['line']; + $startLine = \max($lineNum - $num, 1); + $endLine = $lineNum + $num; + $code = \file_get_contents($info['file']); + + if ($input->getOption('file')) { + $startLine = 1; + $endLine = null; + } + + if ($output instanceof ShellOutput) { + $output->startPaging(); + } + + $output->writeln(\sprintf('From %s:%s:', $this->replaceCwd($info['file']), $lineNum)); + $output->write(CodeFormatter::formatCode($code, $startLine, $endLine, $lineNum), false); + + if ($output instanceof ShellOutput) { + $output->stopPaging(); + } + + return 0; + } + + /** + * Replace the given directory from the start of a filepath. + * + * @param string $file + * + * @return string + */ + private function replaceCwd(string $file): string + { + $cwd = \getcwd(); + if ($cwd === false) { + return $file; + } + + $cwd = \rtrim($cwd, \DIRECTORY_SEPARATOR).\DIRECTORY_SEPARATOR; + + return \preg_replace('/^'.\preg_quote($cwd, '/').'/', '', $file); + } +} diff --git a/vendor/psy/psysh/src/Command/WtfCommand.php b/vendor/psy/psysh/src/Command/WtfCommand.php new file mode 100644 index 0000000000..a2e8feb54a --- /dev/null +++ b/vendor/psy/psysh/src/Command/WtfCommand.php @@ -0,0 +1,134 @@ +context = $context; + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + list($grep, $insensitive, $invert) = FilterOptions::getOptions(); + + $this + ->setName('wtf') + ->setAliases(['last-exception', 'wtf?']) + ->setDefinition([ + new InputArgument('incredulity', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Number of lines to show.'), + new InputOption('all', 'a', InputOption::VALUE_NONE, 'Show entire backtrace.'), + + $grep, + $insensitive, + $invert, + ]) + ->setDescription('Show the backtrace of the most recent exception.') + ->setHelp( + <<<'HELP' +Shows a few lines of the backtrace of the most recent exception. + +If you want to see more lines, add more question marks or exclamation marks: + +e.g. +>>> wtf ? +>>> wtf ?!???!?!? + +To see the entire backtrace, pass the -a/--all flag: + +e.g. +>>> wtf -a +HELP + ); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->filter->bind($input); + + $incredulity = \implode('', $input->getArgument('incredulity')); + if (\strlen(\preg_replace('/[\\?!]/', '', $incredulity))) { + throw new \InvalidArgumentException('Incredulity must include only "?" and "!"'); + } + + $exception = $this->context->getLastException(); + $count = $input->getOption('all') ? \PHP_INT_MAX : \max(3, \pow(2, \strlen($incredulity) + 1)); + + $shell = $this->getApplication(); + + if ($output instanceof ShellOutput) { + $output->startPaging(); + } + + do { + $traceCount = \count($exception->getTrace()); + $showLines = $count; + // Show the whole trace if we'd only be hiding a few lines + if ($traceCount < \max($count * 1.2, $count + 2)) { + $showLines = \PHP_INT_MAX; + } + + $trace = $this->getBacktrace($exception, $showLines); + $moreLines = $traceCount - \count($trace); + + $output->writeln($shell->formatException($exception)); + $output->writeln('--'); + $output->write($trace, true, ShellOutput::NUMBER_LINES); + $output->writeln(''); + + if ($moreLines > 0) { + $output->writeln(\sprintf( + '', + $moreLines + )); + $output->writeln(''); + } + } while ($exception = $exception->getPrevious()); + + if ($output instanceof ShellOutput) { + $output->stopPaging(); + } + + return 0; + } +} diff --git a/vendor/psy/psysh/src/ConfigPaths.php b/vendor/psy/psysh/src/ConfigPaths.php new file mode 100644 index 0000000000..7e766a9734 --- /dev/null +++ b/vendor/psy/psysh/src/ConfigPaths.php @@ -0,0 +1,413 @@ +overrideDirs($overrides); + $this->env = $env ?: new SuperglobalsEnv(); + } + + /** + * Provide `configDir`, `dataDir` and `runtimeDir` overrides. + * + * If a key is set but empty, the override will be removed. If it is not set + * at all, any existing override will persist. + * + * @param string[] $overrides Directory overrides + */ + public function overrideDirs(array $overrides) + { + if (\array_key_exists('configDir', $overrides)) { + $this->configDir = $overrides['configDir'] ?: null; + } + + if (\array_key_exists('dataDir', $overrides)) { + $this->dataDir = $overrides['dataDir'] ?: null; + } + + if (\array_key_exists('runtimeDir', $overrides)) { + $this->runtimeDir = $overrides['runtimeDir'] ?: null; + } + } + + /** + * Get the current home directory. + * + * @return string|null + */ + public function homeDir() + { + if ($homeDir = $this->getEnv('HOME') ?: $this->windowsHomeDir()) { + return \strtr($homeDir, '\\', '/'); + } + + return null; + } + + private function windowsHomeDir() + { + if (\defined('PHP_WINDOWS_VERSION_MAJOR')) { + $homeDrive = $this->getEnv('HOMEDRIVE'); + $homePath = $this->getEnv('HOMEPATH'); + if ($homeDrive && $homePath) { + return $homeDrive.'/'.$homePath; + } + } + + return null; + } + + private function homeConfigDir() + { + if ($homeConfigDir = $this->getEnv('XDG_CONFIG_HOME')) { + return $homeConfigDir; + } + + $homeDir = $this->homeDir(); + + return $homeDir === '/' ? $homeDir.'.config' : $homeDir.'/.config'; + } + + /** + * Get potential config directory paths. + * + * Returns `~/.psysh`, `%APPDATA%/PsySH` (when on Windows), and all + * XDG Base Directory config directories: + * + * http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html + * + * @return string[] + */ + public function configDirs(): array + { + if ($this->configDir !== null) { + return [$this->configDir]; + } + + $configDirs = $this->getEnvArray('XDG_CONFIG_DIRS') ?: ['/etc/xdg']; + + return $this->allDirNames(\array_merge([$this->homeConfigDir()], $configDirs)); + } + + /** + * @deprecated + */ + public static function getConfigDirs(): array + { + return (new self())->configDirs(); + } + + /** + * Get potential home config directory paths. + * + * Returns `~/.psysh`, `%APPDATA%/PsySH` (when on Windows), and the + * XDG Base Directory home config directory: + * + * http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html + * + * @deprecated + * + * @return string[] + */ + public static function getHomeConfigDirs(): array + { + // Not quite the same, but this is deprecated anyway /shrug + return self::getConfigDirs(); + } + + /** + * Get the current home config directory. + * + * Returns the highest precedence home config directory which actually + * exists. If none of them exists, returns the highest precedence home + * config directory (`%APPDATA%/PsySH` on Windows, `~/.config/psysh` + * everywhere else). + * + * @see self::homeConfigDir + * + * @return string + */ + public function currentConfigDir(): string + { + if ($this->configDir !== null) { + return $this->configDir; + } + + $configDirs = $this->allDirNames([$this->homeConfigDir()]); + + foreach ($configDirs as $configDir) { + if (@\is_dir($configDir)) { + return $configDir; + } + } + + return $configDirs[0]; + } + + /** + * @deprecated + */ + public static function getCurrentConfigDir(): string + { + return (new self())->currentConfigDir(); + } + + /** + * Find real config files in config directories. + * + * @param string[] $names Config file names + * + * @return string[] + */ + public function configFiles(array $names): array + { + return $this->allRealFiles($this->configDirs(), $names); + } + + /** + * @deprecated + */ + public static function getConfigFiles(array $names, $configDir = null): array + { + return (new self(['configDir' => $configDir]))->configFiles($names); + } + + /** + * Get potential data directory paths. + * + * If a `dataDir` option was explicitly set, returns an array containing + * just that directory. + * + * Otherwise, it returns `~/.psysh` and all XDG Base Directory data directories: + * + * http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html + * + * @return string[] + */ + public function dataDirs(): array + { + if ($this->dataDir !== null) { + return [$this->dataDir]; + } + + $homeDataDir = $this->getEnv('XDG_DATA_HOME') ?: $this->homeDir().'/.local/share'; + $dataDirs = $this->getEnvArray('XDG_DATA_DIRS') ?: ['/usr/local/share', '/usr/share']; + + return $this->allDirNames(\array_merge([$homeDataDir], $dataDirs)); + } + + /** + * @deprecated + */ + public static function getDataDirs(): array + { + return (new self())->dataDirs(); + } + + /** + * Find real data files in config directories. + * + * @param string[] $names Config file names + * + * @return string[] + */ + public function dataFiles(array $names): array + { + return $this->allRealFiles($this->dataDirs(), $names); + } + + /** + * @deprecated + */ + public static function getDataFiles(array $names, $dataDir = null): array + { + return (new self(['dataDir' => $dataDir]))->dataFiles($names); + } + + /** + * Get a runtime directory. + * + * Defaults to `/psysh` inside the system's temp dir. + * + * @return string + */ + public function runtimeDir(): string + { + if ($this->runtimeDir !== null) { + return $this->runtimeDir; + } + + // Fallback to a boring old folder in the system temp dir. + $runtimeDir = $this->getEnv('XDG_RUNTIME_DIR') ?: \sys_get_temp_dir(); + + return \strtr($runtimeDir, '\\', '/').'/psysh'; + } + + /** + * @deprecated + */ + public static function getRuntimeDir(): string + { + return (new self())->runtimeDir(); + } + + /** + * Get all PsySH directory name candidates given a list of base directories. + * + * This expects that XDG-compatible directory paths will be passed in. + * `psysh` will be added to each of $baseDirs, and we'll throw in `~/.psysh` + * and a couple of Windows-friendly paths as well. + * + * @param string[] $baseDirs base directory paths + * + * @return string[] + */ + private function allDirNames(array $baseDirs): array + { + $dirs = \array_map(function ($dir) { + return \strtr($dir, '\\', '/').'/psysh'; + }, $baseDirs); + + // Add ~/.psysh + if ($home = $this->getEnv('HOME')) { + $dirs[] = \strtr($home, '\\', '/').'/.psysh'; + } + + // Add some Windows specific ones :) + if (\defined('PHP_WINDOWS_VERSION_MAJOR')) { + if ($appData = $this->getEnv('APPDATA')) { + // AppData gets preference + \array_unshift($dirs, \strtr($appData, '\\', '/').'/PsySH'); + } + + if ($windowsHomeDir = $this->windowsHomeDir()) { + $dir = \strtr($windowsHomeDir, '\\', '/').'/.psysh'; + if (!\in_array($dir, $dirs)) { + $dirs[] = $dir; + } + } + } + + return $dirs; + } + + /** + * Given a list of directories, and a list of filenames, find the ones that + * are real files. + * + * @return string[] + */ + private function allRealFiles(array $dirNames, array $fileNames): array + { + $files = []; + foreach ($dirNames as $dir) { + foreach ($fileNames as $name) { + $file = $dir.'/'.$name; + if (@\is_file($file)) { + $files[] = $file; + } + } + } + + return $files; + } + + /** + * Ensure that $dir exists and is writable. + * + * Generates E_USER_NOTICE error if the directory is not writable or creatable. + * + * @param string $dir + * + * @return bool False if directory exists but is not writeable, or cannot be created + */ + public static function ensureDir(string $dir): bool + { + if (!\is_dir($dir)) { + // Just try making it and see if it works + @\mkdir($dir, 0700, true); + } + + if (!\is_dir($dir) || !\is_writable($dir)) { + \trigger_error(\sprintf('Writing to directory %s is not allowed.', $dir), \E_USER_NOTICE); + + return false; + } + + return true; + } + + /** + * Ensure that $file exists and is writable, make the parent directory if necessary. + * + * Generates E_USER_NOTICE error if either $file or its directory is not writable. + * + * @param string $file + * + * @return string|false Full path to $file, or false if file is not writable + */ + public static function touchFileWithMkdir(string $file) + { + if (\file_exists($file)) { + if (\is_writable($file)) { + return $file; + } + + \trigger_error(\sprintf('Writing to %s is not allowed.', $file), \E_USER_NOTICE); + + return false; + } + + if (!self::ensureDir(\dirname($file))) { + return false; + } + + \touch($file); + + return $file; + } + + private function getEnv($key) + { + return $this->env->get($key); + } + + private function getEnvArray($key) + { + if ($value = $this->getEnv($key)) { + return \explode(':', $value); + } + + return null; + } +} diff --git a/vendor/psy/psysh/src/Configuration.php b/vendor/psy/psysh/src/Configuration.php new file mode 100644 index 0000000000..d7039d81c5 --- /dev/null +++ b/vendor/psy/psysh/src/Configuration.php @@ -0,0 +1,1826 @@ +configPaths = new ConfigPaths(); + + // explicit configFile option + if (isset($config['configFile'])) { + $this->configFile = $config['configFile']; + } elseif (isset($_SERVER['PSYSH_CONFIG']) && $_SERVER['PSYSH_CONFIG']) { + $this->configFile = $_SERVER['PSYSH_CONFIG']; + } + + // legacy baseDir option + if (isset($config['baseDir'])) { + $msg = "The 'baseDir' configuration option is deprecated; ". + "please specify 'configDir' and 'dataDir' options instead"; + throw new DeprecatedException($msg); + } + + unset($config['configFile'], $config['baseDir']); + + // go go gadget, config! + $this->loadConfig($config); + $this->init(); + } + + /** + * Construct a Configuration object from Symfony Console input. + * + * This is great for adding psysh-compatible command line options to framework- or app-specific + * wrappers. + * + * $input should already be bound to an appropriate InputDefinition (see self::getInputOptions + * if you want to build your own) before calling this method. It's not required, but things work + * a lot better if we do. + * + * @see self::getInputOptions + * + * @throws \InvalidArgumentException + * + * @param InputInterface $input + * + * @return self + */ + public static function fromInput(InputInterface $input): self + { + $config = new self(['configFile' => self::getConfigFileFromInput($input)]); + + // Handle --color and --no-color (and --ansi and --no-ansi aliases) + if (self::getOptionFromInput($input, ['color', 'ansi'])) { + $config->setColorMode(self::COLOR_MODE_FORCED); + } elseif (self::getOptionFromInput($input, ['no-color', 'no-ansi'])) { + $config->setColorMode(self::COLOR_MODE_DISABLED); + } + + // Handle verbosity options + if ($verbosity = self::getVerbosityFromInput($input)) { + $config->setVerbosity($verbosity); + } + + // Handle interactive mode + if (self::getOptionFromInput($input, ['interactive', 'interaction'], ['-a', '-i'])) { + $config->setInteractiveMode(self::INTERACTIVE_MODE_FORCED); + } elseif (self::getOptionFromInput($input, ['no-interactive', 'no-interaction'], ['-n'])) { + $config->setInteractiveMode(self::INTERACTIVE_MODE_DISABLED); + } + + // Handle --raw-output + // @todo support raw output with interactive input? + if (!$config->getInputInteractive()) { + if (self::getOptionFromInput($input, ['raw-output'], ['-r'])) { + $config->setRawOutput(true); + } + } + + // Handle --yolo + if (self::getOptionFromInput($input, ['yolo'])) { + $config->setYolo(true); + } + + return $config; + } + + /** + * Get the desired config file from the given input. + * + * @return string|null config file path, or null if none is specified + */ + private static function getConfigFileFromInput(InputInterface $input) + { + // Best case, input is properly bound and validated. + if ($input->hasOption('config')) { + return $input->getOption('config'); + } + + return $input->getParameterOption('--config', null, true) ?: $input->getParameterOption('-c', null, true); + } + + /** + * Get a boolean option from the given input. + * + * This helper allows fallback for unbound and unvalidated input. It's not perfect--for example, + * it can't deal with several short options squished together--but it's better than falling over + * any time someone gives us unbound input. + * + * @return bool true if the option (or an alias) is present + */ + private static function getOptionFromInput(InputInterface $input, array $names, array $otherParams = []): bool + { + // Best case, input is properly bound and validated. + foreach ($names as $name) { + if ($input->hasOption($name) && $input->getOption($name)) { + return true; + } + } + + foreach ($names as $name) { + $otherParams[] = '--'.$name; + } + + foreach ($otherParams as $name) { + if ($input->hasParameterOption($name, true)) { + return true; + } + } + + return false; + } + + /** + * Get the desired verbosity from the given input. + * + * This is a bit more complext than the other options parsers. It handles `--quiet` and + * `--verbose`, along with their short aliases, and fancy things like `-vvv`. + * + * @return string|null configuration constant, or null if no verbosity option is specified + */ + private static function getVerbosityFromInput(InputInterface $input) + { + // --quiet wins! + if (self::getOptionFromInput($input, ['quiet'], ['-q'])) { + return self::VERBOSITY_QUIET; + } + + // Best case, input is properly bound and validated. + // + // Note that if the `--verbose` option is incorrectly defined as `VALUE_NONE` rather than + // `VALUE_OPTIONAL` (as it is in Symfony Console by default) it doesn't actually work with + // multiple verbosity levels as it claims. + // + // We can detect this by checking whether the the value === true, and fall back to unbound + // parsing for this option. + if ($input->hasOption('verbose') && $input->getOption('verbose') !== true) { + switch ($input->getOption('verbose')) { + case '-1': + return self::VERBOSITY_QUIET; + case '0': // explicitly normal, overrides config file default + return self::VERBOSITY_NORMAL; + case '1': + case null: // `--verbose` and `-v` + return self::VERBOSITY_VERBOSE; + case '2': + case 'v': // `-vv` + return self::VERBOSITY_VERY_VERBOSE; + case '3': + case 'vv': // `-vvv` + return self::VERBOSITY_DEBUG; + default: // implicitly normal, config file default wins + return; + } + } + + // quiet and normal have to come before verbose, because it eats everything else. + if ($input->hasParameterOption('--verbose=-1', true) || $input->getParameterOption('--verbose', false, true) === '-1') { + return self::VERBOSITY_QUIET; + } + + if ($input->hasParameterOption('--verbose=0', true) || $input->getParameterOption('--verbose', false, true) === '0') { + return self::VERBOSITY_NORMAL; + } + + // `-vvv`, `-vv` and `-v` have to come in descending length order, because `hasParameterOption` matches prefixes. + if ($input->hasParameterOption('-vvv', true) || $input->hasParameterOption('--verbose=3', true) || $input->getParameterOption('--verbose', false, true) === '3') { + return self::VERBOSITY_DEBUG; + } + + if ($input->hasParameterOption('-vv', true) || $input->hasParameterOption('--verbose=2', true) || $input->getParameterOption('--verbose', false, true) === '2') { + return self::VERBOSITY_VERY_VERBOSE; + } + + if ($input->hasParameterOption('-v', true) || $input->hasParameterOption('--verbose=1', true) || $input->hasParameterOption('--verbose', true)) { + return self::VERBOSITY_VERBOSE; + } + } + + /** + * Get a list of input options expected when initializing Configuration via input. + * + * @see self::fromInput + * + * @return InputOption[] + */ + public static function getInputOptions(): array + { + return [ + new InputOption('config', 'c', InputOption::VALUE_REQUIRED, 'Use an alternate PsySH config file location.'), + new InputOption('cwd', null, InputOption::VALUE_REQUIRED, 'Use an alternate working directory.'), + + new InputOption('color', null, InputOption::VALUE_NONE, 'Force colors in output.'), + new InputOption('no-color', null, InputOption::VALUE_NONE, 'Disable colors in output.'), + // --ansi and --no-ansi aliases to match Symfony, Composer, etc. + new InputOption('ansi', null, InputOption::VALUE_NONE, 'Force colors in output.'), + new InputOption('no-ansi', null, InputOption::VALUE_NONE, 'Disable colors in output.'), + + new InputOption('quiet', 'q', InputOption::VALUE_NONE, 'Shhhhhh.'), + new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_OPTIONAL, 'Increase the verbosity of messages.', '0'), + new InputOption('interactive', 'i|a', InputOption::VALUE_NONE, 'Force PsySH to run in interactive mode.'), + new InputOption('no-interactive', 'n', InputOption::VALUE_NONE, 'Run PsySH without interactive input. Requires input from stdin.'), + // --interaction and --no-interaction aliases for compatibility with Symfony, Composer, etc + new InputOption('interaction', null, InputOption::VALUE_NONE, 'Force PsySH to run in interactive mode.'), + new InputOption('no-interaction', null, InputOption::VALUE_NONE, 'Run PsySH without interactive input. Requires input from stdin.'), + new InputOption('raw-output', 'r', InputOption::VALUE_NONE, 'Print var_export-style return values (for non-interactive input)'), + + new InputOption('yolo', null, InputOption::VALUE_NONE, 'Run PsySH with minimal input validation. You probably don\'t want this.'), + ]; + } + + /** + * Initialize the configuration. + * + * This checks for the presence of Readline and Pcntl extensions. + * + * If a config file is available, it will be loaded and merged with the current config. + * + * If no custom config file was specified and a local project config file + * is available, it will be loaded and merged with the current config. + */ + public function init() + { + // feature detection + $this->hasReadline = \function_exists('readline'); + $this->hasPcntl = ProcessForker::isSupported(); + + if ($configFile = $this->getConfigFile()) { + $this->loadConfigFile($configFile); + } + + if (!$this->configFile && $localConfig = $this->getLocalConfigFile()) { + $this->loadConfigFile($localConfig); + } + + $this->configPaths->overrideDirs([ + 'configDir' => $this->configDir, + 'dataDir' => $this->dataDir, + 'runtimeDir' => $this->runtimeDir, + ]); + } + + /** + * Get the current PsySH config file. + * + * If a `configFile` option was passed to the Configuration constructor, + * this file will be returned. If not, all possible config directories will + * be searched, and the first `config.php` or `rc.php` file which exists + * will be returned. + * + * If you're trying to decide where to put your config file, pick + * + * ~/.config/psysh/config.php + * + * @return string|null + */ + public function getConfigFile() + { + if (isset($this->configFile)) { + return $this->configFile; + } + + $files = $this->configPaths->configFiles(['config.php', 'rc.php']); + + if (!empty($files)) { + if ($this->warnOnMultipleConfigs && \count($files) > 1) { + $msg = \sprintf('Multiple configuration files found: %s. Using %s', \implode(', ', $files), $files[0]); + \trigger_error($msg, \E_USER_NOTICE); + } + + return $files[0]; + } + } + + /** + * Get the local PsySH config file. + * + * Searches for a project specific config file `.psysh.php` in the current + * working directory. + * + * @return string|null + */ + public function getLocalConfigFile() + { + $localConfig = \getcwd().'/.psysh.php'; + + if (@\is_file($localConfig)) { + return $localConfig; + } + } + + /** + * Load configuration values from an array of options. + * + * @param array $options + */ + public function loadConfig(array $options) + { + foreach (self::$AVAILABLE_OPTIONS as $option) { + if (isset($options[$option])) { + $method = 'set'.\ucfirst($option); + $this->$method($options[$option]); + } + } + + // legacy `tabCompletion` option + if (isset($options['tabCompletion'])) { + $msg = '`tabCompletion` is deprecated; use `useTabCompletion` instead.'; + @\trigger_error($msg, \E_USER_DEPRECATED); + + $this->setUseTabCompletion($options['tabCompletion']); + } + + foreach (['commands', 'matchers', 'casters'] as $option) { + if (isset($options[$option])) { + $method = 'add'.\ucfirst($option); + $this->$method($options[$option]); + } + } + + // legacy `tabCompletionMatchers` option + if (isset($options['tabCompletionMatchers'])) { + $msg = '`tabCompletionMatchers` is deprecated; use `matchers` instead.'; + @\trigger_error($msg, \E_USER_DEPRECATED); + + $this->addMatchers($options['tabCompletionMatchers']); + } + } + + /** + * Load a configuration file (default: `$HOME/.config/psysh/config.php`). + * + * This configuration instance will be available to the config file as $config. + * The config file may directly manipulate the configuration, or may return + * an array of options which will be merged with the current configuration. + * + * @throws \InvalidArgumentException if the config file does not exist or returns a non-array result + * + * @param string $file + */ + public function loadConfigFile(string $file) + { + if (!\is_file($file)) { + throw new \InvalidArgumentException(\sprintf('Invalid configuration file specified, %s does not exist', $file)); + } + + $__psysh_config_file__ = $file; + $load = function ($config) use ($__psysh_config_file__) { + $result = require $__psysh_config_file__; + if ($result !== 1) { + return $result; + } + }; + $result = $load($this); + + if (!empty($result)) { + if (\is_array($result)) { + $this->loadConfig($result); + } else { + throw new \InvalidArgumentException('Psy Shell configuration must return an array of options'); + } + } + } + + /** + * Set files to be included by default at the start of each shell session. + * + * @param array $includes + */ + public function setDefaultIncludes(array $includes = []) + { + $this->defaultIncludes = $includes; + } + + /** + * Get files to be included by default at the start of each shell session. + * + * @return array + */ + public function getDefaultIncludes(): array + { + return $this->defaultIncludes ?: []; + } + + /** + * Set the shell's config directory location. + * + * @param string $dir + */ + public function setConfigDir(string $dir) + { + $this->configDir = (string) $dir; + + $this->configPaths->overrideDirs([ + 'configDir' => $this->configDir, + 'dataDir' => $this->dataDir, + 'runtimeDir' => $this->runtimeDir, + ]); + } + + /** + * Get the current configuration directory, if any is explicitly set. + * + * @return string|null + */ + public function getConfigDir() + { + return $this->configDir; + } + + /** + * Set the shell's data directory location. + * + * @param string $dir + */ + public function setDataDir(string $dir) + { + $this->dataDir = (string) $dir; + + $this->configPaths->overrideDirs([ + 'configDir' => $this->configDir, + 'dataDir' => $this->dataDir, + 'runtimeDir' => $this->runtimeDir, + ]); + } + + /** + * Get the current data directory, if any is explicitly set. + * + * @return string|null + */ + public function getDataDir() + { + return $this->dataDir; + } + + /** + * Set the shell's temporary directory location. + * + * @param string $dir + */ + public function setRuntimeDir(string $dir) + { + $this->runtimeDir = (string) $dir; + + $this->configPaths->overrideDirs([ + 'configDir' => $this->configDir, + 'dataDir' => $this->dataDir, + 'runtimeDir' => $this->runtimeDir, + ]); + } + + /** + * Get the shell's temporary directory location. + * + * Defaults to `/psysh` inside the system's temp dir unless explicitly + * overridden. + * + * @throws RuntimeException if no temporary directory is set and it is not possible to create one + * + * @return string + */ + public function getRuntimeDir(): string + { + $runtimeDir = $this->configPaths->runtimeDir(); + + if (!\is_dir($runtimeDir)) { + if (!@\mkdir($runtimeDir, 0700, true)) { + throw new RuntimeException(\sprintf('Unable to create PsySH runtime directory. Make sure PHP is able to write to %s in order to continue.', \dirname($runtimeDir))); + } + } + + return $runtimeDir; + } + + /** + * Set the readline history file path. + * + * @param string $file + */ + public function setHistoryFile(string $file) + { + $this->historyFile = ConfigPaths::touchFileWithMkdir($file); + } + + /** + * Get the readline history file path. + * + * Defaults to `/history` inside the shell's base config dir unless + * explicitly overridden. + * + * @return string + */ + public function getHistoryFile(): string + { + if (isset($this->historyFile)) { + return $this->historyFile; + } + + $files = $this->configPaths->configFiles(['psysh_history', 'history']); + + if (!empty($files)) { + if ($this->warnOnMultipleConfigs && \count($files) > 1) { + $msg = \sprintf('Multiple history files found: %s. Using %s', \implode(', ', $files), $files[0]); + \trigger_error($msg, \E_USER_NOTICE); + } + + $this->setHistoryFile($files[0]); + } else { + // fallback: create our own history file + $this->setHistoryFile($this->configPaths->currentConfigDir().'/psysh_history'); + } + + return $this->historyFile; + } + + /** + * Set the readline max history size. + * + * @param int $value + */ + public function setHistorySize(int $value) + { + $this->historySize = (int) $value; + } + + /** + * Get the readline max history size. + * + * @return int + */ + public function getHistorySize() + { + return $this->historySize; + } + + /** + * Sets whether readline erases old duplicate history entries. + * + * @param bool $value + */ + public function setEraseDuplicates(bool $value) + { + $this->eraseDuplicates = (bool) $value; + } + + /** + * Get whether readline erases old duplicate history entries. + * + * @return bool|null + */ + public function getEraseDuplicates() + { + return $this->eraseDuplicates; + } + + /** + * Get a temporary file of type $type for process $pid. + * + * The file will be created inside the current temporary directory. + * + * @see self::getRuntimeDir + * + * @param string $type + * @param int $pid + * + * @return string Temporary file name + */ + public function getTempFile(string $type, int $pid): string + { + return \tempnam($this->getRuntimeDir(), $type.'_'.$pid.'_'); + } + + /** + * Get a filename suitable for a FIFO pipe of $type for process $pid. + * + * The pipe will be created inside the current temporary directory. + * + * @param string $type + * @param int $pid + * + * @return string Pipe name + */ + public function getPipe(string $type, int $pid): string + { + return \sprintf('%s/%s_%s', $this->getRuntimeDir(), $type, $pid); + } + + /** + * Check whether this PHP instance has Readline available. + * + * @return bool True if Readline is available + */ + public function hasReadline(): bool + { + return $this->hasReadline; + } + + /** + * Enable or disable Readline usage. + * + * @param bool $useReadline + */ + public function setUseReadline(bool $useReadline) + { + $this->useReadline = (bool) $useReadline; + } + + /** + * Check whether to use Readline. + * + * If `setUseReadline` as been set to true, but Readline is not actually + * available, this will return false. + * + * @return bool True if the current Shell should use Readline + */ + public function useReadline(): bool + { + return isset($this->useReadline) ? ($this->hasReadline && $this->useReadline) : $this->hasReadline; + } + + /** + * Set the Psy Shell readline service. + * + * @param Readline\Readline $readline + */ + public function setReadline(Readline\Readline $readline) + { + $this->readline = $readline; + } + + /** + * Get the Psy Shell readline service. + * + * By default, this service uses (in order of preference): + * + * * GNU Readline + * * Libedit + * * A transient array-based readline emulation. + * + * @return Readline\Readline + */ + public function getReadline(): Readline\Readline + { + if (!isset($this->readline)) { + $className = $this->getReadlineClass(); + $this->readline = new $className( + $this->getHistoryFile(), + $this->getHistorySize(), + $this->getEraseDuplicates() + ); + } + + return $this->readline; + } + + /** + * Get the appropriate Readline implementation class name. + * + * @see self::getReadline + * + * @return string + */ + private function getReadlineClass(): string + { + if ($this->useReadline()) { + if (Readline\GNUReadline::isSupported()) { + return Readline\GNUReadline::class; + } elseif (Readline\Libedit::isSupported()) { + return Readline\Libedit::class; + } + } + + if (Readline\Userland::isSupported()) { + return Readline\Userland::class; + } + + return Readline\Transient::class; + } + + /** + * Enable or disable bracketed paste. + * + * Note that this only works with readline (not libedit) integration for now. + * + * @param bool $useBracketedPaste + */ + public function setUseBracketedPaste(bool $useBracketedPaste) + { + $this->useBracketedPaste = (bool) $useBracketedPaste; + } + + /** + * Check whether to use bracketed paste with readline. + * + * When this works, it's magical. Tabs in pastes don't try to autcomplete. + * Newlines in paste don't execute code until you get to the end. It makes + * readline act like you'd expect when pasting. + * + * But it often (usually?) does not work. And when it doesn't, it just spews + * escape codes all over the place and generally makes things ugly :( + * + * If `useBracketedPaste` has been set to true, but the current readline + * implementation is anything besides GNU readline, this will return false. + * + * @return bool True if the shell should use bracketed paste + */ + public function useBracketedPaste(): bool + { + $readlineClass = $this->getReadlineClass(); + + return $this->useBracketedPaste && $readlineClass::supportsBracketedPaste(); + + // @todo mebbe turn this on by default some day? + // return $readlineClass::supportsBracketedPaste() && $this->useBracketedPaste !== false; + } + + /** + * Check whether this PHP instance has Pcntl available. + * + * @return bool True if Pcntl is available + */ + public function hasPcntl(): bool + { + return $this->hasPcntl; + } + + /** + * Enable or disable Pcntl usage. + * + * @param bool $usePcntl + */ + public function setUsePcntl(bool $usePcntl) + { + $this->usePcntl = (bool) $usePcntl; + } + + /** + * Check whether to use Pcntl. + * + * If `setUsePcntl` has been set to true, but Pcntl is not actually + * available, this will return false. + * + * @return bool True if the current Shell should use Pcntl + */ + public function usePcntl(): bool + { + return isset($this->usePcntl) ? ($this->hasPcntl && $this->usePcntl) : $this->hasPcntl; + } + + /** + * Check whether to use raw output. + * + * This is set by the --raw-output (-r) flag, and really only makes sense + * when non-interactive, e.g. executing stdin. + * + * @return bool true if raw output is enabled + */ + public function rawOutput(): bool + { + return $this->rawOutput; + } + + /** + * Enable or disable raw output. + * + * @param bool $rawOutput + */ + public function setRawOutput(bool $rawOutput) + { + $this->rawOutput = (bool) $rawOutput; + } + + /** + * Enable or disable strict requirement of semicolons. + * + * @see self::requireSemicolons() + * + * @param bool $requireSemicolons + */ + public function setRequireSemicolons(bool $requireSemicolons) + { + $this->requireSemicolons = (bool) $requireSemicolons; + } + + /** + * Check whether to require semicolons on all statements. + * + * By default, PsySH will automatically insert semicolons at the end of + * statements if they're missing. To strictly require semicolons, set + * `requireSemicolons` to true. + * + * @return bool + */ + public function requireSemicolons(): bool + { + return $this->requireSemicolons; + } + + /** + * Enable or disable Unicode in PsySH specific output. + * + * Note that this does not disable Unicode output in general, it just makes + * it so PsySH won't output any itself. + * + * @param bool $useUnicode + */ + public function setUseUnicode(bool $useUnicode) + { + $this->useUnicode = (bool) $useUnicode; + } + + /** + * Check whether to use Unicode in PsySH specific output. + * + * Note that this does not disable Unicode output in general, it just makes + * it so PsySH won't output any itself. + * + * @return bool + */ + public function useUnicode(): bool + { + if (isset($this->useUnicode)) { + return $this->useUnicode; + } + + // @todo detect `chsh` != 65001 on Windows and return false + return true; + } + + /** + * Set the error logging level. + * + * @see self::errorLoggingLevel + * + * @param int $errorLoggingLevel + */ + public function setErrorLoggingLevel($errorLoggingLevel) + { + $this->errorLoggingLevel = (\E_ALL | \E_STRICT) & $errorLoggingLevel; + } + + /** + * Get the current error logging level. + * + * By default, PsySH will automatically log all errors, regardless of the + * current `error_reporting` level. + * + * Set `errorLoggingLevel` to 0 to prevent logging non-thrown errors. Set it + * to any valid error_reporting value to log only errors which match that + * level. + * + * http://php.net/manual/en/function.error-reporting.php + * + * @return int + */ + public function errorLoggingLevel(): int + { + return $this->errorLoggingLevel; + } + + /** + * Set a CodeCleaner service instance. + * + * @param CodeCleaner $cleaner + */ + public function setCodeCleaner(CodeCleaner $cleaner) + { + $this->cleaner = $cleaner; + } + + /** + * Get a CodeCleaner service instance. + * + * If none has been explicitly defined, this will create a new instance. + * + * @return CodeCleaner + */ + public function getCodeCleaner(): CodeCleaner + { + if (!isset($this->cleaner)) { + $this->cleaner = new CodeCleaner(null, null, null, $this->yolo()); + } + + return $this->cleaner; + } + + /** + * Enable or disable running PsySH without input validation. + * + * You don't want this. + */ + public function setYolo($yolo) + { + $this->yolo = (bool) $yolo; + } + + /** + * Check whether to disable input validation. + */ + public function yolo(): bool + { + return $this->yolo; + } + + /** + * Enable or disable tab completion. + * + * @param bool $useTabCompletion + */ + public function setUseTabCompletion(bool $useTabCompletion) + { + $this->useTabCompletion = (bool) $useTabCompletion; + } + + /** + * @deprecated Call `setUseTabCompletion` instead + * + * @param bool $useTabCompletion + */ + public function setTabCompletion(bool $useTabCompletion) + { + $this->setUseTabCompletion($useTabCompletion); + } + + /** + * Check whether to use tab completion. + * + * If `setUseTabCompletion` has been set to true, but readline is not + * actually available, this will return false. + * + * @return bool True if the current Shell should use tab completion + */ + public function useTabCompletion(): bool + { + return isset($this->useTabCompletion) ? ($this->hasReadline && $this->useTabCompletion) : $this->hasReadline; + } + + /** + * @deprecated Call `useTabCompletion` instead + * + * @return bool + */ + public function getTabCompletion(): bool + { + return $this->useTabCompletion(); + } + + /** + * Set the Shell Output service. + * + * @param ShellOutput $output + */ + public function setOutput(ShellOutput $output) + { + $this->output = $output; + $this->pipedOutput = null; // Reset cached pipe info + $this->applyFormatterStyles(); + } + + /** + * Get a Shell Output service instance. + * + * If none has been explicitly provided, this will create a new instance + * with the configured verbosity and output pager supplied by self::getPager + * + * @see self::verbosity + * @see self::getPager + * + * @return ShellOutput + */ + public function getOutput(): ShellOutput + { + if (!isset($this->output)) { + $this->setOutput(new ShellOutput( + $this->getOutputVerbosity(), + null, + null, + $this->getPager() ?: null + )); + + // This is racy because `getOutputDecorated` needs access to the + // output stream to figure out if it's piped or not, so create it + // first, then update after we have a stream. + $decorated = $this->getOutputDecorated(); + if ($decorated !== null) { + $this->output->setDecorated($decorated); + } + } + + return $this->output; + } + + /** + * Get the decoration (i.e. color) setting for the Shell Output service. + * + * @return bool|null 3-state boolean corresponding to the current color mode + */ + public function getOutputDecorated() + { + switch ($this->colorMode()) { + case self::COLOR_MODE_AUTO: + return $this->outputIsPiped() ? false : null; + case self::COLOR_MODE_FORCED: + return true; + case self::COLOR_MODE_DISABLED: + return false; + } + } + + /** + * Get the interactive setting for shell input. + * + * @return bool + */ + public function getInputInteractive(): bool + { + switch ($this->interactiveMode()) { + case self::INTERACTIVE_MODE_AUTO: + return !$this->inputIsPiped(); + case self::INTERACTIVE_MODE_FORCED: + return true; + case self::INTERACTIVE_MODE_DISABLED: + return false; + } + } + + /** + * Set the OutputPager service. + * + * If a string is supplied, a ProcOutputPager will be used which shells out + * to the specified command. + * + * `cat` is special-cased to use the PassthruPager directly. + * + * @throws \InvalidArgumentException if $pager is not a string or OutputPager instance + * + * @param string|OutputPager|false $pager + */ + public function setPager($pager) + { + if ($pager === null || $pager === false || $pager === 'cat') { + $pager = false; + } + + if ($pager !== false && !\is_string($pager) && !$pager instanceof OutputPager) { + throw new \InvalidArgumentException('Unexpected pager instance'); + } + + $this->pager = $pager; + } + + /** + * Get an OutputPager instance or a command for an external Proc pager. + * + * If no Pager has been explicitly provided, and Pcntl is available, this + * will default to `cli.pager` ini value, falling back to `which less`. + * + * @return string|OutputPager|false + */ + public function getPager() + { + if (!isset($this->pager) && $this->usePcntl()) { + if (\getenv('TERM') === 'dumb') { + return false; + } + + if ($pager = \ini_get('cli.pager')) { + // use the default pager + $this->pager = $pager; + } elseif ($less = \exec('which less 2>/dev/null')) { + // check for the presence of less... + $this->pager = $less.' -R -F -X'; + } + } + + return $this->pager; + } + + /** + * Set the Shell AutoCompleter service. + * + * @param AutoCompleter $autoCompleter + */ + public function setAutoCompleter(AutoCompleter $autoCompleter) + { + $this->autoCompleter = $autoCompleter; + } + + /** + * Get an AutoCompleter service instance. + * + * @return AutoCompleter + */ + public function getAutoCompleter(): AutoCompleter + { + if (!isset($this->autoCompleter)) { + $this->autoCompleter = new AutoCompleter(); + } + + return $this->autoCompleter; + } + + /** + * @deprecated Nothing should be using this anymore + * + * @return array + */ + public function getTabCompletionMatchers(): array + { + return []; + } + + /** + * Add tab completion matchers to the AutoCompleter. + * + * This will buffer new matchers in the event that the Shell has not yet + * been instantiated. This allows the user to specify matchers in their + * config rc file, despite the fact that their file is needed in the Shell + * constructor. + * + * @param array $matchers + */ + public function addMatchers(array $matchers) + { + $this->newMatchers = \array_merge($this->newMatchers, $matchers); + if (isset($this->shell)) { + $this->doAddMatchers(); + } + } + + /** + * Internal method for adding tab completion matchers. This will set any new + * matchers once a Shell is available. + */ + private function doAddMatchers() + { + if (!empty($this->newMatchers)) { + $this->shell->addMatchers($this->newMatchers); + $this->newMatchers = []; + } + } + + /** + * @deprecated Use `addMatchers` instead + * + * @param array $matchers + */ + public function addTabCompletionMatchers(array $matchers) + { + $this->addMatchers($matchers); + } + + /** + * Add commands to the Shell. + * + * This will buffer new commands in the event that the Shell has not yet + * been instantiated. This allows the user to specify commands in their + * config rc file, despite the fact that their file is needed in the Shell + * constructor. + * + * @param array $commands + */ + public function addCommands(array $commands) + { + $this->newCommands = \array_merge($this->newCommands, $commands); + if (isset($this->shell)) { + $this->doAddCommands(); + } + } + + /** + * Internal method for adding commands. This will set any new commands once + * a Shell is available. + */ + private function doAddCommands() + { + if (!empty($this->newCommands)) { + $this->shell->addCommands($this->newCommands); + $this->newCommands = []; + } + } + + /** + * Set the Shell backreference and add any new commands to the Shell. + * + * @param Shell $shell + */ + public function setShell(Shell $shell) + { + $this->shell = $shell; + $this->doAddCommands(); + $this->doAddMatchers(); + } + + /** + * Set the PHP manual database file. + * + * This file should be an SQLite database generated from the phpdoc source + * with the `bin/build_manual` script. + * + * @param string $filename + */ + public function setManualDbFile(string $filename) + { + $this->manualDbFile = (string) $filename; + } + + /** + * Get the current PHP manual database file. + * + * @return string|null Default: '~/.local/share/psysh/php_manual.sqlite' + */ + public function getManualDbFile() + { + if (isset($this->manualDbFile)) { + return $this->manualDbFile; + } + + $files = $this->configPaths->dataFiles(['php_manual.sqlite']); + if (!empty($files)) { + if ($this->warnOnMultipleConfigs && \count($files) > 1) { + $msg = \sprintf('Multiple manual database files found: %s. Using %s', \implode(', ', $files), $files[0]); + \trigger_error($msg, \E_USER_NOTICE); + } + + return $this->manualDbFile = $files[0]; + } + } + + /** + * Get a PHP manual database connection. + * + * @return \PDO|null + */ + public function getManualDb() + { + if (!isset($this->manualDb)) { + $dbFile = $this->getManualDbFile(); + if ($dbFile !== null && \is_file($dbFile)) { + try { + $this->manualDb = new \PDO('sqlite:'.$dbFile); + } catch (\PDOException $e) { + if ($e->getMessage() === 'could not find driver') { + throw new RuntimeException('SQLite PDO driver not found', 0, $e); + } else { + throw $e; + } + } + } + } + + return $this->manualDb; + } + + /** + * Add an array of casters definitions. + * + * @param array $casters + */ + public function addCasters(array $casters) + { + $this->getPresenter()->addCasters($casters); + } + + /** + * Get the Presenter service. + * + * @return Presenter + */ + public function getPresenter(): Presenter + { + if (!isset($this->presenter)) { + $this->presenter = new Presenter($this->getOutput()->getFormatter(), $this->forceArrayIndexes()); + } + + return $this->presenter; + } + + /** + * Enable or disable warnings on multiple configuration or data files. + * + * @see self::warnOnMultipleConfigs() + * + * @param bool $warnOnMultipleConfigs + */ + public function setWarnOnMultipleConfigs(bool $warnOnMultipleConfigs) + { + $this->warnOnMultipleConfigs = (bool) $warnOnMultipleConfigs; + } + + /** + * Check whether to warn on multiple configuration or data files. + * + * By default, PsySH will use the file with highest precedence, and will + * silently ignore all others. With this enabled, a warning will be emitted + * (but not an exception thrown) if multiple configuration or data files + * are found. + * + * This will default to true in a future release, but is false for now. + * + * @return bool + */ + public function warnOnMultipleConfigs(): bool + { + return $this->warnOnMultipleConfigs; + } + + /** + * Set the current color mode. + * + * @param string $colorMode + */ + public function setColorMode(string $colorMode) + { + $validColorModes = [ + self::COLOR_MODE_AUTO, + self::COLOR_MODE_FORCED, + self::COLOR_MODE_DISABLED, + ]; + + if (!\in_array($colorMode, $validColorModes)) { + throw new \InvalidArgumentException('Invalid color mode: '.$colorMode); + } + + $this->colorMode = $colorMode; + } + + /** + * Get the current color mode. + * + * @return string + */ + public function colorMode(): string + { + return $this->colorMode; + } + + /** + * Set the shell's interactive mode. + * + * @throws \InvalidArgumentException if interactive mode isn't disabled, forced, or auto + * + * @param string $interactiveMode + */ + public function setInteractiveMode(string $interactiveMode) + { + $validInteractiveModes = [ + self::INTERACTIVE_MODE_AUTO, + self::INTERACTIVE_MODE_FORCED, + self::INTERACTIVE_MODE_DISABLED, + ]; + + if (!\in_array($interactiveMode, $validInteractiveModes)) { + throw new \InvalidArgumentException('Invalid interactive mode: '.$interactiveMode); + } + + $this->interactiveMode = $interactiveMode; + } + + /** + * Get the current interactive mode. + * + * @return string + */ + public function interactiveMode(): string + { + return $this->interactiveMode; + } + + /** + * Set an update checker service instance. + * + * @param Checker $checker + */ + public function setChecker(Checker $checker) + { + $this->checker = $checker; + } + + /** + * Get an update checker service instance. + * + * If none has been explicitly defined, this will create a new instance. + * + * @return Checker + */ + public function getChecker(): Checker + { + if (!isset($this->checker)) { + $interval = $this->getUpdateCheck(); + switch ($interval) { + case Checker::ALWAYS: + $this->checker = new GitHubChecker(); + break; + + case Checker::DAILY: + case Checker::WEEKLY: + case Checker::MONTHLY: + $checkFile = $this->getUpdateCheckCacheFile(); + if ($checkFile === false) { + $this->checker = new NoopChecker(); + } else { + $this->checker = new IntervalChecker($checkFile, $interval); + } + break; + + case Checker::NEVER: + $this->checker = new NoopChecker(); + break; + } + } + + return $this->checker; + } + + /** + * Get the current update check interval. + * + * One of 'always', 'daily', 'weekly', 'monthly' or 'never'. If none is + * explicitly set, default to 'weekly'. + * + * @return string + */ + public function getUpdateCheck(): string + { + return isset($this->updateCheck) ? $this->updateCheck : Checker::WEEKLY; + } + + /** + * Set the update check interval. + * + * @throws \InvalidArgumentException if the update check interval is unknown + * + * @param string $interval + */ + public function setUpdateCheck(string $interval) + { + $validIntervals = [ + Checker::ALWAYS, + Checker::DAILY, + Checker::WEEKLY, + Checker::MONTHLY, + Checker::NEVER, + ]; + + if (!\in_array($interval, $validIntervals)) { + throw new \InvalidArgumentException('Invalid update check interval: '.$interval); + } + + $this->updateCheck = $interval; + } + + /** + * Get a cache file path for the update checker. + * + * @return string|false Return false if config file/directory is not writable + */ + public function getUpdateCheckCacheFile() + { + return ConfigPaths::touchFileWithMkdir($this->configPaths->currentConfigDir().'/update_check.json'); + } + + /** + * Set the startup message. + * + * @param string $message + */ + public function setStartupMessage(string $message) + { + $this->startupMessage = $message; + } + + /** + * Get the startup message. + * + * @return string|null + */ + public function getStartupMessage() + { + return $this->startupMessage; + } + + /** + * Set the prompt. + * + * @param string $prompt + */ + public function setPrompt(string $prompt) + { + $this->prompt = $prompt; + } + + /** + * Get the prompt. + * + * @return string|null + */ + public function getPrompt() + { + return $this->prompt; + } + + /** + * Get the force array indexes. + * + * @return bool + */ + public function forceArrayIndexes(): bool + { + return $this->forceArrayIndexes; + } + + /** + * Set the force array indexes. + * + * @param bool $forceArrayIndexes + */ + public function setForceArrayIndexes(bool $forceArrayIndexes) + { + $this->forceArrayIndexes = $forceArrayIndexes; + } + + /** + * Set the shell output formatter styles. + * + * Accepts a map from style name to [fg, bg, options], for example: + * + * [ + * 'error' => ['white', 'red', ['bold']], + * 'warning' => ['black', 'yellow'], + * ] + * + * Foreground, background or options can be null, or even omitted entirely. + * + * @see ShellOutput::initFormatters + * + * @param array $formatterStyles + */ + public function setFormatterStyles(array $formatterStyles) + { + foreach ($formatterStyles as $name => $style) { + list($fg, $bg, $opts) = \array_pad($style, 3, null); + $this->formatterStyles[$name] = new OutputFormatterStyle($fg ?: null, $bg ?: null, $opts ?: []); + } + + if (isset($this->output)) { + $this->applyFormatterStyles(); + } + } + + /** + * Internal method for applying output formatter style customization. + * + * This is called on initialization of the shell output, and again if the + * formatter styles config is updated. + */ + private function applyFormatterStyles() + { + $formatter = $this->output->getFormatter(); + foreach ($this->formatterStyles as $name => $style) { + $formatter->setStyle($name, $style); + } + } + + /** + * Get the configured output verbosity. + * + * @return string + */ + public function verbosity(): string + { + return $this->verbosity; + } + + /** + * Set the shell output verbosity. + * + * Accepts OutputInterface verbosity constants. + * + * @throws \InvalidArgumentException if verbosity level is invalid + * + * @param string $verbosity + */ + public function setVerbosity(string $verbosity) + { + $validVerbosityLevels = [ + self::VERBOSITY_QUIET, + self::VERBOSITY_NORMAL, + self::VERBOSITY_VERBOSE, + self::VERBOSITY_VERY_VERBOSE, + self::VERBOSITY_DEBUG, + ]; + + if (!\in_array($verbosity, $validVerbosityLevels)) { + throw new \InvalidArgumentException('Invalid verbosity level: '.$verbosity); + } + + $this->verbosity = $verbosity; + + if (isset($this->output)) { + $this->output->setVerbosity($this->getOutputVerbosity()); + } + } + + /** + * Map the verbosity configuration to OutputInterface verbosity constants. + * + * @return int OutputInterface verbosity level + */ + public function getOutputVerbosity(): int + { + switch ($this->verbosity()) { + case self::VERBOSITY_QUIET: + return OutputInterface::VERBOSITY_QUIET; + case self::VERBOSITY_VERBOSE: + return OutputInterface::VERBOSITY_VERBOSE; + case self::VERBOSITY_VERY_VERBOSE: + return OutputInterface::VERBOSITY_VERY_VERBOSE; + case self::VERBOSITY_DEBUG: + return OutputInterface::VERBOSITY_DEBUG; + case self::VERBOSITY_NORMAL: + default: + return OutputInterface::VERBOSITY_NORMAL; + } + } + + /** + * Guess whether stdin is piped. + * + * This is mostly useful for deciding whether to use non-interactive mode. + * + * @return bool + */ + public function inputIsPiped(): bool + { + if ($this->pipedInput === null) { + $this->pipedInput = \defined('STDIN') && static::looksLikeAPipe(\STDIN); + } + + return $this->pipedInput; + } + + /** + * Guess whether shell output is piped. + * + * This is mostly useful for deciding whether to use non-decorated output. + * + * @return bool + */ + public function outputIsPiped(): bool + { + if ($this->pipedOutput === null) { + $this->pipedOutput = static::looksLikeAPipe($this->getOutput()->getStream()); + } + + return $this->pipedOutput; + } + + /** + * Guess whether an input or output stream is piped. + * + * @param resource|int $stream + * + * @return bool + */ + private static function looksLikeAPipe($stream): bool + { + if (\function_exists('posix_isatty')) { + return !\posix_isatty($stream); + } + + $stat = \fstat($stream); + $mode = $stat['mode'] & 0170000; + + return $mode === 0010000 || $mode === 0040000 || $mode === 0100000 || $mode === 0120000; + } +} diff --git a/vendor/psy/psysh/src/ConsoleColorFactory.php b/vendor/psy/psysh/src/ConsoleColorFactory.php new file mode 100644 index 0000000000..c589863bcb --- /dev/null +++ b/vendor/psy/psysh/src/ConsoleColorFactory.php @@ -0,0 +1,39 @@ +returnValue; + + case '_e': + if (isset($this->lastException)) { + return $this->lastException; + } + break; + + case '__out': + if (isset($this->lastStdout)) { + return $this->lastStdout; + } + break; + + case 'this': + if (isset($this->boundObject)) { + return $this->boundObject; + } + break; + + case '__function': + case '__method': + case '__class': + case '__namespace': + case '__file': + case '__line': + case '__dir': + if (\array_key_exists($name, $this->commandScopeVariables)) { + return $this->commandScopeVariables[$name]; + } + break; + + default: + if (\array_key_exists($name, $this->scopeVariables)) { + return $this->scopeVariables[$name]; + } + break; + } + + throw new \InvalidArgumentException('Unknown variable: $'.$name); + } + + /** + * Get all defined variables. + * + * @return array + */ + public function getAll(): array + { + return \array_merge($this->scopeVariables, $this->getSpecialVariables()); + } + + /** + * Get all defined magic variables: $_, $_e, $__out, $__class, $__file, etc. + * + * @return array + */ + public function getSpecialVariables(): array + { + $vars = [ + '_' => $this->returnValue, + ]; + + if (isset($this->lastException)) { + $vars['_e'] = $this->lastException; + } + + if (isset($this->lastStdout)) { + $vars['__out'] = $this->lastStdout; + } + + if (isset($this->boundObject)) { + $vars['this'] = $this->boundObject; + } + + return \array_merge($vars, $this->commandScopeVariables); + } + + /** + * Set all scope variables. + * + * This method does *not* set any of the magic variables: $_, $_e, $__out, + * $__class, $__file, etc. + * + * @param array $vars + */ + public function setAll(array $vars) + { + foreach (self::$specialNames as $key) { + unset($vars[$key]); + } + + foreach (self::$commandScopeNames as $key) { + unset($vars[$key]); + } + + $this->scopeVariables = $vars; + } + + /** + * Set the most recent return value. + * + * @param mixed $value + */ + public function setReturnValue($value) + { + $this->returnValue = $value; + } + + /** + * Get the most recent return value. + * + * @return mixed + */ + public function getReturnValue() + { + return $this->returnValue; + } + + /** + * Set the most recent Exception. + * + * @param \Exception $e + */ + public function setLastException(\Exception $e) + { + $this->lastException = $e; + } + + /** + * Get the most recent Exception. + * + * @throws \InvalidArgumentException If no Exception has been caught + * + * @return \Exception|null + */ + public function getLastException() + { + if (!isset($this->lastException)) { + throw new \InvalidArgumentException('No most-recent exception'); + } + + return $this->lastException; + } + + /** + * Set the most recent output from evaluated code. + * + * @param string $lastStdout + */ + public function setLastStdout(string $lastStdout) + { + $this->lastStdout = $lastStdout; + } + + /** + * Get the most recent output from evaluated code. + * + * @throws \InvalidArgumentException If no output has happened yet + * + * @return string|null + */ + public function getLastStdout() + { + if (!isset($this->lastStdout)) { + throw new \InvalidArgumentException('No most-recent output'); + } + + return $this->lastStdout; + } + + /** + * Set the bound object ($this variable) for the interactive shell. + * + * Note that this unsets the bound class, if any exists. + * + * @param object|null $boundObject + */ + public function setBoundObject($boundObject) + { + $this->boundObject = \is_object($boundObject) ? $boundObject : null; + $this->boundClass = null; + } + + /** + * Get the bound object ($this variable) for the interactive shell. + * + * @return object|null + */ + public function getBoundObject() + { + return $this->boundObject; + } + + /** + * Set the bound class (self) for the interactive shell. + * + * Note that this unsets the bound object, if any exists. + * + * @param string|null $boundClass + */ + public function setBoundClass($boundClass) + { + $this->boundClass = (\is_string($boundClass) && $boundClass !== '') ? $boundClass : null; + $this->boundObject = null; + } + + /** + * Get the bound class (self) for the interactive shell. + * + * @return string|null + */ + public function getBoundClass() + { + return $this->boundClass; + } + + /** + * Set command-scope magic variables: $__class, $__file, etc. + * + * @param array $commandScopeVariables + */ + public function setCommandScopeVariables(array $commandScopeVariables) + { + $vars = []; + foreach ($commandScopeVariables as $key => $value) { + // kind of type check + if (\is_scalar($value) && \in_array($key, self::$commandScopeNames)) { + $vars[$key] = $value; + } + } + + $this->commandScopeVariables = $vars; + } + + /** + * Get command-scope magic variables: $__class, $__file, etc. + * + * @return array + */ + public function getCommandScopeVariables(): array + { + return $this->commandScopeVariables; + } + + /** + * Get unused command-scope magic variables names: __class, __file, etc. + * + * This is used by the shell to unset old command-scope variables after a + * new batch is set. + * + * @return array Array of unused variable names + */ + public function getUnusedCommandScopeVariableNames(): array + { + return \array_diff(self::$commandScopeNames, \array_keys($this->commandScopeVariables)); + } + + /** + * Check whether a variable name is a magic variable. + * + * @param string $name + * + * @return bool + */ + public static function isSpecialVariableName(string $name): bool + { + return \in_array($name, self::$specialNames) || \in_array($name, self::$commandScopeNames); + } +} diff --git a/vendor/psy/psysh/src/ContextAware.php b/vendor/psy/psysh/src/ContextAware.php new file mode 100644 index 0000000000..5f6d7df35f --- /dev/null +++ b/vendor/psy/psysh/src/ContextAware.php @@ -0,0 +1,28 @@ +rawMessage = $message; + parent::__construct(\sprintf('Exit: %s', $message), $code, $previous); + } + + /** + * Return a raw (unformatted) version of the error message. + * + * @return string + */ + public function getRawMessage(): string + { + return $this->rawMessage; + } + + /** + * Throws BreakException. + * + * Since `throw` can not be inserted into arbitrary expressions, it wraps with function call. + * + * @throws BreakException + */ + public static function exitShell() + { + throw new self('Goodbye'); + } +} diff --git a/vendor/psy/psysh/src/Exception/DeprecatedException.php b/vendor/psy/psysh/src/Exception/DeprecatedException.php new file mode 100644 index 0000000000..a0397eafb1 --- /dev/null +++ b/vendor/psy/psysh/src/Exception/DeprecatedException.php @@ -0,0 +1,20 @@ +rawMessage = $message; + + if (!empty($filename) && \preg_match('{Psy[/\\\\]ExecutionLoop}', $filename)) { + $filename = ''; + } + + switch ($severity) { + case \E_STRICT: + $type = 'Strict error'; + break; + + case \E_NOTICE: + case \E_USER_NOTICE: + $type = 'Notice'; + break; + + case \E_WARNING: + case \E_CORE_WARNING: + case \E_COMPILE_WARNING: + case \E_USER_WARNING: + $type = 'Warning'; + break; + + case \E_DEPRECATED: + case \E_USER_DEPRECATED: + $type = 'Deprecated'; + break; + + case \E_RECOVERABLE_ERROR: + $type = 'Recoverable fatal error'; + break; + + default: + $type = 'Error'; + break; + } + + $message = \sprintf('PHP %s: %s%s on line %d', $type, $message, $filename ? ' in '.$filename : '', $lineno); + parent::__construct($message, $code, $severity, $filename, $lineno, $previous); + } + + /** + * Get the raw (unformatted) message for this error. + * + * @return string + */ + public function getRawMessage(): string + { + return $this->rawMessage; + } + + /** + * Helper for throwing an ErrorException. + * + * This allows us to: + * + * set_error_handler([ErrorException::class, 'throwException']); + * + * @throws self + * + * @param int $errno Error type + * @param string $errstr Message + * @param string $errfile Filename + * @param int $errline Line number + */ + public static function throwException($errno, $errstr, $errfile, $errline) + { + throw new self($errstr, 0, $errno, $errfile, $errline); + } + + /** + * Create an ErrorException from an Error. + * + * @param \Error $e + * + * @return self + */ + public static function fromError(\Error $e): self + { + return new self($e->getMessage(), $e->getCode(), 1, $e->getFile(), $e->getLine(), $e); + } +} diff --git a/vendor/psy/psysh/src/Exception/Exception.php b/vendor/psy/psysh/src/Exception/Exception.php new file mode 100644 index 0000000000..0f2e0d972b --- /dev/null +++ b/vendor/psy/psysh/src/Exception/Exception.php @@ -0,0 +1,27 @@ +rawMessage = $message; + $message = \sprintf('PHP Fatal error: %s in %s on line %d', $message, $filename ?: "eval()'d code", $lineno); + parent::__construct($message, $code, $severity, $filename, $lineno, $previous); + } + + /** + * Return a raw (unformatted) version of the error message. + * + * @return string + */ + public function getRawMessage(): string + { + return $this->rawMessage; + } +} diff --git a/vendor/psy/psysh/src/Exception/ParseErrorException.php b/vendor/psy/psysh/src/Exception/ParseErrorException.php new file mode 100644 index 0000000000..ff4695ce86 --- /dev/null +++ b/vendor/psy/psysh/src/Exception/ParseErrorException.php @@ -0,0 +1,42 @@ +getRawMessage(), $e->getStartLine()); + } +} diff --git a/vendor/psy/psysh/src/Exception/RuntimeException.php b/vendor/psy/psysh/src/Exception/RuntimeException.php new file mode 100644 index 0000000000..99e131b984 --- /dev/null +++ b/vendor/psy/psysh/src/Exception/RuntimeException.php @@ -0,0 +1,43 @@ +rawMessage = $message; + parent::__construct($message, $code, $previous); + } + + /** + * Return a raw (unformatted) version of the error message. + * + * @return string + */ + public function getRawMessage(): string + { + return $this->rawMessage; + } +} diff --git a/vendor/psy/psysh/src/Exception/ThrowUpException.php b/vendor/psy/psysh/src/Exception/ThrowUpException.php new file mode 100644 index 0000000000..a2b3fc4599 --- /dev/null +++ b/vendor/psy/psysh/src/Exception/ThrowUpException.php @@ -0,0 +1,57 @@ +getMessage()); + parent::__construct($message, $exception->getCode(), $exception); + } + + /** + * Return a raw (unformatted) version of the error message. + * + * @return string + */ + public function getRawMessage(): string + { + return $this->getPrevious()->getMessage(); + } + + /** + * Create a ThrowUpException from a Throwable. + * + * @param \Throwable $throwable + * + * @return self + */ + public static function fromThrowable($throwable): self + { + if ($throwable instanceof \Error) { + $throwable = ErrorException::fromError($throwable); + } + + if (!$throwable instanceof \Exception) { + throw new \InvalidArgumentException('throw-up can only throw Exceptions and Errors'); + } + + return new self($throwable); + } +} diff --git a/vendor/psy/psysh/src/Exception/TypeErrorException.php b/vendor/psy/psysh/src/Exception/TypeErrorException.php new file mode 100644 index 0000000000..b1c828b47f --- /dev/null +++ b/vendor/psy/psysh/src/Exception/TypeErrorException.php @@ -0,0 +1,55 @@ +rawMessage = $message; + $message = \preg_replace('/, called in .*?: eval\\(\\)\'d code/', '', $message); + parent::__construct(\sprintf('TypeError: %s', $message), $code); + } + + /** + * Get the raw (unformatted) message for this error. + * + * @return string + */ + public function getRawMessage(): string + { + return $this->rawMessage; + } + + /** + * Create a TypeErrorException from a TypeError. + * + * @param \TypeError $e + * + * @return self + */ + public static function fromTypeError(\TypeError $e): self + { + return new self($e->getMessage(), $e->getCode()); + } +} diff --git a/vendor/psy/psysh/src/Exception/UnexpectedTargetException.php b/vendor/psy/psysh/src/Exception/UnexpectedTargetException.php new file mode 100644 index 0000000000..ef6eec7867 --- /dev/null +++ b/vendor/psy/psysh/src/Exception/UnexpectedTargetException.php @@ -0,0 +1,37 @@ +target = $target; + parent::__construct($message, $code, $previous); + } + + /** + * @return mixed + */ + public function getTarget() + { + return $this->target; + } +} diff --git a/vendor/psy/psysh/src/ExecutionClosure.php b/vendor/psy/psysh/src/ExecutionClosure.php new file mode 100644 index 0000000000..1418d3df4c --- /dev/null +++ b/vendor/psy/psysh/src/ExecutionClosure.php @@ -0,0 +1,91 @@ +setClosure($__psysh__, function () use ($__psysh__) { + try { + // Restore execution scope variables + \extract($__psysh__->getScopeVariables(false)); + + // Buffer stdout; we'll need it later + \ob_start([$__psysh__, 'writeStdout'], 1); + + // Convert all errors to exceptions + \set_error_handler([$__psysh__, 'handleError']); + + // Evaluate the current code buffer + $_ = eval($__psysh__->onExecute($__psysh__->flushCode() ?: self::NOOP_INPUT)); + } catch (\Throwable $_e) { + // Clean up on our way out. + if (\ob_get_level() > 0) { + \ob_end_clean(); + } + + throw $_e; + } finally { + // Won't be needing this anymore + \restore_error_handler(); + } + + // Flush stdout (write to shell output, plus save to magic variable) + \ob_end_flush(); + + // Save execution scope variables for next time + $__psysh__->setScopeVariables(\get_defined_vars()); + + return $_; + }); + } + + /** + * Set the closure instance. + * + * @param Shell $shell + * @param \Closure $closure + */ + protected function setClosure(Shell $shell, \Closure $closure) + { + $that = $shell->getBoundObject(); + + if (\is_object($that)) { + $this->closure = $closure->bindTo($that, \get_class($that)); + } else { + $this->closure = $closure->bindTo(null, $shell->getBoundClass()); + } + } + + /** + * Go go gadget closure. + * + * @return mixed + */ + public function execute() + { + $closure = $this->closure; + + return $closure(); + } +} diff --git a/vendor/psy/psysh/src/ExecutionLoop/AbstractListener.php b/vendor/psy/psysh/src/ExecutionLoop/AbstractListener.php new file mode 100644 index 0000000000..cd6e0024fd --- /dev/null +++ b/vendor/psy/psysh/src/ExecutionLoop/AbstractListener.php @@ -0,0 +1,62 @@ + 0) { + // This is the main thread. We'll just wait for a while. + + // We won't be needing this one. + \fclose($up); + + // Wait for a return value from the loop process. + $read = [$down]; + $write = null; + $except = null; + + do { + $n = @\stream_select($read, $write, $except, null); + + if ($n === 0) { + throw new \RuntimeException('Process timed out waiting for execution loop'); + } + + if ($n === false) { + $err = \error_get_last(); + if (!isset($err['message']) || \stripos($err['message'], 'interrupted system call') === false) { + $msg = $err['message'] ? + \sprintf('Error waiting for execution loop: %s', $err['message']) : + 'Error waiting for execution loop'; + throw new \RuntimeException($msg); + } + } + } while ($n < 1); + + $content = \stream_get_contents($down); + \fclose($down); + + if ($content) { + $shell->setScopeVariables(@\unserialize($content)); + } + + throw new BreakException('Exiting main thread'); + } + + // This is the child process. It's going to do all the work. + if (!@\cli_set_process_title('psysh (loop)')) { + // Fall back to `setproctitle` if that wasn't succesful. + if (\function_exists('setproctitle')) { + @\setproctitle('psysh (loop)'); + } + } + + // We won't be needing this one. + \fclose($down); + + // Save this; we'll need to close it in `afterRun` + $this->up = $up; + } + + /** + * Create a savegame at the start of each loop iteration. + * + * @param Shell $shell + */ + public function beforeLoop(Shell $shell) + { + $this->createSavegame(); + } + + /** + * Clean up old savegames at the end of each loop iteration. + * + * @param Shell $shell + */ + public function afterLoop(Shell $shell) + { + // if there's an old savegame hanging around, let's kill it. + if (isset($this->savegame)) { + \posix_kill($this->savegame, \SIGKILL); + \pcntl_signal_dispatch(); + } + } + + /** + * After the REPL session ends, send the scope variables back up to the main + * thread (if this is a child thread). + * + * @param Shell $shell + */ + public function afterRun(Shell $shell) + { + // We're a child thread. Send the scope variables back up to the main thread. + if (isset($this->up)) { + \fwrite($this->up, $this->serializeReturn($shell->getScopeVariables(false))); + \fclose($this->up); + + \posix_kill(\posix_getpid(), \SIGKILL); + } + } + + /** + * Create a savegame fork. + * + * The savegame contains the current execution state, and can be resumed in + * the event that the worker dies unexpectedly (for example, by encountering + * a PHP fatal error). + */ + private function createSavegame() + { + // the current process will become the savegame + $this->savegame = \posix_getpid(); + + $pid = \pcntl_fork(); + if ($pid < 0) { + throw new \RuntimeException('Unable to create savegame fork'); + } elseif ($pid > 0) { + // we're the savegame now... let's wait and see what happens + \pcntl_waitpid($pid, $status); + + // worker exited cleanly, let's bail + if (!\pcntl_wexitstatus($status)) { + \posix_kill(\posix_getpid(), \SIGKILL); + } + + // worker didn't exit cleanly, we'll need to have another go + $this->createSavegame(); + } + } + + /** + * Serialize all serializable return values. + * + * A naïve serialization will run into issues if there is a Closure or + * SimpleXMLElement (among other things) in scope when exiting the execution + * loop. We'll just ignore these unserializable classes, and serialize what + * we can. + * + * @param array $return + * + * @return string + */ + private function serializeReturn(array $return): string + { + $serializable = []; + + foreach ($return as $key => $value) { + // No need to return magic variables + if (Context::isSpecialVariableName($key)) { + continue; + } + + // Resources and Closures don't error, but they don't serialize well either. + if (\is_resource($value) || $value instanceof \Closure) { + continue; + } + + if (\version_compare(\PHP_VERSION, '8.1', '>=') && $value instanceof \UnitEnum) { + // Enums defined in the REPL session can't be unserialized. + $ref = new \ReflectionObject($value); + if (\strpos($ref->getFileName(), ": eval()'d code") !== false) { + continue; + } + } + + try { + @\serialize($value); + $serializable[$key] = $value; + } catch (\Throwable $e) { + // we'll just ignore this one... + } + } + + return @\serialize($serializable); + } +} diff --git a/vendor/psy/psysh/src/ExecutionLoop/RunkitReloader.php b/vendor/psy/psysh/src/ExecutionLoop/RunkitReloader.php new file mode 100644 index 0000000000..0854ba76f2 --- /dev/null +++ b/vendor/psy/psysh/src/ExecutionLoop/RunkitReloader.php @@ -0,0 +1,144 @@ +parser = $parserFactory->createParser(); + } + + /** + * Reload code on input. + * + * @param Shell $shell + * @param string $input + */ + public function onInput(Shell $shell, string $input) + { + $this->reload($shell); + } + + /** + * Look through included files and update anything with a new timestamp. + * + * @param Shell $shell + */ + private function reload(Shell $shell) + { + \clearstatcache(); + $modified = []; + + foreach (\get_included_files() as $file) { + $timestamp = \filemtime($file); + + if (!isset($this->timestamps[$file])) { + $this->timestamps[$file] = $timestamp; + continue; + } + + if ($this->timestamps[$file] === $timestamp) { + continue; + } + + if (!$this->lintFile($file)) { + $msg = \sprintf('Modified file "%s" could not be reloaded', $file); + $shell->writeException(new ParseErrorException($msg)); + continue; + } + + $modified[] = $file; + $this->timestamps[$file] = $timestamp; + } + + // switch (count($modified)) { + // case 0: + // return; + + // case 1: + // printf("Reloading modified file: \"%s\"\n", str_replace(getcwd(), '.', $file)); + // break; + + // default: + // printf("Reloading %d modified files\n", count($modified)); + // break; + // } + + foreach ($modified as $file) { + $flags = ( + RUNKIT_IMPORT_FUNCTIONS | + RUNKIT_IMPORT_CLASSES | + RUNKIT_IMPORT_CLASS_METHODS | + RUNKIT_IMPORT_CLASS_CONSTS | + RUNKIT_IMPORT_CLASS_PROPS | + RUNKIT_IMPORT_OVERRIDE + ); + + // these two const cannot be used with RUNKIT_IMPORT_OVERRIDE in runkit7 + if (\extension_loaded('runkit7')) { + $flags &= ~RUNKIT_IMPORT_CLASS_PROPS & ~RUNKIT_IMPORT_CLASS_STATIC_PROPS; + runkit7_import($file, $flags); + } else { + runkit_import($file, $flags); + } + } + } + + /** + * Should this file be re-imported? + * + * Use PHP-Parser to ensure that the file is valid PHP. + * + * @param string $file + * + * @return bool + */ + private function lintFile(string $file): bool + { + // first try to parse it + try { + $this->parser->parse(\file_get_contents($file)); + } catch (\Throwable $e) { + return false; + } + + return true; + } +} diff --git a/vendor/psy/psysh/src/ExecutionLoopClosure.php b/vendor/psy/psysh/src/ExecutionLoopClosure.php new file mode 100644 index 0000000000..2f320bfea6 --- /dev/null +++ b/vendor/psy/psysh/src/ExecutionLoopClosure.php @@ -0,0 +1,95 @@ +setClosure($__psysh__, function () use ($__psysh__) { + // Restore execution scope variables + \extract($__psysh__->getScopeVariables(false)); + + while (true) { + $__psysh__->beforeLoop(); + + try { + $__psysh__->getInput(); + + try { + // Pull in any new execution scope variables + if ($__psysh__->getLastExecSuccess()) { + \extract($__psysh__->getScopeVariablesDiff(\get_defined_vars())); + } + + // Buffer stdout; we'll need it later + \ob_start([$__psysh__, 'writeStdout'], 1); + + // Convert all errors to exceptions + \set_error_handler([$__psysh__, 'handleError']); + + // Evaluate the current code buffer + $_ = eval($__psysh__->onExecute($__psysh__->flushCode() ?: ExecutionClosure::NOOP_INPUT)); + } catch (\Throwable $_e) { + // Clean up on our way out. + if (\ob_get_level() > 0) { + \ob_end_clean(); + } + + throw $_e; + } finally { + // Won't be needing this anymore + \restore_error_handler(); + } + + // Flush stdout (write to shell output, plus save to magic variable) + \ob_end_flush(); + + // Save execution scope variables for next time + $__psysh__->setScopeVariables(\get_defined_vars()); + + $__psysh__->writeReturnValue($_); + } catch (BreakException $_e) { + $__psysh__->writeException($_e); + + return; + } catch (ThrowUpException $_e) { + $__psysh__->writeException($_e); + + throw $_e; + } catch (\TypeError $_e) { + $__psysh__->writeException(TypeErrorException::fromTypeError($_e)); + } catch (\Error $_e) { + $__psysh__->writeException(ErrorException::fromError($_e)); + } catch (\Exception $_e) { + $__psysh__->writeException($_e); + } + + $__psysh__->afterLoop(); + } + }); + } +} diff --git a/vendor/psy/psysh/src/Formatter/CodeFormatter.php b/vendor/psy/psysh/src/Formatter/CodeFormatter.php new file mode 100644 index 0000000000..3d1f611ff2 --- /dev/null +++ b/vendor/psy/psysh/src/Formatter/CodeFormatter.php @@ -0,0 +1,320 @@ +> '; + const NO_LINE_MARKER = ' '; + + const HIGHLIGHT_DEFAULT = 'default'; + const HIGHLIGHT_KEYWORD = 'keyword'; + + const HIGHLIGHT_PUBLIC = 'public'; + const HIGHLIGHT_PROTECTED = 'protected'; + const HIGHLIGHT_PRIVATE = 'private'; + + const HIGHLIGHT_CONST = 'const'; + const HIGHLIGHT_NUMBER = 'number'; + const HIGHLIGHT_STRING = 'string'; + const HIGHLIGHT_COMMENT = 'comment'; + const HIGHLIGHT_INLINE_HTML = 'inline_html'; + + private static $tokenMap = [ + // Not highlighted + \T_OPEN_TAG => self::HIGHLIGHT_DEFAULT, + \T_OPEN_TAG_WITH_ECHO => self::HIGHLIGHT_DEFAULT, + \T_CLOSE_TAG => self::HIGHLIGHT_DEFAULT, + \T_STRING => self::HIGHLIGHT_DEFAULT, + \T_VARIABLE => self::HIGHLIGHT_DEFAULT, + \T_NS_SEPARATOR => self::HIGHLIGHT_DEFAULT, + + // Visibility + \T_PUBLIC => self::HIGHLIGHT_PUBLIC, + \T_PROTECTED => self::HIGHLIGHT_PROTECTED, + \T_PRIVATE => self::HIGHLIGHT_PRIVATE, + + // Constants + \T_DIR => self::HIGHLIGHT_CONST, + \T_FILE => self::HIGHLIGHT_CONST, + \T_METHOD_C => self::HIGHLIGHT_CONST, + \T_NS_C => self::HIGHLIGHT_CONST, + \T_LINE => self::HIGHLIGHT_CONST, + \T_CLASS_C => self::HIGHLIGHT_CONST, + \T_FUNC_C => self::HIGHLIGHT_CONST, + \T_TRAIT_C => self::HIGHLIGHT_CONST, + + // Types + \T_DNUMBER => self::HIGHLIGHT_NUMBER, + \T_LNUMBER => self::HIGHLIGHT_NUMBER, + \T_ENCAPSED_AND_WHITESPACE => self::HIGHLIGHT_STRING, + \T_CONSTANT_ENCAPSED_STRING => self::HIGHLIGHT_STRING, + + // Comments + \T_COMMENT => self::HIGHLIGHT_COMMENT, + \T_DOC_COMMENT => self::HIGHLIGHT_COMMENT, + + // @todo something better here? + \T_INLINE_HTML => self::HIGHLIGHT_INLINE_HTML, + ]; + + /** + * Format the code represented by $reflector for shell output. + * + * @param \Reflector $reflector + * @param string|null $colorMode (deprecated and ignored) + * + * @return string formatted code + */ + public static function format(\Reflector $reflector, string $colorMode = null): string + { + if (self::isReflectable($reflector)) { + if ($code = @\file_get_contents($reflector->getFileName())) { + return self::formatCode($code, self::getStartLine($reflector), $reflector->getEndLine()); + } + } + + throw new RuntimeException('Source code unavailable'); + } + + /** + * Format code for shell output. + * + * Optionally, restrict by $startLine and $endLine line numbers, or pass $markLine to add a line marker. + * + * @param string $code + * @param int $startLine + * @param int|null $endLine + * @param int|null $markLine + * + * @return string formatted code + */ + public static function formatCode(string $code, int $startLine = 1, int $endLine = null, int $markLine = null): string + { + $spans = self::tokenizeSpans($code); + $lines = self::splitLines($spans, $startLine, $endLine); + $lines = self::formatLines($lines); + $lines = self::numberLines($lines, $markLine); + + return \implode('', \iterator_to_array($lines)); + } + + /** + * Get the start line for a given Reflector. + * + * Tries to incorporate doc comments if possible. + * + * This is typehinted as \Reflector but we've narrowed the input via self::isReflectable already. + * + * @param \ReflectionClass|\ReflectionFunctionAbstract $reflector + * + * @return int + */ + private static function getStartLine(\Reflector $reflector): int + { + $startLine = $reflector->getStartLine(); + + if ($docComment = $reflector->getDocComment()) { + $startLine -= \preg_match_all('/(\r\n?|\n)/', $docComment) + 1; + } + + return \max($startLine, 1); + } + + /** + * Split code into highlight spans. + * + * Tokenize via \token_get_all, then map these tokens to internal highlight types, combining + * adjacent spans of the same highlight type. + * + * @todo consider switching \token_get_all() out for PHP-Parser-based formatting at some point. + * + * @param string $code + * + * @return \Generator [$spanType, $spanText] highlight spans + */ + private static function tokenizeSpans(string $code): \Generator + { + $spanType = null; + $buffer = ''; + + foreach (\token_get_all($code) as $token) { + $nextType = self::nextHighlightType($token, $spanType); + $spanType = $spanType ?: $nextType; + + if ($spanType !== $nextType) { + yield [$spanType, $buffer]; + $spanType = $nextType; + $buffer = ''; + } + + $buffer .= \is_array($token) ? $token[1] : $token; + } + + if ($spanType !== null && $buffer !== '') { + yield [$spanType, $buffer]; + } + } + + /** + * Given a token and the current highlight span type, compute the next type. + * + * @param array|string $token \token_get_all token + * @param string|null $currentType + * + * @return string|null + */ + private static function nextHighlightType($token, $currentType) + { + if ($token === '"') { + return self::HIGHLIGHT_STRING; + } + + if (\is_array($token)) { + if ($token[0] === \T_WHITESPACE) { + return $currentType; + } + + if (\array_key_exists($token[0], self::$tokenMap)) { + return self::$tokenMap[$token[0]]; + } + } + + return self::HIGHLIGHT_KEYWORD; + } + + /** + * Group highlight spans into an array of lines. + * + * Optionally, restrict by start and end line numbers. + * + * @param \Generator $spans as [$spanType, $spanText] pairs + * @param int $startLine + * @param int|null $endLine + * + * @return \Generator lines, each an array of [$spanType, $spanText] pairs + */ + private static function splitLines(\Generator $spans, int $startLine = 1, int $endLine = null): \Generator + { + $lineNum = 1; + $buffer = []; + + foreach ($spans as list($spanType, $spanText)) { + foreach (\preg_split('/(\r\n?|\n)/', $spanText) as $index => $spanLine) { + if ($index > 0) { + if ($lineNum >= $startLine) { + yield $lineNum => $buffer; + } + + $lineNum++; + $buffer = []; + + if ($endLine !== null && $lineNum > $endLine) { + return; + } + } + + if ($spanLine !== '') { + $buffer[] = [$spanType, $spanLine]; + } + } + } + + if (!empty($buffer)) { + yield $lineNum => $buffer; + } + } + + /** + * Format lines of highlight spans for shell output. + * + * @param \Generator $spanLines lines, each an array of [$spanType, $spanText] pairs + * + * @return \Generator Formatted lines + */ + private static function formatLines(\Generator $spanLines): \Generator + { + foreach ($spanLines as $lineNum => $spanLine) { + $line = ''; + + foreach ($spanLine as list($spanType, $spanText)) { + if ($spanType === self::HIGHLIGHT_DEFAULT) { + $line .= OutputFormatter::escape($spanText); + } else { + $line .= \sprintf('<%s>%s', $spanType, OutputFormatter::escape($spanText), $spanType); + } + } + + yield $lineNum => $line.\PHP_EOL; + } + } + + /** + * Prepend line numbers to formatted lines. + * + * Lines must be in an associative array with the correct keys in order to be numbered properly. + * + * Optionally, pass $markLine to add a line marker. + * + * @param \Generator $lines Formatted lines + * @param int|null $markLine + * + * @return \Generator Numbered, formatted lines + */ + private static function numberLines(\Generator $lines, int $markLine = null): \Generator + { + $lines = \iterator_to_array($lines); + + // Figure out how much space to reserve for line numbers. + \end($lines); + $pad = \strlen(\key($lines)); + + // If $markLine is before or after our line range, don't bother reserving space for the marker. + if ($markLine !== null) { + if ($markLine > \key($lines)) { + $markLine = null; + } + + \reset($lines); + if ($markLine < \key($lines)) { + $markLine = null; + } + } + + foreach ($lines as $lineNum => $line) { + $mark = ''; + if ($markLine !== null) { + $mark = ($markLine === $lineNum) ? self::LINE_MARKER : self::NO_LINE_MARKER; + } + + yield \sprintf("%s: %s", $mark, $lineNum, $line); + } + } + + /** + * Check whether a Reflector instance is reflectable by this formatter. + * + * @param \Reflector $reflector + * + * @return bool + */ + private static function isReflectable(\Reflector $reflector): bool + { + return ($reflector instanceof \ReflectionClass || $reflector instanceof \ReflectionFunctionAbstract) && \is_file($reflector->getFileName()); + } +} diff --git a/vendor/psy/psysh/src/Formatter/DocblockFormatter.php b/vendor/psy/psysh/src/Formatter/DocblockFormatter.php new file mode 100644 index 0000000000..05a73bd816 --- /dev/null +++ b/vendor/psy/psysh/src/Formatter/DocblockFormatter.php @@ -0,0 +1,174 @@ + 'info', + 'var' => 'strong', + ]; + + /** + * Format a docblock. + * + * @param \Reflector $reflector + * + * @return string Formatted docblock + */ + public static function format(\Reflector $reflector): string + { + $docblock = new Docblock($reflector); + $chunks = []; + + if (!empty($docblock->desc)) { + $chunks[] = 'Description:'; + $chunks[] = self::indent(OutputFormatter::escape($docblock->desc), ' '); + $chunks[] = ''; + } + + if (!empty($docblock->tags)) { + foreach ($docblock::$vectors as $name => $vector) { + if (isset($docblock->tags[$name])) { + $chunks[] = \sprintf('%s:', self::inflect($name)); + $chunks[] = self::formatVector($vector, $docblock->tags[$name]); + $chunks[] = ''; + } + } + + $tags = self::formatTags(\array_keys($docblock::$vectors), $docblock->tags); + if (!empty($tags)) { + $chunks[] = $tags; + $chunks[] = ''; + } + } + + return \rtrim(\implode("\n", $chunks)); + } + + /** + * Format a docblock vector, for example, `@throws`, `@param`, or `@return`. + * + * @see DocBlock::$vectors + * + * @param array $vector + * @param array $lines + * + * @return string + */ + private static function formatVector(array $vector, array $lines): string + { + $template = [' ']; + foreach ($vector as $type) { + $max = 0; + foreach ($lines as $line) { + $chunk = $line[$type]; + $cur = empty($chunk) ? 0 : \strlen($chunk) + 1; + if ($cur > $max) { + $max = $cur; + } + } + + $template[] = self::getVectorParamTemplate($type, $max); + } + $template = \implode(' ', $template); + + return \implode("\n", \array_map(function ($line) use ($template) { + $escaped = \array_map(function ($l) { + if ($l === null) { + return ''; + } + + return OutputFormatter::escape($l); + }, $line); + + return \rtrim(\vsprintf($template, $escaped)); + }, $lines)); + } + + /** + * Format docblock tags. + * + * @param array $skip Tags to exclude + * @param array $tags Tags to format + * + * @return string formatted tags + */ + private static function formatTags(array $skip, array $tags): string + { + $chunks = []; + + foreach ($tags as $name => $values) { + if (\in_array($name, $skip)) { + continue; + } + + foreach ($values as $value) { + $chunks[] = \sprintf('%s%s %s', self::inflect($name), empty($value) ? '' : ':', OutputFormatter::escape($value)); + } + + $chunks[] = ''; + } + + return \implode("\n", $chunks); + } + + /** + * Get a docblock vector template. + * + * @param string $type Vector type + * @param int $max Pad width + * + * @return string + */ + private static function getVectorParamTemplate(string $type, int $max): string + { + if (!isset(self::$vectorParamTemplates[$type])) { + return \sprintf('%%-%ds', $max); + } + + return \sprintf('<%s>%%-%ds', self::$vectorParamTemplates[$type], $max, self::$vectorParamTemplates[$type]); + } + + /** + * Indent a string. + * + * @param string $text String to indent + * @param string $indent (default: ' ') + * + * @return string + */ + private static function indent(string $text, string $indent = ' '): string + { + return $indent.\str_replace("\n", "\n".$indent, $text); + } + + /** + * Convert underscored or whitespace separated words into sentence case. + * + * @param string $text + * + * @return string + */ + private static function inflect(string $text): string + { + $words = \trim(\preg_replace('/[\s_-]+/', ' ', \preg_replace('/([a-z])([A-Z])/', '$1 $2', $text))); + + return \implode(' ', \array_map('ucfirst', \explode(' ', $words))); + } +} diff --git a/vendor/psy/psysh/src/Formatter/Formatter.php b/vendor/psy/psysh/src/Formatter/Formatter.php new file mode 100644 index 0000000000..a8a37bc067 --- /dev/null +++ b/vendor/psy/psysh/src/Formatter/Formatter.php @@ -0,0 +1,21 @@ +getName(); + } + + /** + * Print the method, property or class modifiers. + * + * @param \Reflector $reflector + * + * @return string Formatted modifiers + */ + private static function formatModifiers(\Reflector $reflector): string + { + return \implode(' ', \array_map(function ($modifier) { + return \sprintf('%s', $modifier); + }, \Reflection::getModifierNames($reflector->getModifiers()))); + } + + /** + * Format a class signature. + * + * @param \ReflectionClass $reflector + * + * @return string Formatted signature + */ + private static function formatClass(\ReflectionClass $reflector): string + { + $chunks = []; + + if ($modifiers = self::formatModifiers($reflector)) { + $chunks[] = $modifiers; + } + + if ($reflector->isTrait()) { + $chunks[] = 'trait'; + } else { + $chunks[] = $reflector->isInterface() ? 'interface' : 'class'; + } + + $chunks[] = \sprintf('%s', self::formatName($reflector)); + + if ($parent = $reflector->getParentClass()) { + $chunks[] = 'extends'; + $chunks[] = \sprintf('%s', $parent->getName()); + } + + $interfaces = $reflector->getInterfaceNames(); + if (!empty($interfaces)) { + \sort($interfaces); + + $chunks[] = $reflector->isInterface() ? 'extends' : 'implements'; + $chunks[] = \implode(', ', \array_map(function ($name) { + return \sprintf('%s', $name); + }, $interfaces)); + } + + return \implode(' ', $chunks); + } + + /** + * Format a constant signature. + * + * @param ReflectionClassConstant|\ReflectionClassConstant $reflector + * + * @return string Formatted signature + */ + private static function formatClassConstant($reflector): string + { + $value = $reflector->getValue(); + $style = self::getTypeStyle($value); + + return \sprintf( + 'const %s = <%s>%s', + self::formatName($reflector), + $style, + OutputFormatter::escape(Json::encode($value)), + $style + ); + } + + /** + * Format a constant signature. + * + * @param ReflectionConstant_ $reflector + * + * @return string Formatted signature + */ + private static function formatConstant(ReflectionConstant_ $reflector): string + { + $value = $reflector->getValue(); + $style = self::getTypeStyle($value); + + return \sprintf( + 'define(%s, <%s>%s)', + OutputFormatter::escape(Json::encode($reflector->getName())), + $style, + OutputFormatter::escape(Json::encode($value)), + $style + ); + } + + /** + * Helper for getting output style for a given value's type. + * + * @param mixed $value + * + * @return string + */ + private static function getTypeStyle($value): string + { + if (\is_int($value) || \is_float($value)) { + return 'number'; + } elseif (\is_string($value)) { + return 'string'; + } elseif (\is_bool($value) || $value === null) { + return 'bool'; + } else { + return 'strong'; // @codeCoverageIgnore + } + } + + /** + * Format a property signature. + * + * @param \ReflectionProperty $reflector + * + * @return string Formatted signature + */ + private static function formatProperty(\ReflectionProperty $reflector): string + { + return \sprintf( + '%s $%s', + self::formatModifiers($reflector), + $reflector->getName() + ); + } + + /** + * Format a function signature. + * + * @param \ReflectionFunction $reflector + * + * @return string Formatted signature + */ + private static function formatFunction(\ReflectionFunctionAbstract $reflector): string + { + return \sprintf( + 'function %s%s(%s)%s', + $reflector->returnsReference() ? '&' : '', + self::formatName($reflector), + \implode(', ', self::formatFunctionParams($reflector)), + self::formatFunctionReturnType($reflector) + ); + } + + /** + * Format a function signature's return type (if available). + * + * @param \ReflectionFunctionAbstract $reflector + * + * @return string Formatted return type + */ + private static function formatFunctionReturnType(\ReflectionFunctionAbstract $reflector): string + { + if (!\method_exists($reflector, 'hasReturnType') || !$reflector->hasReturnType()) { + return ''; + } + + return \sprintf(': %s', self::formatReflectionType($reflector->getReturnType())); + } + + /** + * Format a method signature. + * + * @param \ReflectionMethod $reflector + * + * @return string Formatted signature + */ + private static function formatMethod(\ReflectionMethod $reflector): string + { + return \sprintf( + '%s %s', + self::formatModifiers($reflector), + self::formatFunction($reflector) + ); + } + + /** + * Print the function params. + * + * @param \ReflectionFunctionAbstract $reflector + * + * @return array + */ + private static function formatFunctionParams(\ReflectionFunctionAbstract $reflector): array + { + $params = []; + foreach ($reflector->getParameters() as $param) { + $hint = ''; + try { + if (\method_exists($param, 'getType')) { + $hint = self::formatReflectionType($param->getType()); + } else { + if ($param->isArray()) { + $hint = 'array'; + } elseif ($class = $param->getClass()) { + $hint = \sprintf('%s', $class->getName()); + } + } + } catch (\Throwable $e) { + // sometimes we just don't know... + // bad class names, or autoloaded classes that haven't been loaded yet, or whathaveyou. + // come to think of it, the only time I've seen this is with the intl extension. + + // Hax: we'll try to extract it :P + + // @codeCoverageIgnoreStart + $chunks = \explode('$'.$param->getName(), (string) $param); + $chunks = \explode(' ', \trim($chunks[0])); + $guess = \end($chunks); + + $hint = \sprintf('%s', OutputFormatter::escape($guess)); + // @codeCoverageIgnoreEnd + } + + if ($param->isOptional()) { + if (!$param->isDefaultValueAvailable()) { + $value = 'unknown'; + $typeStyle = 'urgent'; + } else { + $value = $param->getDefaultValue(); + $typeStyle = self::getTypeStyle($value); + $value = \is_array($value) ? '[]' : ($value === null ? 'null' : \var_export($value, true)); + } + $default = \sprintf(' = <%s>%s', $typeStyle, OutputFormatter::escape($value), $typeStyle); + } else { + $default = ''; + } + + $params[] = \sprintf( + '%s%s%s$%s%s', + $param->isPassedByReference() ? '&' : '', + $hint, + $hint !== '' ? ' ' : '', + $param->getName(), + $default + ); + } + + return $params; + } + + /** + * Print function param or return type(s). + * + * @param \ReflectionType $type + * + * @return string + */ + private static function formatReflectionType(\ReflectionType $type = null): string + { + if ($type === null) { + return ''; + } + + $types = $type instanceof \ReflectionUnionType ? $type->getTypes() : [$type]; + $formattedTypes = []; + + foreach ($types as $type) { + $typeStyle = $type->isBuiltin() ? 'keyword' : 'class'; + + // PHP 7.0 didn't have `getName` on reflection types, so wheee! + $typeName = \method_exists($type, 'getName') ? $type->getName() : (string) $type; + + // @todo Do we want to include the ? for nullable types? Maybe only sometimes? + $formattedTypes[] = \sprintf('<%s>%s', $typeStyle, OutputFormatter::escape($typeName), $typeStyle); + } + + return \implode('|', $formattedTypes); + } +} diff --git a/vendor/psy/psysh/src/Formatter/TraceFormatter.php b/vendor/psy/psysh/src/Formatter/TraceFormatter.php new file mode 100644 index 0000000000..7575068e7f --- /dev/null +++ b/vendor/psy/psysh/src/Formatter/TraceFormatter.php @@ -0,0 +1,96 @@ +getTrace(); + \array_unshift($trace, [ + 'function' => '', + 'file' => $throwable->getFile() !== null ? $throwable->getFile() : 'n/a', + 'line' => $throwable->getLine() !== null ? $throwable->getLine() : 'n/a', + 'args' => [], + ]); + + if (!$includePsy) { + for ($i = \count($trace) - 1; $i >= 0; $i--) { + $thing = isset($trace[$i]['class']) ? $trace[$i]['class'] : $trace[$i]['function']; + if (\preg_match('/\\\\?Psy\\\\/', $thing)) { + $trace = \array_slice($trace, $i + 1); + break; + } + } + } + + for ($i = 0, $count = \min($count, \count($trace)); $i < $count; $i++) { + $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : ''; + $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : ''; + $function = $trace[$i]['function']; + $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a'; + $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a'; + + // Make file paths relative to cwd + if ($cwd !== false) { + $file = \preg_replace('/^'.\preg_quote($cwd, '/').'/', '', $file); + } + + // Leave execution loop out of the `eval()'d code` lines + if (\preg_match("#/src/Execution(?:Loop)?Closure.php\(\d+\) : eval\(\)'d code$#", \str_replace('\\', '/', $file))) { + $file = "eval()'d code"; + } + + // Skip any lines that don't match our filter options + if ($filter !== null && !$filter->match(\sprintf('%s%s%s() at %s:%s', $class, $type, $function, $file, $line))) { + continue; + } + + $lines[] = \sprintf( + ' %s%s%s() at %s:%s', + OutputFormatter::escape($class), + OutputFormatter::escape($type), + OutputFormatter::escape($function), + OutputFormatter::escape($file), + OutputFormatter::escape($line) + ); + } + + return $lines; + } +} diff --git a/vendor/psy/psysh/src/Input/CodeArgument.php b/vendor/psy/psysh/src/Input/CodeArgument.php new file mode 100644 index 0000000000..f9c8bcdbcc --- /dev/null +++ b/vendor/psy/psysh/src/Input/CodeArgument.php @@ -0,0 +1,50 @@ +validateInput($input); + + if (!$pattern = $input->getOption('grep')) { + $this->filter = false; + + return; + } + + if (!$this->stringIsRegex($pattern)) { + $pattern = '/'.\preg_quote($pattern, '/').'/'; + } + + if ($insensitive = $input->getOption('insensitive')) { + $pattern .= 'i'; + } + + $this->validateRegex($pattern); + + $this->filter = true; + $this->pattern = $pattern; + $this->insensitive = $insensitive; + $this->invert = $input->getOption('invert'); + } + + /** + * Check whether the bound input has filter options. + * + * @return bool + */ + public function hasFilter(): bool + { + return $this->filter; + } + + /** + * Check whether a string matches the current filter options. + * + * @param string $string + * @param array $matches + * + * @return bool + */ + public function match(string $string, array &$matches = null): bool + { + return $this->filter === false || (\preg_match($this->pattern, $string, $matches) xor $this->invert); + } + + /** + * Validate that grep, invert and insensitive input options are consistent. + * + * @throws RuntimeException if input is invalid + * + * @param InputInterface $input + */ + private function validateInput(InputInterface $input) + { + if (!$input->getOption('grep')) { + foreach (['invert', 'insensitive'] as $option) { + if ($input->getOption($option)) { + throw new RuntimeException('--'.$option.' does not make sense without --grep'); + } + } + } + } + + /** + * Check whether a string appears to be a regular expression. + * + * @param string $string + * + * @return bool + */ + private function stringIsRegex(string $string): bool + { + return \substr($string, 0, 1) === '/' && \substr($string, -1) === '/' && \strlen($string) >= 3; + } + + /** + * Validate that $pattern is a valid regular expression. + * + * @throws RuntimeException if pattern is invalid + * + * @param string $pattern + */ + private function validateRegex(string $pattern) + { + \set_error_handler([ErrorException::class, 'throwException']); + try { + \preg_match($pattern, ''); + } catch (ErrorException $e) { + throw new RuntimeException(\str_replace('preg_match(): ', 'Invalid regular expression: ', $e->getRawMessage())); + } finally { + \restore_error_handler(); + } + } +} diff --git a/vendor/psy/psysh/src/Input/ShellInput.php b/vendor/psy/psysh/src/Input/ShellInput.php new file mode 100644 index 0000000000..af21f448a1 --- /dev/null +++ b/vendor/psy/psysh/src/Input/ShellInput.php @@ -0,0 +1,331 @@ +tokenPairs = $this->tokenize($input); + } + + /** + * {@inheritdoc} + * + * @throws \InvalidArgumentException if $definition has CodeArgument before the final argument position + */ + public function bind(InputDefinition $definition) + { + $hasCodeArgument = false; + + if ($definition->getArgumentCount() > 0) { + $args = $definition->getArguments(); + $lastArg = \array_pop($args); + foreach ($args as $arg) { + if ($arg instanceof CodeArgument) { + $msg = \sprintf('Unexpected CodeArgument before the final position: %s', $arg->getName()); + throw new \InvalidArgumentException($msg); + } + } + + if ($lastArg instanceof CodeArgument) { + $hasCodeArgument = true; + } + } + + $this->hasCodeArgument = $hasCodeArgument; + + return parent::bind($definition); + } + + /** + * Tokenizes a string. + * + * The version of this on StringInput is good, but doesn't handle code + * arguments if they're at all complicated. This does :) + * + * @param string $input The input to tokenize + * + * @return array An array of token/rest pairs + * + * @throws \InvalidArgumentException When unable to parse input (should never happen) + */ + private function tokenize(string $input): array + { + $tokens = []; + $length = \strlen($input); + $cursor = 0; + while ($cursor < $length) { + if (\preg_match('/\s+/A', $input, $match, 0, $cursor)) { + } elseif (\preg_match('/([^="\'\s]+?)(=?)('.StringInput::REGEX_QUOTED_STRING.'+)/A', $input, $match, 0, $cursor)) { + $tokens[] = [ + $match[1].$match[2].\stripcslashes(\str_replace(['"\'', '\'"', '\'\'', '""'], '', \substr($match[3], 1, \strlen($match[3]) - 2))), + \stripcslashes(\substr($input, $cursor)), + ]; + } elseif (\preg_match('/'.StringInput::REGEX_QUOTED_STRING.'/A', $input, $match, 0, $cursor)) { + $tokens[] = [ + \stripcslashes(\substr($match[0], 1, \strlen($match[0]) - 2)), + \stripcslashes(\substr($input, $cursor)), + ]; + } elseif (\preg_match('/'.StringInput::REGEX_STRING.'/A', $input, $match, 0, $cursor)) { + $tokens[] = [ + \stripcslashes($match[1]), + \stripcslashes(\substr($input, $cursor)), + ]; + } else { + // should never happen + // @codeCoverageIgnoreStart + throw new \InvalidArgumentException(\sprintf('Unable to parse input near "... %s ..."', \substr($input, $cursor, 10))); + // @codeCoverageIgnoreEnd + } + + $cursor += \strlen($match[0]); + } + + return $tokens; + } + + /** + * Same as parent, but with some bonus handling for code arguments. + */ + protected function parse() + { + $parseOptions = true; + $this->parsed = $this->tokenPairs; + while (null !== $tokenPair = \array_shift($this->parsed)) { + // token is what you'd expect. rest is the remainder of the input + // string, including token, and will be used if this is a code arg. + list($token, $rest) = $tokenPair; + + if ($parseOptions && '' === $token) { + $this->parseShellArgument($token, $rest); + } elseif ($parseOptions && '--' === $token) { + $parseOptions = false; + } elseif ($parseOptions && 0 === \strpos($token, '--')) { + $this->parseLongOption($token); + } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) { + $this->parseShortOption($token); + } else { + $this->parseShellArgument($token, $rest); + } + } + } + + /** + * Parses an argument, with bonus handling for code arguments. + * + * @param string $token The current token + * @param string $rest The remaining unparsed input, including the current token + * + * @throws \RuntimeException When too many arguments are given + */ + private function parseShellArgument(string $token, string $rest) + { + $c = \count($this->arguments); + + // if input is expecting another argument, add it + if ($this->definition->hasArgument($c)) { + $arg = $this->definition->getArgument($c); + + if ($arg instanceof CodeArgument) { + // When we find a code argument, we're done parsing. Add the + // remaining input to the current argument and call it a day. + $this->parsed = []; + $this->arguments[$arg->getName()] = $rest; + } else { + $this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token; + } + + return; + } + + // (copypasta) + // + // @codeCoverageIgnoreStart + + // if last argument isArray(), append token to last argument + if ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) { + $arg = $this->definition->getArgument($c - 1); + $this->arguments[$arg->getName()][] = $token; + + return; + } + + // unexpected argument + $all = $this->definition->getArguments(); + if (\count($all)) { + throw new \RuntimeException(\sprintf('Too many arguments, expected arguments "%s".', \implode('" "', \array_keys($all)))); + } + + throw new \RuntimeException(\sprintf('No arguments expected, got "%s".', $token)); + // @codeCoverageIgnoreEnd + } + + // Everything below this is copypasta from ArgvInput private methods + // @codeCoverageIgnoreStart + + /** + * Parses a short option. + * + * @param string $token The current token + */ + private function parseShortOption(string $token) + { + $name = \substr($token, 1); + + if (\strlen($name) > 1) { + if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptValue()) { + // an option with a value (with no space) + $this->addShortOption($name[0], \substr($name, 1)); + } else { + $this->parseShortOptionSet($name); + } + } else { + $this->addShortOption($name, null); + } + } + + /** + * Parses a short option set. + * + * @param string $name The current token + * + * @throws \RuntimeException When option given doesn't exist + */ + private function parseShortOptionSet(string $name) + { + $len = \strlen($name); + for ($i = 0; $i < $len; $i++) { + if (!$this->definition->hasShortcut($name[$i])) { + throw new \RuntimeException(\sprintf('The "-%s" option does not exist.', $name[$i])); + } + + $option = $this->definition->getOptionForShortcut($name[$i]); + if ($option->acceptValue()) { + $this->addLongOption($option->getName(), $i === $len - 1 ? null : \substr($name, $i + 1)); + + break; + } else { + $this->addLongOption($option->getName(), null); + } + } + } + + /** + * Parses a long option. + * + * @param string $token The current token + */ + private function parseLongOption(string $token) + { + $name = \substr($token, 2); + + if (false !== $pos = \strpos($name, '=')) { + if (($value = \substr($name, $pos + 1)) === '') { + \array_unshift($this->parsed, [$value, null]); + } + $this->addLongOption(\substr($name, 0, $pos), $value); + } else { + $this->addLongOption($name, null); + } + } + + /** + * Adds a short option value. + * + * @param string $shortcut The short option key + * @param mixed $value The value for the option + * + * @throws \RuntimeException When option given doesn't exist + */ + private function addShortOption(string $shortcut, $value) + { + if (!$this->definition->hasShortcut($shortcut)) { + throw new \RuntimeException(\sprintf('The "-%s" option does not exist.', $shortcut)); + } + + $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); + } + + /** + * Adds a long option value. + * + * @param string $name The long option key + * @param mixed $value The value for the option + * + * @throws \RuntimeException When option given doesn't exist + */ + private function addLongOption(string $name, $value) + { + if (!$this->definition->hasOption($name)) { + throw new \RuntimeException(\sprintf('The "--%s" option does not exist.', $name)); + } + + $option = $this->definition->getOption($name); + + if (null !== $value && !$option->acceptValue()) { + throw new \RuntimeException(\sprintf('The "--%s" option does not accept a value.', $name)); + } + + if (\in_array($value, ['', null], true) && $option->acceptValue() && \count($this->parsed)) { + // if option accepts an optional or mandatory argument + // let's see if there is one provided + $next = \array_shift($this->parsed); + $nextToken = $next[0]; + if ((isset($nextToken[0]) && '-' !== $nextToken[0]) || \in_array($nextToken, ['', null], true)) { + $value = $nextToken; + } else { + \array_unshift($this->parsed, $next); + } + } + + if ($value === null) { + if ($option->isValueRequired()) { + throw new \RuntimeException(\sprintf('The "--%s" option requires a value.', $name)); + } + + if (!$option->isArray() && !$option->isValueOptional()) { + $value = true; + } + } + + if ($option->isArray()) { + $this->options[$name][] = $value; + } else { + $this->options[$name] = $value; + } + } + + // @codeCoverageIgnoreEnd +} diff --git a/vendor/psy/psysh/src/Input/SilentInput.php b/vendor/psy/psysh/src/Input/SilentInput.php new file mode 100644 index 0000000000..cf19409b3e --- /dev/null +++ b/vendor/psy/psysh/src/Input/SilentInput.php @@ -0,0 +1,44 @@ +inputString = $inputString; + } + + /** + * To. String. + * + * @return string + */ + public function __toString(): string + { + return $this->inputString; + } +} diff --git a/vendor/psy/psysh/src/Output/OutputPager.php b/vendor/psy/psysh/src/Output/OutputPager.php new file mode 100644 index 0000000000..67f523d00b --- /dev/null +++ b/vendor/psy/psysh/src/Output/OutputPager.php @@ -0,0 +1,26 @@ +getStream()); + } + + /** + * Close the current pager process. + */ + public function close() + { + // nothing to do here + } +} diff --git a/vendor/psy/psysh/src/Output/ProcOutputPager.php b/vendor/psy/psysh/src/Output/ProcOutputPager.php new file mode 100644 index 0000000000..5e562b94ed --- /dev/null +++ b/vendor/psy/psysh/src/Output/ProcOutputPager.php @@ -0,0 +1,105 @@ +stream = $output->getStream(); + $this->cmd = $cmd; + } + + /** + * Writes a message to the output. + * + * @param string $message A message to write to the output + * @param bool $newline Whether to add a newline or not + * + * @throws \RuntimeException When unable to write output (should never happen) + */ + public function doWrite($message, $newline) + { + $pipe = $this->getPipe(); + if (false === @\fwrite($pipe, $message.($newline ? \PHP_EOL : ''))) { + // @codeCoverageIgnoreStart + // should never happen + $this->close(); + throw new \RuntimeException('Unable to write output'); + // @codeCoverageIgnoreEnd + } + + \fflush($pipe); + } + + /** + * Close the current pager process. + */ + public function close() + { + if (isset($this->pipe)) { + \fclose($this->pipe); + } + + if (isset($this->proc)) { + $exit = \proc_close($this->proc); + if ($exit !== 0) { + throw new \RuntimeException('Error closing output stream'); + } + } + + $this->pipe = null; + $this->proc = null; + } + + /** + * Get a pipe for paging output. + * + * If no active pager process exists, fork one and return its input pipe. + */ + private function getPipe() + { + if (!isset($this->pipe) || !isset($this->proc)) { + $desc = [['pipe', 'r'], $this->stream, \fopen('php://stderr', 'w')]; + $this->proc = \proc_open($this->cmd, $desc, $pipes); + + if (!\is_resource($this->proc)) { + throw new \RuntimeException('Error opening output stream'); + } + + $this->pipe = $pipes[0]; + } + + return $this->pipe; + } +} diff --git a/vendor/psy/psysh/src/Output/ShellOutput.php b/vendor/psy/psysh/src/Output/ShellOutput.php new file mode 100644 index 0000000000..00aebbab29 --- /dev/null +++ b/vendor/psy/psysh/src/Output/ShellOutput.php @@ -0,0 +1,215 @@ +initFormatters(); + + if ($pager === null) { + $this->pager = new PassthruPager($this); + } elseif (\is_string($pager)) { + $this->pager = new ProcOutputPager($this, $pager); + } elseif ($pager instanceof OutputPager) { + $this->pager = $pager; + } else { + throw new \InvalidArgumentException('Unexpected pager parameter: '.$pager); + } + } + + /** + * Page multiple lines of output. + * + * The output pager is started + * + * If $messages is callable, it will be called, passing this output instance + * for rendering. Otherwise, all passed $messages are paged to output. + * + * Upon completion, the output pager is flushed. + * + * @param string|array|\Closure $messages A string, array of strings or a callback + * @param int $type (default: 0) + */ + public function page($messages, int $type = 0) + { + if (\is_string($messages)) { + $messages = (array) $messages; + } + + if (!\is_array($messages) && !\is_callable($messages)) { + throw new \InvalidArgumentException('Paged output requires a string, array or callback'); + } + + $this->startPaging(); + + if (\is_callable($messages)) { + $messages($this); + } else { + $this->write($messages, true, $type); + } + + $this->stopPaging(); + } + + /** + * Start sending output to the output pager. + */ + public function startPaging() + { + $this->paging++; + } + + /** + * Stop paging output and flush the output pager. + */ + public function stopPaging() + { + $this->paging--; + $this->closePager(); + } + + /** + * Writes a message to the output. + * + * Optionally, pass `$type | self::NUMBER_LINES` as the $type parameter to + * number the lines of output. + * + * @throws \InvalidArgumentException When unknown output type is given + * + * @param string|array $messages The message as an array of lines or a single string + * @param bool $newline Whether to add a newline or not + * @param int $type The type of output + */ + public function write($messages, $newline = false, $type = 0) + { + if ($this->getVerbosity() === self::VERBOSITY_QUIET) { + return; + } + + $messages = (array) $messages; + + if ($type & self::NUMBER_LINES) { + $pad = \strlen((string) \count($messages)); + $template = $this->isDecorated() ? ": %s" : "%{$pad}s: %s"; + + if ($type & self::OUTPUT_RAW) { + $messages = \array_map([OutputFormatter::class, 'escape'], $messages); + } + + foreach ($messages as $i => $line) { + $messages[$i] = \sprintf($template, $i, $line); + } + + // clean this up for super. + $type = $type & ~self::NUMBER_LINES & ~self::OUTPUT_RAW; + } + + parent::write($messages, $newline, $type); + } + + /** + * Writes a message to the output. + * + * Handles paged output, or writes directly to the output stream. + * + * @param string $message A message to write to the output + * @param bool $newline Whether to add a newline or not + */ + public function doWrite($message, $newline) + { + if ($this->paging > 0) { + $this->pager->doWrite($message, $newline); + } else { + parent::doWrite($message, $newline); + } + } + + /** + * Flush and close the output pager. + */ + private function closePager() + { + if ($this->paging <= 0) { + $this->pager->close(); + } + } + + /** + * Initialize output formatter styles. + */ + private function initFormatters() + { + $formatter = $this->getFormatter(); + $errorFormatter = $this->getErrorOutput()->getFormatter(); + + $formatter->setStyle('warning', new OutputFormatterStyle('black', 'yellow')); + $errorFormatter->setStyle('warning', new OutputFormatterStyle('black', 'yellow')); + $formatter->setStyle('error', new OutputFormatterStyle('white', 'red', ['bold'])); + $errorFormatter->setStyle('error', new OutputFormatterStyle('white', 'red', ['bold'])); + + $formatter->setStyle('aside', new OutputFormatterStyle('blue')); + $formatter->setStyle('strong', new OutputFormatterStyle(null, null, ['bold'])); + $formatter->setStyle('return', new OutputFormatterStyle('cyan')); + $formatter->setStyle('urgent', new OutputFormatterStyle('red')); + $formatter->setStyle('hidden', new OutputFormatterStyle('black')); + + // Visibility + $formatter->setStyle('public', new OutputFormatterStyle(null, null, ['bold'])); + $formatter->setStyle('protected', new OutputFormatterStyle('yellow')); + $formatter->setStyle('private', new OutputFormatterStyle('red')); + $formatter->setStyle('global', new OutputFormatterStyle('cyan', null, ['bold'])); + $formatter->setStyle('const', new OutputFormatterStyle('cyan')); + $formatter->setStyle('class', new OutputFormatterStyle('blue', null, ['underscore'])); + $formatter->setStyle('function', new OutputFormatterStyle(null)); + $formatter->setStyle('default', new OutputFormatterStyle(null)); + + // Types + $formatter->setStyle('number', new OutputFormatterStyle('magenta')); + $formatter->setStyle('integer', new OutputFormatterStyle('magenta')); + $formatter->setStyle('float', new OutputFormatterStyle('yellow')); + $formatter->setStyle('string', new OutputFormatterStyle('green')); + $formatter->setStyle('bool', new OutputFormatterStyle('cyan')); + $formatter->setStyle('keyword', new OutputFormatterStyle('yellow')); + $formatter->setStyle('comment', new OutputFormatterStyle('blue')); + $formatter->setStyle('object', new OutputFormatterStyle('blue')); + $formatter->setStyle('resource', new OutputFormatterStyle('yellow')); + + // Code-specific formatting + $formatter->setStyle('inline_html', new OutputFormatterStyle('cyan')); + } +} diff --git a/vendor/psy/psysh/src/ParserFactory.php b/vendor/psy/psysh/src/ParserFactory.php new file mode 100644 index 0000000000..fb348dd9e8 --- /dev/null +++ b/vendor/psy/psysh/src/ParserFactory.php @@ -0,0 +1,68 @@ +getDefaultKind(); + + if (!\in_array($kind, static::getPossibleKinds())) { + throw new \InvalidArgumentException('Unknown parser kind'); + } + + $parser = $originalFactory->create(\constant(OriginalParserFactory::class.'::'.$kind)); + + return $parser; + } +} diff --git a/vendor/psy/psysh/src/Readline/GNUReadline.php b/vendor/psy/psysh/src/Readline/GNUReadline.php new file mode 100644 index 0000000000..d4dba0cc93 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/GNUReadline.php @@ -0,0 +1,179 @@ +historyFile = ($historyFile !== null) ? $historyFile : false; + $this->historySize = $historySize; + $this->eraseDups = $eraseDups; + + \readline_info('readline_name', 'psysh'); + } + + /** + * {@inheritdoc} + */ + public function addHistory(string $line): bool + { + if ($res = \readline_add_history($line)) { + $this->writeHistory(); + } + + return $res; + } + + /** + * {@inheritdoc} + */ + public function clearHistory(): bool + { + if ($res = \readline_clear_history()) { + $this->writeHistory(); + } + + return $res; + } + + /** + * {@inheritdoc} + */ + public function listHistory(): array + { + return \readline_list_history(); + } + + /** + * {@inheritdoc} + */ + public function readHistory(): bool + { + \readline_read_history(); + \readline_clear_history(); + + return \readline_read_history($this->historyFile); + } + + /** + * {@inheritdoc} + */ + public function readline(string $prompt = null) + { + return \readline($prompt); + } + + /** + * {@inheritdoc} + */ + public function redisplay() + { + \readline_redisplay(); + } + + /** + * {@inheritdoc} + */ + public function writeHistory(): bool + { + // We have to write history first, since it is used + // by Libedit to list history + if ($this->historyFile !== false) { + $res = \readline_write_history($this->historyFile); + } else { + $res = true; + } + + if (!$res || !$this->eraseDups && !$this->historySize > 0) { + return $res; + } + + $hist = $this->listHistory(); + if (!$hist) { + return true; + } + + if ($this->eraseDups) { + // flip-flip technique: removes duplicates, latest entries win. + $hist = \array_flip(\array_flip($hist)); + // sort on keys to get the order back + \ksort($hist); + } + + if ($this->historySize > 0) { + $histsize = \count($hist); + if ($histsize > $this->historySize) { + $hist = \array_slice($hist, $histsize - $this->historySize); + } + } + + \readline_clear_history(); + foreach ($hist as $line) { + \readline_add_history($line); + } + + if ($this->historyFile !== false) { + return \readline_write_history($this->historyFile); + } + + return true; + } +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/Autocompleter.php b/vendor/psy/psysh/src/Readline/Hoa/Autocompleter.php new file mode 100644 index 0000000000..6955de6224 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/Autocompleter.php @@ -0,0 +1,57 @@ +setAutocompleters($autocompleters); + + return; + } + + /** + * Complete a word. + * Returns null for no word, a full-word or an array of full-words. + */ + public function complete(&$prefix) + { + foreach ($this->getAutocompleters() as $autocompleter) { + $preg = \preg_match( + '#('.$autocompleter->getWordDefinition().')$#u', + $prefix, + $match + ); + + if (0 === $preg) { + continue; + } + + $_prefix = $match[0]; + + if (null === $out = $autocompleter->complete($_prefix)) { + continue; + } + + $prefix = $_prefix; + + return $out; + } + + return null; + } + + /** + * Set/initialize list of autocompleters. + */ + protected function setAutocompleters(array $autocompleters) + { + $old = $this->_autocompleters; + $this->_autocompleters = new \ArrayObject($autocompleters); + + return $old; + } + + /** + * Get list of autocompleters. + */ + public function getAutocompleters() + { + return $this->_autocompleters; + } + + /** + * Get definition of a word. + */ + public function getWordDefinition(): string + { + return '.*'; + } +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/AutocompleterPath.php b/vendor/psy/psysh/src/Readline/Hoa/AutocompleterPath.php new file mode 100644 index 0000000000..a922b78986 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/AutocompleterPath.php @@ -0,0 +1,194 @@ +setRoot($root); + } + + if (null !== $iteratorFactory) { + $this->setIteratorFactory($iteratorFactory); + } + } + + /** + * Complete a word. + * Returns null for no word, a full-word or an array of full-words. + */ + public function complete(&$prefix) + { + $root = $this->getRoot(); + + if (static::PWD === $root) { + $root = \getcwd(); + } + + $path = $root.\DIRECTORY_SEPARATOR.$prefix; + + if (!\is_dir($path)) { + $path = \dirname($path).\DIRECTORY_SEPARATOR; + $prefix = \basename($prefix); + } else { + $prefix = null; + } + + $iteratorFactory = $this->getIteratorFactory() ?: + static::getDefaultIteratorFactory(); + + try { + $iterator = $iteratorFactory($path); + $out = []; + $length = \mb_strlen($prefix); + + foreach ($iterator as $fileinfo) { + $filename = $fileinfo->getFilename(); + + if (null === $prefix || + (\mb_substr($filename, 0, $length) === $prefix)) { + if ($fileinfo->isDir()) { + $out[] = $filename.'/'; + } else { + $out[] = $filename; + } + } + } + } catch (\Exception $e) { + return null; + } + + $count = \count($out); + + if (1 === $count) { + return $out[0]; + } + + if (0 === $count) { + return null; + } + + return $out; + } + + /** + * Get definition of a word. + */ + public function getWordDefinition(): string + { + return '/?[\w\d\\_\-\.]+(/[\w\d\\_\-\.]*)*'; + } + + /** + * Set root. + */ + public function setRoot(string $root) + { + $old = $this->_root; + $this->_root = $root; + + return $old; + } + + /** + * Get root. + */ + public function getRoot() + { + return $this->_root; + } + + /** + * Set iterator factory (a finder). + */ + public function setIteratorFactory(\Closure $iteratorFactory) + { + $old = $this->_iteratorFactory; + $this->_iteratorFactory = $iteratorFactory; + + return $old; + } + + /** + * Get iterator factory. + */ + public function getIteratorFactory() + { + return $this->_iteratorFactory; + } + + /** + * Get default iterator factory (based on \DirectoryIterator). + */ + public static function getDefaultIteratorFactory() + { + return function ($path) { + return new \DirectoryIterator($path); + }; + } +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/AutocompleterWord.php b/vendor/psy/psysh/src/Readline/Hoa/AutocompleterWord.php new file mode 100644 index 0000000000..c60823eac2 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/AutocompleterWord.php @@ -0,0 +1,119 @@ +setWords($words); + } + + /** + * Complete a word. + * Returns null for no word, a full-word or an array of full-words. + * + * @param string &$prefix Prefix to autocomplete + * + * @return mixed + */ + public function complete(&$prefix) + { + $out = []; + $length = \mb_strlen($prefix); + + foreach ($this->getWords() as $word) { + if (\mb_substr($word, 0, $length) === $prefix) { + $out[] = $word; + } + } + + if (empty($out)) { + return null; + } + + if (1 === \count($out)) { + return $out[0]; + } + + return $out; + } + + /** + * Get definition of a word. + */ + public function getWordDefinition(): string + { + return '\b\w+'; + } + + /** + * Set list of words. + * + * @param array $words words + * + * @return array + */ + public function setWords(array $words) + { + $old = $this->_words; + $this->_words = $words; + + return $old; + } + + /** + * Get list of words. + */ + public function getWords(): array + { + return $this->_words; + } +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/Console.php b/vendor/psy/psysh/src/Readline/Hoa/Console.php new file mode 100644 index 0000000000..17b1fecc64 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/Console.php @@ -0,0 +1,347 @@ + $repeat) { + return; + } elseif (1 === $repeat) { + $handle = \explode(' ', $steps); + } else { + $handle = \explode(' ', $steps, 1); + } + + $tput = Console::getTput(); + $output = Console::getOutput(); + + foreach ($handle as $step) { + switch ($step) { + case 'u': + case 'up': + case '↑': + $output->writeAll( + \str_replace( + '%p1%d', + $repeat, + $tput->get('parm_up_cursor') + ) + ); + + break; + + case 'U': + case 'UP': + static::moveTo(null, 1); + + break; + + case 'r': + case 'right': + case '→': + $output->writeAll( + \str_replace( + '%p1%d', + $repeat, + $tput->get('parm_right_cursor') + ) + ); + + break; + + case 'R': + case 'RIGHT': + static::moveTo(9999); + + break; + + case 'd': + case 'down': + case '↓': + $output->writeAll( + \str_replace( + '%p1%d', + $repeat, + $tput->get('parm_down_cursor') + ) + ); + + break; + + case 'D': + case 'DOWN': + static::moveTo(null, 9999); + + break; + + case 'l': + case 'left': + case '←': + $output->writeAll( + \str_replace( + '%p1%d', + $repeat, + $tput->get('parm_left_cursor') + ) + ); + + break; + + case 'L': + case 'LEFT': + static::moveTo(1); + + break; + } + } + } + + /** + * Move to the line X and the column Y. + * If null, use the current coordinate. + */ + public static function moveTo(int $x = null, int $y = null) + { + if (null === $x || null === $y) { + $position = static::getPosition(); + + if (null === $x) { + $x = $position['x']; + } + + if (null === $y) { + $y = $position['y']; + } + } + + Console::getOutput()->writeAll( + \str_replace( + ['%i%p1%d', '%p2%d'], + [$y, $x], + Console::getTput()->get('cursor_address') + ) + ); + } + + /** + * Get current position (x and y) of the cursor. + */ + public static function getPosition(): array + { + $tput = Console::getTput(); + $user7 = $tput->get('user7'); + + if (null === $user7) { + return [ + 'x' => 0, + 'y' => 0, + ]; + } + + Console::getOutput()->writeAll($user7); + + $input = Console::getInput(); + + // Read $tput->get('user6'). + $input->read(2); // skip \033 and [. + + $x = null; + $y = null; + $handle = &$y; + + while (true) { + $char = $input->readCharacter(); + + switch ($char) { + case ';': + $handle = &$x; + + break; + + case 'R': + break 2; + + default: + $handle .= $char; + } + } + + return [ + 'x' => (int) $x, + 'y' => (int) $y, + ]; + } + + /** + * Save current position. + */ + public static function save() + { + Console::getOutput()->writeAll( + Console::getTput()->get('save_cursor') + ); + } + + /** + * Restore cursor to the last saved position. + */ + public static function restore() + { + Console::getOutput()->writeAll( + Console::getTput()->get('restore_cursor') + ); + } + + /** + * Clear the screen. + * Part can be: + * • a, all, ↕ : clear entire screen and static::move(1, 1); + * • u, up, ↑ : clear from cursor to beginning of the screen; + * • r, right, → : clear from cursor to the end of the line; + * • d, down, ↓ : clear from cursor to end of the screen; + * • l, left, ← : clear from cursor to beginning of the screen; + * • line, ↔ : clear all the line and static::move(1). + * Parts can be concatenated by a single space. + */ + public static function clear(string $parts = 'all') + { + $tput = Console::getTput(); + $output = Console::getOutput(); + + foreach (\explode(' ', $parts) as $part) { + switch ($part) { + case 'a': + case 'all': + case '↕': + $output->writeAll($tput->get('clear_screen')); + static::moveTo(1, 1); + + break; + + case 'u': + case 'up': + case '↑': + $output->writeAll("\033[1J"); + + break; + + case 'r': + case 'right': + case '→': + $output->writeAll($tput->get('clr_eol')); + + break; + + case 'd': + case 'down': + case '↓': + $output->writeAll($tput->get('clr_eos')); + + break; + + case 'l': + case 'left': + case '←': + $output->writeAll($tput->get('clr_bol')); + + break; + + case 'line': + case '↔': + $output->writeAll("\r".$tput->get('clr_eol')); + + break; + } + } + } + + /** + * Hide the cursor. + */ + public static function hide() + { + Console::getOutput()->writeAll( + Console::getTput()->get('cursor_invisible') + ); + } + + /** + * Show the cursor. + */ + public static function show() + { + Console::getOutput()->writeAll( + Console::getTput()->get('cursor_visible') + ); + } + + /** + * Colorize cursor. + * Attributes can be: + * • n, normal : normal; + * • b, bold : bold; + * • u, underlined : underlined; + * • bl, blink : blink; + * • i, inverse : inverse; + * • !b, !bold : normal weight; + * • !u, !underlined : not underlined; + * • !bl, !blink : steady; + * • !i, !inverse : positive; + * • fg(color), foreground(color) : set foreground to “color”; + * • bg(color), background(color) : set background to “color”. + * “color” can be: + * • default; + * • black; + * • red; + * • green; + * • yellow; + * • blue; + * • magenta; + * • cyan; + * • white; + * • 0-256 (classic palette); + * • #hexa. + * Attributes can be concatenated by a single space. + */ + public static function colorize(string $attributes) + { + static $_rgbTo256 = null; + + if (null === $_rgbTo256) { + $_rgbTo256 = [ + '000000', '800000', '008000', '808000', '000080', '800080', + '008080', 'c0c0c0', '808080', 'ff0000', '00ff00', 'ffff00', + '0000ff', 'ff00ff', '00ffff', 'ffffff', '000000', '00005f', + '000087', '0000af', '0000d7', '0000ff', '005f00', '005f5f', + '005f87', '005faf', '005fd7', '005fff', '008700', '00875f', + '008787', '0087af', '0087d7', '0087ff', '00af00', '00af5f', + '00af87', '00afaf', '00afd7', '00afff', '00d700', '00d75f', + '00d787', '00d7af', '00d7d7', '00d7ff', '00ff00', '00ff5f', + '00ff87', '00ffaf', '00ffd7', '00ffff', '5f0000', '5f005f', + '5f0087', '5f00af', '5f00d7', '5f00ff', '5f5f00', '5f5f5f', + '5f5f87', '5f5faf', '5f5fd7', '5f5fff', '5f8700', '5f875f', + '5f8787', '5f87af', '5f87d7', '5f87ff', '5faf00', '5faf5f', + '5faf87', '5fafaf', '5fafd7', '5fafff', '5fd700', '5fd75f', + '5fd787', '5fd7af', '5fd7d7', '5fd7ff', '5fff00', '5fff5f', + '5fff87', '5fffaf', '5fffd7', '5fffff', '870000', '87005f', + '870087', '8700af', '8700d7', '8700ff', '875f00', '875f5f', + '875f87', '875faf', '875fd7', '875fff', '878700', '87875f', + '878787', '8787af', '8787d7', '8787ff', '87af00', '87af5f', + '87af87', '87afaf', '87afd7', '87afff', '87d700', '87d75f', + '87d787', '87d7af', '87d7d7', '87d7ff', '87ff00', '87ff5f', + '87ff87', '87ffaf', '87ffd7', '87ffff', 'af0000', 'af005f', + 'af0087', 'af00af', 'af00d7', 'af00ff', 'af5f00', 'af5f5f', + 'af5f87', 'af5faf', 'af5fd7', 'af5fff', 'af8700', 'af875f', + 'af8787', 'af87af', 'af87d7', 'af87ff', 'afaf00', 'afaf5f', + 'afaf87', 'afafaf', 'afafd7', 'afafff', 'afd700', 'afd75f', + 'afd787', 'afd7af', 'afd7d7', 'afd7ff', 'afff00', 'afff5f', + 'afff87', 'afffaf', 'afffd7', 'afffff', 'd70000', 'd7005f', + 'd70087', 'd700af', 'd700d7', 'd700ff', 'd75f00', 'd75f5f', + 'd75f87', 'd75faf', 'd75fd7', 'd75fff', 'd78700', 'd7875f', + 'd78787', 'd787af', 'd787d7', 'd787ff', 'd7af00', 'd7af5f', + 'd7af87', 'd7afaf', 'd7afd7', 'd7afff', 'd7d700', 'd7d75f', + 'd7d787', 'd7d7af', 'd7d7d7', 'd7d7ff', 'd7ff00', 'd7ff5f', + 'd7ff87', 'd7ffaf', 'd7ffd7', 'd7ffff', 'ff0000', 'ff005f', + 'ff0087', 'ff00af', 'ff00d7', 'ff00ff', 'ff5f00', 'ff5f5f', + 'ff5f87', 'ff5faf', 'ff5fd7', 'ff5fff', 'ff8700', 'ff875f', + 'ff8787', 'ff87af', 'ff87d7', 'ff87ff', 'ffaf00', 'ffaf5f', + 'ffaf87', 'ffafaf', 'ffafd7', 'ffafff', 'ffd700', 'ffd75f', + 'ffd787', 'ffd7af', 'ffd7d7', 'ffd7ff', 'ffff00', 'ffff5f', + 'ffff87', 'ffffaf', 'ffffd7', 'ffffff', '080808', '121212', + '1c1c1c', '262626', '303030', '3a3a3a', '444444', '4e4e4e', + '585858', '606060', '666666', '767676', '808080', '8a8a8a', + '949494', '9e9e9e', 'a8a8a8', 'b2b2b2', 'bcbcbc', 'c6c6c6', + 'd0d0d0', 'dadada', 'e4e4e4', 'eeeeee', + ]; + } + + $tput = Console::getTput(); + + if (1 >= $tput->count('max_colors')) { + return; + } + + $handle = []; + + foreach (\explode(' ', $attributes) as $attribute) { + switch ($attribute) { + case 'n': + case 'normal': + $handle[] = 0; + + break; + + case 'b': + case 'bold': + $handle[] = 1; + + break; + + case 'u': + case 'underlined': + $handle[] = 4; + + break; + + case 'bl': + case 'blink': + $handle[] = 5; + + break; + + case 'i': + case 'inverse': + $handle[] = 7; + + break; + + case '!b': + case '!bold': + $handle[] = 22; + + break; + + case '!u': + case '!underlined': + $handle[] = 24; + + break; + + case '!bl': + case '!blink': + $handle[] = 25; + + break; + + case '!i': + case '!inverse': + $handle[] = 27; + + break; + + default: + if (0 === \preg_match('#^([^\(]+)\(([^\)]+)\)$#', $attribute, $m)) { + break; + } + + $shift = 0; + + switch ($m[1]) { + case 'fg': + case 'foreground': + $shift = 0; + + break; + + case 'bg': + case 'background': + $shift = 10; + + break; + + default: + break 2; + } + + $_handle = 0; + $_keyword = true; + + switch ($m[2]) { + case 'black': + $_handle = 30; + + break; + + case 'red': + $_handle = 31; + + break; + + case 'green': + $_handle = 32; + + break; + + case 'yellow': + $_handle = 33; + + break; + + case 'blue': + $_handle = 34; + + break; + + case 'magenta': + $_handle = 35; + + break; + + case 'cyan': + $_handle = 36; + + break; + + case 'white': + $_handle = 37; + + break; + + case 'default': + $_handle = 39; + + break; + + default: + $_keyword = false; + + if (256 <= $tput->count('max_colors') && + '#' === $m[2][0]) { + $rgb = \hexdec(\substr($m[2], 1)); + $r = ($rgb >> 16) & 255; + $g = ($rgb >> 8) & 255; + $b = $rgb & 255; + $distance = null; + + foreach ($_rgbTo256 as $i => $_rgb) { + $_rgb = \hexdec($_rgb); + $_r = ($_rgb >> 16) & 255; + $_g = ($_rgb >> 8) & 255; + $_b = $_rgb & 255; + + $d = \sqrt( + ($_r - $r) ** 2 + + ($_g - $g) ** 2 + + ($_b - $b) ** 2 + ); + + if (null === $distance || + $d <= $distance) { + $distance = $d; + $_handle = $i; + } + } + } else { + $_handle = (int) ($m[2]); + } + } + + if (true === $_keyword) { + $handle[] = $_handle + $shift; + } else { + $handle[] = (38 + $shift).';5;'.$_handle; + } + } + } + + Console::getOutput()->writeAll("\033[".\implode(';', $handle).'m'); + + return; + } + + /** + * Change color number to a specific RGB color. + */ + public static function changeColor(int $fromCode, int $toColor) + { + $tput = Console::getTput(); + + if (true !== $tput->has('can_change')) { + return; + } + + $r = ($toColor >> 16) & 255; + $g = ($toColor >> 8) & 255; + $b = $toColor & 255; + + Console::getOutput()->writeAll( + \str_replace( + [ + '%p1%d', + 'rgb:', + '%p2%{255}%*%{1000}%/%2.2X/', + '%p3%{255}%*%{1000}%/%2.2X/', + '%p4%{255}%*%{1000}%/%2.2X', + ], + [ + $fromCode, + '', + \sprintf('%02x', $r), + \sprintf('%02x', $g), + \sprintf('%02x', $b), + ], + $tput->get('initialize_color') + ) + ); + + return; + } + + /** + * Set cursor style. + * Style can be: + * • b, block, ▋: block; + * • u, underline, _: underline; + * • v, vertical, |: vertical. + */ + public static function setStyle(string $style, bool $blink = true) + { + if (\defined('PHP_WINDOWS_VERSION_PLATFORM')) { + return; + } + + switch ($style) { + case 'b': + case 'block': + case '▋': + $_style = 1; + + break; + + case 'u': + case 'underline': + case '_': + $_style = 2; + + break; + + case 'v': + case 'vertical': + case '|': + $_style = 5; + + break; + } + + if (false === $blink) { + ++$_style; + } + + // Not sure what tput entry we can use here… + Console::getOutput()->writeAll("\033[".$_style.' q'); + + return; + } + + /** + * Make a stupid “bip”. + */ + public static function bip() + { + Console::getOutput()->writeAll( + Console::getTput()->get('bell') + ); + } +} + +/* + * Advanced interaction. + */ +Console::advancedInteraction(); diff --git a/vendor/psy/psysh/src/Readline/Hoa/ConsoleException.php b/vendor/psy/psysh/src/Readline/Hoa/ConsoleException.php new file mode 100644 index 0000000000..17e6f607b8 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/ConsoleException.php @@ -0,0 +1,46 @@ +_input = $input; + + return; + } + + /** + * Get underlying stream. + */ + public function getStream(): StreamIn + { + return $this->_input; + } + + /** + * Test for end-of-file. + */ + public function eof(): bool + { + return $this->_input->eof(); + } + + /** + * Read n characters. + */ + public function read(int $length) + { + return $this->_input->read($length); + } + + /** + * Alias of $this->read(). + */ + public function readString(int $length) + { + return $this->_input->readString($length); + } + + /** + * Read a character. + */ + public function readCharacter() + { + return $this->_input->readCharacter(); + } + + /** + * Read a boolean. + */ + public function readBoolean() + { + return $this->_input->readBoolean(); + } + + /** + * Read an integer. + */ + public function readInteger(int $length = 1) + { + return $this->_input->readInteger($length); + } + + /** + * Read a float. + */ + public function readFloat(int $length = 1) + { + return $this->_input->readFloat($length); + } + + /** + * Read an array. + * Alias of the $this->scanf() method. + */ + public function readArray($argument = null) + { + return $this->_input->readArray($argument); + } + + /** + * Read a line. + */ + public function readLine() + { + return $this->_input->readLine(); + } + + /** + * Read all, i.e. read as much as possible. + */ + public function readAll(int $offset = 0) + { + return $this->_input->readAll($offset); + } + + /** + * Parse input from a stream according to a format. + */ + public function scanf(string $format): array + { + return $this->_input->scanf($format); + } +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/ConsoleOutput.php b/vendor/psy/psysh/src/Readline/Hoa/ConsoleOutput.php new file mode 100644 index 0000000000..ded5ec0601 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/ConsoleOutput.php @@ -0,0 +1,208 @@ +_output = $output; + + return; + } + + /** + * Get the real output stream. + */ + public function getStream(): StreamOut + { + return $this->_output; + } + + /** + * Write n characters. + */ + public function write(string $string, int $length) + { + if (0 > $length) { + throw new ConsoleException('Length must be greater than 0, given %d.', 0, $length); + } + + $out = \substr($string, 0, $length); + + if (true === $this->isMultiplexerConsidered()) { + if (true === Console::isTmuxRunning()) { + $out = + "\033Ptmux;". + \str_replace("\033", "\033\033", $out). + "\033\\"; + } + + $length = \strlen($out); + } + + if (null === $this->_output) { + echo $out; + } else { + $this->_output->write($out, $length); + } + } + + /** + * Write a string. + */ + public function writeString(string $string) + { + $string = (string) $string; + + return $this->write($string, \strlen($string)); + } + + /** + * Write a character. + */ + public function writeCharacter(string $character) + { + return $this->write((string) $character[0], 1); + } + + /** + * Write a boolean. + */ + public function writeBoolean(bool $boolean) + { + return $this->write(((bool) $boolean) ? '1' : '0', 1); + } + + /** + * Write an integer. + */ + public function writeInteger(int $integer) + { + $integer = (string) (int) $integer; + + return $this->write($integer, \strlen($integer)); + } + + /** + * Write a float. + */ + public function writeFloat(float $float) + { + $float = (string) (float) $float; + + return $this->write($float, \strlen($float)); + } + + /** + * Write an array. + */ + public function writeArray(array $array) + { + $array = \var_export($array, true); + + return $this->write($array, \strlen($array)); + } + + /** + * Write a line. + */ + public function writeLine(string $line) + { + if (false === $n = \strpos($line, "\n")) { + return $this->write($line."\n", \strlen($line) + 1); + } + + ++$n; + + return $this->write(\substr($line, 0, $n), $n); + } + + /** + * Write all, i.e. as much as possible. + */ + public function writeAll(string $string) + { + return $this->write($string ?: '', \strlen($string ?: '')); + } + + /** + * Truncate a stream to a given length. + */ + public function truncate(int $size): bool + { + return false; + } + + /** + * Consider the multiplexer (if running) while writing on the output. + */ + public function considerMultiplexer(bool $consider): bool + { + $old = $this->_considerMultiplexer; + $this->_considerMultiplexer = $consider; + + return $old; + } + + /** + * Check whether the multiplexer must be considered or not. + */ + public function isMultiplexerConsidered(): bool + { + return $this->_considerMultiplexer; + } +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/ConsoleProcessus.php b/vendor/psy/psysh/src/Readline/Hoa/ConsoleProcessus.php new file mode 100644 index 0000000000..69bf1ddb19 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/ConsoleProcessus.php @@ -0,0 +1,892 @@ + value, or input). + */ + protected $_options = []; + + /** + * Current working directory. + */ + protected $_cwd = null; + + /** + * Environment. + */ + protected $_environment = null; + + /** + * Timeout. + */ + protected $_timeout = 30; + + /** + * Descriptor. + */ + protected $_descriptors = [ + 0 => ['pipe', 'r'], + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ]; + + /** + * Pipe descriptors of the processus. + */ + protected $_pipes = null; + + /** + * Seekability of pipes. + */ + protected $_seekable = []; + + /** + * Start a processus. + */ + public function __construct( + string $command, + array $options = null, + array $descriptors = null, + string $cwd = null, + array $environment = null, + int $timeout = 30 + ) { + $this->setCommand($command); + + if (null !== $options) { + $this->setOptions($options); + } + + if (null !== $descriptors) { + $this->_descriptors = []; + + foreach ($descriptors as $descriptor => $nature) { + if (isset($this->_descriptors[$descriptor])) { + throw new ConsoleException('Pipe descriptor %d already exists, cannot '.'redefine it.', 0, $descriptor); + } + + $this->_descriptors[$descriptor] = $nature; + } + } + + $this->setCwd($cwd ?: \getcwd()); + + if (null !== $environment) { + $this->setEnvironment($environment); + } + + $this->setTimeout($timeout); + parent::__construct($this->getCommandLine(), null, true); + $this->getListener()->addIds(['input', 'output', 'timeout', 'start', 'stop']); + + return; + } + + /** + * Open the stream and return the associated resource. + */ + protected function &_open(string $streamName, StreamContext $context = null) + { + $out = @\proc_open( + $streamName, + $this->_descriptors, + $this->_pipes, + $this->getCwd(), + $this->getEnvironment() + ); + + if (false === $out) { + throw new ConsoleException('Something wrong happen when running %s.', 1, $streamName); + } + + return $out; + } + + /** + * Close the current stream. + */ + protected function _close(): bool + { + foreach ($this->_pipes as $pipe) { + @\fclose($pipe); + } + + return (bool) @\proc_close($this->getStream()); + } + + /** + * Run the process and fire events (amongst start, stop, input, output and + * timeout). + * If an event returns false, it will close the current pipe. + * For a simple run without firing events, use the $this->open() method. + */ + public function run() + { + if (false === $this->isOpened()) { + $this->open(); + } else { + $this->_close(); + $this->_setStream($this->_open( + $this->getStreamName(), + $this->getStreamContext() + )); + } + + $this->getListener()->fire('start', new EventBucket()); + + $_read = []; + $_write = []; + $_except = []; + + foreach ($this->_pipes as $p => $pipe) { + switch ($this->_descriptors[$p][1]) { + case 'r': + \stream_set_blocking($pipe, false); + $_write[] = $pipe; + + break; + + case 'w': + case 'a': + \stream_set_blocking($pipe, true); + $_read[] = $pipe; + + break; + } + } + + while (true) { + foreach ($_read as $i => $r) { + if (false === \is_resource($r)) { + unset($_read[$i]); + } + } + + foreach ($_write as $i => $w) { + if (false === \is_resource($w)) { + unset($_write[$i]); + } + } + + foreach ($_except as $i => $e) { + if (false === \is_resource($e)) { + unset($_except[$i]); + } + } + + if (empty($_read) && empty($_write) && empty($_except)) { + break; + } + + $read = $_read; + $write = $_write; + $except = $_except; + $select = \stream_select($read, $write, $except, $this->getTimeout()); + + if (0 === $select) { + $this->getListener()->fire('timeout', new EventBucket()); + + break; + } + + foreach ($read as $i => $_r) { + $pipe = \array_search($_r, $this->_pipes); + $line = $this->readLine($pipe); + + if (false === $line) { + $result = [false]; + } else { + $result = $this->getListener()->fire( + 'output', + new EventBucket([ + 'pipe' => $pipe, + 'line' => $line, + ]) + ); + } + + if (true === \feof($_r) || \in_array(false, $result, true)) { + \fclose($_r); + unset($_read[$i]); + + break; + } + } + + foreach ($write as $j => $_w) { + $result = $this->getListener()->fire( + 'input', + new EventBucket([ + 'pipe' => \array_search($_w, $this->_pipes), + ]) + ); + + if (true === \feof($_w) || \in_array(false, $result, true)) { + \fclose($_w); + unset($_write[$j]); + } + } + + if (empty($_read)) { + break; + } + } + + $this->getListener()->fire('stop', new EventBucket()); + + return; + } + + /** + * Get pipe resource. + */ + protected function getPipe(int $pipe) + { + if (!isset($this->_pipes[$pipe])) { + throw new ConsoleException('Pipe descriptor %d does not exist, cannot read from it.', 2, $pipe); + } + + return $this->_pipes[$pipe]; + } + + /** + * Check if a pipe is seekable or not. + */ + protected function isPipeSeekable(int $pipe): bool + { + if (!isset($this->_seekable[$pipe])) { + $_pipe = $this->getPipe($pipe); + $data = \stream_get_meta_data($_pipe); + $this->_seekable[$pipe] = $data['seekable']; + } + + return $this->_seekable[$pipe]; + } + + /** + * Test for end-of-file. + */ + public function eof(int $pipe = 1): bool + { + return \feof($this->getPipe($pipe)); + } + + /** + * Read n characters. + */ + public function read(int $length, int $pipe = 1) + { + if (0 > $length) { + throw new ConsoleException('Length must be greater than 0, given %d.', 3, $length); + } + + return \fread($this->getPipe($pipe), $length); + } + + /** + * Alias of $this->read(). + */ + public function readString(int $length, int $pipe = 1) + { + return $this->read($length, $pipe); + } + + /** + * Read a character. + */ + public function readCharacter(int $pipe = 1) + { + return \fgetc($this->getPipe($pipe)); + } + + /** + * Read a boolean. + */ + public function readBoolean(int $pipe = 1) + { + return (bool) $this->read(1, $pipe); + } + + /** + * Read an integer. + */ + public function readInteger(int $length = 1, int $pipe = 1) + { + return (int) $this->read($length, $pipe); + } + + /** + * Read a float. + */ + public function readFloat(int $length = 1, int $pipe = 1) + { + return (float) $this->read($length, $pipe); + } + + /** + * Read an array. + * Alias of the $this->scanf() method. + */ + public function readArray(string $format = null, int $pipe = 1) + { + return $this->scanf($format, $pipe); + } + + /** + * Read a line. + */ + public function readLine(int $pipe = 1) + { + return \stream_get_line($this->getPipe($pipe), 1 << 15, "\n"); + } + + /** + * Read all, i.e. read as much as possible. + */ + public function readAll(int $offset = -1, int $pipe = 1) + { + $_pipe = $this->getPipe($pipe); + + if (true === $this->isPipeSeekable($pipe)) { + $offset += \ftell($_pipe); + } else { + $offset = -1; + } + + return \stream_get_contents($_pipe, -1, $offset); + } + + /** + * Parse input from a stream according to a format. + */ + public function scanf(string $format, int $pipe = 1): array + { + return \fscanf($this->getPipe($pipe), $format); + } + + /** + * Write n characters. + */ + public function write(string $string, int $length, int $pipe = 0) + { + if (0 > $length) { + throw new ConsoleException('Length must be greater than 0, given %d.', 4, $length); + } + + return \fwrite($this->getPipe($pipe), $string, $length); + } + + /** + * Write a string. + */ + public function writeString(string $string, int $pipe = 0) + { + $string = (string) $string; + + return $this->write($string, \strlen($string), $pipe); + } + + /** + * Write a character. + */ + public function writeCharacter(string $char, int $pipe = 0) + { + return $this->write((string) $char[0], 1, $pipe); + } + + /** + * Write a boolean. + */ + public function writeBoolean(bool $boolean, int $pipe = 0) + { + return $this->write((string) (bool) $boolean, 1, $pipe); + } + + /** + * Write an integer. + */ + public function writeInteger(int $integer, int $pipe = 0) + { + $integer = (string) (int) $integer; + + return $this->write($integer, \strlen($integer), $pipe); + } + + /** + * Write a float. + */ + public function writeFloat(float $float, int $pipe = 0) + { + $float = (string) (float) $float; + + return $this->write($float, \strlen($float), $pipe); + } + + /** + * Write an array. + */ + public function writeArray(array $array, int $pipe = 0) + { + $array = \var_export($array, true); + + return $this->write($array, \strlen($array), $pipe); + } + + /** + * Write a line. + */ + public function writeLine(string $line, int $pipe = 0) + { + if (false === $n = \strpos($line, "\n")) { + return $this->write($line."\n", \strlen($line) + 1, $pipe); + } + + ++$n; + + return $this->write(\substr($line, 0, $n), $n, $pipe); + } + + /** + * Write all, i.e. as much as possible. + */ + public function writeAll(string $string, int $pipe = 0) + { + return $this->write($string, \strlen($string), $pipe); + } + + /** + * Truncate a file to a given length. + */ + public function truncate(int $size, int $pipe = 0): bool + { + return \ftruncate($this->getPipe($pipe), $size); + } + + /** + * Get filename component of path. + */ + public function getBasename(): string + { + return \basename($this->getCommand()); + } + + /** + * Get directory name component of path. + */ + public function getDirname(): string + { + return \dirname($this->getCommand()); + } + + /** + * Get status. + */ + public function getStatus(): array + { + return \proc_get_status($this->getStream()); + } + + /** + * Get exit code (alias of $this->getStatus()['exitcode']);. + */ + public function getExitCode(): int + { + $handle = $this->getStatus(); + + return $handle['exitcode']; + } + + /** + * Whether the processus have ended successfully. + * + * @return bool + */ + public function isSuccessful(): bool + { + return 0 === $this->getExitCode(); + } + + /** + * Terminate the process. + * + * Valid signals are self::SIGHUP, SIGINT, SIGQUIT, SIGABRT, SIGKILL, + * SIGALRM and SIGTERM. + */ + public function terminate(int $signal = self::SIGTERM): bool + { + return \proc_terminate($this->getStream(), $signal); + } + + /** + * Set command name. + */ + protected function setCommand(string $command) + { + $old = $this->_command; + $this->_command = \escapeshellcmd($command); + + return $old; + } + + /** + * Get command name. + */ + public function getCommand() + { + return $this->_command; + } + + /** + * Set command options. + */ + protected function setOptions(array $options): array + { + foreach ($options as &$option) { + $option = \escapeshellarg($option); + } + + $old = $this->_options; + $this->_options = $options; + + return $old; + } + + /** + * Get options. + */ + public function getOptions(): array + { + return $this->_options; + } + + /** + * Get command-line. + */ + public function getCommandLine(): string + { + $out = $this->getCommand(); + + foreach ($this->getOptions() as $key => $value) { + if (!\is_int($key)) { + $out .= ' '.$key.'='.$value; + } else { + $out .= ' '.$value; + } + } + + return $out; + } + + /** + * Set current working directory of the process. + */ + protected function setCwd(string $cwd) + { + $old = $this->_cwd; + $this->_cwd = $cwd; + + return $old; + } + + /** + * Get current working directory of the process. + */ + public function getCwd(): string + { + return $this->_cwd; + } + + /** + * Set environment of the process. + */ + protected function setEnvironment(array $environment) + { + $old = $this->_environment; + $this->_environment = $environment; + + return $old; + } + + /** + * Get environment of the process. + */ + public function getEnvironment() + { + return $this->_environment; + } + + /** + * Set timeout of the process. + */ + public function setTimeout(int $timeout) + { + $old = $this->_timeout; + $this->_timeout = $timeout; + + return $old; + } + + /** + * Get timeout of the process. + */ + public function getTimeout(): int + { + return $this->_timeout; + } + + /** + * Set process title. + */ + public static function setTitle(string $title) + { + \cli_set_process_title($title); + } + + /** + * Get process title. + */ + public static function getTitle() + { + return \cli_get_process_title(); + } + + /** + * Found the place of a binary. + */ + public static function locate(string $binary) + { + if (isset($_ENV['PATH'])) { + $separator = ':'; + $path = &$_ENV['PATH']; + } elseif (isset($_SERVER['PATH'])) { + $separator = ':'; + $path = &$_SERVER['PATH']; + } elseif (isset($_SERVER['Path'])) { + $separator = ';'; + $path = &$_SERVER['Path']; + } else { + return null; + } + + foreach (\explode($separator, $path) as $directory) { + if (true === \file_exists($out = $directory.\DIRECTORY_SEPARATOR.$binary)) { + return $out; + } + } + + return null; + } + + /** + * Quick process execution. + * Returns only the STDOUT. + */ + public static function execute(string $commandLine, bool $escape = true): string + { + if (true === $escape) { + $commandLine = \escapeshellcmd($commandLine); + } + + return \rtrim(\shell_exec($commandLine) ?? ''); + } +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/ConsoleTput.php b/vendor/psy/psysh/src/Readline/Hoa/ConsoleTput.php new file mode 100644 index 0000000000..3b13689d47 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/ConsoleTput.php @@ -0,0 +1,841 @@ +parse($terminfo); + + return; + } + + /** + * Parse. + */ + protected function parse(string $terminfo): array + { + if (!\file_exists($terminfo)) { + throw new ConsoleException('Terminfo file %s does not exist.', 0, $terminfo); + } + + $data = \file_get_contents($terminfo); + $length = \strlen($data); + $out = ['file' => $terminfo]; + + $headers = [ + 'data_size' => $length, + 'header_size' => 12, + 'magic_number' => (\ord($data[1]) << 8) | \ord($data[0]), + 'names_size' => (\ord($data[3]) << 8) | \ord($data[2]), + 'bool_count' => (\ord($data[5]) << 8) | \ord($data[4]), + 'number_count' => (\ord($data[7]) << 8) | \ord($data[6]), + 'string_count' => (\ord($data[9]) << 8) | \ord($data[8]), + 'string_table_size' => (\ord($data[11]) << 8) | \ord($data[10]), + ]; + $out['headers'] = $headers; + + // Names. + $i = $headers['header_size']; + $nameAndDescription = \explode('|', \substr($data, $i, $headers['names_size'] - 1)); + $out['name'] = $nameAndDescription[0]; + $out['description'] = $nameAndDescription[1]; + + // Booleans. + $i += $headers['names_size']; + $booleans = []; + $booleanNames = &static::$_booleans; + + for ( + $e = 0, $max = $i + $headers['bool_count']; + $i < $max; + ++$e, ++$i + ) { + $booleans[$booleanNames[$e]] = 1 === \ord($data[$i]); + } + + $out['booleans'] = $booleans; + + // Numbers. + if (1 === ($i % 2)) { + ++$i; + } + + $numbers = []; + $numberNames = &static::$_numbers; + + for ( + $e = 0, $max = $i + $headers['number_count'] * 2; + $i < $max; + ++$e, $i += 2 + ) { + $name = $numberNames[$e]; + $data_i0 = \ord($data[$i]); + $data_i1 = \ord($data[$i + 1]); + + if ($data_i1 === 255 && $data_i0 === 255) { + $numbers[$name] = -1; + } else { + $numbers[$name] = ($data_i1 << 8) | $data_i0; + } + } + + $out['numbers'] = $numbers; + + // Strings. + $strings = []; + $stringNames = &static::$_strings; + $ii = $i + $headers['string_count'] * 2; + + for ( + $e = 0, $max = $ii; + $i < $max; + ++$e, $i += 2 + ) { + $name = $stringNames[$e]; + $data_i0 = \ord($data[$i]); + $data_i1 = \ord($data[$i + 1]); + + if ($data_i1 === 255 && $data_i0 === 255) { + continue; + } + + $a = ($data_i1 << 8) | $data_i0; + $strings[$name] = $a; + + if (65534 === $a) { + continue; + } + + $b = $ii + $a; + $c = $b; + + while ($c < $length && \ord($data[$c])) { + $c++; + } + + $value = \substr($data, $b, $c - $b); + $strings[$name] = false !== $value ? $value : null; + } + + $out['strings'] = $strings; + + return $this->_informations = $out; + } + + /** + * Get all informations. + */ + public function getInformations(): array + { + return $this->_informations; + } + + /** + * Get a boolean value. + */ + public function has(string $boolean): bool + { + if (!isset($this->_informations['booleans'][$boolean])) { + return false; + } + + return $this->_informations['booleans'][$boolean]; + } + + /** + * Get a number value. + */ + public function count(string $number): int + { + if (!isset($this->_informations['numbers'][$number])) { + return 0; + } + + return $this->_informations['numbers'][$number]; + } + + /** + * Get a string value. + */ + public function get(string $string) + { + if (!isset($this->_informations['strings'][$string])) { + return null; + } + + return $this->_informations['strings'][$string]; + } + + /** + * Get current term profile. + */ + public static function getTerm(): string + { + return + isset($_SERVER['TERM']) && !empty($_SERVER['TERM']) + ? $_SERVER['TERM'] + : (\defined('PHP_WINDOWS_VERSION_PLATFORM') ? 'windows-ansi' : 'xterm'); + } + + /** + * Get pathname to the current terminfo. + */ + public static function getTerminfo($term = null): string + { + $paths = []; + + if (isset($_SERVER['TERMINFO'])) { + $paths[] = $_SERVER['TERMINFO']; + } + + if (isset($_SERVER['HOME'])) { + $paths[] = $_SERVER['HOME'].\DIRECTORY_SEPARATOR.'.terminfo'; + } + + if (isset($_SERVER['TERMINFO_DIRS'])) { + foreach (\explode(':', $_SERVER['TERMINFO_DIRS']) as $path) { + $paths[] = $path; + } + } + + $paths[] = '/usr/share/terminfo'; + $paths[] = '/usr/share/lib/terminfo'; + $paths[] = '/lib/terminfo'; + $paths[] = '/usr/lib/terminfo'; + $paths[] = '/usr/local/share/terminfo'; + $paths[] = '/usr/local/share/lib/terminfo'; + $paths[] = '/usr/local/lib/terminfo'; + $paths[] = '/usr/local/ncurses/lib/terminfo'; + $paths[] = 'hoa://Library/Terminfo'; + + $term = $term ?: static::getTerm(); + $fileHexa = \dechex(\ord($term[0])).\DIRECTORY_SEPARATOR.$term; + $fileAlpha = $term[0].\DIRECTORY_SEPARATOR.$term; + $pathname = null; + + foreach ($paths as $path) { + if (\file_exists($_ = $path.\DIRECTORY_SEPARATOR.$fileHexa) || + \file_exists($_ = $path.\DIRECTORY_SEPARATOR.$fileAlpha)) { + $pathname = $_; + + break; + } + } + + if (null === $pathname && 'xterm' !== $term) { + return static::getTerminfo('xterm'); + } + + return $pathname ?? ''; + } + + /** + * Check whether all required terminfo capabilities are defined. + */ + public static function isSupported(): bool + { + if (static::getTerminfo() === '') { + return false; + } + + $requiredVars = [ + 'clear_screen', + 'clr_bol', + 'clr_eol', + 'clr_eos', + 'initialize_color', + 'parm_down_cursor', + 'parm_index', + 'parm_left_cursor', + 'parm_right_cursor', + 'parm_rindex', + 'parm_up_cursor', + 'user6', + 'user7', + ]; + + $tput = new static(); + + foreach ($requiredVars as $var) { + if ($tput->get($var) === null) { + return false; + } + } + + return true; + } +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/ConsoleWindow.php b/vendor/psy/psysh/src/Readline/Hoa/ConsoleWindow.php new file mode 100644 index 0000000000..4ebd5cbec4 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/ConsoleWindow.php @@ -0,0 +1,529 @@ +writeAll("\033[8;".$y.';'.$x.'t'); + + return; + } + + /** + * Get current size (x and y) of the window. + */ + public static function getSize(): array + { + if (\defined('PHP_WINDOWS_VERSION_PLATFORM')) { + $modecon = \explode("\n", \ltrim(ConsoleProcessus::execute('mode con'))); + + $_y = \trim($modecon[2]); + \preg_match('#[^:]+:\s*([0-9]+)#', $_y, $matches); + $y = (int) $matches[1]; + + $_x = \trim($modecon[3]); + \preg_match('#[^:]+:\s*([0-9]+)#', $_x, $matches); + $x = (int) $matches[1]; + + return [ + 'x' => $x, + 'y' => $y, + ]; + } + + $term = ''; + + if (isset($_SERVER['TERM'])) { + $term = 'TERM="'.$_SERVER['TERM'].'" '; + } + + $command = $term.'tput cols && '.$term.'tput lines'; + $tput = Processus::execute($command, false); + + if (!empty($tput)) { + list($x, $y) = \explode("\n", $tput); + + return [ + 'x' => (int) $x, + 'y' => (int) $y, + ]; + } + + // DECSLPP. + Console::getOutput()->writeAll("\033[18t"); + + $input = Console::getInput(); + + // Read \033[8;y;xt. + $input->read(4); // skip \033, [, 8 and ;. + + $x = null; + $y = null; + $handle = &$y; + + while (true) { + $char = $input->readCharacter(); + + switch ($char) { + case ';': + $handle = &$x; + + break; + + case 't': + break 2; + + default: + if (false === \ctype_digit($char)) { + break 2; + } + + $handle .= $char; + } + } + + if (null === $x || null === $y) { + return [ + 'x' => 0, + 'y' => 0, + ]; + } + + return [ + 'x' => (int) $x, + 'y' => (int) $y, + ]; + } + + /** + * Move to X and Y (in pixels). + */ + public static function moveTo(int $x, int $y) + { + if (\defined('PHP_WINDOWS_VERSION_PLATFORM')) { + return; + } + + // DECSLPP. + Console::getOutput()->writeAll("\033[3;".$x.';'.$y.'t'); + + return; + } + + /** + * Get current position (x and y) of the window (in pixels). + */ + public static function getPosition(): array + { + if (\defined('PHP_WINDOWS_VERSION_PLATFORM')) { + return ['x' => 0, 'y' => 0]; + } + + // DECSLPP. + Console::getOutput()->writeAll("\033[13t"); + + $input = Console::getInput(); + + // Read \033[3;x;yt. + $input->read(4); // skip \033, [, 3 and ;. + + $x = null; + $y = null; + $handle = &$x; + + while (true) { + $char = $input->readCharacter(); + + switch ($char) { + case ';': + $handle = &$y; + + break; + + case 't': + break 2; + + default: + $handle .= $char; + } + } + + return [ + 'x' => (int) $x, + 'y' => (int) $y, + ]; + } + + /** + * Scroll whole page. + * Directions can be: + * • u, up, ↑ : scroll whole page up; + * • d, down, ↓ : scroll whole page down. + * Directions can be concatenated by a single space. + */ + public static function scroll(string $directions, int $repeat = 1) + { + if (\defined('PHP_WINDOWS_VERSION_PLATFORM')) { + return; + } + + if (1 > $repeat) { + return; + } elseif (1 === $repeat) { + $handle = \explode(' ', $directions); + } else { + $handle = \explode(' ', $directions, 1); + } + + $tput = Console::getTput(); + $count = ['up' => 0, 'down' => 0]; + + foreach ($handle as $direction) { + switch ($direction) { + case 'u': + case 'up': + case '↑': + ++$count['up']; + + break; + + case 'd': + case 'down': + case '↓': + ++$count['down']; + + break; + } + } + + $output = Console::getOutput(); + + if (0 < $count['up']) { + $output->writeAll( + \str_replace( + '%p1%d', + $count['up'] * $repeat, + $tput->get('parm_index') + ) + ); + } + + if (0 < $count['down']) { + $output->writeAll( + \str_replace( + '%p1%d', + $count['down'] * $repeat, + $tput->get('parm_rindex') + ) + ); + } + + return; + } + + /** + * Minimize the window. + */ + public static function minimize() + { + if (\defined('PHP_WINDOWS_VERSION_PLATFORM')) { + return; + } + + // DECSLPP. + Console::getOutput()->writeAll("\033[2t"); + + return; + } + + /** + * Restore the window (de-minimize). + */ + public static function restore() + { + if (\defined('PHP_WINDOWS_VERSION_PLATFORM')) { + return; + } + + Console::getOutput()->writeAll("\033[1t"); + + return; + } + + /** + * Raise the window to the front of the stacking order. + */ + public static function raise() + { + if (\defined('PHP_WINDOWS_VERSION_PLATFORM')) { + return; + } + + Console::getOutput()->writeAll("\033[5t"); + + return; + } + + /** + * Lower the window to the bottom of the stacking order. + */ + public static function lower() + { + if (\defined('PHP_WINDOWS_VERSION_PLATFORM')) { + return; + } + + Console::getOutput()->writeAll("\033[6t"); + + return; + } + + /** + * Set title. + */ + public static function setTitle(string $title) + { + if (\defined('PHP_WINDOWS_VERSION_PLATFORM')) { + return; + } + + // DECSLPP. + Console::getOutput()->writeAll("\033]0;".$title."\033\\"); + + return; + } + + /** + * Get title. + */ + public static function getTitle() + { + if (\defined('PHP_WINDOWS_VERSION_PLATFORM')) { + return null; + } + + // DECSLPP. + Console::getOutput()->writeAll("\033[21t"); + + $input = Console::getInput(); + $read = [$input->getStream()->getStream()]; + $write = []; + $except = []; + $out = null; + + if (0 === \stream_select($read, $write, $except, 0, 50000)) { + return $out; + } + + // Read \033]l\033\ + $input->read(3); // skip \033, ] and l. + + while (true) { + $char = $input->readCharacter(); + + if ("\033" === $char) { + $chaar = $input->readCharacter(); + + if ('\\' === $chaar) { + break; + } + + $char .= $chaar; + } + + $out .= $char; + } + + return $out; + } + + /** + * Get label. + */ + public static function getLabel() + { + if (\defined('PHP_WINDOWS_VERSION_PLATFORM')) { + return null; + } + + // DECSLPP. + Console::getOutput()->writeAll("\033[20t"); + + $input = Console::getInput(); + $read = [$input->getStream()->getStream()]; + $write = []; + $except = []; + $out = null; + + if (0 === \stream_select($read, $write, $except, 0, 50000)) { + return $out; + } + + // Read \033]L<label>\033\ + $input->read(3); // skip \033, ] and L. + + while (true) { + $char = $input->readCharacter(); + + if ("\033" === $char) { + $chaar = $input->readCharacter(); + + if ('\\' === $chaar) { + break; + } + + $char .= $chaar; + } + + $out .= $char; + } + + return $out; + } + + /** + * Refresh the window. + */ + public static function refresh() + { + if (\defined('PHP_WINDOWS_VERSION_PLATFORM')) { + return; + } + + // DECSLPP. + Console::getOutput()->writeAll("\033[7t"); + + return; + } + + /** + * Set clipboard value. + */ + public static function copy(string $data) + { + if (\defined('PHP_WINDOWS_VERSION_PLATFORM')) { + return; + } + + $out = "\033]52;;".\base64_encode($data)."\033\\"; + $output = Console::getOutput(); + $considerMultiplexer = $output->considerMultiplexer(true); + + $output->writeAll($out); + $output->considerMultiplexer($considerMultiplexer); + + return; + } +} + +/* + * Advanced interaction. + */ +Console::advancedInteraction(); + +/* + * Event. + */ +if (\function_exists('pcntl_signal')) { + ConsoleWindow::getInstance(); + \pcntl_signal( + \SIGWINCH, + function () { + static $_window = null; + + if (null === $_window) { + $_window = ConsoleWindow::getInstance(); + } + + Event::notify( + 'hoa://Event/Console/Window:resize', + $_window, + new EventBucket([ + 'size' => ConsoleWindow::getSize(), + ]) + ); + } + ); +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/Event.php b/vendor/psy/psysh/src/Readline/Hoa/Event.php new file mode 100644 index 0000000000..bb08f328ab --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/Event.php @@ -0,0 +1,193 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Events are asynchronous at registration, anonymous at use (until we + * receive a bucket) and useful to largely spread data through components + * without any known connection between them. + */ +class Event +{ + /** + * Event ID key. + */ + const KEY_EVENT = 0; + + /** + * Source object key. + */ + const KEY_SOURCE = 1; + + /** + * Static register of all observable objects, i.e. `Hoa\Event\Source` + * object, i.e. object that can send event. + */ + private static $_register = []; + + /** + * Collection of callables, i.e. observer objects. + */ + protected $_callable = []; + + /** + * Privatize the constructor. + */ + private function __construct() + { + return; + } + + /** + * Manage multiton of events, with the principle of asynchronous + * attachments. + */ + public static function getEvent(string $eventId): self + { + if (!isset(self::$_register[$eventId][self::KEY_EVENT])) { + self::$_register[$eventId] = [ + self::KEY_EVENT => new self(), + self::KEY_SOURCE => null, + ]; + } + + return self::$_register[$eventId][self::KEY_EVENT]; + } + + /** + * Declares a new object in the observable collection. + * Note: Hoa's libraries use `hoa://Event/anID` for their observable objects. + */ + public static function register(string $eventId, /* Source|string */ $source) + { + if (true === self::eventExists($eventId)) { + throw new EventException('Cannot redeclare an event with the same ID, i.e. the event '.'ID %s already exists.', 0, $eventId); + } + + if (\is_object($source) && !($source instanceof EventSource)) { + throw new EventException('The source must implement \Hoa\Event\Source '.'interface; given %s.', 1, \get_class($source)); + } else { + $reflection = new \ReflectionClass($source); + + if (false === $reflection->implementsInterface('\Psy\Readline\Hoa\EventSource')) { + throw new EventException('The source must implement \Hoa\Event\Source '.'interface; given %s.', 2, $source); + } + } + + if (!isset(self::$_register[$eventId][self::KEY_EVENT])) { + self::$_register[$eventId][self::KEY_EVENT] = new self(); + } + + self::$_register[$eventId][self::KEY_SOURCE] = $source; + } + + /** + * Undeclares an object in the observable collection. + * + * If `$hard` is set to `true, then the source and its attached callables + * will be deleted. + */ + public static function unregister(string $eventId, bool $hard = false) + { + if (false !== $hard) { + unset(self::$_register[$eventId]); + } else { + self::$_register[$eventId][self::KEY_SOURCE] = null; + } + } + + /** + * Attach an object to an event. + * + * It can be a callable or an accepted callable form (please, see the + * `Hoa\Consistency\Xcallable` class). + */ + public function attach($callable): self + { + $callable = Xcallable::from($callable); + $this->_callable[$callable->getHash()] = $callable; + + return $this; + } + + /** + * Detaches an object to an event. + * + * Please see `self::attach` method. + */ + public function detach($callable): self + { + unset($this->_callable[Xcallable::from($callable)->getHash()]); + + return $this; + } + + /** + * Checks if at least one callable is attached to an event. + */ + public function isListened(): bool + { + return !empty($this->_callable); + } + + /** + * Notifies, i.e. send data to observers. + */ + public static function notify(string $eventId, EventSource $source, EventBucket $data) + { + if (false === self::eventExists($eventId)) { + throw new EventException('Event ID %s does not exist, cannot send notification.', 3, $eventId); + } + + $data->setSource($source); + $event = self::getEvent($eventId); + + foreach ($event->_callable as $callable) { + $callable($data); + } + } + + /** + * Checks whether an event exists. + */ + public static function eventExists(string $eventId): bool + { + return + \array_key_exists($eventId, self::$_register) && + self::$_register[$eventId][self::KEY_SOURCE] !== null; + } +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/EventBucket.php b/vendor/psy/psysh/src/Readline/Hoa/EventBucket.php new file mode 100644 index 0000000000..37d3ca15a9 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/EventBucket.php @@ -0,0 +1,109 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * This class is the object which is transmit through event channels. + */ +class EventBucket +{ + /** + * The source object (must be of kind `Hoa\Event\Source`). + */ + protected $_source = null; + + /** + * Data attached to the bucket. + */ + protected $_data = null; + + /** + * Allocates a new bucket with various data attached to it. + */ + public function __construct($data = null) + { + $this->setData($data); + + return; + } + + /** + * Sends this object on the event channel. + */ + public function send(string $eventId, EventSource $source) + { + return Event::notify($eventId, $source, $this); + } + + /** + * Sets a new source. + */ + public function setSource(EventSource $source) + { + $old = $this->_source; + $this->_source = $source; + + return $old; + } + + /** + * Returns the source. + */ + public function getSource() + { + return $this->_source; + } + + /** + * Sets new data. + */ + public function setData($data) + { + $old = $this->_data; + $this->_data = $data; + + return $old; + } + + /** + * Returns the data. + */ + public function getData() + { + return $this->_data; + } +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/EventException.php b/vendor/psy/psysh/src/Readline/Hoa/EventException.php new file mode 100644 index 0000000000..9517d9c968 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/EventException.php @@ -0,0 +1,44 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Extending the `Hoa\Exception\Exception` class. + */ +class EventException extends Exception +{ +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/EventListenable.php b/vendor/psy/psysh/src/Readline/Hoa/EventListenable.php new file mode 100644 index 0000000000..cefd3e6aa0 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/EventListenable.php @@ -0,0 +1,48 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Each object which is listenable must implement this interface. + */ +interface EventListenable extends EventSource +{ + /** + * Attaches a callable to a listenable component. + */ + public function on(string $listenerId, $callable): self; +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/EventListener.php b/vendor/psy/psysh/src/Readline/Hoa/EventListener.php new file mode 100644 index 0000000000..8e877e610d --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/EventListener.php @@ -0,0 +1,137 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * A contrario of events, listeners are synchronous, identified at use and + * useful for close interactions between one or some components. + */ +class EventListener +{ + /** + * Source of listener (for `Hoa\Event\Bucket`). + */ + protected $_source = null; + + /** + * All listener IDs and associated listeners. + */ + protected $_callables = []; + + /** + * Build a listener. + */ + public function __construct(EventListenable $source, array $ids) + { + $this->_source = $source; + $this->addIds($ids); + + return; + } + + /** + * Adds acceptable ID (or reset). + */ + public function addIds(array $ids) + { + foreach ($ids as $id) { + $this->_callables[$id] = []; + } + } + + /** + * Attaches a callable to a listenable component. + */ + public function attach(string $listenerId, $callable): self + { + if (false === $this->listenerExists($listenerId)) { + throw new EventException('Cannot listen %s because it is not defined.', 0, $listenerId); + } + + $callable = Xcallable::from($callable); + $this->_callables[$listenerId][$callable->getHash()] = $callable; + + return $this; + } + + /** + * Detaches a callable from a listenable component. + */ + public function detach(string $listenerId, $callable): self + { + unset($this->_callables[$listenerId][Xcallable::from($callable)->getHash()]); + + return $this; + } + + /** + * Detaches all callables from a listenable component. + */ + public function detachAll(string $listenerId): self + { + unset($this->_callables[$listenerId]); + + return $this; + } + + /** + * Checks if a listener exists. + */ + public function listenerExists(string $listenerId): bool + { + return \array_key_exists($listenerId, $this->_callables); + } + + /** + * Sends/fires a bucket to a listener. + */ + public function fire(string $listenerId, EventBucket $data): array + { + if (false === $this->listenerExists($listenerId)) { + throw new EventException('Cannot fire on %s because it is not defined.', 1, $listenerId); + } + + $data->setSource($this->_source); + $out = []; + + foreach ($this->_callables[$listenerId] as $callable) { + $out[] = $callable($data); + } + + return $out; + } +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/EventListens.php b/vendor/psy/psysh/src/Readline/Hoa/EventListens.php new file mode 100644 index 0000000000..41f1172b56 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/EventListens.php @@ -0,0 +1,83 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Implementation of a listener. + */ +trait EventListens +{ + /** + * Listener instance of type `Hoa\Event\Listener`. + */ + protected $_listener = null; + + /** + * Attaches a callable to a listenable component. + */ + public function on(string $listenerId, $callable): EventListenable + { + $listener = $this->getListener(); + + if (null === $listener) { + throw new EventException('Cannot attach a callable to the listener %s because '.'it has not been initialized yet.', 0, static::class); + } + + $listener->attach($listenerId, $callable); + + return $this; + } + + /** + * Sets a new listener. + */ + protected function setListener(EventListener $listener) + { + $old = $this->_listener; + $this->_listener = $listener; + + return $old; + } + + /** + * Returns the listener. + */ + protected function getListener() + { + return $this->_listener; + } +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/EventSource.php b/vendor/psy/psysh/src/Readline/Hoa/EventSource.php new file mode 100644 index 0000000000..3a495d4b69 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/EventSource.php @@ -0,0 +1,44 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Each object which is listenable must implement this interface. + */ +interface EventSource +{ +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/Exception.php b/vendor/psy/psysh/src/Readline/Hoa/Exception.php new file mode 100644 index 0000000000..e0b8bc8ca7 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/Exception.php @@ -0,0 +1,79 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Each exception must extend `Hoa\Exception\Exception`. + */ +class Exception extends ExceptionIdle implements EventSource +{ + /** + * Allocates a new exception. + * + * An exception is built with a formatted message, a code (an ID), and an + * array that contains the list of formatted string for the message. If + * chaining, a previous exception can be added. + */ + public function __construct( + string $message, + int $code = 0, + $arguments = [], + \Throwable $previous = null + ) { + parent::__construct($message, $code, $arguments, $previous); + + if (false === Event::eventExists('hoa://Event/Exception')) { + Event::register('hoa://Event/Exception', __CLASS__); + } + + $this->send(); + + return; + } + + /** + * Sends the exception on `hoa://Event/Exception`. + */ + public function send() + { + Event::notify( + 'hoa://Event/Exception', + $this, + new EventBucket($this) + ); + } +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/ExceptionIdle.php b/vendor/psy/psysh/src/Readline/Hoa/ExceptionIdle.php new file mode 100644 index 0000000000..1d44c43509 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/ExceptionIdle.php @@ -0,0 +1,267 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * `Hoa\Exception\Idle` is the mother exception class of libraries. The only + * difference between `Hoa\Exception\Idle` and its direct children + * `Hoa\Exception` is that the latter fires events after beeing constructed. + */ +class ExceptionIdle extends \Exception +{ + /** + * Delay processing on arguments. + */ + protected $_tmpArguments = null; + + /** + * List of arguments to format message. + */ + protected $_arguments = null; + + /** + * Backtrace. + */ + protected $_trace = null; + + /** + * Previous exception if any. + */ + protected $_previous = null; + + /** + * Original exception message. + */ + protected $_rawMessage = null; + + /** + * Allocates a new exception. + * + * An exception is built with a formatted message, a code (an ID) and an + * array that contains the list of formatted strings for the message. If + * chaining, we can add a previous exception. + */ + public function __construct( + string $message, + int $code = 0, + $arguments = [], + \Exception $previous = null + ) { + $this->_tmpArguments = $arguments; + parent::__construct($message, $code, $previous); + $this->_rawMessage = $message; + $this->message = @\vsprintf($message, $this->getArguments()); + + return; + } + + /** + * Returns the backtrace. + * + * Do not use `Exception::getTrace` any more. + */ + public function getBacktrace() + { + if (null === $this->_trace) { + $this->_trace = $this->getTrace(); + } + + return $this->_trace; + } + + /** + * Returns the previous exception if any. + * + * Do not use `Exception::getPrevious` any more. + */ + public function getPreviousThrow() + { + if (null === $this->_previous) { + $this->_previous = $this->getPrevious(); + } + + return $this->_previous; + } + + /** + * Returns the arguments of the message. + */ + public function getArguments() + { + if (null === $this->_arguments) { + $arguments = $this->_tmpArguments; + + if (!\is_array($arguments)) { + $arguments = [$arguments]; + } + + foreach ($arguments as &$value) { + if (null === $value) { + $value = '(null)'; + } + } + + $this->_arguments = $arguments; + unset($this->_tmpArguments); + } + + return $this->_arguments; + } + + /** + * Returns the raw message. + */ + public function getRawMessage(): string + { + return $this->_rawMessage; + } + + /** + * Returns the message already formatted. + */ + public function getFormattedMessage(): string + { + return $this->getMessage(); + } + + /** + * Returns the source of the exception (class, method, function, main etc.). + */ + public function getFrom(): string + { + $trace = $this->getBacktrace(); + $from = '{main}'; + + if (!empty($trace)) { + $t = $trace[0]; + $from = ''; + + if (isset($t['class'])) { + $from .= $t['class'].'::'; + } + + if (isset($t['function'])) { + $from .= $t['function'].'()'; + } + } + + return $from; + } + + /** + * Raises an exception as a string. + */ + public function raise(bool $includePrevious = false): string + { + $message = $this->getFormattedMessage(); + $trace = $this->getBacktrace(); + $file = '/dev/null'; + $line = -1; + $pre = $this->getFrom(); + + if (!empty($trace)) { + $file = $trace['file'] ?? null; + $line = $trace['line'] ?? null; + } + + $pre .= ': '; + + try { + $out = + $pre.'('.$this->getCode().') '.$message."\n". + 'in '.$this->getFile().' at line '. + $this->getLine().'.'; + } catch (\Exception $e) { + $out = + $pre.'('.$this->getCode().') '.$message."\n". + 'in '.$file.' around line '.$line.'.'; + } + + if (true === $includePrevious && + null !== $previous = $this->getPreviousThrow()) { + $out .= + "\n\n".' ⬇'."\n\n". + 'Nested exception ('.\get_class($previous).'):'."\n". + ($previous instanceof self + ? $previous->raise(true) + : $previous->getMessage()); + } + + return $out; + } + + /** + * Catches uncaught exception (only `Hoa\Exception\Idle` and children). + */ + public static function uncaught(\Throwable $exception) + { + if (!($exception instanceof self)) { + throw $exception; + } + + while (0 < \ob_get_level()) { + \ob_end_flush(); + } + + echo 'Uncaught exception ('.\get_class($exception).'):'."\n". + $exception->raise(true); + } + + /** + * String representation of object. + */ + public function __toString(): string + { + return $this->raise(); + } + + /** + * Enables uncaught exception handler. + * + * This is restricted to Hoa's exceptions only. + */ + public static function enableUncaughtHandler(bool $enable = true) + { + if (false === $enable) { + return \restore_exception_handler(); + } + + return \set_exception_handler(function ($exception) { + return self::uncaught($exception); + }); + } +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/File.php b/vendor/psy/psysh/src/Readline/Hoa/File.php new file mode 100644 index 0000000000..bdfd947897 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/File.php @@ -0,0 +1,278 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Class \Hoa\File. + * + * File handler. + */ +abstract class File extends FileGeneric implements StreamBufferable, StreamLockable, StreamPointable +{ + /** + * Open for reading only; place the file pointer at the beginning of the + * file. + */ + const MODE_READ = 'rb'; + + /** + * Open for reading and writing; place the file pointer at the beginning of + * the file. + */ + const MODE_READ_WRITE = 'r+b'; + + /** + * Open for writing only; place the file pointer at the beginning of the + * file and truncate the file to zero length. If the file does not exist, + * attempt to create it. + */ + const MODE_TRUNCATE_WRITE = 'wb'; + + /** + * Open for reading and writing; place the file pointer at the beginning of + * the file and truncate the file to zero length. If the file does not + * exist, attempt to create it. + */ + const MODE_TRUNCATE_READ_WRITE = 'w+b'; + + /** + * Open for writing only; place the file pointer at the end of the file. If + * the file does not exist, attempt to create it. + */ + const MODE_APPEND_WRITE = 'ab'; + + /** + * Open for reading and writing; place the file pointer at the end of the + * file. If the file does not exist, attempt to create it. + */ + const MODE_APPEND_READ_WRITE = 'a+b'; + + /** + * Create and open for writing only; place the file pointer at the beginning + * of the file. If the file already exits, the fopen() call with fail by + * returning false and generating an error of level E_WARNING. If the file + * does not exist, attempt to create it. This is equivalent to specifying + * O_EXCL | O_CREAT flags for the underlying open(2) system call. + */ + const MODE_CREATE_WRITE = 'xb'; + + /** + * Create and open for reading and writing; place the file pointer at the + * beginning of the file. If the file already exists, the fopen() call with + * fail by returning false and generating an error of level E_WARNING. If + * the file does not exist, attempt to create it. This is equivalent to + * specifying O_EXCL | O_CREAT flags for the underlying open(2) system call. + */ + const MODE_CREATE_READ_WRITE = 'x+b'; + + /** + * Open a file. + */ + public function __construct( + string $streamName, + string $mode, + string $context = null, + bool $wait = false + ) { + $this->setMode($mode); + + switch ($streamName) { + case '0': + $streamName = 'php://stdin'; + + break; + + case '1': + $streamName = 'php://stdout'; + + break; + + case '2': + $streamName = 'php://stderr'; + + break; + + default: + if (true === \ctype_digit($streamName)) { + if (\PHP_VERSION_ID >= 50306) { + $streamName = 'php://fd/'.$streamName; + } else { + throw new FileException('You need PHP5.3.6 to use a file descriptor '.'other than 0, 1 or 2 (tried %d with PHP%s).', 0, [$streamName, \PHP_VERSION]); + } + } + } + + parent::__construct($streamName, $context, $wait); + + return; + } + + /** + * Open the stream and return the associated resource. + */ + protected function &_open(string $streamName, StreamContext $context = null) + { + if (\substr($streamName, 0, 4) === 'file' && + false === \is_dir(\dirname($streamName))) { + throw new FileException('Directory %s does not exist. Could not open file %s.', 1, [\dirname($streamName), \basename($streamName)]); + } + + if (null === $context) { + if (false === $out = @\fopen($streamName, $this->getMode(), true)) { + throw new FileException('Failed to open stream %s.', 2, $streamName); + } + + return $out; + } + + $out = @\fopen( + $streamName, + $this->getMode(), + true, + $context->getContext() + ); + + if (false === $out) { + throw new FileException('Failed to open stream %s.', 3, $streamName); + } + + return $out; + } + + /** + * Close the current stream. + */ + protected function _close(): bool + { + return @\fclose($this->getStream()); + } + + /** + * Start a new buffer. + * The callable acts like a light filter. + */ + public function newBuffer($callable = null, int $size = null): int + { + $this->setStreamBuffer($size); + + // @TODO manage $callable as a filter? + + return 1; + } + + /** + * Flush the output to a stream. + */ + public function flush(): bool + { + return \fflush($this->getStream()); + } + + /** + * Delete buffer. + */ + public function deleteBuffer(): bool + { + return $this->disableStreamBuffer(); + } + + /** + * Get bufffer level. + */ + public function getBufferLevel(): int + { + return 1; + } + + /** + * Get buffer size. + */ + public function getBufferSize(): int + { + return $this->getStreamBufferSize(); + } + + /** + * Portable advisory locking. + */ + public function lock(int $operation): bool + { + return \flock($this->getStream(), $operation); + } + + /** + * Rewind the position of a stream pointer. + */ + public function rewind(): bool + { + return \rewind($this->getStream()); + } + + /** + * Seek on a stream pointer. + */ + public function seek(int $offset, int $whence = StreamPointable::SEEK_SET): int + { + return \fseek($this->getStream(), $offset, $whence); + } + + /** + * Get the current position of the stream pointer. + */ + public function tell(): int + { + $stream = $this->getStream(); + + if (null === $stream) { + return 0; + } + + return \ftell($stream); + } + + /** + * Create a file. + */ + public static function create(string $name) + { + if (\file_exists($name)) { + return true; + } + + return \touch($name); + } +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/FileDirectory.php b/vendor/psy/psysh/src/Readline/Hoa/FileDirectory.php new file mode 100644 index 0000000000..e7191410e9 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/FileDirectory.php @@ -0,0 +1,221 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Class \Hoa\File\Directory. + * + * Directory handler. + */ +class FileDirectory extends FileGeneric +{ + /** + * Open for reading. + */ + const MODE_READ = 'rb'; + + /** + * Open for reading and writing. If the directory does not exist, attempt to + * create it. + */ + const MODE_CREATE = 'xb'; + + /** + * Open for reading and writing. If the directory does not exist, attempt to + * create it recursively. + */ + const MODE_CREATE_RECURSIVE = 'xrb'; + + /** + * Open a directory. + */ + public function __construct( + string $streamName, + string $mode = self::MODE_READ, + string $context = null, + bool $wait = false + ) { + $this->setMode($mode); + parent::__construct($streamName, $context, $wait); + + return; + } + + /** + * Open the stream and return the associated resource. + */ + protected function &_open(string $streamName, StreamContext $context = null) + { + if (false === \is_dir($streamName)) { + if ($this->getMode() === self::MODE_READ) { + throw new FileDoesNotExistException('Directory %s does not exist.', 0, $streamName); + } else { + self::create( + $streamName, + $this->getMode(), + null !== $context + ? $context->getContext() + : null + ); + } + } + + $out = null; + + return $out; + } + + /** + * Close the current stream. + */ + protected function _close(): bool + { + return true; + } + + /** + * Recursive copy of a directory. + */ + public function copy(string $to, bool $force = StreamTouchable::DO_NOT_OVERWRITE): bool + { + if (empty($to)) { + throw new FileException('The destination path (to copy) is empty.', 1); + } + + $from = $this->getStreamName(); + $fromLength = \strlen($from) + 1; + $finder = new FileFinder(); + $finder->in($from); + + self::create($to, self::MODE_CREATE_RECURSIVE); + + foreach ($finder as $file) { + $relative = \substr($file->getPathname(), $fromLength); + $_to = $to.\DIRECTORY_SEPARATOR.$relative; + + if (true === $file->isDir()) { + self::create($_to, self::MODE_CREATE); + + continue; + } + + // This is not possible to do `$file->open()->copy(); + // $file->close();` because the file will be opened in read and + // write mode. In a PHAR for instance, this operation is + // forbidden. So a special care must be taken to open file in read + // only mode. + $handle = null; + + if (true === $file->isFile()) { + $handle = new FileRead($file->getPathname()); + } elseif (true === $file->isDir()) { + $handle = new self($file->getPathName()); + } elseif (true === $file->isLink()) { + $handle = new FileLinkRead($file->getPathName()); + } + + if (null !== $handle) { + $handle->copy($_to, $force); + $handle->close(); + } + } + + return true; + } + + /** + * Delete a directory. + */ + public function delete(): bool + { + $from = $this->getStreamName(); + $finder = new FileFinder(); + $finder->in($from) + ->childFirst(); + + foreach ($finder as $file) { + $file->open()->delete(); + $file->close(); + } + + if (null === $this->getStreamContext()) { + return @\rmdir($from); + } + + return @\rmdir($from, $this->getStreamContext()->getContext()); + } + + /** + * Create a directory. + */ + public static function create( + string $name, + string $mode = self::MODE_CREATE_RECURSIVE, + string $context = null + ): bool { + if (true === \is_dir($name)) { + return true; + } + + if (empty($name)) { + return false; + } + + if (null !== $context) { + if (false === StreamContext::contextExists($context)) { + throw new FileException('Context %s was not previously declared, cannot retrieve '.'this context.', 2, $context); + } else { + $context = StreamContext::getInstance($context); + } + } + + if (null === $context) { + return @\mkdir( + $name, + 0755, + self::MODE_CREATE_RECURSIVE === $mode + ); + } + + return @\mkdir( + $name, + 0755, + self::MODE_CREATE_RECURSIVE === $mode, + $context->getContext() + ); + } +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/FileDoesNotExistException.php b/vendor/psy/psysh/src/Readline/Hoa/FileDoesNotExistException.php new file mode 100644 index 0000000000..81599a5de7 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/FileDoesNotExistException.php @@ -0,0 +1,48 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Class \Hoa\File\Exception\FileDoesNotExist. + * + * Extending the \Hoa\File\Exception class. + * + * @license New BSD License + */ +class FileDoesNotExistException extends FileException +{ +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/FileException.php b/vendor/psy/psysh/src/Readline/Hoa/FileException.php new file mode 100644 index 0000000000..0e224c2524 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/FileException.php @@ -0,0 +1,48 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Class \Hoa\File\Exception. + * + * Extending the \Hoa\Exception\Exception class. + * + * @license New BSD License + */ +class FileException extends Exception +{ +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/FileFinder.php b/vendor/psy/psysh/src/Readline/Hoa/FileFinder.php new file mode 100644 index 0000000000..4e7677d79e --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/FileFinder.php @@ -0,0 +1,658 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Class \Hoa\File\Finder. + * + * This class allows to find files easily by using filters and flags. + */ +class FileFinder implements \IteratorAggregate +{ + /** + * SplFileInfo classname. + */ + protected $_splFileInfo = SplFileInfo::class; + + /** + * Paths where to look for. + */ + protected $_paths = []; + + /** + * Max depth in recursion. + */ + protected $_maxDepth = -1; + + /** + * Filters. + */ + protected $_filters = []; + + /** + * Flags. + */ + protected $_flags = -1; + + /** + * Types of files to handle. + */ + protected $_types = []; + + /** + * What comes first: parent or child? + */ + protected $_first = -1; + + /** + * Sorts. + */ + protected $_sorts = []; + + /** + * Initialize. + */ + public function __construct() + { + $this->_flags = IteratorFileSystem::KEY_AS_PATHNAME + | IteratorFileSystem::CURRENT_AS_FILEINFO + | IteratorFileSystem::SKIP_DOTS; + $this->_first = \RecursiveIteratorIterator::SELF_FIRST; + + return; + } + + /** + * Select a directory to scan. + */ + public function in($paths): self + { + if (!\is_array($paths)) { + $paths = [$paths]; + } + + foreach ($paths as $path) { + if (1 === \preg_match('/[\*\?\[\]]/', $path)) { + $iterator = new \CallbackFilterIterator( + new \GlobIterator(\rtrim($path, \DIRECTORY_SEPARATOR)), + function ($current) { + return $current->isDir(); + } + ); + + foreach ($iterator as $fileInfo) { + $this->_paths[] = $fileInfo->getPathname(); + } + } else { + $this->_paths[] = $path; + } + } + + return $this; + } + + /** + * Set max depth for recursion. + */ + public function maxDepth(int $depth): self + { + $this->_maxDepth = $depth; + + return $this; + } + + /** + * Include files in the result. + */ + public function files(): self + { + $this->_types[] = 'file'; + + return $this; + } + + /** + * Include directories in the result. + */ + public function directories(): self + { + $this->_types[] = 'dir'; + + return $this; + } + + /** + * Include links in the result. + */ + public function links(): self + { + $this->_types[] = 'link'; + + return $this; + } + + /** + * Follow symbolink links. + */ + public function followSymlinks(bool $flag = true): self + { + if (true === $flag) { + $this->_flags ^= IteratorFileSystem::FOLLOW_SYMLINKS; + } else { + $this->_flags |= IteratorFileSystem::FOLLOW_SYMLINKS; + } + + return $this; + } + + /** + * Include files that match a regex. + * Example: + * $this->name('#\.php$#');. + */ + public function name(string $regex): self + { + $this->_filters[] = function (\SplFileInfo $current) use ($regex) { + return 0 !== \preg_match($regex, $current->getBasename()); + }; + + return $this; + } + + /** + * Exclude directories that match a regex. + * Example: + * $this->notIn('#^\.(git|hg)$#');. + */ + public function notIn(string $regex): self + { + $this->_filters[] = function (\SplFileInfo $current) use ($regex) { + foreach (\explode(\DIRECTORY_SEPARATOR, $current->getPathname()) as $part) { + if (0 !== \preg_match($regex, $part)) { + return false; + } + } + + return true; + }; + + return $this; + } + + /** + * Include files that respect a certain size. + * The size is a string of the form: + * operator number unit + * where + * • operator could be: <, <=, >, >= or =; + * • number is a positive integer; + * • unit could be: b (default), Kb, Mb, Gb, Tb, Pb, Eb, Zb, Yb. + * Example: + * $this->size('>= 12Kb');. + */ + public function size(string $size): self + { + if (0 === \preg_match('#^(<|<=|>|>=|=)\s*(\d+)\s*((?:[KMGTPEZY])b)?$#', $size, $matches)) { + return $this; + } + + $number = (float) ($matches[2]); + $unit = $matches[3] ?? 'b'; + $operator = $matches[1]; + + switch ($unit) { + case 'b': + break; + + // kilo + case 'Kb': + $number <<= 10; + + break; + + // mega. + case 'Mb': + $number <<= 20; + + break; + + // giga. + case 'Gb': + $number <<= 30; + + break; + + // tera. + case 'Tb': + $number *= 1099511627776; + + break; + + // peta. + case 'Pb': + $number *= 1024 ** 5; + + break; + + // exa. + case 'Eb': + $number *= 1024 ** 6; + + break; + + // zetta. + case 'Zb': + $number *= 1024 ** 7; + + break; + + // yota. + case 'Yb': + $number *= 1024 ** 8; + + break; + } + + $filter = null; + + switch ($operator) { + case '<': + $filter = function (\SplFileInfo $current) use ($number) { + return $current->getSize() < $number; + }; + + break; + + case '<=': + $filter = function (\SplFileInfo $current) use ($number) { + return $current->getSize() <= $number; + }; + + break; + + case '>': + $filter = function (\SplFileInfo $current) use ($number) { + return $current->getSize() > $number; + }; + + break; + + case '>=': + $filter = function (\SplFileInfo $current) use ($number) { + return $current->getSize() >= $number; + }; + + break; + + case '=': + $filter = function (\SplFileInfo $current) use ($number) { + return $current->getSize() === $number; + }; + + break; + } + + $this->_filters[] = $filter; + + return $this; + } + + /** + * Whether we should include dots or not (respectively . and ..). + */ + public function dots(bool $flag = true): self + { + if (true === $flag) { + $this->_flags ^= IteratorFileSystem::SKIP_DOTS; + } else { + $this->_flags |= IteratorFileSystem::SKIP_DOTS; + } + + return $this; + } + + /** + * Include files that are owned by a certain owner. + */ + public function owner(int $owner): self + { + $this->_filters[] = function (\SplFileInfo $current) use ($owner) { + return $current->getOwner() === $owner; + }; + + return $this; + } + + /** + * Format date. + * Date can have the following syntax: + * date + * since date + * until date + * If the date does not have the “ago” keyword, it will be added. + * Example: “42 hours” is equivalent to “since 42 hours” which is equivalent + * to “since 42 hours ago”. + */ + protected function formatDate(string $date, &$operator): int + { + $operator = -1; + + if (0 === \preg_match('#\bago\b#', $date)) { + $date .= ' ago'; + } + + if (0 !== \preg_match('#^(since|until)\b(.+)$#', $date, $matches)) { + $time = \strtotime($matches[2]); + + if ('until' === $matches[1]) { + $operator = 1; + } + } else { + $time = \strtotime($date); + } + + return $time; + } + + /** + * Include files that have been changed from a certain date. + * Example: + * $this->changed('since 13 days');. + */ + public function changed(string $date): self + { + $time = $this->formatDate($date, $operator); + + if (-1 === $operator) { + $this->_filters[] = function (\SplFileInfo $current) use ($time) { + return $current->getCTime() >= $time; + }; + } else { + $this->_filters[] = function (\SplFileInfo $current) use ($time) { + return $current->getCTime() < $time; + }; + } + + return $this; + } + + /** + * Include files that have been modified from a certain date. + * Example: + * $this->modified('since 13 days');. + */ + public function modified(string $date): self + { + $time = $this->formatDate($date, $operator); + + if (-1 === $operator) { + $this->_filters[] = function (\SplFileInfo $current) use ($time) { + return $current->getMTime() >= $time; + }; + } else { + $this->_filters[] = function (\SplFileInfo $current) use ($time) { + return $current->getMTime() < $time; + }; + } + + return $this; + } + + /** + * Add your own filter. + * The callback will receive 3 arguments: $current, $key and $iterator. It + * must return a boolean: true to include the file, false to exclude it. + * Example: + * // Include files that are readable + * $this->filter(function ($current) { + * return $current->isReadable(); + * });. + */ + public function filter($callback): self + { + $this->_filters[] = $callback; + + return $this; + } + + /** + * Sort result by name. + * If \Collator exists (from ext/intl), the $locale argument will be used + * for its constructor. Else, strcmp() will be used. + * Example: + * $this->sortByName('fr_FR');. + */ + public function sortByName(string $locale = 'root'): self + { + if (true === \class_exists('Collator', false)) { + $collator = new \Collator($locale); + + $this->_sorts[] = function (\SplFileInfo $a, \SplFileInfo $b) use ($collator) { + return $collator->compare($a->getPathname(), $b->getPathname()); + }; + } else { + $this->_sorts[] = function (\SplFileInfo $a, \SplFileInfo $b) { + return \strcmp($a->getPathname(), $b->getPathname()); + }; + } + + return $this; + } + + /** + * Sort result by size. + * Example: + * $this->sortBySize();. + */ + public function sortBySize(): self + { + $this->_sorts[] = function (\SplFileInfo $a, \SplFileInfo $b) { + return $a->getSize() < $b->getSize(); + }; + + return $this; + } + + /** + * Add your own sort. + * The callback will receive 2 arguments: $a and $b. Please see the uasort() + * function. + * Example: + * // Sort files by their modified time. + * $this->sort(function ($a, $b) { + * return $a->getMTime() < $b->getMTime(); + * });. + */ + public function sort($callable): self + { + $this->_sorts[] = $callable; + + return $this; + } + + /** + * Child comes first when iterating. + */ + public function childFirst(): self + { + $this->_first = \RecursiveIteratorIterator::CHILD_FIRST; + + return $this; + } + + /** + * Get the iterator. + */ + public function getIterator() + { + $_iterator = new \AppendIterator(); + $types = $this->getTypes(); + + if (!empty($types)) { + $this->_filters[] = function (\SplFileInfo $current) use ($types) { + return \in_array($current->getType(), $types); + }; + } + + $maxDepth = $this->getMaxDepth(); + $splFileInfo = $this->getSplFileInfo(); + + foreach ($this->getPaths() as $path) { + if (1 === $maxDepth) { + $iterator = new \IteratorIterator( + new IteratorRecursiveDirectory( + $path, + $this->getFlags(), + $splFileInfo + ), + $this->getFirst() + ); + } else { + $iterator = new \RecursiveIteratorIterator( + new IteratorRecursiveDirectory( + $path, + $this->getFlags(), + $splFileInfo + ), + $this->getFirst() + ); + + if (1 < $maxDepth) { + $iterator->setMaxDepth($maxDepth - 1); + } + } + + $_iterator->append($iterator); + } + + foreach ($this->getFilters() as $filter) { + $_iterator = new \CallbackFilterIterator( + $_iterator, + $filter + ); + } + + $sorts = $this->getSorts(); + + if (empty($sorts)) { + return $_iterator; + } + + $array = \iterator_to_array($_iterator); + + foreach ($sorts as $sort) { + \uasort($array, $sort); + } + + return new \ArrayIterator($array); + } + + /** + * Set SplFileInfo classname. + */ + public function setSplFileInfo(string $splFileInfo): string + { + $old = $this->_splFileInfo; + $this->_splFileInfo = $splFileInfo; + + return $old; + } + + /** + * Get SplFileInfo classname. + */ + public function getSplFileInfo(): string + { + return $this->_splFileInfo; + } + + /** + * Get all paths. + */ + protected function getPaths(): array + { + return $this->_paths; + } + + /** + * Get max depth. + */ + public function getMaxDepth(): int + { + return $this->_maxDepth; + } + + /** + * Get types. + */ + public function getTypes(): array + { + return $this->_types; + } + + /** + * Get filters. + */ + protected function getFilters(): array + { + return $this->_filters; + } + + /** + * Get sorts. + */ + protected function getSorts(): array + { + return $this->_sorts; + } + + /** + * Get flags. + */ + public function getFlags(): int + { + return $this->_flags; + } + + /** + * Get first. + */ + public function getFirst(): int + { + return $this->_first; + } +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/FileGeneric.php b/vendor/psy/psysh/src/Readline/Hoa/FileGeneric.php new file mode 100644 index 0000000000..0aa2af7496 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/FileGeneric.php @@ -0,0 +1,487 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Class \Hoa\File\Generic. + * + * Describe a super-file. + */ +abstract class FileGeneric extends Stream implements StreamPathable, StreamStatable, StreamTouchable +{ + /** + * Mode. + */ + protected $_mode = null; + + /** + * Get filename component of path. + */ + public function getBasename(): string + { + return \basename($this->getStreamName()); + } + + /** + * Get directory name component of path. + */ + public function getDirname(): string + { + return \dirname($this->getStreamName()); + } + + /** + * Get size. + */ + public function getSize(): int + { + if (false === $this->getStatistic()) { + return false; + } + + return \filesize($this->getStreamName()); + } + + /** + * Get informations about a file. + */ + public function getStatistic(): array + { + return \fstat($this->getStream()); + } + + /** + * Get last access time of file. + */ + public function getATime(): int + { + return \fileatime($this->getStreamName()); + } + + /** + * Get inode change time of file. + */ + public function getCTime(): int + { + return \filectime($this->getStreamName()); + } + + /** + * Get file modification time. + */ + public function getMTime(): int + { + return \filemtime($this->getStreamName()); + } + + /** + * Get file group. + */ + public function getGroup(): int + { + return \filegroup($this->getStreamName()); + } + + /** + * Get file owner. + */ + public function getOwner(): int + { + return \fileowner($this->getStreamName()); + } + + /** + * Get file permissions. + */ + public function getPermissions(): int + { + return \fileperms($this->getStreamName()); + } + + /** + * Get file permissions as a string. + * Result sould be interpreted like this: + * * s: socket; + * * l: symbolic link; + * * -: regular; + * * b: block special; + * * d: directory; + * * c: character special; + * * p: FIFO pipe; + * * u: unknown. + */ + public function getReadablePermissions(): string + { + $p = $this->getPermissions(); + + if (($p & 0xC000) === 0xC000) { + $out = 's'; + } elseif (($p & 0xA000) === 0xA000) { + $out = 'l'; + } elseif (($p & 0x8000) === 0x8000) { + $out = '-'; + } elseif (($p & 0x6000) === 0x6000) { + $out = 'b'; + } elseif (($p & 0x4000) === 0x4000) { + $out = 'd'; + } elseif (($p & 0x2000) === 0x2000) { + $out = 'c'; + } elseif (($p & 0x1000) === 0x1000) { + $out = 'p'; + } else { + $out = 'u'; + } + + $out .= + (($p & 0x0100) ? 'r' : '-'). + (($p & 0x0080) ? 'w' : '-'). + (($p & 0x0040) ? + (($p & 0x0800) ? 's' : 'x') : + (($p & 0x0800) ? 'S' : '-')). + (($p & 0x0020) ? 'r' : '-'). + (($p & 0x0010) ? 'w' : '-'). + (($p & 0x0008) ? + (($p & 0x0400) ? 's' : 'x') : + (($p & 0x0400) ? 'S' : '-')). + (($p & 0x0004) ? 'r' : '-'). + (($p & 0x0002) ? 'w' : '-'). + (($p & 0x0001) ? + (($p & 0x0200) ? 't' : 'x') : + (($p & 0x0200) ? 'T' : '-')); + + return $out; + } + + /** + * Check if the file is readable. + */ + public function isReadable(): bool + { + return \is_readable($this->getStreamName()); + } + + /** + * Check if the file is writable. + */ + public function isWritable(): bool + { + return \is_writable($this->getStreamName()); + } + + /** + * Check if the file is executable. + */ + public function isExecutable(): bool + { + return \is_executable($this->getStreamName()); + } + + /** + * Clear file status cache. + */ + public function clearStatisticCache() + { + \clearstatcache(true, $this->getStreamName()); + } + + /** + * Clear all files status cache. + */ + public static function clearAllStatisticCaches() + { + \clearstatcache(); + } + + /** + * Set access and modification time of file. + */ + public function touch(int $time = null, int $atime = null): bool + { + if (null === $time) { + $time = \time(); + } + + if (null === $atime) { + $atime = $time; + } + + return \touch($this->getStreamName(), $time, $atime); + } + + /** + * Copy file. + * Return the destination file path if succeed, false otherwise. + */ + public function copy(string $to, bool $force = StreamTouchable::DO_NOT_OVERWRITE): bool + { + $from = $this->getStreamName(); + + if ($force === StreamTouchable::DO_NOT_OVERWRITE && + true === \file_exists($to)) { + return true; + } + + if (null === $this->getStreamContext()) { + return @\copy($from, $to); + } + + return @\copy($from, $to, $this->getStreamContext()->getContext()); + } + + /** + * Move a file. + */ + public function move( + string $name, + bool $force = StreamTouchable::DO_NOT_OVERWRITE, + bool $mkdir = StreamTouchable::DO_NOT_MAKE_DIRECTORY + ): bool { + $from = $this->getStreamName(); + + if ($force === StreamTouchable::DO_NOT_OVERWRITE && + true === \file_exists($name)) { + return false; + } + + if (StreamTouchable::MAKE_DIRECTORY === $mkdir) { + Directory::create( + \dirname($name), + Directory::MODE_CREATE_RECURSIVE + ); + } + + if (null === $this->getStreamContext()) { + return @\rename($from, $name); + } + + return @\rename($from, $name, $this->getStreamContext()->getContext()); + } + + /** + * Delete a file. + */ + public function delete(): bool + { + if (null === $this->getStreamContext()) { + return @\unlink($this->getStreamName()); + } + + return @\unlink( + $this->getStreamName(), + $this->getStreamContext()->getContext() + ); + } + + /** + * Change file group. + */ + public function changeGroup($group): bool + { + return \chgrp($this->getStreamName(), $group); + } + + /** + * Change file mode. + */ + public function changeMode(int $mode): bool + { + return \chmod($this->getStreamName(), $mode); + } + + /** + * Change file owner. + */ + public function changeOwner($user): bool + { + return \chown($this->getStreamName(), $user); + } + + /** + * Change the current umask. + */ + public static function umask(int $umask = null): int + { + if (null === $umask) { + return \umask(); + } + + return \umask($umask); + } + + /** + * Check if it is a file. + */ + public function isFile(): bool + { + return \is_file($this->getStreamName()); + } + + /** + * Check if it is a link. + */ + public function isLink(): bool + { + return \is_link($this->getStreamName()); + } + + /** + * Check if it is a directory. + */ + public function isDirectory(): bool + { + return \is_dir($this->getStreamName()); + } + + /** + * Check if it is a socket. + */ + public function isSocket(): bool + { + return \filetype($this->getStreamName()) === 'socket'; + } + + /** + * Check if it is a FIFO pipe. + */ + public function isFIFOPipe(): bool + { + return \filetype($this->getStreamName()) === 'fifo'; + } + + /** + * Check if it is character special file. + */ + public function isCharacterSpecial(): bool + { + return \filetype($this->getStreamName()) === 'char'; + } + + /** + * Check if it is block special. + */ + public function isBlockSpecial(): bool + { + return \filetype($this->getStreamName()) === 'block'; + } + + /** + * Check if it is an unknown type. + */ + public function isUnknown(): bool + { + return \filetype($this->getStreamName()) === 'unknown'; + } + + /** + * Set the open mode. + */ + protected function setMode(string $mode) + { + $old = $this->_mode; + $this->_mode = $mode; + + return $old; + } + + /** + * Get the open mode. + */ + public function getMode() + { + return $this->_mode; + } + + /** + * Get inode. + */ + public function getINode(): int + { + return \fileinode($this->getStreamName()); + } + + /** + * Check if the system is case sensitive or not. + */ + public static function isCaseSensitive(): bool + { + return !( + \file_exists(\mb_strtolower(__FILE__)) && + \file_exists(\mb_strtoupper(__FILE__)) + ); + } + + /** + * Get a canonicalized absolute pathname. + */ + public function getRealPath(): string + { + if (false === $out = \realpath($this->getStreamName())) { + return $this->getStreamName(); + } + + return $out; + } + + /** + * Get file extension (if exists). + */ + public function getExtension(): string + { + return \pathinfo( + $this->getStreamName(), + \PATHINFO_EXTENSION + ); + } + + /** + * Get filename without extension. + */ + public function getFilename(): string + { + $file = \basename($this->getStreamName()); + + if (\defined('PATHINFO_FILENAME')) { + return \pathinfo($file, \PATHINFO_FILENAME); + } + + if (\strstr($file, '.')) { + return \substr($file, 0, \strrpos($file, '.')); + } + + return $file; + } +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/FileLink.php b/vendor/psy/psysh/src/Readline/Hoa/FileLink.php new file mode 100644 index 0000000000..21a4485f99 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/FileLink.php @@ -0,0 +1,149 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Class \Hoa\File\Link. + * + * Link handler. + */ +class FileLink extends File +{ + /** + * Open a link. + */ + public function __construct( + string $streamName, + string $mode, + string $context = null, + bool $wait = false + ) { + if (!\is_link($streamName)) { + throw new FileException('File %s is not a link.', 0, $streamName); + } + + parent::__construct($streamName, $mode, $context, $wait); + + return; + } + + /** + * Get informations about a link. + */ + public function getStatistic(): array + { + return \lstat($this->getStreamName()); + } + + /** + * Change file group. + */ + public function changeGroup($group): bool + { + return \lchgrp($this->getStreamName(), $group); + } + + /** + * Change file owner. + */ + public function changeOwner($user): bool + { + return \lchown($this->getStreamName(), $user); + } + + /** + * Get file permissions. + */ + public function getPermissions(): int + { + return 41453; // i.e. lrwxr-xr-x + } + + /** + * Get the target of a symbolic link. + */ + public function getTarget(): FileGeneric + { + $target = \dirname($this->getStreamName()).\DIRECTORY_SEPARATOR. + $this->getTargetName(); + $context = null !== $this->getStreamContext() + ? $this->getStreamContext()->getCurrentId() + : null; + + if (true === \is_link($target)) { + return new FileLinkReadWrite( + $target, + File::MODE_APPEND_READ_WRITE, + $context + ); + } elseif (true === \is_file($target)) { + return new FileReadWrite( + $target, + File::MODE_APPEND_READ_WRITE, + $context + ); + } elseif (true === \is_dir($target)) { + return new FileDirectory( + $target, + File::MODE_READ, + $context + ); + } + + throw new FileException('Cannot find an appropriated object that matches with '.'path %s when defining it.', 1, $target); + } + + /** + * Get the target name of a symbolic link. + */ + public function getTargetName(): string + { + return \readlink($this->getStreamName()); + } + + /** + * Create a link. + */ + public static function create(string $name, string $target): bool + { + if (false !== \linkinfo($name)) { + return true; + } + + return \symlink($target, $name); + } +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/FileLinkRead.php b/vendor/psy/psysh/src/Readline/Hoa/FileLinkRead.php new file mode 100644 index 0000000000..37bb514cb1 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/FileLinkRead.php @@ -0,0 +1,231 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Class \Hoa\File\Link\Read. + * + * File handler. + * + * @license New BSD License + */ +class FileLinkRead extends FileLink implements StreamIn +{ + /** + * Open a file. + * + * @param string $streamName stream name + * @param string $mode open mode, see the parent::MODE_* constants + * @param string $context context ID (please, see the + * \Hoa\Stream\Context class) + * @param bool $wait differ opening or not + */ + public function __construct( + string $streamName, + string $mode = parent::MODE_READ, + string $context = null, + bool $wait = false + ) { + parent::__construct($streamName, $mode, $context, $wait); + + return; + } + + /** + * Open the stream and return the associated resource. + * + * @param string $streamName Stream name (e.g. path or URL). + * @param \Hoa\Stream\Context $context context + * + * @return resource + * + * @throws \Hoa\File\Exception\FileDoesNotExist + * @throws \Hoa\File\Exception + */ + protected function &_open(string $streamName, StreamContext $context = null) + { + static $createModes = [ + parent::MODE_READ, + ]; + + if (!\in_array($this->getMode(), $createModes)) { + throw new FileException('Open mode are not supported; given %d. Only %s are supported.', 0, [$this->getMode(), \implode(', ', $createModes)]); + } + + \preg_match('#^(\w+)://#', $streamName, $match); + + if (((isset($match[1]) && $match[1] === 'file') || !isset($match[1])) && + !\file_exists($streamName)) { + throw new FileDoesNotExistException('File %s does not exist.', 1, $streamName); + } + + $out = parent::_open($streamName, $context); + + return $out; + } + + /** + * Test for end-of-file. + * + * @return bool + */ + public function eof(): bool + { + return \feof($this->getStream()); + } + + /** + * Read n characters. + * + * @param int $length length + * + * @return string + * + * @throws \Hoa\File\Exception + */ + public function read(int $length) + { + if (0 > $length) { + throw new FileException('Length must be greater than 0, given %d.', 2, $length); + } + + return \fread($this->getStream(), $length); + } + + /** + * Alias of $this->read(). + * + * @param int $length length + * + * @return string + */ + public function readString(int $length) + { + return $this->read($length); + } + + /** + * Read a character. + * + * @return string + */ + public function readCharacter() + { + return \fgetc($this->getStream()); + } + + /** + * Read a boolean. + * + * @return bool + */ + public function readBoolean() + { + return (bool) $this->read(1); + } + + /** + * Read an integer. + * + * @param int $length length + * + * @return int + */ + public function readInteger(int $length = 1) + { + return (int) $this->read($length); + } + + /** + * Read a float. + * + * @param int $length length + * + * @return float + */ + public function readFloat(int $length = 1) + { + return (float) $this->read($length); + } + + /** + * Read an array. + * Alias of the $this->scanf() method. + * + * @param string $format format (see printf's formats) + * + * @return array + */ + public function readArray(string $format = null) + { + return $this->scanf($format); + } + + /** + * Read a line. + * + * @return string + */ + public function readLine() + { + return \fgets($this->getStream()); + } + + /** + * Read all, i.e. read as much as possible. + * + * @param int $offset offset + * + * @return string + */ + public function readAll(int $offset = 0) + { + return \stream_get_contents($this->getStream(), -1, $offset); + } + + /** + * Parse input from a stream according to a format. + * + * @param string $format format (see printf's formats) + * + * @return array + */ + public function scanf(string $format): array + { + return \fscanf($this->getStream(), $format); + } +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/FileLinkReadWrite.php b/vendor/psy/psysh/src/Readline/Hoa/FileLinkReadWrite.php new file mode 100644 index 0000000000..0d16585cf0 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/FileLinkReadWrite.php @@ -0,0 +1,279 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Class \Hoa\File\Link\ReadWrite. + * + * File handler. + */ +class FileLinkReadWrite extends FileLink implements StreamIn, StreamOut +{ + /** + * Open a file. + */ + public function __construct( + string $streamName, + string $mode = parent::MODE_APPEND_READ_WRITE, + string $context = null, + bool $wait = false + ) { + parent::__construct($streamName, $mode, $context, $wait); + + return; + } + + /** + * Open the stream and return the associated resource. + */ + protected function &_open(string $streamName, StreamContext $context = null) + { + static $createModes = [ + parent::MODE_READ_WRITE, + parent::MODE_TRUNCATE_READ_WRITE, + parent::MODE_APPEND_READ_WRITE, + parent::MODE_CREATE_READ_WRITE, + ]; + + if (!\in_array($this->getMode(), $createModes)) { + throw new FileException('Open mode are not supported; given %d. Only %s are supported.', 0, [$this->getMode(), \implode(', ', $createModes)]); + } + + \preg_match('#^(\w+)://#', $streamName, $match); + + if (((isset($match[1]) && $match[1] === 'file') || !isset($match[1])) && + !\file_exists($streamName) && + parent::MODE_READ_WRITE === $this->getMode()) { + throw new FileDoesNotExistException('File %s does not exist.', 1, $streamName); + } + + $out = parent::_open($streamName, $context); + + return $out; + } + + /** + * Test for end-of-file. + */ + public function eof(): bool + { + return \feof($this->getStream()); + } + + /** + * Read n characters. + */ + public function read(int $length) + { + if (0 > $length) { + throw new FileException('Length must be greater than 0, given %d.', 2, $length); + } + + return \fread($this->getStream(), $length); + } + + /** + * Alias of $this->read(). + */ + public function readString(int $length) + { + return $this->read($length); + } + + /** + * Read a character. + */ + public function readCharacter() + { + return \fgetc($this->getStream()); + } + + /** + * Read a boolean. + */ + public function readBoolean() + { + return (bool) $this->read(1); + } + + /** + * Read an integer. + */ + public function readInteger(int $length = 1) + { + return (int) $this->read($length); + } + + /** + * Read a float. + */ + public function readFloat(int $length = 1) + { + return (float) $this->read($length); + } + + /** + * Read an array. + * Alias of the $this->scanf() method. + */ + public function readArray(string $format = null) + { + return $this->scanf($format); + } + + /** + * Read a line. + */ + public function readLine() + { + return \fgets($this->getStream()); + } + + /** + * Read all, i.e. read as much as possible. + */ + public function readAll(int $offset = 0) + { + return \stream_get_contents($this->getStream(), -1, $offset); + } + + /** + * Parse input from a stream according to a format. + */ + public function scanf(string $format): array + { + return \fscanf($this->getStream(), $format); + } + + /** + * Write n characters. + */ + public function write(string $string, int $length) + { + if (0 > $length) { + throw new FileException('Length must be greater than 0, given %d.', 3, $length); + } + + return \fwrite($this->getStream(), $string, $length); + } + + /** + * Write a string. + */ + public function writeString(string $string) + { + $string = (string) $string; + + return $this->write($string, \strlen($string)); + } + + /** + * Write a character. + */ + public function writeCharacter(string $char) + { + return $this->write((string) $char[0], 1); + } + + /** + * Write a boolean. + */ + public function writeBoolean(bool $boolean) + { + return $this->write((string) (bool) $boolean, 1); + } + + /** + * Write an integer. + */ + public function writeInteger(int $integer) + { + $integer = (string) (int) $integer; + + return $this->write($integer, \strlen($integer)); + } + + /** + * Write a float. + */ + public function writeFloat(float $float) + { + $float = (string) (float) $float; + + return $this->write($float, \strlen($float)); + } + + /** + * Write an array. + */ + public function writeArray(array $array) + { + $array = \var_export($array, true); + + return $this->write($array, \strlen($array)); + } + + /** + * Write a line. + */ + public function writeLine(string $line) + { + if (false === $n = \strpos($line, "\n")) { + return $this->write($line."\n", \strlen($line) + 1); + } + + ++$n; + + return $this->write(\substr($line, 0, $n), $n); + } + + /** + * Write all, i.e. as much as possible. + */ + public function writeAll(string $string) + { + return $this->write($string, \strlen($string)); + } + + /** + * Truncate a file to a given length. + */ + public function truncate(int $size): bool + { + return \ftruncate($this->getStream(), $size); + } +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/FileRead.php b/vendor/psy/psysh/src/Readline/Hoa/FileRead.php new file mode 100644 index 0000000000..9e10fe6928 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/FileRead.php @@ -0,0 +1,177 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Class \Hoa\File\Read. + * + * File handler. + */ +class FileRead extends File implements StreamIn +{ + /** + * Open a file. + */ + public function __construct( + string $streamName, + string $mode = parent::MODE_READ, + string $context = null, + bool $wait = false + ) { + parent::__construct($streamName, $mode, $context, $wait); + + return; + } + + /** + * Open the stream and return the associated resource. + */ + protected function &_open(string $streamName, StreamContext $context = null) + { + static $createModes = [ + parent::MODE_READ, + ]; + + if (!\in_array($this->getMode(), $createModes)) { + throw new FileException('Open mode are not supported; given %d. Only %s are supported.', 0, [$this->getMode(), \implode(', ', $createModes)]); + } + + \preg_match('#^(\w+)://#', $streamName, $match); + + if (((isset($match[1]) && $match[1] === 'file') || !isset($match[1])) && + !\file_exists($streamName)) { + throw new FileDoesNotExistException('File %s does not exist.', 1, $streamName); + } + + $out = parent::_open($streamName, $context); + + return $out; + } + + /** + * Test for end-of-file. + */ + public function eof(): bool + { + return \feof($this->getStream()); + } + + /** + * Read n characters. + */ + public function read(int $length) + { + if (0 > $length) { + throw new FileException('Length must be greater than 0, given %d.', 2, $length); + } + + return \fread($this->getStream(), $length); + } + + /** + * Alias of $this->read(). + */ + public function readString(int $length) + { + return $this->read($length); + } + + /** + * Read a character. + */ + public function readCharacter() + { + return \fgetc($this->getStream()); + } + + /** + * Read a boolean. + */ + public function readBoolean() + { + return (bool) $this->read(1); + } + + /** + * Read an integer. + */ + public function readInteger(int $length = 1) + { + return (int) $this->read($length); + } + + /** + * Read a float. + */ + public function readFloat(int $length = 1) + { + return (float) $this->read($length); + } + + /** + * Read an array. + * Alias of the $this->scanf() method. + */ + public function readArray(string $format = null) + { + return $this->scanf($format); + } + + /** + * Read a line. + */ + public function readLine() + { + return \fgets($this->getStream()); + } + + /** + * Read all, i.e. read as much as possible. + */ + public function readAll(int $offset = 0) + { + return \stream_get_contents($this->getStream(), -1, $offset); + } + + /** + * Parse input from a stream according to a format. + */ + public function scanf(string $format): array + { + return \fscanf($this->getStream(), $format); + } +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/FileReadWrite.php b/vendor/psy/psysh/src/Readline/Hoa/FileReadWrite.php new file mode 100644 index 0000000000..406b6aa73b --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/FileReadWrite.php @@ -0,0 +1,279 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Class \Hoa\File\ReadWrite. + * + * File handler. + */ +class FileReadWrite extends File implements StreamIn, StreamOut +{ + /** + * Open a file. + */ + public function __construct( + string $streamName, + string $mode = parent::MODE_APPEND_READ_WRITE, + string $context = null, + bool $wait = false + ) { + parent::__construct($streamName, $mode, $context, $wait); + + return; + } + + /** + * Open the stream and return the associated resource. + */ + protected function &_open(string $streamName, StreamContext $context = null) + { + static $createModes = [ + parent::MODE_READ_WRITE, + parent::MODE_TRUNCATE_READ_WRITE, + parent::MODE_APPEND_READ_WRITE, + parent::MODE_CREATE_READ_WRITE, + ]; + + if (!\in_array($this->getMode(), $createModes)) { + throw new FileException('Open mode are not supported; given %d. Only %s are supported.', 0, [$this->getMode(), \implode(', ', $createModes)]); + } + + \preg_match('#^(\w+)://#', $streamName, $match); + + if (((isset($match[1]) && $match[1] === 'file') || !isset($match[1])) && + !\file_exists($streamName) && + parent::MODE_READ_WRITE === $this->getMode()) { + throw new FileDoesNotExistException('File %s does not exist.', 1, $streamName); + } + + $out = parent::_open($streamName, $context); + + return $out; + } + + /** + * Test for end-of-file. + */ + public function eof(): bool + { + return \feof($this->getStream()); + } + + /** + * Read n characters. + */ + public function read(int $length) + { + if (0 > $length) { + throw new FileException('Length must be greater than 0, given %d.', 2, $length); + } + + return \fread($this->getStream(), $length); + } + + /** + * Alias of $this->read(). + */ + public function readString(int $length) + { + return $this->read($length); + } + + /** + * Read a character. + */ + public function readCharacter() + { + return \fgetc($this->getStream()); + } + + /** + * Read a boolean. + */ + public function readBoolean() + { + return (bool) $this->read(1); + } + + /** + * Read an integer. + */ + public function readInteger(int $length = 1) + { + return (int) $this->read($length); + } + + /** + * Read a float. + */ + public function readFloat(int $length = 1) + { + return (float) $this->read($length); + } + + /** + * Read an array. + * Alias of the $this->scanf() method. + */ + public function readArray(string $format = null) + { + return $this->scanf($format); + } + + /** + * Read a line. + */ + public function readLine() + { + return \fgets($this->getStream()); + } + + /** + * Read all, i.e. read as much as possible. + */ + public function readAll(int $offset = 0) + { + return \stream_get_contents($this->getStream(), -1, $offset); + } + + /** + * Parse input from a stream according to a format. + */ + public function scanf(string $format): array + { + return \fscanf($this->getStream(), $format); + } + + /** + * Write n characters. + */ + public function write(string $string, int $length) + { + if (0 > $length) { + throw new FileException('Length must be greater than 0, given %d.', 3, $length); + } + + return \fwrite($this->getStream(), $string, $length); + } + + /** + * Write a string. + */ + public function writeString(string $string) + { + $string = (string) $string; + + return $this->write($string, \strlen($string)); + } + + /** + * Write a character. + */ + public function writeCharacter(string $char) + { + return $this->write((string) $char[0], 1); + } + + /** + * Write a boolean. + */ + public function writeBoolean(bool $boolean) + { + return $this->write((string) (bool) $boolean, 1); + } + + /** + * Write an integer. + */ + public function writeInteger(int $integer) + { + $integer = (string) (int) $integer; + + return $this->write($integer, \strlen($integer)); + } + + /** + * Write a float. + */ + public function writeFloat(float $float) + { + $float = (string) (float) $float; + + return $this->write($float, \strlen($float)); + } + + /** + * Write an array. + */ + public function writeArray(array $array) + { + $array = \var_export($array, true); + + return $this->write($array, \strlen($array)); + } + + /** + * Write a line. + */ + public function writeLine(string $line) + { + if (false === $n = \strpos($line, "\n")) { + return $this->write($line."\n", \strlen($line) + 1); + } + + ++$n; + + return $this->write(\substr($line, 0, $n), $n); + } + + /** + * Write all, i.e. as much as possible. + */ + public function writeAll(string $string) + { + return $this->write($string, \strlen($string)); + } + + /** + * Truncate a file to a given length. + */ + public function truncate(int $size): bool + { + return \ftruncate($this->getStream(), $size); + } +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/IStream.php b/vendor/psy/psysh/src/Readline/Hoa/IStream.php new file mode 100644 index 0000000000..9b9949a240 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/IStream.php @@ -0,0 +1,50 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Interface \Hoa\Stream\IStream\Stream. + * + * Interface for all streams. + */ +interface IStream +{ + /** + * Get the current stream. + */ + public function getStream(); +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/IteratorFileSystem.php b/vendor/psy/psysh/src/Readline/Hoa/IteratorFileSystem.php new file mode 100644 index 0000000000..f0fc5c5750 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/IteratorFileSystem.php @@ -0,0 +1,86 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Class \Hoa\Iterator\FileSystem. + * + * Extending the SPL FileSystemIterator class. + */ +class IteratorFileSystem extends \FilesystemIterator +{ + /** + * SplFileInfo classname. + */ + protected $_splFileInfoClass = null; + + /** + * Constructor. + * Please, see \FileSystemIterator::__construct() method. + * We add the $splFileInfoClass parameter. + */ + public function __construct(string $path, int $flags = null, string $splFileInfoClass = null) + { + $this->_splFileInfoClass = $splFileInfoClass; + + if (null === $flags) { + parent::__construct($path); + } else { + parent::__construct($path, $flags); + } + + return; + } + + /** + * Current. + * Please, see \FileSystemIterator::current() method. + */ + #[\ReturnTypeWillChange] + public function current() + { + $out = parent::current(); + + if (null !== $this->_splFileInfoClass && + $out instanceof \SplFileInfo) { + $out->setInfoClass($this->_splFileInfoClass); + $out = $out->getFileInfo(); + } + + return $out; + } +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/IteratorRecursiveDirectory.php b/vendor/psy/psysh/src/Readline/Hoa/IteratorRecursiveDirectory.php new file mode 100644 index 0000000000..ad69512333 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/IteratorRecursiveDirectory.php @@ -0,0 +1,126 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Class \Hoa\Iterator\Recursive\Directory. + * + * Extending the SPL RecursiveDirectoryIterator class. + */ +class IteratorRecursiveDirectory extends \RecursiveDirectoryIterator +{ + /** + * SplFileInfo classname. + */ + protected $_splFileInfoClass = null; + + /** + * Relative path. + */ + protected $_relativePath = null; + + /** + * Constructor. + * Please, see \RecursiveDirectoryIterator::__construct() method. + * We add the $splFileInfoClass parameter. + */ + public function __construct(string $path, int $flags = null, string $splFileInfoClass = null) + { + if (null === $flags) { + parent::__construct($path); + } else { + parent::__construct($path, $flags); + } + + $this->_relativePath = $path; + $this->setSplFileInfoClass($splFileInfoClass); + + return; + } + + /** + * Current. + * Please, see \RecursiveDirectoryIterator::current() method. + */ + #[\ReturnTypeWillChange] + public function current() + { + $out = parent::current(); + + if (null !== $this->_splFileInfoClass && + $out instanceof \SplFileInfo) { + $out->setInfoClass($this->_splFileInfoClass); + $out = $out->getFileInfo(); + + if ($out instanceof IteratorSplFileInfo) { + $out->setRelativePath($this->getRelativePath()); + } + } + + return $out; + } + + /** + * Get children. + * Please, see \RecursiveDirectoryIterator::getChildren() method. + */ + #[\ReturnTypeWillChange] + public function getChildren() + { + $out = parent::getChildren(); + $out->_relativePath = $this->getRelativePath(); + $out->setSplFileInfoClass($this->_splFileInfoClass); + + return $out; + } + + /** + * Set SplFileInfo classname. + */ + public function setSplFileInfoClass($splFileInfoClass) + { + $this->_splFileInfoClass = $splFileInfoClass; + } + + /** + * Get relative path (if given). + */ + public function getRelativePath(): string + { + return $this->_relativePath; + } +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/IteratorSplFileInfo.php b/vendor/psy/psysh/src/Readline/Hoa/IteratorSplFileInfo.php new file mode 100644 index 0000000000..3cf54b845c --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/IteratorSplFileInfo.php @@ -0,0 +1,122 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Class \Hoa\Iterator\SplFileInfo. + * + * Enhance SplFileInfo implementation. + */ +class IteratorSplFileInfo extends \SplFileInfo +{ + /** + * Hash. + */ + protected $_hash = null; + + /** + * Relative path. + */ + protected $_relativePath = null; + + /** + * Construct. + */ + public function __construct(string $filename, string $relativePath = null) + { + parent::__construct($filename); + + if (-1 !== $mtime = $this->getMTime()) { + $this->_hash = \md5($this->getPathname().$mtime); + } + + $this->_relativePath = $relativePath; + + return; + } + + /** + * Get the hash. + */ + public function getHash(): string + { + return $this->_hash; + } + + /** + * Get the MTime. + */ + public function getMTime(): int + { + try { + return parent::getMTime(); + } catch (\RuntimeException $e) { + return -1; + } + } + + /** + * Set relative path. + */ + public function setRelativePath(string $relativePath) + { + $old = $this->_relativePath; + $this->_relativePath = $relativePath; + + return $old; + } + + /** + * Get relative path (if given). + */ + public function getRelativePath() + { + return $this->_relativePath; + } + + /** + * Get relative pathname (if possible). + */ + public function getRelativePathname(): string + { + if (null === $relative = $this->getRelativePath()) { + return $this->getPathname(); + } + + return \substr($this->getPathname(), \strlen($relative)); + } +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/Protocol.php b/vendor/psy/psysh/src/Readline/Hoa/Protocol.php new file mode 100644 index 0000000000..45a2ddad7d --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/Protocol.php @@ -0,0 +1,223 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Root of the `hoa://` protocol. + */ +class Protocol extends ProtocolNode +{ + /** + * No resolution value. + * + * @const string + */ + const NO_RESOLUTION = '/hoa/flatland'; + + /** + * Singleton. + */ + private static $_instance = null; + + /** + * Cache of resolver. + */ + private static $_cache = []; + + /** + * Initialize the protocol. + */ + public function __construct() + { + $this->initialize(); + + return; + } + + /** + * Singleton. + * To use the `hoa://` protocol shared by everyone. + */ + public static function getInstance(): self + { + if (null === static::$_instance) { + static::$_instance = new static(); + } + + return static::$_instance; + } + + /** + * Initialize the protocol. + */ + protected function initialize() + { + $root = \dirname(__DIR__, 3); + $argv0 = \realpath($_SERVER['argv'][0]); + + $cwd = + 'cli' === \PHP_SAPI + ? false !== $argv0 ? \dirname($argv0) : '' + : \getcwd(); + + $this[] = new ProtocolNode( + 'Application', + $cwd.\DIRECTORY_SEPARATOR, + [ + new ProtocolNode('Public', 'Public'.\DIRECTORY_SEPARATOR), + ] + ); + + $this[] = new ProtocolNode( + 'Data', + \dirname($cwd).\DIRECTORY_SEPARATOR, + [ + new ProtocolNode( + 'Etc', + 'Etc'.\DIRECTORY_SEPARATOR, + [ + new ProtocolNode('Configuration', 'Configuration'.\DIRECTORY_SEPARATOR), + new ProtocolNode('Locale', 'Locale'.\DIRECTORY_SEPARATOR), + ] + ), + new ProtocolNode('Lost+found', 'Lost+found'.\DIRECTORY_SEPARATOR), + new ProtocolNode('Temporary', 'Temporary'.\DIRECTORY_SEPARATOR), + new ProtocolNode( + 'Variable', + 'Variable'.\DIRECTORY_SEPARATOR, + [ + new ProtocolNode('Cache', 'Cache'.\DIRECTORY_SEPARATOR), + new ProtocolNode('Database', 'Database'.\DIRECTORY_SEPARATOR), + new ProtocolNode('Log', 'Log'.\DIRECTORY_SEPARATOR), + new ProtocolNode('Private', 'Private'.\DIRECTORY_SEPARATOR), + new ProtocolNode('Run', 'Run'.\DIRECTORY_SEPARATOR), + new ProtocolNode('Test', 'Test'.\DIRECTORY_SEPARATOR), + ] + ), + ] + ); + + $this[] = new ProtocolNodeLibrary( + 'Library', + $root.\DIRECTORY_SEPARATOR.'Hoathis'.\DIRECTORY_SEPARATOR.';'. + $root.\DIRECTORY_SEPARATOR.'Hoa'.\DIRECTORY_SEPARATOR + ); + } + + /** + * Resolve (unfold) an `hoa://` path to its real resource. + * + * If `$exists` is set to `true`, try to find the first that exists, + * otherwise returns the first solution. If `$unfold` is set to `true`, + * it returns all the paths. + */ + public function resolve(string $path, bool $exists = true, bool $unfold = false) + { + if (\substr($path, 0, 6) !== 'hoa://') { + if (true === \is_dir($path)) { + $path = \rtrim($path, '/\\'); + + if ('' === $path) { + $path = '/'; + } + } + + return $path; + } + + if (isset(self::$_cache[$path])) { + $handle = self::$_cache[$path]; + } else { + $out = $this->_resolve($path, $handle); + + // Not a path but a resource. + if (!\is_array($handle)) { + return $out; + } + + $handle = \array_values(\array_unique($handle, \SORT_REGULAR)); + + foreach ($handle as &$entry) { + if (true === \is_dir($entry)) { + $entry = \rtrim($entry, '/\\'); + + if ('' === $entry) { + $entry = '/'; + } + } + } + + self::$_cache[$path] = $handle; + } + + if (true === $unfold) { + if (true !== $exists) { + return $handle; + } + + $out = []; + + foreach ($handle as $solution) { + if (\file_exists($solution)) { + $out[] = $solution; + } + } + + return $out; + } + + if (true !== $exists) { + return $handle[0]; + } + + foreach ($handle as $solution) { + if (\file_exists($solution)) { + return $solution; + } + } + + return static::NO_RESOLUTION; + } + + /** + * Clear the cache. + */ + public static function clearCache() + { + self::$_cache = []; + } +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/ProtocolException.php b/vendor/psy/psysh/src/Readline/Hoa/ProtocolException.php new file mode 100644 index 0000000000..6b624bb9e3 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/ProtocolException.php @@ -0,0 +1,44 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Extends the `Hoa\Exception\Exception` class. + */ +class ProtocolException extends Exception +{ +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/ProtocolNode.php b/vendor/psy/psysh/src/Readline/Hoa/ProtocolNode.php new file mode 100644 index 0000000000..78812d7ec7 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/ProtocolNode.php @@ -0,0 +1,323 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Abstract class for all `hoa://`'s nodes. + */ +class ProtocolNode implements \ArrayAccess, \IteratorAggregate +{ + /** + * Node's name. + */ + protected $_name = null; + + /** + * Path for the `reach` method. + */ + protected $_reach = null; + + /** + * Children of the node. + */ + private $_children = []; + + /** + * Construct a protocol's node. + * If it is not a data object (i.e. if it does not extend this class to + * overload the `$_name` attribute), we can set the `$_name` attribute + * dynamically. This is useful to create a node on-the-fly. + */ + public function __construct(string $name = null, string $reach = null, array $children = []) + { + if (null !== $name) { + $this->_name = $name; + } + + if (null !== $reach) { + $this->_reach = $reach; + } + + foreach ($children as $child) { + $this[] = $child; + } + + return; + } + + /** + * Add a node. + */ + #[\ReturnTypeWillChange] + public function offsetSet($name, $node) + { + if (!($node instanceof self)) { + throw new ProtocolException('Protocol node must extend %s.', 0, __CLASS__); + } + + if (empty($name)) { + $name = $node->getName(); + } + + if (empty($name)) { + throw new ProtocolException('Cannot add a node to the `hoa://` protocol without a name.', 1); + } + + $this->_children[$name] = $node; + } + + /** + * Get a specific node. + */ + public function offsetGet($name): self + { + if (!isset($this[$name])) { + throw new ProtocolException('Node %s does not exist.', 2, $name); + } + + return $this->_children[$name]; + } + + /** + * Check if a node exists. + */ + public function offsetExists($name): bool + { + return true === \array_key_exists($name, $this->_children); + } + + /** + * Remove a node. + */ + #[\ReturnTypeWillChange] + public function offsetUnset($name) + { + unset($this->_children[$name]); + } + + /** + * Resolve a path, i.e. iterate the nodes tree and reach the queue of + * the path. + */ + protected function _resolve(string $path, &$accumulator, string $id = null) + { + if (\substr($path, 0, 6) === 'hoa://') { + $path = \substr($path, 6); + } + + if (empty($path)) { + return null; + } + + if (null === $accumulator) { + $accumulator = []; + $posId = \strpos($path, '#'); + + if (false !== $posId) { + $id = \substr($path, $posId + 1); + $path = \substr($path, 0, $posId); + } else { + $id = null; + } + } + + $path = \trim($path, '/'); + $pos = \strpos($path, '/'); + + if (false !== $pos) { + $next = \substr($path, 0, $pos); + } else { + $next = $path; + } + + if (isset($this[$next])) { + if (false === $pos) { + if (null === $id) { + $this->_resolveChoice($this[$next]->reach(), $accumulator); + + return true; + } + + $accumulator = null; + + return $this[$next]->reachId($id); + } + + $tnext = $this[$next]; + $this->_resolveChoice($tnext->reach(), $accumulator); + + return $tnext->_resolve(\substr($path, $pos + 1), $accumulator, $id); + } + + $this->_resolveChoice($this->reach($path), $accumulator); + + return true; + } + + /** + * Resolve choices, i.e. a reach value has a “;”. + */ + protected function _resolveChoice($reach, &$accumulator) + { + if (null === $reach) { + $reach = ''; + } + + if (empty($accumulator)) { + $accumulator = \explode(';', $reach); + + return; + } + + if (false === \strpos($reach, ';')) { + if (false !== $pos = \strrpos($reach, "\r")) { + $reach = \substr($reach, $pos + 1); + + foreach ($accumulator as &$entry) { + $entry = null; + } + } + + foreach ($accumulator as &$entry) { + $entry .= $reach; + } + + return; + } + + $choices = \explode(';', $reach); + $ref = $accumulator; + $accumulator = []; + + foreach ($choices as $choice) { + if (false !== $pos = \strrpos($choice, "\r")) { + $choice = \substr($choice, $pos + 1); + + foreach ($ref as $entry) { + $accumulator[] = $choice; + } + } else { + foreach ($ref as $entry) { + $accumulator[] = $entry.$choice; + } + } + } + + unset($ref); + + return; + } + + /** + * Queue of the node. + * Generic one. Must be overrided in children classes. + */ + public function reach(string $queue = null) + { + return empty($queue) ? $this->_reach : $queue; + } + + /** + * ID of the component. + * Generic one. Should be overrided in children classes. + */ + public function reachId(string $id) + { + throw new ProtocolException('The node %s has no ID support (tried to reach #%s).', 4, [$this->getName(), $id]); + } + + /** + * Set a new reach value. + */ + public function setReach(string $reach) + { + $old = $this->_reach; + $this->_reach = $reach; + + return $old; + } + + /** + * Get node's name. + */ + public function getName() + { + return $this->_name; + } + + /** + * Get reach's root. + */ + protected function getReach() + { + return $this->_reach; + } + + /** + * Get an iterator. + */ + public function getIterator(): \ArrayIterator + { + return new \ArrayIterator($this->_children); + } + + /** + * Get root the protocol. + */ + public static function getRoot(): Protocol + { + return Protocol::getInstance(); + } + + /** + * Print a tree of component. + */ + public function __toString(): string + { + static $i = 0; + + $out = \str_repeat(' ', $i).$this->getName()."\n"; + + foreach ($this as $node) { + ++$i; + $out .= $node; + --$i; + } + + return $out; + } +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/ProtocolNodeLibrary.php b/vendor/psy/psysh/src/Readline/Hoa/ProtocolNodeLibrary.php new file mode 100644 index 0000000000..dfb0cb206d --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/ProtocolNodeLibrary.php @@ -0,0 +1,90 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * The `hoa://Library/` node. + */ +class ProtocolNodeLibrary extends ProtocolNode +{ + /** + * Queue of the component. + */ + public function reach(string $queue = null) + { + $withComposer = \class_exists('Composer\Autoload\ClassLoader', false) || + ('cli' === \PHP_SAPI && \file_exists(__DIR__.DS.'..'.DS.'..'.DS.'..'.DS.'..'.DS.'autoload.php')); + + if ($withComposer) { + return parent::reach($queue); + } + + if (!empty($queue)) { + $head = $queue; + + if (false !== $pos = \strpos($queue, '/')) { + $head = \substr($head, 0, $pos); + $queue = \DIRECTORY_SEPARATOR.\substr($queue, $pos + 1); + } else { + $queue = null; + } + + $out = []; + + foreach (\explode(';', $this->_reach) as $part) { + $out[] = "\r".$part.\strtolower($head).$queue; + } + + $out[] = "\r".\dirname(__DIR__, 5).$queue; + + return \implode(';', $out); + } + + $out = []; + + foreach (\explode(';', $this->_reach) as $part) { + $pos = \strrpos(\rtrim($part, \DIRECTORY_SEPARATOR), \DIRECTORY_SEPARATOR) + 1; + $head = \substr($part, 0, $pos); + $tail = \substr($part, $pos); + $out[] = $head.\strtolower($tail); + } + + $this->_reach = \implode(';', $out); + + return parent::reach($queue); + } +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/ProtocolWrapper.php b/vendor/psy/psysh/src/Readline/Hoa/ProtocolWrapper.php new file mode 100644 index 0000000000..8d525e7ef9 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/ProtocolWrapper.php @@ -0,0 +1,473 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Stream wrapper for the `hoa://` protocol. + */ +class ProtocolWrapper +{ + /** + * Opened stream as a resource. + */ + private $_stream = null; + + /** + * Stream name (filename). + */ + private $_streamName = null; + + /** + * Stream context (given by the streamWrapper class) as a resource. + */ + public $context = null; + + /** + * Get the real path of the given URL. + * Could return false if the path cannot be reached. + */ + public static function realPath(string $path, bool $exists = true) + { + return ProtocolNode::getRoot()->resolve($path, $exists); + } + + /** + * Retrieve the underlying resource. + * + * `$castAs` can be `STREAM_CAST_FOR_SELECT` when `stream_select` is + * calling `stream_cast` or `STREAM_CAST_AS_STREAM` when `stream_cast` is + * called for other uses. + */ + public function stream_cast(int $castAs) + { + return null; + } + + /** + * Closes a resource. + * This method is called in response to `fclose`. + * All resources that were locked, or allocated, by the wrapper should be + * released. + */ + public function stream_close() + { + if (true === @\fclose($this->getStream())) { + $this->_stream = null; + $this->_streamName = null; + } + } + + /** + * Tests for end-of-file on a file pointer. + * This method is called in response to feof(). + */ + public function stream_eof(): bool + { + return \feof($this->getStream()); + } + + /** + * Flush the output. + * This method is called in respond to fflush(). + * If we have cached data in our stream but not yet stored it into the + * underlying storage, we should do so now. + */ + public function stream_flush(): bool + { + return \fflush($this->getStream()); + } + + /** + * Advisory file locking. + * This method is called in response to flock(), when file_put_contents() + * (when flags contains LOCK_EX), stream_set_blocking() and when closing the + * stream (LOCK_UN). + * + * Operation is one the following: + * * LOCK_SH to acquire a shared lock (reader) ; + * * LOCK_EX to acquire an exclusive lock (writer) ; + * * LOCK_UN to release a lock (shared or exclusive) ; + * * LOCK_NB if we don't want flock() to + * block while locking (not supported on + * Windows). + */ + public function stream_lock(int $operation): bool + { + return \flock($this->getStream(), $operation); + } + + /** + * Change stream options. + * This method is called to set metadata on the stream. It is called when + * one of the following functions is called on a stream URL: touch, chmod, + * chown or chgrp. + * + * Option must be one of the following constant: + * * STREAM_META_TOUCH, + * * STREAM_META_OWNER_NAME, + * * STREAM_META_OWNER, + * * STREAM_META_GROUP_NAME, + * * STREAM_META_GROUP, + * * STREAM_META_ACCESS. + * + * Values are arguments of `touch`, `chmod`, `chown`, and `chgrp`. + */ + public function stream_metadata(string $path, int $option, $values): bool + { + $path = static::realPath($path, false); + + switch ($option) { + case \STREAM_META_TOUCH: + $arity = \count($values); + + if (0 === $arity) { + $out = \touch($path); + } elseif (1 === $arity) { + $out = \touch($path, $values[0]); + } else { + $out = \touch($path, $values[0], $values[1]); + } + + break; + + case \STREAM_META_OWNER_NAME: + case \STREAM_META_OWNER: + $out = \chown($path, $values); + + break; + + case \STREAM_META_GROUP_NAME: + case \STREAM_META_GROUP: + $out = \chgrp($path, $values); + + break; + + case \STREAM_META_ACCESS: + $out = \chmod($path, $values); + + break; + + default: + $out = false; + } + + return $out; + } + + /** + * Open file or URL. + * This method is called immediately after the wrapper is initialized (f.e. + * by fopen() and file_get_contents()). + */ + public function stream_open(string $path, string $mode, int $options, &$openedPath): bool + { + $path = static::realPath($path, 'r' === $mode[0]); + + if (Protocol::NO_RESOLUTION === $path) { + return false; + } + + if (null === $this->context) { + $openedPath = \fopen($path, $mode, $options & \STREAM_USE_PATH); + } else { + $openedPath = \fopen( + $path, + $mode, + (bool) ($options & \STREAM_USE_PATH), + $this->context + ); + } + + if (false === \is_resource($openedPath)) { + return false; + } + + $this->_stream = $openedPath; + $this->_streamName = $path; + + return true; + } + + /** + * Read from stream. + * This method is called in response to fread() and fgets(). + */ + public function stream_read(int $size): string + { + return \fread($this->getStream(), $size); + } + + /** + * Seek to specific location in a stream. + * This method is called in response to fseek(). + * The read/write position of the stream should be updated according to the + * $offset and $whence. + * + * The possible values for `$whence` are: + * * SEEK_SET to set position equal to $offset bytes, + * * SEEK_CUR to set position to current location plus `$offset`, + * * SEEK_END to set position to end-of-file plus `$offset`. + */ + public function stream_seek(int $offset, int $whence = \SEEK_SET): bool + { + return 0 === \fseek($this->getStream(), $offset, $whence); + } + + /** + * Retrieve information about a file resource. + * This method is called in response to fstat(). + */ + public function stream_stat(): array + { + return \fstat($this->getStream()); + } + + /** + * Retrieve the current position of a stream. + * This method is called in response to ftell(). + */ + public function stream_tell(): int + { + return \ftell($this->getStream()); + } + + /** + * Truncate a stream to a given length. + */ + public function stream_truncate(int $size): bool + { + return \ftruncate($this->getStream(), $size); + } + + /** + * Write to stream. + * This method is called in response to fwrite(). + */ + public function stream_write(string $data): int + { + return \fwrite($this->getStream(), $data); + } + + /** + * Close directory handle. + * This method is called in to closedir(). + * Any resources which were locked, or allocated, during opening and use of + * the directory stream should be released. + */ + public function dir_closedir() + { + \closedir($this->getStream()); + $this->_stream = null; + $this->_streamName = null; + } + + /** + * Open directory handle. + * This method is called in response to opendir(). + * + * The `$options` input represents whether or not to enforce safe_mode + * (0x04). It is not used here. + */ + public function dir_opendir(string $path, int $options): bool + { + $path = static::realPath($path); + $handle = null; + + if (null === $this->context) { + $handle = @\opendir($path); + } else { + $handle = @\opendir($path, $this->context); + } + + if (false === $handle) { + return false; + } + + $this->_stream = $handle; + $this->_streamName = $path; + + return true; + } + + /** + * Read entry from directory handle. + * This method is called in response to readdir(). + * + * @return mixed + */ + public function dir_readdir() + { + return \readdir($this->getStream()); + } + + /** + * Rewind directory handle. + * This method is called in response to rewinddir(). + * Should reset the output generated by self::dir_readdir, i.e. the next + * call to self::dir_readdir should return the first entry in the location + * returned by self::dir_opendir. + */ + public function dir_rewinddir() + { + \rewinddir($this->getStream()); + } + + /** + * Create a directory. + * This method is called in response to mkdir(). + */ + public function mkdir(string $path, int $mode, int $options): bool + { + if (null === $this->context) { + return \mkdir( + static::realPath($path, false), + $mode, + $options | \STREAM_MKDIR_RECURSIVE + ); + } + + return \mkdir( + static::realPath($path, false), + $mode, + (bool) ($options | \STREAM_MKDIR_RECURSIVE), + $this->context + ); + } + + /** + * Rename a file or directory. + * This method is called in response to rename(). + * Should attempt to rename $from to $to. + */ + public function rename(string $from, string $to): bool + { + if (null === $this->context) { + return \rename(static::realPath($from), static::realPath($to, false)); + } + + return \rename( + static::realPath($from), + static::realPath($to, false), + $this->context + ); + } + + /** + * Remove a directory. + * This method is called in response to rmdir(). + * The `$options` input is a bitwise mask of values. It is not used here. + */ + public function rmdir(string $path, int $options): bool + { + if (null === $this->context) { + return \rmdir(static::realPath($path)); + } + + return \rmdir(static::realPath($path), $this->context); + } + + /** + * Delete a file. + * This method is called in response to unlink(). + */ + public function unlink(string $path): bool + { + if (null === $this->context) { + return \unlink(static::realPath($path)); + } + + return \unlink(static::realPath($path), $this->context); + } + + /** + * Retrieve information about a file. + * This method is called in response to all stat() related functions. + * The `$flags` input holds additional flags set by the streams API. It + * can hold one or more of the following values OR'd together. + * STREAM_URL_STAT_LINK: for resource with the ability to link to other + * resource (such as an HTTP location: forward, or a filesystem + * symlink). This flag specified that only information about the link + * itself should be returned, not the resource pointed to by the + * link. This flag is set in response to calls to lstat(), is_link(), or + * filetype(). STREAM_URL_STAT_QUIET: if this flag is set, our wrapper + * should not raise any errors. If this flag is not set, we are + * responsible for reporting errors using the trigger_error() function + * during stating of the path. + */ + public function url_stat(string $path, int $flags) + { + $path = static::realPath($path); + + if (Protocol::NO_RESOLUTION === $path) { + if ($flags & \STREAM_URL_STAT_QUIET) { + return 0; + } else { + return \trigger_error( + 'Path '.$path.' cannot be resolved.', + \E_WARNING + ); + } + } + + if ($flags & \STREAM_URL_STAT_LINK) { + return @\lstat($path); + } + + return @\stat($path); + } + + /** + * Get stream resource. + */ + public function getStream() + { + return $this->_stream; + } + + /** + * Get stream name. + */ + public function getStreamName() + { + return $this->_streamName; + } +} + +/* + * Register the `hoa://` protocol. + */ +\stream_wrapper_register('hoa', ProtocolWrapper::class); diff --git a/vendor/psy/psysh/src/Readline/Hoa/Readline.php b/vendor/psy/psysh/src/Readline/Hoa/Readline.php new file mode 100644 index 0000000000..c2a6064b23 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/Readline.php @@ -0,0 +1,1030 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Class \Hoa\Console\Readline. + * + * Read, edit, bind… a line from the input. + */ +class Readline +{ + /** + * State: continue to read. + */ + const STATE_CONTINUE = 1; + + /** + * State: stop to read. + */ + const STATE_BREAK = 2; + + /** + * State: no output the current buffer. + */ + const STATE_NO_ECHO = 4; + + /** + * Current editing line. + */ + protected $_line = null; + + /** + * Current editing line seek. + */ + protected $_lineCurrent = 0; + + /** + * Current editing line length. + */ + protected $_lineLength = 0; + + /** + * Current buffer (most of the time, a char). + */ + protected $_buffer = null; + + /** + * Mapping. + */ + protected $_mapping = []; + + /** + * History. + */ + protected $_history = []; + + /** + * History current position. + */ + protected $_historyCurrent = 0; + + /** + * History size. + */ + protected $_historySize = 0; + + /** + * Prefix. + */ + protected $_prefix = null; + + /** + * Autocompleter. + */ + protected $_autocompleter = null; + + /** + * Initialize the readline editor. + */ + public function __construct() + { + if (\defined('PHP_WINDOWS_VERSION_PLATFORM')) { + return; + } + + $this->_mapping["\033[A"] = [$this, '_bindArrowUp']; + $this->_mapping["\033[B"] = [$this, '_bindArrowDown']; + $this->_mapping["\033[C"] = [$this, '_bindArrowRight']; + $this->_mapping["\033[D"] = [$this, '_bindArrowLeft']; + $this->_mapping["\001"] = [$this, '_bindControlA']; + $this->_mapping["\002"] = [$this, '_bindControlB']; + $this->_mapping["\005"] = [$this, '_bindControlE']; + $this->_mapping["\006"] = [$this, '_bindControlF']; + $this->_mapping["\010"] = + $this->_mapping["\177"] = [$this, '_bindBackspace']; + $this->_mapping["\027"] = [$this, '_bindControlW']; + $this->_mapping["\n"] = [$this, '_bindNewline']; + $this->_mapping["\t"] = [$this, '_bindTab']; + + return; + } + + /** + * Read a line from the input. + */ + public function readLine(string $prefix = null) + { + $input = Console::getInput(); + + if (true === $input->eof()) { + return false; + } + + $direct = Console::isDirect($input->getStream()->getStream()); + $output = Console::getOutput(); + + if (false === $direct || \defined('PHP_WINDOWS_VERSION_PLATFORM')) { + $out = $input->readLine(); + + if (false === $out) { + return false; + } + + $out = \substr($out, 0, -1); + + if (true === $direct) { + $output->writeAll($prefix); + } else { + $output->writeAll($prefix.$out."\n"); + } + + return $out; + } + + $this->resetLine(); + $this->setPrefix($prefix); + $read = [$input->getStream()->getStream()]; + $output->writeAll($prefix); + + while (true) { + @\stream_select($read, $write, $except, 30, 0); + + if (empty($read)) { + $read = [$input->getStream()->getStream()]; + + continue; + } + + $char = $this->_read(); + $this->_buffer = $char; + $return = $this->_readLine($char); + + if (0 === ($return & self::STATE_NO_ECHO)) { + $output->writeAll($this->_buffer); + } + + if (0 !== ($return & self::STATE_BREAK)) { + break; + } + } + + return $this->getLine(); + } + + /** + * Readline core. + */ + public function _readLine(string $char) + { + if (isset($this->_mapping[$char]) && + \is_callable($this->_mapping[$char])) { + $mapping = $this->_mapping[$char]; + + return $mapping($this); + } + + if (isset($this->_mapping[$char])) { + $this->_buffer = $this->_mapping[$char]; + } elseif (false === Ustring::isCharPrintable($char)) { + ConsoleCursor::bip(); + + return static::STATE_CONTINUE | static::STATE_NO_ECHO; + } + + if ($this->getLineLength() === $this->getLineCurrent()) { + $this->appendLine($this->_buffer); + + return static::STATE_CONTINUE; + } + + $this->insertLine($this->_buffer); + $tail = \mb_substr( + $this->getLine(), + $this->getLineCurrent() - 1 + ); + $this->_buffer = "\033[K".$tail.\str_repeat( + "\033[D", + \mb_strlen($tail) - 1 + ); + + return static::STATE_CONTINUE; + } + + /** + * Add mappings. + */ + public function addMappings(array $mappings) + { + foreach ($mappings as $key => $mapping) { + $this->addMapping($key, $mapping); + } + } + + /** + * Add a mapping. + * Supported key: + * • \e[… for \033[…; + * • \C-… for Ctrl-…; + * • abc for a simple mapping. + * A mapping is a callable that has only one parameter of type + * Hoa\Console\Readline and that returns a self::STATE_* constant. + */ + public function addMapping(string $key, $mapping) + { + if ('\e[' === \substr($key, 0, 3)) { + $this->_mapping["\033[".\substr($key, 3)] = $mapping; + } elseif ('\C-' === \substr($key, 0, 3)) { + $_key = \ord(\strtolower(\substr($key, 3))) - 96; + $this->_mapping[\chr($_key)] = $mapping; + } else { + $this->_mapping[$key] = $mapping; + } + } + + /** + * Add an entry in the history. + */ + public function addHistory(string $line = null) + { + if (empty($line)) { + return; + } + + $this->_history[] = $line; + $this->_historyCurrent = $this->_historySize++; + } + + /** + * Clear history. + */ + public function clearHistory() + { + unset($this->_history); + $this->_history = []; + $this->_historyCurrent = 0; + $this->_historySize = 1; + } + + /** + * Get an entry in the history. + */ + public function getHistory(int $i = null) + { + if (null === $i) { + $i = $this->_historyCurrent; + } + + if (!isset($this->_history[$i])) { + return null; + } + + return $this->_history[$i]; + } + + /** + * Go backward in the history. + */ + public function previousHistory() + { + if (0 >= $this->_historyCurrent) { + return $this->getHistory(0); + } + + return $this->getHistory($this->_historyCurrent--); + } + + /** + * Go forward in the history. + */ + public function nextHistory() + { + if ($this->_historyCurrent + 1 >= $this->_historySize) { + return $this->getLine(); + } + + return $this->getHistory(++$this->_historyCurrent); + } + + /** + * Get current line. + */ + public function getLine() + { + return $this->_line; + } + + /** + * Append to current line. + */ + public function appendLine(string $append) + { + $this->_line .= $append; + $this->_lineLength = \mb_strlen($this->_line); + $this->_lineCurrent = $this->_lineLength; + } + + /** + * Insert into current line at the current seek. + */ + public function insertLine(string $insert) + { + if ($this->_lineLength === $this->_lineCurrent) { + return $this->appendLine($insert); + } + + $this->_line = \mb_substr($this->_line, 0, $this->_lineCurrent). + $insert. + \mb_substr($this->_line, $this->_lineCurrent); + $this->_lineLength = \mb_strlen($this->_line); + $this->_lineCurrent += \mb_strlen($insert); + + return; + } + + /** + * Reset current line. + */ + protected function resetLine() + { + $this->_line = null; + $this->_lineCurrent = 0; + $this->_lineLength = 0; + } + + /** + * Get current line seek. + */ + public function getLineCurrent(): int + { + return $this->_lineCurrent; + } + + /** + * Get current line length. + * + * @return int + */ + public function getLineLength(): int + { + return $this->_lineLength; + } + + /** + * Set prefix. + */ + public function setPrefix(string $prefix) + { + $this->_prefix = $prefix; + } + + /** + * Get prefix. + */ + public function getPrefix() + { + return $this->_prefix; + } + + /** + * Get buffer. Not for user. + */ + public function getBuffer() + { + return $this->_buffer; + } + + /** + * Set an autocompleter. + */ + public function setAutocompleter(Autocompleter $autocompleter) + { + $old = $this->_autocompleter; + $this->_autocompleter = $autocompleter; + + return $old; + } + + /** + * Get the autocompleter. + * + * @return ?Autocompleter + */ + public function getAutocompleter() + { + return $this->_autocompleter; + } + + /** + * Read on input. Not for user. + */ + public function _read(int $length = 512): string + { + return Console::getInput()->read($length); + } + + /** + * Set current line. Not for user. + */ + public function setLine(string $line) + { + $this->_line = $line; + $this->_lineLength = \mb_strlen($this->_line ?: ''); + $this->_lineCurrent = $this->_lineLength; + } + + /** + * Set current line seek. Not for user. + */ + public function setLineCurrent(int $current) + { + $this->_lineCurrent = $current; + } + + /** + * Set line length. Not for user. + */ + public function setLineLength(int $length) + { + $this->_lineLength = $length; + } + + /** + * Set buffer. Not for user. + */ + public function setBuffer(string $buffer) + { + $this->_buffer = $buffer; + } + + /** + * Up arrow binding. + * Go backward in the history. + */ + public function _bindArrowUp(self $self): int + { + if (0 === (static::STATE_CONTINUE & static::STATE_NO_ECHO)) { + ConsoleCursor::clear('↔'); + Console::getOutput()->writeAll($self->getPrefix()); + } + $buffer = $self->previousHistory() ?? ''; + $self->setBuffer($buffer); + $self->setLine($buffer); + + return static::STATE_CONTINUE; + } + + /** + * Down arrow binding. + * Go forward in the history. + */ + public function _bindArrowDown(self $self): int + { + if (0 === (static::STATE_CONTINUE & static::STATE_NO_ECHO)) { + ConsoleCursor::clear('↔'); + Console::getOutput()->writeAll($self->getPrefix()); + } + + $self->setBuffer($buffer = $self->nextHistory()); + $self->setLine($buffer); + + return static::STATE_CONTINUE; + } + + /** + * Right arrow binding. + * Move cursor to the right. + */ + public function _bindArrowRight(self $self): int + { + if ($self->getLineLength() > $self->getLineCurrent()) { + if (0 === (static::STATE_CONTINUE & static::STATE_NO_ECHO)) { + ConsoleCursor::move('→'); + } + + $self->setLineCurrent($self->getLineCurrent() + 1); + } + + $self->setBuffer(''); + + return static::STATE_CONTINUE; + } + + /** + * Left arrow binding. + * Move cursor to the left. + */ + public function _bindArrowLeft(self $self): int + { + if (0 < $self->getLineCurrent()) { + if (0 === (static::STATE_CONTINUE & static::STATE_NO_ECHO)) { + ConsoleCursor::move('←'); + } + + $self->setLineCurrent($self->getLineCurrent() - 1); + } + + $self->setBuffer(''); + + return static::STATE_CONTINUE; + } + + /** + * Backspace and Control-H binding. + * Delete the first character at the right of the cursor. + */ + public function _bindBackspace(self $self): int + { + $buffer = ''; + + if (0 < $self->getLineCurrent()) { + if (0 === (static::STATE_CONTINUE & static::STATE_NO_ECHO)) { + ConsoleCursor::move('←'); + ConsoleCursor::clear('→'); + } + + if ($self->getLineLength() === $current = $self->getLineCurrent()) { + $self->setLine(\mb_substr($self->getLine(), 0, -1)); + } else { + $line = $self->getLine(); + $current = $self->getLineCurrent(); + $tail = \mb_substr($line, $current); + $buffer = $tail.\str_repeat("\033[D", \mb_strlen($tail)); + $self->setLine(\mb_substr($line, 0, $current - 1).$tail); + $self->setLineCurrent($current - 1); + } + } + + $self->setBuffer($buffer); + + return static::STATE_CONTINUE; + } + + /** + * Control-A binding. + * Move cursor to beginning of line. + */ + public function _bindControlA(self $self): int + { + for ($i = $self->getLineCurrent() - 1; 0 <= $i; --$i) { + $self->_bindArrowLeft($self); + } + + return static::STATE_CONTINUE; + } + + /** + * Control-B binding. + * Move cursor backward one word. + */ + public function _bindControlB(self $self): int + { + $current = $self->getLineCurrent(); + + if (0 === $current) { + return static::STATE_CONTINUE; + } + + $words = \preg_split( + '#\b#u', + $self->getLine(), + -1, + \PREG_SPLIT_OFFSET_CAPTURE | \PREG_SPLIT_NO_EMPTY + ); + + for ( + $i = 0, $max = \count($words) - 1; + $i < $max && $words[$i + 1][1] < $current; + ++$i + ) { + } + + for ($j = $words[$i][1] + 1; $current >= $j; ++$j) { + $self->_bindArrowLeft($self); + } + + return static::STATE_CONTINUE; + } + + /** + * Control-E binding. + * Move cursor to end of line. + */ + public function _bindControlE(self $self): int + { + for ( + $i = $self->getLineCurrent(), $max = $self->getLineLength(); + $i < $max; + ++$i + ) { + $self->_bindArrowRight($self); + } + + return static::STATE_CONTINUE; + } + + /** + * Control-F binding. + * Move cursor forward one word. + */ + public function _bindControlF(self $self): int + { + $current = $self->getLineCurrent(); + + if ($self->getLineLength() === $current) { + return static::STATE_CONTINUE; + } + + $words = \preg_split( + '#\b#u', + $self->getLine(), + -1, + \PREG_SPLIT_OFFSET_CAPTURE | \PREG_SPLIT_NO_EMPTY + ); + + for ( + $i = 0, $max = \count($words) - 1; + $i < $max && $words[$i][1] < $current; + ++$i + ) { + } + + if (!isset($words[$i + 1])) { + $words[$i + 1] = [1 => $self->getLineLength()]; + } + + for ($j = $words[$i + 1][1]; $j > $current; --$j) { + $self->_bindArrowRight($self); + } + + return static::STATE_CONTINUE; + } + + /** + * Control-W binding. + * Delete first backward word. + */ + public function _bindControlW(self $self): int + { + $current = $self->getLineCurrent(); + + if (0 === $current) { + return static::STATE_CONTINUE; + } + + $words = \preg_split( + '#\b#u', + $self->getLine(), + -1, + \PREG_SPLIT_OFFSET_CAPTURE | \PREG_SPLIT_NO_EMPTY + ); + + for ( + $i = 0, $max = \count($words) - 1; + $i < $max && $words[$i + 1][1] < $current; + ++$i + ) { + } + + for ($j = $words[$i][1] + 1; $current >= $j; ++$j) { + $self->_bindBackspace($self); + } + + return static::STATE_CONTINUE; + } + + /** + * Newline binding. + */ + public function _bindNewline(self $self): int + { + $self->addHistory($self->getLine()); + + return static::STATE_BREAK; + } + + /** + * Tab binding. + */ + public function _bindTab(self $self): int + { + $output = Console::getOutput(); + $autocompleter = $self->getAutocompleter(); + $state = static::STATE_CONTINUE | static::STATE_NO_ECHO; + + if (null === $autocompleter) { + return $state; + } + + $current = $self->getLineCurrent(); + $line = $self->getLine(); + + if (0 === $current) { + return $state; + } + + $matches = \preg_match_all( + '#'.$autocompleter->getWordDefinition().'$#u', + \mb_substr($line, 0, $current), + $words + ); + + if (0 === $matches) { + return $state; + } + + $word = $words[0][0]; + + if ('' === \trim($word)) { + return $state; + } + + $solution = $autocompleter->complete($word); + $length = \mb_strlen($word); + + if (null === $solution) { + return $state; + } + + if (\is_array($solution)) { + $_solution = $solution; + $count = \count($_solution) - 1; + $cWidth = 0; + $window = ConsoleWindow::getSize(); + $wWidth = $window['x']; + $cursor = ConsoleCursor::getPosition(); + + \array_walk($_solution, function (&$value) use (&$cWidth) { + $handle = \mb_strlen($value); + + if ($handle > $cWidth) { + $cWidth = $handle; + } + + return; + }); + \array_walk($_solution, function (&$value) use (&$cWidth) { + $handle = \mb_strlen($value); + + if ($handle >= $cWidth) { + return; + } + + $value .= \str_repeat(' ', $cWidth - $handle); + + return; + }); + + $mColumns = (int) \floor($wWidth / ($cWidth + 2)); + $mLines = (int) \ceil(($count + 1) / $mColumns); + --$mColumns; + $i = 0; + + if (0 > $window['y'] - $cursor['y'] - $mLines) { + ConsoleWindow::scroll('↑', $mLines); + ConsoleCursor::move('↑', $mLines); + } + + ConsoleCursor::save(); + ConsoleCursor::hide(); + ConsoleCursor::move('↓ LEFT'); + ConsoleCursor::clear('↓'); + + foreach ($_solution as $j => $s) { + $output->writeAll("\033[0m".$s."\033[0m"); + + if ($i++ < $mColumns) { + $output->writeAll(' '); + } else { + $i = 0; + + if (isset($_solution[$j + 1])) { + $output->writeAll("\n"); + } + } + } + + ConsoleCursor::restore(); + ConsoleCursor::show(); + + ++$mColumns; + $input = Console::getInput(); + $read = [$input->getStream()->getStream()]; + $mColumn = -1; + $mLine = -1; + $coord = -1; + $unselect = function () use ( + &$mColumn, + &$mLine, + &$coord, + &$_solution, + &$cWidth, + $output + ) { + ConsoleCursor::save(); + ConsoleCursor::hide(); + ConsoleCursor::move('↓ LEFT'); + ConsoleCursor::move('→', $mColumn * ($cWidth + 2)); + ConsoleCursor::move('↓', $mLine); + $output->writeAll("\033[0m".$_solution[$coord]."\033[0m"); + ConsoleCursor::restore(); + ConsoleCursor::show(); + + return; + }; + $select = function () use ( + &$mColumn, + &$mLine, + &$coord, + &$_solution, + &$cWidth, + $output + ) { + ConsoleCursor::save(); + ConsoleCursor::hide(); + ConsoleCursor::move('↓ LEFT'); + ConsoleCursor::move('→', $mColumn * ($cWidth + 2)); + ConsoleCursor::move('↓', $mLine); + $output->writeAll("\033[7m".$_solution[$coord]."\033[0m"); + ConsoleCursor::restore(); + ConsoleCursor::show(); + + return; + }; + $init = function () use ( + &$mColumn, + &$mLine, + &$coord, + &$select + ) { + $mColumn = 0; + $mLine = 0; + $coord = 0; + $select(); + + return; + }; + + while (true) { + @\stream_select($read, $write, $except, 30, 0); + + if (empty($read)) { + $read = [$input->getStream()->getStream()]; + + continue; + } + + switch ($char = $self->_read()) { + case "\033[A": + if (-1 === $mColumn && -1 === $mLine) { + $init(); + + break; + } + + $unselect(); + $coord = \max(0, $coord - $mColumns); + $mLine = (int) \floor($coord / $mColumns); + $mColumn = $coord % $mColumns; + $select(); + + break; + + case "\033[B": + if (-1 === $mColumn && -1 === $mLine) { + $init(); + + break; + } + + $unselect(); + $coord = \min($count, $coord + $mColumns); + $mLine = (int) \floor($coord / $mColumns); + $mColumn = $coord % $mColumns; + $select(); + + break; + + case "\t": + case "\033[C": + if (-1 === $mColumn && -1 === $mLine) { + $init(); + + break; + } + + $unselect(); + $coord = \min($count, $coord + 1); + $mLine = (int) \floor($coord / $mColumns); + $mColumn = $coord % $mColumns; + $select(); + + break; + + case "\033[D": + if (-1 === $mColumn && -1 === $mLine) { + $init(); + + break; + } + + $unselect(); + $coord = \max(0, $coord - 1); + $mLine = (int) \floor($coord / $mColumns); + $mColumn = $coord % $mColumns; + $select(); + + break; + + case "\n": + if (-1 !== $mColumn && -1 !== $mLine) { + $tail = \mb_substr($line, $current); + $current -= $length; + $self->setLine( + \mb_substr($line, 0, $current). + $solution[$coord]. + $tail + ); + $self->setLineCurrent( + $current + \mb_strlen($solution[$coord]) + ); + + ConsoleCursor::move('←', $length); + $output->writeAll($solution[$coord]); + ConsoleCursor::clear('→'); + $output->writeAll($tail); + ConsoleCursor::move('←', \mb_strlen($tail)); + } + + // no break + default: + $mColumn = -1; + $mLine = -1; + $coord = -1; + ConsoleCursor::save(); + ConsoleCursor::move('↓ LEFT'); + ConsoleCursor::clear('↓'); + ConsoleCursor::restore(); + + if ("\033" !== $char && "\n" !== $char) { + $self->setBuffer($char); + + return $self->_readLine($char); + } + + break 2; + } + } + + return $state; + } + + $tail = \mb_substr($line, $current); + $current -= $length; + $self->setLine( + \mb_substr($line, 0, $current). + $solution. + $tail + ); + $self->setLineCurrent( + $current + \mb_strlen($solution) + ); + + ConsoleCursor::move('←', $length); + $output->writeAll($solution); + ConsoleCursor::clear('→'); + $output->writeAll($tail); + ConsoleCursor::move('←', \mb_strlen($tail)); + + return $state; + } +} + +/* + * Advanced interaction. + */ +Console::advancedInteraction(); diff --git a/vendor/psy/psysh/src/Readline/Hoa/Stream.php b/vendor/psy/psysh/src/Readline/Hoa/Stream.php new file mode 100644 index 0000000000..62e56ce3f0 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/Stream.php @@ -0,0 +1,571 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Class \Hoa\Stream. + * + * Static register for all streams (files, sockets etc.). + */ +abstract class Stream implements IStream, EventListenable +{ + use EventListens; + + /** + * Name index in the stream bucket. + */ + const NAME = 0; + + /** + * Handler index in the stream bucket. + */ + const HANDLER = 1; + + /** + * Resource index in the stream bucket. + */ + const RESOURCE = 2; + + /** + * Context index in the stream bucket. + */ + const CONTEXT = 3; + + /** + * Default buffer size. + */ + const DEFAULT_BUFFER_SIZE = 8192; + + /** + * Current stream bucket. + */ + protected $_bucket = []; + + /** + * Static stream register. + */ + private static $_register = []; + + /** + * Buffer size (default is 8Ko). + */ + protected $_bufferSize = self::DEFAULT_BUFFER_SIZE; + + /** + * Original stream name, given to the stream constructor. + */ + protected $_streamName = null; + + /** + * Context name. + */ + protected $_context = null; + + /** + * Whether the opening has been deferred. + */ + protected $_hasBeenDeferred = false; + + /** + * Whether this stream is already opened by another handler. + */ + protected $_borrowing = false; + + /** + * Set the current stream. + * If not exists in the register, try to call the + * `$this->_open()` method. Please, see the `self::_getStream()` method. + */ + public function __construct(string $streamName, string $context = null, bool $wait = false) + { + $this->_streamName = $streamName; + $this->_context = $context; + $this->_hasBeenDeferred = $wait; + $this->setListener( + new EventListener( + $this, + [ + 'authrequire', + 'authresult', + 'complete', + 'connect', + 'failure', + 'mimetype', + 'progress', + 'redirect', + 'resolve', + 'size', + ] + ) + ); + + if (true === $wait) { + return; + } + + $this->open(); + + return; + } + + /** + * Get a stream in the register. + * If the stream does not exist, try to open it by calling the + * $handler->_open() method. + */ + private static function &_getStream( + string $streamName, + self $handler, + string $context = null + ): array { + $name = \md5($streamName); + + if (null !== $context) { + if (false === StreamContext::contextExists($context)) { + throw new StreamException('Context %s was not previously declared, cannot retrieve '.'this context.', 0, $context); + } + + $context = StreamContext::getInstance($context); + } + + if (!isset(self::$_register[$name])) { + self::$_register[$name] = [ + self::NAME => $streamName, + self::HANDLER => $handler, + self::RESOURCE => $handler->_open($streamName, $context), + self::CONTEXT => $context, + ]; + Event::register( + 'hoa://Event/Stream/'.$streamName, + $handler + ); + // Add :open-ready? + Event::register( + 'hoa://Event/Stream/'.$streamName.':close-before', + $handler + ); + } else { + $handler->_borrowing = true; + } + + if (null === self::$_register[$name][self::RESOURCE]) { + self::$_register[$name][self::RESOURCE] + = $handler->_open($streamName, $context); + } + + return self::$_register[$name]; + } + + /** + * Open the stream and return the associated resource. + * Note: This method is protected, but do not forget that it could be + * overloaded into a public context. + */ + abstract protected function &_open(string $streamName, StreamContext $context = null); + + /** + * Close the current stream. + * Note: this method is protected, but do not forget that it could be + * overloaded into a public context. + */ + abstract protected function _close(): bool; + + /** + * Open the stream. + */ + final public function open(): self + { + $context = $this->_context; + + if (true === $this->hasBeenDeferred()) { + if (null === $context) { + $handle = StreamContext::getInstance(\uniqid()); + $handle->setParameters([ + 'notification' => [$this, '_notify'], + ]); + $context = $handle->getId(); + } elseif (true === StreamContext::contextExists($context)) { + $handle = StreamContext::getInstance($context); + $parameters = $handle->getParameters(); + + if (!isset($parameters['notification'])) { + $handle->setParameters([ + 'notification' => [$this, '_notify'], + ]); + } + } + } + + $this->_bufferSize = self::DEFAULT_BUFFER_SIZE; + $this->_bucket = self::_getStream( + $this->_streamName, + $this, + $context + ); + + return $this; + } + + /** + * Close the current stream. + */ + final public function close() + { + $streamName = $this->getStreamName(); + + if (null === $streamName) { + return; + } + + $name = \md5($streamName); + + if (!isset(self::$_register[$name])) { + return; + } + + Event::notify( + 'hoa://Event/Stream/'.$streamName.':close-before', + $this, + new EventBucket() + ); + + if (false === $this->_close()) { + return; + } + + unset(self::$_register[$name]); + $this->_bucket[self::HANDLER] = null; + Event::unregister( + 'hoa://Event/Stream/'.$streamName + ); + Event::unregister( + 'hoa://Event/Stream/'.$streamName.':close-before' + ); + + return; + } + + /** + * Get the current stream name. + */ + public function getStreamName() + { + if (empty($this->_bucket)) { + return null; + } + + return $this->_bucket[self::NAME]; + } + + /** + * Get the current stream. + */ + public function getStream() + { + if (empty($this->_bucket)) { + return null; + } + + return $this->_bucket[self::RESOURCE]; + } + + /** + * Get the current stream context. + */ + public function getStreamContext() + { + if (empty($this->_bucket)) { + return null; + } + + return $this->_bucket[self::CONTEXT]; + } + + /** + * Get stream handler according to its name. + */ + public static function getStreamHandler(string $streamName) + { + $name = \md5($streamName); + + if (!isset(self::$_register[$name])) { + return null; + } + + return self::$_register[$name][self::HANDLER]; + } + + /** + * Set the current stream. Useful to manage a stack of streams (e.g. socket + * and select). Notice that it could be unsafe to use this method without + * taking time to think about it two minutes. Resource of type “Unknown” is + * considered as valid. + */ + public function _setStream($stream) + { + if (false === \is_resource($stream) && + ('resource' !== \gettype($stream) || + 'Unknown' !== \get_resource_type($stream))) { + throw new StreamException('Try to change the stream resource with an invalid one; '.'given %s.', 1, \gettype($stream)); + } + + $old = $this->_bucket[self::RESOURCE]; + $this->_bucket[self::RESOURCE] = $stream; + + return $old; + } + + /** + * Check if the stream is opened. + */ + public function isOpened(): bool + { + return \is_resource($this->getStream()); + } + + /** + * Set the timeout period. + */ + public function setStreamTimeout(int $seconds, int $microseconds = 0): bool + { + return \stream_set_timeout($this->getStream(), $seconds, $microseconds); + } + + /** + * Whether the opening of the stream has been deferred. + */ + protected function hasBeenDeferred() + { + return $this->_hasBeenDeferred; + } + + /** + * Check whether the connection has timed out or not. + * This is basically a shortcut of `getStreamMetaData` + the `timed_out` + * index, but the resulting code is more readable. + */ + public function hasTimedOut(): bool + { + $metaData = $this->getStreamMetaData(); + + return true === $metaData['timed_out']; + } + + /** + * Set blocking/non-blocking mode. + */ + public function setStreamBlocking(bool $mode): bool + { + return \stream_set_blocking($this->getStream(), $mode); + } + + /** + * Set stream buffer. + * Output using fwrite() (or similar function) is normally buffered at 8 Ko. + * This means that if there are two processes wanting to write to the same + * output stream, each is paused after 8 Ko of data to allow the other to + * write. + */ + public function setStreamBuffer(int $buffer): bool + { + // Zero means success. + $out = 0 === \stream_set_write_buffer($this->getStream(), $buffer); + + if (true === $out) { + $this->_bufferSize = $buffer; + } + + return $out; + } + + /** + * Disable stream buffering. + * Alias of $this->setBuffer(0). + */ + public function disableStreamBuffer(): bool + { + return $this->setStreamBuffer(0); + } + + /** + * Get stream buffer size. + */ + public function getStreamBufferSize(): int + { + return $this->_bufferSize; + } + + /** + * Get stream wrapper name. + */ + public function getStreamWrapperName(): string + { + if (false === $pos = \strpos($this->getStreamName(), '://')) { + return 'file'; + } + + return \substr($this->getStreamName(), 0, $pos); + } + + /** + * Get stream meta data. + */ + public function getStreamMetaData(): array + { + return \stream_get_meta_data($this->getStream()); + } + + /** + * Whether this stream is already opened by another handler. + */ + public function isBorrowing(): bool + { + return $this->_borrowing; + } + + /** + * Notification callback. + */ + public function _notify( + int $ncode, + int $severity, + $message, + $code, + $transferred, + $max + ) { + static $_map = [ + \STREAM_NOTIFY_AUTH_REQUIRED => 'authrequire', + \STREAM_NOTIFY_AUTH_RESULT => 'authresult', + \STREAM_NOTIFY_COMPLETED => 'complete', + \STREAM_NOTIFY_CONNECT => 'connect', + \STREAM_NOTIFY_FAILURE => 'failure', + \STREAM_NOTIFY_MIME_TYPE_IS => 'mimetype', + \STREAM_NOTIFY_PROGRESS => 'progress', + \STREAM_NOTIFY_REDIRECTED => 'redirect', + \STREAM_NOTIFY_RESOLVE => 'resolve', + \STREAM_NOTIFY_FILE_SIZE_IS => 'size', + ]; + + $this->getListener()->fire($_map[$ncode], new EventBucket([ + 'code' => $code, + 'severity' => $severity, + 'message' => $message, + 'transferred' => $transferred, + 'max' => $max, + ])); + } + + /** + * Call the $handler->close() method on each stream in the static stream + * register. + * This method does not check the return value of $handler->close(). Thus, + * if a stream is persistent, the $handler->close() should do anything. It + * is a very generic method. + */ + final public static function _Hoa_Stream() + { + foreach (self::$_register as $entry) { + $entry[self::HANDLER]->close(); + } + + return; + } + + /** + * Transform object to string. + */ + public function __toString(): string + { + return $this->getStreamName(); + } + + /** + * Close the stream when destructing. + */ + public function __destruct() + { + if (false === $this->isOpened()) { + return; + } + + $this->close(); + + return; + } +} + +/** + * Class \Hoa\Stream\_Protocol. + * + * The `hoa://Library/Stream` node. + * + * @license New BSD License + */ +class _Protocol extends ProtocolNode +{ + /** + * Component's name. + * + * @var string + */ + protected $_name = 'Stream'; + + /** + * ID of the component. + * + * @param string $id ID of the component + * + * @return mixed + */ + public function reachId(string $id) + { + return Stream::getStreamHandler($id); + } +} + +/* + * Shutdown method. + */ +\register_shutdown_function([Stream::class, '_Hoa_Stream']); + +/** + * Add the `hoa://Library/Stream` node. Should be use to reach/get an entry + * in the stream register. + */ +$protocol = Protocol::getInstance(); +$protocol['Library'][] = new _Protocol(); diff --git a/vendor/psy/psysh/src/Readline/Hoa/StreamBufferable.php b/vendor/psy/psysh/src/Readline/Hoa/StreamBufferable.php new file mode 100644 index 0000000000..e431021a85 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/StreamBufferable.php @@ -0,0 +1,73 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Interface \Hoa\Stream\IStream\Bufferable. + * + * Interface for bufferable streams. It's complementary to native buffer support + * of Hoa\Stream (please, see *StreamBuffer*() methods). Classes implementing + * this interface are able to create nested buffers, flush them etc. + */ +interface StreamBufferable extends IStream +{ + /** + * Start a new buffer. + * The callable acts like a light filter. + */ + public function newBuffer($callable = null, int $size = null): int; + + /** + * Flush the buffer. + */ + public function flush(); + + /** + * Delete buffer. + */ + public function deleteBuffer(): bool; + + /** + * Get bufffer level. + */ + public function getBufferLevel(): int; + + /** + * Get buffer size. + */ + public function getBufferSize(): int; +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/StreamContext.php b/vendor/psy/psysh/src/Readline/Hoa/StreamContext.php new file mode 100644 index 0000000000..7e36e9b09b --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/StreamContext.php @@ -0,0 +1,136 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Class \Hoa\Stream\Context. + * + * Make a multiton of stream contexts. + */ +class StreamContext +{ + /** + * Context ID. + */ + protected $_id = null; + + /** + * Multiton. + */ + protected static $_instances = []; + + /** + * Construct a context. + */ + protected function __construct($id) + { + $this->_id = $id; + $this->_context = \stream_context_create(); + + return; + } + + /** + * Multiton. + */ + public static function getInstance(string $id): self + { + if (false === static::contextExists($id)) { + static::$_instances[$id] = new static($id); + } + + return static::$_instances[$id]; + } + + /** + * Get context ID. + */ + public function getId(): string + { + return $this->_id; + } + + /** + * Check if a context exists. + */ + public static function contextExists(string $id): bool + { + return \array_key_exists($id, static::$_instances); + } + + /** + * Set options. + * Please, see http://php.net/context. + */ + public function setOptions(array $options): bool + { + return \stream_context_set_option($this->getContext(), $options); + } + + /** + * Set parameters. + * Please, see http://php.net/context.params. + */ + public function setParameters(array $parameters): bool + { + return \stream_context_set_params($this->getContext(), $parameters); + } + + /** + * Get options. + */ + public function getOptions(): array + { + return \stream_context_get_options($this->getContext()); + } + + /** + * Get parameters. + */ + public function getParameters(): array + { + return \stream_context_get_params($this->getContext()); + } + + /** + * Get context as a resource. + */ + public function getContext() + { + return $this->_context; + } +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/StreamException.php b/vendor/psy/psysh/src/Readline/Hoa/StreamException.php new file mode 100644 index 0000000000..21da03cfb9 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/StreamException.php @@ -0,0 +1,46 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Class \Hoa\Stream\Exception. + * + * Extending the \Hoa\Exception\Exception class. + */ +class StreamException extends Exception +{ +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/StreamIn.php b/vendor/psy/psysh/src/Readline/Hoa/StreamIn.php new file mode 100644 index 0000000000..1ced9135b8 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/StreamIn.php @@ -0,0 +1,102 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Interface \Hoa\Stream\IStream\In. + * + * Interface for input. + */ +interface StreamIn extends IStream +{ + /** + * Test for end-of-stream. + */ + public function eof(): bool; + + /** + * Read n characters. + */ + public function read(int $length); + + /** + * Alias of $this->read(). + */ + public function readString(int $length); + + /** + * Read a character. + * It could be equivalent to $this->read(1). + */ + public function readCharacter(); + + /** + * Read a boolean. + */ + public function readBoolean(); + + /** + * Read an integer. + */ + public function readInteger(int $length = 1); + + /** + * Read a float. + */ + public function readFloat(int $length = 1); + + /** + * Read an array. + * In most cases, it could be an alias to the $this->scanf() method. + */ + public function readArray(); + + /** + * Read a line. + */ + public function readLine(); + + /** + * Read all, i.e. read as much as possible. + */ + public function readAll(int $offset = 0); + + /** + * Parse input from a stream according to a format. + */ + public function scanf(string $format): array; +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/StreamLockable.php b/vendor/psy/psysh/src/Readline/Hoa/StreamLockable.php new file mode 100644 index 0000000000..c19c4dba00 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/StreamLockable.php @@ -0,0 +1,85 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Interface \Hoa\Stream\IStream\Lockable. + * + * Interface for lockable input/output. + * + * @license New BSD License + */ +interface StreamLockable extends IStream +{ + /** + * Acquire a shared lock (reader). + * + * @const int + */ + const LOCK_SHARED = \LOCK_SH; + + /** + * Acquire an exclusive lock (writer). + * + * @const int + */ + const LOCK_EXCLUSIVE = \LOCK_EX; + + /** + * Release a lock (shared or exclusive). + * + * @const int + */ + const LOCK_RELEASE = \LOCK_UN; + + /** + * If we do not want $this->lock() to block while locking. + * + * @const int + */ + const LOCK_NO_BLOCK = \LOCK_NB; + + /** + * Portable advisory locking. + * Should take a look at stream_supports_lock(). + * + * @param int $operation operation, use the self::LOCK_* constants + * + * @return bool + */ + public function lock(int $operation): bool; +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/StreamOut.php b/vendor/psy/psysh/src/Readline/Hoa/StreamOut.php new file mode 100644 index 0000000000..e4bb925e15 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/StreamOut.php @@ -0,0 +1,95 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Interface \Hoa\Stream\IStream\Out. + * + * Interface for output. + */ +interface StreamOut extends IStream +{ + /** + * Write n characters. + */ + public function write(string $string, int $length); + + /** + * Write a string. + */ + public function writeString(string $string); + + /** + * Write a character. + */ + public function writeCharacter(string $character); + + /** + * Write a boolean. + */ + public function writeBoolean(bool $boolean); + + /** + * Write an integer. + */ + public function writeInteger(int $integer); + + /** + * Write a float. + */ + public function writeFloat(float $float); + + /** + * Write an array. + */ + public function writeArray(array $array); + + /** + * Write a line. + */ + public function writeLine(string $line); + + /** + * Write all, i.e. as much as possible. + */ + public function writeAll(string $string); + + /** + * Truncate a stream to a given length. + */ + public function truncate(int $size): bool; +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/StreamPathable.php b/vendor/psy/psysh/src/Readline/Hoa/StreamPathable.php new file mode 100644 index 0000000000..558684aedc --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/StreamPathable.php @@ -0,0 +1,55 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Interface \Hoa\Stream\IStream\Pathable. + * + * Interface for pathable input/output. + */ +interface StreamPathable extends IStream +{ + /** + * Get filename component of path. + */ + public function getBasename(): string; + + /** + * Get directory name component of path. + */ + public function getDirname(): string; +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/StreamPointable.php b/vendor/psy/psysh/src/Readline/Hoa/StreamPointable.php new file mode 100644 index 0000000000..4030acbd35 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/StreamPointable.php @@ -0,0 +1,75 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Interface \Hoa\Stream\IStream\Pointable. + * + * Interface for pointable input/output. + */ +interface StreamPointable extends IStream +{ + /** + * Set position equal to $offset bytes. + */ + const SEEK_SET = \SEEK_SET; + + /** + * Set position to current location plus $offset. + */ + const SEEK_CURRENT = \SEEK_CUR; + + /** + * Set position to end-of-file plus $offset. + */ + const SEEK_END = \SEEK_END; + + /** + * Rewind the position of a stream pointer. + */ + public function rewind(): bool; + + /** + * Seek on a stream pointer. + */ + public function seek(int $offset, int $whence = self::SEEK_SET): int; + + /** + * Get the current position of the stream pointer. + */ + public function tell(): int; +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/StreamStatable.php b/vendor/psy/psysh/src/Readline/Hoa/StreamStatable.php new file mode 100644 index 0000000000..9b83696db2 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/StreamStatable.php @@ -0,0 +1,115 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Interface \Hoa\Stream\IStream\Statable. + * + * Interface for statable input/output. + */ +interface StreamStatable extends IStream +{ + /** + * Size is undefined. + */ + const SIZE_UNDEFINED = -1; + + /** + * Get size. + */ + public function getSize(): int; + + /** + * Get informations about a file. + */ + public function getStatistic(): array; + + /** + * Get last access time of file. + */ + public function getATime(): int; + + /** + * Get inode change time of file. + */ + public function getCTime(): int; + + /** + * Get file modification time. + */ + public function getMTime(): int; + + /** + * Get file group. + */ + public function getGroup(): int; + + /** + * Get file owner. + */ + public function getOwner(): int; + + /** + * Get file permissions. + */ + public function getPermissions(): int; + + /** + * Check if the file is readable. + */ + public function isReadable(): bool; + + /** + * Check if the file is writable. + */ + public function isWritable(): bool; + + /** + * Check if the file is executable. + */ + public function isExecutable(): bool; + + /** + * Clear file status cache. + */ + public function clearStatisticCache(); + + /** + * Clear all files status cache. + */ + public static function clearAllStatisticCaches(); +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/StreamTouchable.php b/vendor/psy/psysh/src/Readline/Hoa/StreamTouchable.php new file mode 100644 index 0000000000..08b75255ff --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/StreamTouchable.php @@ -0,0 +1,110 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Interface \Hoa\Stream\IStream\Touchable. + * + * Interface for touchable input/output. + */ +interface StreamTouchable extends IStream +{ + /** + * Overwrite file if already exists. + */ + const OVERWRITE = true; + + /** + * Do not overwrite file if already exists. + */ + const DO_NOT_OVERWRITE = false; + + /** + * Make directory if does not exist. + */ + const MAKE_DIRECTORY = true; + + /** + * Do not make directory if does not exist. + */ + const DO_NOT_MAKE_DIRECTORY = false; + + /** + * Set access and modification time of file. + */ + public function touch(int $time = -1, int $atime = -1): bool; + + /** + * Copy file. + * Return the destination file path if succeed, false otherwise. + */ + public function copy(string $to, bool $force = self::DO_NOT_OVERWRITE): bool; + + /** + * Move a file. + */ + public function move( + string $name, + bool $force = self::DO_NOT_OVERWRITE, + bool $mkdir = self::DO_NOT_MAKE_DIRECTORY + ): bool; + + /** + * Delete a file. + */ + public function delete(): bool; + + /** + * Change file group. + */ + public function changeGroup($group): bool; + + /** + * Change file mode. + */ + public function changeMode(int $mode): bool; + + /** + * Change file owner. + */ + public function changeOwner($user): bool; + + /** + * Change the current umask. + */ + public static function umask(int $umask = null): int; +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/Terminfo/77/windows-ansi b/vendor/psy/psysh/src/Readline/Hoa/Terminfo/77/windows-ansi new file mode 100644 index 0000000000..50864235f4 Binary files /dev/null and b/vendor/psy/psysh/src/Readline/Hoa/Terminfo/77/windows-ansi differ diff --git a/vendor/psy/psysh/src/Readline/Hoa/Terminfo/78/xterm b/vendor/psy/psysh/src/Readline/Hoa/Terminfo/78/xterm new file mode 100644 index 0000000000..fec988defd Binary files /dev/null and b/vendor/psy/psysh/src/Readline/Hoa/Terminfo/78/xterm differ diff --git a/vendor/psy/psysh/src/Readline/Hoa/Terminfo/78/xterm-256color b/vendor/psy/psysh/src/Readline/Hoa/Terminfo/78/xterm-256color new file mode 100644 index 0000000000..d3be7ef313 Binary files /dev/null and b/vendor/psy/psysh/src/Readline/Hoa/Terminfo/78/xterm-256color differ diff --git a/vendor/psy/psysh/src/Readline/Hoa/Ustring.php b/vendor/psy/psysh/src/Readline/Hoa/Ustring.php new file mode 100644 index 0000000000..8d7312b9b8 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/Ustring.php @@ -0,0 +1,143 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * This class represents a UTF-8 string. + * Please, see: + * * http://www.ietf.org/rfc/rfc3454.txt, + * * http://unicode.org/reports/tr9/, + * * http://www.unicode.org/Public/6.0.0/ucd/UnicodeData.txt. + */ +class Ustring +{ + /** + * Check if ext/mbstring is available. + */ + public static function checkMbString(): bool + { + return \function_exists('mb_substr'); + } + + /** + * Get the number of column positions of a wide-character. + * + * This is a PHP implementation of wcwidth() and wcswidth() (defined in IEEE + * Std 1002.1-2001) for Unicode, by Markus Kuhn. Please, see + * http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c. + * + * The wcwidth(wc) function shall either return 0 (if wc is a null + * wide-character code), or return the number of column positions to be + * occupied by the wide-character code wc, or return -1 (if wc does not + * correspond to a printable wide-character code). + */ + public static function getCharWidth(string $char): int + { + $char = (string) $char; + $c = static::toCode($char); + + // Test for 8-bit control characters. + if (0x0 === $c) { + return 0; + } + + if (0x20 > $c || (0x7F <= $c && $c < 0xA0)) { + return -1; + } + + // Non-spacing characters. + if (0xAD !== $c && + 0 !== \preg_match('#^[\p{Mn}\p{Me}\p{Cf}\x{1160}-\x{11ff}\x{200b}]#u', $char)) { + return 0; + } + + // If we arrive here, $c is not a combining C0/C1 control character. + return 1 + + (0x1100 <= $c && + (0x115F >= $c || // Hangul Jamo init. consonants + 0x2329 === $c || 0x232A === $c || + (0x2E80 <= $c && 0xA4CF >= $c && + 0x303F !== $c) || // CJK…Yi + (0xAC00 <= $c && 0xD7A3 >= $c) || // Hangul Syllables + (0xF900 <= $c && 0xFAFF >= $c) || // CJK Compatibility Ideographs + (0xFE10 <= $c && 0xFE19 >= $c) || // Vertical forms + (0xFE30 <= $c && 0xFE6F >= $c) || // CJK Compatibility Forms + (0xFF00 <= $c && 0xFF60 >= $c) || // Fullwidth Forms + (0xFFE0 <= $c && 0xFFE6 >= $c) || + (0x20000 <= $c && 0x2FFFD >= $c) || + (0x30000 <= $c && 0x3FFFD >= $c))); + } + + /** + * Check whether the character is printable or not. + */ + public static function isCharPrintable(string $char): bool + { + return 1 <= static::getCharWidth($char); + } + + /** + * Get a decimal code representation of a specific character. + */ + public static function toCode(string $char): int + { + $char = (string) $char; + $code = \ord($char[0]); + $bytes = 1; + + if (!($code & 0x80)) { // 0xxxxxxx + return $code; + } + + if (($code & 0xE0) === 0xC0) { // 110xxxxx + $bytes = 2; + $code = $code & ~0xC0; + } elseif (($code & 0xF0) === 0xE0) { // 1110xxxx + $bytes = 3; + $code = $code & ~0xE0; + } elseif (($code & 0xF8) === 0xF0) { // 11110xxx + $bytes = 4; + $code = $code & ~0xF0; + } + + for ($i = 2; $i <= $bytes; $i++) { // 10xxxxxx + $code = ($code << 6) + (\ord($char[$i - 1]) & ~0x80); + } + + return $code; + } +} diff --git a/vendor/psy/psysh/src/Readline/Hoa/Xcallable.php b/vendor/psy/psysh/src/Readline/Hoa/Xcallable.php new file mode 100644 index 0000000000..fa7bc4700b --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Hoa/Xcallable.php @@ -0,0 +1,254 @@ +<?php + +/** + * Hoa + * + * + * @license + * + * New BSD License + * + * Copyright © 2007-2017, Hoa community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Hoa nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Psy\Readline\Hoa; + +/** + * Build a callable object, i.e. `function`, `class::method`, `object->method` or + * closure. They all have the same behaviour. This callable is an extension of + * native PHP callable (aka callback) to integrate Hoa's structures. + */ +class Xcallable +{ + /** + * Callback with the PHP format. + */ + protected $_callback = null; + + /** + * Callable hash. + */ + protected $_hash = null; + + /** + * Allocates a xcallable based on a callback. + * + * Accepted forms: + * * `'function'`, + * * `'class::method'`, + * * `'class', 'method'`, + * * `$object, 'method'`, + * * `$object, ''`, + * * `function (…) { … }`, + * * `['class', 'method']`, + * * `[$object, 'method']`. + * + * # Examples + * + * ```php + * $toUpper = new Hoa\Consistency\Xcallable('strtoupper'); + * assert('FOO' === $toUpper('foo')); + * ``` + * + * # Exceptions + * + * A `Hoa\Consistency\Exception` exception is thrown if the callback form + * is invalid. + * + * ```php,must_throw(Hoa\Consistency\Exception) + * new Hoa\Consistency\Xcallable('Foo:'); + * ``` + */ + public function __construct($call, $able = '') + { + if ($call instanceof \Closure) { + $this->_callback = $call; + + return; + } + + if (!\is_string($able)) { + throw new Exception('Bad callback form; the able part must be a string.', 0); + } + + if ('' === $able) { + if (\is_string($call)) { + if (false === \strpos($call, '::')) { + if (!\function_exists($call)) { + throw new Exception('Bad callback form; function %s does not exist.', 1, $call); + } + + $this->_callback = $call; + + return; + } + + list($call, $able) = \explode('::', $call); + } elseif (\is_object($call)) { + if ($call instanceof StreamOut) { + $able = null; + } elseif (\method_exists($call, '__invoke')) { + $able = '__invoke'; + } else { + throw new Exception('Bad callback form; an object but without a known '.'method.', 2); + } + } elseif (\is_array($call) && isset($call[0])) { + if (!isset($call[1])) { + return $this->__construct($call[0]); + } + + return $this->__construct($call[0], $call[1]); + } else { + throw new Exception('Bad callback form.', 3); + } + } + + $this->_callback = [$call, $able]; + + return; + } + + /** + * Calls the callable. + */ + public function __invoke(...$arguments) + { + $callback = $this->getValidCallback($arguments); + + return $callback(...$arguments); + } + + /** + * Returns a valid PHP callback. + */ + public function getValidCallback(array &$arguments = []) + { + $callback = $this->_callback; + $head = null; + + if (isset($arguments[0])) { + $head = &$arguments[0]; + } + + // If method is undetermined, we find it (we understand event bucket and + // stream). + if (null !== $head && + \is_array($callback) && + null === $callback[1]) { + if ($head instanceof EventBucket) { + $head = $head->getData(); + } + + switch ($type = \gettype($head)) { + case 'string': + if (1 === \strlen($head)) { + $method = 'writeCharacter'; + } else { + $method = 'writeString'; + } + + break; + + case 'boolean': + case 'integer': + case 'array': + $method = 'write'.\ucfirst($type); + + break; + + case 'double': + $method = 'writeFloat'; + + break; + + default: + $method = 'writeAll'; + $head = $head."\n"; + } + + $callback[1] = $method; + } + + return $callback; + } + + /** + * Computes the hash of this callable. + * + * Will produce: + * * `function#…`, + * * `class#…::…`, + * * `object(…)#…::…`, + * * `closure(…)`. + */ + public function getHash(): string + { + if (null !== $this->_hash) { + return $this->_hash; + } + + $_ = &$this->_callback; + + if (\is_string($_)) { + return $this->_hash = 'function#'.$_; + } + + if (\is_array($_)) { + return + $this->_hash = + (\is_object($_[0]) + ? 'object('.\spl_object_hash($_[0]).')'. + '#'.\get_class($_[0]) + : 'class#'.$_[0]). + '::'. + (null !== $_[1] + ? $_[1] + : '???'); + } + + return $this->_hash = 'closure('.\spl_object_hash($_).')'; + } + + /** + * The string representation of a callable is its hash. + */ + public function __toString(): string + { + return $this->getHash(); + } + + /** + * Hoa's xcallable() helper. + */ + public static function from($call, $able = '') + { + if ($call instanceof self) { + return $call; + } + + return new self($call, $able); + } +} diff --git a/vendor/psy/psysh/src/Readline/HoaConsole.php b/vendor/psy/psysh/src/Readline/HoaConsole.php new file mode 100644 index 0000000000..db0a07d02c --- /dev/null +++ b/vendor/psy/psysh/src/Readline/HoaConsole.php @@ -0,0 +1,21 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\Readline; + +/** + * Hoa\Console Readline implementation. + * + * @deprecated, use Userland readline + */ +class HoaConsole extends Userland +{ +} diff --git a/vendor/psy/psysh/src/Readline/Libedit.php b/vendor/psy/psysh/src/Readline/Libedit.php new file mode 100644 index 0000000000..a2f4815e10 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Libedit.php @@ -0,0 +1,118 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\Readline; + +use Psy\Util\Str; + +/** + * A Libedit-based Readline implementation. + * + * This is largely the same as the Readline implementation, but it emulates + * support for `readline_list_history` since PHP decided it was a good idea to + * ship a fake Readline implementation that is missing history support. + * + * NOTE: As of PHP 7.4, PHP sometimes has history support in the Libedit + * wrapper, so it will use the GNUReadline implementation rather than this one. + */ +class Libedit extends GNUReadline +{ + private $hasWarnedOwnership = false; + + /** + * Let's emulate GNU Readline by manually reading and parsing the history file! + * + * @return bool + */ + public static function isSupported(): bool + { + return \function_exists('readline') && !\function_exists('readline_list_history'); + } + + /** + * {@inheritdoc} + */ + public static function supportsBracketedPaste(): bool + { + return false; + } + + /** + * {@inheritdoc} + */ + public function listHistory(): array + { + $history = \file_get_contents($this->historyFile); + if (!$history) { + return []; + } + + // libedit doesn't seem to support non-unix line separators. + $history = \explode("\n", $history); + + // remove history signature if it exists + if ($history[0] === '_HiStOrY_V2_') { + \array_shift($history); + } + + // decode the line + $history = \array_map([$this, 'parseHistoryLine'], $history); + // filter empty lines & comments + return \array_values(\array_filter($history)); + } + + /** + * {@inheritdoc} + */ + public function writeHistory(): bool + { + $res = parent::writeHistory(); + + // Libedit apparently refuses to save history if the history file is not + // owned by the user, even if it is writable. Warn when this happens. + // + // See https://github.com/bobthecow/psysh/issues/552 + if ($res === false && !$this->hasWarnedOwnership) { + if (\is_file($this->historyFile) && \is_writable($this->historyFile)) { + $this->hasWarnedOwnership = true; + $msg = \sprintf('Error writing history file, check file ownership: %s', $this->historyFile); + \trigger_error($msg, \E_USER_NOTICE); + } + } + + return $res; + } + + /** + * From GNUReadline (readline/histfile.c & readline/histexpand.c): + * lines starting with "\0" are comments or timestamps; + * if "\0" is found in an entry, + * everything from it until the next line is a comment. + * + * @param string $line The history line to parse + * + * @return string|null + */ + protected function parseHistoryLine(string $line) + { + // empty line, comment or timestamp + if (!$line || $line[0] === "\0") { + return; + } + // if "\0" is found in an entry, then + // everything from it until the end of line is a comment. + if (($pos = \strpos($line, "\0")) !== false) { + $line = \substr($line, 0, $pos); + } + + return ($line !== '') ? Str::unvis($line) : null; + } +} diff --git a/vendor/psy/psysh/src/Readline/Readline.php b/vendor/psy/psysh/src/Readline/Readline.php new file mode 100644 index 0000000000..8a8f79bb5f --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Readline.php @@ -0,0 +1,83 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\Readline; + +/** + * An interface abstracting the various readline_* functions. + */ +interface Readline +{ + /** + * Check whether this Readline class is supported by the current system. + * + * @return bool + */ + public static function isSupported(): bool; + + /** + * Check whether this Readline class supports bracketed paste. + * + * @return bool + */ + public static function supportsBracketedPaste(): bool; + + /** + * Add a line to the command history. + * + * @param string $line + * + * @return bool Success + */ + public function addHistory(string $line): bool; + + /** + * Clear the command history. + * + * @return bool Success + */ + public function clearHistory(): bool; + + /** + * List the command history. + * + * @return array + */ + public function listHistory(): array; + + /** + * Read the command history. + * + * @return bool Success + */ + public function readHistory(): bool; + + /** + * Read a single line of input from the user. + * + * @param string|null $prompt + * + * @return false|string + */ + public function readline(string $prompt = null); + + /** + * Redraw readline to redraw the display. + */ + public function redisplay(); + + /** + * Write the command history to a file. + * + * @return bool Success + */ + public function writeHistory(): bool; +} diff --git a/vendor/psy/psysh/src/Readline/Transient.php b/vendor/psy/psysh/src/Readline/Transient.php new file mode 100644 index 0000000000..128ef8674c --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Transient.php @@ -0,0 +1,155 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\Readline; + +use Psy\Exception\BreakException; + +/** + * An array-based Readline emulation implementation. + */ +class Transient implements Readline +{ + private $history; + private $historySize; + private $eraseDups; + private $stdin; + + /** + * Transient Readline is always supported. + * + * {@inheritdoc} + */ + public static function isSupported(): bool + { + return true; + } + + /** + * {@inheritdoc} + */ + public static function supportsBracketedPaste(): bool + { + return false; + } + + /** + * Transient Readline constructor. + */ + public function __construct($historyFile = null, $historySize = 0, $eraseDups = false) + { + // don't do anything with the history file... + $this->history = []; + $this->historySize = $historySize; + $this->eraseDups = $eraseDups; + } + + /** + * {@inheritdoc} + */ + public function addHistory(string $line): bool + { + if ($this->eraseDups) { + if (($key = \array_search($line, $this->history)) !== false) { + unset($this->history[$key]); + } + } + + $this->history[] = $line; + + if ($this->historySize > 0) { + $histsize = \count($this->history); + if ($histsize > $this->historySize) { + $this->history = \array_slice($this->history, $histsize - $this->historySize); + } + } + + $this->history = \array_values($this->history); + + return true; + } + + /** + * {@inheritdoc} + */ + public function clearHistory(): bool + { + $this->history = []; + + return true; + } + + /** + * {@inheritdoc} + */ + public function listHistory(): array + { + return $this->history; + } + + /** + * {@inheritdoc} + */ + public function readHistory(): bool + { + return true; + } + + /** + * {@inheritdoc} + * + * @throws BreakException if user hits Ctrl+D + * + * @return false|string + */ + public function readline(string $prompt = null) + { + echo $prompt; + + return \rtrim(\fgets($this->getStdin()), "\n\r"); + } + + /** + * {@inheritdoc} + */ + public function redisplay() + { + // noop + } + + /** + * {@inheritdoc} + */ + public function writeHistory(): bool + { + return true; + } + + /** + * Get a STDIN file handle. + * + * @throws BreakException if user hits Ctrl+D + * + * @return resource + */ + private function getStdin() + { + if (!isset($this->stdin)) { + $this->stdin = \fopen('php://stdin', 'r'); + } + + if (\feof($this->stdin)) { + throw new BreakException('Ctrl+D'); + } + + return $this->stdin; + } +} diff --git a/vendor/psy/psysh/src/Readline/Userland.php b/vendor/psy/psysh/src/Readline/Userland.php new file mode 100644 index 0000000000..c9679b0691 --- /dev/null +++ b/vendor/psy/psysh/src/Readline/Userland.php @@ -0,0 +1,168 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\Readline; + +use Psy\Exception\BreakException; +use Psy\Readline\Hoa\Console as HoaConsole; +use Psy\Readline\Hoa\ConsoleCursor as HoaConsoleCursor; +use Psy\Readline\Hoa\ConsoleInput as HoaConsoleInput; +use Psy\Readline\Hoa\ConsoleOutput as HoaConsoleOutput; +use Psy\Readline\Hoa\ConsoleTput as HoaConsoleTput; +use Psy\Readline\Hoa\Readline as HoaReadline; +use Psy\Readline\Hoa\Ustring as HoaUstring; + +/** + * Userland Readline implementation. + */ +class Userland implements Readline +{ + /** @var HoaReadline */ + private $hoaReadline; + + /** @var string|null */ + private $lastPrompt; + + private $tput; + private $input; + private $output; + + /** + * @return bool + */ + public static function isSupported(): bool + { + static::bootstrapHoa(); + + return HoaUstring::checkMbString() && HoaConsoleTput::isSupported(); + } + + /** + * {@inheritdoc} + */ + public static function supportsBracketedPaste(): bool + { + return false; + } + + /** + * Doesn't (currently) support history file, size or erase dupes configs. + */ + public function __construct($historyFile = null, $historySize = 0, $eraseDups = false) + { + static::bootstrapHoa(true); + + $this->hoaReadline = new HoaReadline(); + $this->hoaReadline->addMapping('\C-l', function () { + $this->redisplay(); + + return HoaReadline::STATE_NO_ECHO; + }); + + $this->tput = new HoaConsoleTput(); + HoaConsole::setTput($this->tput); + + $this->input = new HoaConsoleInput(); + HoaConsole::setInput($this->input); + + $this->output = new HoaConsoleOutput(); + HoaConsole::setOutput($this->output); + } + + /** + * Bootstrap some things that Hoa used to do itself. + */ + public static function bootstrapHoa(bool $withTerminalResize = false) + { + // A side effect registers hoa:// stream wrapper + \class_exists('Psy\Readline\Hoa\ProtocolWrapper'); + + // A side effect registers hoa://Library/Stream + \class_exists('Psy\Readline\Hoa\Stream'); + + // A side effect binds terminal resize + $withTerminalResize && \class_exists('Psy\Readline\Hoa\ConsoleWindow'); + } + + /** + * {@inheritdoc} + */ + public function addHistory(string $line): bool + { + $this->hoaReadline->addHistory($line); + + return true; + } + + /** + * {@inheritdoc} + */ + public function clearHistory(): bool + { + $this->hoaReadline->clearHistory(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function listHistory(): array + { + $i = 0; + $list = []; + while (($item = $this->hoaReadline->getHistory($i++)) !== null) { + $list[] = $item; + } + + return $list; + } + + /** + * {@inheritdoc} + */ + public function readHistory(): bool + { + return true; + } + + /** + * {@inheritdoc} + * + * @throws BreakException if user hits Ctrl+D + * + * @return string + */ + public function readline(string $prompt = null) + { + $this->lastPrompt = $prompt; + + return $this->hoaReadline->readLine($prompt); + } + + /** + * {@inheritdoc} + */ + public function redisplay() + { + $currentLine = $this->hoaReadline->getLine(); + HoaConsoleCursor::clear('all'); + echo $this->lastPrompt, $currentLine; + } + + /** + * {@inheritdoc} + */ + public function writeHistory(): bool + { + return true; + } +} diff --git a/vendor/psy/psysh/src/Reflection/ReflectionClassConstant.php b/vendor/psy/psysh/src/Reflection/ReflectionClassConstant.php new file mode 100644 index 0000000000..7120a470ad --- /dev/null +++ b/vendor/psy/psysh/src/Reflection/ReflectionClassConstant.php @@ -0,0 +1,228 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\Reflection; + +/** + * Somehow the standard reflection library didn't include class constants until 7.1. + * + * ReflectionClassConstant corrects that omission. + */ +class ReflectionClassConstant implements \Reflector +{ + public $class; + public $name; + private $value; + + /** + * Construct a ReflectionClassConstant object. + * + * @param string|object $class + * @param string $name + */ + public function __construct($class, string $name) + { + if (!$class instanceof \ReflectionClass) { + $class = new \ReflectionClass($class); + } + + $this->class = $class; + $this->name = $name; + + $constants = $class->getConstants(); + if (!\array_key_exists($name, $constants)) { + throw new \InvalidArgumentException('Unknown constant: '.$name); + } + + $this->value = $constants[$name]; + } + + /** + * Exports a reflection. + * + * @param string|object $class + * @param string $name + * @param bool $return pass true to return the export, as opposed to emitting it + * + * @return string|null + */ + public static function export($class, string $name, bool $return = false) + { + $refl = new self($class, $name); + $value = $refl->getValue(); + + $str = \sprintf('Constant [ public %s %s ] { %s }', \gettype($value), $refl->getName(), $value); + + if ($return) { + return $str; + } + + echo $str."\n"; + } + + /** + * Gets the declaring class. + * + * @return \ReflectionClass + */ + public function getDeclaringClass(): \ReflectionClass + { + $parent = $this->class; + + // Since we don't have real reflection constants, we can't see where + // it's actually defined. Let's check for a constant that is also + // available on the parent class which has exactly the same value. + // + // While this isn't _technically_ correct, it's prolly close enough. + do { + $class = $parent; + $parent = $class->getParentClass(); + } while ($parent && $parent->hasConstant($this->name) && $parent->getConstant($this->name) === $this->value); + + return $class; + } + + /** + * Get the constant's docblock. + * + * @return false + */ + public function getDocComment(): bool + { + return false; + } + + /** + * Gets the class constant modifiers. + * + * Since this is only used for PHP < 7.1, we can just return "public". All + * the fancier modifiers are only available on PHP versions which have their + * own ReflectionClassConstant class :) + * + * @return int + */ + public function getModifiers(): int + { + return \ReflectionMethod::IS_PUBLIC; + } + + /** + * Gets the constant name. + * + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * Gets the value of the constant. + * + * @return mixed + */ + public function getValue() + { + return $this->value; + } + + /** + * Checks if class constant is private. + * + * @return bool false + */ + public function isPrivate(): bool + { + return false; + } + + /** + * Checks if class constant is protected. + * + * @return bool false + */ + public function isProtected(): bool + { + return false; + } + + /** + * Checks if class constant is public. + * + * @return bool true + */ + public function isPublic(): bool + { + return true; + } + + /** + * To string. + * + * @return string + */ + public function __toString(): string + { + return $this->getName(); + } + + /** + * Gets the constant's file name. + * + * Currently returns null, because if it returns a file name the signature + * formatter will barf. + */ + public function getFileName() + { + return; + // return $this->class->getFileName(); + } + + /** + * Get the code start line. + * + * @throws \RuntimeException + */ + public function getStartLine() + { + throw new \RuntimeException('Not yet implemented because it\'s unclear what I should do here :)'); + } + + /** + * Get the code end line. + * + * @throws \RuntimeException + */ + public function getEndLine() + { + return $this->getStartLine(); + } + + /** + * Get a ReflectionClassConstant instance. + * + * In PHP >= 7.1, this will return a \ReflectionClassConstant from the + * standard reflection library. For older PHP, it will return this polyfill. + * + * @param string|object $class + * @param string $name + * + * @return ReflectionClassConstant|\ReflectionClassConstant + */ + public static function create($class, string $name) + { + if (\class_exists(\ReflectionClassConstant::class)) { + return new \ReflectionClassConstant($class, $name); + } + + return new self($class, $name); + } +} diff --git a/vendor/psy/psysh/src/Reflection/ReflectionConstant.php b/vendor/psy/psysh/src/Reflection/ReflectionConstant.php new file mode 100644 index 0000000000..a56391b6b9 --- /dev/null +++ b/vendor/psy/psysh/src/Reflection/ReflectionConstant.php @@ -0,0 +1,30 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\Reflection; + +/** + * @deprecated ReflectionConstant is now ReflectionClassConstant. This class + * name will be reclaimed in the next stable release, to be used for + * ReflectionConstant_ :) + */ +class ReflectionConstant extends ReflectionClassConstant +{ + /** + * {inheritDoc}. + */ + public function __construct($class, $name) + { + @\trigger_error('ReflectionConstant is now ReflectionClassConstant', \E_USER_DEPRECATED); + + parent::__construct($class, $name); + } +} diff --git a/vendor/psy/psysh/src/Reflection/ReflectionConstant_.php b/vendor/psy/psysh/src/Reflection/ReflectionConstant_.php new file mode 100644 index 0000000000..f1df1805a9 --- /dev/null +++ b/vendor/psy/psysh/src/Reflection/ReflectionConstant_.php @@ -0,0 +1,182 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\Reflection; + +/** + * Somehow the standard reflection library doesn't include constants. + * + * ReflectionConstant_ corrects that omission. + * + * Note: For backwards compatibility reasons, this class is named + * ReflectionConstant_ rather than ReflectionConstant. It will be renamed in + * v0.10.0. + */ +class ReflectionConstant_ implements \Reflector +{ + public $name; + private $value; + + private static $magicConstants = [ + '__LINE__', + '__FILE__', + '__DIR__', + '__FUNCTION__', + '__CLASS__', + '__TRAIT__', + '__METHOD__', + '__NAMESPACE__', + '__COMPILER_HALT_OFFSET__', + ]; + + /** + * Construct a ReflectionConstant_ object. + * + * @param string $name + */ + public function __construct(string $name) + { + $this->name = $name; + + if (!\defined($name) && !self::isMagicConstant($name)) { + throw new \InvalidArgumentException('Unknown constant: '.$name); + } + + if (!self::isMagicConstant($name)) { + $this->value = @\constant($name); + } + } + + /** + * Exports a reflection. + * + * @param string $name + * @param bool $return pass true to return the export, as opposed to emitting it + * + * @return string|null + */ + public static function export(string $name, bool $return = false) + { + $refl = new self($name); + $value = $refl->getValue(); + + $str = \sprintf('Constant [ %s %s ] { %s }', \gettype($value), $refl->getName(), $value); + + if ($return) { + return $str; + } + + echo $str."\n"; + } + + public static function isMagicConstant($name) + { + return \in_array($name, self::$magicConstants); + } + + /** + * Get the constant's docblock. + * + * @return false + */ + public function getDocComment(): bool + { + return false; + } + + /** + * Gets the constant name. + * + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * Gets the namespace name. + * + * Returns '' when the constant is not namespaced. + * + * @return string + */ + public function getNamespaceName(): string + { + if (!$this->inNamespace()) { + return ''; + } + + return \preg_replace('/\\\\[^\\\\]+$/', '', $this->name); + } + + /** + * Gets the value of the constant. + * + * @return mixed + */ + public function getValue() + { + return $this->value; + } + + /** + * Checks if this constant is defined in a namespace. + * + * @return bool + */ + public function inNamespace(): bool + { + return \strpos($this->name, '\\') !== false; + } + + /** + * To string. + * + * @return string + */ + public function __toString(): string + { + return $this->getName(); + } + + /** + * Gets the constant's file name. + * + * Currently returns null, because if it returns a file name the signature + * formatter will barf. + */ + public function getFileName() + { + return; + // return $this->class->getFileName(); + } + + /** + * Get the code start line. + * + * @throws \RuntimeException + */ + public function getStartLine() + { + throw new \RuntimeException('Not yet implemented because it\'s unclear what I should do here :)'); + } + + /** + * Get the code end line. + * + * @throws \RuntimeException + */ + public function getEndLine() + { + return $this->getStartLine(); + } +} diff --git a/vendor/psy/psysh/src/Reflection/ReflectionLanguageConstruct.php b/vendor/psy/psysh/src/Reflection/ReflectionLanguageConstruct.php new file mode 100644 index 0000000000..3a9f4293c7 --- /dev/null +++ b/vendor/psy/psysh/src/Reflection/ReflectionLanguageConstruct.php @@ -0,0 +1,167 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\Reflection; + +/** + * A fake ReflectionFunction but for language constructs. + */ +class ReflectionLanguageConstruct extends \ReflectionFunctionAbstract +{ + public $keyword; + + /** + * Language construct parameter definitions. + */ + private static $languageConstructs = [ + 'isset' => [ + 'var' => [], + '...' => [ + 'isOptional' => true, + 'defaultValue' => null, + ], + ], + + 'unset' => [ + 'var' => [], + '...' => [ + 'isOptional' => true, + 'defaultValue' => null, + ], + ], + + 'empty' => [ + 'var' => [], + ], + + 'echo' => [ + 'arg1' => [], + '...' => [ + 'isOptional' => true, + 'defaultValue' => null, + ], + ], + + 'print' => [ + 'arg' => [], + ], + + 'die' => [ + 'status' => [ + 'isOptional' => true, + 'defaultValue' => 0, + ], + ], + + 'exit' => [ + 'status' => [ + 'isOptional' => true, + 'defaultValue' => 0, + ], + ], + ]; + + /** + * Construct a ReflectionLanguageConstruct object. + * + * @param string $keyword + */ + public function __construct(string $keyword) + { + if (!self::isLanguageConstruct($keyword)) { + throw new \InvalidArgumentException('Unknown language construct: '.$keyword); + } + + $this->keyword = $keyword; + } + + /** + * This can't (and shouldn't) do anything :). + * + * @throws \RuntimeException + */ + public static function export($name) + { + throw new \RuntimeException('Not yet implemented because it\'s unclear what I should do here :)'); + } + + /** + * Get language construct name. + * + * @return string + */ + public function getName(): string + { + return $this->keyword; + } + + /** + * None of these return references. + * + * @return bool + */ + public function returnsReference(): bool + { + return false; + } + + /** + * Get language construct params. + * + * @return array + */ + public function getParameters(): array + { + $params = []; + foreach (self::$languageConstructs[$this->keyword] as $parameter => $opts) { + $params[] = new ReflectionLanguageConstructParameter($this->keyword, $parameter, $opts); + } + + return $params; + } + + /** + * Gets the file name from a language construct. + * + * (Hint: it always returns false) + * + * @todo remove \ReturnTypeWillChange attribute after dropping support for PHP 7.x (when we can use union types) + * + * @return string|false (false) + */ + #[\ReturnTypeWillChange] + public function getFileName() + { + return false; + } + + /** + * To string. + * + * @return string + */ + public function __toString(): string + { + return $this->getName(); + } + + /** + * Check whether keyword is a (known) language construct. + * + * @param string $keyword + * + * @return bool + */ + public static function isLanguageConstruct(string $keyword): bool + { + return \array_key_exists($keyword, self::$languageConstructs); + } +} diff --git a/vendor/psy/psysh/src/Reflection/ReflectionLanguageConstructParameter.php b/vendor/psy/psysh/src/Reflection/ReflectionLanguageConstructParameter.php new file mode 100644 index 0000000000..2ce89f40c3 --- /dev/null +++ b/vendor/psy/psysh/src/Reflection/ReflectionLanguageConstructParameter.php @@ -0,0 +1,111 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\Reflection; + +/** + * A fake ReflectionParameter but for language construct parameters. + * + * It stubs out all the important bits and returns whatever was passed in $opts. + */ +class ReflectionLanguageConstructParameter extends \ReflectionParameter +{ + private $function; + private $parameter; + private $opts; + + public function __construct($function, $parameter, array $opts) + { + $this->function = $function; + $this->parameter = $parameter; + $this->opts = $opts; + } + + /** + * No class here. + * + * @todo remove \ReturnTypeWillChange attribute after dropping support for PHP 7.0 (when we can use nullable types) + */ + #[\ReturnTypeWillChange] + public function getClass() + { + return; + } + + /** + * Is the param an array? + * + * @return bool + */ + public function isArray(): bool + { + return \array_key_exists('isArray', $this->opts) && $this->opts['isArray']; + } + + /** + * Get param default value. + * + * @todo remove \ReturnTypeWillChange attribute after dropping support for PHP 7.x (when we can use mixed type) + * + * @return mixed + */ + #[\ReturnTypeWillChange] + public function getDefaultValue() + { + if ($this->isDefaultValueAvailable()) { + return $this->opts['defaultValue']; + } + + return null; + } + + /** + * Get param name. + * + * @return string + */ + public function getName(): string + { + return $this->parameter; + } + + /** + * Is the param optional? + * + * @return bool + */ + public function isOptional(): bool + { + return \array_key_exists('isOptional', $this->opts) && $this->opts['isOptional']; + } + + /** + * Does the param have a default value? + * + * @return bool + */ + public function isDefaultValueAvailable(): bool + { + return \array_key_exists('defaultValue', $this->opts); + } + + /** + * Is the param passed by reference? + * + * (I don't think this is true for anything we need to fake a param for) + * + * @return bool + */ + public function isPassedByReference(): bool + { + return \array_key_exists('isPassedByReference', $this->opts) && $this->opts['isPassedByReference']; + } +} diff --git a/vendor/psy/psysh/src/Reflection/ReflectionNamespace.php b/vendor/psy/psysh/src/Reflection/ReflectionNamespace.php new file mode 100644 index 0000000000..f6e0fcd6a2 --- /dev/null +++ b/vendor/psy/psysh/src/Reflection/ReflectionNamespace.php @@ -0,0 +1,60 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\Reflection; + +/** + * A fake Reflector for namespaces. + */ +class ReflectionNamespace implements \Reflector +{ + private $name; + + /** + * Construct a ReflectionNamespace object. + * + * @param string $name + */ + public function __construct(string $name) + { + $this->name = $name; + } + + /** + * Gets the constant name. + * + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * This can't (and shouldn't) do anything :). + * + * @throws \RuntimeException + */ + public static function export($name) + { + throw new \RuntimeException('Not yet implemented because it\'s unclear what I should do here :)'); + } + + /** + * To string. + * + * @return string + */ + public function __toString(): string + { + return $this->getName(); + } +} diff --git a/vendor/psy/psysh/src/Shell.php b/vendor/psy/psysh/src/Shell.php new file mode 100644 index 0000000000..19e3a85a77 --- /dev/null +++ b/vendor/psy/psysh/src/Shell.php @@ -0,0 +1,1554 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy; + +use Psy\CodeCleaner\NoReturnValue; +use Psy\Exception\BreakException; +use Psy\Exception\ErrorException; +use Psy\Exception\Exception as PsyException; +use Psy\Exception\ThrowUpException; +use Psy\Exception\TypeErrorException; +use Psy\ExecutionLoop\ProcessForker; +use Psy\ExecutionLoop\RunkitReloader; +use Psy\Formatter\TraceFormatter; +use Psy\Input\ShellInput; +use Psy\Input\SilentInput; +use Psy\Output\ShellOutput; +use Psy\TabCompletion\Matcher; +use Psy\VarDumper\PresenterAware; +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command as BaseCommand; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Input\StringInput; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * The Psy Shell application. + * + * Usage: + * + * $shell = new Shell; + * $shell->run(); + * + * @author Justin Hileman <justin@justinhileman.info> + */ +class Shell extends Application +{ + const VERSION = 'v0.11.8'; + + const PROMPT = '>>> '; + const BUFF_PROMPT = '... '; + const REPLAY = '--> '; + const RETVAL = '=> '; + + private $config; + private $cleaner; + private $output; + private $originalVerbosity; + private $readline; + private $inputBuffer; + private $code; + private $codeBuffer; + private $codeBufferOpen; + private $codeStack; + private $stdoutBuffer; + private $context; + private $includes; + private $outputWantsNewline = false; + private $loopListeners; + private $autoCompleter; + private $matchers = []; + private $commandsMatcher; + private $lastExecSuccess = true; + private $nonInteractive = false; + private $errorReporting; + + /** + * Create a new Psy Shell. + * + * @param Configuration|null $config (default: null) + */ + public function __construct(Configuration $config = null) + { + $this->config = $config ?: new Configuration(); + $this->cleaner = $this->config->getCodeCleaner(); + $this->context = new Context(); + $this->includes = []; + $this->readline = $this->config->getReadline(); + $this->inputBuffer = []; + $this->codeStack = []; + $this->stdoutBuffer = ''; + $this->loopListeners = $this->getDefaultLoopListeners(); + + parent::__construct('Psy Shell', self::VERSION); + + $this->config->setShell($this); + + // Register the current shell session's config with \Psy\info + \Psy\info($this->config); + } + + /** + * Check whether the first thing in a backtrace is an include call. + * + * This is used by the psysh bin to decide whether to start a shell on boot, + * or to simply autoload the library. + */ + public static function isIncluded(array $trace): bool + { + $isIncluded = isset($trace[0]['function']) && + \in_array($trace[0]['function'], ['require', 'include', 'require_once', 'include_once']); + + // Detect Composer PHP bin proxies. + if ($isIncluded && \array_key_exists('_composer_autoload_path', $GLOBALS) && \preg_match('{[\\\\/]psysh$}', $trace[0]['file'])) { + // If we're in a bin proxy, we'll *always* see one include, but we + // care if we see a second immediately after that. + return isset($trace[1]['function']) && + \in_array($trace[1]['function'], ['require', 'include', 'require_once', 'include_once']); + } + + return $isIncluded; + } + + /** + * Invoke a Psy Shell from the current context. + * + * @see Psy\debug + * @deprecated will be removed in 1.0. Use \Psy\debug instead + * + * @param array $vars Scope variables from the calling context (default: []) + * @param object|string $bindTo Bound object ($this) or class (self) value for the shell + * + * @return array Scope variables from the debugger session + */ + public static function debug(array $vars = [], $bindTo = null): array + { + return \Psy\debug($vars, $bindTo); + } + + /** + * Adds a command object. + * + * {@inheritdoc} + * + * @param BaseCommand $command A Symfony Console Command object + * + * @return BaseCommand The registered command + */ + public function add(BaseCommand $command): BaseCommand + { + if ($ret = parent::add($command)) { + if ($ret instanceof ContextAware) { + $ret->setContext($this->context); + } + + if ($ret instanceof PresenterAware) { + $ret->setPresenter($this->config->getPresenter()); + } + + if (isset($this->commandsMatcher)) { + $this->commandsMatcher->setCommands($this->all()); + } + } + + return $ret; + } + + /** + * Gets the default input definition. + * + * @return InputDefinition An InputDefinition instance + */ + protected function getDefaultInputDefinition(): InputDefinition + { + return new InputDefinition([ + new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'), + new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message.'), + ]); + } + + /** + * Gets the default commands that should always be available. + * + * @return array An array of default Command instances + */ + protected function getDefaultCommands(): array + { + $sudo = new Command\SudoCommand(); + $sudo->setReadline($this->readline); + + $hist = new Command\HistoryCommand(); + $hist->setReadline($this->readline); + + return [ + new Command\HelpCommand(), + new Command\ListCommand(), + new Command\DumpCommand(), + new Command\DocCommand(), + new Command\ShowCommand(), + new Command\WtfCommand(), + new Command\WhereamiCommand(), + new Command\ThrowUpCommand(), + new Command\TimeitCommand(), + new Command\TraceCommand(), + new Command\BufferCommand(), + new Command\ClearCommand(), + new Command\EditCommand($this->config->getRuntimeDir()), + // new Command\PsyVersionCommand(), + $sudo, + $hist, + new Command\ExitCommand(), + ]; + } + + /** + * @return array + */ + protected function getDefaultMatchers(): array + { + // Store the Commands Matcher for later. If more commands are added, + // we'll update the Commands Matcher too. + $this->commandsMatcher = new Matcher\CommandsMatcher($this->all()); + + return [ + $this->commandsMatcher, + new Matcher\KeywordsMatcher(), + new Matcher\VariablesMatcher(), + new Matcher\ConstantsMatcher(), + new Matcher\FunctionsMatcher(), + new Matcher\ClassNamesMatcher(), + new Matcher\ClassMethodsMatcher(), + new Matcher\ClassAttributesMatcher(), + new Matcher\ObjectMethodsMatcher(), + new Matcher\ObjectAttributesMatcher(), + new Matcher\ClassMethodDefaultParametersMatcher(), + new Matcher\ObjectMethodDefaultParametersMatcher(), + new Matcher\FunctionDefaultParametersMatcher(), + ]; + } + + /** + * @deprecated Nothing should use this anymore + */ + protected function getTabCompletionMatchers() + { + @\trigger_error('getTabCompletionMatchers is no longer used', \E_USER_DEPRECATED); + } + + /** + * Gets the default command loop listeners. + * + * @return array An array of Execution Loop Listener instances + */ + protected function getDefaultLoopListeners(): array + { + $listeners = []; + + if (ProcessForker::isSupported() && $this->config->usePcntl()) { + $listeners[] = new ProcessForker(); + } + + if (RunkitReloader::isSupported()) { + $listeners[] = new RunkitReloader(); + } + + return $listeners; + } + + /** + * Add tab completion matchers. + * + * @param array $matchers + */ + public function addMatchers(array $matchers) + { + $this->matchers = \array_merge($this->matchers, $matchers); + + if (isset($this->autoCompleter)) { + $this->addMatchersToAutoCompleter($matchers); + } + } + + /** + * @deprecated Call `addMatchers` instead + * + * @param array $matchers + */ + public function addTabCompletionMatchers(array $matchers) + { + $this->addMatchers($matchers); + } + + /** + * Set the Shell output. + * + * @param OutputInterface $output + */ + public function setOutput(OutputInterface $output) + { + $this->output = $output; + $this->originalVerbosity = $output->getVerbosity(); + } + + /** + * Runs PsySH. + * + * @param InputInterface|null $input An Input instance + * @param OutputInterface|null $output An Output instance + * + * @return int 0 if everything went fine, or an error code + */ + public function run(InputInterface $input = null, OutputInterface $output = null): int + { + // We'll just ignore the input passed in, and set up our own! + $input = new ArrayInput([]); + + if ($output === null) { + $output = $this->config->getOutput(); + } + + $this->setAutoExit(false); + $this->setCatchExceptions(false); + + try { + return parent::run($input, $output); + } catch (\Exception $e) { + $this->writeException($e); + } + + return 1; + } + + /** + * Runs PsySH. + * + * @throws \Exception if thrown via the `throw-up` command + * + * @param InputInterface $input An Input instance + * @param OutputInterface $output An Output instance + * + * @return int 0 if everything went fine, or an error code + */ + public function doRun(InputInterface $input, OutputInterface $output): int + { + $this->setOutput($output); + $this->resetCodeBuffer(); + + if ($input->isInteractive()) { + // @todo should it be possible to have raw output in an interactive run? + return $this->doInteractiveRun(); + } else { + return $this->doNonInteractiveRun($this->config->rawOutput()); + } + } + + /** + * Run PsySH in interactive mode. + * + * Initializes tab completion and readline history, then spins up the + * execution loop. + * + * @throws \Exception if thrown via the `throw-up` command + * + * @return int 0 if everything went fine, or an error code + */ + private function doInteractiveRun(): int + { + $this->initializeTabCompletion(); + $this->readline->readHistory(); + + $this->output->writeln($this->getHeader()); + $this->writeVersionInfo(); + $this->writeStartupMessage(); + + try { + $this->beforeRun(); + $this->loadIncludes(); + $loop = new ExecutionLoopClosure($this); + $loop->execute(); + $this->afterRun(); + } catch (ThrowUpException $e) { + throw $e->getPrevious(); + } catch (BreakException $e) { + // The ProcessForker throws a BreakException to finish the main thread. + } + + return 0; + } + + /** + * Run PsySH in non-interactive mode. + * + * Note that this isn't very useful unless you supply "include" arguments at + * the command line, or code via stdin. + * + * @param bool $rawOutput + * + * @return int 0 if everything went fine, or an error code + */ + private function doNonInteractiveRun(bool $rawOutput): int + { + $this->nonInteractive = true; + + // If raw output is enabled (or output is piped) we don't want startup messages. + if (!$rawOutput && !$this->config->outputIsPiped()) { + $this->output->writeln($this->getHeader()); + $this->writeVersionInfo(); + $this->writeStartupMessage(); + } + + $this->beforeRun(); + $this->loadIncludes(); + + // For non-interactive execution, read only from the input buffer or from piped input. + // Otherwise it'll try to readline and hang, waiting for user input with no indication of + // what's holding things up. + if (!empty($this->inputBuffer) || $this->config->inputIsPiped()) { + $this->getInput(false); + } + + if ($this->hasCode()) { + $ret = $this->execute($this->flushCode()); + $this->writeReturnValue($ret, $rawOutput); + } + + $this->afterRun(); + $this->nonInteractive = false; + + return 0; + } + + /** + * Configures the input and output instances based on the user arguments and options. + */ + protected function configureIO(InputInterface $input, OutputInterface $output) + { + // @todo overrides via environment variables (or should these happen in config? ... probably config) + $input->setInteractive($this->config->getInputInteractive()); + + if ($this->config->getOutputDecorated() !== null) { + $output->setDecorated($this->config->getOutputDecorated()); + } + + $output->setVerbosity($this->config->getOutputVerbosity()); + } + + /** + * Load user-defined includes. + */ + private function loadIncludes() + { + // Load user-defined includes + $load = function (self $__psysh__) { + \set_error_handler([$__psysh__, 'handleError']); + foreach ($__psysh__->getIncludes() as $__psysh_include__) { + try { + include_once $__psysh_include__; + } catch (\Error $_e) { + $__psysh__->writeException(ErrorException::fromError($_e)); + } catch (\Exception $_e) { + $__psysh__->writeException($_e); + } + } + \restore_error_handler(); + unset($__psysh_include__); + + // Override any new local variables with pre-defined scope variables + \extract($__psysh__->getScopeVariables(false)); + + // ... then add the whole mess of variables back. + $__psysh__->setScopeVariables(\get_defined_vars()); + }; + + $load($this); + } + + /** + * Read user input. + * + * This will continue fetching user input until the code buffer contains + * valid code. + * + * @throws BreakException if user hits Ctrl+D + * + * @param bool $interactive + */ + public function getInput(bool $interactive = true) + { + $this->codeBufferOpen = false; + + do { + // reset output verbosity (in case it was altered by a subcommand) + $this->output->setVerbosity($this->originalVerbosity); + + $input = $this->readline(); + + /* + * Handle Ctrl+D. It behaves differently in different cases: + * + * 1) In an expression, like a function or "if" block, clear the input buffer + * 2) At top-level session, behave like the exit command + * 3) When non-interactive, return, because that's the end of stdin + */ + if ($input === false) { + if (!$interactive) { + return; + } + + $this->output->writeln(''); + + if ($this->hasCode()) { + $this->resetCodeBuffer(); + } else { + throw new BreakException('Ctrl+D'); + } + } + + // handle empty input + if (\trim($input) === '' && !$this->codeBufferOpen) { + continue; + } + + $input = $this->onInput($input); + + // If the input isn't in an open string or comment, check for commands to run. + if ($this->hasCommand($input) && !$this->inputInOpenStringOrComment($input)) { + $this->addHistory($input); + $this->runCommand($input); + + continue; + } + + $this->addCode($input); + } while (!$interactive || !$this->hasValidCode()); + } + + /** + * Check whether the code buffer (plus current input) is in an open string or comment. + * + * @param string $input current line of input + * + * @return bool true if the input is in an open string or comment + */ + private function inputInOpenStringOrComment(string $input): bool + { + if (!$this->hasCode()) { + return false; + } + + $code = $this->codeBuffer; + $code[] = $input; + $tokens = @\token_get_all('<?php '.\implode("\n", $code)); + $last = \array_pop($tokens); + + return $last === '"' || $last === '`' || + (\is_array($last) && \in_array($last[0], [\T_ENCAPSED_AND_WHITESPACE, \T_START_HEREDOC, \T_COMMENT])); + } + + /** + * Run execution loop listeners before the shell session. + */ + protected function beforeRun() + { + foreach ($this->loopListeners as $listener) { + $listener->beforeRun($this); + } + } + + /** + * Run execution loop listeners at the start of each loop. + */ + public function beforeLoop() + { + foreach ($this->loopListeners as $listener) { + $listener->beforeLoop($this); + } + } + + /** + * Run execution loop listeners on user input. + * + * @param string $input + * + * @return string + */ + public function onInput(string $input): string + { + foreach ($this->loopListeners as $listeners) { + if (($return = $listeners->onInput($this, $input)) !== null) { + $input = $return; + } + } + + return $input; + } + + /** + * Run execution loop listeners on code to be executed. + * + * @param string $code + * + * @return string + */ + public function onExecute(string $code): string + { + $this->errorReporting = \error_reporting(); + + foreach ($this->loopListeners as $listener) { + if (($return = $listener->onExecute($this, $code)) !== null) { + $code = $return; + } + } + + $output = $this->output; + if ($output instanceof ConsoleOutput) { + $output = $output->getErrorOutput(); + } + + $output->writeln(\sprintf('<aside>%s</aside>', OutputFormatter::escape($code)), ConsoleOutput::VERBOSITY_DEBUG); + + return $code; + } + + /** + * Run execution loop listeners after each loop. + */ + public function afterLoop() + { + foreach ($this->loopListeners as $listener) { + $listener->afterLoop($this); + } + } + + /** + * Run execution loop listers after the shell session. + */ + protected function afterRun() + { + foreach ($this->loopListeners as $listener) { + $listener->afterRun($this); + } + } + + /** + * Set the variables currently in scope. + * + * @param array $vars + */ + public function setScopeVariables(array $vars) + { + $this->context->setAll($vars); + } + + /** + * Return the set of variables currently in scope. + * + * @param bool $includeBoundObject Pass false to exclude 'this'. If you're + * passing the scope variables to `extract` + * in PHP 7.1+, you _must_ exclude 'this' + * + * @return array Associative array of scope variables + */ + public function getScopeVariables(bool $includeBoundObject = true): array + { + $vars = $this->context->getAll(); + + if (!$includeBoundObject) { + unset($vars['this']); + } + + return $vars; + } + + /** + * Return the set of magic variables currently in scope. + * + * @param bool $includeBoundObject Pass false to exclude 'this'. If you're + * passing the scope variables to `extract` + * in PHP 7.1+, you _must_ exclude 'this' + * + * @return array Associative array of magic scope variables + */ + public function getSpecialScopeVariables(bool $includeBoundObject = true): array + { + $vars = $this->context->getSpecialVariables(); + + if (!$includeBoundObject) { + unset($vars['this']); + } + + return $vars; + } + + /** + * Return the set of variables currently in scope which differ from the + * values passed as $currentVars. + * + * This is used inside the Execution Loop Closure to pick up scope variable + * changes made by commands while the loop is running. + * + * @param array $currentVars + * + * @return array Associative array of scope variables which differ from $currentVars + */ + public function getScopeVariablesDiff(array $currentVars): array + { + $newVars = []; + + foreach ($this->getScopeVariables(false) as $key => $value) { + if (!\array_key_exists($key, $currentVars) || $currentVars[$key] !== $value) { + $newVars[$key] = $value; + } + } + + return $newVars; + } + + /** + * Get the set of unused command-scope variable names. + * + * @return array Array of unused variable names + */ + public function getUnusedCommandScopeVariableNames(): array + { + return $this->context->getUnusedCommandScopeVariableNames(); + } + + /** + * Get the set of variable names currently in scope. + * + * @return array Array of variable names + */ + public function getScopeVariableNames(): array + { + return \array_keys($this->context->getAll()); + } + + /** + * Get a scope variable value by name. + * + * @param string $name + * + * @return mixed + */ + public function getScopeVariable(string $name) + { + return $this->context->get($name); + } + + /** + * Set the bound object ($this variable) for the interactive shell. + * + * @param object|null $boundObject + */ + public function setBoundObject($boundObject) + { + $this->context->setBoundObject($boundObject); + } + + /** + * Get the bound object ($this variable) for the interactive shell. + * + * @return object|null + */ + public function getBoundObject() + { + return $this->context->getBoundObject(); + } + + /** + * Set the bound class (self) for the interactive shell. + * + * @param string|null $boundClass + */ + public function setBoundClass($boundClass) + { + $this->context->setBoundClass($boundClass); + } + + /** + * Get the bound class (self) for the interactive shell. + * + * @return string|null + */ + public function getBoundClass() + { + return $this->context->getBoundClass(); + } + + /** + * Add includes, to be parsed and executed before running the interactive shell. + * + * @param array $includes + */ + public function setIncludes(array $includes = []) + { + $this->includes = $includes; + } + + /** + * Get PHP files to be parsed and executed before running the interactive shell. + * + * @return array + */ + public function getIncludes(): array + { + return \array_merge($this->config->getDefaultIncludes(), $this->includes); + } + + /** + * Check whether this shell's code buffer contains code. + * + * @return bool True if the code buffer contains code + */ + public function hasCode(): bool + { + return !empty($this->codeBuffer); + } + + /** + * Check whether the code in this shell's code buffer is valid. + * + * If the code is valid, the code buffer should be flushed and evaluated. + * + * @return bool True if the code buffer content is valid + */ + protected function hasValidCode(): bool + { + return !$this->codeBufferOpen && $this->code !== false; + } + + /** + * Add code to the code buffer. + * + * @param string $code + * @param bool $silent + */ + public function addCode(string $code, bool $silent = false) + { + try { + // Code lines ending in \ keep the buffer open + if (\substr(\rtrim($code), -1) === '\\') { + $this->codeBufferOpen = true; + $code = \substr(\rtrim($code), 0, -1); + } else { + $this->codeBufferOpen = false; + } + + $this->codeBuffer[] = $silent ? new SilentInput($code) : $code; + $this->code = $this->cleaner->clean($this->codeBuffer, $this->config->requireSemicolons()); + } catch (\Throwable $e) { + // Add failed code blocks to the readline history. + $this->addCodeBufferToHistory(); + + throw $e; + } + } + + /** + * Set the code buffer. + * + * This is mostly used by `Shell::execute`. Any existing code in the input + * buffer is pushed onto a stack and will come back after this new code is + * executed. + * + * @throws \InvalidArgumentException if $code isn't a complete statement + * + * @param string $code + * @param bool $silent + */ + private function setCode(string $code, bool $silent = false) + { + if ($this->hasCode()) { + $this->codeStack[] = [$this->codeBuffer, $this->codeBufferOpen, $this->code]; + } + + $this->resetCodeBuffer(); + try { + $this->addCode($code, $silent); + } catch (\Throwable $e) { + $this->popCodeStack(); + + throw $e; + } + + if (!$this->hasValidCode()) { + $this->popCodeStack(); + + throw new \InvalidArgumentException('Unexpected end of input'); + } + } + + /** + * Get the current code buffer. + * + * This is useful for commands which manipulate the buffer. + * + * @return array + */ + public function getCodeBuffer(): array + { + return $this->codeBuffer; + } + + /** + * Run a Psy Shell command given the user input. + * + * @throws \InvalidArgumentException if the input is not a valid command + * + * @param string $input User input string + * + * @return mixed Who knows? + */ + protected function runCommand(string $input) + { + $command = $this->getCommand($input); + + if (empty($command)) { + throw new \InvalidArgumentException('Command not found: '.$input); + } + + $input = new ShellInput(\str_replace('\\', '\\\\', \rtrim($input, " \t\n\r\0\x0B;"))); + + if ($input->hasParameterOption(['--help', '-h'])) { + $helpCommand = $this->get('help'); + $helpCommand->setCommand($command); + + return $helpCommand->run(new StringInput(''), $this->output); + } + + return $command->run($input, $this->output); + } + + /** + * Reset the current code buffer. + * + * This should be run after evaluating user input, catching exceptions, or + * on demand by commands such as BufferCommand. + */ + public function resetCodeBuffer() + { + $this->codeBuffer = []; + $this->code = false; + } + + /** + * Inject input into the input buffer. + * + * This is useful for commands which want to replay history. + * + * @param string|array $input + * @param bool $silent + */ + public function addInput($input, bool $silent = false) + { + foreach ((array) $input as $line) { + $this->inputBuffer[] = $silent ? new SilentInput($line) : $line; + } + } + + /** + * Flush the current (valid) code buffer. + * + * If the code buffer is valid, resets the code buffer and returns the + * current code. + * + * @return string|null PHP code buffer contents + */ + public function flushCode() + { + if ($this->hasValidCode()) { + $this->addCodeBufferToHistory(); + $code = $this->code; + $this->popCodeStack(); + + return $code; + } + } + + /** + * Reset the code buffer and restore any code pushed during `execute` calls. + */ + private function popCodeStack() + { + $this->resetCodeBuffer(); + + if (empty($this->codeStack)) { + return; + } + + list($codeBuffer, $codeBufferOpen, $code) = \array_pop($this->codeStack); + + $this->codeBuffer = $codeBuffer; + $this->codeBufferOpen = $codeBufferOpen; + $this->code = $code; + } + + /** + * (Possibly) add a line to the readline history. + * + * Like Bash, if the line starts with a space character, it will be omitted + * from history. Note that an entire block multi-line code input will be + * omitted iff the first line begins with a space. + * + * Additionally, if a line is "silent", i.e. it was initially added with the + * silent flag, it will also be omitted. + * + * @param string|SilentInput $line + */ + private function addHistory($line) + { + if ($line instanceof SilentInput) { + return; + } + + // Skip empty lines and lines starting with a space + if (\trim($line) !== '' && \substr($line, 0, 1) !== ' ') { + $this->readline->addHistory($line); + } + } + + /** + * Filter silent input from code buffer, write the rest to readline history. + */ + private function addCodeBufferToHistory() + { + $codeBuffer = \array_filter($this->codeBuffer, function ($line) { + return !$line instanceof SilentInput; + }); + + $this->addHistory(\implode("\n", $codeBuffer)); + } + + /** + * Get the current evaluation scope namespace. + * + * @see CodeCleaner::getNamespace + * + * @return string|null Current code namespace + */ + public function getNamespace() + { + if ($namespace = $this->cleaner->getNamespace()) { + return \implode('\\', $namespace); + } + } + + /** + * Write a string to stdout. + * + * This is used by the shell loop for rendering output from evaluated code. + * + * @param string $out + * @param int $phase Output buffering phase + */ + public function writeStdout(string $out, int $phase = \PHP_OUTPUT_HANDLER_END) + { + if ($phase & \PHP_OUTPUT_HANDLER_START) { + if ($this->output instanceof ShellOutput) { + $this->output->startPaging(); + } + } + + $isCleaning = $phase & \PHP_OUTPUT_HANDLER_CLEAN; + + // Incremental flush + if ($out !== '' && !$isCleaning) { + $this->output->write($out, false, OutputInterface::OUTPUT_RAW); + $this->outputWantsNewline = (\substr($out, -1) !== "\n"); + $this->stdoutBuffer .= $out; + } + + // Output buffering is done! + if ($phase & \PHP_OUTPUT_HANDLER_END) { + // Write an extra newline if stdout didn't end with one + if ($this->outputWantsNewline) { + if (!$this->config->rawOutput() && !$this->config->outputIsPiped()) { + $this->output->writeln(\sprintf('<aside>%s</aside>', $this->config->useUnicode() ? '⏎' : '\\n')); + } else { + $this->output->writeln(''); + } + $this->outputWantsNewline = false; + } + + // Save the stdout buffer as $__out + if ($this->stdoutBuffer !== '') { + $this->context->setLastStdout($this->stdoutBuffer); + $this->stdoutBuffer = ''; + } + + if ($this->output instanceof ShellOutput) { + $this->output->stopPaging(); + } + } + } + + /** + * Write a return value to stdout. + * + * The return value is formatted or pretty-printed, and rendered in a + * visibly distinct manner (in this case, as cyan). + * + * @see self::presentValue + * + * @param mixed $ret + * @param bool $rawOutput Write raw var_export-style values + */ + public function writeReturnValue($ret, bool $rawOutput = false) + { + $this->lastExecSuccess = true; + + if ($ret instanceof NoReturnValue) { + return; + } + + $this->context->setReturnValue($ret); + + if ($rawOutput) { + $formatted = \var_export($ret, true); + } else { + $indent = \str_repeat(' ', \strlen(static::RETVAL)); + $formatted = $this->presentValue($ret); + $formatted = static::RETVAL.\str_replace(\PHP_EOL, \PHP_EOL.$indent, $formatted); + } + + if ($this->output instanceof ShellOutput) { + $this->output->page($formatted.\PHP_EOL); + } else { + $this->output->writeln($formatted); + } + } + + /** + * Renders a caught Exception. + * + * Exceptions are formatted according to severity. ErrorExceptions which were + * warnings or Strict errors aren't rendered as harshly as real errors. + * + * Stores $e as the last Exception in the Shell Context. + * + * @param \Exception $e An exception instance + */ + public function writeException(\Exception $e) + { + // No need to write the break exception during a non-interactive run. + if ($e instanceof BreakException && $this->nonInteractive) { + $this->resetCodeBuffer(); + + return; + } + + // Break exceptions don't count :) + if (!$e instanceof BreakException) { + $this->lastExecSuccess = false; + $this->context->setLastException($e); + } + + $output = $this->output; + if ($output instanceof ConsoleOutput) { + $output = $output->getErrorOutput(); + } + $output->writeln($this->formatException($e)); + + // Include an exception trace (as long as this isn't a BreakException). + if (!$e instanceof BreakException && $output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) { + $trace = TraceFormatter::formatTrace($e); + if (\count($trace) !== 0) { + $output->writeln('--'); + $output->write($trace, true); + $output->writeln(''); + } + } + + $this->resetCodeBuffer(); + } + + /** + * Check whether the last exec was successful. + * + * Returns true if a return value was logged rather than an exception. + * + * @return bool + */ + public function getLastExecSuccess(): bool + { + return $this->lastExecSuccess; + } + + /** + * Helper for formatting an exception for writeException(). + * + * @todo extract this to somewhere it makes more sense + * + * @param \Exception $e + * + * @return string + */ + public function formatException(\Exception $e): string + { + $message = $e->getMessage(); + if (!$e instanceof PsyException) { + if ($message === '') { + $message = \get_class($e); + } else { + $message = \sprintf('%s with message \'%s\'', \get_class($e), $message); + } + } + + $message = \preg_replace( + "#(\\w:)?([\\\\/]\\w+)*[\\\\/]src[\\\\/]Execution(?:Loop)?Closure.php\(\d+\) : eval\(\)'d code#", + "eval()'d code", + $message + ); + + $message = \str_replace(" in eval()'d code", ' in Psy Shell code', $message); + + $severity = ($e instanceof \ErrorException) ? $this->getSeverity($e) : 'error'; + + return \sprintf('<%s>%s</%s>', $severity, OutputFormatter::escape($message), $severity); + } + + /** + * Helper for getting an output style for the given ErrorException's level. + * + * @param \ErrorException $e + * + * @return string + */ + protected function getSeverity(\ErrorException $e): string + { + $severity = $e->getSeverity(); + if ($severity & \error_reporting()) { + switch ($severity) { + case \E_WARNING: + case \E_NOTICE: + case \E_CORE_WARNING: + case \E_COMPILE_WARNING: + case \E_USER_WARNING: + case \E_USER_NOTICE: + case \E_STRICT: + return 'warning'; + + default: + return 'error'; + } + } else { + // Since this is below the user's reporting threshold, it's always going to be a warning. + return 'warning'; + } + } + + /** + * Execute code in the shell execution context. + * + * @param string $code + * @param bool $throwExceptions + * + * @return mixed + */ + public function execute(string $code, bool $throwExceptions = false) + { + $this->setCode($code, true); + $closure = new ExecutionClosure($this); + + if ($throwExceptions) { + return $closure->execute(); + } + + try { + return $closure->execute(); + } catch (\TypeError $_e) { + $this->writeException(TypeErrorException::fromTypeError($_e)); + } catch (\Error $_e) { + $this->writeException(ErrorException::fromError($_e)); + } catch (\Exception $_e) { + $this->writeException($_e); + } + } + + /** + * Helper for throwing an ErrorException. + * + * This allows us to: + * + * set_error_handler([$psysh, 'handleError']); + * + * Unlike ErrorException::throwException, this error handler respects error + * levels; i.e. it logs warnings and notices, but doesn't throw exceptions. + * This should probably only be used in the inner execution loop of the + * shell, as most of the time a thrown exception is much more useful. + * + * If the error type matches the `errorLoggingLevel` config, it will be + * logged as well, regardless of the `error_reporting` level. + * + * @see \Psy\Exception\ErrorException::throwException + * @see \Psy\Shell::writeException + * + * @throws \Psy\Exception\ErrorException depending on the error level + * + * @param int $errno Error type + * @param string $errstr Message + * @param string $errfile Filename + * @param int $errline Line number + */ + public function handleError($errno, $errstr, $errfile, $errline) + { + // This is an error worth throwing. + // + // n.b. Technically we can't handle all of these in userland code, but + // we'll list 'em all for good measure + if ($errno & (\E_ERROR | \E_PARSE | \E_CORE_ERROR | \E_COMPILE_ERROR | \E_USER_ERROR | \E_RECOVERABLE_ERROR)) { + ErrorException::throwException($errno, $errstr, $errfile, $errline); + } + + // When errors are suppressed, the error_reporting value will differ + // from when we started executing. In that case, we won't log errors. + $errorsSuppressed = $this->errorReporting !== null && $this->errorReporting !== \error_reporting(); + + // Otherwise log it and continue. + if ($errno & \error_reporting() || (!$errorsSuppressed && ($errno & $this->config->errorLoggingLevel()))) { + $this->writeException(new ErrorException($errstr, 0, $errno, $errfile, $errline)); + } + } + + /** + * Format a value for display. + * + * @see Presenter::present + * + * @param mixed $val + * + * @return string Formatted value + */ + protected function presentValue($val): string + { + return $this->config->getPresenter()->present($val); + } + + /** + * Get a command (if one exists) for the current input string. + * + * @param string $input + * + * @return BaseCommand|null + */ + protected function getCommand(string $input) + { + $input = new StringInput($input); + if ($name = $input->getFirstArgument()) { + return $this->get($name); + } + } + + /** + * Check whether a command is set for the current input string. + * + * @param string $input + * + * @return bool True if the shell has a command for the given input + */ + protected function hasCommand(string $input): bool + { + if (\preg_match('/([^\s]+?)(?:\s|$)/A', \ltrim($input), $match)) { + return $this->has($match[1]); + } + + return false; + } + + /** + * Get the current input prompt. + * + * @return string|null + */ + protected function getPrompt() + { + if ($this->output->isQuiet()) { + return null; + } + + if ($this->hasCode()) { + return static::BUFF_PROMPT; + } + + return $this->config->getPrompt() ?: static::PROMPT; + } + + /** + * Read a line of user input. + * + * This will return a line from the input buffer (if any exist). Otherwise, + * it will ask the user for input. + * + * If readline is enabled, this delegates to readline. Otherwise, it's an + * ugly `fgets` call. + * + * @param bool $interactive + * + * @return string|false One line of user input + */ + protected function readline(bool $interactive = true) + { + if (!empty($this->inputBuffer)) { + $line = \array_shift($this->inputBuffer); + if (!$line instanceof SilentInput) { + $this->output->writeln(\sprintf('<aside>%s %s</aside>', static::REPLAY, OutputFormatter::escape($line))); + } + + return $line; + } + + $bracketedPaste = $interactive && $this->config->useBracketedPaste(); + + if ($bracketedPaste) { + \printf("\e[?2004h"); // Enable bracketed paste + } + + $line = $this->readline->readline($this->getPrompt()); + + if ($bracketedPaste) { + \printf("\e[?2004l"); // ... and disable it again + } + + return $line; + } + + /** + * Get the shell output header. + * + * @return string + */ + protected function getHeader(): string + { + return \sprintf('<aside>%s by Justin Hileman</aside>', $this->getVersion()); + } + + /** + * Get the current version of Psy Shell. + * + * @deprecated call self::getVersionHeader instead + * + * @return string + */ + public function getVersion(): string + { + return self::getVersionHeader($this->config->useUnicode()); + } + + /** + * Get a pretty header including the current version of Psy Shell. + * + * @param bool $useUnicode + * + * @return string + */ + public static function getVersionHeader(bool $useUnicode = false): string + { + $separator = $useUnicode ? '—' : '-'; + + return \sprintf('Psy Shell %s (PHP %s %s %s)', self::VERSION, \PHP_VERSION, $separator, \PHP_SAPI); + } + + /** + * Get a PHP manual database instance. + * + * @return \PDO|null + */ + public function getManualDb() + { + return $this->config->getManualDb(); + } + + /** + * @deprecated Tab completion is provided by the AutoCompleter service + */ + protected function autocomplete($text) + { + @\trigger_error('Tab completion is provided by the AutoCompleter service', \E_USER_DEPRECATED); + } + + /** + * Initialize tab completion matchers. + * + * If tab completion is enabled this adds tab completion matchers to the + * auto completer and sets context if needed. + */ + protected function initializeTabCompletion() + { + if (!$this->config->useTabCompletion()) { + return; + } + + $this->autoCompleter = $this->config->getAutoCompleter(); + + // auto completer needs shell to be linked to configuration because of + // the context aware matchers + $this->addMatchersToAutoCompleter($this->getDefaultMatchers()); + $this->addMatchersToAutoCompleter($this->matchers); + + $this->autoCompleter->activate(); + } + + /** + * Add matchers to the auto completer, setting context if needed. + * + * @param array $matchers + */ + private function addMatchersToAutoCompleter(array $matchers) + { + foreach ($matchers as $matcher) { + if ($matcher instanceof ContextAware) { + $matcher->setContext($this->context); + } + $this->autoCompleter->addMatcher($matcher); + } + } + + /** + * @todo Implement self-update + * @todo Implement prompt to start update + * + * @return void|string + */ + protected function writeVersionInfo() + { + if (\PHP_SAPI !== 'cli') { + return; + } + + try { + $client = $this->config->getChecker(); + if (!$client->isLatest()) { + $this->output->writeln(\sprintf('New version is available (current: %s, latest: %s)', self::VERSION, $client->getLatest())); + } + } catch (\InvalidArgumentException $e) { + $this->output->writeln($e->getMessage()); + } + } + + /** + * Write a startup message if set. + */ + protected function writeStartupMessage() + { + $message = $this->config->getStartupMessage(); + if ($message !== null && $message !== '') { + $this->output->writeln($message); + } + } +} diff --git a/vendor/psy/psysh/src/Sudo.php b/vendor/psy/psysh/src/Sudo.php new file mode 100644 index 0000000000..a251678900 --- /dev/null +++ b/vendor/psy/psysh/src/Sudo.php @@ -0,0 +1,182 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy; + +/** + * Helpers for bypassing visibility restrictions, mostly used in code generated + * by the `sudo` command. + */ +class Sudo +{ + /** + * Fetch a property of an object, bypassing visibility restrictions. + * + * @param object $object + * @param string $property property name + * + * @return mixed Value of $object->property + */ + public static function fetchProperty($object, string $property) + { + $prop = static::getProperty(new \ReflectionObject($object), $property); + + return $prop->getValue($object); + } + + /** + * Assign the value of a property of an object, bypassing visibility restrictions. + * + * @param object $object + * @param string $property property name + * @param mixed $value + * + * @return mixed Value of $object->property + */ + public static function assignProperty($object, string $property, $value) + { + $prop = static::getProperty(new \ReflectionObject($object), $property); + $prop->setValue($object, $value); + + return $value; + } + + /** + * Call a method on an object, bypassing visibility restrictions. + * + * @param object $object + * @param string $method method name + * @param mixed $args... + * + * @return mixed + */ + public static function callMethod($object, string $method, $args = null) + { + $args = \func_get_args(); + $object = \array_shift($args); + $method = \array_shift($args); + + $refl = new \ReflectionObject($object); + $reflMethod = $refl->getMethod($method); + $reflMethod->setAccessible(true); + + return $reflMethod->invokeArgs($object, $args); + } + + /** + * Fetch a property of a class, bypassing visibility restrictions. + * + * @param string|object $class class name or instance + * @param string $property property name + * + * @return mixed Value of $class::$property + */ + public static function fetchStaticProperty($class, string $property) + { + $prop = static::getProperty(new \ReflectionClass($class), $property); + $prop->setAccessible(true); + + return $prop->getValue(); + } + + /** + * Assign the value of a static property of a class, bypassing visibility restrictions. + * + * @param string|object $class class name or instance + * @param string $property property name + * @param mixed $value + * + * @return mixed Value of $class::$property + */ + public static function assignStaticProperty($class, string $property, $value) + { + $prop = static::getProperty(new \ReflectionClass($class), $property); + $prop->setValue($value); + + return $value; + } + + /** + * Call a static method on a class, bypassing visibility restrictions. + * + * @param string|object $class class name or instance + * @param string $method method name + * @param mixed $args... + * + * @return mixed + */ + public static function callStatic($class, string $method, $args = null) + { + $args = \func_get_args(); + $class = \array_shift($args); + $method = \array_shift($args); + + $refl = new \ReflectionClass($class); + $reflMethod = $refl->getMethod($method); + $reflMethod->setAccessible(true); + + return $reflMethod->invokeArgs(null, $args); + } + + /** + * Fetch a class constant, bypassing visibility restrictions. + * + * @param string|object $class class name or instance + * @param string $const constant name + * + * @return mixed + */ + public static function fetchClassConst($class, string $const) + { + $refl = new \ReflectionClass($class); + + do { + if ($refl->hasConstant($const)) { + return $refl->getConstant($const); + } + + $refl = $refl->getParentClass(); + } while ($refl !== false); + + return false; + } + + /** + * Get a ReflectionProperty from an object (or its parent classes). + * + * @throws \ReflectionException if neither the object nor any of its parents has this property + * + * @param \ReflectionClass $refl + * @param string $property property name + * + * @return \ReflectionProperty + */ + private static function getProperty(\ReflectionClass $refl, string $property): \ReflectionProperty + { + $firstException = null; + do { + try { + $prop = $refl->getProperty($property); + $prop->setAccessible(true); + + return $prop; + } catch (\ReflectionException $e) { + if ($firstException === null) { + $firstException = $e; + } + + $refl = $refl->getParentClass(); + } + } while ($refl !== false); + + throw $firstException; + } +} diff --git a/vendor/psy/psysh/src/Sudo/SudoVisitor.php b/vendor/psy/psysh/src/Sudo/SudoVisitor.php new file mode 100644 index 0000000000..28f5d922ba --- /dev/null +++ b/vendor/psy/psysh/src/Sudo/SudoVisitor.php @@ -0,0 +1,123 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\Sudo; + +use PhpParser\Node; +use PhpParser\Node\Arg; +use PhpParser\Node\Expr\Assign; +use PhpParser\Node\Expr\ClassConstFetch; +use PhpParser\Node\Expr\MethodCall; +use PhpParser\Node\Expr\PropertyFetch; +use PhpParser\Node\Expr\StaticCall; +use PhpParser\Node\Expr\StaticPropertyFetch; +use PhpParser\Node\Identifier; +use PhpParser\Node\Name; +use PhpParser\Node\Name\FullyQualified as FullyQualifiedName; +use PhpParser\Node\Scalar\String_; +use PhpParser\NodeVisitorAbstract; +use Psy\Sudo; + +/** + * A PHP Parser node visitor which rewrites property and method access to use + * the Psy\Sudo visibility bypass methods. + * + * @todo handle assigning by reference + */ +class SudoVisitor extends NodeVisitorAbstract +{ + const PROPERTY_FETCH = 'fetchProperty'; + const PROPERTY_ASSIGN = 'assignProperty'; + const METHOD_CALL = 'callMethod'; + const STATIC_PROPERTY_FETCH = 'fetchStaticProperty'; + const STATIC_PROPERTY_ASSIGN = 'assignStaticProperty'; + const STATIC_CALL = 'callStatic'; + const CLASS_CONST_FETCH = 'fetchClassConst'; + + /** + * {@inheritdoc} + */ + public function enterNode(Node $node) + { + if ($node instanceof PropertyFetch) { + $name = $node->name instanceof Identifier ? $node->name->toString() : $node->name; + $args = [ + $node->var, + \is_string($name) ? new String_($name) : $name, + ]; + + return $this->prepareCall(self::PROPERTY_FETCH, $args); + } elseif ($node instanceof Assign && $node->var instanceof PropertyFetch) { + $target = $node->var; + $name = $target->name instanceof Identifier ? $target->name->toString() : $target->name; + $args = [ + $target->var, + \is_string($name) ? new String_($name) : $name, + $node->expr, + ]; + + return $this->prepareCall(self::PROPERTY_ASSIGN, $args); + } elseif ($node instanceof MethodCall) { + $name = $node->name instanceof Identifier ? $node->name->toString() : $node->name; + $args = $node->args; + \array_unshift($args, new Arg(\is_string($name) ? new String_($name) : $name)); + \array_unshift($args, new Arg($node->var)); + + // not using prepareCall because the $node->args we started with are already Arg instances + return new StaticCall(new FullyQualifiedName(Sudo::class), self::METHOD_CALL, $args); + } elseif ($node instanceof StaticPropertyFetch) { + $class = $node->class instanceof Name ? $node->class->toString() : $node->class; + $name = $node->name instanceof Identifier ? $node->name->toString() : $node->name; + $args = [ + \is_string($class) ? new String_($class) : $class, + \is_string($name) ? new String_($name) : $name, + ]; + + return $this->prepareCall(self::STATIC_PROPERTY_FETCH, $args); + } elseif ($node instanceof Assign && $node->var instanceof StaticPropertyFetch) { + $target = $node->var; + $class = $target->class instanceof Name ? $target->class->toString() : $target->class; + $name = $target->name instanceof Identifier ? $target->name->toString() : $target->name; + $args = [ + \is_string($class) ? new String_($class) : $class, + \is_string($name) ? new String_($name) : $name, + $node->expr, + ]; + + return $this->prepareCall(self::STATIC_PROPERTY_ASSIGN, $args); + } elseif ($node instanceof StaticCall) { + $args = $node->args; + $class = $node->class instanceof Name ? $node->class->toString() : $node->class; + $name = $node->name instanceof Identifier ? $node->name->toString() : $node->name; + \array_unshift($args, new Arg(\is_string($name) ? new String_($name) : $name)); + \array_unshift($args, new Arg(\is_string($class) ? new String_($class) : $class)); + + // not using prepareCall because the $node->args we started with are already Arg instances + return new StaticCall(new FullyQualifiedName(Sudo::class), self::STATIC_CALL, $args); + } elseif ($node instanceof ClassConstFetch) { + $class = $node->class instanceof Name ? $node->class->toString() : $node->class; + $name = $node->name instanceof Identifier ? $node->name->toString() : $node->name; + $args = [ + \is_string($class) ? new String_($class) : $class, + \is_string($name) ? new String_($name) : $name, + ]; + + return $this->prepareCall(self::CLASS_CONST_FETCH, $args); + } + } + + private function prepareCall(string $method, array $args): StaticCall + { + return new StaticCall(new FullyQualifiedName(Sudo::class), $method, \array_map(function ($arg) { + return new Arg($arg); + }, $args)); + } +} diff --git a/vendor/psy/psysh/src/SuperglobalsEnv.php b/vendor/psy/psysh/src/SuperglobalsEnv.php new file mode 100644 index 0000000000..0d0b002574 --- /dev/null +++ b/vendor/psy/psysh/src/SuperglobalsEnv.php @@ -0,0 +1,32 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy; + +/** + * Environment variables implementation via $_SERVER superglobal. + */ +class SuperglobalsEnv implements EnvInterface +{ + /** + * Get an environment variable by name. + * + * @return string|null + */ + public function get(string $key) + { + if (isset($_SERVER[$key]) && $_SERVER[$key]) { + return $_SERVER[$key]; + } + + return null; + } +} diff --git a/vendor/psy/psysh/src/TabCompletion/AutoCompleter.php b/vendor/psy/psysh/src/TabCompletion/AutoCompleter.php new file mode 100644 index 0000000000..17d0ec56fa --- /dev/null +++ b/vendor/psy/psysh/src/TabCompletion/AutoCompleter.php @@ -0,0 +1,112 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\TabCompletion; + +use Psy\TabCompletion\Matcher\AbstractMatcher; + +/** + * A readline tab completion service. + * + * @author Marc Garcia <markcial@gmail.com> + */ +class AutoCompleter +{ + /** @var Matcher\AbstractMatcher[] */ + protected $matchers; + + /** + * Register a tab completion Matcher. + * + * @param AbstractMatcher $matcher + */ + public function addMatcher(AbstractMatcher $matcher) + { + $this->matchers[] = $matcher; + } + + /** + * Activate readline tab completion. + */ + public function activate() + { + \readline_completion_function([&$this, 'callback']); + } + + /** + * Handle readline completion. + * + * @param string $input Readline current word + * @param int $index Current word index + * @param array $info readline_info() data + * + * @return array + */ + public function processCallback(string $input, int $index, array $info = []): array + { + // Some (Windows?) systems provide incomplete `readline_info`, so let's + // try to work around it. + $line = $info['line_buffer']; + if (isset($info['end'])) { + $line = \substr($line, 0, $info['end']); + } + if ($line === '' && $input !== '') { + $line = $input; + } + + $tokens = \token_get_all('<?php '.$line); + + // remove whitespaces + $tokens = \array_filter($tokens, function ($token) { + return !AbstractMatcher::tokenIs($token, AbstractMatcher::T_WHITESPACE); + }); + // reset index from 0 to remove missing index number + $tokens = \array_values($tokens); + + $matches = []; + foreach ($this->matchers as $matcher) { + if ($matcher->hasMatched($tokens)) { + $matches = \array_merge($matcher->getMatches($tokens), $matches); + } + } + + $matches = \array_unique($matches); + + return !empty($matches) ? $matches : ['']; + } + + /** + * The readline_completion_function callback handler. + * + * @see processCallback + * + * @param string $input + * @param int $index + * + * @return array + */ + public function callback(string $input, int $index): array + { + return $this->processCallback($input, $index, \readline_info()); + } + + /** + * Remove readline callback handler on destruct. + */ + public function __destruct() + { + // PHP didn't implement the whole readline API when they first switched + // to libedit. And they still haven't. + if (\function_exists('readline_callback_handler_remove')) { + \readline_callback_handler_remove(); + } + } +} diff --git a/vendor/psy/psysh/src/TabCompletion/Matcher/AbstractContextAwareMatcher.php b/vendor/psy/psysh/src/TabCompletion/Matcher/AbstractContextAwareMatcher.php new file mode 100644 index 0000000000..ac0a52503e --- /dev/null +++ b/vendor/psy/psysh/src/TabCompletion/Matcher/AbstractContextAwareMatcher.php @@ -0,0 +1,65 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\TabCompletion\Matcher; + +use Psy\Context; +use Psy\ContextAware; + +/** + * An abstract tab completion Matcher which implements ContextAware. + * + * The AutoCompleter service will inject a Context instance into all + * ContextAware Matchers. + * + * @author Marc Garcia <markcial@gmail.com> + */ +abstract class AbstractContextAwareMatcher extends AbstractMatcher implements ContextAware +{ + /** + * Context instance (for ContextAware interface). + * + * @var Context + */ + protected $context; + + /** + * ContextAware interface. + * + * @param Context $context + */ + public function setContext(Context $context) + { + $this->context = $context; + } + + /** + * Get a Context variable by name. + * + * @param string $var Variable name + * + * @return mixed + */ + protected function getVariable(string $var) + { + return $this->context->get($var); + } + + /** + * Get all variables in the current Context. + * + * @return array + */ + protected function getVariables(): array + { + return $this->context->getAll(); + } +} diff --git a/vendor/psy/psysh/src/TabCompletion/Matcher/AbstractDefaultParametersMatcher.php b/vendor/psy/psysh/src/TabCompletion/Matcher/AbstractDefaultParametersMatcher.php new file mode 100644 index 0000000000..702109ddb4 --- /dev/null +++ b/vendor/psy/psysh/src/TabCompletion/Matcher/AbstractDefaultParametersMatcher.php @@ -0,0 +1,76 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\TabCompletion\Matcher; + +abstract class AbstractDefaultParametersMatcher extends AbstractContextAwareMatcher +{ + /** + * @param \ReflectionParameter[] $reflectionParameters + * + * @return array + */ + public function getDefaultParameterCompletion(array $reflectionParameters): array + { + $parametersProcessed = []; + + foreach ($reflectionParameters as $parameter) { + if (!$parameter->isDefaultValueAvailable()) { + return []; + } + + $defaultValue = $this->valueToShortString($parameter->getDefaultValue()); + + $parametersProcessed[] = \sprintf('$%s = %s', $parameter->getName(), $defaultValue); + } + + if (empty($parametersProcessed)) { + return []; + } + + return [\implode(', ', $parametersProcessed).')']; + } + + /** + * Takes in the default value of a parameter and turns it into a + * string representation that fits inline. + * This is not 100% true to the original (newlines are inlined, for example). + * + * @param mixed $value + * + * @return string + */ + private function valueToShortString($value): string + { + if (!\is_array($value)) { + return \json_encode($value); + } + + $chunks = []; + $chunksSequential = []; + + $allSequential = true; + + foreach ($value as $key => $item) { + $allSequential = $allSequential && \is_numeric($key) && $key === \count($chunksSequential); + + $keyString = $this->valueToShortString($key); + $itemString = $this->valueToShortString($item); + + $chunks[] = "{$keyString} => {$itemString}"; + $chunksSequential[] = $itemString; + } + + $chunksToImplode = $allSequential ? $chunksSequential : $chunks; + + return '['.\implode(', ', $chunksToImplode).']'; + } +} diff --git a/vendor/psy/psysh/src/TabCompletion/Matcher/AbstractMatcher.php b/vendor/psy/psysh/src/TabCompletion/Matcher/AbstractMatcher.php new file mode 100644 index 0000000000..64726e84d4 --- /dev/null +++ b/vendor/psy/psysh/src/TabCompletion/Matcher/AbstractMatcher.php @@ -0,0 +1,196 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\TabCompletion\Matcher; + +/** + * Abstract tab completion Matcher. + * + * @author Marc Garcia <markcial@gmail.com> + */ +abstract class AbstractMatcher +{ + /** Syntax types */ + const CONSTANT_SYNTAX = '^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$'; + const VAR_SYNTAX = '^\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$'; + const MISC_OPERATORS = '+-*/^|&'; + /** Token values */ + const T_OPEN_TAG = 'T_OPEN_TAG'; + const T_VARIABLE = 'T_VARIABLE'; + const T_OBJECT_OPERATOR = 'T_OBJECT_OPERATOR'; + const T_DOUBLE_COLON = 'T_DOUBLE_COLON'; + const T_NEW = 'T_NEW'; + const T_CLONE = 'T_CLONE'; + const T_NS_SEPARATOR = 'T_NS_SEPARATOR'; + const T_STRING = 'T_STRING'; + const T_NAME_QUALIFIED = 'T_NAME_QUALIFIED'; + const T_WHITESPACE = 'T_WHITESPACE'; + const T_AND_EQUAL = 'T_AND_EQUAL'; + const T_BOOLEAN_AND = 'T_BOOLEAN_AND'; + const T_BOOLEAN_OR = 'T_BOOLEAN_OR'; + + const T_ENCAPSED_AND_WHITESPACE = 'T_ENCAPSED_AND_WHITESPACE'; + const T_REQUIRE = 'T_REQUIRE'; + const T_REQUIRE_ONCE = 'T_REQUIRE_ONCE'; + const T_INCLUDE = 'T_INCLUDE'; + const T_INCLUDE_ONCE = 'T_INCLUDE_ONCE'; + + /** + * Check whether this matcher can provide completions for $tokens. + * + * @param array $tokens Tokenized readline input + * + * @return bool + */ + public function hasMatched(array $tokens): bool + { + return false; + } + + /** + * Get current readline input word. + * + * @param array $tokens Tokenized readline input (see token_get_all) + * + * @return string + */ + protected function getInput(array $tokens): string + { + $var = ''; + $firstToken = \array_pop($tokens); + if (self::tokenIs($firstToken, self::T_STRING)) { + $var = $firstToken[1]; + } + + return $var; + } + + /** + * Get current namespace and class (if any) from readline input. + * + * @param array $tokens Tokenized readline input (see token_get_all) + * + * @return string + */ + protected function getNamespaceAndClass(array $tokens): string + { + $class = ''; + while (self::hasToken( + [self::T_NS_SEPARATOR, self::T_STRING, self::T_NAME_QUALIFIED], + $token = \array_pop($tokens) + )) { + if (self::needCompleteClass($token)) { + continue; + } + + $class = $token[1].$class; + } + + return $class; + } + + /** + * Provide tab completion matches for readline input. + * + * @param array $tokens information substracted with get_token_all + * @param array $info readline_info object + * + * @return array The matches resulting from the query + */ + abstract public function getMatches(array $tokens, array $info = []): array; + + /** + * Check whether $word starts with $prefix. + * + * @param string $prefix + * @param string $word + * + * @return bool + */ + public static function startsWith(string $prefix, string $word): bool + { + return \preg_match(\sprintf('#^%s#', $prefix), $word); + } + + /** + * Check whether $token matches a given syntax pattern. + * + * @param mixed $token A PHP token (see token_get_all) + * @param string $syntax A syntax pattern (default: variable pattern) + * + * @return bool + */ + public static function hasSyntax($token, string $syntax = self::VAR_SYNTAX): bool + { + if (!\is_array($token)) { + return false; + } + + $regexp = \sprintf('#%s#', $syntax); + + return (bool) \preg_match($regexp, $token[1]); + } + + /** + * Check whether $token type is $which. + * + * @param mixed $token A PHP token (see token_get_all) + * @param string $which A PHP token type + * + * @return bool + */ + public static function tokenIs($token, string $which): bool + { + if (!\is_array($token)) { + return false; + } + + return \token_name($token[0]) === $which; + } + + /** + * Check whether $token is an operator. + * + * @param mixed $token A PHP token (see token_get_all) + * + * @return bool + */ + public static function isOperator($token): bool + { + if (!\is_string($token)) { + return false; + } + + return \strpos(self::MISC_OPERATORS, $token) !== false; + } + + public static function needCompleteClass($token): bool + { + return \in_array($token[1], ['doc', 'ls', 'show']); + } + + /** + * Check whether $token type is present in $coll. + * + * @param array $coll A list of token types + * @param mixed $token A PHP token (see token_get_all) + * + * @return bool + */ + public static function hasToken(array $coll, $token): bool + { + if (!\is_array($token)) { + return false; + } + + return \in_array(\token_name($token[0]), $coll); + } +} diff --git a/vendor/psy/psysh/src/TabCompletion/Matcher/ClassAttributesMatcher.php b/vendor/psy/psysh/src/TabCompletion/Matcher/ClassAttributesMatcher.php new file mode 100644 index 0000000000..8d17b4a831 --- /dev/null +++ b/vendor/psy/psysh/src/TabCompletion/Matcher/ClassAttributesMatcher.php @@ -0,0 +1,87 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\TabCompletion\Matcher; + +/** + * A class attribute tab completion Matcher. + * + * Given a namespace and class, this matcher provides completion for constants + * and static properties. + * + * @author Marc Garcia <markcial@gmail.com> + */ +class ClassAttributesMatcher extends AbstractMatcher +{ + /** + * {@inheritdoc} + */ + public function getMatches(array $tokens, array $info = []): array + { + $input = $this->getInput($tokens); + + $firstToken = \array_pop($tokens); + if (self::tokenIs($firstToken, self::T_STRING)) { + // second token is the nekudotayim operator + \array_pop($tokens); + } + + $class = $this->getNamespaceAndClass($tokens); + + try { + $reflection = new \ReflectionClass($class); + } catch (\ReflectionException $re) { + return []; + } + + $vars = \array_merge( + \array_map( + function ($var) { + return '$'.$var; + }, + \array_keys($reflection->getStaticProperties()) + ), + \array_keys($reflection->getConstants()) + ); + + return \array_map( + function ($name) use ($class) { + $chunks = \explode('\\', $class); + $className = \array_pop($chunks); + + return $className.'::'.$name; + }, + \array_filter( + $vars, + function ($var) use ($input) { + return AbstractMatcher::startsWith($input, $var); + } + ) + ); + } + + /** + * {@inheritdoc} + */ + public function hasMatched(array $tokens): bool + { + $token = \array_pop($tokens); + $prevToken = \array_pop($tokens); + + switch (true) { + case self::tokenIs($prevToken, self::T_DOUBLE_COLON) && self::tokenIs($token, self::T_STRING): + case self::tokenIs($token, self::T_DOUBLE_COLON): + return true; + } + + return false; + } +} diff --git a/vendor/psy/psysh/src/TabCompletion/Matcher/ClassMethodDefaultParametersMatcher.php b/vendor/psy/psysh/src/TabCompletion/Matcher/ClassMethodDefaultParametersMatcher.php new file mode 100644 index 0000000000..a15f886886 --- /dev/null +++ b/vendor/psy/psysh/src/TabCompletion/Matcher/ClassMethodDefaultParametersMatcher.php @@ -0,0 +1,64 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\TabCompletion\Matcher; + +class ClassMethodDefaultParametersMatcher extends AbstractDefaultParametersMatcher +{ + public function getMatches(array $tokens, array $info = []): array + { + $openBracket = \array_pop($tokens); + $functionName = \array_pop($tokens); + $methodOperator = \array_pop($tokens); + + $class = $this->getNamespaceAndClass($tokens); + + try { + $reflection = new \ReflectionClass($class); + } catch (\ReflectionException $e) { + // In this case the class apparently does not exist, so we can do nothing + return []; + } + + $methods = $reflection->getMethods(\ReflectionMethod::IS_STATIC); + + foreach ($methods as $method) { + if ($method->getName() === $functionName[1]) { + return $this->getDefaultParameterCompletion($method->getParameters()); + } + } + + return []; + } + + public function hasMatched(array $tokens): bool + { + $openBracket = \array_pop($tokens); + + if ($openBracket !== '(') { + return false; + } + + $functionName = \array_pop($tokens); + + if (!self::tokenIs($functionName, self::T_STRING)) { + return false; + } + + $operator = \array_pop($tokens); + + if (!self::tokenIs($operator, self::T_DOUBLE_COLON)) { + return false; + } + + return true; + } +} diff --git a/vendor/psy/psysh/src/TabCompletion/Matcher/ClassMethodsMatcher.php b/vendor/psy/psysh/src/TabCompletion/Matcher/ClassMethodsMatcher.php new file mode 100644 index 0000000000..671d050d5f --- /dev/null +++ b/vendor/psy/psysh/src/TabCompletion/Matcher/ClassMethodsMatcher.php @@ -0,0 +1,84 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\TabCompletion\Matcher; + +/** + * A class method tab completion Matcher. + * + * Given a namespace and class, this matcher provides completion for static + * methods. + * + * @author Marc Garcia <markcial@gmail.com> + */ +class ClassMethodsMatcher extends AbstractMatcher +{ + /** + * {@inheritdoc} + */ + public function getMatches(array $tokens, array $info = []): array + { + $input = $this->getInput($tokens); + + $firstToken = \array_pop($tokens); + if (self::tokenIs($firstToken, self::T_STRING)) { + // second token is the nekudotayim operator + \array_pop($tokens); + } + + $class = $this->getNamespaceAndClass($tokens); + + try { + $reflection = new \ReflectionClass($class); + } catch (\ReflectionException $re) { + return []; + } + + if (self::needCompleteClass($tokens[1])) { + $methods = $reflection->getMethods(); + } else { + $methods = $reflection->getMethods(\ReflectionMethod::IS_STATIC); + } + + $methods = \array_map(function (\ReflectionMethod $method) { + return $method->getName(); + }, $methods); + + return \array_map( + function ($name) use ($class) { + $chunks = \explode('\\', $class); + $className = \array_pop($chunks); + + return $className.'::'.$name; + }, + \array_filter($methods, function ($method) use ($input) { + return AbstractMatcher::startsWith($input, $method); + }) + ); + } + + /** + * {@inheritdoc} + */ + public function hasMatched(array $tokens): bool + { + $token = \array_pop($tokens); + $prevToken = \array_pop($tokens); + + switch (true) { + case self::tokenIs($prevToken, self::T_DOUBLE_COLON) && self::tokenIs($token, self::T_STRING): + case self::tokenIs($token, self::T_DOUBLE_COLON): + return true; + } + + return false; + } +} diff --git a/vendor/psy/psysh/src/TabCompletion/Matcher/ClassNamesMatcher.php b/vendor/psy/psysh/src/TabCompletion/Matcher/ClassNamesMatcher.php new file mode 100644 index 0000000000..1b9835a121 --- /dev/null +++ b/vendor/psy/psysh/src/TabCompletion/Matcher/ClassNamesMatcher.php @@ -0,0 +1,77 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\TabCompletion\Matcher; + +/** + * A class name tab completion Matcher. + * + * This matcher provides completion for all declared classes. + * + * @author Marc Garcia <markcial@gmail.com> + */ +class ClassNamesMatcher extends AbstractMatcher +{ + /** + * {@inheritdoc} + */ + public function getMatches(array $tokens, array $info = []): array + { + $class = $this->getNamespaceAndClass($tokens); + if ($class !== '' && $class[0] === '\\') { + $class = \substr($class, 1, \strlen($class)); + } + $quotedClass = \preg_quote($class); + + return \array_map( + function ($className) use ($class) { + // get the number of namespace separators + $nsPos = \substr_count($class, '\\'); + $pieces = \explode('\\', $className); + // $methods = Mirror::get($class); + return \implode('\\', \array_slice($pieces, $nsPos, \count($pieces))); + }, + \array_filter( + \array_merge(\get_declared_classes(), \get_declared_interfaces()), + function ($className) use ($quotedClass) { + return AbstractMatcher::startsWith($quotedClass, $className); + } + ) + ); + } + + /** + * {@inheritdoc} + */ + public function hasMatched(array $tokens): bool + { + $token = \array_pop($tokens); + $prevToken = \array_pop($tokens); + + $ignoredTokens = [ + self::T_INCLUDE, self::T_INCLUDE_ONCE, self::T_REQUIRE, self::T_REQUIRE_ONCE, + ]; + + switch (true) { + case self::hasToken([$ignoredTokens], $token): + case self::hasToken([$ignoredTokens], $prevToken): + case \is_string($token) && $token === '$': + return false; + case self::hasToken([self::T_NEW, self::T_OPEN_TAG, self::T_NS_SEPARATOR, self::T_STRING], $prevToken): + case self::hasToken([self::T_NEW, self::T_OPEN_TAG, self::T_NS_SEPARATOR], $token): + case self::hasToken([self::T_OPEN_TAG, self::T_VARIABLE], $token): + case self::isOperator($token): + return true; + } + + return false; + } +} diff --git a/vendor/psy/psysh/src/TabCompletion/Matcher/CommandsMatcher.php b/vendor/psy/psysh/src/TabCompletion/Matcher/CommandsMatcher.php new file mode 100644 index 0000000000..8f7f3f6111 --- /dev/null +++ b/vendor/psy/psysh/src/TabCompletion/Matcher/CommandsMatcher.php @@ -0,0 +1,114 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\TabCompletion\Matcher; + +use Psy\Command\Command; + +/** + * A Psy Command tab completion Matcher. + * + * This matcher provides completion for all registered Psy Command names and + * aliases. + * + * @author Marc Garcia <markcial@gmail.com> + */ +class CommandsMatcher extends AbstractMatcher +{ + /** @var string[] */ + protected $commands = []; + + /** + * CommandsMatcher constructor. + * + * @param Command[] $commands + */ + public function __construct(array $commands) + { + $this->setCommands($commands); + } + + /** + * Set Commands for completion. + * + * @param Command[] $commands + */ + public function setCommands(array $commands) + { + $names = []; + foreach ($commands as $command) { + $names = \array_merge([$command->getName()], $names); + $names = \array_merge($command->getAliases(), $names); + } + $this->commands = $names; + } + + /** + * Check whether a command $name is defined. + * + * @param string $name + * + * @return bool + */ + protected function isCommand(string $name): bool + { + return \in_array($name, $this->commands); + } + + /** + * Check whether input matches a defined command. + * + * @param string $name + * + * @return bool + */ + protected function matchCommand(string $name): bool + { + foreach ($this->commands as $cmd) { + if ($this->startsWith($name, $cmd)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getMatches(array $tokens, array $info = []): array + { + $input = $this->getInput($tokens); + + return \array_filter($this->commands, function ($command) use ($input) { + return AbstractMatcher::startsWith($input, $command); + }); + } + + /** + * {@inheritdoc} + */ + public function hasMatched(array $tokens): bool + { + /* $openTag */ \array_shift($tokens); + $command = \array_shift($tokens); + + switch (true) { + case self::tokenIs($command, self::T_STRING) && + !$this->isCommand($command[1]) && + $this->matchCommand($command[1]) && + empty($tokens): + return true; + } + + return false; + } +} diff --git a/vendor/psy/psysh/src/TabCompletion/Matcher/ConstantsMatcher.php b/vendor/psy/psysh/src/TabCompletion/Matcher/ConstantsMatcher.php new file mode 100644 index 0000000000..58634352d1 --- /dev/null +++ b/vendor/psy/psysh/src/TabCompletion/Matcher/ConstantsMatcher.php @@ -0,0 +1,54 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\TabCompletion\Matcher; + +/** + * A constant name tab completion Matcher. + * + * This matcher provides completion for all defined constants. + * + * @author Marc Garcia <markcial@gmail.com> + */ +class ConstantsMatcher extends AbstractMatcher +{ + /** + * {@inheritdoc} + */ + public function getMatches(array $tokens, array $info = []): array + { + $const = $this->getInput($tokens); + + return \array_filter(\array_keys(\get_defined_constants()), function ($constant) use ($const) { + return AbstractMatcher::startsWith($const, $constant); + }); + } + + /** + * {@inheritdoc} + */ + public function hasMatched(array $tokens): bool + { + $token = \array_pop($tokens); + $prevToken = \array_pop($tokens); + + switch (true) { + case self::tokenIs($prevToken, self::T_NEW): + case self::tokenIs($prevToken, self::T_NS_SEPARATOR): + return false; + case self::hasToken([self::T_OPEN_TAG, self::T_STRING], $token): + case self::isOperator($token): + return true; + } + + return false; + } +} diff --git a/vendor/psy/psysh/src/TabCompletion/Matcher/FunctionDefaultParametersMatcher.php b/vendor/psy/psysh/src/TabCompletion/Matcher/FunctionDefaultParametersMatcher.php new file mode 100644 index 0000000000..3d683bd8d3 --- /dev/null +++ b/vendor/psy/psysh/src/TabCompletion/Matcher/FunctionDefaultParametersMatcher.php @@ -0,0 +1,53 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\TabCompletion\Matcher; + +class FunctionDefaultParametersMatcher extends AbstractDefaultParametersMatcher +{ + public function getMatches(array $tokens, array $info = []): array + { + \array_pop($tokens); // open bracket + + $functionName = \array_pop($tokens); + + try { + $reflection = new \ReflectionFunction($functionName[1]); + } catch (\ReflectionException $e) { + return []; + } + + $parameters = $reflection->getParameters(); + + return $this->getDefaultParameterCompletion($parameters); + } + + public function hasMatched(array $tokens): bool + { + $openBracket = \array_pop($tokens); + + if ($openBracket !== '(') { + return false; + } + + $functionName = \array_pop($tokens); + + if (!self::tokenIs($functionName, self::T_STRING)) { + return false; + } + + if (!\function_exists($functionName[1])) { + return false; + } + + return true; + } +} diff --git a/vendor/psy/psysh/src/TabCompletion/Matcher/FunctionsMatcher.php b/vendor/psy/psysh/src/TabCompletion/Matcher/FunctionsMatcher.php new file mode 100644 index 0000000000..e31f14d87d --- /dev/null +++ b/vendor/psy/psysh/src/TabCompletion/Matcher/FunctionsMatcher.php @@ -0,0 +1,56 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\TabCompletion\Matcher; + +/** + * A function name tab completion Matcher. + * + * This matcher provides completion for all internal and user-defined functions. + * + * @author Marc Garcia <markcial@gmail.com> + */ +class FunctionsMatcher extends AbstractMatcher +{ + /** + * {@inheritdoc} + */ + public function getMatches(array $tokens, array $info = []): array + { + $func = $this->getInput($tokens); + + $functions = \get_defined_functions(); + $allFunctions = \array_merge($functions['user'], $functions['internal']); + + return \array_filter($allFunctions, function ($function) use ($func) { + return AbstractMatcher::startsWith($func, $function); + }); + } + + /** + * {@inheritdoc} + */ + public function hasMatched(array $tokens): bool + { + $token = \array_pop($tokens); + $prevToken = \array_pop($tokens); + + switch (true) { + case self::tokenIs($prevToken, self::T_NEW): + return false; + case self::hasToken([self::T_OPEN_TAG, self::T_STRING], $token): + case self::isOperator($token): + return true; + } + + return false; + } +} diff --git a/vendor/psy/psysh/src/TabCompletion/Matcher/KeywordsMatcher.php b/vendor/psy/psysh/src/TabCompletion/Matcher/KeywordsMatcher.php new file mode 100644 index 0000000000..cc4c643d3e --- /dev/null +++ b/vendor/psy/psysh/src/TabCompletion/Matcher/KeywordsMatcher.php @@ -0,0 +1,85 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\TabCompletion\Matcher; + +/** + * A PHP keyword tab completion Matcher. + * + * This matcher provides completion for all function-like PHP keywords. + * + * @author Marc Garcia <markcial@gmail.com> + */ +class KeywordsMatcher extends AbstractMatcher +{ + protected $keywords = [ + 'array', 'clone', 'declare', 'die', 'echo', 'empty', 'eval', 'exit', 'include', + 'include_once', 'isset', 'list', 'print', 'require', 'require_once', 'unset', + ]; + + protected $mandatoryStartKeywords = [ + 'die', 'echo', 'print', 'unset', + ]; + + /** + * Get all (completable) PHP keywords. + * + * @return array + */ + public function getKeywords(): array + { + return $this->keywords; + } + + /** + * Check whether $keyword is a (completable) PHP keyword. + * + * @param string $keyword + * + * @return bool + */ + public function isKeyword(string $keyword): bool + { + return \in_array($keyword, $this->keywords); + } + + /** + * {@inheritdoc} + */ + public function getMatches(array $tokens, array $info = []): array + { + $input = $this->getInput($tokens); + + return \array_filter($this->keywords, function ($keyword) use ($input) { + return AbstractMatcher::startsWith($input, $keyword); + }); + } + + /** + * {@inheritdoc} + */ + public function hasMatched(array $tokens): bool + { + $token = \array_pop($tokens); + $prevToken = \array_pop($tokens); + + switch (true) { + case self::hasToken([self::T_OPEN_TAG, self::T_VARIABLE], $token): +// case is_string($token) && $token === '$': + case self::hasToken([self::T_OPEN_TAG, self::T_VARIABLE], $prevToken) && + self::tokenIs($token, self::T_STRING): + case self::isOperator($token): + return true; + } + + return false; + } +} diff --git a/vendor/psy/psysh/src/TabCompletion/Matcher/MongoClientMatcher.php b/vendor/psy/psysh/src/TabCompletion/Matcher/MongoClientMatcher.php new file mode 100644 index 0000000000..2f3e163314 --- /dev/null +++ b/vendor/psy/psysh/src/TabCompletion/Matcher/MongoClientMatcher.php @@ -0,0 +1,71 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\TabCompletion\Matcher; + +/** + * A MongoDB Client tab completion Matcher. + * + * This matcher provides completion for MongoClient database names. + * + * @author Marc Garcia <markcial@gmail.com> + */ +class MongoClientMatcher extends AbstractContextAwareMatcher +{ + /** + * {@inheritdoc} + */ + public function getMatches(array $tokens, array $info = []): array + { + $input = $this->getInput($tokens); + + $firstToken = \array_pop($tokens); + if (self::tokenIs($firstToken, self::T_STRING)) { + // second token is the object operator + \array_pop($tokens); + } + $objectToken = \array_pop($tokens); + $objectName = \str_replace('$', '', $objectToken[1]); + $object = $this->getVariable($objectName); + + if (!$object instanceof \MongoClient) { + return []; + } + + $list = $object->listDBs(); + + return \array_filter( + \array_map(function ($info) { + return $info['name']; + }, $list['databases']), + function ($var) use ($input) { + return AbstractMatcher::startsWith($input, $var); + } + ); + } + + /** + * {@inheritdoc} + */ + public function hasMatched(array $tokens): bool + { + $token = \array_pop($tokens); + $prevToken = \array_pop($tokens); + + switch (true) { + case self::tokenIs($token, self::T_OBJECT_OPERATOR): + case self::tokenIs($prevToken, self::T_OBJECT_OPERATOR): + return true; + } + + return false; + } +} diff --git a/vendor/psy/psysh/src/TabCompletion/Matcher/MongoDatabaseMatcher.php b/vendor/psy/psysh/src/TabCompletion/Matcher/MongoDatabaseMatcher.php new file mode 100644 index 0000000000..c1edbc934f --- /dev/null +++ b/vendor/psy/psysh/src/TabCompletion/Matcher/MongoDatabaseMatcher.php @@ -0,0 +1,67 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\TabCompletion\Matcher; + +/** + * A MongoDB tab completion Matcher. + * + * This matcher provides completion for Mongo collection names. + * + * @author Marc Garcia <markcial@gmail.com> + */ +class MongoDatabaseMatcher extends AbstractContextAwareMatcher +{ + /** + * {@inheritdoc} + */ + public function getMatches(array $tokens, array $info = []): array + { + $input = $this->getInput($tokens); + + $firstToken = \array_pop($tokens); + if (self::tokenIs($firstToken, self::T_STRING)) { + // second token is the object operator + \array_pop($tokens); + } + $objectToken = \array_pop($tokens); + $objectName = \str_replace('$', '', $objectToken[1]); + $object = $this->getVariable($objectName); + + if (!$object instanceof \MongoDB) { + return []; + } + + return \array_filter( + $object->getCollectionNames(), + function ($var) use ($input) { + return AbstractMatcher::startsWith($input, $var); + } + ); + } + + /** + * {@inheritdoc} + */ + public function hasMatched(array $tokens): bool + { + $token = \array_pop($tokens); + $prevToken = \array_pop($tokens); + + switch (true) { + case self::tokenIs($token, self::T_OBJECT_OPERATOR): + case self::tokenIs($prevToken, self::T_OBJECT_OPERATOR): + return true; + } + + return false; + } +} diff --git a/vendor/psy/psysh/src/TabCompletion/Matcher/ObjectAttributesMatcher.php b/vendor/psy/psysh/src/TabCompletion/Matcher/ObjectAttributesMatcher.php new file mode 100644 index 0000000000..29281f6ecb --- /dev/null +++ b/vendor/psy/psysh/src/TabCompletion/Matcher/ObjectAttributesMatcher.php @@ -0,0 +1,78 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\TabCompletion\Matcher; + +use InvalidArgumentException; + +/** + * An object attribute tab completion Matcher. + * + * This matcher provides completion for properties of objects in the current + * Context. + * + * @author Marc Garcia <markcial@gmail.com> + */ +class ObjectAttributesMatcher extends AbstractContextAwareMatcher +{ + /** + * {@inheritdoc} + */ + public function getMatches(array $tokens, array $info = []): array + { + $input = $this->getInput($tokens); + + $firstToken = \array_pop($tokens); + if (self::tokenIs($firstToken, self::T_STRING)) { + // second token is the object operator + \array_pop($tokens); + } + $objectToken = \array_pop($tokens); + if (!\is_array($objectToken)) { + return []; + } + $objectName = \str_replace('$', '', $objectToken[1]); + + try { + $object = $this->getVariable($objectName); + } catch (InvalidArgumentException $e) { + return []; + } + + if (!\is_object($object)) { + return []; + } + + return \array_filter( + \array_keys(\get_class_vars(\get_class($object))), + function ($var) use ($input) { + return AbstractMatcher::startsWith($input, $var); + } + ); + } + + /** + * {@inheritdoc} + */ + public function hasMatched(array $tokens): bool + { + $token = \array_pop($tokens); + $prevToken = \array_pop($tokens); + + switch (true) { + case self::tokenIs($token, self::T_OBJECT_OPERATOR): + case self::tokenIs($prevToken, self::T_OBJECT_OPERATOR): + return true; + } + + return false; + } +} diff --git a/vendor/psy/psysh/src/TabCompletion/Matcher/ObjectMethodDefaultParametersMatcher.php b/vendor/psy/psysh/src/TabCompletion/Matcher/ObjectMethodDefaultParametersMatcher.php new file mode 100644 index 0000000000..fbf111e803 --- /dev/null +++ b/vendor/psy/psysh/src/TabCompletion/Matcher/ObjectMethodDefaultParametersMatcher.php @@ -0,0 +1,71 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\TabCompletion\Matcher; + +class ObjectMethodDefaultParametersMatcher extends AbstractDefaultParametersMatcher +{ + public function getMatches(array $tokens, array $info = []): array + { + $openBracket = \array_pop($tokens); + $functionName = \array_pop($tokens); + $methodOperator = \array_pop($tokens); + + $objectToken = \array_pop($tokens); + if (!\is_array($objectToken)) { + return []; + } + + $objectName = \str_replace('$', '', $objectToken[1]); + + try { + $object = $this->getVariable($objectName); + $reflection = new \ReflectionObject($object); + } catch (\InvalidArgumentException $e) { + return []; + } catch (\ReflectionException $e) { + return []; + } + + $methods = $reflection->getMethods(); + + foreach ($methods as $method) { + if ($method->getName() === $functionName[1]) { + return $this->getDefaultParameterCompletion($method->getParameters()); + } + } + + return []; + } + + public function hasMatched(array $tokens): bool + { + $openBracket = \array_pop($tokens); + + if ($openBracket !== '(') { + return false; + } + + $functionName = \array_pop($tokens); + + if (!self::tokenIs($functionName, self::T_STRING)) { + return false; + } + + $operator = \array_pop($tokens); + + if (!self::tokenIs($operator, self::T_OBJECT_OPERATOR)) { + return false; + } + + return true; + } +} diff --git a/vendor/psy/psysh/src/TabCompletion/Matcher/ObjectMethodsMatcher.php b/vendor/psy/psysh/src/TabCompletion/Matcher/ObjectMethodsMatcher.php new file mode 100644 index 0000000000..918f16881f --- /dev/null +++ b/vendor/psy/psysh/src/TabCompletion/Matcher/ObjectMethodsMatcher.php @@ -0,0 +1,80 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\TabCompletion\Matcher; + +use InvalidArgumentException; + +/** + * An object method tab completion Matcher. + * + * This matcher provides completion for methods of objects in the current + * Context. + * + * @author Marc Garcia <markcial@gmail.com> + */ +class ObjectMethodsMatcher extends AbstractContextAwareMatcher +{ + /** + * {@inheritdoc} + */ + public function getMatches(array $tokens, array $info = []): array + { + $input = $this->getInput($tokens); + + $firstToken = \array_pop($tokens); + if (self::tokenIs($firstToken, self::T_STRING)) { + // second token is the object operator + \array_pop($tokens); + } + $objectToken = \array_pop($tokens); + if (!\is_array($objectToken)) { + return []; + } + $objectName = \str_replace('$', '', $objectToken[1]); + + try { + $object = $this->getVariable($objectName); + } catch (InvalidArgumentException $e) { + return []; + } + + if (!\is_object($object)) { + return []; + } + + return \array_filter( + \get_class_methods($object), + function ($var) use ($input) { + return AbstractMatcher::startsWith($input, $var) && + // also check that we do not suggest invoking a super method(__construct, __wakeup, …) + !AbstractMatcher::startsWith('__', $var); + } + ); + } + + /** + * {@inheritdoc} + */ + public function hasMatched(array $tokens): bool + { + $token = \array_pop($tokens); + $prevToken = \array_pop($tokens); + + switch (true) { + case self::tokenIs($token, self::T_OBJECT_OPERATOR): + case self::tokenIs($prevToken, self::T_OBJECT_OPERATOR): + return true; + } + + return false; + } +} diff --git a/vendor/psy/psysh/src/TabCompletion/Matcher/VariablesMatcher.php b/vendor/psy/psysh/src/TabCompletion/Matcher/VariablesMatcher.php new file mode 100644 index 0000000000..0c401068a6 --- /dev/null +++ b/vendor/psy/psysh/src/TabCompletion/Matcher/VariablesMatcher.php @@ -0,0 +1,51 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\TabCompletion\Matcher; + +/** + * A variable name tab completion Matcher. + * + * This matcher provides completion for variable names in the current Context. + * + * @author Marc Garcia <markcial@gmail.com> + */ +class VariablesMatcher extends AbstractContextAwareMatcher +{ + /** + * {@inheritdoc} + */ + public function getMatches(array $tokens, array $info = []): array + { + $var = \str_replace('$', '', $this->getInput($tokens)); + + return \array_filter(\array_keys($this->getVariables()), function ($variable) use ($var) { + return AbstractMatcher::startsWith($var, $variable); + }); + } + + /** + * {@inheritdoc} + */ + public function hasMatched(array $tokens): bool + { + $token = \array_pop($tokens); + + switch (true) { + case self::hasToken([self::T_OPEN_TAG, self::T_VARIABLE], $token): + case \is_string($token) && $token === '$': + case self::isOperator($token): + return true; + } + + return false; + } +} diff --git a/vendor/psy/psysh/src/Util/Docblock.php b/vendor/psy/psysh/src/Util/Docblock.php new file mode 100644 index 0000000000..1d06ba6fd1 --- /dev/null +++ b/vendor/psy/psysh/src/Util/Docblock.php @@ -0,0 +1,246 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\Util; + +/** + * A docblock representation. + * + * Based on PHP-DocBlock-Parser by Paul Scott: + * + * {@link http://www.github.com/icio/PHP-DocBlock-Parser} + * + * @author Paul Scott <paul@duedil.com> + * @author Justin Hileman <justin@justinhileman.info> + */ +class Docblock +{ + /** + * Tags in the docblock that have a whitespace-delimited number of parameters + * (such as `@param type var desc` and `@return type desc`) and the names of + * those parameters. + * + * @var array + */ + public static $vectors = [ + 'throws' => ['type', 'desc'], + 'param' => ['type', 'var', 'desc'], + 'return' => ['type', 'desc'], + ]; + + protected $reflector; + + /** + * The description of the symbol. + * + * @var string + */ + public $desc; + + /** + * The tags defined in the docblock. + * + * The array has keys which are the tag names (excluding the @) and values + * that are arrays, each of which is an entry for the tag. + * + * In the case where the tag name is defined in {@see DocBlock::$vectors} the + * value within the tag-value array is an array in itself with keys as + * described by {@see DocBlock::$vectors}. + * + * @var array + */ + public $tags; + + /** + * The entire DocBlock comment that was parsed. + * + * @var string + */ + public $comment; + + /** + * Docblock constructor. + * + * @param \Reflector $reflector + */ + public function __construct(\Reflector $reflector) + { + $this->reflector = $reflector; + $this->setComment($reflector->getDocComment()); + } + + /** + * Set and parse the docblock comment. + * + * @param string $comment The docblock + */ + protected function setComment(string $comment) + { + $this->desc = ''; + $this->tags = []; + $this->comment = $comment; + + $this->parseComment($comment); + } + + /** + * Find the length of the docblock prefix. + * + * @param array $lines + * + * @return int Prefix length + */ + protected static function prefixLength(array $lines): int + { + // find only lines with interesting things + $lines = \array_filter($lines, function ($line) { + return \substr($line, \strspn($line, "* \t\n\r\0\x0B")); + }); + + // if we sort the lines, we only have to compare two items + \sort($lines); + + $first = \reset($lines); + $last = \end($lines); + + // Special case for single-line comments + if (\count($lines) === 1) { + return \strspn($first, "* \t\n\r\0\x0B"); + } + + // find the longest common substring + $count = \min(\strlen($first), \strlen($last)); + for ($i = 0; $i < $count; $i++) { + if ($first[$i] !== $last[$i]) { + return $i; + } + } + + return $count; + } + + /** + * Parse the comment into the component parts and set the state of the object. + * + * @param string $comment The docblock + */ + protected function parseComment(string $comment) + { + // Strip the opening and closing tags of the docblock + $comment = \substr($comment, 3, -2); + + // Split into arrays of lines + $comment = \array_filter(\preg_split('/\r?\n\r?/', $comment)); + + // Trim asterisks and whitespace from the beginning and whitespace from the end of lines + $prefixLength = self::prefixLength($comment); + $comment = \array_map(function ($line) use ($prefixLength) { + return \rtrim(\substr($line, $prefixLength)); + }, $comment); + + // Group the lines together by @tags + $blocks = []; + $b = -1; + foreach ($comment as $line) { + if (self::isTagged($line)) { + $b++; + $blocks[] = []; + } elseif ($b === -1) { + $b = 0; + $blocks[] = []; + } + $blocks[$b][] = $line; + } + + // Parse the blocks + foreach ($blocks as $block => $body) { + $body = \trim(\implode("\n", $body)); + + if ($block === 0 && !self::isTagged($body)) { + // This is the description block + $this->desc = $body; + } else { + // This block is tagged + $tag = \substr(self::strTag($body), 1); + $body = \ltrim(\substr($body, \strlen($tag) + 2)); + + if (isset(self::$vectors[$tag])) { + // The tagged block is a vector + $count = \count(self::$vectors[$tag]); + if ($body) { + $parts = \preg_split('/\s+/', $body, $count); + } else { + $parts = []; + } + + // Default the trailing values + $parts = \array_pad($parts, $count, null); + + // Store as a mapped array + $this->tags[$tag][] = \array_combine(self::$vectors[$tag], $parts); + } else { + // The tagged block is only text + $this->tags[$tag][] = $body; + } + } + } + } + + /** + * Whether or not a docblock contains a given @tag. + * + * @param string $tag The name of the @tag to check for + * + * @return bool + */ + public function hasTag(string $tag): bool + { + return \is_array($this->tags) && \array_key_exists($tag, $this->tags); + } + + /** + * The value of a tag. + * + * @param string $tag + * + * @return array + */ + public function tag(string $tag): array + { + return $this->hasTag($tag) ? $this->tags[$tag] : null; + } + + /** + * Whether or not a string begins with a @tag. + * + * @param string $str + * + * @return bool + */ + public static function isTagged(string $str): bool + { + return isset($str[1]) && $str[0] === '@' && !\preg_match('/[^A-Za-z]/', $str[1]); + } + + /** + * The tag at the beginning of a string. + * + * @param string $str + * + * @return string|null + */ + public static function strTag(string $str) + { + if (\preg_match('/^@[a-z0-9_]+/', $str, $matches)) { + return $matches[0]; + } + } +} diff --git a/vendor/psy/psysh/src/Util/Json.php b/vendor/psy/psysh/src/Util/Json.php new file mode 100644 index 0000000000..71e6336f53 --- /dev/null +++ b/vendor/psy/psysh/src/Util/Json.php @@ -0,0 +1,33 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\Util; + +/** + * A static class to wrap JSON encoding/decoding with PsySH's default options. + */ +class Json +{ + /** + * Encode a value as JSON. + * + * @param mixed $val + * @param int $opt + * + * @return string + */ + public static function encode($val, int $opt = 0): string + { + $opt |= \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE; + + return \json_encode($val, $opt); + } +} diff --git a/vendor/psy/psysh/src/Util/Mirror.php b/vendor/psy/psysh/src/Util/Mirror.php new file mode 100644 index 0000000000..d13d8d75ab --- /dev/null +++ b/vendor/psy/psysh/src/Util/Mirror.php @@ -0,0 +1,150 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\Util; + +use Psy\Exception\RuntimeException; +use Psy\Reflection\ReflectionClassConstant; +use Psy\Reflection\ReflectionConstant_; +use Psy\Reflection\ReflectionNamespace; + +/** + * A utility class for getting Reflectors. + */ +class Mirror +{ + const CONSTANT = 1; + const METHOD = 2; + const STATIC_PROPERTY = 4; + const PROPERTY = 8; + + /** + * Get a Reflector for a function, class or instance, constant, method or property. + * + * Optionally, pass a $filter param to restrict the types of members checked. For example, to only Reflectors for + * static properties and constants, pass: + * + * $filter = Mirror::CONSTANT | Mirror::STATIC_PROPERTY + * + * @throws \Psy\Exception\RuntimeException when a $member specified but not present on $value + * @throws \InvalidArgumentException if $value is something other than an object or class/function name + * + * @param mixed $value Class or function name, or variable instance + * @param string $member Optional: property, constant or method name (default: null) + * @param int $filter (default: CONSTANT | METHOD | PROPERTY | STATIC_PROPERTY) + * + * @return \Reflector + */ + public static function get($value, string $member = null, int $filter = 15): \Reflector + { + if ($member === null && \is_string($value)) { + if (\function_exists($value)) { + return new \ReflectionFunction($value); + } elseif (\defined($value) || ReflectionConstant_::isMagicConstant($value)) { + return new ReflectionConstant_($value); + } + } + + $class = self::getClass($value); + + if ($member === null) { + return $class; + } elseif ($filter & self::CONSTANT && $class->hasConstant($member)) { + return ReflectionClassConstant::create($value, $member); + } elseif ($filter & self::METHOD && $class->hasMethod($member)) { + return $class->getMethod($member); + } elseif ($filter & self::PROPERTY && $class->hasProperty($member)) { + return $class->getProperty($member); + } elseif ($filter & self::STATIC_PROPERTY && $class->hasProperty($member) && $class->getProperty($member)->isStatic()) { + return $class->getProperty($member); + } else { + throw new RuntimeException(\sprintf('Unknown member %s on class %s', $member, \is_object($value) ? \get_class($value) : $value)); + } + } + + /** + * Get a ReflectionClass (or ReflectionObject, or ReflectionNamespace) if possible. + * + * @throws \InvalidArgumentException if $value is not a namespace or class name or instance + * + * @param mixed $value + * + * @return \ReflectionClass|ReflectionNamespace + */ + private static function getClass($value) + { + if (\is_object($value)) { + return new \ReflectionObject($value); + } + + if (!\is_string($value)) { + throw new \InvalidArgumentException('Mirror expects an object or class'); + } + + if (\class_exists($value) || \interface_exists($value) || \trait_exists($value)) { + return new \ReflectionClass($value); + } + + $namespace = \preg_replace('/(^\\\\|\\\\$)/', '', $value); + if (self::namespaceExists($namespace)) { + return new ReflectionNamespace($namespace); + } + + throw new \InvalidArgumentException('Unknown namespace, class or function: '.$value); + } + + /** + * Check declared namespaces for a given namespace. + */ + private static function namespaceExists(string $value): bool + { + return \in_array(\strtolower($value), self::getDeclaredNamespaces()); + } + + /** + * Get an array of all currently declared namespaces. + * + * Note that this relies on at least one function, class, interface, trait + * or constant to have been declared in that namespace. + */ + private static function getDeclaredNamespaces(): array + { + $functions = \get_defined_functions(); + + $allNames = \array_merge( + $functions['internal'], + $functions['user'], + \get_declared_classes(), + \get_declared_interfaces(), + \get_declared_traits(), + \array_keys(\get_defined_constants()) + ); + + $namespaces = []; + foreach ($allNames as $name) { + $chunks = \explode('\\', \strtolower($name)); + + // the last one is the function or class or whatever... + \array_pop($chunks); + + while (!empty($chunks)) { + $namespaces[\implode('\\', $chunks)] = true; + \array_pop($chunks); + } + } + + $namespaceNames = \array_keys($namespaces); + + \sort($namespaceNames); + + return $namespaceNames; + } +} diff --git a/vendor/psy/psysh/src/Util/Str.php b/vendor/psy/psysh/src/Util/Str.php new file mode 100644 index 0000000000..f6d93e1f2e --- /dev/null +++ b/vendor/psy/psysh/src/Util/Str.php @@ -0,0 +1,114 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\Util; + +/** + * String utility methods. + * + * @author ju1ius + */ +class Str +{ + const UNVIS_RX = <<<'EOS' +/ + \\(?: + ((?:040)|s) + | (240) + | (?: M-(.) ) + | (?: M\^(.) ) + | (?: \^(.) ) + ) +/xS +EOS; + + /** + * Decodes a string encoded by libsd's strvis. + * + * From `man 3 vis`: + * + * Use an ‘M’ to represent meta characters (characters with the 8th bit set), + * and use a caret ‘^’ to represent control characters (see iscntrl(3)). + * The following formats are used: + * + * \040 Represents ASCII space. + * + * \240 Represents Meta-space (  in HTML). + * + * \M-C Represents character ‘C’ with the 8th bit set. + * Spans characters ‘\241’ through ‘\376’. + * + * \M^C Represents control character ‘C’ with the 8th bit set. + * Spans characters ‘\200’ through ‘\237’, and ‘\377’ (as ‘\M^?’). + * + * \^C Represents the control character ‘C’. + * Spans characters ‘\000’ through ‘\037’, and ‘\177’ (as ‘\^?’). + * + * The other formats are supported by PHP's stripcslashes, + * except for the \s sequence (ASCII space). + * + * @param string $input The string to decode + * + * @return string + */ + public static function unvis(string $input): string + { + $output = \preg_replace_callback(self::UNVIS_RX, 'self::unvisReplace', $input); + // other escapes & octal are handled by stripcslashes + return \stripcslashes($output); + } + + /** + * Callback for Str::unvis. + * + * @param array $match The matches passed by preg_replace_callback + * + * @return string + */ + protected static function unvisReplace(array $match): string + { + // \040, \s + if (!empty($match[1])) { + return "\x20"; + } + // \240 + if (!empty($match[2])) { + return "\xa0"; + } + // \M-(.) + if (isset($match[3]) && $match[3] !== '') { + $chr = $match[3]; + // unvis S_META1 + $cp = 0200; + $cp |= \ord($chr); + + return \chr($cp); + } + // \M^(.) + if (isset($match[4]) && $match[4] !== '') { + $chr = $match[4]; + // unvis S_META | S_CTRL + $cp = 0200; + $cp |= ($chr === '?') ? 0177 : \ord($chr) & 037; + + return \chr($cp); + } + // \^(.) + if (isset($match[5]) && $match[5] !== '') { + $chr = $match[5]; + // unvis S_CTRL + $cp = 0; + $cp |= ($chr === '?') ? 0177 : \ord($chr) & 037; + + return \chr($cp); + } + } +} diff --git a/vendor/psy/psysh/src/VarDumper/Cloner.php b/vendor/psy/psysh/src/VarDumper/Cloner.php new file mode 100644 index 0000000000..2b58594aa1 --- /dev/null +++ b/vendor/psy/psysh/src/VarDumper/Cloner.php @@ -0,0 +1,43 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\VarDumper; + +use Symfony\Component\VarDumper\Caster\Caster; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Cloner\Stub; +use Symfony\Component\VarDumper\Cloner\VarCloner; + +/** + * A PsySH-specialized VarCloner. + */ +class Cloner extends VarCloner +{ + private $filter = 0; + + /** + * {@inheritdoc} + */ + public function cloneVar($var, $filter = 0): Data + { + $this->filter = $filter; + + return parent::cloneVar($var, $filter); + } + + /** + * {@inheritdoc} + */ + protected function castResource(Stub $stub, $isNested): array + { + return Caster::EXCLUDE_VERBOSE & $this->filter ? [] : parent::castResource($stub, $isNested); + } +} diff --git a/vendor/psy/psysh/src/VarDumper/Dumper.php b/vendor/psy/psysh/src/VarDumper/Dumper.php new file mode 100644 index 0000000000..230880e2e9 --- /dev/null +++ b/vendor/psy/psysh/src/VarDumper/Dumper.php @@ -0,0 +1,109 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\VarDumper; + +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\VarDumper\Cloner\Cursor; +use Symfony\Component\VarDumper\Dumper\CliDumper; + +/** + * A PsySH-specialized CliDumper. + */ +class Dumper extends CliDumper +{ + private $formatter; + private $forceArrayIndexes; + + protected static $onlyControlCharsRx = '/^[\x00-\x1F\x7F]+$/'; + protected static $controlCharsRx = '/([\x00-\x1F\x7F]+)/'; + protected static $controlCharsMap = [ + "\0" => '\0', + "\t" => '\t', + "\n" => '\n', + "\v" => '\v', + "\f" => '\f', + "\r" => '\r', + "\033" => '\e', + ]; + + public function __construct(OutputFormatter $formatter, $forceArrayIndexes = false) + { + $this->formatter = $formatter; + $this->forceArrayIndexes = $forceArrayIndexes; + parent::__construct(); + $this->setColors(false); + } + + /** + * {@inheritdoc} + */ + public function enterHash(Cursor $cursor, $type, $class, $hasChild) + { + if (Cursor::HASH_INDEXED === $type || Cursor::HASH_ASSOC === $type) { + $class = 0; + } + parent::enterHash($cursor, $type, $class, $hasChild); + } + + /** + * {@inheritdoc} + */ + protected function dumpKey(Cursor $cursor) + { + if ($this->forceArrayIndexes || Cursor::HASH_INDEXED !== $cursor->hashType) { + parent::dumpKey($cursor); + } + } + + protected function style($style, $value, $attr = []): string + { + if ('ref' === $style) { + $value = \strtr($value, '@', '#'); + } + + $styled = ''; + $map = self::$controlCharsMap; + $cchr = $this->styles['cchr']; + + $chunks = \preg_split(self::$controlCharsRx, $value, -1, \PREG_SPLIT_NO_EMPTY | \PREG_SPLIT_DELIM_CAPTURE); + foreach ($chunks as $chunk) { + if (\preg_match(self::$onlyControlCharsRx, $chunk)) { + $chars = ''; + $i = 0; + do { + $chars .= isset($map[$chunk[$i]]) ? $map[$chunk[$i]] : \sprintf('\x%02X', \ord($chunk[$i])); + } while (isset($chunk[++$i])); + + $chars = $this->formatter->escape($chars); + $styled .= "<{$cchr}>{$chars}</{$cchr}>"; + } else { + $styled .= $this->formatter->escape($chunk); + } + } + + $style = $this->styles[$style]; + + return "<{$style}>{$styled}</{$style}>"; + } + + /** + * {@inheritdoc} + */ + protected function dumpLine($depth, $endOfValue = false) + { + if ($endOfValue && 0 < $depth) { + $this->line .= ','; + } + $this->line = $this->formatter->format($this->line); + parent::dumpLine($depth, $endOfValue); + } +} diff --git a/vendor/psy/psysh/src/VarDumper/Presenter.php b/vendor/psy/psysh/src/VarDumper/Presenter.php new file mode 100644 index 0000000000..7319534ff9 --- /dev/null +++ b/vendor/psy/psysh/src/VarDumper/Presenter.php @@ -0,0 +1,139 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\VarDumper; + +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\VarDumper\Caster\Caster; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * A Presenter service. + */ +class Presenter +{ + const VERBOSE = 1; + + private $cloner; + private $dumper; + private $exceptionsImportants = [ + "\0*\0message", + "\0*\0code", + "\0*\0file", + "\0*\0line", + "\0Exception\0previous", + ]; + private $styles = [ + 'num' => 'number', + 'integer' => 'integer', + 'float' => 'float', + 'const' => 'const', + 'str' => 'string', + 'cchr' => 'default', + 'note' => 'class', + 'ref' => 'default', + 'public' => 'public', + 'protected' => 'protected', + 'private' => 'private', + 'meta' => 'comment', + 'key' => 'comment', + 'index' => 'number', + ]; + + public function __construct(OutputFormatter $formatter, $forceArrayIndexes = false) + { + // Work around https://github.com/symfony/symfony/issues/23572 + $oldLocale = \setlocale(\LC_NUMERIC, 0); + \setlocale(\LC_NUMERIC, 'C'); + + $this->dumper = new Dumper($formatter, $forceArrayIndexes); + $this->dumper->setStyles($this->styles); + + // Now put the locale back + \setlocale(\LC_NUMERIC, $oldLocale); + + $this->cloner = new Cloner(); + $this->cloner->addCasters(['*' => function ($obj, array $a, Stub $stub, $isNested, $filter = 0) { + if ($filter || $isNested) { + if ($obj instanceof \Exception) { + $a = Caster::filter($a, Caster::EXCLUDE_NOT_IMPORTANT | Caster::EXCLUDE_EMPTY, $this->exceptionsImportants); + } else { + $a = Caster::filter($a, Caster::EXCLUDE_PROTECTED | Caster::EXCLUDE_PRIVATE); + } + } + + return $a; + }]); + } + + /** + * Register casters. + * + * @see http://symfony.com/doc/current/components/var_dumper/advanced.html#casters + * + * @param callable[] $casters A map of casters + */ + public function addCasters(array $casters) + { + $this->cloner->addCasters($casters); + } + + /** + * Present a reference to the value. + * + * @param mixed $value + * + * @return string + */ + public function presentRef($value): string + { + return $this->present($value, 0); + } + + /** + * Present a full representation of the value. + * + * If $depth is 0, the value will be presented as a ref instead. + * + * @param mixed $value + * @param int $depth (default: null) + * @param int $options One of Presenter constants + * + * @return string + */ + public function present($value, int $depth = null, int $options = 0): string + { + $data = $this->cloner->cloneVar($value, !($options & self::VERBOSE) ? Caster::EXCLUDE_VERBOSE : 0); + + if (null !== $depth) { + $data = $data->withMaxDepth($depth); + } + + // Work around https://github.com/symfony/symfony/issues/23572 + $oldLocale = \setlocale(\LC_NUMERIC, 0); + \setlocale(\LC_NUMERIC, 'C'); + + $output = ''; + $this->dumper->dump($data, function ($line, $depth) use (&$output) { + if ($depth >= 0) { + if ('' !== $output) { + $output .= \PHP_EOL; + } + $output .= \str_repeat(' ', $depth).$line; + } + }); + + // Now put the locale back + \setlocale(\LC_NUMERIC, $oldLocale); + + return OutputFormatter::escape($output); + } +} diff --git a/vendor/psy/psysh/src/VarDumper/PresenterAware.php b/vendor/psy/psysh/src/VarDumper/PresenterAware.php new file mode 100644 index 0000000000..9375d67da0 --- /dev/null +++ b/vendor/psy/psysh/src/VarDumper/PresenterAware.php @@ -0,0 +1,26 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\VarDumper; + +/** + * Presenter injects itself as a dependency to all objects which + * implement PresenterAware. + */ +interface PresenterAware +{ + /** + * Set a reference to the Presenter. + * + * @param Presenter $presenter + */ + public function setPresenter(Presenter $presenter); +} diff --git a/vendor/psy/psysh/src/VersionUpdater/Checker.php b/vendor/psy/psysh/src/VersionUpdater/Checker.php new file mode 100644 index 0000000000..8e6f845e4b --- /dev/null +++ b/vendor/psy/psysh/src/VersionUpdater/Checker.php @@ -0,0 +1,31 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\VersionUpdater; + +interface Checker +{ + const ALWAYS = 'always'; + const DAILY = 'daily'; + const WEEKLY = 'weekly'; + const MONTHLY = 'monthly'; + const NEVER = 'never'; + + /** + * @return bool + */ + public function isLatest(): bool; + + /** + * @return string + */ + public function getLatest(): string; +} diff --git a/vendor/psy/psysh/src/VersionUpdater/GitHubChecker.php b/vendor/psy/psysh/src/VersionUpdater/GitHubChecker.php new file mode 100644 index 0000000000..726a3382b0 --- /dev/null +++ b/vendor/psy/psysh/src/VersionUpdater/GitHubChecker.php @@ -0,0 +1,93 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\VersionUpdater; + +use Psy\Shell; + +class GitHubChecker implements Checker +{ + const URL = 'https://api.github.com/repos/bobthecow/psysh/releases/latest'; + + private $latest; + + /** + * @return bool + */ + public function isLatest(): bool + { + // version_compare doesn't handle semver completely; + // strip pre-release and build metadata before comparing + $version = \preg_replace('/[+-]\w+/', '', Shell::VERSION); + + return \version_compare($version, $this->getLatest(), '>='); + } + + /** + * @return string + */ + public function getLatest(): string + { + if (!isset($this->latest)) { + $this->setLatest($this->getVersionFromTag()); + } + + return $this->latest; + } + + /** + * @param string $version + */ + public function setLatest(string $version) + { + $this->latest = $version; + } + + /** + * @return string|null + */ + private function getVersionFromTag() + { + $contents = $this->fetchLatestRelease(); + if (!$contents || !isset($contents->tag_name)) { + throw new \InvalidArgumentException('Unable to check for updates'); + } + $this->setLatest($contents->tag_name); + + return $this->getLatest(); + } + + /** + * Set to public to make testing easier. + * + * @return mixed + */ + public function fetchLatestRelease() + { + $context = \stream_context_create([ + 'http' => [ + 'user_agent' => 'PsySH/'.Shell::VERSION, + 'timeout' => 1.0, + ], + ]); + + \set_error_handler(function () { + // Just ignore all errors with this. The checker will throw an exception + // if it doesn't work :) + }); + + $result = @\file_get_contents(self::URL, false, $context); + + \restore_error_handler(); + + return \json_decode($result); + } +} diff --git a/vendor/psy/psysh/src/VersionUpdater/IntervalChecker.php b/vendor/psy/psysh/src/VersionUpdater/IntervalChecker.php new file mode 100644 index 0000000000..0e40b74b57 --- /dev/null +++ b/vendor/psy/psysh/src/VersionUpdater/IntervalChecker.php @@ -0,0 +1,67 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\VersionUpdater; + +class IntervalChecker extends GitHubChecker +{ + private $cacheFile; + private $interval; + + public function __construct($cacheFile, $interval) + { + $this->cacheFile = $cacheFile; + $this->interval = $interval; + } + + public function fetchLatestRelease() + { + // Read the cached file + $cached = \json_decode(@\file_get_contents($this->cacheFile, false)); + if ($cached && isset($cached->last_check) && isset($cached->release)) { + $now = new \DateTime(); + $lastCheck = new \DateTime($cached->last_check); + if ($lastCheck >= $now->sub($this->getDateInterval())) { + return $cached->release; + } + } + + // Fall back to fetching from GitHub + $release = parent::fetchLatestRelease(); + if ($release && isset($release->tag_name)) { + $this->updateCache($release); + } + + return $release; + } + + private function getDateInterval(): \DateInterval + { + switch ($this->interval) { + case Checker::DAILY: + return new \DateInterval('P1D'); + case Checker::WEEKLY: + return new \DateInterval('P1W'); + case Checker::MONTHLY: + return new \DateInterval('P1M'); + } + } + + private function updateCache($release) + { + $data = [ + 'last_check' => \date(\DATE_ATOM), + 'release' => $release, + ]; + + \file_put_contents($this->cacheFile, \json_encode($data)); + } +} diff --git a/vendor/psy/psysh/src/VersionUpdater/NoopChecker.php b/vendor/psy/psysh/src/VersionUpdater/NoopChecker.php new file mode 100644 index 0000000000..40d0fb223b --- /dev/null +++ b/vendor/psy/psysh/src/VersionUpdater/NoopChecker.php @@ -0,0 +1,36 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\VersionUpdater; + +use Psy\Shell; + +/** + * A version checker stub which always thinks the current verion is up to date. + */ +class NoopChecker implements Checker +{ + /** + * @return bool + */ + public function isLatest(): bool + { + return true; + } + + /** + * @return string + */ + public function getLatest(): string + { + return Shell::VERSION; + } +} diff --git a/vendor/psy/psysh/src/functions.php b/vendor/psy/psysh/src/functions.php new file mode 100644 index 0000000000..439a720b61 --- /dev/null +++ b/vendor/psy/psysh/src/functions.php @@ -0,0 +1,440 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2022 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy; + +use Psy\ExecutionLoop\ProcessForker; +use Psy\VersionUpdater\GitHubChecker; +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; + +if (!\function_exists('Psy\\sh')) { + /** + * Command to return the eval-able code to startup PsySH. + * + * eval(\Psy\sh()); + * + * @return string + */ + function sh(): string + { + if (\version_compare(\PHP_VERSION, '8.0', '<')) { + return '\extract(\Psy\debug(\get_defined_vars(), isset($this) ? $this : @\get_called_class()));'; + } + + return <<<'EOS' +if (isset($this)) { + \extract(\Psy\debug(\get_defined_vars(), $this)); +} else { + try { + static::class; + \extract(\Psy\debug(\get_defined_vars(), static::class)); + } catch (\Error $e) { + \extract(\Psy\debug(\get_defined_vars())); + } +} +EOS; + } +} + +if (!\function_exists('Psy\\debug')) { + /** + * Invoke a Psy Shell from the current context. + * + * For example: + * + * foreach ($items as $item) { + * \Psy\debug(get_defined_vars()); + * } + * + * If you would like your shell interaction to affect the state of the + * current context, you can extract() the values returned from this call: + * + * foreach ($items as $item) { + * extract(\Psy\debug(get_defined_vars())); + * var_dump($item); // will be whatever you set $item to in Psy Shell + * } + * + * Optionally, supply an object as the `$bindTo` parameter. This determines + * the value `$this` will have in the shell, and sets up class scope so that + * private and protected members are accessible: + * + * class Foo { + * function bar() { + * \Psy\debug(get_defined_vars(), $this); + * } + * } + * + * For the static equivalent, pass a class name as the `$bindTo` parameter. + * This makes `self` work in the shell, and sets up static scope so that + * private and protected static members are accessible: + * + * class Foo { + * static function bar() { + * \Psy\debug(get_defined_vars(), get_called_class()); + * } + * } + * + * @param array $vars Scope variables from the calling context (default: []) + * @param object|string $bindTo Bound object ($this) or class (self) value for the shell + * + * @return array Scope variables from the debugger session + */ + function debug(array $vars = [], $bindTo = null): array + { + echo \PHP_EOL; + + $sh = new Shell(); + $sh->setScopeVariables($vars); + + // Show a couple of lines of call context for the debug session. + // + // @todo come up with a better way of doing this which doesn't involve injecting input :-P + if ($sh->has('whereami')) { + $sh->addInput('whereami -n2', true); + } + + if (\is_string($bindTo)) { + $sh->setBoundClass($bindTo); + } elseif ($bindTo !== null) { + $sh->setBoundObject($bindTo); + } + + $sh->run(); + + return $sh->getScopeVariables(false); + } +} + +if (!\function_exists('Psy\\info')) { + /** + * Get a bunch of debugging info about the current PsySH environment and + * configuration. + * + * If a Configuration param is passed, that configuration is stored and + * used for the current shell session, and no debugging info is returned. + * + * @param Configuration|null $config + * + * @return array|null + */ + function info(Configuration $config = null) + { + static $lastConfig; + if ($config !== null) { + $lastConfig = $config; + + return; + } + + $prettyPath = function ($path) { + return $path; + }; + + $homeDir = (new ConfigPaths())->homeDir(); + if ($homeDir && $homeDir = \rtrim($homeDir, '/')) { + $homePattern = '#^'.\preg_quote($homeDir, '#').'/#'; + $prettyPath = function ($path) use ($homePattern) { + if (\is_string($path)) { + return \preg_replace($homePattern, '~/', $path); + } else { + return $path; + } + }; + } + + $config = $lastConfig ?: new Configuration(); + $configEnv = (isset($_SERVER['PSYSH_CONFIG']) && $_SERVER['PSYSH_CONFIG']) ? $_SERVER['PSYSH_CONFIG'] : false; + + $shellInfo = [ + 'PsySH version' => Shell::VERSION, + ]; + + $core = [ + 'PHP version' => \PHP_VERSION, + 'OS' => \PHP_OS, + 'default includes' => $config->getDefaultIncludes(), + 'require semicolons' => $config->requireSemicolons(), + 'error logging level' => $config->errorLoggingLevel(), + 'config file' => [ + 'default config file' => $prettyPath($config->getConfigFile()), + 'local config file' => $prettyPath($config->getLocalConfigFile()), + 'PSYSH_CONFIG env' => $prettyPath($configEnv), + ], + // 'config dir' => $config->getConfigDir(), + // 'data dir' => $config->getDataDir(), + // 'runtime dir' => $config->getRuntimeDir(), + ]; + + // Use an explicit, fresh update check here, rather than relying on whatever is in $config. + $checker = new GitHubChecker(); + $updateAvailable = null; + $latest = null; + try { + $updateAvailable = !$checker->isLatest(); + $latest = $checker->getLatest(); + } catch (\Throwable $e) { + } + + $updates = [ + 'update available' => $updateAvailable, + 'latest release version' => $latest, + 'update check interval' => $config->getUpdateCheck(), + 'update cache file' => $prettyPath($config->getUpdateCheckCacheFile()), + ]; + + $input = [ + 'interactive mode' => $config->interactiveMode(), + 'input interactive' => $config->getInputInteractive(), + 'yolo' => $config->yolo(), + ]; + + if ($config->hasReadline()) { + $info = \readline_info(); + + $readline = [ + 'readline available' => true, + 'readline enabled' => $config->useReadline(), + 'readline service' => \get_class($config->getReadline()), + ]; + + if (isset($info['library_version'])) { + $readline['readline library'] = $info['library_version']; + } + + if (isset($info['readline_name']) && $info['readline_name'] !== '') { + $readline['readline name'] = $info['readline_name']; + } + } else { + $readline = [ + 'readline available' => false, + ]; + } + + $output = [ + 'color mode' => $config->colorMode(), + 'output decorated' => $config->getOutputDecorated(), + 'output verbosity' => $config->verbosity(), + 'output pager' => $config->getPager(), + ]; + + $pcntl = [ + 'pcntl available' => ProcessForker::isPcntlSupported(), + 'posix available' => ProcessForker::isPosixSupported(), + ]; + + if ($disabledPcntl = ProcessForker::disabledPcntlFunctions()) { + $pcntl['disabled pcntl functions'] = $disabledPcntl; + } + + if ($disabledPosix = ProcessForker::disabledPosixFunctions()) { + $pcntl['disabled posix functions'] = $disabledPosix; + } + + $pcntl['use pcntl'] = $config->usePcntl(); + + $history = [ + 'history file' => $prettyPath($config->getHistoryFile()), + 'history size' => $config->getHistorySize(), + 'erase duplicates' => $config->getEraseDuplicates(), + ]; + + $docs = [ + 'manual db file' => $prettyPath($config->getManualDbFile()), + 'sqlite available' => true, + ]; + + try { + if ($db = $config->getManualDb()) { + if ($q = $db->query('SELECT * FROM meta;')) { + $q->setFetchMode(\PDO::FETCH_KEY_PAIR); + $meta = $q->fetchAll(); + + foreach ($meta as $key => $val) { + switch ($key) { + case 'built_at': + $d = new \DateTime('@'.$val); + $val = $d->format(\DateTime::RFC2822); + break; + } + $key = 'db '.\str_replace('_', ' ', $key); + $docs[$key] = $val; + } + } else { + $docs['db schema'] = '0.1.0'; + } + } + } catch (Exception\RuntimeException $e) { + if ($e->getMessage() === 'SQLite PDO driver not found') { + $docs['sqlite available'] = false; + } else { + throw $e; + } + } + + $autocomplete = [ + 'tab completion enabled' => $config->useTabCompletion(), + 'bracketed paste' => $config->useBracketedPaste(), + ]; + + // Shenanigans, but totally justified. + try { + if ($shell = Sudo::fetchProperty($config, 'shell')) { + $shellClass = \get_class($shell); + if ($shellClass !== 'Psy\\Shell') { + $shellInfo = [ + 'PsySH version' => $shell::VERSION, + 'Shell class' => $shellClass, + ]; + } + + try { + $core['loop listeners'] = \array_map('get_class', Sudo::fetchProperty($shell, 'loopListeners')); + } catch (\ReflectionException $e) { + // shrug + } + + $core['commands'] = \array_map('get_class', $shell->all()); + + try { + $autocomplete['custom matchers'] = \array_map('get_class', Sudo::fetchProperty($shell, 'matchers')); + } catch (\ReflectionException $e) { + // shrug + } + } + } catch (\ReflectionException $e) { + // shrug + } + + // @todo Show Presenter / custom casters. + + return \array_merge($shellInfo, $core, \compact('updates', 'pcntl', 'input', 'readline', 'output', 'history', 'docs', 'autocomplete')); + } +} + +if (!\function_exists('Psy\\bin')) { + /** + * `psysh` command line executable. + * + * @return \Closure + */ + function bin(): \Closure + { + return function () { + if (!isset($_SERVER['PSYSH_IGNORE_ENV']) || !$_SERVER['PSYSH_IGNORE_ENV']) { + if (\defined('HHVM_VERSION_ID')) { + \fwrite(\STDERR, 'PsySH v0.11 and higher does not support HHVM. Install an older version, or set the environment variable PSYSH_IGNORE_ENV=1 to override this restriction and proceed anyway.'.\PHP_EOL); + exit(1); + } + + if (\PHP_VERSION_ID < 70000) { + \fwrite(\STDERR, 'PHP 7.0.0 or higher is required. You can set the environment variable PSYSH_IGNORE_ENV=1 to override this restriction and proceed anyway.'.\PHP_EOL); + exit(1); + } + + if (\PHP_VERSION_ID > 89999) { + \fwrite(\STDERR, 'PHP 9 or higher is not supported. You can set the environment variable PSYSH_IGNORE_ENV=1 to override this restriction and proceed anyway.'.\PHP_EOL); + exit(1); + } + + if (!\function_exists('json_encode')) { + \fwrite(\STDERR, 'The JSON extension is required. Please install it. You can set the environment variable PSYSH_IGNORE_ENV=1 to override this restriction and proceed anyway.'.\PHP_EOL); + exit(1); + } + + if (!\function_exists('token_get_all')) { + \fwrite(\STDERR, 'The Tokenizer extension is required. Please install it. You can set the environment variable PSYSH_IGNORE_ENV=1 to override this restriction and proceed anyway.'.\PHP_EOL); + exit(1); + } + } + + $usageException = null; + + $input = new ArgvInput(); + try { + $input->bind(new InputDefinition(\array_merge(Configuration::getInputOptions(), [ + new InputOption('help', 'h', InputOption::VALUE_NONE), + new InputOption('version', 'V', InputOption::VALUE_NONE), + + new InputArgument('include', InputArgument::IS_ARRAY), + ]))); + } catch (\RuntimeException $e) { + $usageException = $e; + } + + try { + $config = Configuration::fromInput($input); + } catch (\InvalidArgumentException $e) { + $usageException = $e; + } + + // Handle --help + if ($usageException !== null || $input->getOption('help')) { + if ($usageException !== null) { + echo $usageException->getMessage().\PHP_EOL.\PHP_EOL; + } + + $version = Shell::getVersionHeader(false); + $argv = isset($_SERVER['argv']) ? $_SERVER['argv'] : []; + $name = $argv ? \basename(\reset($argv)) : 'psysh'; + + echo <<<EOL +$version + +Usage: + $name [--version] [--help] [files...] + +Options: + -h, --help Display this help message. + -c, --config FILE Use an alternate PsySH config file location. + --cwd PATH Use an alternate working directory. + -V, --version Display the PsySH version. + --color Force colors in output. + --no-color Disable colors in output. + -i, --interactive Force PsySH to run in interactive mode. + -n, --no-interactive Run PsySH without interactive input. Requires input from stdin. + -r, --raw-output Print var_export-style return values (for non-interactive input) + -q, --quiet Shhhhhh. + -v|vv|vvv, --verbose Increase the verbosity of messages. + --yolo Run PsySH without input validation. You don't want this. + +EOL; + exit($usageException === null ? 0 : 1); + } + + // Handle --version + if ($input->getOption('version')) { + echo Shell::getVersionHeader($config->useUnicode()).\PHP_EOL; + exit(0); + } + + $shell = new Shell($config); + + // Pass additional arguments to Shell as 'includes' + $shell->setIncludes($input->getArgument('include')); + + try { + // And go! + $shell->run(); + } catch (\Throwable $e) { + \fwrite(\STDERR, $e->getMessage().\PHP_EOL); + + // @todo this triggers the "exited unexpectedly" logic in the + // ForkingLoop, so we can't exit(1) after starting the shell... + // fix this :) + + // exit(1); + } + }; + } +} diff --git a/vendor/symfony/console/Application.php b/vendor/symfony/console/Application.php new file mode 100644 index 0000000000..1021a900f0 --- /dev/null +++ b/vendor/symfony/console/Application.php @@ -0,0 +1,1278 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Command\HelpCommand; +use Symfony\Component\Console\Command\ListCommand; +use Symfony\Component\Console\CommandLoader\CommandLoaderInterface; +use Symfony\Component\Console\Event\ConsoleCommandEvent; +use Symfony\Component\Console\Event\ConsoleErrorEvent; +use Symfony\Component\Console\Event\ConsoleTerminateEvent; +use Symfony\Component\Console\Exception\CommandNotFoundException; +use Symfony\Component\Console\Exception\ExceptionInterface; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Exception\NamespaceNotFoundException; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Helper\DebugFormatterHelper; +use Symfony\Component\Console\Helper\FormatterHelper; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\ProcessHelper; +use Symfony\Component\Console\Helper\QuestionHelper; +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputAwareInterface; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Debug\ErrorHandler as LegacyErrorHandler; +use Symfony\Component\Debug\Exception\FatalThrowableError; +use Symfony\Component\ErrorHandler\ErrorHandler; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy; +use Symfony\Contracts\Service\ResetInterface; + +/** + * An Application is the container for a collection of commands. + * + * It is the main entry point of a Console application. + * + * This class is optimized for a standard CLI environment. + * + * Usage: + * + * $app = new Application('myapp', '1.0 (stable)'); + * $app->add(new SimpleCommand()); + * $app->run(); + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class Application implements ResetInterface +{ + private $commands = []; + private $wantHelps = false; + private $runningCommand; + private $name; + private $version; + private $commandLoader; + private $catchExceptions = true; + private $autoExit = true; + private $definition; + private $helperSet; + private $dispatcher; + private $terminal; + private $defaultCommand; + private $singleCommand = false; + private $initialized; + + /** + * @param string $name The name of the application + * @param string $version The version of the application + */ + public function __construct(string $name = 'UNKNOWN', string $version = 'UNKNOWN') + { + $this->name = $name; + $this->version = $version; + $this->terminal = new Terminal(); + $this->defaultCommand = 'list'; + } + + /** + * @final since Symfony 4.3, the type-hint will be updated to the interface from symfony/contracts in 5.0 + */ + public function setDispatcher(EventDispatcherInterface $dispatcher) + { + $this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher); + } + + public function setCommandLoader(CommandLoaderInterface $commandLoader) + { + $this->commandLoader = $commandLoader; + } + + /** + * Runs the current application. + * + * @return int 0 if everything went fine, or an error code + * + * @throws \Exception When running fails. Bypass this when {@link setCatchExceptions()}. + */ + public function run(InputInterface $input = null, OutputInterface $output = null) + { + if (\function_exists('putenv')) { + @putenv('LINES='.$this->terminal->getHeight()); + @putenv('COLUMNS='.$this->terminal->getWidth()); + } + + if (null === $input) { + $input = new ArgvInput(); + } + + if (null === $output) { + $output = new ConsoleOutput(); + } + + $renderException = function (\Throwable $e) use ($output) { + if ($output instanceof ConsoleOutputInterface) { + $this->renderThrowable($e, $output->getErrorOutput()); + } else { + $this->renderThrowable($e, $output); + } + }; + if ($phpHandler = set_exception_handler($renderException)) { + restore_exception_handler(); + if (!\is_array($phpHandler) || (!$phpHandler[0] instanceof ErrorHandler && !$phpHandler[0] instanceof LegacyErrorHandler)) { + $errorHandler = true; + } elseif ($errorHandler = $phpHandler[0]->setExceptionHandler($renderException)) { + $phpHandler[0]->setExceptionHandler($errorHandler); + } + } + + $this->configureIO($input, $output); + + try { + $exitCode = $this->doRun($input, $output); + } catch (\Exception $e) { + if (!$this->catchExceptions) { + throw $e; + } + + $renderException($e); + + $exitCode = $e->getCode(); + if (is_numeric($exitCode)) { + $exitCode = (int) $exitCode; + if ($exitCode <= 0) { + $exitCode = 1; + } + } else { + $exitCode = 1; + } + } finally { + // if the exception handler changed, keep it + // otherwise, unregister $renderException + if (!$phpHandler) { + if (set_exception_handler($renderException) === $renderException) { + restore_exception_handler(); + } + restore_exception_handler(); + } elseif (!$errorHandler) { + $finalHandler = $phpHandler[0]->setExceptionHandler(null); + if ($finalHandler !== $renderException) { + $phpHandler[0]->setExceptionHandler($finalHandler); + } + } + } + + if ($this->autoExit) { + if ($exitCode > 255) { + $exitCode = 255; + } + + exit($exitCode); + } + + return $exitCode; + } + + /** + * Runs the current application. + * + * @return int 0 if everything went fine, or an error code + */ + public function doRun(InputInterface $input, OutputInterface $output) + { + if (true === $input->hasParameterOption(['--version', '-V'], true)) { + $output->writeln($this->getLongVersion()); + + return 0; + } + + try { + // Makes ArgvInput::getFirstArgument() able to distinguish an option from an argument. + $input->bind($this->getDefinition()); + } catch (ExceptionInterface $e) { + // Errors must be ignored, full binding/validation happens later when the command is known. + } + + $name = $this->getCommandName($input); + if (true === $input->hasParameterOption(['--help', '-h'], true)) { + if (!$name) { + $name = 'help'; + $input = new ArrayInput(['command_name' => $this->defaultCommand]); + } else { + $this->wantHelps = true; + } + } + + if (!$name) { + $name = $this->defaultCommand; + $definition = $this->getDefinition(); + $definition->setArguments(array_merge( + $definition->getArguments(), + [ + 'command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name), + ] + )); + } + + try { + $this->runningCommand = null; + // the command name MUST be the first element of the input + $command = $this->find($name); + } catch (\Throwable $e) { + if (!($e instanceof CommandNotFoundException && !$e instanceof NamespaceNotFoundException) || 1 !== \count($alternatives = $e->getAlternatives()) || !$input->isInteractive()) { + if (null !== $this->dispatcher) { + $event = new ConsoleErrorEvent($input, $output, $e); + $this->dispatcher->dispatch($event, ConsoleEvents::ERROR); + + if (0 === $event->getExitCode()) { + return 0; + } + + $e = $event->getError(); + } + + throw $e; + } + + $alternative = $alternatives[0]; + + $style = new SymfonyStyle($input, $output); + $style->block(sprintf("\nCommand \"%s\" is not defined.\n", $name), null, 'error'); + if (!$style->confirm(sprintf('Do you want to run "%s" instead? ', $alternative), false)) { + if (null !== $this->dispatcher) { + $event = new ConsoleErrorEvent($input, $output, $e); + $this->dispatcher->dispatch($event, ConsoleEvents::ERROR); + + return $event->getExitCode(); + } + + return 1; + } + + $command = $this->find($alternative); + } + + $this->runningCommand = $command; + $exitCode = $this->doRunCommand($command, $input, $output); + $this->runningCommand = null; + + return $exitCode; + } + + /** + * {@inheritdoc} + */ + public function reset() + { + } + + public function setHelperSet(HelperSet $helperSet) + { + $this->helperSet = $helperSet; + } + + /** + * Get the helper set associated with the command. + * + * @return HelperSet The HelperSet instance associated with this command + */ + public function getHelperSet() + { + if (!$this->helperSet) { + $this->helperSet = $this->getDefaultHelperSet(); + } + + return $this->helperSet; + } + + public function setDefinition(InputDefinition $definition) + { + $this->definition = $definition; + } + + /** + * Gets the InputDefinition related to this Application. + * + * @return InputDefinition The InputDefinition instance + */ + public function getDefinition() + { + if (!$this->definition) { + $this->definition = $this->getDefaultInputDefinition(); + } + + if ($this->singleCommand) { + $inputDefinition = $this->definition; + $inputDefinition->setArguments(); + + return $inputDefinition; + } + + return $this->definition; + } + + /** + * Gets the help message. + * + * @return string A help message + */ + public function getHelp() + { + return $this->getLongVersion(); + } + + /** + * Gets whether to catch exceptions or not during commands execution. + * + * @return bool Whether to catch exceptions or not during commands execution + */ + public function areExceptionsCaught() + { + return $this->catchExceptions; + } + + /** + * Sets whether to catch exceptions or not during commands execution. + * + * @param bool $boolean Whether to catch exceptions or not during commands execution + */ + public function setCatchExceptions($boolean) + { + $this->catchExceptions = (bool) $boolean; + } + + /** + * Gets whether to automatically exit after a command execution or not. + * + * @return bool Whether to automatically exit after a command execution or not + */ + public function isAutoExitEnabled() + { + return $this->autoExit; + } + + /** + * Sets whether to automatically exit after a command execution or not. + * + * @param bool $boolean Whether to automatically exit after a command execution or not + */ + public function setAutoExit($boolean) + { + $this->autoExit = (bool) $boolean; + } + + /** + * Gets the name of the application. + * + * @return string The application name + */ + public function getName() + { + return $this->name; + } + + /** + * Sets the application name. + * + * @param string $name The application name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * Gets the application version. + * + * @return string The application version + */ + public function getVersion() + { + return $this->version; + } + + /** + * Sets the application version. + * + * @param string $version The application version + */ + public function setVersion($version) + { + $this->version = $version; + } + + /** + * Returns the long version of the application. + * + * @return string The long application version + */ + public function getLongVersion() + { + if ('UNKNOWN' !== $this->getName()) { + if ('UNKNOWN' !== $this->getVersion()) { + return sprintf('%s <info>%s</info>', $this->getName(), $this->getVersion()); + } + + return $this->getName(); + } + + return 'Console Tool'; + } + + /** + * Registers a new command. + * + * @param string $name The command name + * + * @return Command The newly created command + */ + public function register($name) + { + return $this->add(new Command($name)); + } + + /** + * Adds an array of command objects. + * + * If a Command is not enabled it will not be added. + * + * @param Command[] $commands An array of commands + */ + public function addCommands(array $commands) + { + foreach ($commands as $command) { + $this->add($command); + } + } + + /** + * Adds a command object. + * + * If a command with the same name already exists, it will be overridden. + * If the command is not enabled it will not be added. + * + * @return Command|null The registered command if enabled or null + */ + public function add(Command $command) + { + $this->init(); + + $command->setApplication($this); + + if (!$command->isEnabled()) { + $command->setApplication(null); + + return null; + } + + // Will throw if the command is not correctly initialized. + $command->getDefinition(); + + if (!$command->getName()) { + throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', \get_class($command))); + } + + $this->commands[$command->getName()] = $command; + + foreach ($command->getAliases() as $alias) { + $this->commands[$alias] = $command; + } + + return $command; + } + + /** + * Returns a registered command by name or alias. + * + * @param string $name The command name or alias + * + * @return Command A Command object + * + * @throws CommandNotFoundException When given command name does not exist + */ + public function get($name) + { + $this->init(); + + if (!$this->has($name)) { + throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name)); + } + + // When the command has a different name than the one used at the command loader level + if (!isset($this->commands[$name])) { + throw new CommandNotFoundException(sprintf('The "%s" command cannot be found because it is registered under multiple names. Make sure you don\'t set a different name via constructor or "setName()".', $name)); + } + + $command = $this->commands[$name]; + + if ($this->wantHelps) { + $this->wantHelps = false; + + $helpCommand = $this->get('help'); + $helpCommand->setCommand($command); + + return $helpCommand; + } + + return $command; + } + + /** + * Returns true if the command exists, false otherwise. + * + * @param string $name The command name or alias + * + * @return bool true if the command exists, false otherwise + */ + public function has($name) + { + $this->init(); + + return isset($this->commands[$name]) || ($this->commandLoader && $this->commandLoader->has($name) && $this->add($this->commandLoader->get($name))); + } + + /** + * Returns an array of all unique namespaces used by currently registered commands. + * + * It does not return the global namespace which always exists. + * + * @return string[] An array of namespaces + */ + public function getNamespaces() + { + $namespaces = []; + foreach ($this->all() as $command) { + if ($command->isHidden()) { + continue; + } + + $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName())); + + foreach ($command->getAliases() as $alias) { + $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias)); + } + } + + return array_values(array_unique(array_filter($namespaces))); + } + + /** + * Finds a registered namespace by a name or an abbreviation. + * + * @param string $namespace A namespace or abbreviation to search for + * + * @return string A registered namespace + * + * @throws NamespaceNotFoundException When namespace is incorrect or ambiguous + */ + public function findNamespace($namespace) + { + $allNamespaces = $this->getNamespaces(); + $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $namespace); + $namespaces = preg_grep('{^'.$expr.'}', $allNamespaces); + + if (empty($namespaces)) { + $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace); + + if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) { + if (1 == \count($alternatives)) { + $message .= "\n\nDid you mean this?\n "; + } else { + $message .= "\n\nDid you mean one of these?\n "; + } + + $message .= implode("\n ", $alternatives); + } + + throw new NamespaceNotFoundException($message, $alternatives); + } + + $exact = \in_array($namespace, $namespaces, true); + if (\count($namespaces) > 1 && !$exact) { + throw new NamespaceNotFoundException(sprintf("The namespace \"%s\" is ambiguous.\nDid you mean one of these?\n%s.", $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))), array_values($namespaces)); + } + + return $exact ? $namespace : reset($namespaces); + } + + /** + * Finds a command by name or alias. + * + * Contrary to get, this command tries to find the best + * match if you give it an abbreviation of a name or alias. + * + * @param string $name A command name or a command alias + * + * @return Command A Command instance + * + * @throws CommandNotFoundException When command name is incorrect or ambiguous + */ + public function find($name) + { + $this->init(); + + $aliases = []; + + foreach ($this->commands as $command) { + foreach ($command->getAliases() as $alias) { + if (!$this->has($alias)) { + $this->commands[$alias] = $command; + } + } + } + + if ($this->has($name)) { + return $this->get($name); + } + + $allCommands = $this->commandLoader ? array_merge($this->commandLoader->getNames(), array_keys($this->commands)) : array_keys($this->commands); + $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $name); + $commands = preg_grep('{^'.$expr.'}', $allCommands); + + if (empty($commands)) { + $commands = preg_grep('{^'.$expr.'}i', $allCommands); + } + + // if no commands matched or we just matched namespaces + if (empty($commands) || \count(preg_grep('{^'.$expr.'$}i', $commands)) < 1) { + if (false !== $pos = strrpos($name, ':')) { + // check if a namespace exists and contains commands + $this->findNamespace(substr($name, 0, $pos)); + } + + $message = sprintf('Command "%s" is not defined.', $name); + + if ($alternatives = $this->findAlternatives($name, $allCommands)) { + // remove hidden commands + $alternatives = array_filter($alternatives, function ($name) { + return !$this->get($name)->isHidden(); + }); + + if (1 == \count($alternatives)) { + $message .= "\n\nDid you mean this?\n "; + } else { + $message .= "\n\nDid you mean one of these?\n "; + } + $message .= implode("\n ", $alternatives); + } + + throw new CommandNotFoundException($message, array_values($alternatives)); + } + + // filter out aliases for commands which are already on the list + if (\count($commands) > 1) { + $commandList = $this->commandLoader ? array_merge(array_flip($this->commandLoader->getNames()), $this->commands) : $this->commands; + $commands = array_unique(array_filter($commands, function ($nameOrAlias) use (&$commandList, $commands, &$aliases) { + if (!$commandList[$nameOrAlias] instanceof Command) { + $commandList[$nameOrAlias] = $this->commandLoader->get($nameOrAlias); + } + + $commandName = $commandList[$nameOrAlias]->getName(); + + $aliases[$nameOrAlias] = $commandName; + + return $commandName === $nameOrAlias || !\in_array($commandName, $commands); + })); + } + + if (\count($commands) > 1) { + $usableWidth = $this->terminal->getWidth() - 10; + $abbrevs = array_values($commands); + $maxLen = 0; + foreach ($abbrevs as $abbrev) { + $maxLen = max(Helper::strlen($abbrev), $maxLen); + } + $abbrevs = array_map(function ($cmd) use ($commandList, $usableWidth, $maxLen, &$commands) { + if ($commandList[$cmd]->isHidden()) { + unset($commands[array_search($cmd, $commands)]); + + return false; + } + + $abbrev = str_pad($cmd, $maxLen, ' ').' '.$commandList[$cmd]->getDescription(); + + return Helper::strlen($abbrev) > $usableWidth ? Helper::substr($abbrev, 0, $usableWidth - 3).'...' : $abbrev; + }, array_values($commands)); + + if (\count($commands) > 1) { + $suggestions = $this->getAbbreviationSuggestions(array_filter($abbrevs)); + + throw new CommandNotFoundException(sprintf("Command \"%s\" is ambiguous.\nDid you mean one of these?\n%s.", $name, $suggestions), array_values($commands)); + } + } + + $command = $this->get(reset($commands)); + + if ($command->isHidden()) { + @trigger_error(sprintf('Command "%s" is hidden, finding it using an abbreviation is deprecated since Symfony 4.4, use its full name instead.', $command->getName()), \E_USER_DEPRECATED); + } + + return $command; + } + + /** + * Gets the commands (registered in the given namespace if provided). + * + * The array keys are the full names and the values the command instances. + * + * @param string $namespace A namespace name + * + * @return Command[] An array of Command instances + */ + public function all($namespace = null) + { + $this->init(); + + if (null === $namespace) { + if (!$this->commandLoader) { + return $this->commands; + } + + $commands = $this->commands; + foreach ($this->commandLoader->getNames() as $name) { + if (!isset($commands[$name]) && $this->has($name)) { + $commands[$name] = $this->get($name); + } + } + + return $commands; + } + + $commands = []; + foreach ($this->commands as $name => $command) { + if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) { + $commands[$name] = $command; + } + } + + if ($this->commandLoader) { + foreach ($this->commandLoader->getNames() as $name) { + if (!isset($commands[$name]) && $namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1) && $this->has($name)) { + $commands[$name] = $this->get($name); + } + } + } + + return $commands; + } + + /** + * Returns an array of possible abbreviations given a set of names. + * + * @param array $names An array of names + * + * @return array An array of abbreviations + */ + public static function getAbbreviations($names) + { + $abbrevs = []; + foreach ($names as $name) { + for ($len = \strlen($name); $len > 0; --$len) { + $abbrev = substr($name, 0, $len); + $abbrevs[$abbrev][] = $name; + } + } + + return $abbrevs; + } + + /** + * Renders a caught exception. + * + * @deprecated since Symfony 4.4, use "renderThrowable()" instead + */ + public function renderException(\Exception $e, OutputInterface $output) + { + @trigger_error(sprintf('The "%s::renderException()" method is deprecated since Symfony 4.4, use "renderThrowable()" instead.', __CLASS__), \E_USER_DEPRECATED); + + $output->writeln('', OutputInterface::VERBOSITY_QUIET); + + $this->doRenderException($e, $output); + + $this->finishRenderThrowableOrException($output); + } + + public function renderThrowable(\Throwable $e, OutputInterface $output): void + { + if (__CLASS__ !== static::class && __CLASS__ === (new \ReflectionMethod($this, 'renderThrowable'))->getDeclaringClass()->getName() && __CLASS__ !== (new \ReflectionMethod($this, 'renderException'))->getDeclaringClass()->getName()) { + @trigger_error(sprintf('The "%s::renderException()" method is deprecated since Symfony 4.4, use "renderThrowable()" instead.', __CLASS__), \E_USER_DEPRECATED); + + if (!$e instanceof \Exception) { + $e = class_exists(FatalThrowableError::class) ? new FatalThrowableError($e) : new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine()); + } + + $this->renderException($e, $output); + + return; + } + + $output->writeln('', OutputInterface::VERBOSITY_QUIET); + + $this->doRenderThrowable($e, $output); + + $this->finishRenderThrowableOrException($output); + } + + private function finishRenderThrowableOrException(OutputInterface $output): void + { + if (null !== $this->runningCommand) { + $output->writeln(sprintf('<info>%s</info>', OutputFormatter::escape(sprintf($this->runningCommand->getSynopsis(), $this->getName()))), OutputInterface::VERBOSITY_QUIET); + $output->writeln('', OutputInterface::VERBOSITY_QUIET); + } + } + + /** + * @deprecated since Symfony 4.4, use "doRenderThrowable()" instead + */ + protected function doRenderException(\Exception $e, OutputInterface $output) + { + @trigger_error(sprintf('The "%s::doRenderException()" method is deprecated since Symfony 4.4, use "doRenderThrowable()" instead.', __CLASS__), \E_USER_DEPRECATED); + + $this->doActuallyRenderThrowable($e, $output); + } + + protected function doRenderThrowable(\Throwable $e, OutputInterface $output): void + { + if (__CLASS__ !== static::class && __CLASS__ === (new \ReflectionMethod($this, 'doRenderThrowable'))->getDeclaringClass()->getName() && __CLASS__ !== (new \ReflectionMethod($this, 'doRenderException'))->getDeclaringClass()->getName()) { + @trigger_error(sprintf('The "%s::doRenderException()" method is deprecated since Symfony 4.4, use "doRenderThrowable()" instead.', __CLASS__), \E_USER_DEPRECATED); + + if (!$e instanceof \Exception) { + $e = class_exists(FatalThrowableError::class) ? new FatalThrowableError($e) : new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine()); + } + + $this->doRenderException($e, $output); + + return; + } + + $this->doActuallyRenderThrowable($e, $output); + } + + private function doActuallyRenderThrowable(\Throwable $e, OutputInterface $output): void + { + do { + $message = trim($e->getMessage()); + if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { + $class = get_debug_type($e); + $title = sprintf(' [%s%s] ', $class, 0 !== ($code = $e->getCode()) ? ' ('.$code.')' : ''); + $len = Helper::strlen($title); + } else { + $len = 0; + } + + if (str_contains($message, "@anonymous\0")) { + $message = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) { + return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0]; + }, $message); + } + + $width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : \PHP_INT_MAX; + $lines = []; + foreach ('' !== $message ? preg_split('/\r?\n/', $message) : [] as $line) { + foreach ($this->splitStringByWidth($line, $width - 4) as $line) { + // pre-format lines to get the right string length + $lineLength = Helper::strlen($line) + 4; + $lines[] = [$line, $lineLength]; + + $len = max($lineLength, $len); + } + } + + $messages = []; + if (!$e instanceof ExceptionInterface || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { + $messages[] = sprintf('<comment>%s</comment>', OutputFormatter::escape(sprintf('In %s line %s:', basename($e->getFile()) ?: 'n/a', $e->getLine() ?: 'n/a'))); + } + $messages[] = $emptyLine = sprintf('<error>%s</error>', str_repeat(' ', $len)); + if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { + $messages[] = sprintf('<error>%s%s</error>', $title, str_repeat(' ', max(0, $len - Helper::strlen($title)))); + } + foreach ($lines as $line) { + $messages[] = sprintf('<error> %s %s</error>', OutputFormatter::escape($line[0]), str_repeat(' ', $len - $line[1])); + } + $messages[] = $emptyLine; + $messages[] = ''; + + $output->writeln($messages, OutputInterface::VERBOSITY_QUIET); + + if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { + $output->writeln('<comment>Exception trace:</comment>', OutputInterface::VERBOSITY_QUIET); + + // exception related properties + $trace = $e->getTrace(); + + array_unshift($trace, [ + 'function' => '', + 'file' => $e->getFile() ?: 'n/a', + 'line' => $e->getLine() ?: 'n/a', + 'args' => [], + ]); + + for ($i = 0, $count = \count($trace); $i < $count; ++$i) { + $class = $trace[$i]['class'] ?? ''; + $type = $trace[$i]['type'] ?? ''; + $function = $trace[$i]['function'] ?? ''; + $file = $trace[$i]['file'] ?? 'n/a'; + $line = $trace[$i]['line'] ?? 'n/a'; + + $output->writeln(sprintf(' %s%s at <info>%s:%s</info>', $class, $function ? $type.$function.'()' : '', $file, $line), OutputInterface::VERBOSITY_QUIET); + } + + $output->writeln('', OutputInterface::VERBOSITY_QUIET); + } + } while ($e = $e->getPrevious()); + } + + /** + * Configures the input and output instances based on the user arguments and options. + */ + protected function configureIO(InputInterface $input, OutputInterface $output) + { + if (true === $input->hasParameterOption(['--ansi'], true)) { + $output->setDecorated(true); + } elseif (true === $input->hasParameterOption(['--no-ansi'], true)) { + $output->setDecorated(false); + } + + if (true === $input->hasParameterOption(['--no-interaction', '-n'], true)) { + $input->setInteractive(false); + } + + switch ($shellVerbosity = (int) getenv('SHELL_VERBOSITY')) { + case -1: $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); break; + case 1: $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); break; + case 2: $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); break; + case 3: $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); break; + default: $shellVerbosity = 0; break; + } + + if (true === $input->hasParameterOption(['--quiet', '-q'], true)) { + $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); + $shellVerbosity = -1; + } else { + if ($input->hasParameterOption('-vvv', true) || $input->hasParameterOption('--verbose=3', true) || 3 === $input->getParameterOption('--verbose', false, true)) { + $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); + $shellVerbosity = 3; + } elseif ($input->hasParameterOption('-vv', true) || $input->hasParameterOption('--verbose=2', true) || 2 === $input->getParameterOption('--verbose', false, true)) { + $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); + $shellVerbosity = 2; + } elseif ($input->hasParameterOption('-v', true) || $input->hasParameterOption('--verbose=1', true) || $input->hasParameterOption('--verbose', true) || $input->getParameterOption('--verbose', false, true)) { + $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); + $shellVerbosity = 1; + } + } + + if (-1 === $shellVerbosity) { + $input->setInteractive(false); + } + + if (\function_exists('putenv')) { + @putenv('SHELL_VERBOSITY='.$shellVerbosity); + } + $_ENV['SHELL_VERBOSITY'] = $shellVerbosity; + $_SERVER['SHELL_VERBOSITY'] = $shellVerbosity; + } + + /** + * Runs the current command. + * + * If an event dispatcher has been attached to the application, + * events are also dispatched during the life-cycle of the command. + * + * @return int 0 if everything went fine, or an error code + */ + protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output) + { + foreach ($command->getHelperSet() as $helper) { + if ($helper instanceof InputAwareInterface) { + $helper->setInput($input); + } + } + + if (null === $this->dispatcher) { + return $command->run($input, $output); + } + + // bind before the console.command event, so the listeners have access to input options/arguments + try { + $command->mergeApplicationDefinition(); + $input->bind($command->getDefinition()); + } catch (ExceptionInterface $e) { + // ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition + } + + $event = new ConsoleCommandEvent($command, $input, $output); + $e = null; + + try { + $this->dispatcher->dispatch($event, ConsoleEvents::COMMAND); + + if ($event->commandShouldRun()) { + $exitCode = $command->run($input, $output); + } else { + $exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED; + } + } catch (\Throwable $e) { + $event = new ConsoleErrorEvent($input, $output, $e, $command); + $this->dispatcher->dispatch($event, ConsoleEvents::ERROR); + $e = $event->getError(); + + if (0 === $exitCode = $event->getExitCode()) { + $e = null; + } + } + + $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode); + $this->dispatcher->dispatch($event, ConsoleEvents::TERMINATE); + + if (null !== $e) { + throw $e; + } + + return $event->getExitCode(); + } + + /** + * Gets the name of the command based on input. + * + * @return string|null + */ + protected function getCommandName(InputInterface $input) + { + return $this->singleCommand ? $this->defaultCommand : $input->getFirstArgument(); + } + + /** + * Gets the default input definition. + * + * @return InputDefinition An InputDefinition instance + */ + protected function getDefaultInputDefinition() + { + return new InputDefinition([ + new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'), + + new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'), + new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'), + new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'), + new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'), + new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'), + new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'), + new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'), + ]); + } + + /** + * Gets the default commands that should always be available. + * + * @return Command[] An array of default Command instances + */ + protected function getDefaultCommands() + { + return [new HelpCommand(), new ListCommand()]; + } + + /** + * Gets the default helper set with the helpers that should always be available. + * + * @return HelperSet A HelperSet instance + */ + protected function getDefaultHelperSet() + { + return new HelperSet([ + new FormatterHelper(), + new DebugFormatterHelper(), + new ProcessHelper(), + new QuestionHelper(), + ]); + } + + /** + * Returns abbreviated suggestions in string format. + */ + private function getAbbreviationSuggestions(array $abbrevs): string + { + return ' '.implode("\n ", $abbrevs); + } + + /** + * Returns the namespace part of the command name. + * + * This method is not part of public API and should not be used directly. + * + * @param string $name The full name of the command + * @param string $limit The maximum number of parts of the namespace + * + * @return string The namespace of the command + */ + public function extractNamespace($name, $limit = null) + { + $parts = explode(':', $name, -1); + + return implode(':', null === $limit ? $parts : \array_slice($parts, 0, $limit)); + } + + /** + * Finds alternative of $name among $collection, + * if nothing is found in $collection, try in $abbrevs. + * + * @return string[] A sorted array of similar string + */ + private function findAlternatives(string $name, iterable $collection): array + { + $threshold = 1e3; + $alternatives = []; + + $collectionParts = []; + foreach ($collection as $item) { + $collectionParts[$item] = explode(':', $item); + } + + foreach (explode(':', $name) as $i => $subname) { + foreach ($collectionParts as $collectionName => $parts) { + $exists = isset($alternatives[$collectionName]); + if (!isset($parts[$i]) && $exists) { + $alternatives[$collectionName] += $threshold; + continue; + } elseif (!isset($parts[$i])) { + continue; + } + + $lev = levenshtein($subname, $parts[$i]); + if ($lev <= \strlen($subname) / 3 || '' !== $subname && str_contains($parts[$i], $subname)) { + $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev; + } elseif ($exists) { + $alternatives[$collectionName] += $threshold; + } + } + } + + foreach ($collection as $item) { + $lev = levenshtein($name, $item); + if ($lev <= \strlen($name) / 3 || str_contains($item, $name)) { + $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev; + } + } + + $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; }); + ksort($alternatives, \SORT_NATURAL | \SORT_FLAG_CASE); + + return array_keys($alternatives); + } + + /** + * Sets the default Command name. + * + * @param string $commandName The Command name + * @param bool $isSingleCommand Set to true if there is only one command in this application + * + * @return self + */ + public function setDefaultCommand($commandName, $isSingleCommand = false) + { + $this->defaultCommand = $commandName; + + if ($isSingleCommand) { + // Ensure the command exist + $this->find($commandName); + + $this->singleCommand = true; + } + + return $this; + } + + /** + * @internal + */ + public function isSingleCommand(): bool + { + return $this->singleCommand; + } + + private function splitStringByWidth(string $string, int $width): array + { + // str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly. + // additionally, array_slice() is not enough as some character has doubled width. + // we need a function to split string not by character count but by string width + if (false === $encoding = mb_detect_encoding($string, null, true)) { + return str_split($string, $width); + } + + $utf8String = mb_convert_encoding($string, 'utf8', $encoding); + $lines = []; + $line = ''; + + $offset = 0; + while (preg_match('/.{1,10000}/u', $utf8String, $m, 0, $offset)) { + $offset += \strlen($m[0]); + + foreach (preg_split('//u', $m[0]) as $char) { + // test if $char could be appended to current line + if (mb_strwidth($line.$char, 'utf8') <= $width) { + $line .= $char; + continue; + } + // if not, push current line to array and make new line + $lines[] = str_pad($line, $width); + $line = $char; + } + } + + $lines[] = \count($lines) ? str_pad($line, $width) : $line; + + mb_convert_variables($encoding, 'utf8', $lines); + + return $lines; + } + + /** + * Returns all namespaces of the command name. + * + * @return string[] The namespaces of the command + */ + private function extractAllNamespaces(string $name): array + { + // -1 as third argument is needed to skip the command short name when exploding + $parts = explode(':', $name, -1); + $namespaces = []; + + foreach ($parts as $part) { + if (\count($namespaces)) { + $namespaces[] = end($namespaces).':'.$part; + } else { + $namespaces[] = $part; + } + } + + return $namespaces; + } + + private function init() + { + if ($this->initialized) { + return; + } + $this->initialized = true; + + foreach ($this->getDefaultCommands() as $command) { + $this->add($command); + } + } +} diff --git a/vendor/symfony/console/CHANGELOG.md b/vendor/symfony/console/CHANGELOG.md new file mode 100644 index 0000000000..5159244557 --- /dev/null +++ b/vendor/symfony/console/CHANGELOG.md @@ -0,0 +1,162 @@ +CHANGELOG +========= + +4.4.0 +----- + + * deprecated finding hidden commands using an abbreviation, use the full name instead + * added `Question::setTrimmable` default to true to allow the answer to be trimmed + * added method `minSecondsBetweenRedraws()` and `maxSecondsBetweenRedraws()` on `ProgressBar` + * `Application` implements `ResetInterface` + * marked all dispatched event classes as `@final` + * added support for displaying table horizontally + * deprecated returning `null` from `Command::execute()`, return `0` instead + * Deprecated the `Application::renderException()` and `Application::doRenderException()` methods, + use `renderThrowable()` and `doRenderThrowable()` instead. + * added support for the `NO_COLOR` env var (https://no-color.org/) + +4.3.0 +----- + + * added support for hyperlinks + * added `ProgressBar::iterate()` method that simplify updating the progress bar when iterating + * added `Question::setAutocompleterCallback()` to provide a callback function + that dynamically generates suggestions as the user types + +4.2.0 +----- + + * allowed passing commands as `[$process, 'ENV_VAR' => 'value']` to + `ProcessHelper::run()` to pass environment variables + * deprecated passing a command as a string to `ProcessHelper::run()`, + pass it the command as an array of its arguments instead + * made the `ProcessHelper` class final + * added `WrappableOutputFormatterInterface::formatAndWrap()` (implemented in `OutputFormatter`) + * added `capture_stderr_separately` option to `CommandTester::execute()` + +4.1.0 +----- + + * added option to run suggested command if command is not found and only 1 alternative is available + * added option to modify console output and print multiple modifiable sections + * added support for iterable messages in output `write` and `writeln` methods + +4.0.0 +----- + + * `OutputFormatter` throws an exception when unknown options are used + * removed `QuestionHelper::setInputStream()/getInputStream()` + * removed `Application::getTerminalWidth()/getTerminalHeight()` and + `Application::setTerminalDimensions()/getTerminalDimensions()` + * removed `ConsoleExceptionEvent` + * removed `ConsoleEvents::EXCEPTION` + +3.4.0 +----- + + * added `SHELL_VERBOSITY` env var to control verbosity + * added `CommandLoaderInterface`, `FactoryCommandLoader` and PSR-11 + `ContainerCommandLoader` for commands lazy-loading + * added a case-insensitive command name matching fallback + * added static `Command::$defaultName/getDefaultName()`, allowing for + commands to be registered at compile time in the application command loader. + Setting the `$defaultName` property avoids the need for filling the `command` + attribute on the `console.command` tag when using `AddConsoleCommandPass`. + +3.3.0 +----- + + * added `ExceptionListener` + * added `AddConsoleCommandPass` (originally in FrameworkBundle) + * [BC BREAK] `Input::getOption()` no longer returns the default value for options + with value optional explicitly passed empty + * added console.error event to catch exceptions thrown by other listeners + * deprecated console.exception event in favor of console.error + * added ability to handle `CommandNotFoundException` through the + `console.error` event + * deprecated default validation in `SymfonyQuestionHelper::ask` + +3.2.0 +------ + + * added `setInputs()` method to CommandTester for ease testing of commands expecting inputs + * added `setStream()` and `getStream()` methods to Input (implement StreamableInputInterface) + * added StreamableInputInterface + * added LockableTrait + +3.1.0 +----- + + * added truncate method to FormatterHelper + * added setColumnWidth(s) method to Table + +2.8.3 +----- + + * remove readline support from the question helper as it caused issues + +2.8.0 +----- + + * use readline for user input in the question helper when available to allow + the use of arrow keys + +2.6.0 +----- + + * added a Process helper + * added a DebugFormatter helper + +2.5.0 +----- + + * deprecated the dialog helper (use the question helper instead) + * deprecated TableHelper in favor of Table + * deprecated ProgressHelper in favor of ProgressBar + * added ConsoleLogger + * added a question helper + * added a way to set the process name of a command + * added a way to set a default command instead of `ListCommand` + +2.4.0 +----- + + * added a way to force terminal dimensions + * added a convenient method to detect verbosity level + * [BC BREAK] made descriptors use output instead of returning a string + +2.3.0 +----- + + * added multiselect support to the select dialog helper + * added Table Helper for tabular data rendering + * added support for events in `Application` + * added a way to normalize EOLs in `ApplicationTester::getDisplay()` and `CommandTester::getDisplay()` + * added a way to set the progress bar progress via the `setCurrent` method + * added support for multiple InputOption shortcuts, written as `'-a|-b|-c'` + * added two additional verbosity levels, VERBOSITY_VERY_VERBOSE and VERBOSITY_DEBUG + +2.2.0 +----- + + * added support for colorization on Windows via ConEmu + * add a method to Dialog Helper to ask for a question and hide the response + * added support for interactive selections in console (DialogHelper::select()) + * added support for autocompletion as you type in Dialog Helper + +2.1.0 +----- + + * added ConsoleOutputInterface + * added the possibility to disable a command (Command::isEnabled()) + * added suggestions when a command does not exist + * added a --raw option to the list command + * added support for STDERR in the console output class (errors are now sent + to STDERR) + * made the defaults (helper set, commands, input definition) in Application + more easily customizable + * added support for the shell even if readline is not available + * added support for process isolation in Symfony shell via + `--process-isolation` switch + * added support for `--`, which disables options parsing after that point + (tokens will be parsed as arguments) diff --git a/vendor/symfony/console/Command/Command.php b/vendor/symfony/console/Command/Command.php new file mode 100644 index 0000000000..b0c1bf864b --- /dev/null +++ b/vendor/symfony/console/Command/Command.php @@ -0,0 +1,667 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Exception\ExceptionInterface; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Base class for all commands. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class Command +{ + /** + * @var string|null The default command name + */ + protected static $defaultName; + + private $application; + private $name; + private $processTitle; + private $aliases = []; + private $definition; + private $hidden = false; + private $help = ''; + private $description = ''; + private $ignoreValidationErrors = false; + private $applicationDefinitionMerged = false; + private $applicationDefinitionMergedWithArgs = false; + private $code; + private $synopsis = []; + private $usages = []; + private $helperSet; + + /** + * @return string|null The default command name or null when no default name is set + */ + public static function getDefaultName() + { + $class = static::class; + $r = new \ReflectionProperty($class, 'defaultName'); + + return $class === $r->class ? static::$defaultName : null; + } + + /** + * @param string|null $name The name of the command; passing null means it must be set in configure() + * + * @throws LogicException When the command name is empty + */ + public function __construct(string $name = null) + { + $this->definition = new InputDefinition(); + + if (null !== $name || null !== $name = static::getDefaultName()) { + $this->setName($name); + } + + $this->configure(); + } + + /** + * Ignores validation errors. + * + * This is mainly useful for the help command. + */ + public function ignoreValidationErrors() + { + $this->ignoreValidationErrors = true; + } + + public function setApplication(Application $application = null) + { + $this->application = $application; + if ($application) { + $this->setHelperSet($application->getHelperSet()); + } else { + $this->helperSet = null; + } + } + + public function setHelperSet(HelperSet $helperSet) + { + $this->helperSet = $helperSet; + } + + /** + * Gets the helper set. + * + * @return HelperSet|null A HelperSet instance + */ + public function getHelperSet() + { + return $this->helperSet; + } + + /** + * Gets the application instance for this command. + * + * @return Application|null An Application instance + */ + public function getApplication() + { + return $this->application; + } + + /** + * Checks whether the command is enabled or not in the current environment. + * + * Override this to check for x or y and return false if the command can not + * run properly under the current conditions. + * + * @return bool + */ + public function isEnabled() + { + return true; + } + + /** + * Configures the current command. + */ + protected function configure() + { + } + + /** + * Executes the current command. + * + * This method is not abstract because you can use this class + * as a concrete class. In this case, instead of defining the + * execute() method, you set the code to execute by passing + * a Closure to the setCode() method. + * + * @return int 0 if everything went fine, or an exit code + * + * @throws LogicException When this abstract method is not implemented + * + * @see setCode() + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + throw new LogicException('You must override the execute() method in the concrete command class.'); + } + + /** + * Interacts with the user. + * + * This method is executed before the InputDefinition is validated. + * This means that this is the only place where the command can + * interactively ask for values of missing required arguments. + */ + protected function interact(InputInterface $input, OutputInterface $output) + { + } + + /** + * Initializes the command after the input has been bound and before the input + * is validated. + * + * This is mainly useful when a lot of commands extends one main command + * where some things need to be initialized based on the input arguments and options. + * + * @see InputInterface::bind() + * @see InputInterface::validate() + */ + protected function initialize(InputInterface $input, OutputInterface $output) + { + } + + /** + * Runs the command. + * + * The code to execute is either defined directly with the + * setCode() method or by overriding the execute() method + * in a sub-class. + * + * @return int The command exit code + * + * @throws ExceptionInterface When input binding fails. Bypass this by calling {@link ignoreValidationErrors()}. + * + * @see setCode() + * @see execute() + */ + public function run(InputInterface $input, OutputInterface $output) + { + // force the creation of the synopsis before the merge with the app definition + $this->getSynopsis(true); + $this->getSynopsis(false); + + // add the application arguments and options + $this->mergeApplicationDefinition(); + + // bind the input against the command specific arguments/options + try { + $input->bind($this->definition); + } catch (ExceptionInterface $e) { + if (!$this->ignoreValidationErrors) { + throw $e; + } + } + + $this->initialize($input, $output); + + if (null !== $this->processTitle) { + if (\function_exists('cli_set_process_title')) { + if (!@cli_set_process_title($this->processTitle)) { + if ('Darwin' === \PHP_OS) { + $output->writeln('<comment>Running "cli_set_process_title" as an unprivileged user is not supported on MacOS.</comment>', OutputInterface::VERBOSITY_VERY_VERBOSE); + } else { + cli_set_process_title($this->processTitle); + } + } + } elseif (\function_exists('setproctitle')) { + setproctitle($this->processTitle); + } elseif (OutputInterface::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) { + $output->writeln('<comment>Install the proctitle PECL to be able to change the process title.</comment>'); + } + } + + if ($input->isInteractive()) { + $this->interact($input, $output); + } + + // The command name argument is often omitted when a command is executed directly with its run() method. + // It would fail the validation if we didn't make sure the command argument is present, + // since it's required by the application. + if ($input->hasArgument('command') && null === $input->getArgument('command')) { + $input->setArgument('command', $this->getName()); + } + + $input->validate(); + + if ($this->code) { + $statusCode = ($this->code)($input, $output); + } else { + $statusCode = $this->execute($input, $output); + + if (!\is_int($statusCode)) { + @trigger_error(sprintf('Return value of "%s::execute()" should always be of the type int since Symfony 4.4, %s returned.', static::class, \gettype($statusCode)), \E_USER_DEPRECATED); + } + } + + return is_numeric($statusCode) ? (int) $statusCode : 0; + } + + /** + * Sets the code to execute when running this command. + * + * If this method is used, it overrides the code defined + * in the execute() method. + * + * @param callable $code A callable(InputInterface $input, OutputInterface $output) + * + * @return $this + * + * @throws InvalidArgumentException + * + * @see execute() + */ + public function setCode(callable $code) + { + if ($code instanceof \Closure) { + $r = new \ReflectionFunction($code); + if (null === $r->getClosureThis()) { + set_error_handler(static function () {}); + try { + if ($c = \Closure::bind($code, $this)) { + $code = $c; + } + } finally { + restore_error_handler(); + } + } + } + + $this->code = $code; + + return $this; + } + + /** + * Merges the application definition with the command definition. + * + * This method is not part of public API and should not be used directly. + * + * @param bool $mergeArgs Whether to merge or not the Application definition arguments to Command definition arguments + */ + public function mergeApplicationDefinition($mergeArgs = true) + { + if (null === $this->application || (true === $this->applicationDefinitionMerged && ($this->applicationDefinitionMergedWithArgs || !$mergeArgs))) { + return; + } + + $this->definition->addOptions($this->application->getDefinition()->getOptions()); + + $this->applicationDefinitionMerged = true; + + if ($mergeArgs) { + $currentArguments = $this->definition->getArguments(); + $this->definition->setArguments($this->application->getDefinition()->getArguments()); + $this->definition->addArguments($currentArguments); + + $this->applicationDefinitionMergedWithArgs = true; + } + } + + /** + * Sets an array of argument and option instances. + * + * @param array|InputDefinition $definition An array of argument and option instances or a definition instance + * + * @return $this + */ + public function setDefinition($definition) + { + if ($definition instanceof InputDefinition) { + $this->definition = $definition; + } else { + $this->definition->setDefinition($definition); + } + + $this->applicationDefinitionMerged = false; + + return $this; + } + + /** + * Gets the InputDefinition attached to this Command. + * + * @return InputDefinition An InputDefinition instance + */ + public function getDefinition() + { + if (null === $this->definition) { + throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', static::class)); + } + + return $this->definition; + } + + /** + * Gets the InputDefinition to be used to create representations of this Command. + * + * Can be overridden to provide the original command representation when it would otherwise + * be changed by merging with the application InputDefinition. + * + * This method is not part of public API and should not be used directly. + * + * @return InputDefinition An InputDefinition instance + */ + public function getNativeDefinition() + { + return $this->getDefinition(); + } + + /** + * Adds an argument. + * + * @param string $name The argument name + * @param int|null $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL + * @param string $description A description text + * @param mixed $default The default value (for InputArgument::OPTIONAL mode only) + * + * @throws InvalidArgumentException When argument mode is not valid + * + * @return $this + */ + public function addArgument($name, $mode = null, $description = '', $default = null) + { + $this->definition->addArgument(new InputArgument($name, $mode, $description, $default)); + + return $this; + } + + /** + * Adds an option. + * + * @param string $name The option name + * @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts + * @param int|null $mode The option mode: One of the InputOption::VALUE_* constants + * @param string $description A description text + * @param mixed $default The default value (must be null for InputOption::VALUE_NONE) + * + * @throws InvalidArgumentException If option mode is invalid or incompatible + * + * @return $this + */ + public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null) + { + $this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default)); + + return $this; + } + + /** + * Sets the name of the command. + * + * This method can set both the namespace and the name if + * you separate them by a colon (:) + * + * $command->setName('foo:bar'); + * + * @param string $name The command name + * + * @return $this + * + * @throws InvalidArgumentException When the name is invalid + */ + public function setName($name) + { + $this->validateName($name); + + $this->name = $name; + + return $this; + } + + /** + * Sets the process title of the command. + * + * This feature should be used only when creating a long process command, + * like a daemon. + * + * @param string $title The process title + * + * @return $this + */ + public function setProcessTitle($title) + { + $this->processTitle = $title; + + return $this; + } + + /** + * Returns the command name. + * + * @return string|null + */ + public function getName() + { + return $this->name; + } + + /** + * @param bool $hidden Whether or not the command should be hidden from the list of commands + * + * @return $this + */ + public function setHidden($hidden) + { + $this->hidden = (bool) $hidden; + + return $this; + } + + /** + * @return bool whether the command should be publicly shown or not + */ + public function isHidden() + { + return $this->hidden; + } + + /** + * Sets the description for the command. + * + * @param string $description The description for the command + * + * @return $this + */ + public function setDescription($description) + { + $this->description = $description; + + return $this; + } + + /** + * Returns the description for the command. + * + * @return string The description for the command + */ + public function getDescription() + { + return $this->description; + } + + /** + * Sets the help for the command. + * + * @param string $help The help for the command + * + * @return $this + */ + public function setHelp($help) + { + $this->help = $help; + + return $this; + } + + /** + * Returns the help for the command. + * + * @return string The help for the command + */ + public function getHelp() + { + return $this->help; + } + + /** + * Returns the processed help for the command replacing the %command.name% and + * %command.full_name% patterns with the real values dynamically. + * + * @return string The processed help for the command + */ + public function getProcessedHelp() + { + $name = $this->name; + $isSingleCommand = $this->application && $this->application->isSingleCommand(); + + $placeholders = [ + '%command.name%', + '%command.full_name%', + ]; + $replacements = [ + $name, + $isSingleCommand ? $_SERVER['PHP_SELF'] : $_SERVER['PHP_SELF'].' '.$name, + ]; + + return str_replace($placeholders, $replacements, $this->getHelp() ?: $this->getDescription()); + } + + /** + * Sets the aliases for the command. + * + * @param string[] $aliases An array of aliases for the command + * + * @return $this + * + * @throws InvalidArgumentException When an alias is invalid + */ + public function setAliases($aliases) + { + if (!\is_array($aliases) && !$aliases instanceof \Traversable) { + throw new InvalidArgumentException('$aliases must be an array or an instance of \Traversable.'); + } + + foreach ($aliases as $alias) { + $this->validateName($alias); + } + + $this->aliases = $aliases; + + return $this; + } + + /** + * Returns the aliases for the command. + * + * @return array An array of aliases for the command + */ + public function getAliases() + { + return $this->aliases; + } + + /** + * Returns the synopsis for the command. + * + * @param bool $short Whether to show the short version of the synopsis (with options folded) or not + * + * @return string The synopsis + */ + public function getSynopsis($short = false) + { + $key = $short ? 'short' : 'long'; + + if (!isset($this->synopsis[$key])) { + $this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short))); + } + + return $this->synopsis[$key]; + } + + /** + * Add a command usage example. + * + * @param string $usage The usage, it'll be prefixed with the command name + * + * @return $this + */ + public function addUsage($usage) + { + if (!str_starts_with($usage, $this->name)) { + $usage = sprintf('%s %s', $this->name, $usage); + } + + $this->usages[] = $usage; + + return $this; + } + + /** + * Returns alternative usages of the command. + * + * @return array + */ + public function getUsages() + { + return $this->usages; + } + + /** + * Gets a helper instance by name. + * + * @param string $name The helper name + * + * @return mixed The helper value + * + * @throws LogicException if no HelperSet is defined + * @throws InvalidArgumentException if the helper is not defined + */ + public function getHelper($name) + { + if (null === $this->helperSet) { + throw new LogicException(sprintf('Cannot retrieve helper "%s" because there is no HelperSet defined. Did you forget to add your command to the application or to set the application on the command using the setApplication() method? You can also set the HelperSet directly using the setHelperSet() method.', $name)); + } + + return $this->helperSet->get($name); + } + + /** + * Validates a command name. + * + * It must be non-empty and parts can optionally be separated by ":". + * + * @throws InvalidArgumentException When the name is invalid + */ + private function validateName(string $name) + { + if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) { + throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); + } + } +} diff --git a/vendor/symfony/console/Command/HelpCommand.php b/vendor/symfony/console/Command/HelpCommand.php new file mode 100644 index 0000000000..cece782999 --- /dev/null +++ b/vendor/symfony/console/Command/HelpCommand.php @@ -0,0 +1,83 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Helper\DescriptorHelper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * HelpCommand displays the help for a given command. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class HelpCommand extends Command +{ + private $command; + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->ignoreValidationErrors(); + + $this + ->setName('help') + ->setDefinition([ + new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'), + ]) + ->setDescription('Display help for a command') + ->setHelp(<<<'EOF' +The <info>%command.name%</info> command displays help for a given command: + + <info>php %command.full_name% list</info> + +You can also output the help in other formats by using the <comment>--format</comment> option: + + <info>php %command.full_name% --format=xml list</info> + +To display the list of available commands, please use the <info>list</info> command. +EOF + ) + ; + } + + public function setCommand(Command $command) + { + $this->command = $command; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + if (null === $this->command) { + $this->command = $this->getApplication()->find($input->getArgument('command_name')); + } + + $helper = new DescriptorHelper(); + $helper->describe($output, $this->command, [ + 'format' => $input->getOption('format'), + 'raw_text' => $input->getOption('raw'), + ]); + + $this->command = null; + + return 0; + } +} diff --git a/vendor/symfony/console/Command/ListCommand.php b/vendor/symfony/console/Command/ListCommand.php new file mode 100644 index 0000000000..44324a5e7f --- /dev/null +++ b/vendor/symfony/console/Command/ListCommand.php @@ -0,0 +1,89 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Helper\DescriptorHelper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * ListCommand displays the list of all available commands for the application. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class ListCommand extends Command +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('list') + ->setDefinition($this->createDefinition()) + ->setDescription('List commands') + ->setHelp(<<<'EOF' +The <info>%command.name%</info> command lists all commands: + + <info>php %command.full_name%</info> + +You can also display the commands for a specific namespace: + + <info>php %command.full_name% test</info> + +You can also output the information in other formats by using the <comment>--format</comment> option: + + <info>php %command.full_name% --format=xml</info> + +It's also possible to get raw list of commands (useful for embedding command runner): + + <info>php %command.full_name% --raw</info> +EOF + ) + ; + } + + /** + * {@inheritdoc} + */ + public function getNativeDefinition() + { + return $this->createDefinition(); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $helper = new DescriptorHelper(); + $helper->describe($output, $this->getApplication(), [ + 'format' => $input->getOption('format'), + 'raw_text' => $input->getOption('raw'), + 'namespace' => $input->getArgument('namespace'), + ]); + + return 0; + } + + private function createDefinition(): InputDefinition + { + return new InputDefinition([ + new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), + ]); + } +} diff --git a/vendor/symfony/console/Command/LockableTrait.php b/vendor/symfony/console/Command/LockableTrait.php new file mode 100644 index 0000000000..60cfe360f7 --- /dev/null +++ b/vendor/symfony/console/Command/LockableTrait.php @@ -0,0 +1,69 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Lock\Lock; +use Symfony\Component\Lock\LockFactory; +use Symfony\Component\Lock\Store\FlockStore; +use Symfony\Component\Lock\Store\SemaphoreStore; + +/** + * Basic lock feature for commands. + * + * @author Geoffrey Brier <geoffrey.brier@gmail.com> + */ +trait LockableTrait +{ + /** @var Lock */ + private $lock; + + /** + * Locks a command. + */ + private function lock(string $name = null, bool $blocking = false): bool + { + if (!class_exists(SemaphoreStore::class)) { + throw new LogicException('To enable the locking feature you must install the symfony/lock component.'); + } + + if (null !== $this->lock) { + throw new LogicException('A lock is already in place.'); + } + + if (SemaphoreStore::isSupported()) { + $store = new SemaphoreStore(); + } else { + $store = new FlockStore(); + } + + $this->lock = (new LockFactory($store))->createLock($name ?: $this->getName()); + if (!$this->lock->acquire($blocking)) { + $this->lock = null; + + return false; + } + + return true; + } + + /** + * Releases the command lock if there is one. + */ + private function release() + { + if ($this->lock) { + $this->lock->release(); + $this->lock = null; + } + } +} diff --git a/vendor/symfony/console/CommandLoader/CommandLoaderInterface.php b/vendor/symfony/console/CommandLoader/CommandLoaderInterface.php new file mode 100644 index 0000000000..ca1029cb60 --- /dev/null +++ b/vendor/symfony/console/CommandLoader/CommandLoaderInterface.php @@ -0,0 +1,46 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\CommandLoader; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\CommandNotFoundException; + +/** + * @author Robin Chalas <robin.chalas@gmail.com> + */ +interface CommandLoaderInterface +{ + /** + * Loads a command. + * + * @param string $name + * + * @return Command + * + * @throws CommandNotFoundException + */ + public function get($name); + + /** + * Checks if a command exists. + * + * @param string $name + * + * @return bool + */ + public function has($name); + + /** + * @return string[] All registered command names + */ + public function getNames(); +} diff --git a/vendor/symfony/console/CommandLoader/ContainerCommandLoader.php b/vendor/symfony/console/CommandLoader/ContainerCommandLoader.php new file mode 100644 index 0000000000..50e5950a4d --- /dev/null +++ b/vendor/symfony/console/CommandLoader/ContainerCommandLoader.php @@ -0,0 +1,63 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\CommandLoader; + +use Psr\Container\ContainerInterface; +use Symfony\Component\Console\Exception\CommandNotFoundException; + +/** + * Loads commands from a PSR-11 container. + * + * @author Robin Chalas <robin.chalas@gmail.com> + */ +class ContainerCommandLoader implements CommandLoaderInterface +{ + private $container; + private $commandMap; + + /** + * @param array $commandMap An array with command names as keys and service ids as values + */ + public function __construct(ContainerInterface $container, array $commandMap) + { + $this->container = $container; + $this->commandMap = $commandMap; + } + + /** + * {@inheritdoc} + */ + public function get($name) + { + if (!$this->has($name)) { + throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name)); + } + + return $this->container->get($this->commandMap[$name]); + } + + /** + * {@inheritdoc} + */ + public function has($name) + { + return isset($this->commandMap[$name]) && $this->container->has($this->commandMap[$name]); + } + + /** + * {@inheritdoc} + */ + public function getNames() + { + return array_keys($this->commandMap); + } +} diff --git a/vendor/symfony/console/CommandLoader/FactoryCommandLoader.php b/vendor/symfony/console/CommandLoader/FactoryCommandLoader.php new file mode 100644 index 0000000000..d9c2055710 --- /dev/null +++ b/vendor/symfony/console/CommandLoader/FactoryCommandLoader.php @@ -0,0 +1,62 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\CommandLoader; + +use Symfony\Component\Console\Exception\CommandNotFoundException; + +/** + * A simple command loader using factories to instantiate commands lazily. + * + * @author Maxime Steinhausser <maxime.steinhausser@gmail.com> + */ +class FactoryCommandLoader implements CommandLoaderInterface +{ + private $factories; + + /** + * @param callable[] $factories Indexed by command names + */ + public function __construct(array $factories) + { + $this->factories = $factories; + } + + /** + * {@inheritdoc} + */ + public function has($name) + { + return isset($this->factories[$name]); + } + + /** + * {@inheritdoc} + */ + public function get($name) + { + if (!isset($this->factories[$name])) { + throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name)); + } + + $factory = $this->factories[$name]; + + return $factory(); + } + + /** + * {@inheritdoc} + */ + public function getNames() + { + return array_keys($this->factories); + } +} diff --git a/vendor/symfony/console/ConsoleEvents.php b/vendor/symfony/console/ConsoleEvents.php new file mode 100644 index 0000000000..99b423c83c --- /dev/null +++ b/vendor/symfony/console/ConsoleEvents.php @@ -0,0 +1,47 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console; + +/** + * Contains all events dispatched by an Application. + * + * @author Francesco Levorato <git@flevour.net> + */ +final class ConsoleEvents +{ + /** + * The COMMAND event allows you to attach listeners before any command is + * executed by the console. It also allows you to modify the command, input and output + * before they are handed to the command. + * + * @Event("Symfony\Component\Console\Event\ConsoleCommandEvent") + */ + public const COMMAND = 'console.command'; + + /** + * The TERMINATE event allows you to attach listeners after a command is + * executed by the console. + * + * @Event("Symfony\Component\Console\Event\ConsoleTerminateEvent") + */ + public const TERMINATE = 'console.terminate'; + + /** + * The ERROR event occurs when an uncaught exception or error appears. + * + * This event allows you to deal with the exception/error or + * to modify the thrown exception. + * + * @Event("Symfony\Component\Console\Event\ConsoleErrorEvent") + */ + public const ERROR = 'console.error'; +} diff --git a/vendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php b/vendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php new file mode 100644 index 0000000000..d9449dc56a --- /dev/null +++ b/vendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php @@ -0,0 +1,98 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\DependencyInjection; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\CommandLoader\ContainerCommandLoader; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\TypedReference; + +/** + * Registers console commands. + * + * @author Grégoire Pineau <lyrixx@lyrixx.info> + */ +class AddConsoleCommandPass implements CompilerPassInterface +{ + private $commandLoaderServiceId; + private $commandTag; + + public function __construct(string $commandLoaderServiceId = 'console.command_loader', string $commandTag = 'console.command') + { + $this->commandLoaderServiceId = $commandLoaderServiceId; + $this->commandTag = $commandTag; + } + + public function process(ContainerBuilder $container) + { + $commandServices = $container->findTaggedServiceIds($this->commandTag, true); + $lazyCommandMap = []; + $lazyCommandRefs = []; + $serviceIds = []; + + foreach ($commandServices as $id => $tags) { + $definition = $container->getDefinition($id); + $class = $container->getParameterBag()->resolveValue($definition->getClass()); + + if (isset($tags[0]['command'])) { + $commandName = $tags[0]['command']; + } else { + if (!$r = $container->getReflectionClass($class)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + if (!$r->isSubclassOf(Command::class)) { + throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class)); + } + $commandName = null !== $class::getDefaultName() ? str_replace('%', '%%', $class::getDefaultName()) : null; + } + + if (null === $commandName) { + if (!$definition->isPublic() || $definition->isPrivate()) { + $commandId = 'console.command.public_alias.'.$id; + $container->setAlias($commandId, $id)->setPublic(true); + $id = $commandId; + } + $serviceIds[] = $id; + + continue; + } + + unset($tags[0]); + $lazyCommandMap[$commandName] = $id; + $lazyCommandRefs[$id] = new TypedReference($id, $class); + $aliases = []; + + foreach ($tags as $tag) { + if (isset($tag['command'])) { + $aliases[] = $tag['command']; + $lazyCommandMap[$tag['command']] = $id; + } + } + + $definition->addMethodCall('setName', [$commandName]); + + if ($aliases) { + $definition->addMethodCall('setAliases', [$aliases]); + } + } + + $container + ->register($this->commandLoaderServiceId, ContainerCommandLoader::class) + ->setPublic(true) + ->setArguments([ServiceLocatorTagPass::register($container, $lazyCommandRefs), $lazyCommandMap]); + + $container->setParameter('console.command.ids', $serviceIds); + } +} diff --git a/vendor/symfony/console/Descriptor/ApplicationDescription.php b/vendor/symfony/console/Descriptor/ApplicationDescription.php new file mode 100644 index 0000000000..3970b90007 --- /dev/null +++ b/vendor/symfony/console/Descriptor/ApplicationDescription.php @@ -0,0 +1,143 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\CommandNotFoundException; + +/** + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + * + * @internal + */ +class ApplicationDescription +{ + public const GLOBAL_NAMESPACE = '_global'; + + private $application; + private $namespace; + private $showHidden; + + /** + * @var array + */ + private $namespaces; + + /** + * @var Command[] + */ + private $commands; + + /** + * @var Command[] + */ + private $aliases; + + public function __construct(Application $application, string $namespace = null, bool $showHidden = false) + { + $this->application = $application; + $this->namespace = $namespace; + $this->showHidden = $showHidden; + } + + public function getNamespaces(): array + { + if (null === $this->namespaces) { + $this->inspectApplication(); + } + + return $this->namespaces; + } + + /** + * @return Command[] + */ + public function getCommands(): array + { + if (null === $this->commands) { + $this->inspectApplication(); + } + + return $this->commands; + } + + /** + * @throws CommandNotFoundException + */ + public function getCommand(string $name): Command + { + if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) { + throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name)); + } + + return $this->commands[$name] ?? $this->aliases[$name]; + } + + private function inspectApplication() + { + $this->commands = []; + $this->namespaces = []; + + $all = $this->application->all($this->namespace ? $this->application->findNamespace($this->namespace) : null); + foreach ($this->sortCommands($all) as $namespace => $commands) { + $names = []; + + /** @var Command $command */ + foreach ($commands as $name => $command) { + if (!$command->getName() || (!$this->showHidden && $command->isHidden())) { + continue; + } + + if ($command->getName() === $name) { + $this->commands[$name] = $command; + } else { + $this->aliases[$name] = $command; + } + + $names[] = $name; + } + + $this->namespaces[$namespace] = ['id' => $namespace, 'commands' => $names]; + } + } + + private function sortCommands(array $commands): array + { + $namespacedCommands = []; + $globalCommands = []; + $sortedCommands = []; + foreach ($commands as $name => $command) { + $key = $this->application->extractNamespace($name, 1); + if (\in_array($key, ['', self::GLOBAL_NAMESPACE], true)) { + $globalCommands[$name] = $command; + } else { + $namespacedCommands[$key][$name] = $command; + } + } + + if ($globalCommands) { + ksort($globalCommands); + $sortedCommands[self::GLOBAL_NAMESPACE] = $globalCommands; + } + + if ($namespacedCommands) { + ksort($namespacedCommands); + foreach ($namespacedCommands as $key => $commandsSet) { + ksort($commandsSet); + $sortedCommands[$key] = $commandsSet; + } + } + + return $sortedCommands; + } +} diff --git a/vendor/symfony/console/Descriptor/Descriptor.php b/vendor/symfony/console/Descriptor/Descriptor.php new file mode 100644 index 0000000000..9c3878d1ed --- /dev/null +++ b/vendor/symfony/console/Descriptor/Descriptor.php @@ -0,0 +1,97 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + * + * @internal + */ +abstract class Descriptor implements DescriptorInterface +{ + /** + * @var OutputInterface + */ + protected $output; + + /** + * {@inheritdoc} + */ + public function describe(OutputInterface $output, $object, array $options = []) + { + $this->output = $output; + + switch (true) { + case $object instanceof InputArgument: + $this->describeInputArgument($object, $options); + break; + case $object instanceof InputOption: + $this->describeInputOption($object, $options); + break; + case $object instanceof InputDefinition: + $this->describeInputDefinition($object, $options); + break; + case $object instanceof Command: + $this->describeCommand($object, $options); + break; + case $object instanceof Application: + $this->describeApplication($object, $options); + break; + default: + throw new InvalidArgumentException(sprintf('Object of type "%s" is not describable.', \get_class($object))); + } + } + + /** + * Writes content to output. + * + * @param string $content + * @param bool $decorated + */ + protected function write($content, $decorated = false) + { + $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW); + } + + /** + * Describes an InputArgument instance. + */ + abstract protected function describeInputArgument(InputArgument $argument, array $options = []); + + /** + * Describes an InputOption instance. + */ + abstract protected function describeInputOption(InputOption $option, array $options = []); + + /** + * Describes an InputDefinition instance. + */ + abstract protected function describeInputDefinition(InputDefinition $definition, array $options = []); + + /** + * Describes a Command instance. + */ + abstract protected function describeCommand(Command $command, array $options = []); + + /** + * Describes an Application instance. + */ + abstract protected function describeApplication(Application $application, array $options = []); +} diff --git a/vendor/symfony/console/Descriptor/DescriptorInterface.php b/vendor/symfony/console/Descriptor/DescriptorInterface.php new file mode 100644 index 0000000000..e3184a6a5a --- /dev/null +++ b/vendor/symfony/console/Descriptor/DescriptorInterface.php @@ -0,0 +1,29 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Descriptor interface. + * + * @author Jean-François Simon <contact@jfsimon.fr> + */ +interface DescriptorInterface +{ + /** + * Describes an object if supported. + * + * @param object $object + */ + public function describe(OutputInterface $output, $object, array $options = []); +} diff --git a/vendor/symfony/console/Descriptor/JsonDescriptor.php b/vendor/symfony/console/Descriptor/JsonDescriptor.php new file mode 100644 index 0000000000..4c09e12676 --- /dev/null +++ b/vendor/symfony/console/Descriptor/JsonDescriptor.php @@ -0,0 +1,156 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; + +/** + * JSON descriptor. + * + * @author Jean-François Simon <contact@jfsimon.fr> + * + * @internal + */ +class JsonDescriptor extends Descriptor +{ + /** + * {@inheritdoc} + */ + protected function describeInputArgument(InputArgument $argument, array $options = []) + { + $this->writeData($this->getInputArgumentData($argument), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeInputOption(InputOption $option, array $options = []) + { + $this->writeData($this->getInputOptionData($option), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = []) + { + $this->writeData($this->getInputDefinitionData($definition), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeCommand(Command $command, array $options = []) + { + $this->writeData($this->getCommandData($command), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeApplication(Application $application, array $options = []) + { + $describedNamespace = $options['namespace'] ?? null; + $description = new ApplicationDescription($application, $describedNamespace, true); + $commands = []; + + foreach ($description->getCommands() as $command) { + $commands[] = $this->getCommandData($command); + } + + $data = []; + if ('UNKNOWN' !== $application->getName()) { + $data['application']['name'] = $application->getName(); + if ('UNKNOWN' !== $application->getVersion()) { + $data['application']['version'] = $application->getVersion(); + } + } + + $data['commands'] = $commands; + + if ($describedNamespace) { + $data['namespace'] = $describedNamespace; + } else { + $data['namespaces'] = array_values($description->getNamespaces()); + } + + $this->writeData($data, $options); + } + + /** + * Writes data as json. + */ + private function writeData(array $data, array $options) + { + $flags = $options['json_encoding'] ?? 0; + + $this->write(json_encode($data, $flags)); + } + + private function getInputArgumentData(InputArgument $argument): array + { + return [ + 'name' => $argument->getName(), + 'is_required' => $argument->isRequired(), + 'is_array' => $argument->isArray(), + 'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $argument->getDescription()), + 'default' => \INF === $argument->getDefault() ? 'INF' : $argument->getDefault(), + ]; + } + + private function getInputOptionData(InputOption $option): array + { + return [ + 'name' => '--'.$option->getName(), + 'shortcut' => $option->getShortcut() ? '-'.str_replace('|', '|-', $option->getShortcut()) : '', + 'accept_value' => $option->acceptValue(), + 'is_value_required' => $option->isValueRequired(), + 'is_multiple' => $option->isArray(), + 'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $option->getDescription()), + 'default' => \INF === $option->getDefault() ? 'INF' : $option->getDefault(), + ]; + } + + private function getInputDefinitionData(InputDefinition $definition): array + { + $inputArguments = []; + foreach ($definition->getArguments() as $name => $argument) { + $inputArguments[$name] = $this->getInputArgumentData($argument); + } + + $inputOptions = []; + foreach ($definition->getOptions() as $name => $option) { + $inputOptions[$name] = $this->getInputOptionData($option); + } + + return ['arguments' => $inputArguments, 'options' => $inputOptions]; + } + + private function getCommandData(Command $command): array + { + $command->getSynopsis(); + $command->mergeApplicationDefinition(false); + + return [ + 'name' => $command->getName(), + 'usage' => array_merge([$command->getSynopsis()], $command->getUsages(), $command->getAliases()), + 'description' => $command->getDescription(), + 'help' => $command->getProcessedHelp(), + 'definition' => $this->getInputDefinitionData($command->getNativeDefinition()), + 'hidden' => $command->isHidden(), + ]; + } +} diff --git a/vendor/symfony/console/Descriptor/MarkdownDescriptor.php b/vendor/symfony/console/Descriptor/MarkdownDescriptor.php new file mode 100644 index 0000000000..9a9d280751 --- /dev/null +++ b/vendor/symfony/console/Descriptor/MarkdownDescriptor.php @@ -0,0 +1,182 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Markdown descriptor. + * + * @author Jean-François Simon <contact@jfsimon.fr> + * + * @internal + */ +class MarkdownDescriptor extends Descriptor +{ + /** + * {@inheritdoc} + */ + public function describe(OutputInterface $output, $object, array $options = []) + { + $decorated = $output->isDecorated(); + $output->setDecorated(false); + + parent::describe($output, $object, $options); + + $output->setDecorated($decorated); + } + + /** + * {@inheritdoc} + */ + protected function write($content, $decorated = true) + { + parent::write($content, $decorated); + } + + /** + * {@inheritdoc} + */ + protected function describeInputArgument(InputArgument $argument, array $options = []) + { + $this->write( + '#### `'.($argument->getName() ?: '<none>')."`\n\n" + .($argument->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $argument->getDescription())."\n\n" : '') + .'* Is required: '.($argument->isRequired() ? 'yes' : 'no')."\n" + .'* Is array: '.($argument->isArray() ? 'yes' : 'no')."\n" + .'* Default: `'.str_replace("\n", '', var_export($argument->getDefault(), true)).'`' + ); + } + + /** + * {@inheritdoc} + */ + protected function describeInputOption(InputOption $option, array $options = []) + { + $name = '--'.$option->getName(); + if ($option->getShortcut()) { + $name .= '|-'.str_replace('|', '|-', $option->getShortcut()).''; + } + + $this->write( + '#### `'.$name.'`'."\n\n" + .($option->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $option->getDescription())."\n\n" : '') + .'* Accept value: '.($option->acceptValue() ? 'yes' : 'no')."\n" + .'* Is value required: '.($option->isValueRequired() ? 'yes' : 'no')."\n" + .'* Is multiple: '.($option->isArray() ? 'yes' : 'no')."\n" + .'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`' + ); + } + + /** + * {@inheritdoc} + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = []) + { + if ($showArguments = \count($definition->getArguments()) > 0) { + $this->write('### Arguments'); + foreach ($definition->getArguments() as $argument) { + $this->write("\n\n"); + $this->write($this->describeInputArgument($argument)); + } + } + + if (\count($definition->getOptions()) > 0) { + if ($showArguments) { + $this->write("\n\n"); + } + + $this->write('### Options'); + foreach ($definition->getOptions() as $option) { + $this->write("\n\n"); + $this->write($this->describeInputOption($option)); + } + } + } + + /** + * {@inheritdoc} + */ + protected function describeCommand(Command $command, array $options = []) + { + $command->getSynopsis(); + $command->mergeApplicationDefinition(false); + + $this->write( + '`'.$command->getName()."`\n" + .str_repeat('-', Helper::strlen($command->getName()) + 2)."\n\n" + .($command->getDescription() ? $command->getDescription()."\n\n" : '') + .'### Usage'."\n\n" + .array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), function ($carry, $usage) { + return $carry.'* `'.$usage.'`'."\n"; + }) + ); + + if ($help = $command->getProcessedHelp()) { + $this->write("\n"); + $this->write($help); + } + + if ($command->getNativeDefinition()) { + $this->write("\n\n"); + $this->describeInputDefinition($command->getNativeDefinition()); + } + } + + /** + * {@inheritdoc} + */ + protected function describeApplication(Application $application, array $options = []) + { + $describedNamespace = $options['namespace'] ?? null; + $description = new ApplicationDescription($application, $describedNamespace); + $title = $this->getApplicationTitle($application); + + $this->write($title."\n".str_repeat('=', Helper::strlen($title))); + + foreach ($description->getNamespaces() as $namespace) { + if (ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { + $this->write("\n\n"); + $this->write('**'.$namespace['id'].':**'); + } + + $this->write("\n\n"); + $this->write(implode("\n", array_map(function ($commandName) use ($description) { + return sprintf('* [`%s`](#%s)', $commandName, str_replace(':', '', $description->getCommand($commandName)->getName())); + }, $namespace['commands']))); + } + + foreach ($description->getCommands() as $command) { + $this->write("\n\n"); + $this->write($this->describeCommand($command)); + } + } + + private function getApplicationTitle(Application $application): string + { + if ('UNKNOWN' !== $application->getName()) { + if ('UNKNOWN' !== $application->getVersion()) { + return sprintf('%s %s', $application->getName(), $application->getVersion()); + } + + return $application->getName(); + } + + return 'Console Tool'; + } +} diff --git a/vendor/symfony/console/Descriptor/TextDescriptor.php b/vendor/symfony/console/Descriptor/TextDescriptor.php new file mode 100644 index 0000000000..7d4d5f0bb1 --- /dev/null +++ b/vendor/symfony/console/Descriptor/TextDescriptor.php @@ -0,0 +1,342 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; + +/** + * Text descriptor. + * + * @author Jean-François Simon <contact@jfsimon.fr> + * + * @internal + */ +class TextDescriptor extends Descriptor +{ + /** + * {@inheritdoc} + */ + protected function describeInputArgument(InputArgument $argument, array $options = []) + { + if (null !== $argument->getDefault() && (!\is_array($argument->getDefault()) || \count($argument->getDefault()))) { + $default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($argument->getDefault())); + } else { + $default = ''; + } + + $totalWidth = $options['total_width'] ?? Helper::strlen($argument->getName()); + $spacingWidth = $totalWidth - \strlen($argument->getName()); + + $this->writeText(sprintf(' <info>%s</info> %s%s%s', + $argument->getName(), + str_repeat(' ', $spacingWidth), + // + 4 = 2 spaces before <info>, 2 spaces after </info> + preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $argument->getDescription()), + $default + ), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeInputOption(InputOption $option, array $options = []) + { + if ($option->acceptValue() && null !== $option->getDefault() && (!\is_array($option->getDefault()) || \count($option->getDefault()))) { + $default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($option->getDefault())); + } else { + $default = ''; + } + + $value = ''; + if ($option->acceptValue()) { + $value = '='.strtoupper($option->getName()); + + if ($option->isValueOptional()) { + $value = '['.$value.']'; + } + } + + $totalWidth = $options['total_width'] ?? $this->calculateTotalWidthForOptions([$option]); + $synopsis = sprintf('%s%s', + $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ', + sprintf('--%s%s', $option->getName(), $value) + ); + + $spacingWidth = $totalWidth - Helper::strlen($synopsis); + + $this->writeText(sprintf(' <info>%s</info> %s%s%s%s', + $synopsis, + str_repeat(' ', $spacingWidth), + // + 4 = 2 spaces before <info>, 2 spaces after </info> + preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $option->getDescription()), + $default, + $option->isArray() ? '<comment> (multiple values allowed)</comment>' : '' + ), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = []) + { + $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions()); + foreach ($definition->getArguments() as $argument) { + $totalWidth = max($totalWidth, Helper::strlen($argument->getName())); + } + + if ($definition->getArguments()) { + $this->writeText('<comment>Arguments:</comment>', $options); + $this->writeText("\n"); + foreach ($definition->getArguments() as $argument) { + $this->describeInputArgument($argument, array_merge($options, ['total_width' => $totalWidth])); + $this->writeText("\n"); + } + } + + if ($definition->getArguments() && $definition->getOptions()) { + $this->writeText("\n"); + } + + if ($definition->getOptions()) { + $laterOptions = []; + + $this->writeText('<comment>Options:</comment>', $options); + foreach ($definition->getOptions() as $option) { + if (\strlen($option->getShortcut() ?? '') > 1) { + $laterOptions[] = $option; + continue; + } + $this->writeText("\n"); + $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth])); + } + foreach ($laterOptions as $option) { + $this->writeText("\n"); + $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth])); + } + } + } + + /** + * {@inheritdoc} + */ + protected function describeCommand(Command $command, array $options = []) + { + $command->getSynopsis(true); + $command->getSynopsis(false); + $command->mergeApplicationDefinition(false); + + if ($description = $command->getDescription()) { + $this->writeText('<comment>Description:</comment>', $options); + $this->writeText("\n"); + $this->writeText(' '.$description); + $this->writeText("\n\n"); + } + + $this->writeText('<comment>Usage:</comment>', $options); + foreach (array_merge([$command->getSynopsis(true)], $command->getAliases(), $command->getUsages()) as $usage) { + $this->writeText("\n"); + $this->writeText(' '.OutputFormatter::escape($usage), $options); + } + $this->writeText("\n"); + + $definition = $command->getNativeDefinition(); + if ($definition->getOptions() || $definition->getArguments()) { + $this->writeText("\n"); + $this->describeInputDefinition($definition, $options); + $this->writeText("\n"); + } + + $help = $command->getProcessedHelp(); + if ($help && $help !== $description) { + $this->writeText("\n"); + $this->writeText('<comment>Help:</comment>', $options); + $this->writeText("\n"); + $this->writeText(' '.str_replace("\n", "\n ", $help), $options); + $this->writeText("\n"); + } + } + + /** + * {@inheritdoc} + */ + protected function describeApplication(Application $application, array $options = []) + { + $describedNamespace = $options['namespace'] ?? null; + $description = new ApplicationDescription($application, $describedNamespace); + + if (isset($options['raw_text']) && $options['raw_text']) { + $width = $this->getColumnWidth($description->getCommands()); + + foreach ($description->getCommands() as $command) { + $this->writeText(sprintf("%-{$width}s %s", $command->getName(), $command->getDescription()), $options); + $this->writeText("\n"); + } + } else { + if ('' != $help = $application->getHelp()) { + $this->writeText("$help\n\n", $options); + } + + $this->writeText("<comment>Usage:</comment>\n", $options); + $this->writeText(" command [options] [arguments]\n\n", $options); + + $this->describeInputDefinition(new InputDefinition($application->getDefinition()->getOptions()), $options); + + $this->writeText("\n"); + $this->writeText("\n"); + + $commands = $description->getCommands(); + $namespaces = $description->getNamespaces(); + if ($describedNamespace && $namespaces) { + // make sure all alias commands are included when describing a specific namespace + $describedNamespaceInfo = reset($namespaces); + foreach ($describedNamespaceInfo['commands'] as $name) { + $commands[$name] = $description->getCommand($name); + } + } + + // calculate max. width based on available commands per namespace + $width = $this->getColumnWidth(array_merge(...array_values(array_map(function ($namespace) use ($commands) { + return array_intersect($namespace['commands'], array_keys($commands)); + }, array_values($namespaces))))); + + if ($describedNamespace) { + $this->writeText(sprintf('<comment>Available commands for the "%s" namespace:</comment>', $describedNamespace), $options); + } else { + $this->writeText('<comment>Available commands:</comment>', $options); + } + + foreach ($namespaces as $namespace) { + $namespace['commands'] = array_filter($namespace['commands'], function ($name) use ($commands) { + return isset($commands[$name]); + }); + + if (!$namespace['commands']) { + continue; + } + + if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { + $this->writeText("\n"); + $this->writeText(' <comment>'.$namespace['id'].'</comment>', $options); + } + + foreach ($namespace['commands'] as $name) { + $this->writeText("\n"); + $spacingWidth = $width - Helper::strlen($name); + $command = $commands[$name]; + $commandAliases = $name === $command->getName() ? $this->getCommandAliasesText($command) : ''; + $this->writeText(sprintf(' <info>%s</info>%s%s', $name, str_repeat(' ', $spacingWidth), $commandAliases.$command->getDescription()), $options); + } + } + + $this->writeText("\n"); + } + } + + /** + * {@inheritdoc} + */ + private function writeText(string $content, array $options = []) + { + $this->write( + isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content, + isset($options['raw_output']) ? !$options['raw_output'] : true + ); + } + + /** + * Formats command aliases to show them in the command description. + */ + private function getCommandAliasesText(Command $command): string + { + $text = ''; + $aliases = $command->getAliases(); + + if ($aliases) { + $text = '['.implode('|', $aliases).'] '; + } + + return $text; + } + + /** + * Formats input option/argument default value. + * + * @param mixed $default + */ + private function formatDefaultValue($default): string + { + if (\INF === $default) { + return 'INF'; + } + + if (\is_string($default)) { + $default = OutputFormatter::escape($default); + } elseif (\is_array($default)) { + foreach ($default as $key => $value) { + if (\is_string($value)) { + $default[$key] = OutputFormatter::escape($value); + } + } + } + + return str_replace('\\\\', '\\', json_encode($default, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE)); + } + + /** + * @param array<Command|string> $commands + */ + private function getColumnWidth(array $commands): int + { + $widths = []; + + foreach ($commands as $command) { + if ($command instanceof Command) { + $widths[] = Helper::strlen($command->getName()); + foreach ($command->getAliases() as $alias) { + $widths[] = Helper::strlen($alias); + } + } else { + $widths[] = Helper::strlen($command); + } + } + + return $widths ? max($widths) + 2 : 0; + } + + /** + * @param InputOption[] $options + */ + private function calculateTotalWidthForOptions(array $options): int + { + $totalWidth = 0; + foreach ($options as $option) { + // "-" + shortcut + ", --" + name + $nameLength = 1 + max(Helper::strlen($option->getShortcut()), 1) + 4 + Helper::strlen($option->getName()); + + if ($option->acceptValue()) { + $valueLength = 1 + Helper::strlen($option->getName()); // = + value + $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ] + + $nameLength += $valueLength; + } + $totalWidth = max($totalWidth, $nameLength); + } + + return $totalWidth; + } +} diff --git a/vendor/symfony/console/Descriptor/XmlDescriptor.php b/vendor/symfony/console/Descriptor/XmlDescriptor.php new file mode 100644 index 0000000000..e0ed53a381 --- /dev/null +++ b/vendor/symfony/console/Descriptor/XmlDescriptor.php @@ -0,0 +1,231 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; + +/** + * XML descriptor. + * + * @author Jean-François Simon <contact@jfsimon.fr> + * + * @internal + */ +class XmlDescriptor extends Descriptor +{ + public function getInputDefinitionDocument(InputDefinition $definition): \DOMDocument + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($definitionXML = $dom->createElement('definition')); + + $definitionXML->appendChild($argumentsXML = $dom->createElement('arguments')); + foreach ($definition->getArguments() as $argument) { + $this->appendDocument($argumentsXML, $this->getInputArgumentDocument($argument)); + } + + $definitionXML->appendChild($optionsXML = $dom->createElement('options')); + foreach ($definition->getOptions() as $option) { + $this->appendDocument($optionsXML, $this->getInputOptionDocument($option)); + } + + return $dom; + } + + public function getCommandDocument(Command $command): \DOMDocument + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($commandXML = $dom->createElement('command')); + + $command->getSynopsis(); + $command->mergeApplicationDefinition(false); + + $commandXML->setAttribute('id', $command->getName()); + $commandXML->setAttribute('name', $command->getName()); + $commandXML->setAttribute('hidden', $command->isHidden() ? 1 : 0); + + $commandXML->appendChild($usagesXML = $dom->createElement('usages')); + + foreach (array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()) as $usage) { + $usagesXML->appendChild($dom->createElement('usage', $usage)); + } + + $commandXML->appendChild($descriptionXML = $dom->createElement('description')); + $descriptionXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getDescription()))); + + $commandXML->appendChild($helpXML = $dom->createElement('help')); + $helpXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getProcessedHelp()))); + + $definitionXML = $this->getInputDefinitionDocument($command->getNativeDefinition()); + $this->appendDocument($commandXML, $definitionXML->getElementsByTagName('definition')->item(0)); + + return $dom; + } + + public function getApplicationDocument(Application $application, string $namespace = null): \DOMDocument + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($rootXml = $dom->createElement('symfony')); + + if ('UNKNOWN' !== $application->getName()) { + $rootXml->setAttribute('name', $application->getName()); + if ('UNKNOWN' !== $application->getVersion()) { + $rootXml->setAttribute('version', $application->getVersion()); + } + } + + $rootXml->appendChild($commandsXML = $dom->createElement('commands')); + + $description = new ApplicationDescription($application, $namespace, true); + + if ($namespace) { + $commandsXML->setAttribute('namespace', $namespace); + } + + foreach ($description->getCommands() as $command) { + $this->appendDocument($commandsXML, $this->getCommandDocument($command)); + } + + if (!$namespace) { + $rootXml->appendChild($namespacesXML = $dom->createElement('namespaces')); + + foreach ($description->getNamespaces() as $namespaceDescription) { + $namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace')); + $namespaceArrayXML->setAttribute('id', $namespaceDescription['id']); + + foreach ($namespaceDescription['commands'] as $name) { + $namespaceArrayXML->appendChild($commandXML = $dom->createElement('command')); + $commandXML->appendChild($dom->createTextNode($name)); + } + } + } + + return $dom; + } + + /** + * {@inheritdoc} + */ + protected function describeInputArgument(InputArgument $argument, array $options = []) + { + $this->writeDocument($this->getInputArgumentDocument($argument)); + } + + /** + * {@inheritdoc} + */ + protected function describeInputOption(InputOption $option, array $options = []) + { + $this->writeDocument($this->getInputOptionDocument($option)); + } + + /** + * {@inheritdoc} + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = []) + { + $this->writeDocument($this->getInputDefinitionDocument($definition)); + } + + /** + * {@inheritdoc} + */ + protected function describeCommand(Command $command, array $options = []) + { + $this->writeDocument($this->getCommandDocument($command)); + } + + /** + * {@inheritdoc} + */ + protected function describeApplication(Application $application, array $options = []) + { + $this->writeDocument($this->getApplicationDocument($application, $options['namespace'] ?? null)); + } + + /** + * Appends document children to parent node. + */ + private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent) + { + foreach ($importedParent->childNodes as $childNode) { + $parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true)); + } + } + + /** + * Writes DOM document. + */ + private function writeDocument(\DOMDocument $dom) + { + $dom->formatOutput = true; + $this->write($dom->saveXML()); + } + + private function getInputArgumentDocument(InputArgument $argument): \DOMDocument + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + + $dom->appendChild($objectXML = $dom->createElement('argument')); + $objectXML->setAttribute('name', $argument->getName()); + $objectXML->setAttribute('is_required', $argument->isRequired() ? 1 : 0); + $objectXML->setAttribute('is_array', $argument->isArray() ? 1 : 0); + $objectXML->appendChild($descriptionXML = $dom->createElement('description')); + $descriptionXML->appendChild($dom->createTextNode($argument->getDescription())); + + $objectXML->appendChild($defaultsXML = $dom->createElement('defaults')); + $defaults = \is_array($argument->getDefault()) ? $argument->getDefault() : (\is_bool($argument->getDefault()) ? [var_export($argument->getDefault(), true)] : ($argument->getDefault() ? [$argument->getDefault()] : [])); + foreach ($defaults as $default) { + $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); + $defaultXML->appendChild($dom->createTextNode($default)); + } + + return $dom; + } + + private function getInputOptionDocument(InputOption $option): \DOMDocument + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + + $dom->appendChild($objectXML = $dom->createElement('option')); + $objectXML->setAttribute('name', '--'.$option->getName()); + $pos = strpos($option->getShortcut() ?? '', '|'); + if (false !== $pos) { + $objectXML->setAttribute('shortcut', '-'.substr($option->getShortcut(), 0, $pos)); + $objectXML->setAttribute('shortcuts', '-'.str_replace('|', '|-', $option->getShortcut())); + } else { + $objectXML->setAttribute('shortcut', $option->getShortcut() ? '-'.$option->getShortcut() : ''); + } + $objectXML->setAttribute('accept_value', $option->acceptValue() ? 1 : 0); + $objectXML->setAttribute('is_value_required', $option->isValueRequired() ? 1 : 0); + $objectXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0); + $objectXML->appendChild($descriptionXML = $dom->createElement('description')); + $descriptionXML->appendChild($dom->createTextNode($option->getDescription())); + + if ($option->acceptValue()) { + $defaults = \is_array($option->getDefault()) ? $option->getDefault() : (\is_bool($option->getDefault()) ? [var_export($option->getDefault(), true)] : ($option->getDefault() ? [$option->getDefault()] : [])); + $objectXML->appendChild($defaultsXML = $dom->createElement('defaults')); + + if (!empty($defaults)) { + foreach ($defaults as $default) { + $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); + $defaultXML->appendChild($dom->createTextNode($default)); + } + } + } + + return $dom; + } +} diff --git a/vendor/symfony/console/Event/ConsoleCommandEvent.php b/vendor/symfony/console/Event/ConsoleCommandEvent.php new file mode 100644 index 0000000000..9691db6362 --- /dev/null +++ b/vendor/symfony/console/Event/ConsoleCommandEvent.php @@ -0,0 +1,62 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Event; + +/** + * Allows to do things before the command is executed, like skipping the command or changing the input. + * + * @author Fabien Potencier <fabien@symfony.com> + * + * @final since Symfony 4.4 + */ +class ConsoleCommandEvent extends ConsoleEvent +{ + /** + * The return code for skipped commands, this will also be passed into the terminate event. + */ + public const RETURN_CODE_DISABLED = 113; + + /** + * Indicates if the command should be run or skipped. + */ + private $commandShouldRun = true; + + /** + * Disables the command, so it won't be run. + * + * @return bool + */ + public function disableCommand() + { + return $this->commandShouldRun = false; + } + + /** + * Enables the command. + * + * @return bool + */ + public function enableCommand() + { + return $this->commandShouldRun = true; + } + + /** + * Returns true if the command is runnable, false otherwise. + * + * @return bool + */ + public function commandShouldRun() + { + return $this->commandShouldRun; + } +} diff --git a/vendor/symfony/console/Event/ConsoleErrorEvent.php b/vendor/symfony/console/Event/ConsoleErrorEvent.php new file mode 100644 index 0000000000..57d9b38ba0 --- /dev/null +++ b/vendor/symfony/console/Event/ConsoleErrorEvent.php @@ -0,0 +1,58 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Event; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Allows to handle throwables thrown while running a command. + * + * @author Wouter de Jong <wouter@wouterj.nl> + */ +final class ConsoleErrorEvent extends ConsoleEvent +{ + private $error; + private $exitCode; + + public function __construct(InputInterface $input, OutputInterface $output, \Throwable $error, Command $command = null) + { + parent::__construct($command, $input, $output); + + $this->error = $error; + } + + public function getError(): \Throwable + { + return $this->error; + } + + public function setError(\Throwable $error): void + { + $this->error = $error; + } + + public function setExitCode(int $exitCode): void + { + $this->exitCode = $exitCode; + + $r = new \ReflectionProperty($this->error, 'code'); + $r->setAccessible(true); + $r->setValue($this->error, $this->exitCode); + } + + public function getExitCode(): int + { + return $this->exitCode ?? (\is_int($this->error->getCode()) && 0 !== $this->error->getCode() ? $this->error->getCode() : 1); + } +} diff --git a/vendor/symfony/console/Event/ConsoleEvent.php b/vendor/symfony/console/Event/ConsoleEvent.php new file mode 100644 index 0000000000..400eb5731b --- /dev/null +++ b/vendor/symfony/console/Event/ConsoleEvent.php @@ -0,0 +1,67 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Event; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\EventDispatcher\Event; + +/** + * Allows to inspect input and output of a command. + * + * @author Francesco Levorato <git@flevour.net> + */ +class ConsoleEvent extends Event +{ + protected $command; + + private $input; + private $output; + + public function __construct(?Command $command, InputInterface $input, OutputInterface $output) + { + $this->command = $command; + $this->input = $input; + $this->output = $output; + } + + /** + * Gets the command that is executed. + * + * @return Command|null A Command instance + */ + public function getCommand() + { + return $this->command; + } + + /** + * Gets the input instance. + * + * @return InputInterface An InputInterface instance + */ + public function getInput() + { + return $this->input; + } + + /** + * Gets the output instance. + * + * @return OutputInterface An OutputInterface instance + */ + public function getOutput() + { + return $this->output; + } +} diff --git a/vendor/symfony/console/Event/ConsoleTerminateEvent.php b/vendor/symfony/console/Event/ConsoleTerminateEvent.php new file mode 100644 index 0000000000..43d0f8ab1a --- /dev/null +++ b/vendor/symfony/console/Event/ConsoleTerminateEvent.php @@ -0,0 +1,55 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Event; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Allows to manipulate the exit code of a command after its execution. + * + * @author Francesco Levorato <git@flevour.net> + * + * @final since Symfony 4.4 + */ +class ConsoleTerminateEvent extends ConsoleEvent +{ + private $exitCode; + + public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $exitCode) + { + parent::__construct($command, $input, $output); + + $this->setExitCode($exitCode); + } + + /** + * Sets the exit code. + * + * @param int $exitCode The command exit code + */ + public function setExitCode($exitCode) + { + $this->exitCode = (int) $exitCode; + } + + /** + * Gets the exit code. + * + * @return int The command exit code + */ + public function getExitCode() + { + return $this->exitCode; + } +} diff --git a/vendor/symfony/console/EventListener/ErrorListener.php b/vendor/symfony/console/EventListener/ErrorListener.php new file mode 100644 index 0000000000..897d9853f2 --- /dev/null +++ b/vendor/symfony/console/EventListener/ErrorListener.php @@ -0,0 +1,95 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\EventListener; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\Console\Event\ConsoleErrorEvent; +use Symfony\Component\Console\Event\ConsoleEvent; +use Symfony\Component\Console\Event\ConsoleTerminateEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * @author James Halsall <james.t.halsall@googlemail.com> + * @author Robin Chalas <robin.chalas@gmail.com> + */ +class ErrorListener implements EventSubscriberInterface +{ + private $logger; + + public function __construct(LoggerInterface $logger = null) + { + $this->logger = $logger; + } + + public function onConsoleError(ConsoleErrorEvent $event) + { + if (null === $this->logger) { + return; + } + + $error = $event->getError(); + + if (!$inputString = $this->getInputString($event)) { + $this->logger->critical('An error occurred while using the console. Message: "{message}"', ['exception' => $error, 'message' => $error->getMessage()]); + + return; + } + + $this->logger->critical('Error thrown while running command "{command}". Message: "{message}"', ['exception' => $error, 'command' => $inputString, 'message' => $error->getMessage()]); + } + + public function onConsoleTerminate(ConsoleTerminateEvent $event) + { + if (null === $this->logger) { + return; + } + + $exitCode = $event->getExitCode(); + + if (0 === $exitCode) { + return; + } + + if (!$inputString = $this->getInputString($event)) { + $this->logger->debug('The console exited with code "{code}"', ['code' => $exitCode]); + + return; + } + + $this->logger->debug('Command "{command}" exited with code "{code}"', ['command' => $inputString, 'code' => $exitCode]); + } + + public static function getSubscribedEvents() + { + return [ + ConsoleEvents::ERROR => ['onConsoleError', -128], + ConsoleEvents::TERMINATE => ['onConsoleTerminate', -128], + ]; + } + + private static function getInputString(ConsoleEvent $event): ?string + { + $commandName = $event->getCommand() ? $event->getCommand()->getName() : null; + $input = $event->getInput(); + + if (method_exists($input, '__toString')) { + if ($commandName) { + return str_replace(["'$commandName'", "\"$commandName\""], $commandName, (string) $input); + } + + return (string) $input; + } + + return $commandName; + } +} diff --git a/vendor/symfony/console/Exception/CommandNotFoundException.php b/vendor/symfony/console/Exception/CommandNotFoundException.php new file mode 100644 index 0000000000..590a71c779 --- /dev/null +++ b/vendor/symfony/console/Exception/CommandNotFoundException.php @@ -0,0 +1,43 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * Represents an incorrect command name typed in the console. + * + * @author Jérôme Tamarelle <jerome@tamarelle.net> + */ +class CommandNotFoundException extends \InvalidArgumentException implements ExceptionInterface +{ + private $alternatives; + + /** + * @param string $message Exception message to throw + * @param string[] $alternatives List of similar defined names + * @param int $code Exception code + * @param \Throwable|null $previous Previous exception used for the exception chaining + */ + public function __construct(string $message, array $alternatives = [], int $code = 0, \Throwable $previous = null) + { + parent::__construct($message, $code, $previous); + + $this->alternatives = $alternatives; + } + + /** + * @return string[] A list of similar defined names + */ + public function getAlternatives() + { + return $this->alternatives; + } +} diff --git a/vendor/symfony/console/Exception/ExceptionInterface.php b/vendor/symfony/console/Exception/ExceptionInterface.php new file mode 100644 index 0000000000..1624e13d0b --- /dev/null +++ b/vendor/symfony/console/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * ExceptionInterface. + * + * @author Jérôme Tamarelle <jerome@tamarelle.net> + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/console/Exception/InvalidArgumentException.php b/vendor/symfony/console/Exception/InvalidArgumentException.php new file mode 100644 index 0000000000..07cc0b61d6 --- /dev/null +++ b/vendor/symfony/console/Exception/InvalidArgumentException.php @@ -0,0 +1,19 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * @author Jérôme Tamarelle <jerome@tamarelle.net> + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/console/Exception/InvalidOptionException.php b/vendor/symfony/console/Exception/InvalidOptionException.php new file mode 100644 index 0000000000..b2eec61658 --- /dev/null +++ b/vendor/symfony/console/Exception/InvalidOptionException.php @@ -0,0 +1,21 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * Represents an incorrect option name typed in the console. + * + * @author Jérôme Tamarelle <jerome@tamarelle.net> + */ +class InvalidOptionException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/console/Exception/LogicException.php b/vendor/symfony/console/Exception/LogicException.php new file mode 100644 index 0000000000..fc37b8d8ae --- /dev/null +++ b/vendor/symfony/console/Exception/LogicException.php @@ -0,0 +1,19 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * @author Jérôme Tamarelle <jerome@tamarelle.net> + */ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/console/Exception/MissingInputException.php b/vendor/symfony/console/Exception/MissingInputException.php new file mode 100644 index 0000000000..04f02ade45 --- /dev/null +++ b/vendor/symfony/console/Exception/MissingInputException.php @@ -0,0 +1,21 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * Represents failure to read input from stdin. + * + * @author Gabriel Ostrolucký <gabriel.ostrolucky@gmail.com> + */ +class MissingInputException extends RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/console/Exception/NamespaceNotFoundException.php b/vendor/symfony/console/Exception/NamespaceNotFoundException.php new file mode 100644 index 0000000000..dd16e45086 --- /dev/null +++ b/vendor/symfony/console/Exception/NamespaceNotFoundException.php @@ -0,0 +1,21 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * Represents an incorrect namespace typed in the console. + * + * @author Pierre du Plessis <pdples@gmail.com> + */ +class NamespaceNotFoundException extends CommandNotFoundException +{ +} diff --git a/vendor/symfony/console/Exception/RuntimeException.php b/vendor/symfony/console/Exception/RuntimeException.php new file mode 100644 index 0000000000..51d7d80ac6 --- /dev/null +++ b/vendor/symfony/console/Exception/RuntimeException.php @@ -0,0 +1,19 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * @author Jérôme Tamarelle <jerome@tamarelle.net> + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/console/Formatter/OutputFormatter.php b/vendor/symfony/console/Formatter/OutputFormatter.php new file mode 100644 index 0000000000..e8c10e7006 --- /dev/null +++ b/vendor/symfony/console/Formatter/OutputFormatter.php @@ -0,0 +1,283 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * Formatter class for console output. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + * @author Roland Franssen <franssen.roland@gmail.com> + */ +class OutputFormatter implements WrappableOutputFormatterInterface +{ + private $decorated; + private $styles = []; + private $styleStack; + + public function __clone() + { + $this->styleStack = clone $this->styleStack; + foreach ($this->styles as $key => $value) { + $this->styles[$key] = clone $value; + } + } + + /** + * Escapes "<" and ">" special chars in given text. + * + * @param string $text Text to escape + * + * @return string Escaped text + */ + public static function escape($text) + { + $text = preg_replace('/([^\\\\]|^)([<>])/', '$1\\\\$2', $text); + + return self::escapeTrailingBackslash($text); + } + + /** + * Escapes trailing "\" in given text. + * + * @internal + */ + public static function escapeTrailingBackslash(string $text): string + { + if (str_ends_with($text, '\\')) { + $len = \strlen($text); + $text = rtrim($text, '\\'); + $text = str_replace("\0", '', $text); + $text .= str_repeat("\0", $len - \strlen($text)); + } + + return $text; + } + + /** + * Initializes console output formatter. + * + * @param OutputFormatterStyleInterface[] $styles Array of "name => FormatterStyle" instances + */ + public function __construct(bool $decorated = false, array $styles = []) + { + $this->decorated = $decorated; + + $this->setStyle('error', new OutputFormatterStyle('white', 'red')); + $this->setStyle('info', new OutputFormatterStyle('green')); + $this->setStyle('comment', new OutputFormatterStyle('yellow')); + $this->setStyle('question', new OutputFormatterStyle('black', 'cyan')); + + foreach ($styles as $name => $style) { + $this->setStyle($name, $style); + } + + $this->styleStack = new OutputFormatterStyleStack(); + } + + /** + * {@inheritdoc} + */ + public function setDecorated($decorated) + { + $this->decorated = (bool) $decorated; + } + + /** + * {@inheritdoc} + */ + public function isDecorated() + { + return $this->decorated; + } + + /** + * {@inheritdoc} + */ + public function setStyle($name, OutputFormatterStyleInterface $style) + { + $this->styles[strtolower($name)] = $style; + } + + /** + * {@inheritdoc} + */ + public function hasStyle($name) + { + return isset($this->styles[strtolower($name)]); + } + + /** + * {@inheritdoc} + */ + public function getStyle($name) + { + if (!$this->hasStyle($name)) { + throw new InvalidArgumentException(sprintf('Undefined style: "%s".', $name)); + } + + return $this->styles[strtolower($name)]; + } + + /** + * {@inheritdoc} + */ + public function format($message) + { + return $this->formatAndWrap((string) $message, 0); + } + + /** + * {@inheritdoc} + */ + public function formatAndWrap(string $message, int $width) + { + $offset = 0; + $output = ''; + $openTagRegex = '[a-z](?:[^\\\\<>]*+ | \\\\.)*'; + $closeTagRegex = '[a-z][^<>]*+'; + $currentLineLength = 0; + preg_match_all("#<(($openTagRegex) | /($closeTagRegex)?)>#ix", $message, $matches, \PREG_OFFSET_CAPTURE); + foreach ($matches[0] as $i => $match) { + $pos = $match[1]; + $text = $match[0]; + + if (0 != $pos && '\\' == $message[$pos - 1]) { + continue; + } + + // add the text up to the next tag + $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset), $output, $width, $currentLineLength); + $offset = $pos + \strlen($text); + + // opening tag? + if ($open = '/' != $text[1]) { + $tag = $matches[1][$i][0]; + } else { + $tag = $matches[3][$i][0] ?? ''; + } + + if (!$open && !$tag) { + // </> + $this->styleStack->pop(); + } elseif (null === $style = $this->createStyleFromString($tag)) { + $output .= $this->applyCurrentStyle($text, $output, $width, $currentLineLength); + } elseif ($open) { + $this->styleStack->push($style); + } else { + $this->styleStack->pop($style); + } + } + + $output .= $this->applyCurrentStyle(substr($message, $offset), $output, $width, $currentLineLength); + + return strtr($output, ["\0" => '\\', '\\<' => '<', '\\>' => '>']); + } + + /** + * @return OutputFormatterStyleStack + */ + public function getStyleStack() + { + return $this->styleStack; + } + + /** + * Tries to create new style instance from string. + */ + private function createStyleFromString(string $string): ?OutputFormatterStyleInterface + { + if (isset($this->styles[$string])) { + return $this->styles[$string]; + } + + if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', $string, $matches, \PREG_SET_ORDER)) { + return null; + } + + $style = new OutputFormatterStyle(); + foreach ($matches as $match) { + array_shift($match); + $match[0] = strtolower($match[0]); + + if ('fg' == $match[0]) { + $style->setForeground(strtolower($match[1])); + } elseif ('bg' == $match[0]) { + $style->setBackground(strtolower($match[1])); + } elseif ('href' === $match[0]) { + $url = preg_replace('{\\\\([<>])}', '$1', $match[1]); + $style->setHref($url); + } elseif ('options' === $match[0]) { + preg_match_all('([^,;]+)', strtolower($match[1]), $options); + $options = array_shift($options); + foreach ($options as $option) { + $style->setOption($option); + } + } else { + return null; + } + } + + return $style; + } + + /** + * Applies current style from stack to text, if must be applied. + */ + private function applyCurrentStyle(string $text, string $current, int $width, int &$currentLineLength): string + { + if ('' === $text) { + return ''; + } + + if (!$width) { + return $this->isDecorated() ? $this->styleStack->getCurrent()->apply($text) : $text; + } + + if (!$currentLineLength && '' !== $current) { + $text = ltrim($text); + } + + if ($currentLineLength) { + $prefix = substr($text, 0, $i = $width - $currentLineLength)."\n"; + $text = substr($text, $i); + } else { + $prefix = ''; + } + + preg_match('~(\\n)$~', $text, $matches); + $text = $prefix.preg_replace('~([^\\n]{'.$width.'})\\ *~', "\$1\n", $text); + $text = rtrim($text, "\n").($matches[1] ?? ''); + + if (!$currentLineLength && '' !== $current && "\n" !== substr($current, -1)) { + $text = "\n".$text; + } + + $lines = explode("\n", $text); + + foreach ($lines as $line) { + $currentLineLength += \strlen($line); + if ($width <= $currentLineLength) { + $currentLineLength = 0; + } + } + + if ($this->isDecorated()) { + foreach ($lines as $i => $line) { + $lines[$i] = $this->styleStack->getCurrent()->apply($line); + } + } + + return implode("\n", $lines); + } +} diff --git a/vendor/symfony/console/Formatter/OutputFormatterInterface.php b/vendor/symfony/console/Formatter/OutputFormatterInterface.php new file mode 100644 index 0000000000..22f40a34ed --- /dev/null +++ b/vendor/symfony/console/Formatter/OutputFormatterInterface.php @@ -0,0 +1,70 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +/** + * Formatter interface for console output. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +interface OutputFormatterInterface +{ + /** + * Sets the decorated flag. + * + * @param bool $decorated Whether to decorate the messages or not + */ + public function setDecorated($decorated); + + /** + * Gets the decorated flag. + * + * @return bool true if the output will decorate messages, false otherwise + */ + public function isDecorated(); + + /** + * Sets a new style. + * + * @param string $name The style name + */ + public function setStyle($name, OutputFormatterStyleInterface $style); + + /** + * Checks if output formatter has style with specified name. + * + * @param string $name + * + * @return bool + */ + public function hasStyle($name); + + /** + * Gets style options from style with specified name. + * + * @param string $name + * + * @return OutputFormatterStyleInterface + * + * @throws \InvalidArgumentException When style isn't defined + */ + public function getStyle($name); + + /** + * Formats a message according to the given styles. + * + * @param string $message The message to style + * + * @return string The styled message + */ + public function format($message); +} diff --git a/vendor/symfony/console/Formatter/OutputFormatterStyle.php b/vendor/symfony/console/Formatter/OutputFormatterStyle.php new file mode 100644 index 0000000000..7cb6116b44 --- /dev/null +++ b/vendor/symfony/console/Formatter/OutputFormatterStyle.php @@ -0,0 +1,197 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * Formatter style class for defining styles. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +class OutputFormatterStyle implements OutputFormatterStyleInterface +{ + private static $availableForegroundColors = [ + 'black' => ['set' => 30, 'unset' => 39], + 'red' => ['set' => 31, 'unset' => 39], + 'green' => ['set' => 32, 'unset' => 39], + 'yellow' => ['set' => 33, 'unset' => 39], + 'blue' => ['set' => 34, 'unset' => 39], + 'magenta' => ['set' => 35, 'unset' => 39], + 'cyan' => ['set' => 36, 'unset' => 39], + 'white' => ['set' => 37, 'unset' => 39], + 'default' => ['set' => 39, 'unset' => 39], + ]; + private static $availableBackgroundColors = [ + 'black' => ['set' => 40, 'unset' => 49], + 'red' => ['set' => 41, 'unset' => 49], + 'green' => ['set' => 42, 'unset' => 49], + 'yellow' => ['set' => 43, 'unset' => 49], + 'blue' => ['set' => 44, 'unset' => 49], + 'magenta' => ['set' => 45, 'unset' => 49], + 'cyan' => ['set' => 46, 'unset' => 49], + 'white' => ['set' => 47, 'unset' => 49], + 'default' => ['set' => 49, 'unset' => 49], + ]; + private static $availableOptions = [ + 'bold' => ['set' => 1, 'unset' => 22], + 'underscore' => ['set' => 4, 'unset' => 24], + 'blink' => ['set' => 5, 'unset' => 25], + 'reverse' => ['set' => 7, 'unset' => 27], + 'conceal' => ['set' => 8, 'unset' => 28], + ]; + + private $foreground; + private $background; + private $href; + private $options = []; + private $handlesHrefGracefully; + + /** + * Initializes output formatter style. + * + * @param string|null $foreground The style foreground color name + * @param string|null $background The style background color name + */ + public function __construct(string $foreground = null, string $background = null, array $options = []) + { + if (null !== $foreground) { + $this->setForeground($foreground); + } + if (null !== $background) { + $this->setBackground($background); + } + if (\count($options)) { + $this->setOptions($options); + } + } + + /** + * {@inheritdoc} + */ + public function setForeground($color = null) + { + if (null === $color) { + $this->foreground = null; + + return; + } + + if (!isset(static::$availableForegroundColors[$color])) { + throw new InvalidArgumentException(sprintf('Invalid foreground color specified: "%s". Expected one of (%s).', $color, implode(', ', array_keys(static::$availableForegroundColors)))); + } + + $this->foreground = static::$availableForegroundColors[$color]; + } + + /** + * {@inheritdoc} + */ + public function setBackground($color = null) + { + if (null === $color) { + $this->background = null; + + return; + } + + if (!isset(static::$availableBackgroundColors[$color])) { + throw new InvalidArgumentException(sprintf('Invalid background color specified: "%s". Expected one of (%s).', $color, implode(', ', array_keys(static::$availableBackgroundColors)))); + } + + $this->background = static::$availableBackgroundColors[$color]; + } + + public function setHref(string $url): void + { + $this->href = $url; + } + + /** + * {@inheritdoc} + */ + public function setOption($option) + { + if (!isset(static::$availableOptions[$option])) { + throw new InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s).', $option, implode(', ', array_keys(static::$availableOptions)))); + } + + if (!\in_array(static::$availableOptions[$option], $this->options)) { + $this->options[] = static::$availableOptions[$option]; + } + } + + /** + * {@inheritdoc} + */ + public function unsetOption($option) + { + if (!isset(static::$availableOptions[$option])) { + throw new InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s).', $option, implode(', ', array_keys(static::$availableOptions)))); + } + + $pos = array_search(static::$availableOptions[$option], $this->options); + if (false !== $pos) { + unset($this->options[$pos]); + } + } + + /** + * {@inheritdoc} + */ + public function setOptions(array $options) + { + $this->options = []; + + foreach ($options as $option) { + $this->setOption($option); + } + } + + /** + * {@inheritdoc} + */ + public function apply($text) + { + $setCodes = []; + $unsetCodes = []; + + if (null === $this->handlesHrefGracefully) { + $this->handlesHrefGracefully = 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR') + && (!getenv('KONSOLE_VERSION') || (int) getenv('KONSOLE_VERSION') > 201100); + } + + if (null !== $this->foreground) { + $setCodes[] = $this->foreground['set']; + $unsetCodes[] = $this->foreground['unset']; + } + if (null !== $this->background) { + $setCodes[] = $this->background['set']; + $unsetCodes[] = $this->background['unset']; + } + + foreach ($this->options as $option) { + $setCodes[] = $option['set']; + $unsetCodes[] = $option['unset']; + } + + if (null !== $this->href && $this->handlesHrefGracefully) { + $text = "\033]8;;$this->href\033\\$text\033]8;;\033\\"; + } + + if (0 === \count($setCodes)) { + return $text; + } + + return sprintf("\033[%sm%s\033[%sm", implode(';', $setCodes), $text, implode(';', $unsetCodes)); + } +} diff --git a/vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php b/vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php new file mode 100644 index 0000000000..af171c2702 --- /dev/null +++ b/vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php @@ -0,0 +1,62 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +/** + * Formatter style interface for defining styles. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +interface OutputFormatterStyleInterface +{ + /** + * Sets style foreground color. + * + * @param string|null $color The color name + */ + public function setForeground($color = null); + + /** + * Sets style background color. + * + * @param string $color The color name + */ + public function setBackground($color = null); + + /** + * Sets some specific style option. + * + * @param string $option The option name + */ + public function setOption($option); + + /** + * Unsets some specific style option. + * + * @param string $option The option name + */ + public function unsetOption($option); + + /** + * Sets multiple style options at once. + */ + public function setOptions(array $options); + + /** + * Applies the style to a given text. + * + * @param string $text The text to style + * + * @return string + */ + public function apply($text); +} diff --git a/vendor/symfony/console/Formatter/OutputFormatterStyleStack.php b/vendor/symfony/console/Formatter/OutputFormatterStyleStack.php new file mode 100644 index 0000000000..fc48dc0e15 --- /dev/null +++ b/vendor/symfony/console/Formatter/OutputFormatterStyleStack.php @@ -0,0 +1,110 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Contracts\Service\ResetInterface; + +/** + * @author Jean-François Simon <contact@jfsimon.fr> + */ +class OutputFormatterStyleStack implements ResetInterface +{ + /** + * @var OutputFormatterStyleInterface[] + */ + private $styles; + + private $emptyStyle; + + public function __construct(OutputFormatterStyleInterface $emptyStyle = null) + { + $this->emptyStyle = $emptyStyle ?? new OutputFormatterStyle(); + $this->reset(); + } + + /** + * Resets stack (ie. empty internal arrays). + */ + public function reset() + { + $this->styles = []; + } + + /** + * Pushes a style in the stack. + */ + public function push(OutputFormatterStyleInterface $style) + { + $this->styles[] = $style; + } + + /** + * Pops a style from the stack. + * + * @return OutputFormatterStyleInterface + * + * @throws InvalidArgumentException When style tags incorrectly nested + */ + public function pop(OutputFormatterStyleInterface $style = null) + { + if (empty($this->styles)) { + return $this->emptyStyle; + } + + if (null === $style) { + return array_pop($this->styles); + } + + foreach (array_reverse($this->styles, true) as $index => $stackedStyle) { + if ($style->apply('') === $stackedStyle->apply('')) { + $this->styles = \array_slice($this->styles, 0, $index); + + return $stackedStyle; + } + } + + throw new InvalidArgumentException('Incorrectly nested style tag found.'); + } + + /** + * Computes current style with stacks top codes. + * + * @return OutputFormatterStyle + */ + public function getCurrent() + { + if (empty($this->styles)) { + return $this->emptyStyle; + } + + return $this->styles[\count($this->styles) - 1]; + } + + /** + * @return $this + */ + public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle) + { + $this->emptyStyle = $emptyStyle; + + return $this; + } + + /** + * @return OutputFormatterStyleInterface + */ + public function getEmptyStyle() + { + return $this->emptyStyle; + } +} diff --git a/vendor/symfony/console/Formatter/WrappableOutputFormatterInterface.php b/vendor/symfony/console/Formatter/WrappableOutputFormatterInterface.php new file mode 100644 index 0000000000..6694053f05 --- /dev/null +++ b/vendor/symfony/console/Formatter/WrappableOutputFormatterInterface.php @@ -0,0 +1,25 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +/** + * Formatter interface for console output that supports word wrapping. + * + * @author Roland Franssen <franssen.roland@gmail.com> + */ +interface WrappableOutputFormatterInterface extends OutputFormatterInterface +{ + /** + * Formats a message according to the given styles, wrapping at `$width` (0 means no wrapping). + */ + public function formatAndWrap(string $message, int $width); +} diff --git a/vendor/symfony/console/Helper/DebugFormatterHelper.php b/vendor/symfony/console/Helper/DebugFormatterHelper.php new file mode 100644 index 0000000000..1653edeb1f --- /dev/null +++ b/vendor/symfony/console/Helper/DebugFormatterHelper.php @@ -0,0 +1,122 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +/** + * Helps outputting debug information when running an external program from a command. + * + * An external program can be a Process, an HTTP request, or anything else. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class DebugFormatterHelper extends Helper +{ + private $colors = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'default']; + private $started = []; + private $count = -1; + + /** + * Starts a debug formatting session. + * + * @param string $id The id of the formatting session + * @param string $message The message to display + * @param string $prefix The prefix to use + * + * @return string + */ + public function start($id, $message, $prefix = 'RUN') + { + $this->started[$id] = ['border' => ++$this->count % \count($this->colors)]; + + return sprintf("%s<bg=blue;fg=white> %s </> <fg=blue>%s</>\n", $this->getBorder($id), $prefix, $message); + } + + /** + * Adds progress to a formatting session. + * + * @param string $id The id of the formatting session + * @param string $buffer The message to display + * @param bool $error Whether to consider the buffer as error + * @param string $prefix The prefix for output + * @param string $errorPrefix The prefix for error output + * + * @return string + */ + public function progress($id, $buffer, $error = false, $prefix = 'OUT', $errorPrefix = 'ERR') + { + $message = ''; + + if ($error) { + if (isset($this->started[$id]['out'])) { + $message .= "\n"; + unset($this->started[$id]['out']); + } + if (!isset($this->started[$id]['err'])) { + $message .= sprintf('%s<bg=red;fg=white> %s </> ', $this->getBorder($id), $errorPrefix); + $this->started[$id]['err'] = true; + } + + $message .= str_replace("\n", sprintf("\n%s<bg=red;fg=white> %s </> ", $this->getBorder($id), $errorPrefix), $buffer); + } else { + if (isset($this->started[$id]['err'])) { + $message .= "\n"; + unset($this->started[$id]['err']); + } + if (!isset($this->started[$id]['out'])) { + $message .= sprintf('%s<bg=green;fg=white> %s </> ', $this->getBorder($id), $prefix); + $this->started[$id]['out'] = true; + } + + $message .= str_replace("\n", sprintf("\n%s<bg=green;fg=white> %s </> ", $this->getBorder($id), $prefix), $buffer); + } + + return $message; + } + + /** + * Stops a formatting session. + * + * @param string $id The id of the formatting session + * @param string $message The message to display + * @param bool $successful Whether to consider the result as success + * @param string $prefix The prefix for the end output + * + * @return string + */ + public function stop($id, $message, $successful, $prefix = 'RES') + { + $trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : ''; + + if ($successful) { + return sprintf("%s%s<bg=green;fg=white> %s </> <fg=green>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message); + } + + $message = sprintf("%s%s<bg=red;fg=white> %s </> <fg=red>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message); + + unset($this->started[$id]['out'], $this->started[$id]['err']); + + return $message; + } + + private function getBorder(string $id): string + { + return sprintf('<bg=%s> </>', $this->colors[$this->started[$id]['border']]); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'debug_formatter'; + } +} diff --git a/vendor/symfony/console/Helper/DescriptorHelper.php b/vendor/symfony/console/Helper/DescriptorHelper.php new file mode 100644 index 0000000000..3055baefd4 --- /dev/null +++ b/vendor/symfony/console/Helper/DescriptorHelper.php @@ -0,0 +1,91 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Descriptor\DescriptorInterface; +use Symfony\Component\Console\Descriptor\JsonDescriptor; +use Symfony\Component\Console\Descriptor\MarkdownDescriptor; +use Symfony\Component\Console\Descriptor\TextDescriptor; +use Symfony\Component\Console\Descriptor\XmlDescriptor; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * This class adds helper method to describe objects in various formats. + * + * @author Jean-François Simon <contact@jfsimon.fr> + */ +class DescriptorHelper extends Helper +{ + /** + * @var DescriptorInterface[] + */ + private $descriptors = []; + + public function __construct() + { + $this + ->register('txt', new TextDescriptor()) + ->register('xml', new XmlDescriptor()) + ->register('json', new JsonDescriptor()) + ->register('md', new MarkdownDescriptor()) + ; + } + + /** + * Describes an object if supported. + * + * Available options are: + * * format: string, the output format name + * * raw_text: boolean, sets output type as raw + * + * @param object $object + * + * @throws InvalidArgumentException when the given format is not supported + */ + public function describe(OutputInterface $output, $object, array $options = []) + { + $options = array_merge([ + 'raw_text' => false, + 'format' => 'txt', + ], $options); + + if (!isset($this->descriptors[$options['format']])) { + throw new InvalidArgumentException(sprintf('Unsupported format "%s".', $options['format'])); + } + + $descriptor = $this->descriptors[$options['format']]; + $descriptor->describe($output, $object, $options); + } + + /** + * Registers a descriptor. + * + * @param string $format + * + * @return $this + */ + public function register($format, DescriptorInterface $descriptor) + { + $this->descriptors[$format] = $descriptor; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'descriptor'; + } +} diff --git a/vendor/symfony/console/Helper/Dumper.php b/vendor/symfony/console/Helper/Dumper.php new file mode 100644 index 0000000000..b013b6c527 --- /dev/null +++ b/vendor/symfony/console/Helper/Dumper.php @@ -0,0 +1,64 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\VarDumper\Cloner\ClonerInterface; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\CliDumper; + +/** + * @author Roland Franssen <franssen.roland@gmail.com> + */ +final class Dumper +{ + private $output; + private $dumper; + private $cloner; + private $handler; + + public function __construct(OutputInterface $output, CliDumper $dumper = null, ClonerInterface $cloner = null) + { + $this->output = $output; + $this->dumper = $dumper; + $this->cloner = $cloner; + + if (class_exists(CliDumper::class)) { + $this->handler = function ($var): string { + $dumper = $this->dumper ?? $this->dumper = new CliDumper(null, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR); + $dumper->setColors($this->output->isDecorated()); + + return rtrim($dumper->dump(($this->cloner ?? $this->cloner = new VarCloner())->cloneVar($var)->withRefHandles(false), true)); + }; + } else { + $this->handler = function ($var): string { + switch (true) { + case null === $var: + return 'null'; + case true === $var: + return 'true'; + case false === $var: + return 'false'; + case \is_string($var): + return '"'.$var.'"'; + default: + return rtrim(print_r($var, true)); + } + }; + } + } + + public function __invoke($var): string + { + return ($this->handler)($var); + } +} diff --git a/vendor/symfony/console/Helper/FormatterHelper.php b/vendor/symfony/console/Helper/FormatterHelper.php new file mode 100644 index 0000000000..d6eccee8e8 --- /dev/null +++ b/vendor/symfony/console/Helper/FormatterHelper.php @@ -0,0 +1,102 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Formatter\OutputFormatter; + +/** + * The Formatter class provides helpers to format messages. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class FormatterHelper extends Helper +{ + /** + * Formats a message within a section. + * + * @param string $section The section name + * @param string $message The message + * @param string $style The style to apply to the section + * + * @return string The format section + */ + public function formatSection($section, $message, $style = 'info') + { + return sprintf('<%s>[%s]</%s> %s', $style, $section, $style, $message); + } + + /** + * Formats a message as a block of text. + * + * @param string|array $messages The message to write in the block + * @param string $style The style to apply to the whole block + * @param bool $large Whether to return a large block + * + * @return string The formatter message + */ + public function formatBlock($messages, $style, $large = false) + { + if (!\is_array($messages)) { + $messages = [$messages]; + } + + $len = 0; + $lines = []; + foreach ($messages as $message) { + $message = OutputFormatter::escape($message); + $lines[] = sprintf($large ? ' %s ' : ' %s ', $message); + $len = max(self::strlen($message) + ($large ? 4 : 2), $len); + } + + $messages = $large ? [str_repeat(' ', $len)] : []; + for ($i = 0; isset($lines[$i]); ++$i) { + $messages[] = $lines[$i].str_repeat(' ', $len - self::strlen($lines[$i])); + } + if ($large) { + $messages[] = str_repeat(' ', $len); + } + + for ($i = 0; isset($messages[$i]); ++$i) { + $messages[$i] = sprintf('<%s>%s</%s>', $style, $messages[$i], $style); + } + + return implode("\n", $messages); + } + + /** + * Truncates a message to the given length. + * + * @param string $message + * @param int $length + * @param string $suffix + * + * @return string + */ + public function truncate($message, $length, $suffix = '...') + { + $computedLength = $length - self::strlen($suffix); + + if ($computedLength > self::strlen($message)) { + return $message; + } + + return self::substr($message, 0, $length).$suffix; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'formatter'; + } +} diff --git a/vendor/symfony/console/Helper/Helper.php b/vendor/symfony/console/Helper/Helper.php new file mode 100644 index 0000000000..0521aaf7d2 --- /dev/null +++ b/vendor/symfony/console/Helper/Helper.php @@ -0,0 +1,142 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * Helper is the base class for all helper classes. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +abstract class Helper implements HelperInterface +{ + protected $helperSet = null; + + /** + * {@inheritdoc} + */ + public function setHelperSet(HelperSet $helperSet = null) + { + $this->helperSet = $helperSet; + } + + /** + * {@inheritdoc} + */ + public function getHelperSet() + { + return $this->helperSet; + } + + /** + * Returns the length of a string, using mb_strwidth if it is available. + * + * @param string $string The string to check its length + * + * @return int The length of the string + */ + public static function strlen($string) + { + $string = (string) $string; + + if (false === $encoding = mb_detect_encoding($string, null, true)) { + return \strlen($string); + } + + return mb_strwidth($string, $encoding); + } + + /** + * Returns the subset of a string, using mb_substr if it is available. + * + * @param string $string String to subset + * @param int $from Start offset + * @param int|null $length Length to read + * + * @return string The string subset + */ + public static function substr($string, $from, $length = null) + { + $string = (string) $string; + + if (false === $encoding = mb_detect_encoding($string, null, true)) { + return substr($string, $from, $length); + } + + return mb_substr($string, $from, $length, $encoding); + } + + public static function formatTime($secs) + { + static $timeFormats = [ + [0, '< 1 sec'], + [1, '1 sec'], + [2, 'secs', 1], + [60, '1 min'], + [120, 'mins', 60], + [3600, '1 hr'], + [7200, 'hrs', 3600], + [86400, '1 day'], + [172800, 'days', 86400], + ]; + + foreach ($timeFormats as $index => $format) { + if ($secs >= $format[0]) { + if ((isset($timeFormats[$index + 1]) && $secs < $timeFormats[$index + 1][0]) + || $index == \count($timeFormats) - 1 + ) { + if (2 == \count($format)) { + return $format[1]; + } + + return floor($secs / $format[2]).' '.$format[1]; + } + } + } + } + + public static function formatMemory($memory) + { + if ($memory >= 1024 * 1024 * 1024) { + return sprintf('%.1f GiB', $memory / 1024 / 1024 / 1024); + } + + if ($memory >= 1024 * 1024) { + return sprintf('%.1f MiB', $memory / 1024 / 1024); + } + + if ($memory >= 1024) { + return sprintf('%d KiB', $memory / 1024); + } + + return sprintf('%d B', $memory); + } + + public static function strlenWithoutDecoration(OutputFormatterInterface $formatter, $string) + { + return self::strlen(self::removeDecoration($formatter, $string)); + } + + public static function removeDecoration(OutputFormatterInterface $formatter, $string) + { + $isDecorated = $formatter->isDecorated(); + $formatter->setDecorated(false); + // remove <...> formatting + $string = $formatter->format($string); + // remove already formatted characters + $string = preg_replace("/\033\[[^m]*m/", '', $string); + $formatter->setDecorated($isDecorated); + + return $string; + } +} diff --git a/vendor/symfony/console/Helper/HelperInterface.php b/vendor/symfony/console/Helper/HelperInterface.php new file mode 100644 index 0000000000..1ce823587e --- /dev/null +++ b/vendor/symfony/console/Helper/HelperInterface.php @@ -0,0 +1,39 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +/** + * HelperInterface is the interface all helpers must implement. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +interface HelperInterface +{ + /** + * Sets the helper set associated with this helper. + */ + public function setHelperSet(HelperSet $helperSet = null); + + /** + * Gets the helper set associated with this helper. + * + * @return HelperSet A HelperSet instance + */ + public function getHelperSet(); + + /** + * Returns the canonical name of this helper. + * + * @return string The canonical name + */ + public function getName(); +} diff --git a/vendor/symfony/console/Helper/HelperSet.php b/vendor/symfony/console/Helper/HelperSet.php new file mode 100644 index 0000000000..9aa1e67ba8 --- /dev/null +++ b/vendor/symfony/console/Helper/HelperSet.php @@ -0,0 +1,108 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * HelperSet represents a set of helpers to be used with a command. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class HelperSet implements \IteratorAggregate +{ + /** + * @var Helper[] + */ + private $helpers = []; + private $command; + + /** + * @param Helper[] $helpers An array of helper + */ + public function __construct(array $helpers = []) + { + foreach ($helpers as $alias => $helper) { + $this->set($helper, \is_int($alias) ? null : $alias); + } + } + + /** + * Sets a helper. + * + * @param string $alias An alias + */ + public function set(HelperInterface $helper, $alias = null) + { + $this->helpers[$helper->getName()] = $helper; + if (null !== $alias) { + $this->helpers[$alias] = $helper; + } + + $helper->setHelperSet($this); + } + + /** + * Returns true if the helper if defined. + * + * @param string $name The helper name + * + * @return bool true if the helper is defined, false otherwise + */ + public function has($name) + { + return isset($this->helpers[$name]); + } + + /** + * Gets a helper value. + * + * @param string $name The helper name + * + * @return HelperInterface The helper instance + * + * @throws InvalidArgumentException if the helper is not defined + */ + public function get($name) + { + if (!$this->has($name)) { + throw new InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name)); + } + + return $this->helpers[$name]; + } + + public function setCommand(Command $command = null) + { + $this->command = $command; + } + + /** + * Gets the command associated with this helper set. + * + * @return Command A Command instance + */ + public function getCommand() + { + return $this->command; + } + + /** + * @return \Traversable<Helper> + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + return new \ArrayIterator($this->helpers); + } +} diff --git a/vendor/symfony/console/Helper/InputAwareHelper.php b/vendor/symfony/console/Helper/InputAwareHelper.php new file mode 100644 index 0000000000..0d0dba23e3 --- /dev/null +++ b/vendor/symfony/console/Helper/InputAwareHelper.php @@ -0,0 +1,33 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Input\InputAwareInterface; +use Symfony\Component\Console\Input\InputInterface; + +/** + * An implementation of InputAwareInterface for Helpers. + * + * @author Wouter J <waldio.webdesign@gmail.com> + */ +abstract class InputAwareHelper extends Helper implements InputAwareInterface +{ + protected $input; + + /** + * {@inheritdoc} + */ + public function setInput(InputInterface $input) + { + $this->input = $input; + } +} diff --git a/vendor/symfony/console/Helper/ProcessHelper.php b/vendor/symfony/console/Helper/ProcessHelper.php new file mode 100644 index 0000000000..862d09f214 --- /dev/null +++ b/vendor/symfony/console/Helper/ProcessHelper.php @@ -0,0 +1,154 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Process\Exception\ProcessFailedException; +use Symfony\Component\Process\Process; + +/** + * The ProcessHelper class provides helpers to run external processes. + * + * @author Fabien Potencier <fabien@symfony.com> + * + * @final since Symfony 4.2 + */ +class ProcessHelper extends Helper +{ + /** + * Runs an external process. + * + * @param array|Process $cmd An instance of Process or an array of the command and arguments + * @param string|null $error An error message that must be displayed if something went wrong + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * @param int $verbosity The threshold for verbosity + * + * @return Process The process that ran + */ + public function run(OutputInterface $output, $cmd, $error = null, callable $callback = null, $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE) + { + if (!class_exists(Process::class)) { + throw new \LogicException('The ProcessHelper cannot be run as the Process component is not installed. Try running "compose require symfony/process".'); + } + + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + $formatter = $this->getHelperSet()->get('debug_formatter'); + + if ($cmd instanceof Process) { + $cmd = [$cmd]; + } + + if (!\is_array($cmd)) { + @trigger_error(sprintf('Passing a command as a string to "%s()" is deprecated since Symfony 4.2, pass it the command as an array of arguments instead.', __METHOD__), \E_USER_DEPRECATED); + $cmd = [method_exists(Process::class, 'fromShellCommandline') ? Process::fromShellCommandline($cmd) : new Process($cmd)]; + } + + if (\is_string($cmd[0] ?? null)) { + $process = new Process($cmd); + $cmd = []; + } elseif (($cmd[0] ?? null) instanceof Process) { + $process = $cmd[0]; + unset($cmd[0]); + } else { + throw new \InvalidArgumentException(sprintf('Invalid command provided to "%s()": the command should be an array whose first element is either the path to the binary to run or a "Process" object.', __METHOD__)); + } + + if ($verbosity <= $output->getVerbosity()) { + $output->write($formatter->start(spl_object_hash($process), $this->escapeString($process->getCommandLine()))); + } + + if ($output->isDebug()) { + $callback = $this->wrapCallback($output, $process, $callback); + } + + $process->run($callback, $cmd); + + if ($verbosity <= $output->getVerbosity()) { + $message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run successfully', $process->getExitCode()); + $output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful())); + } + + if (!$process->isSuccessful() && null !== $error) { + $output->writeln(sprintf('<error>%s</error>', $this->escapeString($error))); + } + + return $process; + } + + /** + * Runs the process. + * + * This is identical to run() except that an exception is thrown if the process + * exits with a non-zero exit code. + * + * @param array|Process $cmd An instance of Process or a command to run + * @param string|null $error An error message that must be displayed if something went wrong + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * + * @return Process The process that ran + * + * @throws ProcessFailedException + * + * @see run() + */ + public function mustRun(OutputInterface $output, $cmd, $error = null, callable $callback = null) + { + $process = $this->run($output, $cmd, $error, $callback); + + if (!$process->isSuccessful()) { + throw new ProcessFailedException($process); + } + + return $process; + } + + /** + * Wraps a Process callback to add debugging output. + * + * @return callable + */ + public function wrapCallback(OutputInterface $output, Process $process, callable $callback = null) + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + $formatter = $this->getHelperSet()->get('debug_formatter'); + + return function ($type, $buffer) use ($output, $process, $callback, $formatter) { + $output->write($formatter->progress(spl_object_hash($process), $this->escapeString($buffer), Process::ERR === $type)); + + if (null !== $callback) { + $callback($type, $buffer); + } + }; + } + + private function escapeString(string $str): string + { + return str_replace('<', '\\<', $str); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'process'; + } +} diff --git a/vendor/symfony/console/Helper/ProgressBar.php b/vendor/symfony/console/Helper/ProgressBar.php new file mode 100644 index 0000000000..1de9b7b3cf --- /dev/null +++ b/vendor/symfony/console/Helper/ProgressBar.php @@ -0,0 +1,599 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\ConsoleSectionOutput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Terminal; + +/** + * The ProgressBar provides helpers to display progress output. + * + * @author Fabien Potencier <fabien@symfony.com> + * @author Chris Jones <leeked@gmail.com> + */ +final class ProgressBar +{ + private $barWidth = 28; + private $barChar; + private $emptyBarChar = '-'; + private $progressChar = '>'; + private $format; + private $internalFormat; + private $redrawFreq = 1; + private $writeCount; + private $lastWriteTime; + private $minSecondsBetweenRedraws = 0; + private $maxSecondsBetweenRedraws = 1; + private $output; + private $step = 0; + private $max; + private $startTime; + private $stepWidth; + private $percent = 0.0; + private $formatLineCount; + private $messages = []; + private $overwrite = true; + private $terminal; + private $previousMessage; + + private static $formatters; + private static $formats; + + /** + * @param int $max Maximum steps (0 if unknown) + */ + public function __construct(OutputInterface $output, int $max = 0, float $minSecondsBetweenRedraws = 0.1) + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + $this->output = $output; + $this->setMaxSteps($max); + $this->terminal = new Terminal(); + + if (0 < $minSecondsBetweenRedraws) { + $this->redrawFreq = null; + $this->minSecondsBetweenRedraws = $minSecondsBetweenRedraws; + } + + if (!$this->output->isDecorated()) { + // disable overwrite when output does not support ANSI codes. + $this->overwrite = false; + + // set a reasonable redraw frequency so output isn't flooded + $this->redrawFreq = null; + } + + $this->startTime = time(); + } + + /** + * Sets a placeholder formatter for a given name. + * + * This method also allow you to override an existing placeholder. + * + * @param string $name The placeholder name (including the delimiter char like %) + * @param callable $callable A PHP callable + */ + public static function setPlaceholderFormatterDefinition(string $name, callable $callable): void + { + if (!self::$formatters) { + self::$formatters = self::initPlaceholderFormatters(); + } + + self::$formatters[$name] = $callable; + } + + /** + * Gets the placeholder formatter for a given name. + * + * @param string $name The placeholder name (including the delimiter char like %) + * + * @return callable|null A PHP callable + */ + public static function getPlaceholderFormatterDefinition(string $name): ?callable + { + if (!self::$formatters) { + self::$formatters = self::initPlaceholderFormatters(); + } + + return self::$formatters[$name] ?? null; + } + + /** + * Sets a format for a given name. + * + * This method also allow you to override an existing format. + * + * @param string $name The format name + * @param string $format A format string + */ + public static function setFormatDefinition(string $name, string $format): void + { + if (!self::$formats) { + self::$formats = self::initFormats(); + } + + self::$formats[$name] = $format; + } + + /** + * Gets the format for a given name. + * + * @param string $name The format name + * + * @return string|null A format string + */ + public static function getFormatDefinition(string $name): ?string + { + if (!self::$formats) { + self::$formats = self::initFormats(); + } + + return self::$formats[$name] ?? null; + } + + /** + * Associates a text with a named placeholder. + * + * The text is displayed when the progress bar is rendered but only + * when the corresponding placeholder is part of the custom format line + * (by wrapping the name with %). + * + * @param string $message The text to associate with the placeholder + * @param string $name The name of the placeholder + */ + public function setMessage(string $message, string $name = 'message') + { + $this->messages[$name] = $message; + } + + public function getMessage(string $name = 'message') + { + return $this->messages[$name]; + } + + public function getStartTime(): int + { + return $this->startTime; + } + + public function getMaxSteps(): int + { + return $this->max; + } + + public function getProgress(): int + { + return $this->step; + } + + private function getStepWidth(): int + { + return $this->stepWidth; + } + + public function getProgressPercent(): float + { + return $this->percent; + } + + public function getBarOffset(): int + { + return floor($this->max ? $this->percent * $this->barWidth : (null === $this->redrawFreq ? (int) (min(5, $this->barWidth / 15) * $this->writeCount) : $this->step) % $this->barWidth); + } + + public function setBarWidth(int $size) + { + $this->barWidth = max(1, $size); + } + + public function getBarWidth(): int + { + return $this->barWidth; + } + + public function setBarCharacter(string $char) + { + $this->barChar = $char; + } + + public function getBarCharacter(): string + { + if (null === $this->barChar) { + return $this->max ? '=' : $this->emptyBarChar; + } + + return $this->barChar; + } + + public function setEmptyBarCharacter(string $char) + { + $this->emptyBarChar = $char; + } + + public function getEmptyBarCharacter(): string + { + return $this->emptyBarChar; + } + + public function setProgressCharacter(string $char) + { + $this->progressChar = $char; + } + + public function getProgressCharacter(): string + { + return $this->progressChar; + } + + public function setFormat(string $format) + { + $this->format = null; + $this->internalFormat = $format; + } + + /** + * Sets the redraw frequency. + * + * @param int|null $freq The frequency in steps + */ + public function setRedrawFrequency(?int $freq) + { + $this->redrawFreq = null !== $freq ? max(1, $freq) : null; + } + + public function minSecondsBetweenRedraws(float $seconds): void + { + $this->minSecondsBetweenRedraws = $seconds; + } + + public function maxSecondsBetweenRedraws(float $seconds): void + { + $this->maxSecondsBetweenRedraws = $seconds; + } + + /** + * Returns an iterator that will automatically update the progress bar when iterated. + * + * @param int|null $max Number of steps to complete the bar (0 if indeterminate), if null it will be inferred from $iterable + */ + public function iterate(iterable $iterable, int $max = null): iterable + { + $this->start($max ?? (is_countable($iterable) ? \count($iterable) : 0)); + + foreach ($iterable as $key => $value) { + yield $key => $value; + + $this->advance(); + } + + $this->finish(); + } + + /** + * Starts the progress output. + * + * @param int|null $max Number of steps to complete the bar (0 if indeterminate), null to leave unchanged + */ + public function start(int $max = null) + { + $this->startTime = time(); + $this->step = 0; + $this->percent = 0.0; + + if (null !== $max) { + $this->setMaxSteps($max); + } + + $this->display(); + } + + /** + * Advances the progress output X steps. + * + * @param int $step Number of steps to advance + */ + public function advance(int $step = 1) + { + $this->setProgress($this->step + $step); + } + + /** + * Sets whether to overwrite the progressbar, false for new line. + */ + public function setOverwrite(bool $overwrite) + { + $this->overwrite = $overwrite; + } + + public function setProgress(int $step) + { + if ($this->max && $step > $this->max) { + $this->max = $step; + } elseif ($step < 0) { + $step = 0; + } + + $redrawFreq = $this->redrawFreq ?? (($this->max ?: 10) / 10); + $prevPeriod = (int) ($this->step / $redrawFreq); + $currPeriod = (int) ($step / $redrawFreq); + $this->step = $step; + $this->percent = $this->max ? (float) $this->step / $this->max : 0; + $timeInterval = microtime(true) - $this->lastWriteTime; + + // Draw regardless of other limits + if ($this->max === $step) { + $this->display(); + + return; + } + + // Throttling + if ($timeInterval < $this->minSecondsBetweenRedraws) { + return; + } + + // Draw each step period, but not too late + if ($prevPeriod !== $currPeriod || $timeInterval >= $this->maxSecondsBetweenRedraws) { + $this->display(); + } + } + + public function setMaxSteps(int $max) + { + $this->format = null; + $this->max = max(0, $max); + $this->stepWidth = $this->max ? Helper::strlen((string) $this->max) : 4; + } + + /** + * Finishes the progress output. + */ + public function finish(): void + { + if (!$this->max) { + $this->max = $this->step; + } + + if ($this->step === $this->max && !$this->overwrite) { + // prevent double 100% output + return; + } + + $this->setProgress($this->max); + } + + /** + * Outputs the current progress string. + */ + public function display(): void + { + if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) { + return; + } + + if (null === $this->format) { + $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat()); + } + + $this->overwrite($this->buildLine()); + } + + /** + * Removes the progress bar from the current line. + * + * This is useful if you wish to write some output + * while a progress bar is running. + * Call display() to show the progress bar again. + */ + public function clear(): void + { + if (!$this->overwrite) { + return; + } + + if (null === $this->format) { + $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat()); + } + + $this->overwrite(''); + } + + private function setRealFormat(string $format) + { + // try to use the _nomax variant if available + if (!$this->max && null !== self::getFormatDefinition($format.'_nomax')) { + $this->format = self::getFormatDefinition($format.'_nomax'); + } elseif (null !== self::getFormatDefinition($format)) { + $this->format = self::getFormatDefinition($format); + } else { + $this->format = $format; + } + + $this->formatLineCount = substr_count($this->format, "\n"); + } + + /** + * Overwrites a previous message to the output. + */ + private function overwrite(string $message): void + { + if ($this->previousMessage === $message) { + return; + } + + $originalMessage = $message; + + if ($this->overwrite) { + if (null !== $this->previousMessage) { + if ($this->output instanceof ConsoleSectionOutput) { + $messageLines = explode("\n", $message); + $lineCount = \count($messageLines); + foreach ($messageLines as $messageLine) { + $messageLineLength = Helper::strlenWithoutDecoration($this->output->getFormatter(), $messageLine); + if ($messageLineLength > $this->terminal->getWidth()) { + $lineCount += floor($messageLineLength / $this->terminal->getWidth()); + } + } + $this->output->clear($lineCount); + } else { + // Erase previous lines + if ($this->formatLineCount > 0) { + $message = str_repeat("\x1B[1A\x1B[2K", $this->formatLineCount).$message; + } + + // Move the cursor to the beginning of the line and erase the line + $message = "\x0D\x1B[2K$message"; + } + } + } elseif ($this->step > 0) { + $message = \PHP_EOL.$message; + } + + $this->previousMessage = $originalMessage; + $this->lastWriteTime = microtime(true); + + $this->output->write($message); + ++$this->writeCount; + } + + private function determineBestFormat(): string + { + switch ($this->output->getVerbosity()) { + // OutputInterface::VERBOSITY_QUIET: display is disabled anyway + case OutputInterface::VERBOSITY_VERBOSE: + return $this->max ? 'verbose' : 'verbose_nomax'; + case OutputInterface::VERBOSITY_VERY_VERBOSE: + return $this->max ? 'very_verbose' : 'very_verbose_nomax'; + case OutputInterface::VERBOSITY_DEBUG: + return $this->max ? 'debug' : 'debug_nomax'; + default: + return $this->max ? 'normal' : 'normal_nomax'; + } + } + + private static function initPlaceholderFormatters(): array + { + return [ + 'bar' => function (self $bar, OutputInterface $output) { + $completeBars = $bar->getBarOffset(); + $display = str_repeat($bar->getBarCharacter(), $completeBars); + if ($completeBars < $bar->getBarWidth()) { + $emptyBars = $bar->getBarWidth() - $completeBars - Helper::strlenWithoutDecoration($output->getFormatter(), $bar->getProgressCharacter()); + $display .= $bar->getProgressCharacter().str_repeat($bar->getEmptyBarCharacter(), $emptyBars); + } + + return $display; + }, + 'elapsed' => function (self $bar) { + return Helper::formatTime(time() - $bar->getStartTime()); + }, + 'remaining' => function (self $bar) { + if (!$bar->getMaxSteps()) { + throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.'); + } + + if (!$bar->getProgress()) { + $remaining = 0; + } else { + $remaining = round((time() - $bar->getStartTime()) / $bar->getProgress() * ($bar->getMaxSteps() - $bar->getProgress())); + } + + return Helper::formatTime($remaining); + }, + 'estimated' => function (self $bar) { + if (!$bar->getMaxSteps()) { + throw new LogicException('Unable to display the estimated time if the maximum number of steps is not set.'); + } + + if (!$bar->getProgress()) { + $estimated = 0; + } else { + $estimated = round((time() - $bar->getStartTime()) / $bar->getProgress() * $bar->getMaxSteps()); + } + + return Helper::formatTime($estimated); + }, + 'memory' => function (self $bar) { + return Helper::formatMemory(memory_get_usage(true)); + }, + 'current' => function (self $bar) { + return str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', \STR_PAD_LEFT); + }, + 'max' => function (self $bar) { + return $bar->getMaxSteps(); + }, + 'percent' => function (self $bar) { + return floor($bar->getProgressPercent() * 100); + }, + ]; + } + + private static function initFormats(): array + { + return [ + 'normal' => ' %current%/%max% [%bar%] %percent:3s%%', + 'normal_nomax' => ' %current% [%bar%]', + + 'verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%', + 'verbose_nomax' => ' %current% [%bar%] %elapsed:6s%', + + 'very_verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%', + 'very_verbose_nomax' => ' %current% [%bar%] %elapsed:6s%', + + 'debug' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%', + 'debug_nomax' => ' %current% [%bar%] %elapsed:6s% %memory:6s%', + ]; + } + + private function buildLine(): string + { + $regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i"; + $callback = function ($matches) { + if ($formatter = $this::getPlaceholderFormatterDefinition($matches[1])) { + $text = $formatter($this, $this->output); + } elseif (isset($this->messages[$matches[1]])) { + $text = $this->messages[$matches[1]]; + } else { + return $matches[0]; + } + + if (isset($matches[2])) { + $text = sprintf('%'.$matches[2], $text); + } + + return $text; + }; + $line = preg_replace_callback($regex, $callback, $this->format); + + // gets string length for each sub line with multiline format + $linesLength = array_map(function ($subLine) { + return Helper::strlenWithoutDecoration($this->output->getFormatter(), rtrim($subLine, "\r")); + }, explode("\n", $line)); + + $linesWidth = max($linesLength); + + $terminalWidth = $this->terminal->getWidth(); + if ($linesWidth <= $terminalWidth) { + return $line; + } + + $this->setBarWidth($this->barWidth - $linesWidth + $terminalWidth); + + return preg_replace_callback($regex, $callback, $this->format); + } +} diff --git a/vendor/symfony/console/Helper/ProgressIndicator.php b/vendor/symfony/console/Helper/ProgressIndicator.php new file mode 100644 index 0000000000..dc37148ed8 --- /dev/null +++ b/vendor/symfony/console/Helper/ProgressIndicator.php @@ -0,0 +1,266 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Kevin Bond <kevinbond@gmail.com> + */ +class ProgressIndicator +{ + private $output; + private $startTime; + private $format; + private $message; + private $indicatorValues; + private $indicatorCurrent; + private $indicatorChangeInterval; + private $indicatorUpdateTime; + private $started = false; + + private static $formatters; + private static $formats; + + /** + * @param string|null $format Indicator format + * @param int $indicatorChangeInterval Change interval in milliseconds + * @param array|null $indicatorValues Animated indicator characters + */ + public function __construct(OutputInterface $output, string $format = null, int $indicatorChangeInterval = 100, array $indicatorValues = null) + { + $this->output = $output; + + if (null === $format) { + $format = $this->determineBestFormat(); + } + + if (null === $indicatorValues) { + $indicatorValues = ['-', '\\', '|', '/']; + } + + $indicatorValues = array_values($indicatorValues); + + if (2 > \count($indicatorValues)) { + throw new InvalidArgumentException('Must have at least 2 indicator value characters.'); + } + + $this->format = self::getFormatDefinition($format); + $this->indicatorChangeInterval = $indicatorChangeInterval; + $this->indicatorValues = $indicatorValues; + $this->startTime = time(); + } + + /** + * Sets the current indicator message. + * + * @param string|null $message + */ + public function setMessage($message) + { + $this->message = $message; + + $this->display(); + } + + /** + * Starts the indicator output. + * + * @param $message + */ + public function start($message) + { + if ($this->started) { + throw new LogicException('Progress indicator already started.'); + } + + $this->message = $message; + $this->started = true; + $this->startTime = time(); + $this->indicatorUpdateTime = $this->getCurrentTimeInMilliseconds() + $this->indicatorChangeInterval; + $this->indicatorCurrent = 0; + + $this->display(); + } + + /** + * Advances the indicator. + */ + public function advance() + { + if (!$this->started) { + throw new LogicException('Progress indicator has not yet been started.'); + } + + if (!$this->output->isDecorated()) { + return; + } + + $currentTime = $this->getCurrentTimeInMilliseconds(); + + if ($currentTime < $this->indicatorUpdateTime) { + return; + } + + $this->indicatorUpdateTime = $currentTime + $this->indicatorChangeInterval; + ++$this->indicatorCurrent; + + $this->display(); + } + + /** + * Finish the indicator with message. + * + * @param $message + */ + public function finish($message) + { + if (!$this->started) { + throw new LogicException('Progress indicator has not yet been started.'); + } + + $this->message = $message; + $this->display(); + $this->output->writeln(''); + $this->started = false; + } + + /** + * Gets the format for a given name. + * + * @param string $name The format name + * + * @return string|null A format string + */ + public static function getFormatDefinition($name) + { + if (!self::$formats) { + self::$formats = self::initFormats(); + } + + return self::$formats[$name] ?? null; + } + + /** + * Sets a placeholder formatter for a given name. + * + * This method also allow you to override an existing placeholder. + * + * @param string $name The placeholder name (including the delimiter char like %) + * @param callable $callable A PHP callable + */ + public static function setPlaceholderFormatterDefinition($name, $callable) + { + if (!self::$formatters) { + self::$formatters = self::initPlaceholderFormatters(); + } + + self::$formatters[$name] = $callable; + } + + /** + * Gets the placeholder formatter for a given name. + * + * @param string $name The placeholder name (including the delimiter char like %) + * + * @return callable|null A PHP callable + */ + public static function getPlaceholderFormatterDefinition($name) + { + if (!self::$formatters) { + self::$formatters = self::initPlaceholderFormatters(); + } + + return self::$formatters[$name] ?? null; + } + + private function display() + { + if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) { + return; + } + + $this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) { + if ($formatter = self::getPlaceholderFormatterDefinition($matches[1])) { + return $formatter($this); + } + + return $matches[0]; + }, $this->format ?? '')); + } + + private function determineBestFormat(): string + { + switch ($this->output->getVerbosity()) { + // OutputInterface::VERBOSITY_QUIET: display is disabled anyway + case OutputInterface::VERBOSITY_VERBOSE: + return $this->output->isDecorated() ? 'verbose' : 'verbose_no_ansi'; + case OutputInterface::VERBOSITY_VERY_VERBOSE: + case OutputInterface::VERBOSITY_DEBUG: + return $this->output->isDecorated() ? 'very_verbose' : 'very_verbose_no_ansi'; + default: + return $this->output->isDecorated() ? 'normal' : 'normal_no_ansi'; + } + } + + /** + * Overwrites a previous message to the output. + */ + private function overwrite(string $message) + { + if ($this->output->isDecorated()) { + $this->output->write("\x0D\x1B[2K"); + $this->output->write($message); + } else { + $this->output->writeln($message); + } + } + + private function getCurrentTimeInMilliseconds(): float + { + return round(microtime(true) * 1000); + } + + private static function initPlaceholderFormatters(): array + { + return [ + 'indicator' => function (self $indicator) { + return $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)]; + }, + 'message' => function (self $indicator) { + return $indicator->message; + }, + 'elapsed' => function (self $indicator) { + return Helper::formatTime(time() - $indicator->startTime); + }, + 'memory' => function () { + return Helper::formatMemory(memory_get_usage(true)); + }, + ]; + } + + private static function initFormats(): array + { + return [ + 'normal' => ' %indicator% %message%', + 'normal_no_ansi' => ' %message%', + + 'verbose' => ' %indicator% %message% (%elapsed:6s%)', + 'verbose_no_ansi' => ' %message% (%elapsed:6s%)', + + 'very_verbose' => ' %indicator% %message% (%elapsed:6s%, %memory:6s%)', + 'very_verbose_no_ansi' => ' %message% (%elapsed:6s%, %memory:6s%)', + ]; + } +} diff --git a/vendor/symfony/console/Helper/QuestionHelper.php b/vendor/symfony/console/Helper/QuestionHelper.php new file mode 100644 index 0000000000..a4754b8245 --- /dev/null +++ b/vendor/symfony/console/Helper/QuestionHelper.php @@ -0,0 +1,540 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\MissingInputException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\StreamableInputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\ConsoleSectionOutput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ChoiceQuestion; +use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Terminal; + +/** + * The QuestionHelper class provides helpers to interact with the user. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class QuestionHelper extends Helper +{ + private $inputStream; + private static $shell; + private static $stty = true; + private static $stdinIsInteractive; + + /** + * Asks a question to the user. + * + * @return mixed The user answer + * + * @throws RuntimeException If there is no data to read in the input stream + */ + public function ask(InputInterface $input, OutputInterface $output, Question $question) + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + if (!$input->isInteractive()) { + return $this->getDefaultAnswer($question); + } + + if ($input instanceof StreamableInputInterface && $stream = $input->getStream()) { + $this->inputStream = $stream; + } + + try { + if (!$question->getValidator()) { + return $this->doAsk($output, $question); + } + + $interviewer = function () use ($output, $question) { + return $this->doAsk($output, $question); + }; + + return $this->validateAttempts($interviewer, $output, $question); + } catch (MissingInputException $exception) { + $input->setInteractive(false); + + if (null === $fallbackOutput = $this->getDefaultAnswer($question)) { + throw $exception; + } + + return $fallbackOutput; + } + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'question'; + } + + /** + * Prevents usage of stty. + */ + public static function disableStty() + { + self::$stty = false; + } + + /** + * Asks the question to the user. + * + * @return mixed + * + * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden + */ + private function doAsk(OutputInterface $output, Question $question) + { + $this->writePrompt($output, $question); + + $inputStream = $this->inputStream ?: \STDIN; + $autocomplete = $question->getAutocompleterCallback(); + + if (null === $autocomplete || !self::$stty || !Terminal::hasSttyAvailable()) { + $ret = false; + if ($question->isHidden()) { + try { + $hiddenResponse = $this->getHiddenResponse($output, $inputStream, $question->isTrimmable()); + $ret = $question->isTrimmable() ? trim($hiddenResponse) : $hiddenResponse; + } catch (RuntimeException $e) { + if (!$question->isHiddenFallback()) { + throw $e; + } + } + } + + if (false === $ret) { + $cp = $this->setIOCodepage(); + $ret = fgets($inputStream, 4096); + $ret = $this->resetIOCodepage($cp, $ret); + if (false === $ret) { + throw new MissingInputException('Aborted.'); + } + if ($question->isTrimmable()) { + $ret = trim($ret); + } + } + } else { + $autocomplete = $this->autocomplete($output, $question, $inputStream, $autocomplete); + $ret = $question->isTrimmable() ? trim($autocomplete) : $autocomplete; + } + + if ($output instanceof ConsoleSectionOutput) { + $output->addContent($ret); + } + + $ret = \strlen($ret) > 0 ? $ret : $question->getDefault(); + + if ($normalizer = $question->getNormalizer()) { + return $normalizer($ret); + } + + return $ret; + } + + /** + * @return mixed + */ + private function getDefaultAnswer(Question $question) + { + $default = $question->getDefault(); + + if (null === $default) { + return $default; + } + + if ($validator = $question->getValidator()) { + return \call_user_func($question->getValidator(), $default); + } elseif ($question instanceof ChoiceQuestion) { + $choices = $question->getChoices(); + + if (!$question->isMultiselect()) { + return $choices[$default] ?? $default; + } + + $default = explode(',', $default); + foreach ($default as $k => $v) { + $v = $question->isTrimmable() ? trim($v) : $v; + $default[$k] = $choices[$v] ?? $v; + } + } + + return $default; + } + + /** + * Outputs the question prompt. + */ + protected function writePrompt(OutputInterface $output, Question $question) + { + $message = $question->getQuestion(); + + if ($question instanceof ChoiceQuestion) { + $output->writeln(array_merge([ + $question->getQuestion(), + ], $this->formatChoiceQuestionChoices($question, 'info'))); + + $message = $question->getPrompt(); + } + + $output->write($message); + } + + /** + * @param string $tag + * + * @return string[] + */ + protected function formatChoiceQuestionChoices(ChoiceQuestion $question, $tag) + { + $messages = []; + + $maxWidth = max(array_map([__CLASS__, 'strlen'], array_keys($choices = $question->getChoices()))); + + foreach ($choices as $key => $value) { + $padding = str_repeat(' ', $maxWidth - self::strlen($key)); + + $messages[] = sprintf(" [<$tag>%s$padding</$tag>] %s", $key, $value); + } + + return $messages; + } + + /** + * Outputs an error message. + */ + protected function writeError(OutputInterface $output, \Exception $error) + { + if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) { + $message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error'); + } else { + $message = '<error>'.$error->getMessage().'</error>'; + } + + $output->writeln($message); + } + + /** + * Autocompletes a question. + * + * @param resource $inputStream + */ + private function autocomplete(OutputInterface $output, Question $question, $inputStream, callable $autocomplete): string + { + $fullChoice = ''; + $ret = ''; + + $i = 0; + $ofs = -1; + $matches = $autocomplete($ret); + $numMatches = \count($matches); + + $sttyMode = shell_exec('stty -g'); + + // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead) + shell_exec('stty -icanon -echo'); + + // Add highlighted text style + $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white')); + + // Read a keypress + while (!feof($inputStream)) { + $c = fread($inputStream, 1); + + // as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false. + if (false === $c || ('' === $ret && '' === $c && null === $question->getDefault())) { + shell_exec(sprintf('stty %s', $sttyMode)); + throw new MissingInputException('Aborted.'); + } elseif ("\177" === $c) { // Backspace Character + if (0 === $numMatches && 0 !== $i) { + --$i; + $fullChoice = self::substr($fullChoice, 0, $i); + // Move cursor backwards + $output->write("\033[1D"); + } + + if (0 === $i) { + $ofs = -1; + $matches = $autocomplete($ret); + $numMatches = \count($matches); + } else { + $numMatches = 0; + } + + // Pop the last character off the end of our string + $ret = self::substr($ret, 0, $i); + } elseif ("\033" === $c) { + // Did we read an escape sequence? + $c .= fread($inputStream, 2); + + // A = Up Arrow. B = Down Arrow + if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) { + if ('A' === $c[2] && -1 === $ofs) { + $ofs = 0; + } + + if (0 === $numMatches) { + continue; + } + + $ofs += ('A' === $c[2]) ? -1 : 1; + $ofs = ($numMatches + $ofs) % $numMatches; + } + } elseif (\ord($c) < 32) { + if ("\t" === $c || "\n" === $c) { + if ($numMatches > 0 && -1 !== $ofs) { + $ret = (string) $matches[$ofs]; + // Echo out remaining chars for current match + $remainingCharacters = substr($ret, \strlen(trim($this->mostRecentlyEnteredValue($fullChoice)))); + $output->write($remainingCharacters); + $fullChoice .= $remainingCharacters; + $i = (false === $encoding = mb_detect_encoding($fullChoice, null, true)) ? \strlen($fullChoice) : mb_strlen($fullChoice, $encoding); + + $matches = array_filter( + $autocomplete($ret), + function ($match) use ($ret) { + return '' === $ret || str_starts_with($match, $ret); + } + ); + $numMatches = \count($matches); + $ofs = -1; + } + + if ("\n" === $c) { + $output->write($c); + break; + } + + $numMatches = 0; + } + + continue; + } else { + if ("\x80" <= $c) { + $c .= fread($inputStream, ["\xC0" => 1, "\xD0" => 1, "\xE0" => 2, "\xF0" => 3][$c & "\xF0"]); + } + + $output->write($c); + $ret .= $c; + $fullChoice .= $c; + ++$i; + + $tempRet = $ret; + + if ($question instanceof ChoiceQuestion && $question->isMultiselect()) { + $tempRet = $this->mostRecentlyEnteredValue($fullChoice); + } + + $numMatches = 0; + $ofs = 0; + + foreach ($autocomplete($ret) as $value) { + // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle) + if (str_starts_with($value, $tempRet)) { + $matches[$numMatches++] = $value; + } + } + } + + // Erase characters from cursor to end of line + $output->write("\033[K"); + + if ($numMatches > 0 && -1 !== $ofs) { + // Save cursor position + $output->write("\0337"); + // Write highlighted text, complete the partially entered response + $charactersEntered = \strlen(trim($this->mostRecentlyEnteredValue($fullChoice))); + $output->write('<hl>'.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $charactersEntered)).'</hl>'); + // Restore cursor position + $output->write("\0338"); + } + } + + // Reset stty so it behaves normally again + shell_exec(sprintf('stty %s', $sttyMode)); + + return $fullChoice; + } + + private function mostRecentlyEnteredValue(string $entered): string + { + // Determine the most recent value that the user entered + if (!str_contains($entered, ',')) { + return $entered; + } + + $choices = explode(',', $entered); + if ('' !== $lastChoice = trim($choices[\count($choices) - 1])) { + return $lastChoice; + } + + return $entered; + } + + /** + * Gets a hidden response from user. + * + * @param resource $inputStream The handler resource + * @param bool $trimmable Is the answer trimmable + * + * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden + */ + private function getHiddenResponse(OutputInterface $output, $inputStream, bool $trimmable = true): string + { + if ('\\' === \DIRECTORY_SEPARATOR) { + $exe = __DIR__.'/../Resources/bin/hiddeninput.exe'; + + // handle code running from a phar + if ('phar:' === substr(__FILE__, 0, 5)) { + $tmpExe = sys_get_temp_dir().'/hiddeninput.exe'; + copy($exe, $tmpExe); + $exe = $tmpExe; + } + + $sExec = shell_exec('"'.$exe.'"'); + $value = $trimmable ? rtrim($sExec) : $sExec; + $output->writeln(''); + + if (isset($tmpExe)) { + unlink($tmpExe); + } + + return $value; + } + + if (self::$stty && Terminal::hasSttyAvailable()) { + $sttyMode = shell_exec('stty -g'); + shell_exec('stty -echo'); + } elseif ($this->isInteractiveInput($inputStream)) { + throw new RuntimeException('Unable to hide the response.'); + } + + $value = fgets($inputStream, 4096); + + if (self::$stty && Terminal::hasSttyAvailable()) { + shell_exec(sprintf('stty %s', $sttyMode)); + } + + if (false === $value) { + throw new MissingInputException('Aborted.'); + } + if ($trimmable) { + $value = trim($value); + } + $output->writeln(''); + + return $value; + } + + /** + * Validates an attempt. + * + * @param callable $interviewer A callable that will ask for a question and return the result + * + * @return mixed The validated response + * + * @throws \Exception In case the max number of attempts has been reached and no valid response has been given + */ + private function validateAttempts(callable $interviewer, OutputInterface $output, Question $question) + { + $error = null; + $attempts = $question->getMaxAttempts(); + + while (null === $attempts || $attempts--) { + if (null !== $error) { + $this->writeError($output, $error); + } + + try { + return $question->getValidator()($interviewer()); + } catch (RuntimeException $e) { + throw $e; + } catch (\Exception $error) { + } + } + + throw $error; + } + + private function isInteractiveInput($inputStream): bool + { + if ('php://stdin' !== (stream_get_meta_data($inputStream)['uri'] ?? null)) { + return false; + } + + if (null !== self::$stdinIsInteractive) { + return self::$stdinIsInteractive; + } + + if (\function_exists('stream_isatty')) { + return self::$stdinIsInteractive = @stream_isatty(fopen('php://stdin', 'r')); + } + + if (\function_exists('posix_isatty')) { + return self::$stdinIsInteractive = @posix_isatty(fopen('php://stdin', 'r')); + } + + if (!\function_exists('exec')) { + return self::$stdinIsInteractive = true; + } + + exec('stty 2> /dev/null', $output, $status); + + return self::$stdinIsInteractive = 1 !== $status; + } + + /** + * Sets console I/O to the host code page. + * + * @return int Previous code page in IBM/EBCDIC format + */ + private function setIOCodepage(): int + { + if (\function_exists('sapi_windows_cp_set')) { + $cp = sapi_windows_cp_get(); + sapi_windows_cp_set(sapi_windows_cp_get('oem')); + + return $cp; + } + + return 0; + } + + /** + * Sets console I/O to the specified code page and converts the user input. + * + * @param string|false $input + * + * @return string|false + */ + private function resetIOCodepage(int $cp, $input) + { + if (0 !== $cp) { + sapi_windows_cp_set($cp); + + if (false !== $input && '' !== $input) { + $input = sapi_windows_cp_conv(sapi_windows_cp_get('oem'), $cp, $input); + } + } + + return $input; + } +} diff --git a/vendor/symfony/console/Helper/SymfonyQuestionHelper.php b/vendor/symfony/console/Helper/SymfonyQuestionHelper.php new file mode 100644 index 0000000000..ace5e1868e --- /dev/null +++ b/vendor/symfony/console/Helper/SymfonyQuestionHelper.php @@ -0,0 +1,96 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ChoiceQuestion; +use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * Symfony Style Guide compliant question helper. + * + * @author Kevin Bond <kevinbond@gmail.com> + */ +class SymfonyQuestionHelper extends QuestionHelper +{ + /** + * {@inheritdoc} + */ + protected function writePrompt(OutputInterface $output, Question $question) + { + $text = OutputFormatter::escapeTrailingBackslash($question->getQuestion()); + $default = $question->getDefault(); + + switch (true) { + case null === $default: + $text = sprintf(' <info>%s</info>:', $text); + + break; + + case $question instanceof ConfirmationQuestion: + $text = sprintf(' <info>%s (yes/no)</info> [<comment>%s</comment>]:', $text, $default ? 'yes' : 'no'); + + break; + + case $question instanceof ChoiceQuestion && $question->isMultiselect(): + $choices = $question->getChoices(); + $default = explode(',', $default); + + foreach ($default as $key => $value) { + $default[$key] = $choices[trim($value)]; + } + + $text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape(implode(', ', $default))); + + break; + + case $question instanceof ChoiceQuestion: + $choices = $question->getChoices(); + $text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape($choices[$default] ?? $default)); + + break; + + default: + $text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape($default)); + } + + $output->writeln($text); + + $prompt = ' > '; + + if ($question instanceof ChoiceQuestion) { + $output->writeln($this->formatChoiceQuestionChoices($question, 'comment')); + + $prompt = $question->getPrompt(); + } + + $output->write($prompt); + } + + /** + * {@inheritdoc} + */ + protected function writeError(OutputInterface $output, \Exception $error) + { + if ($output instanceof SymfonyStyle) { + $output->newLine(); + $output->error($error->getMessage()); + + return; + } + + parent::writeError($output, $error); + } +} diff --git a/vendor/symfony/console/Helper/Table.php b/vendor/symfony/console/Helper/Table.php new file mode 100644 index 0000000000..f068f02fae --- /dev/null +++ b/vendor/symfony/console/Helper/Table.php @@ -0,0 +1,875 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Formatter\WrappableOutputFormatterInterface; +use Symfony\Component\Console\Output\ConsoleSectionOutput; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Provides helpers to display a table. + * + * @author Fabien Potencier <fabien@symfony.com> + * @author Саша Стаменковић <umpirsky@gmail.com> + * @author Abdellatif Ait boudad <a.aitboudad@gmail.com> + * @author Max Grigorian <maxakawizard@gmail.com> + * @author Dany Maillard <danymaillard93b@gmail.com> + */ +class Table +{ + private const SEPARATOR_TOP = 0; + private const SEPARATOR_TOP_BOTTOM = 1; + private const SEPARATOR_MID = 2; + private const SEPARATOR_BOTTOM = 3; + private const BORDER_OUTSIDE = 0; + private const BORDER_INSIDE = 1; + + private $headerTitle; + private $footerTitle; + + /** + * Table headers. + */ + private $headers = []; + + /** + * Table rows. + */ + private $rows = []; + private $horizontal = false; + + /** + * Column widths cache. + */ + private $effectiveColumnWidths = []; + + /** + * Number of columns cache. + * + * @var int + */ + private $numberOfColumns; + + /** + * @var OutputInterface + */ + private $output; + + /** + * @var TableStyle + */ + private $style; + + /** + * @var array + */ + private $columnStyles = []; + + /** + * User set column widths. + * + * @var array + */ + private $columnWidths = []; + private $columnMaxWidths = []; + + private static $styles; + + private $rendered = false; + + public function __construct(OutputInterface $output) + { + $this->output = $output; + + if (!self::$styles) { + self::$styles = self::initStyles(); + } + + $this->setStyle('default'); + } + + /** + * Sets a style definition. + * + * @param string $name The style name + */ + public static function setStyleDefinition($name, TableStyle $style) + { + if (!self::$styles) { + self::$styles = self::initStyles(); + } + + self::$styles[$name] = $style; + } + + /** + * Gets a style definition by name. + * + * @param string $name The style name + * + * @return TableStyle + */ + public static function getStyleDefinition($name) + { + if (!self::$styles) { + self::$styles = self::initStyles(); + } + + if (isset(self::$styles[$name])) { + return self::$styles[$name]; + } + + throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); + } + + /** + * Sets table style. + * + * @param TableStyle|string $name The style name or a TableStyle instance + * + * @return $this + */ + public function setStyle($name) + { + $this->style = $this->resolveStyle($name); + + return $this; + } + + /** + * Gets the current table style. + * + * @return TableStyle + */ + public function getStyle() + { + return $this->style; + } + + /** + * Sets table column style. + * + * @param int $columnIndex Column index + * @param TableStyle|string $name The style name or a TableStyle instance + * + * @return $this + */ + public function setColumnStyle($columnIndex, $name) + { + $columnIndex = (int) $columnIndex; + + $this->columnStyles[$columnIndex] = $this->resolveStyle($name); + + return $this; + } + + /** + * Gets the current style for a column. + * + * If style was not set, it returns the global table style. + * + * @param int $columnIndex Column index + * + * @return TableStyle + */ + public function getColumnStyle($columnIndex) + { + return $this->columnStyles[$columnIndex] ?? $this->getStyle(); + } + + /** + * Sets the minimum width of a column. + * + * @param int $columnIndex Column index + * @param int $width Minimum column width in characters + * + * @return $this + */ + public function setColumnWidth($columnIndex, $width) + { + $this->columnWidths[(int) $columnIndex] = (int) $width; + + return $this; + } + + /** + * Sets the minimum width of all columns. + * + * @return $this + */ + public function setColumnWidths(array $widths) + { + $this->columnWidths = []; + foreach ($widths as $index => $width) { + $this->setColumnWidth($index, $width); + } + + return $this; + } + + /** + * Sets the maximum width of a column. + * + * Any cell within this column which contents exceeds the specified width will be wrapped into multiple lines, while + * formatted strings are preserved. + * + * @return $this + */ + public function setColumnMaxWidth(int $columnIndex, int $width): self + { + if (!$this->output->getFormatter() instanceof WrappableOutputFormatterInterface) { + throw new \LogicException(sprintf('Setting a maximum column width is only supported when using a "%s" formatter, got "%s".', WrappableOutputFormatterInterface::class, \get_class($this->output->getFormatter()))); + } + + $this->columnMaxWidths[$columnIndex] = $width; + + return $this; + } + + public function setHeaders(array $headers) + { + $headers = array_values($headers); + if (!empty($headers) && !\is_array($headers[0])) { + $headers = [$headers]; + } + + $this->headers = $headers; + + return $this; + } + + public function setRows(array $rows) + { + $this->rows = []; + + return $this->addRows($rows); + } + + public function addRows(array $rows) + { + foreach ($rows as $row) { + $this->addRow($row); + } + + return $this; + } + + public function addRow($row) + { + if ($row instanceof TableSeparator) { + $this->rows[] = $row; + + return $this; + } + + if (!\is_array($row)) { + throw new InvalidArgumentException('A row must be an array or a TableSeparator instance.'); + } + + $this->rows[] = array_values($row); + + return $this; + } + + /** + * Adds a row to the table, and re-renders the table. + */ + public function appendRow($row): self + { + if (!$this->output instanceof ConsoleSectionOutput) { + throw new RuntimeException(sprintf('Output should be an instance of "%s" when calling "%s".', ConsoleSectionOutput::class, __METHOD__)); + } + + if ($this->rendered) { + $this->output->clear($this->calculateRowCount()); + } + + $this->addRow($row); + $this->render(); + + return $this; + } + + public function setRow($column, array $row) + { + $this->rows[$column] = $row; + + return $this; + } + + public function setHeaderTitle(?string $title): self + { + $this->headerTitle = $title; + + return $this; + } + + public function setFooterTitle(?string $title): self + { + $this->footerTitle = $title; + + return $this; + } + + public function setHorizontal(bool $horizontal = true): self + { + $this->horizontal = $horizontal; + + return $this; + } + + /** + * Renders table to output. + * + * Example: + * + * +---------------+-----------------------+------------------+ + * | ISBN | Title | Author | + * +---------------+-----------------------+------------------+ + * | 99921-58-10-7 | Divine Comedy | Dante Alighieri | + * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | + * | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | + * +---------------+-----------------------+------------------+ + */ + public function render() + { + $divider = new TableSeparator(); + if ($this->horizontal) { + $rows = []; + foreach ($this->headers[0] ?? [] as $i => $header) { + $rows[$i] = [$header]; + foreach ($this->rows as $row) { + if ($row instanceof TableSeparator) { + continue; + } + if (isset($row[$i])) { + $rows[$i][] = $row[$i]; + } elseif ($rows[$i][0] instanceof TableCell && $rows[$i][0]->getColspan() >= 2) { + // Noop, there is a "title" + } else { + $rows[$i][] = null; + } + } + } + } else { + $rows = array_merge($this->headers, [$divider], $this->rows); + } + + $this->calculateNumberOfColumns($rows); + + $rowGroups = $this->buildTableRows($rows); + $this->calculateColumnsWidth($rowGroups); + + $isHeader = !$this->horizontal; + $isFirstRow = $this->horizontal; + $hasTitle = (bool) $this->headerTitle; + + foreach ($rowGroups as $rowGroup) { + $isHeaderSeparatorRendered = false; + + foreach ($rowGroup as $row) { + if ($divider === $row) { + $isHeader = false; + $isFirstRow = true; + + continue; + } + + if ($row instanceof TableSeparator) { + $this->renderRowSeparator(); + + continue; + } + + if (!$row) { + continue; + } + + if ($isHeader && !$isHeaderSeparatorRendered) { + $this->renderRowSeparator( + $isHeader ? self::SEPARATOR_TOP : self::SEPARATOR_TOP_BOTTOM, + $hasTitle ? $this->headerTitle : null, + $hasTitle ? $this->style->getHeaderTitleFormat() : null + ); + $hasTitle = false; + $isHeaderSeparatorRendered = true; + } + + if ($isFirstRow) { + $this->renderRowSeparator( + $isHeader ? self::SEPARATOR_TOP : self::SEPARATOR_TOP_BOTTOM, + $hasTitle ? $this->headerTitle : null, + $hasTitle ? $this->style->getHeaderTitleFormat() : null + ); + $isFirstRow = false; + $hasTitle = false; + } + + if ($this->horizontal) { + $this->renderRow($row, $this->style->getCellRowFormat(), $this->style->getCellHeaderFormat()); + } else { + $this->renderRow($row, $isHeader ? $this->style->getCellHeaderFormat() : $this->style->getCellRowFormat()); + } + } + } + $this->renderRowSeparator(self::SEPARATOR_BOTTOM, $this->footerTitle, $this->style->getFooterTitleFormat()); + + $this->cleanup(); + $this->rendered = true; + } + + /** + * Renders horizontal header separator. + * + * Example: + * + * +-----+-----------+-------+ + */ + private function renderRowSeparator(int $type = self::SEPARATOR_MID, string $title = null, string $titleFormat = null) + { + if (0 === $count = $this->numberOfColumns) { + return; + } + + $borders = $this->style->getBorderChars(); + if (!$borders[0] && !$borders[2] && !$this->style->getCrossingChar()) { + return; + } + + $crossings = $this->style->getCrossingChars(); + if (self::SEPARATOR_MID === $type) { + [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[2], $crossings[8], $crossings[0], $crossings[4]]; + } elseif (self::SEPARATOR_TOP === $type) { + [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[1], $crossings[2], $crossings[3]]; + } elseif (self::SEPARATOR_TOP_BOTTOM === $type) { + [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[9], $crossings[10], $crossings[11]]; + } else { + [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[7], $crossings[6], $crossings[5]]; + } + + $markup = $leftChar; + for ($column = 0; $column < $count; ++$column) { + $markup .= str_repeat($horizontal, $this->effectiveColumnWidths[$column]); + $markup .= $column === $count - 1 ? $rightChar : $midChar; + } + + if (null !== $title) { + $titleLength = Helper::strlenWithoutDecoration($formatter = $this->output->getFormatter(), $formattedTitle = sprintf($titleFormat, $title)); + $markupLength = Helper::strlen($markup); + if ($titleLength > $limit = $markupLength - 4) { + $titleLength = $limit; + $formatLength = Helper::strlenWithoutDecoration($formatter, sprintf($titleFormat, '')); + $formattedTitle = sprintf($titleFormat, Helper::substr($title, 0, $limit - $formatLength - 3).'...'); + } + + $titleStart = intdiv($markupLength - $titleLength, 2); + if (false === mb_detect_encoding($markup, null, true)) { + $markup = substr_replace($markup, $formattedTitle, $titleStart, $titleLength); + } else { + $markup = mb_substr($markup, 0, $titleStart).$formattedTitle.mb_substr($markup, $titleStart + $titleLength); + } + } + + $this->output->writeln(sprintf($this->style->getBorderFormat(), $markup)); + } + + /** + * Renders vertical column separator. + */ + private function renderColumnSeparator(int $type = self::BORDER_OUTSIDE): string + { + $borders = $this->style->getBorderChars(); + + return sprintf($this->style->getBorderFormat(), self::BORDER_OUTSIDE === $type ? $borders[1] : $borders[3]); + } + + /** + * Renders table row. + * + * Example: + * + * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | + */ + private function renderRow(array $row, string $cellFormat, string $firstCellFormat = null) + { + $rowContent = $this->renderColumnSeparator(self::BORDER_OUTSIDE); + $columns = $this->getRowColumns($row); + $last = \count($columns) - 1; + foreach ($columns as $i => $column) { + if ($firstCellFormat && 0 === $i) { + $rowContent .= $this->renderCell($row, $column, $firstCellFormat); + } else { + $rowContent .= $this->renderCell($row, $column, $cellFormat); + } + $rowContent .= $this->renderColumnSeparator($last === $i ? self::BORDER_OUTSIDE : self::BORDER_INSIDE); + } + $this->output->writeln($rowContent); + } + + /** + * Renders table cell with padding. + */ + private function renderCell(array $row, int $column, string $cellFormat): string + { + $cell = $row[$column] ?? ''; + $width = $this->effectiveColumnWidths[$column]; + if ($cell instanceof TableCell && $cell->getColspan() > 1) { + // add the width of the following columns(numbers of colspan). + foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) { + $width += $this->getColumnSeparatorWidth() + $this->effectiveColumnWidths[$nextColumn]; + } + } + + // str_pad won't work properly with multi-byte strings, we need to fix the padding + if (false !== $encoding = mb_detect_encoding($cell, null, true)) { + $width += \strlen($cell) - mb_strwidth($cell, $encoding); + } + + $style = $this->getColumnStyle($column); + + if ($cell instanceof TableSeparator) { + return sprintf($style->getBorderFormat(), str_repeat($style->getBorderChars()[2], $width)); + } + + $width += Helper::strlen($cell) - Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell); + $content = sprintf($style->getCellRowContentFormat(), $cell); + + return sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $style->getPadType())); + } + + /** + * Calculate number of columns for this table. + */ + private function calculateNumberOfColumns(array $rows) + { + $columns = [0]; + foreach ($rows as $row) { + if ($row instanceof TableSeparator) { + continue; + } + + $columns[] = $this->getNumberOfColumns($row); + } + + $this->numberOfColumns = max($columns); + } + + private function buildTableRows(array $rows): TableRows + { + /** @var WrappableOutputFormatterInterface $formatter */ + $formatter = $this->output->getFormatter(); + $unmergedRows = []; + for ($rowKey = 0; $rowKey < \count($rows); ++$rowKey) { + $rows = $this->fillNextRows($rows, $rowKey); + + // Remove any new line breaks and replace it with a new line + foreach ($rows[$rowKey] as $column => $cell) { + $colspan = $cell instanceof TableCell ? $cell->getColspan() : 1; + + if (isset($this->columnMaxWidths[$column]) && Helper::strlenWithoutDecoration($formatter, $cell) > $this->columnMaxWidths[$column]) { + $cell = $formatter->formatAndWrap($cell, $this->columnMaxWidths[$column] * $colspan); + } + if (!strstr($cell ?? '', "\n")) { + continue; + } + $escaped = implode("\n", array_map([OutputFormatter::class, 'escapeTrailingBackslash'], explode("\n", $cell))); + $cell = $cell instanceof TableCell ? new TableCell($escaped, ['colspan' => $cell->getColspan()]) : $escaped; + $lines = explode("\n", str_replace("\n", "<fg=default;bg=default></>\n", $cell)); + foreach ($lines as $lineKey => $line) { + if ($colspan > 1) { + $line = new TableCell($line, ['colspan' => $colspan]); + } + if (0 === $lineKey) { + $rows[$rowKey][$column] = $line; + } else { + if (!\array_key_exists($rowKey, $unmergedRows) || !\array_key_exists($lineKey, $unmergedRows[$rowKey])) { + $unmergedRows[$rowKey][$lineKey] = $this->copyRow($rows, $rowKey); + } + $unmergedRows[$rowKey][$lineKey][$column] = $line; + } + } + } + } + + return new TableRows(function () use ($rows, $unmergedRows): \Traversable { + foreach ($rows as $rowKey => $row) { + $rowGroup = [$row instanceof TableSeparator ? $row : $this->fillCells($row)]; + + if (isset($unmergedRows[$rowKey])) { + foreach ($unmergedRows[$rowKey] as $row) { + $rowGroup[] = $row instanceof TableSeparator ? $row : $this->fillCells($row); + } + } + yield $rowGroup; + } + }); + } + + private function calculateRowCount(): int + { + $numberOfRows = \count(iterator_to_array($this->buildTableRows(array_merge($this->headers, [new TableSeparator()], $this->rows)))); + + if ($this->headers) { + ++$numberOfRows; // Add row for header separator + } + + if (\count($this->rows) > 0) { + ++$numberOfRows; // Add row for footer separator + } + + return $numberOfRows; + } + + /** + * fill rows that contains rowspan > 1. + * + * @throws InvalidArgumentException + */ + private function fillNextRows(array $rows, int $line): array + { + $unmergedRows = []; + foreach ($rows[$line] as $column => $cell) { + if (null !== $cell && !$cell instanceof TableCell && !\is_scalar($cell) && !(\is_object($cell) && method_exists($cell, '__toString'))) { + throw new InvalidArgumentException(sprintf('A cell must be a TableCell, a scalar or an object implementing "__toString()", "%s" given.', \gettype($cell))); + } + if ($cell instanceof TableCell && $cell->getRowspan() > 1) { + $nbLines = $cell->getRowspan() - 1; + $lines = [$cell]; + if (strstr($cell, "\n")) { + $lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell)); + $nbLines = \count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines; + + $rows[$line][$column] = new TableCell($lines[0], ['colspan' => $cell->getColspan()]); + unset($lines[0]); + } + + // create a two dimensional array (rowspan x colspan) + $unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, []), $unmergedRows); + foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { + $value = $lines[$unmergedRowKey - $line] ?? ''; + $unmergedRows[$unmergedRowKey][$column] = new TableCell($value, ['colspan' => $cell->getColspan()]); + if ($nbLines === $unmergedRowKey - $line) { + break; + } + } + } + } + + foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { + // we need to know if $unmergedRow will be merged or inserted into $rows + if (isset($rows[$unmergedRowKey]) && \is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) { + foreach ($unmergedRow as $cellKey => $cell) { + // insert cell into row at cellKey position + array_splice($rows[$unmergedRowKey], $cellKey, 0, [$cell]); + } + } else { + $row = $this->copyRow($rows, $unmergedRowKey - 1); + foreach ($unmergedRow as $column => $cell) { + if (!empty($cell)) { + $row[$column] = $unmergedRow[$column]; + } + } + array_splice($rows, $unmergedRowKey, 0, [$row]); + } + } + + return $rows; + } + + /** + * fill cells for a row that contains colspan > 1. + */ + private function fillCells(iterable $row) + { + $newRow = []; + + foreach ($row as $column => $cell) { + $newRow[] = $cell; + if ($cell instanceof TableCell && $cell->getColspan() > 1) { + foreach (range($column + 1, $column + $cell->getColspan() - 1) as $position) { + // insert empty value at column position + $newRow[] = ''; + } + } + } + + return $newRow ?: $row; + } + + private function copyRow(array $rows, int $line): array + { + $row = $rows[$line]; + foreach ($row as $cellKey => $cellValue) { + $row[$cellKey] = ''; + if ($cellValue instanceof TableCell) { + $row[$cellKey] = new TableCell('', ['colspan' => $cellValue->getColspan()]); + } + } + + return $row; + } + + /** + * Gets number of columns by row. + */ + private function getNumberOfColumns(array $row): int + { + $columns = \count($row); + foreach ($row as $column) { + $columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0; + } + + return $columns; + } + + /** + * Gets list of columns for the given row. + */ + private function getRowColumns(array $row): array + { + $columns = range(0, $this->numberOfColumns - 1); + foreach ($row as $cellKey => $cell) { + if ($cell instanceof TableCell && $cell->getColspan() > 1) { + // exclude grouped columns. + $columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1)); + } + } + + return $columns; + } + + /** + * Calculates columns widths. + */ + private function calculateColumnsWidth(iterable $groups) + { + for ($column = 0; $column < $this->numberOfColumns; ++$column) { + $lengths = []; + foreach ($groups as $group) { + foreach ($group as $row) { + if ($row instanceof TableSeparator) { + continue; + } + + foreach ($row as $i => $cell) { + if ($cell instanceof TableCell) { + $textContent = Helper::removeDecoration($this->output->getFormatter(), $cell); + $textLength = Helper::strlen($textContent); + if ($textLength > 0) { + $contentColumns = str_split($textContent, ceil($textLength / $cell->getColspan())); + foreach ($contentColumns as $position => $content) { + $row[$i + $position] = $content; + } + } + } + } + + $lengths[] = $this->getCellWidth($row, $column); + } + } + + $this->effectiveColumnWidths[$column] = max($lengths) + Helper::strlen($this->style->getCellRowContentFormat()) - 2; + } + } + + private function getColumnSeparatorWidth(): int + { + return Helper::strlen(sprintf($this->style->getBorderFormat(), $this->style->getBorderChars()[3])); + } + + private function getCellWidth(array $row, int $column): int + { + $cellWidth = 0; + + if (isset($row[$column])) { + $cell = $row[$column]; + $cellWidth = Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell); + } + + $columnWidth = $this->columnWidths[$column] ?? 0; + $cellWidth = max($cellWidth, $columnWidth); + + return isset($this->columnMaxWidths[$column]) ? min($this->columnMaxWidths[$column], $cellWidth) : $cellWidth; + } + + /** + * Called after rendering to cleanup cache data. + */ + private function cleanup() + { + $this->effectiveColumnWidths = []; + $this->numberOfColumns = null; + } + + private static function initStyles(): array + { + $borderless = new TableStyle(); + $borderless + ->setHorizontalBorderChars('=') + ->setVerticalBorderChars(' ') + ->setDefaultCrossingChar(' ') + ; + + $compact = new TableStyle(); + $compact + ->setHorizontalBorderChars('') + ->setVerticalBorderChars('') + ->setDefaultCrossingChar('') + ->setCellRowContentFormat('%s ') + ; + + $styleGuide = new TableStyle(); + $styleGuide + ->setHorizontalBorderChars('-') + ->setVerticalBorderChars(' ') + ->setDefaultCrossingChar(' ') + ->setCellHeaderFormat('%s') + ; + + $box = (new TableStyle()) + ->setHorizontalBorderChars('─') + ->setVerticalBorderChars('│') + ->setCrossingChars('┼', '┌', '┬', '┐', '┤', '┘', '┴', '└', '├') + ; + + $boxDouble = (new TableStyle()) + ->setHorizontalBorderChars('═', '─') + ->setVerticalBorderChars('║', '│') + ->setCrossingChars('┼', '╔', '╤', '╗', '╢', '╝', '╧', '╚', '╟', '╠', '╪', '╣') + ; + + return [ + 'default' => new TableStyle(), + 'borderless' => $borderless, + 'compact' => $compact, + 'symfony-style-guide' => $styleGuide, + 'box' => $box, + 'box-double' => $boxDouble, + ]; + } + + private function resolveStyle($name): TableStyle + { + if ($name instanceof TableStyle) { + return $name; + } + + if (isset(self::$styles[$name])) { + return self::$styles[$name]; + } + + throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); + } +} diff --git a/vendor/symfony/console/Helper/TableCell.php b/vendor/symfony/console/Helper/TableCell.php new file mode 100644 index 0000000000..5b6af4a933 --- /dev/null +++ b/vendor/symfony/console/Helper/TableCell.php @@ -0,0 +1,68 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * @author Abdellatif Ait boudad <a.aitboudad@gmail.com> + */ +class TableCell +{ + private $value; + private $options = [ + 'rowspan' => 1, + 'colspan' => 1, + ]; + + public function __construct(string $value = '', array $options = []) + { + $this->value = $value; + + // check option names + if ($diff = array_diff(array_keys($options), array_keys($this->options))) { + throw new InvalidArgumentException(sprintf('The TableCell does not support the following options: \'%s\'.', implode('\', \'', $diff))); + } + + $this->options = array_merge($this->options, $options); + } + + /** + * Returns the cell value. + * + * @return string + */ + public function __toString() + { + return $this->value; + } + + /** + * Gets number of colspan. + * + * @return int + */ + public function getColspan() + { + return (int) $this->options['colspan']; + } + + /** + * Gets number of rowspan. + * + * @return int + */ + public function getRowspan() + { + return (int) $this->options['rowspan']; + } +} diff --git a/vendor/symfony/console/Helper/TableRows.php b/vendor/symfony/console/Helper/TableRows.php new file mode 100644 index 0000000000..16aabb3fc9 --- /dev/null +++ b/vendor/symfony/console/Helper/TableRows.php @@ -0,0 +1,32 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +/** + * @internal + */ +class TableRows implements \IteratorAggregate +{ + private $generator; + + public function __construct(callable $generator) + { + $this->generator = $generator; + } + + public function getIterator(): \Traversable + { + $g = $this->generator; + + return $g(); + } +} diff --git a/vendor/symfony/console/Helper/TableSeparator.php b/vendor/symfony/console/Helper/TableSeparator.php new file mode 100644 index 0000000000..e541c53156 --- /dev/null +++ b/vendor/symfony/console/Helper/TableSeparator.php @@ -0,0 +1,25 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +/** + * Marks a row as being a separator. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class TableSeparator extends TableCell +{ + public function __construct(array $options = []) + { + parent::__construct('', $options); + } +} diff --git a/vendor/symfony/console/Helper/TableStyle.php b/vendor/symfony/console/Helper/TableStyle.php new file mode 100644 index 0000000000..a8df59b3af --- /dev/null +++ b/vendor/symfony/console/Helper/TableStyle.php @@ -0,0 +1,458 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; + +/** + * Defines the styles for a Table. + * + * @author Fabien Potencier <fabien@symfony.com> + * @author Саша Стаменковић <umpirsky@gmail.com> + * @author Dany Maillard <danymaillard93b@gmail.com> + */ +class TableStyle +{ + private $paddingChar = ' '; + private $horizontalOutsideBorderChar = '-'; + private $horizontalInsideBorderChar = '-'; + private $verticalOutsideBorderChar = '|'; + private $verticalInsideBorderChar = '|'; + private $crossingChar = '+'; + private $crossingTopRightChar = '+'; + private $crossingTopMidChar = '+'; + private $crossingTopLeftChar = '+'; + private $crossingMidRightChar = '+'; + private $crossingBottomRightChar = '+'; + private $crossingBottomMidChar = '+'; + private $crossingBottomLeftChar = '+'; + private $crossingMidLeftChar = '+'; + private $crossingTopLeftBottomChar = '+'; + private $crossingTopMidBottomChar = '+'; + private $crossingTopRightBottomChar = '+'; + private $headerTitleFormat = '<fg=black;bg=white;options=bold> %s </>'; + private $footerTitleFormat = '<fg=black;bg=white;options=bold> %s </>'; + private $cellHeaderFormat = '<info>%s</info>'; + private $cellRowFormat = '%s'; + private $cellRowContentFormat = ' %s '; + private $borderFormat = '%s'; + private $padType = \STR_PAD_RIGHT; + + /** + * Sets padding character, used for cell padding. + * + * @param string $paddingChar + * + * @return $this + */ + public function setPaddingChar($paddingChar) + { + if (!$paddingChar) { + throw new LogicException('The padding char must not be empty.'); + } + + $this->paddingChar = $paddingChar; + + return $this; + } + + /** + * Gets padding character, used for cell padding. + * + * @return string + */ + public function getPaddingChar() + { + return $this->paddingChar; + } + + /** + * Sets horizontal border characters. + * + * <code> + * ╔═══════════════╤══════════════════════════╤══════════════════╗ + * 1 ISBN 2 Title │ Author ║ + * ╠═══════════════╪══════════════════════════╪══════════════════╣ + * ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║ + * ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║ + * ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║ + * ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║ + * ╚═══════════════╧══════════════════════════╧══════════════════╝ + * </code> + * + * @param string $outside Outside border char (see #1 of example) + * @param string|null $inside Inside border char (see #2 of example), equals $outside if null + */ + public function setHorizontalBorderChars(string $outside, string $inside = null): self + { + $this->horizontalOutsideBorderChar = $outside; + $this->horizontalInsideBorderChar = $inside ?? $outside; + + return $this; + } + + /** + * Sets horizontal border character. + * + * @param string $horizontalBorderChar + * + * @return $this + * + * @deprecated since Symfony 4.1, use {@link setHorizontalBorderChars()} instead. + */ + public function setHorizontalBorderChar($horizontalBorderChar) + { + @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1, use setHorizontalBorderChars() instead.', __METHOD__), \E_USER_DEPRECATED); + + return $this->setHorizontalBorderChars($horizontalBorderChar, $horizontalBorderChar); + } + + /** + * Gets horizontal border character. + * + * @return string + * + * @deprecated since Symfony 4.1, use {@link getBorderChars()} instead. + */ + public function getHorizontalBorderChar() + { + @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1, use getBorderChars() instead.', __METHOD__), \E_USER_DEPRECATED); + + return $this->horizontalOutsideBorderChar; + } + + /** + * Sets vertical border characters. + * + * <code> + * ╔═══════════════╤══════════════════════════╤══════════════════╗ + * ║ ISBN │ Title │ Author ║ + * ╠═══════1═══════╪══════════════════════════╪══════════════════╣ + * ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║ + * ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║ + * ╟───────2───────┼──────────────────────────┼──────────────────╢ + * ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║ + * ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║ + * ╚═══════════════╧══════════════════════════╧══════════════════╝ + * </code> + * + * @param string $outside Outside border char (see #1 of example) + * @param string|null $inside Inside border char (see #2 of example), equals $outside if null + */ + public function setVerticalBorderChars(string $outside, string $inside = null): self + { + $this->verticalOutsideBorderChar = $outside; + $this->verticalInsideBorderChar = $inside ?? $outside; + + return $this; + } + + /** + * Sets vertical border character. + * + * @param string $verticalBorderChar + * + * @return $this + * + * @deprecated since Symfony 4.1, use {@link setVerticalBorderChars()} instead. + */ + public function setVerticalBorderChar($verticalBorderChar) + { + @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1, use setVerticalBorderChars() instead.', __METHOD__), \E_USER_DEPRECATED); + + return $this->setVerticalBorderChars($verticalBorderChar, $verticalBorderChar); + } + + /** + * Gets vertical border character. + * + * @return string + * + * @deprecated since Symfony 4.1, use {@link getBorderChars()} instead. + */ + public function getVerticalBorderChar() + { + @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1, use getBorderChars() instead.', __METHOD__), \E_USER_DEPRECATED); + + return $this->verticalOutsideBorderChar; + } + + /** + * Gets border characters. + * + * @internal + */ + public function getBorderChars(): array + { + return [ + $this->horizontalOutsideBorderChar, + $this->verticalOutsideBorderChar, + $this->horizontalInsideBorderChar, + $this->verticalInsideBorderChar, + ]; + } + + /** + * Sets crossing characters. + * + * Example: + * <code> + * 1═══════════════2══════════════════════════2══════════════════3 + * ║ ISBN │ Title │ Author ║ + * 8'══════════════0'═════════════════════════0'═════════════════4' + * ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║ + * ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║ + * 8───────────────0──────────────────────────0──────────────────4 + * ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║ + * ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║ + * 7═══════════════6══════════════════════════6══════════════════5 + * </code> + * + * @param string $cross Crossing char (see #0 of example) + * @param string $topLeft Top left char (see #1 of example) + * @param string $topMid Top mid char (see #2 of example) + * @param string $topRight Top right char (see #3 of example) + * @param string $midRight Mid right char (see #4 of example) + * @param string $bottomRight Bottom right char (see #5 of example) + * @param string $bottomMid Bottom mid char (see #6 of example) + * @param string $bottomLeft Bottom left char (see #7 of example) + * @param string $midLeft Mid left char (see #8 of example) + * @param string|null $topLeftBottom Top left bottom char (see #8' of example), equals to $midLeft if null + * @param string|null $topMidBottom Top mid bottom char (see #0' of example), equals to $cross if null + * @param string|null $topRightBottom Top right bottom char (see #4' of example), equals to $midRight if null + */ + public function setCrossingChars(string $cross, string $topLeft, string $topMid, string $topRight, string $midRight, string $bottomRight, string $bottomMid, string $bottomLeft, string $midLeft, string $topLeftBottom = null, string $topMidBottom = null, string $topRightBottom = null): self + { + $this->crossingChar = $cross; + $this->crossingTopLeftChar = $topLeft; + $this->crossingTopMidChar = $topMid; + $this->crossingTopRightChar = $topRight; + $this->crossingMidRightChar = $midRight; + $this->crossingBottomRightChar = $bottomRight; + $this->crossingBottomMidChar = $bottomMid; + $this->crossingBottomLeftChar = $bottomLeft; + $this->crossingMidLeftChar = $midLeft; + $this->crossingTopLeftBottomChar = $topLeftBottom ?? $midLeft; + $this->crossingTopMidBottomChar = $topMidBottom ?? $cross; + $this->crossingTopRightBottomChar = $topRightBottom ?? $midRight; + + return $this; + } + + /** + * Sets default crossing character used for each cross. + * + * @see {@link setCrossingChars()} for setting each crossing individually. + */ + public function setDefaultCrossingChar(string $char): self + { + return $this->setCrossingChars($char, $char, $char, $char, $char, $char, $char, $char, $char); + } + + /** + * Sets crossing character. + * + * @param string $crossingChar + * + * @return $this + * + * @deprecated since Symfony 4.1. Use {@link setDefaultCrossingChar()} instead. + */ + public function setCrossingChar($crossingChar) + { + @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1. Use setDefaultCrossingChar() instead.', __METHOD__), \E_USER_DEPRECATED); + + return $this->setDefaultCrossingChar($crossingChar); + } + + /** + * Gets crossing character. + * + * @return string + */ + public function getCrossingChar() + { + return $this->crossingChar; + } + + /** + * Gets crossing characters. + * + * @internal + */ + public function getCrossingChars(): array + { + return [ + $this->crossingChar, + $this->crossingTopLeftChar, + $this->crossingTopMidChar, + $this->crossingTopRightChar, + $this->crossingMidRightChar, + $this->crossingBottomRightChar, + $this->crossingBottomMidChar, + $this->crossingBottomLeftChar, + $this->crossingMidLeftChar, + $this->crossingTopLeftBottomChar, + $this->crossingTopMidBottomChar, + $this->crossingTopRightBottomChar, + ]; + } + + /** + * Sets header cell format. + * + * @param string $cellHeaderFormat + * + * @return $this + */ + public function setCellHeaderFormat($cellHeaderFormat) + { + $this->cellHeaderFormat = $cellHeaderFormat; + + return $this; + } + + /** + * Gets header cell format. + * + * @return string + */ + public function getCellHeaderFormat() + { + return $this->cellHeaderFormat; + } + + /** + * Sets row cell format. + * + * @param string $cellRowFormat + * + * @return $this + */ + public function setCellRowFormat($cellRowFormat) + { + $this->cellRowFormat = $cellRowFormat; + + return $this; + } + + /** + * Gets row cell format. + * + * @return string + */ + public function getCellRowFormat() + { + return $this->cellRowFormat; + } + + /** + * Sets row cell content format. + * + * @param string $cellRowContentFormat + * + * @return $this + */ + public function setCellRowContentFormat($cellRowContentFormat) + { + $this->cellRowContentFormat = $cellRowContentFormat; + + return $this; + } + + /** + * Gets row cell content format. + * + * @return string + */ + public function getCellRowContentFormat() + { + return $this->cellRowContentFormat; + } + + /** + * Sets table border format. + * + * @param string $borderFormat + * + * @return $this + */ + public function setBorderFormat($borderFormat) + { + $this->borderFormat = $borderFormat; + + return $this; + } + + /** + * Gets table border format. + * + * @return string + */ + public function getBorderFormat() + { + return $this->borderFormat; + } + + /** + * Sets cell padding type. + * + * @param int $padType STR_PAD_* + * + * @return $this + */ + public function setPadType($padType) + { + if (!\in_array($padType, [\STR_PAD_LEFT, \STR_PAD_RIGHT, \STR_PAD_BOTH], true)) { + throw new InvalidArgumentException('Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH).'); + } + + $this->padType = $padType; + + return $this; + } + + /** + * Gets cell padding type. + * + * @return int + */ + public function getPadType() + { + return $this->padType; + } + + public function getHeaderTitleFormat(): string + { + return $this->headerTitleFormat; + } + + public function setHeaderTitleFormat(string $format): self + { + $this->headerTitleFormat = $format; + + return $this; + } + + public function getFooterTitleFormat(): string + { + return $this->footerTitleFormat; + } + + public function setFooterTitleFormat(string $format): self + { + $this->footerTitleFormat = $format; + + return $this; + } +} diff --git a/vendor/symfony/console/Input/ArgvInput.php b/vendor/symfony/console/Input/ArgvInput.php new file mode 100644 index 0000000000..63f40f2711 --- /dev/null +++ b/vendor/symfony/console/Input/ArgvInput.php @@ -0,0 +1,350 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\RuntimeException; + +/** + * ArgvInput represents an input coming from the CLI arguments. + * + * Usage: + * + * $input = new ArgvInput(); + * + * By default, the `$_SERVER['argv']` array is used for the input values. + * + * This can be overridden by explicitly passing the input values in the constructor: + * + * $input = new ArgvInput($_SERVER['argv']); + * + * If you pass it yourself, don't forget that the first element of the array + * is the name of the running application. + * + * When passing an argument to the constructor, be sure that it respects + * the same rules as the argv one. It's almost always better to use the + * `StringInput` when you want to provide your own input. + * + * @author Fabien Potencier <fabien@symfony.com> + * + * @see http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html + * @see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html#tag_12_02 + */ +class ArgvInput extends Input +{ + private $tokens; + private $parsed; + + /** + * @param array|null $argv An array of parameters from the CLI (in the argv format) + */ + public function __construct(array $argv = null, InputDefinition $definition = null) + { + $argv = $argv ?? $_SERVER['argv'] ?? []; + + // strip the application name + array_shift($argv); + + $this->tokens = $argv; + + parent::__construct($definition); + } + + protected function setTokens(array $tokens) + { + $this->tokens = $tokens; + } + + /** + * {@inheritdoc} + */ + protected function parse() + { + $parseOptions = true; + $this->parsed = $this->tokens; + while (null !== $token = array_shift($this->parsed)) { + if ($parseOptions && '' == $token) { + $this->parseArgument($token); + } elseif ($parseOptions && '--' == $token) { + $parseOptions = false; + } elseif ($parseOptions && str_starts_with($token, '--')) { + $this->parseLongOption($token); + } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) { + $this->parseShortOption($token); + } else { + $this->parseArgument($token); + } + } + } + + /** + * Parses a short option. + */ + private function parseShortOption(string $token) + { + $name = substr($token, 1); + + if (\strlen($name) > 1) { + if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptValue()) { + // an option with a value (with no space) + $this->addShortOption($name[0], substr($name, 1)); + } else { + $this->parseShortOptionSet($name); + } + } else { + $this->addShortOption($name, null); + } + } + + /** + * Parses a short option set. + * + * @throws RuntimeException When option given doesn't exist + */ + private function parseShortOptionSet(string $name) + { + $len = \strlen($name); + for ($i = 0; $i < $len; ++$i) { + if (!$this->definition->hasShortcut($name[$i])) { + $encoding = mb_detect_encoding($name, null, true); + throw new RuntimeException(sprintf('The "-%s" option does not exist.', false === $encoding ? $name[$i] : mb_substr($name, $i, 1, $encoding))); + } + + $option = $this->definition->getOptionForShortcut($name[$i]); + if ($option->acceptValue()) { + $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1)); + + break; + } else { + $this->addLongOption($option->getName(), null); + } + } + } + + /** + * Parses a long option. + */ + private function parseLongOption(string $token) + { + $name = substr($token, 2); + + if (false !== $pos = strpos($name, '=')) { + if ('' === $value = substr($name, $pos + 1)) { + array_unshift($this->parsed, $value); + } + $this->addLongOption(substr($name, 0, $pos), $value); + } else { + $this->addLongOption($name, null); + } + } + + /** + * Parses an argument. + * + * @throws RuntimeException When too many arguments are given + */ + private function parseArgument(string $token) + { + $c = \count($this->arguments); + + // if input is expecting another argument, add it + if ($this->definition->hasArgument($c)) { + $arg = $this->definition->getArgument($c); + $this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token; + + // if last argument isArray(), append token to last argument + } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) { + $arg = $this->definition->getArgument($c - 1); + $this->arguments[$arg->getName()][] = $token; + + // unexpected argument + } else { + $all = $this->definition->getArguments(); + if (\count($all)) { + throw new RuntimeException(sprintf('Too many arguments, expected arguments "%s".', implode('" "', array_keys($all)))); + } + + throw new RuntimeException(sprintf('No arguments expected, got "%s".', $token)); + } + } + + /** + * Adds a short option value. + * + * @throws RuntimeException When option given doesn't exist + */ + private function addShortOption(string $shortcut, $value) + { + if (!$this->definition->hasShortcut($shortcut)) { + throw new RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); + } + + /** + * Adds a long option value. + * + * @throws RuntimeException When option given doesn't exist + */ + private function addLongOption(string $name, $value) + { + if (!$this->definition->hasOption($name)) { + throw new RuntimeException(sprintf('The "--%s" option does not exist.', $name)); + } + + $option = $this->definition->getOption($name); + + if (null !== $value && !$option->acceptValue()) { + throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name)); + } + + if (\in_array($value, ['', null], true) && $option->acceptValue() && \count($this->parsed)) { + // if option accepts an optional or mandatory argument + // let's see if there is one provided + $next = array_shift($this->parsed); + if ((isset($next[0]) && '-' !== $next[0]) || \in_array($next, ['', null], true)) { + $value = $next; + } else { + array_unshift($this->parsed, $next); + } + } + + if (null === $value) { + if ($option->isValueRequired()) { + throw new RuntimeException(sprintf('The "--%s" option requires a value.', $name)); + } + + if (!$option->isArray() && !$option->isValueOptional()) { + $value = true; + } + } + + if ($option->isArray()) { + $this->options[$name][] = $value; + } else { + $this->options[$name] = $value; + } + } + + /** + * {@inheritdoc} + */ + public function getFirstArgument() + { + $isOption = false; + foreach ($this->tokens as $i => $token) { + if ($token && '-' === $token[0]) { + if (str_contains($token, '=') || !isset($this->tokens[$i + 1])) { + continue; + } + + // If it's a long option, consider that everything after "--" is the option name. + // Otherwise, use the last char (if it's a short option set, only the last one can take a value with space separator) + $name = '-' === $token[1] ? substr($token, 2) : substr($token, -1); + if (!isset($this->options[$name]) && !$this->definition->hasShortcut($name)) { + // noop + } elseif ((isset($this->options[$name]) || isset($this->options[$name = $this->definition->shortcutToName($name)])) && $this->tokens[$i + 1] === $this->options[$name]) { + $isOption = true; + } + + continue; + } + + if ($isOption) { + $isOption = false; + continue; + } + + return $token; + } + + return null; + } + + /** + * {@inheritdoc} + */ + public function hasParameterOption($values, $onlyParams = false) + { + $values = (array) $values; + + foreach ($this->tokens as $token) { + if ($onlyParams && '--' === $token) { + return false; + } + foreach ($values as $value) { + // Options with values: + // For long options, test for '--option=' at beginning + // For short options, test for '-o' at beginning + $leading = str_starts_with($value, '--') ? $value.'=' : $value; + if ($token === $value || '' !== $leading && str_starts_with($token, $leading)) { + return true; + } + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getParameterOption($values, $default = false, $onlyParams = false) + { + $values = (array) $values; + $tokens = $this->tokens; + + while (0 < \count($tokens)) { + $token = array_shift($tokens); + if ($onlyParams && '--' === $token) { + return $default; + } + + foreach ($values as $value) { + if ($token === $value) { + return array_shift($tokens); + } + // Options with values: + // For long options, test for '--option=' at beginning + // For short options, test for '-o' at beginning + $leading = str_starts_with($value, '--') ? $value.'=' : $value; + if ('' !== $leading && str_starts_with($token, $leading)) { + return substr($token, \strlen($leading)); + } + } + } + + return $default; + } + + /** + * Returns a stringified representation of the args passed to the command. + * + * @return string + */ + public function __toString() + { + $tokens = array_map(function ($token) { + if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) { + return $match[1].$this->escapeToken($match[2]); + } + + if ($token && '-' !== $token[0]) { + return $this->escapeToken($token); + } + + return $token; + }, $this->tokens); + + return implode(' ', $tokens); + } +} diff --git a/vendor/symfony/console/Input/ArrayInput.php b/vendor/symfony/console/Input/ArrayInput.php new file mode 100644 index 0000000000..30bd2054a6 --- /dev/null +++ b/vendor/symfony/console/Input/ArrayInput.php @@ -0,0 +1,203 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\InvalidOptionException; + +/** + * ArrayInput represents an input provided as an array. + * + * Usage: + * + * $input = new ArrayInput(['command' => 'foo:bar', 'foo' => 'bar', '--bar' => 'foobar']); + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class ArrayInput extends Input +{ + private $parameters; + + public function __construct(array $parameters, InputDefinition $definition = null) + { + $this->parameters = $parameters; + + parent::__construct($definition); + } + + /** + * {@inheritdoc} + */ + public function getFirstArgument() + { + foreach ($this->parameters as $param => $value) { + if ($param && \is_string($param) && '-' === $param[0]) { + continue; + } + + return $value; + } + + return null; + } + + /** + * {@inheritdoc} + */ + public function hasParameterOption($values, $onlyParams = false) + { + $values = (array) $values; + + foreach ($this->parameters as $k => $v) { + if (!\is_int($k)) { + $v = $k; + } + + if ($onlyParams && '--' === $v) { + return false; + } + + if (\in_array($v, $values)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getParameterOption($values, $default = false, $onlyParams = false) + { + $values = (array) $values; + + foreach ($this->parameters as $k => $v) { + if ($onlyParams && ('--' === $k || (\is_int($k) && '--' === $v))) { + return $default; + } + + if (\is_int($k)) { + if (\in_array($v, $values)) { + return true; + } + } elseif (\in_array($k, $values)) { + return $v; + } + } + + return $default; + } + + /** + * Returns a stringified representation of the args passed to the command. + * + * @return string + */ + public function __toString() + { + $params = []; + foreach ($this->parameters as $param => $val) { + if ($param && \is_string($param) && '-' === $param[0]) { + $glue = ('-' === $param[1]) ? '=' : ' '; + if (\is_array($val)) { + foreach ($val as $v) { + $params[] = $param.('' != $v ? $glue.$this->escapeToken($v) : ''); + } + } else { + $params[] = $param.('' != $val ? $glue.$this->escapeToken($val) : ''); + } + } else { + $params[] = \is_array($val) ? implode(' ', array_map([$this, 'escapeToken'], $val)) : $this->escapeToken($val); + } + } + + return implode(' ', $params); + } + + /** + * {@inheritdoc} + */ + protected function parse() + { + foreach ($this->parameters as $key => $value) { + if ('--' === $key) { + return; + } + if (str_starts_with($key, '--')) { + $this->addLongOption(substr($key, 2), $value); + } elseif (str_starts_with($key, '-')) { + $this->addShortOption(substr($key, 1), $value); + } else { + $this->addArgument($key, $value); + } + } + } + + /** + * Adds a short option value. + * + * @throws InvalidOptionException When option given doesn't exist + */ + private function addShortOption(string $shortcut, $value) + { + if (!$this->definition->hasShortcut($shortcut)) { + throw new InvalidOptionException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); + } + + /** + * Adds a long option value. + * + * @throws InvalidOptionException When option given doesn't exist + * @throws InvalidOptionException When a required value is missing + */ + private function addLongOption(string $name, $value) + { + if (!$this->definition->hasOption($name)) { + throw new InvalidOptionException(sprintf('The "--%s" option does not exist.', $name)); + } + + $option = $this->definition->getOption($name); + + if (null === $value) { + if ($option->isValueRequired()) { + throw new InvalidOptionException(sprintf('The "--%s" option requires a value.', $name)); + } + + if (!$option->isValueOptional()) { + $value = true; + } + } + + $this->options[$name] = $value; + } + + /** + * Adds an argument value. + * + * @param string|int $name The argument name + * @param mixed $value The value for the argument + * + * @throws InvalidArgumentException When argument given doesn't exist + */ + private function addArgument($name, $value) + { + if (!$this->definition->hasArgument($name)) { + throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $this->arguments[$name] = $value; + } +} diff --git a/vendor/symfony/console/Input/Input.php b/vendor/symfony/console/Input/Input.php new file mode 100644 index 0000000000..d7f29073e5 --- /dev/null +++ b/vendor/symfony/console/Input/Input.php @@ -0,0 +1,203 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; + +/** + * Input is the base class for all concrete Input classes. + * + * Three concrete classes are provided by default: + * + * * `ArgvInput`: The input comes from the CLI arguments (argv) + * * `StringInput`: The input is provided as a string + * * `ArrayInput`: The input is provided as an array + * + * @author Fabien Potencier <fabien@symfony.com> + */ +abstract class Input implements InputInterface, StreamableInputInterface +{ + protected $definition; + protected $stream; + protected $options = []; + protected $arguments = []; + protected $interactive = true; + + public function __construct(InputDefinition $definition = null) + { + if (null === $definition) { + $this->definition = new InputDefinition(); + } else { + $this->bind($definition); + $this->validate(); + } + } + + /** + * {@inheritdoc} + */ + public function bind(InputDefinition $definition) + { + $this->arguments = []; + $this->options = []; + $this->definition = $definition; + + $this->parse(); + } + + /** + * Processes command line arguments. + */ + abstract protected function parse(); + + /** + * {@inheritdoc} + */ + public function validate() + { + $definition = $this->definition; + $givenArguments = $this->arguments; + + $missingArguments = array_filter(array_keys($definition->getArguments()), function ($argument) use ($definition, $givenArguments) { + return !\array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired(); + }); + + if (\count($missingArguments) > 0) { + throw new RuntimeException(sprintf('Not enough arguments (missing: "%s").', implode(', ', $missingArguments))); + } + } + + /** + * {@inheritdoc} + */ + public function isInteractive() + { + return $this->interactive; + } + + /** + * {@inheritdoc} + */ + public function setInteractive($interactive) + { + $this->interactive = (bool) $interactive; + } + + /** + * {@inheritdoc} + */ + public function getArguments() + { + return array_merge($this->definition->getArgumentDefaults(), $this->arguments); + } + + /** + * {@inheritdoc} + */ + public function getArgument($name) + { + if (!$this->definition->hasArgument((string) $name)) { + throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + return $this->arguments[$name] ?? $this->definition->getArgument($name)->getDefault(); + } + + /** + * {@inheritdoc} + */ + public function setArgument($name, $value) + { + if (!$this->definition->hasArgument((string) $name)) { + throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $this->arguments[$name] = $value; + } + + /** + * {@inheritdoc} + */ + public function hasArgument($name) + { + return $this->definition->hasArgument((string) $name); + } + + /** + * {@inheritdoc} + */ + public function getOptions() + { + return array_merge($this->definition->getOptionDefaults(), $this->options); + } + + /** + * {@inheritdoc} + */ + public function getOption($name) + { + if (!$this->definition->hasOption($name)) { + throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + return \array_key_exists($name, $this->options) ? $this->options[$name] : $this->definition->getOption($name)->getDefault(); + } + + /** + * {@inheritdoc} + */ + public function setOption($name, $value) + { + if (!$this->definition->hasOption($name)) { + throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + $this->options[$name] = $value; + } + + /** + * {@inheritdoc} + */ + public function hasOption($name) + { + return $this->definition->hasOption($name); + } + + /** + * Escapes a token through escapeshellarg if it contains unsafe chars. + * + * @param string $token + * + * @return string + */ + public function escapeToken($token) + { + return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token); + } + + /** + * {@inheritdoc} + */ + public function setStream($stream) + { + $this->stream = $stream; + } + + /** + * {@inheritdoc} + */ + public function getStream() + { + return $this->stream; + } +} diff --git a/vendor/symfony/console/Input/InputArgument.php b/vendor/symfony/console/Input/InputArgument.php new file mode 100644 index 0000000000..accd4d0c5b --- /dev/null +++ b/vendor/symfony/console/Input/InputArgument.php @@ -0,0 +1,129 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; + +/** + * Represents a command line argument. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class InputArgument +{ + public const REQUIRED = 1; + public const OPTIONAL = 2; + public const IS_ARRAY = 4; + + private $name; + private $mode; + private $default; + private $description; + + /** + * @param string $name The argument name + * @param int|null $mode The argument mode: self::REQUIRED or self::OPTIONAL + * @param string $description A description text + * @param string|bool|int|float|array|null $default The default value (for self::OPTIONAL mode only) + * + * @throws InvalidArgumentException When argument mode is not valid + */ + public function __construct(string $name, int $mode = null, string $description = '', $default = null) + { + if (null === $mode) { + $mode = self::OPTIONAL; + } elseif ($mode > 7 || $mode < 1) { + throw new InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode)); + } + + $this->name = $name; + $this->mode = $mode; + $this->description = $description; + + $this->setDefault($default); + } + + /** + * Returns the argument name. + * + * @return string The argument name + */ + public function getName() + { + return $this->name; + } + + /** + * Returns true if the argument is required. + * + * @return bool true if parameter mode is self::REQUIRED, false otherwise + */ + public function isRequired() + { + return self::REQUIRED === (self::REQUIRED & $this->mode); + } + + /** + * Returns true if the argument can take multiple values. + * + * @return bool true if mode is self::IS_ARRAY, false otherwise + */ + public function isArray() + { + return self::IS_ARRAY === (self::IS_ARRAY & $this->mode); + } + + /** + * Sets the default value. + * + * @param string|bool|int|float|array|null $default + * + * @throws LogicException When incorrect default value is given + */ + public function setDefault($default = null) + { + if ($this->isRequired() && null !== $default) { + throw new LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.'); + } + + if ($this->isArray()) { + if (null === $default) { + $default = []; + } elseif (!\is_array($default)) { + throw new LogicException('A default value for an array argument must be an array.'); + } + } + + $this->default = $default; + } + + /** + * Returns the default value. + * + * @return string|bool|int|float|array|null + */ + public function getDefault() + { + return $this->default; + } + + /** + * Returns the description text. + * + * @return string The description text + */ + public function getDescription() + { + return $this->description; + } +} diff --git a/vendor/symfony/console/Input/InputAwareInterface.php b/vendor/symfony/console/Input/InputAwareInterface.php new file mode 100644 index 0000000000..5a288de5d4 --- /dev/null +++ b/vendor/symfony/console/Input/InputAwareInterface.php @@ -0,0 +1,26 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +/** + * InputAwareInterface should be implemented by classes that depends on the + * Console Input. + * + * @author Wouter J <waldio.webdesign@gmail.com> + */ +interface InputAwareInterface +{ + /** + * Sets the Console Input. + */ + public function setInput(InputInterface $input); +} diff --git a/vendor/symfony/console/Input/InputDefinition.php b/vendor/symfony/console/Input/InputDefinition.php new file mode 100644 index 0000000000..e2cd6d714f --- /dev/null +++ b/vendor/symfony/console/Input/InputDefinition.php @@ -0,0 +1,396 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; + +/** + * A InputDefinition represents a set of valid command line arguments and options. + * + * Usage: + * + * $definition = new InputDefinition([ + * new InputArgument('name', InputArgument::REQUIRED), + * new InputOption('foo', 'f', InputOption::VALUE_REQUIRED), + * ]); + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class InputDefinition +{ + private $arguments; + private $requiredCount; + private $hasAnArrayArgument = false; + private $hasOptional; + private $options; + private $shortcuts; + + /** + * @param array $definition An array of InputArgument and InputOption instance + */ + public function __construct(array $definition = []) + { + $this->setDefinition($definition); + } + + /** + * Sets the definition of the input. + */ + public function setDefinition(array $definition) + { + $arguments = []; + $options = []; + foreach ($definition as $item) { + if ($item instanceof InputOption) { + $options[] = $item; + } else { + $arguments[] = $item; + } + } + + $this->setArguments($arguments); + $this->setOptions($options); + } + + /** + * Sets the InputArgument objects. + * + * @param InputArgument[] $arguments An array of InputArgument objects + */ + public function setArguments($arguments = []) + { + $this->arguments = []; + $this->requiredCount = 0; + $this->hasOptional = false; + $this->hasAnArrayArgument = false; + $this->addArguments($arguments); + } + + /** + * Adds an array of InputArgument objects. + * + * @param InputArgument[] $arguments An array of InputArgument objects + */ + public function addArguments($arguments = []) + { + if (null !== $arguments) { + foreach ($arguments as $argument) { + $this->addArgument($argument); + } + } + } + + /** + * @throws LogicException When incorrect argument is given + */ + public function addArgument(InputArgument $argument) + { + if (isset($this->arguments[$argument->getName()])) { + throw new LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName())); + } + + if ($this->hasAnArrayArgument) { + throw new LogicException('Cannot add an argument after an array argument.'); + } + + if ($argument->isRequired() && $this->hasOptional) { + throw new LogicException('Cannot add a required argument after an optional one.'); + } + + if ($argument->isArray()) { + $this->hasAnArrayArgument = true; + } + + if ($argument->isRequired()) { + ++$this->requiredCount; + } else { + $this->hasOptional = true; + } + + $this->arguments[$argument->getName()] = $argument; + } + + /** + * Returns an InputArgument by name or by position. + * + * @param string|int $name The InputArgument name or position + * + * @return InputArgument An InputArgument object + * + * @throws InvalidArgumentException When argument given doesn't exist + */ + public function getArgument($name) + { + if (!$this->hasArgument($name)) { + throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $arguments = \is_int($name) ? array_values($this->arguments) : $this->arguments; + + return $arguments[$name]; + } + + /** + * Returns true if an InputArgument object exists by name or position. + * + * @param string|int $name The InputArgument name or position + * + * @return bool true if the InputArgument object exists, false otherwise + */ + public function hasArgument($name) + { + $arguments = \is_int($name) ? array_values($this->arguments) : $this->arguments; + + return isset($arguments[$name]); + } + + /** + * Gets the array of InputArgument objects. + * + * @return InputArgument[] An array of InputArgument objects + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * Returns the number of InputArguments. + * + * @return int The number of InputArguments + */ + public function getArgumentCount() + { + return $this->hasAnArrayArgument ? \PHP_INT_MAX : \count($this->arguments); + } + + /** + * Returns the number of required InputArguments. + * + * @return int The number of required InputArguments + */ + public function getArgumentRequiredCount() + { + return $this->requiredCount; + } + + /** + * @return array<string|bool|int|float|array|null> + */ + public function getArgumentDefaults() + { + $values = []; + foreach ($this->arguments as $argument) { + $values[$argument->getName()] = $argument->getDefault(); + } + + return $values; + } + + /** + * Sets the InputOption objects. + * + * @param InputOption[] $options An array of InputOption objects + */ + public function setOptions($options = []) + { + $this->options = []; + $this->shortcuts = []; + $this->addOptions($options); + } + + /** + * Adds an array of InputOption objects. + * + * @param InputOption[] $options An array of InputOption objects + */ + public function addOptions($options = []) + { + foreach ($options as $option) { + $this->addOption($option); + } + } + + /** + * @throws LogicException When option given already exist + */ + public function addOption(InputOption $option) + { + if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) { + throw new LogicException(sprintf('An option named "%s" already exists.', $option->getName())); + } + + if ($option->getShortcut()) { + foreach (explode('|', $option->getShortcut()) as $shortcut) { + if (isset($this->shortcuts[$shortcut]) && !$option->equals($this->options[$this->shortcuts[$shortcut]])) { + throw new LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut)); + } + } + } + + $this->options[$option->getName()] = $option; + if ($option->getShortcut()) { + foreach (explode('|', $option->getShortcut()) as $shortcut) { + $this->shortcuts[$shortcut] = $option->getName(); + } + } + } + + /** + * Returns an InputOption by name. + * + * @param string $name The InputOption name + * + * @return InputOption A InputOption object + * + * @throws InvalidArgumentException When option given doesn't exist + */ + public function getOption($name) + { + if (!$this->hasOption($name)) { + throw new InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); + } + + return $this->options[$name]; + } + + /** + * Returns true if an InputOption object exists by name. + * + * This method can't be used to check if the user included the option when + * executing the command (use getOption() instead). + * + * @param string $name The InputOption name + * + * @return bool true if the InputOption object exists, false otherwise + */ + public function hasOption($name) + { + return isset($this->options[$name]); + } + + /** + * Gets the array of InputOption objects. + * + * @return InputOption[] An array of InputOption objects + */ + public function getOptions() + { + return $this->options; + } + + /** + * Returns true if an InputOption object exists by shortcut. + * + * @param string $name The InputOption shortcut + * + * @return bool true if the InputOption object exists, false otherwise + */ + public function hasShortcut($name) + { + return isset($this->shortcuts[$name]); + } + + /** + * Gets an InputOption by shortcut. + * + * @param string $shortcut The Shortcut name + * + * @return InputOption An InputOption object + */ + public function getOptionForShortcut($shortcut) + { + return $this->getOption($this->shortcutToName($shortcut)); + } + + /** + * @return array<string|bool|int|float|array|null> + */ + public function getOptionDefaults() + { + $values = []; + foreach ($this->options as $option) { + $values[$option->getName()] = $option->getDefault(); + } + + return $values; + } + + /** + * Returns the InputOption name given a shortcut. + * + * @throws InvalidArgumentException When option given does not exist + * + * @internal + */ + public function shortcutToName(string $shortcut): string + { + if (!isset($this->shortcuts[$shortcut])) { + throw new InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + return $this->shortcuts[$shortcut]; + } + + /** + * Gets the synopsis. + * + * @param bool $short Whether to return the short version (with options folded) or not + * + * @return string The synopsis + */ + public function getSynopsis($short = false) + { + $elements = []; + + if ($short && $this->getOptions()) { + $elements[] = '[options]'; + } elseif (!$short) { + foreach ($this->getOptions() as $option) { + $value = ''; + if ($option->acceptValue()) { + $value = sprintf( + ' %s%s%s', + $option->isValueOptional() ? '[' : '', + strtoupper($option->getName()), + $option->isValueOptional() ? ']' : '' + ); + } + + $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : ''; + $elements[] = sprintf('[%s--%s%s]', $shortcut, $option->getName(), $value); + } + } + + if (\count($elements) && $this->getArguments()) { + $elements[] = '[--]'; + } + + $tail = ''; + foreach ($this->getArguments() as $argument) { + $element = '<'.$argument->getName().'>'; + if ($argument->isArray()) { + $element .= '...'; + } + + if (!$argument->isRequired()) { + $element = '['.$element; + $tail .= ']'; + } + + $elements[] = $element; + } + + return implode(' ', $elements).$tail; + } +} diff --git a/vendor/symfony/console/Input/InputInterface.php b/vendor/symfony/console/Input/InputInterface.php new file mode 100644 index 0000000000..8efc623268 --- /dev/null +++ b/vendor/symfony/console/Input/InputInterface.php @@ -0,0 +1,163 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; + +/** + * InputInterface is the interface implemented by all input classes. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +interface InputInterface +{ + /** + * Returns the first argument from the raw parameters (not parsed). + * + * @return string|null The value of the first argument or null otherwise + */ + public function getFirstArgument(); + + /** + * Returns true if the raw parameters (not parsed) contain a value. + * + * This method is to be used to introspect the input parameters + * before they have been validated. It must be used carefully. + * Does not necessarily return the correct result for short options + * when multiple flags are combined in the same option. + * + * @param string|array $values The values to look for in the raw parameters (can be an array) + * @param bool $onlyParams Only check real parameters, skip those following an end of options (--) signal + * + * @return bool true if the value is contained in the raw parameters + */ + public function hasParameterOption($values, $onlyParams = false); + + /** + * Returns the value of a raw option (not parsed). + * + * This method is to be used to introspect the input parameters + * before they have been validated. It must be used carefully. + * Does not necessarily return the correct result for short options + * when multiple flags are combined in the same option. + * + * @param string|array $values The value(s) to look for in the raw parameters (can be an array) + * @param string|bool|int|float|array|null $default The default value to return if no result is found + * @param bool $onlyParams Only check real parameters, skip those following an end of options (--) signal + * + * @return mixed The option value + */ + public function getParameterOption($values, $default = false, $onlyParams = false); + + /** + * Binds the current Input instance with the given arguments and options. + * + * @throws RuntimeException + */ + public function bind(InputDefinition $definition); + + /** + * Validates the input. + * + * @throws RuntimeException When not enough arguments are given + */ + public function validate(); + + /** + * Returns all the given arguments merged with the default values. + * + * @return array<string|bool|int|float|array|null> + */ + public function getArguments(); + + /** + * Returns the argument value for a given argument name. + * + * @param string $name The argument name + * + * @return mixed + * + * @throws InvalidArgumentException When argument given doesn't exist + */ + public function getArgument($name); + + /** + * Sets an argument value by name. + * + * @param string $name The argument name + * @param mixed $value The argument value + * + * @throws InvalidArgumentException When argument given doesn't exist + */ + public function setArgument($name, $value); + + /** + * Returns true if an InputArgument object exists by name or position. + * + * @param string $name The argument name + * + * @return bool true if the InputArgument object exists, false otherwise + */ + public function hasArgument($name); + + /** + * Returns all the given options merged with the default values. + * + * @return array<string|bool|int|float|array|null> + */ + public function getOptions(); + + /** + * Returns the option value for a given option name. + * + * @param string $name The option name + * + * @return mixed + * + * @throws InvalidArgumentException When option given doesn't exist + */ + public function getOption($name); + + /** + * Sets an option value by name. + * + * @param string $name The option name + * @param mixed $value The option value + * + * @throws InvalidArgumentException When option given doesn't exist + */ + public function setOption($name, $value); + + /** + * Returns true if an InputOption object exists by name. + * + * @param string $name The InputOption name + * + * @return bool true if the InputOption object exists, false otherwise + */ + public function hasOption($name); + + /** + * Is this input means interactive? + * + * @return bool + */ + public function isInteractive(); + + /** + * Sets the input interactivity. + * + * @param bool $interactive If the input should be interactive + */ + public function setInteractive($interactive); +} diff --git a/vendor/symfony/console/Input/InputOption.php b/vendor/symfony/console/Input/InputOption.php new file mode 100644 index 0000000000..c7729db20b --- /dev/null +++ b/vendor/symfony/console/Input/InputOption.php @@ -0,0 +1,219 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; + +/** + * Represents a command line option. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class InputOption +{ + /** + * Do not accept input for the option (e.g. --yell). This is the default behavior of options. + */ + public const VALUE_NONE = 1; + + /** + * A value must be passed when the option is used (e.g. --iterations=5 or -i5). + */ + public const VALUE_REQUIRED = 2; + + /** + * The option may or may not have a value (e.g. --yell or --yell=loud). + */ + public const VALUE_OPTIONAL = 4; + + /** + * The option accepts multiple values (e.g. --dir=/foo --dir=/bar). + */ + public const VALUE_IS_ARRAY = 8; + + private $name; + private $shortcut; + private $mode; + private $default; + private $description; + + /** + * @param string $name The option name + * @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts + * @param int|null $mode The option mode: One of the VALUE_* constants + * @param string $description A description text + * @param string|bool|int|float|array|null $default The default value (must be null for self::VALUE_NONE) + * + * @throws InvalidArgumentException If option mode is invalid or incompatible + */ + public function __construct(string $name, $shortcut = null, int $mode = null, string $description = '', $default = null) + { + if (str_starts_with($name, '--')) { + $name = substr($name, 2); + } + + if (empty($name)) { + throw new InvalidArgumentException('An option name cannot be empty.'); + } + + if (empty($shortcut)) { + $shortcut = null; + } + + if (null !== $shortcut) { + if (\is_array($shortcut)) { + $shortcut = implode('|', $shortcut); + } + $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-')); + $shortcuts = array_filter($shortcuts); + $shortcut = implode('|', $shortcuts); + + if (empty($shortcut)) { + throw new InvalidArgumentException('An option shortcut cannot be empty.'); + } + } + + if (null === $mode) { + $mode = self::VALUE_NONE; + } elseif ($mode > 15 || $mode < 1) { + throw new InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode)); + } + + $this->name = $name; + $this->shortcut = $shortcut; + $this->mode = $mode; + $this->description = $description; + + if ($this->isArray() && !$this->acceptValue()) { + throw new InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.'); + } + + $this->setDefault($default); + } + + /** + * Returns the option shortcut. + * + * @return string|null The shortcut + */ + public function getShortcut() + { + return $this->shortcut; + } + + /** + * Returns the option name. + * + * @return string The name + */ + public function getName() + { + return $this->name; + } + + /** + * Returns true if the option accepts a value. + * + * @return bool true if value mode is not self::VALUE_NONE, false otherwise + */ + public function acceptValue() + { + return $this->isValueRequired() || $this->isValueOptional(); + } + + /** + * Returns true if the option requires a value. + * + * @return bool true if value mode is self::VALUE_REQUIRED, false otherwise + */ + public function isValueRequired() + { + return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode); + } + + /** + * Returns true if the option takes an optional value. + * + * @return bool true if value mode is self::VALUE_OPTIONAL, false otherwise + */ + public function isValueOptional() + { + return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode); + } + + /** + * Returns true if the option can take multiple values. + * + * @return bool true if mode is self::VALUE_IS_ARRAY, false otherwise + */ + public function isArray() + { + return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode); + } + + /** + * @param string|bool|int|float|array|null $default + */ + public function setDefault($default = null) + { + if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) { + throw new LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.'); + } + + if ($this->isArray()) { + if (null === $default) { + $default = []; + } elseif (!\is_array($default)) { + throw new LogicException('A default value for an array option must be an array.'); + } + } + + $this->default = $this->acceptValue() ? $default : false; + } + + /** + * Returns the default value. + * + * @return string|bool|int|float|array|null + */ + public function getDefault() + { + return $this->default; + } + + /** + * Returns the description text. + * + * @return string The description text + */ + public function getDescription() + { + return $this->description; + } + + /** + * Checks whether the given option equals this one. + * + * @return bool + */ + public function equals(self $option) + { + return $option->getName() === $this->getName() + && $option->getShortcut() === $this->getShortcut() + && $option->getDefault() === $this->getDefault() + && $option->isArray() === $this->isArray() + && $option->isValueRequired() === $this->isValueRequired() + && $option->isValueOptional() === $this->isValueOptional() + ; + } +} diff --git a/vendor/symfony/console/Input/StreamableInputInterface.php b/vendor/symfony/console/Input/StreamableInputInterface.php new file mode 100644 index 0000000000..d7e462f244 --- /dev/null +++ b/vendor/symfony/console/Input/StreamableInputInterface.php @@ -0,0 +1,37 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +/** + * StreamableInputInterface is the interface implemented by all input classes + * that have an input stream. + * + * @author Robin Chalas <robin.chalas@gmail.com> + */ +interface StreamableInputInterface extends InputInterface +{ + /** + * Sets the input stream to read from when interacting with the user. + * + * This is mainly useful for testing purpose. + * + * @param resource $stream The input stream + */ + public function setStream($stream); + + /** + * Returns the input stream. + * + * @return resource|null + */ + public function getStream(); +} diff --git a/vendor/symfony/console/Input/StringInput.php b/vendor/symfony/console/Input/StringInput.php new file mode 100644 index 0000000000..56bb66cbfd --- /dev/null +++ b/vendor/symfony/console/Input/StringInput.php @@ -0,0 +1,84 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * StringInput represents an input provided as a string. + * + * Usage: + * + * $input = new StringInput('foo --bar="foobar"'); + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class StringInput extends ArgvInput +{ + public const REGEX_STRING = '([^\s]+?)(?:\s|(?<!\\\\)"|(?<!\\\\)\'|$)'; + public const REGEX_UNQUOTED_STRING = '([^\s\\\\]+?)'; + public const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')'; + + /** + * @param string $input A string representing the parameters from the CLI + */ + public function __construct(string $input) + { + parent::__construct([]); + + $this->setTokens($this->tokenize($input)); + } + + /** + * Tokenizes a string. + * + * @throws InvalidArgumentException When unable to parse input (should never happen) + */ + private function tokenize(string $input): array + { + $tokens = []; + $length = \strlen($input); + $cursor = 0; + $token = null; + while ($cursor < $length) { + if ('\\' === $input[$cursor]) { + $token .= $input[++$cursor] ?? ''; + ++$cursor; + continue; + } + + if (preg_match('/\s+/A', $input, $match, 0, $cursor)) { + if (null !== $token) { + $tokens[] = $token; + $token = null; + } + } elseif (preg_match('/([^="\'\s]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, 0, $cursor)) { + $token .= $match[1].$match[2].stripcslashes(str_replace(['"\'', '\'"', '\'\'', '""'], '', substr($match[3], 1, -1))); + } elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, 0, $cursor)) { + $token .= stripcslashes(substr($match[0], 1, -1)); + } elseif (preg_match('/'.self::REGEX_UNQUOTED_STRING.'/A', $input, $match, 0, $cursor)) { + $token .= $match[1]; + } else { + // should never happen + throw new InvalidArgumentException(sprintf('Unable to parse input near "... %s ...".', substr($input, $cursor, 10))); + } + + $cursor += \strlen($match[0]); + } + + if (null !== $token) { + $tokens[] = $token; + } + + return $tokens; + } +} diff --git a/vendor/symfony/console/LICENSE b/vendor/symfony/console/LICENSE new file mode 100644 index 0000000000..88bf75bb4d --- /dev/null +++ b/vendor/symfony/console/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/console/Logger/ConsoleLogger.php b/vendor/symfony/console/Logger/ConsoleLogger.php new file mode 100644 index 0000000000..4a10fa1720 --- /dev/null +++ b/vendor/symfony/console/Logger/ConsoleLogger.php @@ -0,0 +1,126 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Logger; + +use Psr\Log\AbstractLogger; +use Psr\Log\InvalidArgumentException; +use Psr\Log\LogLevel; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * PSR-3 compliant console logger. + * + * @author Kévin Dunglas <dunglas@gmail.com> + * + * @see https://www.php-fig.org/psr/psr-3/ + */ +class ConsoleLogger extends AbstractLogger +{ + public const INFO = 'info'; + public const ERROR = 'error'; + + private $output; + private $verbosityLevelMap = [ + LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL, + LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL, + LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL, + LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL, + LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL, + LogLevel::NOTICE => OutputInterface::VERBOSITY_VERBOSE, + LogLevel::INFO => OutputInterface::VERBOSITY_VERY_VERBOSE, + LogLevel::DEBUG => OutputInterface::VERBOSITY_DEBUG, + ]; + private $formatLevelMap = [ + LogLevel::EMERGENCY => self::ERROR, + LogLevel::ALERT => self::ERROR, + LogLevel::CRITICAL => self::ERROR, + LogLevel::ERROR => self::ERROR, + LogLevel::WARNING => self::INFO, + LogLevel::NOTICE => self::INFO, + LogLevel::INFO => self::INFO, + LogLevel::DEBUG => self::INFO, + ]; + private $errored = false; + + public function __construct(OutputInterface $output, array $verbosityLevelMap = [], array $formatLevelMap = []) + { + $this->output = $output; + $this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap; + $this->formatLevelMap = $formatLevelMap + $this->formatLevelMap; + } + + /** + * {@inheritdoc} + * + * @return void + */ + public function log($level, $message, array $context = []) + { + if (!isset($this->verbosityLevelMap[$level])) { + throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level)); + } + + $output = $this->output; + + // Write to the error output if necessary and available + if (self::ERROR === $this->formatLevelMap[$level]) { + if ($this->output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + $this->errored = true; + } + + // the if condition check isn't necessary -- it's the same one that $output will do internally anyway. + // We only do it for efficiency here as the message formatting is relatively expensive. + if ($output->getVerbosity() >= $this->verbosityLevelMap[$level]) { + $output->writeln(sprintf('<%1$s>[%2$s] %3$s</%1$s>', $this->formatLevelMap[$level], $level, $this->interpolate($message, $context)), $this->verbosityLevelMap[$level]); + } + } + + /** + * Returns true when any messages have been logged at error levels. + * + * @return bool + */ + public function hasErrored() + { + return $this->errored; + } + + /** + * Interpolates context values into the message placeholders. + * + * @author PHP Framework Interoperability Group + */ + private function interpolate(string $message, array $context): string + { + if (!str_contains($message, '{')) { + return $message; + } + + $replacements = []; + foreach ($context as $key => $val) { + if (null === $val || \is_scalar($val) || (\is_object($val) && method_exists($val, '__toString'))) { + $replacements["{{$key}}"] = $val; + } elseif ($val instanceof \DateTimeInterface) { + $replacements["{{$key}}"] = $val->format(\DateTime::RFC3339); + } elseif (\is_object($val)) { + $replacements["{{$key}}"] = '[object '.\get_class($val).']'; + } else { + $replacements["{{$key}}"] = '['.\gettype($val).']'; + } + } + + return strtr($message, $replacements); + } +} diff --git a/vendor/symfony/console/Output/BufferedOutput.php b/vendor/symfony/console/Output/BufferedOutput.php new file mode 100644 index 0000000000..fefaac2717 --- /dev/null +++ b/vendor/symfony/console/Output/BufferedOutput.php @@ -0,0 +1,45 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +/** + * @author Jean-François Simon <contact@jfsimon.fr> + */ +class BufferedOutput extends Output +{ + private $buffer = ''; + + /** + * Empties buffer and returns its content. + * + * @return string + */ + public function fetch() + { + $content = $this->buffer; + $this->buffer = ''; + + return $content; + } + + /** + * {@inheritdoc} + */ + protected function doWrite($message, $newline) + { + $this->buffer .= $message; + + if ($newline) { + $this->buffer .= \PHP_EOL; + } + } +} diff --git a/vendor/symfony/console/Output/ConsoleOutput.php b/vendor/symfony/console/Output/ConsoleOutput.php new file mode 100644 index 0000000000..484fcbdea2 --- /dev/null +++ b/vendor/symfony/console/Output/ConsoleOutput.php @@ -0,0 +1,172 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * ConsoleOutput is the default class for all CLI output. It uses STDOUT and STDERR. + * + * This class is a convenient wrapper around `StreamOutput` for both STDOUT and STDERR. + * + * $output = new ConsoleOutput(); + * + * This is equivalent to: + * + * $output = new StreamOutput(fopen('php://stdout', 'w')); + * $stdErr = new StreamOutput(fopen('php://stderr', 'w')); + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface +{ + private $stderr; + private $consoleSectionOutputs = []; + + /** + * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) + * @param bool|null $decorated Whether to decorate messages (null for auto-guessing) + * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) + */ + public function __construct(int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = null, OutputFormatterInterface $formatter = null) + { + parent::__construct($this->openOutputStream(), $verbosity, $decorated, $formatter); + + if (null === $formatter) { + // for BC reasons, stdErr has it own Formatter only when user don't inject a specific formatter. + $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated); + + return; + } + + $actualDecorated = $this->isDecorated(); + $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated, $this->getFormatter()); + + if (null === $decorated) { + $this->setDecorated($actualDecorated && $this->stderr->isDecorated()); + } + } + + /** + * Creates a new output section. + */ + public function section(): ConsoleSectionOutput + { + return new ConsoleSectionOutput($this->getStream(), $this->consoleSectionOutputs, $this->getVerbosity(), $this->isDecorated(), $this->getFormatter()); + } + + /** + * {@inheritdoc} + */ + public function setDecorated($decorated) + { + parent::setDecorated($decorated); + $this->stderr->setDecorated($decorated); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(OutputFormatterInterface $formatter) + { + parent::setFormatter($formatter); + $this->stderr->setFormatter($formatter); + } + + /** + * {@inheritdoc} + */ + public function setVerbosity($level) + { + parent::setVerbosity($level); + $this->stderr->setVerbosity($level); + } + + /** + * {@inheritdoc} + */ + public function getErrorOutput() + { + return $this->stderr; + } + + /** + * {@inheritdoc} + */ + public function setErrorOutput(OutputInterface $error) + { + $this->stderr = $error; + } + + /** + * Returns true if current environment supports writing console output to + * STDOUT. + * + * @return bool + */ + protected function hasStdoutSupport() + { + return false === $this->isRunningOS400(); + } + + /** + * Returns true if current environment supports writing console output to + * STDERR. + * + * @return bool + */ + protected function hasStderrSupport() + { + return false === $this->isRunningOS400(); + } + + /** + * Checks if current executing environment is IBM iSeries (OS400), which + * doesn't properly convert character-encodings between ASCII to EBCDIC. + */ + private function isRunningOS400(): bool + { + $checks = [ + \function_exists('php_uname') ? php_uname('s') : '', + getenv('OSTYPE'), + \PHP_OS, + ]; + + return false !== stripos(implode(';', $checks), 'OS400'); + } + + /** + * @return resource + */ + private function openOutputStream() + { + if (!$this->hasStdoutSupport()) { + return fopen('php://output', 'w'); + } + + // Use STDOUT when possible to prevent from opening too many file descriptors + return \defined('STDOUT') ? \STDOUT : (@fopen('php://stdout', 'w') ?: fopen('php://output', 'w')); + } + + /** + * @return resource + */ + private function openErrorStream() + { + if (!$this->hasStderrSupport()) { + return fopen('php://output', 'w'); + } + + // Use STDERR when possible to prevent from opening too many file descriptors + return \defined('STDERR') ? \STDERR : (@fopen('php://stderr', 'w') ?: fopen('php://output', 'w')); + } +} diff --git a/vendor/symfony/console/Output/ConsoleOutputInterface.php b/vendor/symfony/console/Output/ConsoleOutputInterface.php new file mode 100644 index 0000000000..f4c2fa623a --- /dev/null +++ b/vendor/symfony/console/Output/ConsoleOutputInterface.php @@ -0,0 +1,32 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +/** + * ConsoleOutputInterface is the interface implemented by ConsoleOutput class. + * This adds information about stderr and section output stream. + * + * @author Dariusz Górecki <darek.krk@gmail.com> + * + * @method ConsoleSectionOutput section() Creates a new output section + */ +interface ConsoleOutputInterface extends OutputInterface +{ + /** + * Gets the OutputInterface for errors. + * + * @return OutputInterface + */ + public function getErrorOutput(); + + public function setErrorOutput(OutputInterface $error); +} diff --git a/vendor/symfony/console/Output/ConsoleSectionOutput.php b/vendor/symfony/console/Output/ConsoleSectionOutput.php new file mode 100644 index 0000000000..c19edbf95e --- /dev/null +++ b/vendor/symfony/console/Output/ConsoleSectionOutput.php @@ -0,0 +1,143 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Terminal; + +/** + * @author Pierre du Plessis <pdples@gmail.com> + * @author Gabriel Ostrolucký <gabriel.ostrolucky@gmail.com> + */ +class ConsoleSectionOutput extends StreamOutput +{ + private $content = []; + private $lines = 0; + private $sections; + private $terminal; + + /** + * @param resource $stream + * @param ConsoleSectionOutput[] $sections + */ + public function __construct($stream, array &$sections, int $verbosity, bool $decorated, OutputFormatterInterface $formatter) + { + parent::__construct($stream, $verbosity, $decorated, $formatter); + array_unshift($sections, $this); + $this->sections = &$sections; + $this->terminal = new Terminal(); + } + + /** + * Clears previous output for this section. + * + * @param int $lines Number of lines to clear. If null, then the entire output of this section is cleared + */ + public function clear(int $lines = null) + { + if (empty($this->content) || !$this->isDecorated()) { + return; + } + + if ($lines) { + array_splice($this->content, -($lines * 2)); // Multiply lines by 2 to cater for each new line added between content + } else { + $lines = $this->lines; + $this->content = []; + } + + $this->lines -= $lines; + + parent::doWrite($this->popStreamContentUntilCurrentSection($lines), false); + } + + /** + * Overwrites the previous output with a new message. + * + * @param array|string $message + */ + public function overwrite($message) + { + $this->clear(); + $this->writeln($message); + } + + public function getContent(): string + { + return implode('', $this->content); + } + + /** + * @internal + */ + public function addContent(string $input) + { + foreach (explode(\PHP_EOL, $input) as $lineContent) { + $this->lines += ceil($this->getDisplayLength($lineContent) / $this->terminal->getWidth()) ?: 1; + $this->content[] = $lineContent; + $this->content[] = \PHP_EOL; + } + } + + /** + * {@inheritdoc} + */ + protected function doWrite($message, $newline) + { + if (!$this->isDecorated()) { + parent::doWrite($message, $newline); + + return; + } + + $erasedContent = $this->popStreamContentUntilCurrentSection(); + + $this->addContent($message); + + parent::doWrite($message, true); + parent::doWrite($erasedContent, false); + } + + /** + * At initial stage, cursor is at the end of stream output. This method makes cursor crawl upwards until it hits + * current section. Then it erases content it crawled through. Optionally, it erases part of current section too. + */ + private function popStreamContentUntilCurrentSection(int $numberOfLinesToClearFromCurrentSection = 0): string + { + $numberOfLinesToClear = $numberOfLinesToClearFromCurrentSection; + $erasedContent = []; + + foreach ($this->sections as $section) { + if ($section === $this) { + break; + } + + $numberOfLinesToClear += $section->lines; + $erasedContent[] = $section->getContent(); + } + + if ($numberOfLinesToClear > 0) { + // move cursor up n lines + parent::doWrite(sprintf("\x1b[%dA", $numberOfLinesToClear), false); + // erase to end of screen + parent::doWrite("\x1b[0J", false); + } + + return implode('', array_reverse($erasedContent)); + } + + private function getDisplayLength(string $text): string + { + return Helper::strlenWithoutDecoration($this->getFormatter(), str_replace("\t", ' ', $text)); + } +} diff --git a/vendor/symfony/console/Output/NullOutput.php b/vendor/symfony/console/Output/NullOutput.php new file mode 100644 index 0000000000..218f285bfe --- /dev/null +++ b/vendor/symfony/console/Output/NullOutput.php @@ -0,0 +1,123 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * NullOutput suppresses all output. + * + * $output = new NullOutput(); + * + * @author Fabien Potencier <fabien@symfony.com> + * @author Tobias Schultze <http://tobion.de> + */ +class NullOutput implements OutputInterface +{ + /** + * {@inheritdoc} + */ + public function setFormatter(OutputFormatterInterface $formatter) + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + // to comply with the interface we must return a OutputFormatterInterface + return new OutputFormatter(); + } + + /** + * {@inheritdoc} + */ + public function setDecorated($decorated) + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function isDecorated() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function setVerbosity($level) + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function getVerbosity() + { + return self::VERBOSITY_QUIET; + } + + /** + * {@inheritdoc} + */ + public function isQuiet() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function isVerbose() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isVeryVerbose() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isDebug() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function writeln($messages, $options = self::OUTPUT_NORMAL) + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function write($messages, $newline = false, $options = self::OUTPUT_NORMAL) + { + // do nothing + } +} diff --git a/vendor/symfony/console/Output/Output.php b/vendor/symfony/console/Output/Output.php new file mode 100644 index 0000000000..fb838f0532 --- /dev/null +++ b/vendor/symfony/console/Output/Output.php @@ -0,0 +1,177 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * Base class for output classes. + * + * There are five levels of verbosity: + * + * * normal: no option passed (normal output) + * * verbose: -v (more output) + * * very verbose: -vv (highly extended output) + * * debug: -vvv (all debug output) + * * quiet: -q (no output) + * + * @author Fabien Potencier <fabien@symfony.com> + */ +abstract class Output implements OutputInterface +{ + private $verbosity; + private $formatter; + + /** + * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) + * @param bool $decorated Whether to decorate messages + * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) + */ + public function __construct(?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, OutputFormatterInterface $formatter = null) + { + $this->verbosity = null === $verbosity ? self::VERBOSITY_NORMAL : $verbosity; + $this->formatter = $formatter ?? new OutputFormatter(); + $this->formatter->setDecorated($decorated); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(OutputFormatterInterface $formatter) + { + $this->formatter = $formatter; + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + return $this->formatter; + } + + /** + * {@inheritdoc} + */ + public function setDecorated($decorated) + { + $this->formatter->setDecorated($decorated); + } + + /** + * {@inheritdoc} + */ + public function isDecorated() + { + return $this->formatter->isDecorated(); + } + + /** + * {@inheritdoc} + */ + public function setVerbosity($level) + { + $this->verbosity = (int) $level; + } + + /** + * {@inheritdoc} + */ + public function getVerbosity() + { + return $this->verbosity; + } + + /** + * {@inheritdoc} + */ + public function isQuiet() + { + return self::VERBOSITY_QUIET === $this->verbosity; + } + + /** + * {@inheritdoc} + */ + public function isVerbose() + { + return self::VERBOSITY_VERBOSE <= $this->verbosity; + } + + /** + * {@inheritdoc} + */ + public function isVeryVerbose() + { + return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; + } + + /** + * {@inheritdoc} + */ + public function isDebug() + { + return self::VERBOSITY_DEBUG <= $this->verbosity; + } + + /** + * {@inheritdoc} + */ + public function writeln($messages, $options = self::OUTPUT_NORMAL) + { + $this->write($messages, true, $options); + } + + /** + * {@inheritdoc} + */ + public function write($messages, $newline = false, $options = self::OUTPUT_NORMAL) + { + if (!is_iterable($messages)) { + $messages = [$messages]; + } + + $types = self::OUTPUT_NORMAL | self::OUTPUT_RAW | self::OUTPUT_PLAIN; + $type = $types & $options ?: self::OUTPUT_NORMAL; + + $verbosities = self::VERBOSITY_QUIET | self::VERBOSITY_NORMAL | self::VERBOSITY_VERBOSE | self::VERBOSITY_VERY_VERBOSE | self::VERBOSITY_DEBUG; + $verbosity = $verbosities & $options ?: self::VERBOSITY_NORMAL; + + if ($verbosity > $this->getVerbosity()) { + return; + } + + foreach ($messages as $message) { + switch ($type) { + case OutputInterface::OUTPUT_NORMAL: + $message = $this->formatter->format($message); + break; + case OutputInterface::OUTPUT_RAW: + break; + case OutputInterface::OUTPUT_PLAIN: + $message = strip_tags($this->formatter->format($message)); + break; + } + + $this->doWrite($message ?? '', $newline); + } + } + + /** + * Writes a message to the output. + * + * @param string $message A message to write to the output + * @param bool $newline Whether to add a newline or not + */ + abstract protected function doWrite($message, $newline); +} diff --git a/vendor/symfony/console/Output/OutputInterface.php b/vendor/symfony/console/Output/OutputInterface.php new file mode 100644 index 0000000000..671d5bd788 --- /dev/null +++ b/vendor/symfony/console/Output/OutputInterface.php @@ -0,0 +1,114 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * OutputInterface is the interface implemented by all Output classes. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +interface OutputInterface +{ + public const VERBOSITY_QUIET = 16; + public const VERBOSITY_NORMAL = 32; + public const VERBOSITY_VERBOSE = 64; + public const VERBOSITY_VERY_VERBOSE = 128; + public const VERBOSITY_DEBUG = 256; + + public const OUTPUT_NORMAL = 1; + public const OUTPUT_RAW = 2; + public const OUTPUT_PLAIN = 4; + + /** + * Writes a message to the output. + * + * @param string|iterable $messages The message as an iterable of strings or a single string + * @param bool $newline Whether to add a newline + * @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL + */ + public function write($messages, $newline = false, $options = 0); + + /** + * Writes a message to the output and adds a newline at the end. + * + * @param string|iterable $messages The message as an iterable of strings or a single string + * @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL + */ + public function writeln($messages, $options = 0); + + /** + * Sets the verbosity of the output. + * + * @param int $level The level of verbosity (one of the VERBOSITY constants) + */ + public function setVerbosity($level); + + /** + * Gets the current verbosity of the output. + * + * @return int The current level of verbosity (one of the VERBOSITY constants) + */ + public function getVerbosity(); + + /** + * Returns whether verbosity is quiet (-q). + * + * @return bool true if verbosity is set to VERBOSITY_QUIET, false otherwise + */ + public function isQuiet(); + + /** + * Returns whether verbosity is verbose (-v). + * + * @return bool true if verbosity is set to VERBOSITY_VERBOSE, false otherwise + */ + public function isVerbose(); + + /** + * Returns whether verbosity is very verbose (-vv). + * + * @return bool true if verbosity is set to VERBOSITY_VERY_VERBOSE, false otherwise + */ + public function isVeryVerbose(); + + /** + * Returns whether verbosity is debug (-vvv). + * + * @return bool true if verbosity is set to VERBOSITY_DEBUG, false otherwise + */ + public function isDebug(); + + /** + * Sets the decorated flag. + * + * @param bool $decorated Whether to decorate the messages + */ + public function setDecorated($decorated); + + /** + * Gets the decorated flag. + * + * @return bool true if the output will decorate messages, false otherwise + */ + public function isDecorated(); + + public function setFormatter(OutputFormatterInterface $formatter); + + /** + * Returns current output formatter instance. + * + * @return OutputFormatterInterface + */ + public function getFormatter(); +} diff --git a/vendor/symfony/console/Output/StreamOutput.php b/vendor/symfony/console/Output/StreamOutput.php new file mode 100644 index 0000000000..9c22436440 --- /dev/null +++ b/vendor/symfony/console/Output/StreamOutput.php @@ -0,0 +1,125 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * StreamOutput writes the output to a given stream. + * + * Usage: + * + * $output = new StreamOutput(fopen('php://stdout', 'w')); + * + * As `StreamOutput` can use any stream, you can also use a file: + * + * $output = new StreamOutput(fopen('/path/to/output.log', 'a', false)); + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class StreamOutput extends Output +{ + private $stream; + + /** + * @param resource $stream A stream resource + * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) + * @param bool|null $decorated Whether to decorate messages (null for auto-guessing) + * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) + * + * @throws InvalidArgumentException When first argument is not a real stream + */ + public function __construct($stream, int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = null, OutputFormatterInterface $formatter = null) + { + if (!\is_resource($stream) || 'stream' !== get_resource_type($stream)) { + throw new InvalidArgumentException('The StreamOutput class needs a stream as its first argument.'); + } + + $this->stream = $stream; + + if (null === $decorated) { + $decorated = $this->hasColorSupport(); + } + + parent::__construct($verbosity, $decorated, $formatter); + } + + /** + * Gets the stream attached to this StreamOutput instance. + * + * @return resource A stream resource + */ + public function getStream() + { + return $this->stream; + } + + /** + * {@inheritdoc} + */ + protected function doWrite($message, $newline) + { + if ($newline) { + $message .= \PHP_EOL; + } + + @fwrite($this->stream, $message); + + fflush($this->stream); + } + + /** + * Returns true if the stream supports colorization. + * + * Colorization is disabled if not supported by the stream: + * + * This is tricky on Windows, because Cygwin, Msys2 etc emulate pseudo + * terminals via named pipes, so we can only check the environment. + * + * Reference: Composer\XdebugHandler\Process::supportsColor + * https://github.com/composer/xdebug-handler + * + * @return bool true if the stream supports colorization, false otherwise + */ + protected function hasColorSupport() + { + // Follow https://no-color.org/ + if (isset($_SERVER['NO_COLOR']) || false !== getenv('NO_COLOR')) { + return false; + } + + if ('Hyper' === getenv('TERM_PROGRAM')) { + return true; + } + + if (\DIRECTORY_SEPARATOR === '\\') { + return (\function_exists('sapi_windows_vt100_support') + && @sapi_windows_vt100_support($this->stream)) + || false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM'); + } + + if (\function_exists('stream_isatty')) { + return @stream_isatty($this->stream); + } + + if (\function_exists('posix_isatty')) { + return @posix_isatty($this->stream); + } + + $stat = @fstat($this->stream); + // Check if formatted mode is S_IFCHR + return $stat ? 0020000 === ($stat['mode'] & 0170000) : false; + } +} diff --git a/vendor/symfony/console/Output/TrimmedBufferOutput.php b/vendor/symfony/console/Output/TrimmedBufferOutput.php new file mode 100644 index 0000000000..87c04a8921 --- /dev/null +++ b/vendor/symfony/console/Output/TrimmedBufferOutput.php @@ -0,0 +1,63 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * A BufferedOutput that keeps only the last N chars. + * + * @author Jérémy Derussé <jeremy@derusse.com> + */ +class TrimmedBufferOutput extends Output +{ + private $maxLength; + private $buffer = ''; + + public function __construct(int $maxLength, ?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, OutputFormatterInterface $formatter = null) + { + if ($maxLength <= 0) { + throw new InvalidArgumentException(sprintf('"%s()" expects a strictly positive maxLength. Got %d.', __METHOD__, $maxLength)); + } + + parent::__construct($verbosity, $decorated, $formatter); + $this->maxLength = $maxLength; + } + + /** + * Empties buffer and returns its content. + * + * @return string + */ + public function fetch() + { + $content = $this->buffer; + $this->buffer = ''; + + return $content; + } + + /** + * {@inheritdoc} + */ + protected function doWrite($message, $newline) + { + $this->buffer .= $message; + + if ($newline) { + $this->buffer .= \PHP_EOL; + } + + $this->buffer = substr($this->buffer, 0 - $this->maxLength); + } +} diff --git a/vendor/symfony/console/Question/ChoiceQuestion.php b/vendor/symfony/console/Question/ChoiceQuestion.php new file mode 100644 index 0000000000..6247ca7162 --- /dev/null +++ b/vendor/symfony/console/Question/ChoiceQuestion.php @@ -0,0 +1,188 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Question; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * Represents a choice question. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class ChoiceQuestion extends Question +{ + private $choices; + private $multiselect = false; + private $prompt = ' > '; + private $errorMessage = 'Value "%s" is invalid'; + + /** + * @param string $question The question to ask to the user + * @param array $choices The list of available choices + * @param mixed $default The default answer to return + */ + public function __construct(string $question, array $choices, $default = null) + { + if (!$choices) { + throw new \LogicException('Choice question must have at least 1 choice available.'); + } + + parent::__construct($question, $default); + + $this->choices = $choices; + $this->setValidator($this->getDefaultValidator()); + $this->setAutocompleterValues($choices); + } + + /** + * Returns available choices. + * + * @return array + */ + public function getChoices() + { + return $this->choices; + } + + /** + * Sets multiselect option. + * + * When multiselect is set to true, multiple choices can be answered. + * + * @param bool $multiselect + * + * @return $this + */ + public function setMultiselect($multiselect) + { + $this->multiselect = $multiselect; + $this->setValidator($this->getDefaultValidator()); + + return $this; + } + + /** + * Returns whether the choices are multiselect. + * + * @return bool + */ + public function isMultiselect() + { + return $this->multiselect; + } + + /** + * Gets the prompt for choices. + * + * @return string + */ + public function getPrompt() + { + return $this->prompt; + } + + /** + * Sets the prompt for choices. + * + * @param string $prompt + * + * @return $this + */ + public function setPrompt($prompt) + { + $this->prompt = $prompt; + + return $this; + } + + /** + * Sets the error message for invalid values. + * + * The error message has a string placeholder (%s) for the invalid value. + * + * @param string $errorMessage + * + * @return $this + */ + public function setErrorMessage($errorMessage) + { + $this->errorMessage = $errorMessage; + $this->setValidator($this->getDefaultValidator()); + + return $this; + } + + private function getDefaultValidator(): callable + { + $choices = $this->choices; + $errorMessage = $this->errorMessage; + $multiselect = $this->multiselect; + $isAssoc = $this->isAssoc($choices); + + return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) { + if ($multiselect) { + // Check for a separated comma values + if (!preg_match('/^[^,]+(?:,[^,]+)*$/', (string) $selected, $matches)) { + throw new InvalidArgumentException(sprintf($errorMessage, $selected)); + } + + $selectedChoices = explode(',', (string) $selected); + } else { + $selectedChoices = [$selected]; + } + + if ($this->isTrimmable()) { + foreach ($selectedChoices as $k => $v) { + $selectedChoices[$k] = trim((string) $v); + } + } + + $multiselectChoices = []; + foreach ($selectedChoices as $value) { + $results = []; + foreach ($choices as $key => $choice) { + if ($choice === $value) { + $results[] = $key; + } + } + + if (\count($results) > 1) { + throw new InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of "%s".', implode('" or "', $results))); + } + + $result = array_search($value, $choices); + + if (!$isAssoc) { + if (false !== $result) { + $result = $choices[$result]; + } elseif (isset($choices[$value])) { + $result = $choices[$value]; + } + } elseif (false === $result && isset($choices[$value])) { + $result = $value; + } + + if (false === $result) { + throw new InvalidArgumentException(sprintf($errorMessage, $value)); + } + + $multiselectChoices[] = (string) $result; + } + + if ($multiselect) { + return $multiselectChoices; + } + + return current($multiselectChoices); + }; + } +} diff --git a/vendor/symfony/console/Question/ConfirmationQuestion.php b/vendor/symfony/console/Question/ConfirmationQuestion.php new file mode 100644 index 0000000000..4228521b9f --- /dev/null +++ b/vendor/symfony/console/Question/ConfirmationQuestion.php @@ -0,0 +1,57 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Question; + +/** + * Represents a yes/no question. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class ConfirmationQuestion extends Question +{ + private $trueAnswerRegex; + + /** + * @param string $question The question to ask to the user + * @param bool $default The default answer to return, true or false + * @param string $trueAnswerRegex A regex to match the "yes" answer + */ + public function __construct(string $question, bool $default = true, string $trueAnswerRegex = '/^y/i') + { + parent::__construct($question, $default); + + $this->trueAnswerRegex = $trueAnswerRegex; + $this->setNormalizer($this->getDefaultNormalizer()); + } + + /** + * Returns the default answer normalizer. + */ + private function getDefaultNormalizer(): callable + { + $default = $this->getDefault(); + $regex = $this->trueAnswerRegex; + + return function ($answer) use ($default, $regex) { + if (\is_bool($answer)) { + return $answer; + } + + $answerIsTrue = (bool) preg_match($regex, $answer); + if (false === $default) { + return $answer && $answerIsTrue; + } + + return '' === $answer || $answerIsTrue; + }; + } +} diff --git a/vendor/symfony/console/Question/Question.php b/vendor/symfony/console/Question/Question.php new file mode 100644 index 0000000000..cc108018f6 --- /dev/null +++ b/vendor/symfony/console/Question/Question.php @@ -0,0 +1,292 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Question; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; + +/** + * Represents a Question. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class Question +{ + private $question; + private $attempts; + private $hidden = false; + private $hiddenFallback = true; + private $autocompleterCallback; + private $validator; + private $default; + private $normalizer; + private $trimmable = true; + + /** + * @param string $question The question to ask to the user + * @param string|bool|int|float|null $default The default answer to return if the user enters nothing + */ + public function __construct(string $question, $default = null) + { + $this->question = $question; + $this->default = $default; + } + + /** + * Returns the question. + * + * @return string + */ + public function getQuestion() + { + return $this->question; + } + + /** + * Returns the default answer. + * + * @return string|bool|int|float|null + */ + public function getDefault() + { + return $this->default; + } + + /** + * Returns whether the user response must be hidden. + * + * @return bool + */ + public function isHidden() + { + return $this->hidden; + } + + /** + * Sets whether the user response must be hidden or not. + * + * @param bool $hidden + * + * @return $this + * + * @throws LogicException In case the autocompleter is also used + */ + public function setHidden($hidden) + { + if ($this->autocompleterCallback) { + throw new LogicException('A hidden question cannot use the autocompleter.'); + } + + $this->hidden = (bool) $hidden; + + return $this; + } + + /** + * In case the response can not be hidden, whether to fallback on non-hidden question or not. + * + * @return bool + */ + public function isHiddenFallback() + { + return $this->hiddenFallback; + } + + /** + * Sets whether to fallback on non-hidden question if the response can not be hidden. + * + * @param bool $fallback + * + * @return $this + */ + public function setHiddenFallback($fallback) + { + $this->hiddenFallback = (bool) $fallback; + + return $this; + } + + /** + * Gets values for the autocompleter. + * + * @return iterable|null + */ + public function getAutocompleterValues() + { + $callback = $this->getAutocompleterCallback(); + + return $callback ? $callback('') : null; + } + + /** + * Sets values for the autocompleter. + * + * @param iterable|null $values + * + * @return $this + * + * @throws InvalidArgumentException + * @throws LogicException + */ + public function setAutocompleterValues($values) + { + if (\is_array($values)) { + $values = $this->isAssoc($values) ? array_merge(array_keys($values), array_values($values)) : array_values($values); + + $callback = static function () use ($values) { + return $values; + }; + } elseif ($values instanceof \Traversable) { + $valueCache = null; + $callback = static function () use ($values, &$valueCache) { + return $valueCache ?? $valueCache = iterator_to_array($values, false); + }; + } elseif (null === $values) { + $callback = null; + } else { + throw new InvalidArgumentException('Autocompleter values can be either an array, "null" or a "Traversable" object.'); + } + + return $this->setAutocompleterCallback($callback); + } + + /** + * Gets the callback function used for the autocompleter. + */ + public function getAutocompleterCallback(): ?callable + { + return $this->autocompleterCallback; + } + + /** + * Sets the callback function used for the autocompleter. + * + * The callback is passed the user input as argument and should return an iterable of corresponding suggestions. + * + * @return $this + */ + public function setAutocompleterCallback(callable $callback = null): self + { + if ($this->hidden && null !== $callback) { + throw new LogicException('A hidden question cannot use the autocompleter.'); + } + + $this->autocompleterCallback = $callback; + + return $this; + } + + /** + * Sets a validator for the question. + * + * @return $this + */ + public function setValidator(callable $validator = null) + { + $this->validator = $validator; + + return $this; + } + + /** + * Gets the validator for the question. + * + * @return callable|null + */ + public function getValidator() + { + return $this->validator; + } + + /** + * Sets the maximum number of attempts. + * + * Null means an unlimited number of attempts. + * + * @param int|null $attempts + * + * @return $this + * + * @throws InvalidArgumentException in case the number of attempts is invalid + */ + public function setMaxAttempts($attempts) + { + if (null !== $attempts) { + $attempts = (int) $attempts; + if ($attempts < 1) { + throw new InvalidArgumentException('Maximum number of attempts must be a positive value.'); + } + } + + $this->attempts = $attempts; + + return $this; + } + + /** + * Gets the maximum number of attempts. + * + * Null means an unlimited number of attempts. + * + * @return int|null + */ + public function getMaxAttempts() + { + return $this->attempts; + } + + /** + * Sets a normalizer for the response. + * + * The normalizer can be a callable (a string), a closure or a class implementing __invoke. + * + * @return $this + */ + public function setNormalizer(callable $normalizer) + { + $this->normalizer = $normalizer; + + return $this; + } + + /** + * Gets the normalizer for the response. + * + * The normalizer can ba a callable (a string), a closure or a class implementing __invoke. + * + * @return callable|null + */ + public function getNormalizer() + { + return $this->normalizer; + } + + protected function isAssoc($array) + { + return (bool) \count(array_filter(array_keys($array), 'is_string')); + } + + public function isTrimmable(): bool + { + return $this->trimmable; + } + + /** + * @return $this + */ + public function setTrimmable(bool $trimmable): self + { + $this->trimmable = $trimmable; + + return $this; + } +} diff --git a/vendor/symfony/console/README.md b/vendor/symfony/console/README.md new file mode 100644 index 0000000000..c89b4a1a20 --- /dev/null +++ b/vendor/symfony/console/README.md @@ -0,0 +1,20 @@ +Console Component +================= + +The Console component eases the creation of beautiful and testable command line +interfaces. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/console.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) + +Credits +------- + +`Resources/bin/hiddeninput.exe` is a third party binary provided within this +component. Find sources and license at https://github.com/Seldaek/hidden-input. diff --git a/vendor/symfony/console/Resources/bin/hiddeninput.exe b/vendor/symfony/console/Resources/bin/hiddeninput.exe new file mode 100644 index 0000000000..c8cf65e8d8 Binary files /dev/null and b/vendor/symfony/console/Resources/bin/hiddeninput.exe differ diff --git a/vendor/symfony/console/Style/OutputStyle.php b/vendor/symfony/console/Style/OutputStyle.php new file mode 100644 index 0000000000..14d2d60b22 --- /dev/null +++ b/vendor/symfony/console/Style/OutputStyle.php @@ -0,0 +1,155 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Style; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Decorates output to add console style guide helpers. + * + * @author Kevin Bond <kevinbond@gmail.com> + */ +abstract class OutputStyle implements OutputInterface, StyleInterface +{ + private $output; + + public function __construct(OutputInterface $output) + { + $this->output = $output; + } + + /** + * {@inheritdoc} + */ + public function newLine($count = 1) + { + $this->output->write(str_repeat(\PHP_EOL, $count)); + } + + /** + * @param int $max + * + * @return ProgressBar + */ + public function createProgressBar($max = 0) + { + return new ProgressBar($this->output, $max); + } + + /** + * {@inheritdoc} + */ + public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) + { + $this->output->write($messages, $newline, $type); + } + + /** + * {@inheritdoc} + */ + public function writeln($messages, $type = self::OUTPUT_NORMAL) + { + $this->output->writeln($messages, $type); + } + + /** + * {@inheritdoc} + */ + public function setVerbosity($level) + { + $this->output->setVerbosity($level); + } + + /** + * {@inheritdoc} + */ + public function getVerbosity() + { + return $this->output->getVerbosity(); + } + + /** + * {@inheritdoc} + */ + public function setDecorated($decorated) + { + $this->output->setDecorated($decorated); + } + + /** + * {@inheritdoc} + */ + public function isDecorated() + { + return $this->output->isDecorated(); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(OutputFormatterInterface $formatter) + { + $this->output->setFormatter($formatter); + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + return $this->output->getFormatter(); + } + + /** + * {@inheritdoc} + */ + public function isQuiet() + { + return $this->output->isQuiet(); + } + + /** + * {@inheritdoc} + */ + public function isVerbose() + { + return $this->output->isVerbose(); + } + + /** + * {@inheritdoc} + */ + public function isVeryVerbose() + { + return $this->output->isVeryVerbose(); + } + + /** + * {@inheritdoc} + */ + public function isDebug() + { + return $this->output->isDebug(); + } + + protected function getErrorOutput() + { + if (!$this->output instanceof ConsoleOutputInterface) { + return $this->output; + } + + return $this->output->getErrorOutput(); + } +} diff --git a/vendor/symfony/console/Style/StyleInterface.php b/vendor/symfony/console/Style/StyleInterface.php new file mode 100644 index 0000000000..3b5b8af516 --- /dev/null +++ b/vendor/symfony/console/Style/StyleInterface.php @@ -0,0 +1,153 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Style; + +/** + * Output style helpers. + * + * @author Kevin Bond <kevinbond@gmail.com> + */ +interface StyleInterface +{ + /** + * Formats a command title. + * + * @param string $message + */ + public function title($message); + + /** + * Formats a section title. + * + * @param string $message + */ + public function section($message); + + /** + * Formats a list. + */ + public function listing(array $elements); + + /** + * Formats informational text. + * + * @param string|array $message + */ + public function text($message); + + /** + * Formats a success result bar. + * + * @param string|array $message + */ + public function success($message); + + /** + * Formats an error result bar. + * + * @param string|array $message + */ + public function error($message); + + /** + * Formats an warning result bar. + * + * @param string|array $message + */ + public function warning($message); + + /** + * Formats a note admonition. + * + * @param string|array $message + */ + public function note($message); + + /** + * Formats a caution admonition. + * + * @param string|array $message + */ + public function caution($message); + + /** + * Formats a table. + */ + public function table(array $headers, array $rows); + + /** + * Asks a question. + * + * @param string $question + * @param string|null $default + * @param callable|null $validator + * + * @return mixed + */ + public function ask($question, $default = null, $validator = null); + + /** + * Asks a question with the user input hidden. + * + * @param string $question + * @param callable|null $validator + * + * @return mixed + */ + public function askHidden($question, $validator = null); + + /** + * Asks for confirmation. + * + * @param string $question + * @param bool $default + * + * @return bool + */ + public function confirm($question, $default = true); + + /** + * Asks a choice question. + * + * @param string $question + * @param string|int|null $default + * + * @return mixed + */ + public function choice($question, array $choices, $default = null); + + /** + * Add newline(s). + * + * @param int $count The number of newlines + */ + public function newLine($count = 1); + + /** + * Starts the progress output. + * + * @param int $max Maximum steps (0 if unknown) + */ + public function progressStart($max = 0); + + /** + * Advances the progress output X steps. + * + * @param int $step Number of steps to advance + */ + public function progressAdvance($step = 1); + + /** + * Finishes the progress output. + */ + public function progressFinish(); +} diff --git a/vendor/symfony/console/Style/SymfonyStyle.php b/vendor/symfony/console/Style/SymfonyStyle.php new file mode 100644 index 0000000000..1c99a1865d --- /dev/null +++ b/vendor/symfony/console/Style/SymfonyStyle.php @@ -0,0 +1,508 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Style; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Helper\SymfonyQuestionHelper; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Helper\TableCell; +use Symfony\Component\Console\Helper\TableSeparator; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\TrimmedBufferOutput; +use Symfony\Component\Console\Question\ChoiceQuestion; +use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Terminal; + +/** + * Output decorator helpers for the Symfony Style Guide. + * + * @author Kevin Bond <kevinbond@gmail.com> + */ +class SymfonyStyle extends OutputStyle +{ + public const MAX_LINE_LENGTH = 120; + + private $input; + private $questionHelper; + private $progressBar; + private $lineLength; + private $bufferedOutput; + + public function __construct(InputInterface $input, OutputInterface $output) + { + $this->input = $input; + $this->bufferedOutput = new TrimmedBufferOutput(\DIRECTORY_SEPARATOR === '\\' ? 4 : 2, $output->getVerbosity(), false, clone $output->getFormatter()); + // Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not. + $width = (new Terminal())->getWidth() ?: self::MAX_LINE_LENGTH; + $this->lineLength = min($width - (int) (\DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH); + + parent::__construct($output); + } + + /** + * Formats a message as a block of text. + * + * @param string|array $messages The message to write in the block + * @param string|null $type The block type (added in [] on first line) + * @param string|null $style The style to apply to the whole block + * @param string $prefix The prefix for the block + * @param bool $padding Whether to add vertical padding + * @param bool $escape Whether to escape the message + */ + public function block($messages, $type = null, $style = null, $prefix = ' ', $padding = false, $escape = true) + { + $messages = \is_array($messages) ? array_values($messages) : [$messages]; + + $this->autoPrependBlock(); + $this->writeln($this->createBlock($messages, $type, $style, $prefix, $padding, $escape)); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function title($message) + { + $this->autoPrependBlock(); + $this->writeln([ + sprintf('<comment>%s</>', OutputFormatter::escapeTrailingBackslash($message)), + sprintf('<comment>%s</>', str_repeat('=', Helper::strlenWithoutDecoration($this->getFormatter(), $message))), + ]); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function section($message) + { + $this->autoPrependBlock(); + $this->writeln([ + sprintf('<comment>%s</>', OutputFormatter::escapeTrailingBackslash($message)), + sprintf('<comment>%s</>', str_repeat('-', Helper::strlenWithoutDecoration($this->getFormatter(), $message))), + ]); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function listing(array $elements) + { + $this->autoPrependText(); + $elements = array_map(function ($element) { + return sprintf(' * %s', $element); + }, $elements); + + $this->writeln($elements); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function text($message) + { + $this->autoPrependText(); + + $messages = \is_array($message) ? array_values($message) : [$message]; + foreach ($messages as $message) { + $this->writeln(sprintf(' %s', $message)); + } + } + + /** + * Formats a command comment. + * + * @param string|array $message + */ + public function comment($message) + { + $this->block($message, null, null, '<fg=default;bg=default> // </>', false, false); + } + + /** + * {@inheritdoc} + */ + public function success($message) + { + $this->block($message, 'OK', 'fg=black;bg=green', ' ', true); + } + + /** + * {@inheritdoc} + */ + public function error($message) + { + $this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true); + } + + /** + * {@inheritdoc} + */ + public function warning($message) + { + $this->block($message, 'WARNING', 'fg=black;bg=yellow', ' ', true); + } + + /** + * {@inheritdoc} + */ + public function note($message) + { + $this->block($message, 'NOTE', 'fg=yellow', ' ! '); + } + + /** + * {@inheritdoc} + */ + public function caution($message) + { + $this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', true); + } + + /** + * {@inheritdoc} + */ + public function table(array $headers, array $rows) + { + $style = clone Table::getStyleDefinition('symfony-style-guide'); + $style->setCellHeaderFormat('<info>%s</info>'); + + $table = new Table($this); + $table->setHeaders($headers); + $table->setRows($rows); + $table->setStyle($style); + + $table->render(); + $this->newLine(); + } + + /** + * Formats a horizontal table. + */ + public function horizontalTable(array $headers, array $rows) + { + $style = clone Table::getStyleDefinition('symfony-style-guide'); + $style->setCellHeaderFormat('<info>%s</info>'); + + $table = new Table($this); + $table->setHeaders($headers); + $table->setRows($rows); + $table->setStyle($style); + $table->setHorizontal(true); + + $table->render(); + $this->newLine(); + } + + /** + * Formats a list of key/value horizontally. + * + * Each row can be one of: + * * 'A title' + * * ['key' => 'value'] + * * new TableSeparator() + * + * @param string|array|TableSeparator ...$list + */ + public function definitionList(...$list) + { + $style = clone Table::getStyleDefinition('symfony-style-guide'); + $style->setCellHeaderFormat('<info>%s</info>'); + + $table = new Table($this); + $headers = []; + $row = []; + foreach ($list as $value) { + if ($value instanceof TableSeparator) { + $headers[] = $value; + $row[] = $value; + continue; + } + if (\is_string($value)) { + $headers[] = new TableCell($value, ['colspan' => 2]); + $row[] = null; + continue; + } + if (!\is_array($value)) { + throw new InvalidArgumentException('Value should be an array, string, or an instance of TableSeparator.'); + } + $headers[] = key($value); + $row[] = current($value); + } + + $table->setHeaders($headers); + $table->setRows([$row]); + $table->setHorizontal(); + $table->setStyle($style); + + $table->render(); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function ask($question, $default = null, $validator = null) + { + $question = new Question($question, $default); + $question->setValidator($validator); + + return $this->askQuestion($question); + } + + /** + * {@inheritdoc} + */ + public function askHidden($question, $validator = null) + { + $question = new Question($question); + + $question->setHidden(true); + $question->setValidator($validator); + + return $this->askQuestion($question); + } + + /** + * {@inheritdoc} + */ + public function confirm($question, $default = true) + { + return $this->askQuestion(new ConfirmationQuestion($question, $default)); + } + + /** + * {@inheritdoc} + */ + public function choice($question, array $choices, $default = null) + { + if (null !== $default) { + $values = array_flip($choices); + $default = $values[$default] ?? $default; + } + + return $this->askQuestion(new ChoiceQuestion($question, $choices, $default)); + } + + /** + * {@inheritdoc} + */ + public function progressStart($max = 0) + { + $this->progressBar = $this->createProgressBar($max); + $this->progressBar->start(); + } + + /** + * {@inheritdoc} + */ + public function progressAdvance($step = 1) + { + $this->getProgressBar()->advance($step); + } + + /** + * {@inheritdoc} + */ + public function progressFinish() + { + $this->getProgressBar()->finish(); + $this->newLine(2); + $this->progressBar = null; + } + + /** + * {@inheritdoc} + */ + public function createProgressBar($max = 0) + { + $progressBar = parent::createProgressBar($max); + + if ('\\' !== \DIRECTORY_SEPARATOR || 'Hyper' === getenv('TERM_PROGRAM')) { + $progressBar->setEmptyBarCharacter('░'); // light shade character \u2591 + $progressBar->setProgressCharacter(''); + $progressBar->setBarCharacter('▓'); // dark shade character \u2593 + } + + return $progressBar; + } + + /** + * @return mixed + */ + public function askQuestion(Question $question) + { + if ($this->input->isInteractive()) { + $this->autoPrependBlock(); + } + + if (!$this->questionHelper) { + $this->questionHelper = new SymfonyQuestionHelper(); + } + + $answer = $this->questionHelper->ask($this->input, $this, $question); + + if ($this->input->isInteractive()) { + $this->newLine(); + $this->bufferedOutput->write("\n"); + } + + return $answer; + } + + /** + * {@inheritdoc} + */ + public function writeln($messages, $type = self::OUTPUT_NORMAL) + { + if (!is_iterable($messages)) { + $messages = [$messages]; + } + + foreach ($messages as $message) { + parent::writeln($message, $type); + $this->writeBuffer($message, true, $type); + } + } + + /** + * {@inheritdoc} + */ + public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) + { + if (!is_iterable($messages)) { + $messages = [$messages]; + } + + foreach ($messages as $message) { + parent::write($message, $newline, $type); + $this->writeBuffer($message, $newline, $type); + } + } + + /** + * {@inheritdoc} + */ + public function newLine($count = 1) + { + parent::newLine($count); + $this->bufferedOutput->write(str_repeat("\n", $count)); + } + + /** + * Returns a new instance which makes use of stderr if available. + * + * @return self + */ + public function getErrorStyle() + { + return new self($this->input, $this->getErrorOutput()); + } + + private function getProgressBar(): ProgressBar + { + if (!$this->progressBar) { + throw new RuntimeException('The ProgressBar is not started.'); + } + + return $this->progressBar; + } + + private function autoPrependBlock(): void + { + $chars = substr(str_replace(\PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2); + + if (!isset($chars[0])) { + $this->newLine(); // empty history, so we should start with a new line. + + return; + } + // Prepend new line for each non LF chars (This means no blank line was output before) + $this->newLine(2 - substr_count($chars, "\n")); + } + + private function autoPrependText(): void + { + $fetched = $this->bufferedOutput->fetch(); + // Prepend new line if last char isn't EOL: + if (!str_ends_with($fetched, "\n")) { + $this->newLine(); + } + } + + private function writeBuffer(string $message, bool $newLine, int $type): void + { + // We need to know if the last chars are PHP_EOL + $this->bufferedOutput->write($message, $newLine, $type); + } + + private function createBlock(iterable $messages, string $type = null, string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = false): array + { + $indentLength = 0; + $prefixLength = Helper::strlenWithoutDecoration($this->getFormatter(), $prefix); + $lines = []; + + if (null !== $type) { + $type = sprintf('[%s] ', $type); + $indentLength = \strlen($type); + $lineIndentation = str_repeat(' ', $indentLength); + } + + // wrap and add newlines for each element + foreach ($messages as $key => $message) { + if ($escape) { + $message = OutputFormatter::escape($message); + } + + $decorationLength = Helper::strlen($message) - Helper::strlenWithoutDecoration($this->getFormatter(), $message); + $messageLineLength = min($this->lineLength - $prefixLength - $indentLength + $decorationLength, $this->lineLength); + $messageLines = explode(\PHP_EOL, wordwrap($message, $messageLineLength, \PHP_EOL, true)); + foreach ($messageLines as $messageLine) { + $lines[] = $messageLine; + } + + if (\count($messages) > 1 && $key < \count($messages) - 1) { + $lines[] = ''; + } + } + + $firstLineIndex = 0; + if ($padding && $this->isDecorated()) { + $firstLineIndex = 1; + array_unshift($lines, ''); + $lines[] = ''; + } + + foreach ($lines as $i => &$line) { + if (null !== $type) { + $line = $firstLineIndex === $i ? $type.$line : $lineIndentation.$line; + } + + $line = $prefix.$line; + $line .= str_repeat(' ', max($this->lineLength - Helper::strlenWithoutDecoration($this->getFormatter(), $line), 0)); + + if ($style) { + $line = sprintf('<%s>%s</>', $style, $line); + } + } + + return $lines; + } +} diff --git a/vendor/symfony/console/Terminal.php b/vendor/symfony/console/Terminal.php new file mode 100644 index 0000000000..5e5a3c2f76 --- /dev/null +++ b/vendor/symfony/console/Terminal.php @@ -0,0 +1,174 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console; + +class Terminal +{ + private static $width; + private static $height; + private static $stty; + + /** + * Gets the terminal width. + * + * @return int + */ + public function getWidth() + { + $width = getenv('COLUMNS'); + if (false !== $width) { + return (int) trim($width); + } + + if (null === self::$width) { + self::initDimensions(); + } + + return self::$width ?: 80; + } + + /** + * Gets the terminal height. + * + * @return int + */ + public function getHeight() + { + $height = getenv('LINES'); + if (false !== $height) { + return (int) trim($height); + } + + if (null === self::$height) { + self::initDimensions(); + } + + return self::$height ?: 50; + } + + /** + * @internal + * + * @return bool + */ + public static function hasSttyAvailable() + { + if (null !== self::$stty) { + return self::$stty; + } + + // skip check if exec function is disabled + if (!\function_exists('exec')) { + return false; + } + + exec('stty 2>&1', $output, $exitcode); + + return self::$stty = 0 === $exitcode; + } + + private static function initDimensions() + { + if ('\\' === \DIRECTORY_SEPARATOR) { + if (preg_match('/^(\d+)x(\d+)(?: \((\d+)x(\d+)\))?$/', trim(getenv('ANSICON')), $matches)) { + // extract [w, H] from "wxh (WxH)" + // or [w, h] from "wxh" + self::$width = (int) $matches[1]; + self::$height = isset($matches[4]) ? (int) $matches[4] : (int) $matches[2]; + } elseif (!self::hasVt100Support() && self::hasSttyAvailable()) { + // only use stty on Windows if the terminal does not support vt100 (e.g. Windows 7 + git-bash) + // testing for stty in a Windows 10 vt100-enabled console will implicitly disable vt100 support on STDOUT + self::initDimensionsUsingStty(); + } elseif (null !== $dimensions = self::getConsoleMode()) { + // extract [w, h] from "wxh" + self::$width = (int) $dimensions[0]; + self::$height = (int) $dimensions[1]; + } + } else { + self::initDimensionsUsingStty(); + } + } + + /** + * Returns whether STDOUT has vt100 support (some Windows 10+ configurations). + */ + private static function hasVt100Support(): bool + { + return \function_exists('sapi_windows_vt100_support') && sapi_windows_vt100_support(fopen('php://stdout', 'w')); + } + + /** + * Initializes dimensions using the output of an stty columns line. + */ + private static function initDimensionsUsingStty() + { + if ($sttyString = self::getSttyColumns()) { + if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) { + // extract [w, h] from "rows h; columns w;" + self::$width = (int) $matches[2]; + self::$height = (int) $matches[1]; + } elseif (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) { + // extract [w, h] from "; h rows; w columns" + self::$width = (int) $matches[2]; + self::$height = (int) $matches[1]; + } + } + } + + /** + * Runs and parses mode CON if it's available, suppressing any error output. + * + * @return int[]|null An array composed of the width and the height or null if it could not be parsed + */ + private static function getConsoleMode(): ?array + { + $info = self::readFromProcess('mode CON'); + + if (null === $info || !preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) { + return null; + } + + return [(int) $matches[2], (int) $matches[1]]; + } + + /** + * Runs and parses stty -a if it's available, suppressing any error output. + */ + private static function getSttyColumns(): ?string + { + return self::readFromProcess('stty -a | grep columns'); + } + + private static function readFromProcess(string $command): ?string + { + if (!\function_exists('proc_open')) { + return null; + } + + $descriptorspec = [ + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ]; + + $process = proc_open($command, $descriptorspec, $pipes, null, null, ['suppress_errors' => true]); + if (!\is_resource($process)) { + return null; + } + + $info = stream_get_contents($pipes[1]); + fclose($pipes[1]); + fclose($pipes[2]); + proc_close($process); + + return $info; + } +} diff --git a/vendor/symfony/console/Tester/ApplicationTester.php b/vendor/symfony/console/Tester/ApplicationTester.php new file mode 100644 index 0000000000..ce4e5c18dc --- /dev/null +++ b/vendor/symfony/console/Tester/ApplicationTester.php @@ -0,0 +1,90 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tester; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Input\ArrayInput; + +/** + * Eases the testing of console applications. + * + * When testing an application, don't forget to disable the auto exit flag: + * + * $application = new Application(); + * $application->setAutoExit(false); + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class ApplicationTester +{ + use TesterTrait; + + private $application; + private $input; + private $statusCode; + + public function __construct(Application $application) + { + $this->application = $application; + } + + /** + * Executes the application. + * + * Available options: + * + * * interactive: Sets the input interactive flag + * * decorated: Sets the output decorated flag + * * verbosity: Sets the output verbosity flag + * * capture_stderr_separately: Make output of stdOut and stdErr separately available + * + * @param array $input An array of arguments and options + * @param array $options An array of options + * + * @return int The command exit code + */ + public function run(array $input, $options = []) + { + $prevShellVerbosity = getenv('SHELL_VERBOSITY'); + + try { + $this->input = new ArrayInput($input); + if (isset($options['interactive'])) { + $this->input->setInteractive($options['interactive']); + } + + if ($this->inputs) { + $this->input->setStream(self::createStream($this->inputs)); + } + + $this->initOutput($options); + + return $this->statusCode = $this->application->run($this->input, $this->output); + } finally { + // SHELL_VERBOSITY is set by Application::configureIO so we need to unset/reset it + // to its previous value to avoid one test's verbosity to spread to the following tests + if (false === $prevShellVerbosity) { + if (\function_exists('putenv')) { + @putenv('SHELL_VERBOSITY'); + } + unset($_ENV['SHELL_VERBOSITY']); + unset($_SERVER['SHELL_VERBOSITY']); + } else { + if (\function_exists('putenv')) { + @putenv('SHELL_VERBOSITY='.$prevShellVerbosity); + } + $_ENV['SHELL_VERBOSITY'] = $prevShellVerbosity; + $_SERVER['SHELL_VERBOSITY'] = $prevShellVerbosity; + } + } + } +} diff --git a/vendor/symfony/console/Tester/CommandTester.php b/vendor/symfony/console/Tester/CommandTester.php new file mode 100644 index 0000000000..57efc9a675 --- /dev/null +++ b/vendor/symfony/console/Tester/CommandTester.php @@ -0,0 +1,78 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tester; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\ArrayInput; + +/** + * Eases the testing of console commands. + * + * @author Fabien Potencier <fabien@symfony.com> + * @author Robin Chalas <robin.chalas@gmail.com> + */ +class CommandTester +{ + use TesterTrait; + + private $command; + private $input; + private $statusCode; + + public function __construct(Command $command) + { + $this->command = $command; + } + + /** + * Executes the command. + * + * Available execution options: + * + * * interactive: Sets the input interactive flag + * * decorated: Sets the output decorated flag + * * verbosity: Sets the output verbosity flag + * * capture_stderr_separately: Make output of stdOut and stdErr separately available + * + * @param array $input An array of command arguments and options + * @param array $options An array of execution options + * + * @return int The command exit code + */ + public function execute(array $input, array $options = []) + { + // set the command name automatically if the application requires + // this argument and no command name was passed + if (!isset($input['command']) + && (null !== $application = $this->command->getApplication()) + && $application->getDefinition()->hasArgument('command') + ) { + $input = array_merge(['command' => $this->command->getName()], $input); + } + + $this->input = new ArrayInput($input); + // Use an in-memory input stream even if no inputs are set so that QuestionHelper::ask() does not rely on the blocking STDIN. + $this->input->setStream(self::createStream($this->inputs)); + + if (isset($options['interactive'])) { + $this->input->setInteractive($options['interactive']); + } + + if (!isset($options['decorated'])) { + $options['decorated'] = false; + } + + $this->initOutput($options); + + return $this->statusCode = $this->command->run($this->input, $this->output); + } +} diff --git a/vendor/symfony/console/Tester/TesterTrait.php b/vendor/symfony/console/Tester/TesterTrait.php new file mode 100644 index 0000000000..27d5985590 --- /dev/null +++ b/vendor/symfony/console/Tester/TesterTrait.php @@ -0,0 +1,180 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tester; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\StreamOutput; + +/** + * @author Amrouche Hamza <hamza.simperfit@gmail.com> + */ +trait TesterTrait +{ + /** @var StreamOutput */ + private $output; + private $inputs = []; + private $captureStreamsIndependently = false; + + /** + * Gets the display returned by the last execution of the command or application. + * + * @param bool $normalize Whether to normalize end of lines to \n or not + * + * @return string The display + */ + public function getDisplay($normalize = false) + { + if (null === $this->output) { + throw new \RuntimeException('Output not initialized, did you execute the command before requesting the display?'); + } + + rewind($this->output->getStream()); + + $display = stream_get_contents($this->output->getStream()); + + if ($normalize) { + $display = str_replace(\PHP_EOL, "\n", $display); + } + + return $display; + } + + /** + * Gets the output written to STDERR by the application. + * + * @param bool $normalize Whether to normalize end of lines to \n or not + * + * @return string + */ + public function getErrorOutput($normalize = false) + { + if (!$this->captureStreamsIndependently) { + throw new \LogicException('The error output is not available when the tester is run without "capture_stderr_separately" option set.'); + } + + rewind($this->output->getErrorOutput()->getStream()); + + $display = stream_get_contents($this->output->getErrorOutput()->getStream()); + + if ($normalize) { + $display = str_replace(\PHP_EOL, "\n", $display); + } + + return $display; + } + + /** + * Gets the input instance used by the last execution of the command or application. + * + * @return InputInterface The current input instance + */ + public function getInput() + { + return $this->input; + } + + /** + * Gets the output instance used by the last execution of the command or application. + * + * @return OutputInterface The current output instance + */ + public function getOutput() + { + return $this->output; + } + + /** + * Gets the status code returned by the last execution of the command or application. + * + * @return int The status code + */ + public function getStatusCode() + { + return $this->statusCode; + } + + /** + * Sets the user inputs. + * + * @param array $inputs An array of strings representing each input + * passed to the command input stream + * + * @return $this + */ + public function setInputs(array $inputs) + { + $this->inputs = $inputs; + + return $this; + } + + /** + * Initializes the output property. + * + * Available options: + * + * * decorated: Sets the output decorated flag + * * verbosity: Sets the output verbosity flag + * * capture_stderr_separately: Make output of stdOut and stdErr separately available + */ + private function initOutput(array $options) + { + $this->captureStreamsIndependently = \array_key_exists('capture_stderr_separately', $options) && $options['capture_stderr_separately']; + if (!$this->captureStreamsIndependently) { + $this->output = new StreamOutput(fopen('php://memory', 'w', false)); + if (isset($options['decorated'])) { + $this->output->setDecorated($options['decorated']); + } + if (isset($options['verbosity'])) { + $this->output->setVerbosity($options['verbosity']); + } + } else { + $this->output = new ConsoleOutput( + $options['verbosity'] ?? ConsoleOutput::VERBOSITY_NORMAL, + $options['decorated'] ?? null + ); + + $errorOutput = new StreamOutput(fopen('php://memory', 'w', false)); + $errorOutput->setFormatter($this->output->getFormatter()); + $errorOutput->setVerbosity($this->output->getVerbosity()); + $errorOutput->setDecorated($this->output->isDecorated()); + + $reflectedOutput = new \ReflectionObject($this->output); + $strErrProperty = $reflectedOutput->getProperty('stderr'); + $strErrProperty->setAccessible(true); + $strErrProperty->setValue($this->output, $errorOutput); + + $reflectedParent = $reflectedOutput->getParentClass(); + $streamProperty = $reflectedParent->getProperty('stream'); + $streamProperty->setAccessible(true); + $streamProperty->setValue($this->output, fopen('php://memory', 'w', false)); + } + } + + /** + * @return resource + */ + private static function createStream(array $inputs) + { + $stream = fopen('php://memory', 'r+', false); + + foreach ($inputs as $input) { + fwrite($stream, $input.\PHP_EOL); + } + + rewind($stream); + + return $stream; + } +} diff --git a/vendor/symfony/console/composer.json b/vendor/symfony/console/composer.json new file mode 100644 index 0000000000..90cbd24f51 --- /dev/null +++ b/vendor/symfony/console/composer.json @@ -0,0 +1,57 @@ +{ + "name": "symfony/console", + "type": "library", + "description": "Eases the creation of beautiful and testable command line interfaces", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2" + }, + "require-dev": { + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/event-dispatcher": "^4.3", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/lock": "^4.4|^5.0", + "symfony/process": "^3.4|^4.0|^5.0", + "symfony/var-dumper": "^4.3|^5.0", + "psr/log": "^1|^2" + }, + "provide": { + "psr/log-implementation": "1.0|2.0" + }, + "suggest": { + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "", + "psr/log": "For using the console logger" + }, + "conflict": { + "psr/log": ">=3", + "symfony/dependency-injection": "<3.4", + "symfony/event-dispatcher": "<4.3|>=5", + "symfony/lock": "<4.4", + "symfony/process": "<3.3" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Console\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/deprecation-contracts/.gitignore b/vendor/symfony/deprecation-contracts/.gitignore new file mode 100644 index 0000000000..c49a5d8df5 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/deprecation-contracts/CHANGELOG.md b/vendor/symfony/deprecation-contracts/CHANGELOG.md new file mode 100644 index 0000000000..7932e26132 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/vendor/symfony/deprecation-contracts/LICENSE b/vendor/symfony/deprecation-contracts/LICENSE new file mode 100644 index 0000000000..406242ff28 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020-2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/deprecation-contracts/README.md b/vendor/symfony/deprecation-contracts/README.md new file mode 100644 index 0000000000..4957933a6c --- /dev/null +++ b/vendor/symfony/deprecation-contracts/README.md @@ -0,0 +1,26 @@ +Symfony Deprecation Contracts +============================= + +A generic function and convention to trigger deprecation notices. + +This package provides a single global function named `trigger_deprecation()` that triggers silenced deprecation notices. + +By using a custom PHP error handler such as the one provided by the Symfony ErrorHandler component, +the triggered deprecations can be caught and logged for later discovery, both on dev and prod environments. + +The function requires at least 3 arguments: + - the name of the Composer package that is triggering the deprecation + - the version of the package that introduced the deprecation + - the message of the deprecation + - more arguments can be provided: they will be inserted in the message using `printf()` formatting + +Example: +```php +trigger_deprecation('symfony/blockchain', '8.9', 'Using "%s" is deprecated, use "%s" instead.', 'bitcoin', 'fabcoin'); +``` + +This will generate the following message: +`Since symfony/blockchain 8.9: Using "bitcoin" is deprecated, use "fabcoin" instead.` + +While not necessarily recommended, the deprecation notices can be completely ignored by declaring an empty +`function trigger_deprecation() {}` in your application. diff --git a/vendor/symfony/deprecation-contracts/composer.json b/vendor/symfony/deprecation-contracts/composer.json new file mode 100644 index 0000000000..cc7cc12372 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/deprecation-contracts", + "type": "library", + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/vendor/symfony/deprecation-contracts/function.php b/vendor/symfony/deprecation-contracts/function.php new file mode 100644 index 0000000000..d4371504a0 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/function.php @@ -0,0 +1,27 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (!function_exists('trigger_deprecation')) { + /** + * Triggers a silenced deprecation notice. + * + * @param string $package The name of the Composer package that is triggering the deprecation + * @param string $version The version of the package that introduced the deprecation + * @param string $message The message of the deprecation + * @param mixed ...$args Values to insert in the message using printf() formatting + * + * @author Nicolas Grekas <p@tchwork.com> + */ + function trigger_deprecation(string $package, string $version, string $message, ...$args): void + { + @trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED); + } +} diff --git a/vendor/symfony/event-dispatcher-contracts/.gitignore b/vendor/symfony/event-dispatcher-contracts/.gitignore new file mode 100644 index 0000000000..c49a5d8df5 --- /dev/null +++ b/vendor/symfony/event-dispatcher-contracts/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/event-dispatcher-contracts/Event.php b/vendor/symfony/event-dispatcher-contracts/Event.php new file mode 100644 index 0000000000..84f60f3ed0 --- /dev/null +++ b/vendor/symfony/event-dispatcher-contracts/Event.php @@ -0,0 +1,96 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\EventDispatcher; + +use Psr\EventDispatcher\StoppableEventInterface; + +if (interface_exists(StoppableEventInterface::class)) { + /** + * Event is the base class for classes containing event data. + * + * This class contains no event data. It is used by events that do not pass + * state information to an event handler when an event is raised. + * + * You can call the method stopPropagation() to abort the execution of + * further listeners in your event listener. + * + * @author Guilherme Blanco <guilhermeblanco@hotmail.com> + * @author Jonathan Wage <jonwage@gmail.com> + * @author Roman Borschel <roman@code-factory.org> + * @author Bernhard Schussek <bschussek@gmail.com> + * @author Nicolas Grekas <p@tchwork.com> + */ + class Event implements StoppableEventInterface + { + private $propagationStopped = false; + + /** + * Returns whether further event listeners should be triggered. + */ + public function isPropagationStopped(): bool + { + return $this->propagationStopped; + } + + /** + * Stops the propagation of the event to further event listeners. + * + * If multiple event listeners are connected to the same event, no + * further event listener will be triggered once any trigger calls + * stopPropagation(). + */ + public function stopPropagation(): void + { + $this->propagationStopped = true; + } + } +} else { + /** + * Event is the base class for classes containing event data. + * + * This class contains no event data. It is used by events that do not pass + * state information to an event handler when an event is raised. + * + * You can call the method stopPropagation() to abort the execution of + * further listeners in your event listener. + * + * @author Guilherme Blanco <guilhermeblanco@hotmail.com> + * @author Jonathan Wage <jonwage@gmail.com> + * @author Roman Borschel <roman@code-factory.org> + * @author Bernhard Schussek <bschussek@gmail.com> + * @author Nicolas Grekas <p@tchwork.com> + */ + class Event + { + private $propagationStopped = false; + + /** + * Returns whether further event listeners should be triggered. + */ + public function isPropagationStopped(): bool + { + return $this->propagationStopped; + } + + /** + * Stops the propagation of the event to further event listeners. + * + * If multiple event listeners are connected to the same event, no + * further event listener will be triggered once any trigger calls + * stopPropagation(). + */ + public function stopPropagation(): void + { + $this->propagationStopped = true; + } + } +} diff --git a/vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php b/vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php new file mode 100644 index 0000000000..2d470af920 --- /dev/null +++ b/vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php @@ -0,0 +1,58 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\EventDispatcher; + +use Psr\EventDispatcher\EventDispatcherInterface as PsrEventDispatcherInterface; + +if (interface_exists(PsrEventDispatcherInterface::class)) { + /** + * Allows providing hooks on domain-specific lifecycles by dispatching events. + */ + interface EventDispatcherInterface extends PsrEventDispatcherInterface + { + /** + * Dispatches an event to all registered listeners. + * + * For BC with Symfony 4, the $eventName argument is not declared explicitly on the + * signature of the method. Implementations that are not bound by this BC constraint + * MUST declare it explicitly, as allowed by PHP. + * + * @param object $event The event to pass to the event handlers/listeners + * @param string|null $eventName The name of the event to dispatch. If not supplied, + * the class of $event should be used instead. + * + * @return object The passed $event MUST be returned + */ + public function dispatch($event/*, string $eventName = null*/); + } +} else { + /** + * Allows providing hooks on domain-specific lifecycles by dispatching events. + */ + interface EventDispatcherInterface + { + /** + * Dispatches an event to all registered listeners. + * + * For BC with Symfony 4, the $eventName argument is not declared explicitly on the + * signature of the method. Implementations that are not bound by this BC constraint + * MUST declare it explicitly, as allowed by PHP. + * + * @param object $event The event to pass to the event handlers/listeners + * @param string|null $eventName The name of the event to dispatch. If not supplied, + * the class of $event should be used instead. + * + * @return object The passed $event MUST be returned + */ + public function dispatch($event/*, string $eventName = null*/); + } +} diff --git a/vendor/symfony/event-dispatcher-contracts/LICENSE b/vendor/symfony/event-dispatcher-contracts/LICENSE new file mode 100644 index 0000000000..74cdc2dbf6 --- /dev/null +++ b/vendor/symfony/event-dispatcher-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/event-dispatcher-contracts/README.md b/vendor/symfony/event-dispatcher-contracts/README.md new file mode 100644 index 0000000000..b1ab4c00ce --- /dev/null +++ b/vendor/symfony/event-dispatcher-contracts/README.md @@ -0,0 +1,9 @@ +Symfony EventDispatcher Contracts +================================= + +A set of abstractions extracted out of the Symfony components. + +Can be used to build on semantics that the Symfony components proved useful - and +that already have battle tested implementations. + +See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/vendor/symfony/event-dispatcher-contracts/composer.json b/vendor/symfony/event-dispatcher-contracts/composer.json new file mode 100644 index 0000000000..9d4bd7bea7 --- /dev/null +++ b/vendor/symfony/event-dispatcher-contracts/composer.json @@ -0,0 +1,38 @@ +{ + "name": "symfony/event-dispatcher-contracts", + "type": "library", + "description": "Generic abstractions related to dispatching event", + "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1.3" + }, + "suggest": { + "psr/event-dispatcher": "", + "symfony/event-dispatcher-implementation": "" + }, + "autoload": { + "psr-4": { "Symfony\\Contracts\\EventDispatcher\\": "" } + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/vendor/symfony/event-dispatcher/CHANGELOG.md b/vendor/symfony/event-dispatcher/CHANGELOG.md new file mode 100644 index 0000000000..4a3ea066ef --- /dev/null +++ b/vendor/symfony/event-dispatcher/CHANGELOG.md @@ -0,0 +1,67 @@ +CHANGELOG +========= + +4.4.0 +----- + + * `AddEventAliasesPass` has been added, allowing applications and bundles to extend the event alias mapping used by `RegisterListenersPass`. + * Made the `event` attribute of the `kernel.event_listener` tag optional for FQCN events. + +4.3.0 +----- + + * The signature of the `EventDispatcherInterface::dispatch()` method should be updated to `dispatch($event, string $eventName = null)`, not doing so is deprecated + * deprecated the `Event` class, use `Symfony\Contracts\EventDispatcher\Event` instead + +4.1.0 +----- + + * added support for invokable event listeners tagged with `kernel.event_listener` by default + * The `TraceableEventDispatcher::getOrphanedEvents()` method has been added. + * The `TraceableEventDispatcherInterface` has been deprecated. + +4.0.0 +----- + + * removed the `ContainerAwareEventDispatcher` class + * added the `reset()` method to the `TraceableEventDispatcherInterface` + +3.4.0 +----- + + * Implementing `TraceableEventDispatcherInterface` without the `reset()` method has been deprecated. + +3.3.0 +----- + + * The ContainerAwareEventDispatcher class has been deprecated. Use EventDispatcher with closure factories instead. + +3.0.0 +----- + + * The method `getListenerPriority($eventName, $listener)` has been added to the + `EventDispatcherInterface`. + * The methods `Event::setDispatcher()`, `Event::getDispatcher()`, `Event::setName()` + and `Event::getName()` have been removed. + The event dispatcher and the event name are passed to the listener call. + +2.5.0 +----- + + * added Debug\TraceableEventDispatcher (originally in HttpKernel) + * changed Debug\TraceableEventDispatcherInterface to extend EventDispatcherInterface + * added RegisterListenersPass (originally in HttpKernel) + +2.1.0 +----- + + * added TraceableEventDispatcherInterface + * added ContainerAwareEventDispatcher + * added a reference to the EventDispatcher on the Event + * added a reference to the Event name on the event + * added fluid interface to the dispatch() method which now returns the Event + object + * added GenericEvent event class + * added the possibility for subscribers to subscribe several times for the + same event + * added ImmutableEventDispatcher diff --git a/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php new file mode 100644 index 0000000000..98e7df6344 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php @@ -0,0 +1,407 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Debug; + +use Psr\EventDispatcher\StoppableEventInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy; +use Symfony\Component\EventDispatcher\LegacyEventProxy; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Contracts\EventDispatcher\Event as ContractsEvent; + +/** + * Collects some data about event listeners. + * + * This event dispatcher delegates the dispatching to another one. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class TraceableEventDispatcher implements TraceableEventDispatcherInterface +{ + protected $logger; + protected $stopwatch; + + private $callStack; + private $dispatcher; + private $wrappedListeners; + private $orphanedEvents; + private $requestStack; + private $currentRequestHash = ''; + + public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null, RequestStack $requestStack = null) + { + $this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher); + $this->stopwatch = $stopwatch; + $this->logger = $logger; + $this->wrappedListeners = []; + $this->orphanedEvents = []; + $this->requestStack = $requestStack; + } + + /** + * {@inheritdoc} + */ + public function addListener($eventName, $listener, $priority = 0) + { + $this->dispatcher->addListener($eventName, $listener, $priority); + } + + /** + * {@inheritdoc} + */ + public function addSubscriber(EventSubscriberInterface $subscriber) + { + $this->dispatcher->addSubscriber($subscriber); + } + + /** + * {@inheritdoc} + */ + public function removeListener($eventName, $listener) + { + if (isset($this->wrappedListeners[$eventName])) { + foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) { + if ($wrappedListener->getWrappedListener() === $listener || ($listener instanceof \Closure && $wrappedListener->getWrappedListener() == $listener)) { + $listener = $wrappedListener; + unset($this->wrappedListeners[$eventName][$index]); + break; + } + } + } + + return $this->dispatcher->removeListener($eventName, $listener); + } + + /** + * {@inheritdoc} + */ + public function removeSubscriber(EventSubscriberInterface $subscriber) + { + return $this->dispatcher->removeSubscriber($subscriber); + } + + /** + * {@inheritdoc} + */ + public function getListeners($eventName = null) + { + return $this->dispatcher->getListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function getListenerPriority($eventName, $listener) + { + // we might have wrapped listeners for the event (if called while dispatching) + // in that case get the priority by wrapper + if (isset($this->wrappedListeners[$eventName])) { + foreach ($this->wrappedListeners[$eventName] as $wrappedListener) { + if ($wrappedListener->getWrappedListener() === $listener || ($listener instanceof \Closure && $wrappedListener->getWrappedListener() == $listener)) { + return $this->dispatcher->getListenerPriority($eventName, $wrappedListener); + } + } + } + + return $this->dispatcher->getListenerPriority($eventName, $listener); + } + + /** + * {@inheritdoc} + */ + public function hasListeners($eventName = null) + { + return $this->dispatcher->hasListeners($eventName); + } + + /** + * {@inheritdoc} + * + * @param string|null $eventName + */ + public function dispatch($event/* , string $eventName = null */) + { + if (null === $this->callStack) { + $this->callStack = new \SplObjectStorage(); + } + + $currentRequestHash = $this->currentRequestHash = $this->requestStack && ($request = $this->requestStack->getCurrentRequest()) ? spl_object_hash($request) : ''; + $eventName = 1 < \func_num_args() ? func_get_arg(1) : null; + + if (\is_object($event)) { + $eventName = $eventName ?? \get_class($event); + } else { + @trigger_error(sprintf('Calling the "%s::dispatch()" method with the event name as first argument is deprecated since Symfony 4.3, pass it second and provide the event object first instead.', EventDispatcherInterface::class), \E_USER_DEPRECATED); + $swap = $event; + $event = $eventName ?? new Event(); + $eventName = $swap; + + if (!$event instanceof Event) { + throw new \TypeError(sprintf('Argument 1 passed to "%s::dispatch()" must be an instance of "%s", "%s" given.', EventDispatcherInterface::class, Event::class, \is_object($event) ? \get_class($event) : \gettype($event))); + } + } + + if (null !== $this->logger && ($event instanceof Event || $event instanceof ContractsEvent || $event instanceof StoppableEventInterface) && $event->isPropagationStopped()) { + $this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName)); + } + + $this->preProcess($eventName); + try { + $this->beforeDispatch($eventName, $event); + try { + $e = $this->stopwatch->start($eventName, 'section'); + try { + $this->dispatcher->dispatch($event, $eventName); + } finally { + if ($e->isStarted()) { + $e->stop(); + } + } + } finally { + $this->afterDispatch($eventName, $event); + } + } finally { + $this->currentRequestHash = $currentRequestHash; + $this->postProcess($eventName); + } + + return $event; + } + + /** + * {@inheritdoc} + * + * @param Request|null $request The request to get listeners for + */ + public function getCalledListeners(/* Request $request = null */) + { + if (null === $this->callStack) { + return []; + } + + $hash = 1 <= \func_num_args() && null !== ($request = func_get_arg(0)) ? spl_object_hash($request) : null; + $called = []; + foreach ($this->callStack as $listener) { + [$eventName, $requestHash] = $this->callStack->getInfo(); + if (null === $hash || $hash === $requestHash) { + $called[] = $listener->getInfo($eventName); + } + } + + return $called; + } + + /** + * {@inheritdoc} + * + * @param Request|null $request The request to get listeners for + */ + public function getNotCalledListeners(/* Request $request = null */) + { + try { + $allListeners = $this->getListeners(); + } catch (\Exception $e) { + if (null !== $this->logger) { + $this->logger->info('An exception was thrown while getting the uncalled listeners.', ['exception' => $e]); + } + + // unable to retrieve the uncalled listeners + return []; + } + + $hash = 1 <= \func_num_args() && null !== ($request = func_get_arg(0)) ? spl_object_hash($request) : null; + $calledListeners = []; + + if (null !== $this->callStack) { + foreach ($this->callStack as $calledListener) { + [, $requestHash] = $this->callStack->getInfo(); + + if (null === $hash || $hash === $requestHash) { + $calledListeners[] = $calledListener->getWrappedListener(); + } + } + } + + $notCalled = []; + foreach ($allListeners as $eventName => $listeners) { + foreach ($listeners as $listener) { + if (!\in_array($listener, $calledListeners, true)) { + if (!$listener instanceof WrappedListener) { + $listener = new WrappedListener($listener, null, $this->stopwatch, $this); + } + $notCalled[] = $listener->getInfo($eventName); + } + } + } + + uasort($notCalled, [$this, 'sortNotCalledListeners']); + + return $notCalled; + } + + /** + * @param Request|null $request The request to get orphaned events for + */ + public function getOrphanedEvents(/* Request $request = null */): array + { + if (1 <= \func_num_args() && null !== $request = func_get_arg(0)) { + return $this->orphanedEvents[spl_object_hash($request)] ?? []; + } + + if (!$this->orphanedEvents) { + return []; + } + + return array_merge(...array_values($this->orphanedEvents)); + } + + public function reset() + { + $this->callStack = null; + $this->orphanedEvents = []; + $this->currentRequestHash = ''; + } + + /** + * Proxies all method calls to the original event dispatcher. + * + * @param string $method The method name + * @param array $arguments The method arguments + * + * @return mixed + */ + public function __call($method, $arguments) + { + return $this->dispatcher->{$method}(...$arguments); + } + + /** + * Called before dispatching the event. + * + * @param object $event + */ + protected function beforeDispatch(string $eventName, $event) + { + $this->preDispatch($eventName, $event instanceof Event ? $event : new LegacyEventProxy($event)); + } + + /** + * Called after dispatching the event. + * + * @param object $event + */ + protected function afterDispatch(string $eventName, $event) + { + $this->postDispatch($eventName, $event instanceof Event ? $event : new LegacyEventProxy($event)); + } + + /** + * @deprecated since Symfony 4.3, will be removed in 5.0, use beforeDispatch instead + */ + protected function preDispatch($eventName, Event $event) + { + } + + /** + * @deprecated since Symfony 4.3, will be removed in 5.0, use afterDispatch instead + */ + protected function postDispatch($eventName, Event $event) + { + } + + private function preProcess(string $eventName) + { + if (!$this->dispatcher->hasListeners($eventName)) { + $this->orphanedEvents[$this->currentRequestHash][] = $eventName; + + return; + } + + foreach ($this->dispatcher->getListeners($eventName) as $listener) { + $priority = $this->getListenerPriority($eventName, $listener); + $wrappedListener = new WrappedListener($listener instanceof WrappedListener ? $listener->getWrappedListener() : $listener, null, $this->stopwatch, $this); + $this->wrappedListeners[$eventName][] = $wrappedListener; + $this->dispatcher->removeListener($eventName, $listener); + $this->dispatcher->addListener($eventName, $wrappedListener, $priority); + $this->callStack->attach($wrappedListener, [$eventName, $this->currentRequestHash]); + } + } + + private function postProcess(string $eventName) + { + unset($this->wrappedListeners[$eventName]); + $skipped = false; + foreach ($this->dispatcher->getListeners($eventName) as $listener) { + if (!$listener instanceof WrappedListener) { // #12845: a new listener was added during dispatch. + continue; + } + // Unwrap listener + $priority = $this->getListenerPriority($eventName, $listener); + $this->dispatcher->removeListener($eventName, $listener); + $this->dispatcher->addListener($eventName, $listener->getWrappedListener(), $priority); + + if (null !== $this->logger) { + $context = ['event' => $eventName, 'listener' => $listener->getPretty()]; + } + + if ($listener->wasCalled()) { + if (null !== $this->logger) { + $this->logger->debug('Notified event "{event}" to listener "{listener}".', $context); + } + } else { + $this->callStack->detach($listener); + } + + if (null !== $this->logger && $skipped) { + $this->logger->debug('Listener "{listener}" was not called for event "{event}".', $context); + } + + if ($listener->stoppedPropagation()) { + if (null !== $this->logger) { + $this->logger->debug('Listener "{listener}" stopped propagation of the event "{event}".', $context); + } + + $skipped = true; + } + } + } + + private function sortNotCalledListeners(array $a, array $b) + { + if (0 !== $cmp = strcmp($a['event'], $b['event'])) { + return $cmp; + } + + if (\is_int($a['priority']) && !\is_int($b['priority'])) { + return 1; + } + + if (!\is_int($a['priority']) && \is_int($b['priority'])) { + return -1; + } + + if ($a['priority'] === $b['priority']) { + return 0; + } + + if ($a['priority'] > $b['priority']) { + return -1; + } + + return 1; + } +} diff --git a/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php new file mode 100644 index 0000000000..4fedb9a413 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php @@ -0,0 +1,42 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Debug; + +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Contracts\Service\ResetInterface; + +/** + * @deprecated since Symfony 4.1 + * + * @author Fabien Potencier <fabien@symfony.com> + */ +interface TraceableEventDispatcherInterface extends EventDispatcherInterface, ResetInterface +{ + /** + * Gets the called listeners. + * + * @param Request|null $request The request to get listeners for + * + * @return array An array of called listeners + */ + public function getCalledListeners(/* Request $request = null */); + + /** + * Gets the not called listeners. + * + * @param Request|null $request The request to get listeners for + * + * @return array An array of not called listeners + */ + public function getNotCalledListeners(/* Request $request = null */); +} diff --git a/vendor/symfony/event-dispatcher/Debug/WrappedListener.php b/vendor/symfony/event-dispatcher/Debug/WrappedListener.php new file mode 100644 index 0000000000..9b910e6677 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Debug/WrappedListener.php @@ -0,0 +1,136 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Debug; + +use Psr\EventDispatcher\StoppableEventInterface; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\LegacyEventProxy; +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\VarDumper\Caster\ClassStub; +use Symfony\Contracts\EventDispatcher\Event as ContractsEvent; + +/** + * @author Fabien Potencier <fabien@symfony.com> + * + * @final since Symfony 4.3: the "Event" type-hint on __invoke() will be replaced by "object" in 5.0 + */ +class WrappedListener +{ + private $listener; + private $optimizedListener; + private $name; + private $called; + private $stoppedPropagation; + private $stopwatch; + private $dispatcher; + private $pretty; + private $stub; + private $priority; + private static $hasClassStub; + + public function __construct($listener, ?string $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null) + { + $this->listener = $listener; + $this->optimizedListener = $listener instanceof \Closure ? $listener : (\is_callable($listener) ? \Closure::fromCallable($listener) : null); + $this->stopwatch = $stopwatch; + $this->dispatcher = $dispatcher; + $this->called = false; + $this->stoppedPropagation = false; + + if (\is_array($listener)) { + $this->name = \is_object($listener[0]) ? \get_class($listener[0]) : $listener[0]; + $this->pretty = $this->name.'::'.$listener[1]; + } elseif ($listener instanceof \Closure) { + $r = new \ReflectionFunction($listener); + if (str_contains($r->name, '{closure}')) { + $this->pretty = $this->name = 'closure'; + } elseif ($class = $r->getClosureScopeClass()) { + $this->name = $class->name; + $this->pretty = $this->name.'::'.$r->name; + } else { + $this->pretty = $this->name = $r->name; + } + } elseif (\is_string($listener)) { + $this->pretty = $this->name = $listener; + } else { + $this->name = \get_class($listener); + $this->pretty = $this->name.'::__invoke'; + } + + if (null !== $name) { + $this->name = $name; + } + + if (null === self::$hasClassStub) { + self::$hasClassStub = class_exists(ClassStub::class); + } + } + + public function getWrappedListener() + { + return $this->listener; + } + + public function wasCalled() + { + return $this->called; + } + + public function stoppedPropagation() + { + return $this->stoppedPropagation; + } + + public function getPretty() + { + return $this->pretty; + } + + public function getInfo($eventName) + { + if (null === $this->stub) { + $this->stub = self::$hasClassStub ? new ClassStub($this->pretty.'()', $this->listener) : $this->pretty.'()'; + } + + return [ + 'event' => $eventName, + 'priority' => null !== $this->priority ? $this->priority : (null !== $this->dispatcher ? $this->dispatcher->getListenerPriority($eventName, $this->listener) : null), + 'pretty' => $this->pretty, + 'stub' => $this->stub, + ]; + } + + public function __invoke(Event $event, $eventName, EventDispatcherInterface $dispatcher) + { + if ($event instanceof LegacyEventProxy) { + $event = $event->getEvent(); + } + + $dispatcher = $this->dispatcher ?: $dispatcher; + + $this->called = true; + $this->priority = $dispatcher->getListenerPriority($eventName, $this->listener); + + $e = $this->stopwatch->start($this->name, 'event_listener'); + + ($this->optimizedListener ?? $this->listener)($event, $eventName, $dispatcher); + + if ($e->isStarted()) { + $e->stop(); + } + + if (($event instanceof Event || $event instanceof ContractsEvent || $event instanceof StoppableEventInterface) && $event->isPropagationStopped()) { + $this->stoppedPropagation = true; + } + } +} diff --git a/vendor/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php b/vendor/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php new file mode 100644 index 0000000000..c4ea50f786 --- /dev/null +++ b/vendor/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php @@ -0,0 +1,42 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * This pass allows bundles to extend the list of event aliases. + * + * @author Alexander M. Turek <me@derrabus.de> + */ +class AddEventAliasesPass implements CompilerPassInterface +{ + private $eventAliases; + private $eventAliasesParameter; + + public function __construct(array $eventAliases, string $eventAliasesParameter = 'event_dispatcher.event_aliases') + { + $this->eventAliases = $eventAliases; + $this->eventAliasesParameter = $eventAliasesParameter; + } + + public function process(ContainerBuilder $container): void + { + $eventAliases = $container->hasParameter($this->eventAliasesParameter) ? $container->getParameter($this->eventAliasesParameter) : []; + + $container->setParameter( + $this->eventAliasesParameter, + array_merge($eventAliases, $this->eventAliases) + ); + } +} diff --git a/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php b/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php new file mode 100644 index 0000000000..1c4e12ec86 --- /dev/null +++ b/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php @@ -0,0 +1,178 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\DependencyInjection; + +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\EventDispatcher\Event as LegacyEvent; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Contracts\EventDispatcher\Event; + +/** + * Compiler pass to register tagged services for an event dispatcher. + */ +class RegisterListenersPass implements CompilerPassInterface +{ + protected $dispatcherService; + protected $listenerTag; + protected $subscriberTag; + protected $eventAliasesParameter; + + private $hotPathEvents = []; + private $hotPathTagName; + + public function __construct(string $dispatcherService = 'event_dispatcher', string $listenerTag = 'kernel.event_listener', string $subscriberTag = 'kernel.event_subscriber', string $eventAliasesParameter = 'event_dispatcher.event_aliases') + { + $this->dispatcherService = $dispatcherService; + $this->listenerTag = $listenerTag; + $this->subscriberTag = $subscriberTag; + $this->eventAliasesParameter = $eventAliasesParameter; + } + + public function setHotPathEvents(array $hotPathEvents, $tagName = 'container.hot_path') + { + $this->hotPathEvents = array_flip($hotPathEvents); + $this->hotPathTagName = $tagName; + + return $this; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->dispatcherService) && !$container->hasAlias($this->dispatcherService)) { + return; + } + + $aliases = []; + + if ($container->hasParameter($this->eventAliasesParameter)) { + $aliases = $container->getParameter($this->eventAliasesParameter); + } + + $definition = $container->findDefinition($this->dispatcherService); + + foreach ($container->findTaggedServiceIds($this->listenerTag, true) as $id => $events) { + foreach ($events as $event) { + $priority = $event['priority'] ?? 0; + + if (!isset($event['event'])) { + if ($container->getDefinition($id)->hasTag($this->subscriberTag)) { + continue; + } + + $event['method'] = $event['method'] ?? '__invoke'; + $event['event'] = $this->getEventFromTypeDeclaration($container, $id, $event['method']); + } + + $event['event'] = $aliases[$event['event']] ?? $event['event']; + + if (!isset($event['method'])) { + $event['method'] = 'on'.preg_replace_callback([ + '/(?<=\b|_)[a-z]/i', + '/[^a-z0-9]/i', + ], function ($matches) { return strtoupper($matches[0]); }, $event['event']); + $event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']); + + if (null !== ($class = $container->getDefinition($id)->getClass()) && ($r = $container->getReflectionClass($class, false)) && !$r->hasMethod($event['method']) && $r->hasMethod('__invoke')) { + $event['method'] = '__invoke'; + } + } + + $definition->addMethodCall('addListener', [$event['event'], [new ServiceClosureArgument(new Reference($id)), $event['method']], $priority]); + + if (isset($this->hotPathEvents[$event['event']])) { + $container->getDefinition($id)->addTag($this->hotPathTagName); + } + } + } + + $extractingDispatcher = new ExtractingEventDispatcher(); + + foreach ($container->findTaggedServiceIds($this->subscriberTag, true) as $id => $attributes) { + $def = $container->getDefinition($id); + + // We must assume that the class value has been correctly filled, even if the service is created by a factory + $class = $def->getClass(); + + if (!$r = $container->getReflectionClass($class)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + if (!$r->isSubclassOf(EventSubscriberInterface::class)) { + throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, EventSubscriberInterface::class)); + } + $class = $r->name; + + ExtractingEventDispatcher::$aliases = $aliases; + ExtractingEventDispatcher::$subscriber = $class; + $extractingDispatcher->addSubscriber($extractingDispatcher); + foreach ($extractingDispatcher->listeners as $args) { + $args[1] = [new ServiceClosureArgument(new Reference($id)), $args[1]]; + $definition->addMethodCall('addListener', $args); + + if (isset($this->hotPathEvents[$args[0]])) { + $container->getDefinition($id)->addTag($this->hotPathTagName); + } + } + $extractingDispatcher->listeners = []; + ExtractingEventDispatcher::$aliases = []; + } + } + + private function getEventFromTypeDeclaration(ContainerBuilder $container, string $id, string $method): string + { + if ( + null === ($class = $container->getDefinition($id)->getClass()) + || !($r = $container->getReflectionClass($class, false)) + || !$r->hasMethod($method) + || 1 > ($m = $r->getMethod($method))->getNumberOfParameters() + || !($type = $m->getParameters()[0]->getType()) instanceof \ReflectionNamedType + || $type->isBuiltin() + || Event::class === ($name = $type->getName()) + || LegacyEvent::class === $name + ) { + throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag)); + } + + return $name; + } +} + +/** + * @internal + */ +class ExtractingEventDispatcher extends EventDispatcher implements EventSubscriberInterface +{ + public $listeners = []; + + public static $aliases = []; + public static $subscriber; + + public function addListener($eventName, $listener, $priority = 0) + { + $this->listeners[] = [$eventName, $listener[1], $priority]; + } + + public static function getSubscribedEvents(): array + { + $events = []; + + foreach ([self::$subscriber, 'getSubscribedEvents']() as $eventName => $params) { + $events[self::$aliases[$eventName] ?? $eventName] = $params; + } + + return $events; + } +} diff --git a/vendor/symfony/event-dispatcher/Event.php b/vendor/symfony/event-dispatcher/Event.php new file mode 100644 index 0000000000..307c4be5de --- /dev/null +++ b/vendor/symfony/event-dispatcher/Event.php @@ -0,0 +1,38 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * @deprecated since Symfony 4.3, use "Symfony\Contracts\EventDispatcher\Event" instead + */ +class Event +{ + private $propagationStopped = false; + + /** + * @return bool Whether propagation was already stopped for this event + * + * @deprecated since Symfony 4.3, use "Symfony\Contracts\EventDispatcher\Event" instead + */ + public function isPropagationStopped() + { + return $this->propagationStopped; + } + + /** + * @deprecated since Symfony 4.3, use "Symfony\Contracts\EventDispatcher\Event" instead + */ + public function stopPropagation() + { + $this->propagationStopped = true; + } +} diff --git a/vendor/symfony/event-dispatcher/EventDispatcher.php b/vendor/symfony/event-dispatcher/EventDispatcher.php new file mode 100644 index 0000000000..3d8ac4281f --- /dev/null +++ b/vendor/symfony/event-dispatcher/EventDispatcher.php @@ -0,0 +1,314 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +use Psr\EventDispatcher\StoppableEventInterface; +use Symfony\Component\EventDispatcher\Debug\WrappedListener; +use Symfony\Contracts\EventDispatcher\Event as ContractsEvent; + +/** + * The EventDispatcherInterface is the central point of Symfony's event listener system. + * + * Listeners are registered on the manager and events are dispatched through the + * manager. + * + * @author Guilherme Blanco <guilhermeblanco@hotmail.com> + * @author Jonathan Wage <jonwage@gmail.com> + * @author Roman Borschel <roman@code-factory.org> + * @author Bernhard Schussek <bschussek@gmail.com> + * @author Fabien Potencier <fabien@symfony.com> + * @author Jordi Boggiano <j.boggiano@seld.be> + * @author Jordan Alliot <jordan.alliot@gmail.com> + * @author Nicolas Grekas <p@tchwork.com> + */ +class EventDispatcher implements EventDispatcherInterface +{ + private $listeners = []; + private $sorted = []; + private $optimized; + + public function __construct() + { + if (__CLASS__ === static::class) { + $this->optimized = []; + } + } + + /** + * {@inheritdoc} + * + * @param string|null $eventName + */ + public function dispatch($event/* , string $eventName = null */) + { + $eventName = 1 < \func_num_args() ? func_get_arg(1) : null; + + if (\is_object($event)) { + $eventName = $eventName ?? \get_class($event); + } elseif (\is_string($event) && (null === $eventName || $eventName instanceof ContractsEvent || $eventName instanceof Event)) { + @trigger_error(sprintf('Calling the "%s::dispatch()" method with the event name as the first argument is deprecated since Symfony 4.3, pass it as the second argument and provide the event object as the first argument instead.', EventDispatcherInterface::class), \E_USER_DEPRECATED); + $swap = $event; + $event = $eventName ?? new Event(); + $eventName = $swap; + } else { + throw new \TypeError(sprintf('Argument 1 passed to "%s::dispatch()" must be an object, "%s" given.', EventDispatcherInterface::class, \is_object($event) ? \get_class($event) : \gettype($event))); + } + + if (null !== $this->optimized && null !== $eventName) { + $listeners = $this->optimized[$eventName] ?? (empty($this->listeners[$eventName]) ? [] : $this->optimizeListeners($eventName)); + } else { + $listeners = $this->getListeners($eventName); + } + + if ($listeners) { + $this->callListeners($listeners, $eventName, $event); + } + + return $event; + } + + /** + * {@inheritdoc} + */ + public function getListeners($eventName = null) + { + if (null !== $eventName) { + if (empty($this->listeners[$eventName])) { + return []; + } + + if (!isset($this->sorted[$eventName])) { + $this->sortListeners($eventName); + } + + return $this->sorted[$eventName]; + } + + foreach ($this->listeners as $eventName => $eventListeners) { + if (!isset($this->sorted[$eventName])) { + $this->sortListeners($eventName); + } + } + + return array_filter($this->sorted); + } + + /** + * {@inheritdoc} + */ + public function getListenerPriority($eventName, $listener) + { + if (empty($this->listeners[$eventName])) { + return null; + } + + if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { + $listener[0] = $listener[0](); + $listener[1] = $listener[1] ?? '__invoke'; + } + + foreach ($this->listeners[$eventName] as $priority => &$listeners) { + foreach ($listeners as &$v) { + if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) { + $v[0] = $v[0](); + $v[1] = $v[1] ?? '__invoke'; + } + if ($v === $listener || ($listener instanceof \Closure && $v == $listener)) { + return $priority; + } + } + } + + return null; + } + + /** + * {@inheritdoc} + */ + public function hasListeners($eventName = null) + { + if (null !== $eventName) { + return !empty($this->listeners[$eventName]); + } + + foreach ($this->listeners as $eventListeners) { + if ($eventListeners) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function addListener($eventName, $listener, $priority = 0) + { + $this->listeners[$eventName][$priority][] = $listener; + unset($this->sorted[$eventName], $this->optimized[$eventName]); + } + + /** + * {@inheritdoc} + */ + public function removeListener($eventName, $listener) + { + if (empty($this->listeners[$eventName])) { + return; + } + + if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { + $listener[0] = $listener[0](); + $listener[1] = $listener[1] ?? '__invoke'; + } + + foreach ($this->listeners[$eventName] as $priority => &$listeners) { + foreach ($listeners as $k => &$v) { + if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) { + $v[0] = $v[0](); + $v[1] = $v[1] ?? '__invoke'; + } + if ($v === $listener || ($listener instanceof \Closure && $v == $listener)) { + unset($listeners[$k], $this->sorted[$eventName], $this->optimized[$eventName]); + } + } + + if (!$listeners) { + unset($this->listeners[$eventName][$priority]); + } + } + } + + /** + * {@inheritdoc} + */ + public function addSubscriber(EventSubscriberInterface $subscriber) + { + foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { + if (\is_string($params)) { + $this->addListener($eventName, [$subscriber, $params]); + } elseif (\is_string($params[0])) { + $this->addListener($eventName, [$subscriber, $params[0]], $params[1] ?? 0); + } else { + foreach ($params as $listener) { + $this->addListener($eventName, [$subscriber, $listener[0]], $listener[1] ?? 0); + } + } + } + } + + /** + * {@inheritdoc} + */ + public function removeSubscriber(EventSubscriberInterface $subscriber) + { + foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { + if (\is_array($params) && \is_array($params[0])) { + foreach ($params as $listener) { + $this->removeListener($eventName, [$subscriber, $listener[0]]); + } + } else { + $this->removeListener($eventName, [$subscriber, \is_string($params) ? $params : $params[0]]); + } + } + } + + /** + * Triggers the listeners of an event. + * + * This method can be overridden to add functionality that is executed + * for each listener. + * + * @param callable[] $listeners The event listeners + * @param string $eventName The name of the event to dispatch + * @param object $event The event object to pass to the event handlers/listeners + */ + protected function callListeners(iterable $listeners, string $eventName, $event) + { + if ($event instanceof Event) { + $this->doDispatch($listeners, $eventName, $event); + + return; + } + + $stoppable = $event instanceof ContractsEvent || $event instanceof StoppableEventInterface; + + foreach ($listeners as $listener) { + if ($stoppable && $event->isPropagationStopped()) { + break; + } + // @deprecated: the ternary operator is part of a BC layer and should be removed in 5.0 + $listener($listener instanceof WrappedListener ? new LegacyEventProxy($event) : $event, $eventName, $this); + } + } + + /** + * @deprecated since Symfony 4.3, use callListeners() instead + */ + protected function doDispatch($listeners, $eventName, Event $event) + { + foreach ($listeners as $listener) { + if ($event->isPropagationStopped()) { + break; + } + $listener($event, $eventName, $this); + } + } + + /** + * Sorts the internal list of listeners for the given event by priority. + */ + private function sortListeners(string $eventName) + { + krsort($this->listeners[$eventName]); + $this->sorted[$eventName] = []; + + foreach ($this->listeners[$eventName] as &$listeners) { + foreach ($listeners as $k => &$listener) { + if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { + $listener[0] = $listener[0](); + $listener[1] = $listener[1] ?? '__invoke'; + } + $this->sorted[$eventName][] = $listener; + } + } + } + + /** + * Optimizes the internal list of listeners for the given event by priority. + */ + private function optimizeListeners(string $eventName): array + { + krsort($this->listeners[$eventName]); + $this->optimized[$eventName] = []; + + foreach ($this->listeners[$eventName] as &$listeners) { + foreach ($listeners as &$listener) { + $closure = &$this->optimized[$eventName][]; + if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { + $closure = static function (...$args) use (&$listener, &$closure) { + if ($listener[0] instanceof \Closure) { + $listener[0] = $listener[0](); + $listener[1] = $listener[1] ?? '__invoke'; + } + ($closure = \Closure::fromCallable($listener))(...$args); + }; + } else { + $closure = $listener instanceof \Closure || $listener instanceof WrappedListener ? $listener : \Closure::fromCallable($listener); + } + } + } + + return $this->optimized[$eventName]; + } +} diff --git a/vendor/symfony/event-dispatcher/EventDispatcherInterface.php b/vendor/symfony/event-dispatcher/EventDispatcherInterface.php new file mode 100644 index 0000000000..ceaa62aeb0 --- /dev/null +++ b/vendor/symfony/event-dispatcher/EventDispatcherInterface.php @@ -0,0 +1,82 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface as ContractsEventDispatcherInterface; + +/** + * The EventDispatcherInterface is the central point of Symfony's event listener system. + * Listeners are registered on the manager and events are dispatched through the + * manager. + * + * @author Bernhard Schussek <bschussek@gmail.com> + */ +interface EventDispatcherInterface extends ContractsEventDispatcherInterface +{ + /** + * Adds an event listener that listens on the specified events. + * + * @param string $eventName The event to listen on + * @param callable $listener The listener + * @param int $priority The higher this value, the earlier an event + * listener will be triggered in the chain (defaults to 0) + */ + public function addListener($eventName, $listener, $priority = 0); + + /** + * Adds an event subscriber. + * + * The subscriber is asked for all the events it is + * interested in and added as a listener for these events. + */ + public function addSubscriber(EventSubscriberInterface $subscriber); + + /** + * Removes an event listener from the specified events. + * + * @param string $eventName The event to remove a listener from + * @param callable $listener The listener to remove + */ + public function removeListener($eventName, $listener); + + public function removeSubscriber(EventSubscriberInterface $subscriber); + + /** + * Gets the listeners of a specific event or all listeners sorted by descending priority. + * + * @param string|null $eventName The name of the event + * + * @return array The event listeners for the specified event, or all event listeners by event name + */ + public function getListeners($eventName = null); + + /** + * Gets the listener priority for a specific event. + * + * Returns null if the event or the listener does not exist. + * + * @param string $eventName The name of the event + * @param callable $listener The listener + * + * @return int|null The event listener priority + */ + public function getListenerPriority($eventName, $listener); + + /** + * Checks whether an event has any registered listeners. + * + * @param string|null $eventName The name of the event + * + * @return bool true if the specified event has any listeners, false otherwise + */ + public function hasListeners($eventName = null); +} diff --git a/vendor/symfony/event-dispatcher/EventSubscriberInterface.php b/vendor/symfony/event-dispatcher/EventSubscriberInterface.php new file mode 100644 index 0000000000..a0fc96dfe2 --- /dev/null +++ b/vendor/symfony/event-dispatcher/EventSubscriberInterface.php @@ -0,0 +1,49 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * An EventSubscriber knows itself what events it is interested in. + * If an EventSubscriber is added to an EventDispatcherInterface, the manager invokes + * {@link getSubscribedEvents} and registers the subscriber as a listener for all + * returned events. + * + * @author Guilherme Blanco <guilhermeblanco@hotmail.com> + * @author Jonathan Wage <jonwage@gmail.com> + * @author Roman Borschel <roman@code-factory.org> + * @author Bernhard Schussek <bschussek@gmail.com> + */ +interface EventSubscriberInterface +{ + /** + * Returns an array of event names this subscriber wants to listen to. + * + * The array keys are event names and the value can be: + * + * * The method name to call (priority defaults to 0) + * * An array composed of the method name to call and the priority + * * An array of arrays composed of the method names to call and respective + * priorities, or 0 if unset + * + * For instance: + * + * * ['eventName' => 'methodName'] + * * ['eventName' => ['methodName', $priority]] + * * ['eventName' => [['methodName1', $priority], ['methodName2']]] + * + * The code must not depend on runtime state as it will only be called at compile time. + * All logic depending on runtime state must be put into the individual methods handling the events. + * + * @return array<string, mixed> The event names to listen to + */ + public static function getSubscribedEvents(); +} diff --git a/vendor/symfony/event-dispatcher/GenericEvent.php b/vendor/symfony/event-dispatcher/GenericEvent.php new file mode 100644 index 0000000000..23333bc212 --- /dev/null +++ b/vendor/symfony/event-dispatcher/GenericEvent.php @@ -0,0 +1,184 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * Event encapsulation class. + * + * Encapsulates events thus decoupling the observer from the subject they encapsulate. + * + * @author Drak <drak@zikula.org> + */ +class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate +{ + protected $subject; + protected $arguments; + + /** + * Encapsulate an event with $subject and $args. + * + * @param mixed $subject The subject of the event, usually an object or a callable + * @param array $arguments Arguments to store in the event + */ + public function __construct($subject = null, array $arguments = []) + { + $this->subject = $subject; + $this->arguments = $arguments; + } + + /** + * Getter for subject property. + * + * @return mixed The observer subject + */ + public function getSubject() + { + return $this->subject; + } + + /** + * Get argument by key. + * + * @param string $key Key + * + * @return mixed Contents of array key + * + * @throws \InvalidArgumentException if key is not found + */ + public function getArgument($key) + { + if ($this->hasArgument($key)) { + return $this->arguments[$key]; + } + + throw new \InvalidArgumentException(sprintf('Argument "%s" not found.', $key)); + } + + /** + * Add argument to event. + * + * @param string $key Argument name + * @param mixed $value Value + * + * @return $this + */ + public function setArgument($key, $value) + { + $this->arguments[$key] = $value; + + return $this; + } + + /** + * Getter for all arguments. + * + * @return array + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * Set args property. + * + * @param array $args Arguments + * + * @return $this + */ + public function setArguments(array $args = []) + { + $this->arguments = $args; + + return $this; + } + + /** + * Has argument. + * + * @param string $key Key of arguments array + * + * @return bool + */ + public function hasArgument($key) + { + return \array_key_exists($key, $this->arguments); + } + + /** + * ArrayAccess for argument getter. + * + * @param string $key Array key + * + * @return mixed + * + * @throws \InvalidArgumentException if key does not exist in $this->args + */ + #[\ReturnTypeWillChange] + public function offsetGet($key) + { + return $this->getArgument($key); + } + + /** + * ArrayAccess for argument setter. + * + * @param string $key Array key to set + * @param mixed $value Value + * + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetSet($key, $value) + { + $this->setArgument($key, $value); + } + + /** + * ArrayAccess for unset argument. + * + * @param string $key Array key + * + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetUnset($key) + { + if ($this->hasArgument($key)) { + unset($this->arguments[$key]); + } + } + + /** + * ArrayAccess has argument. + * + * @param string $key Array key + * + * @return bool + */ + #[\ReturnTypeWillChange] + public function offsetExists($key) + { + return $this->hasArgument($key); + } + + /** + * IteratorAggregate for iterating over the object like an array. + * + * @return \ArrayIterator + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + return new \ArrayIterator($this->arguments); + } +} diff --git a/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php b/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php new file mode 100644 index 0000000000..f3d04d25ac --- /dev/null +++ b/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php @@ -0,0 +1,102 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * A read-only proxy for an event dispatcher. + * + * @author Bernhard Schussek <bschussek@gmail.com> + */ +class ImmutableEventDispatcher implements EventDispatcherInterface +{ + private $dispatcher; + + public function __construct(EventDispatcherInterface $dispatcher) + { + $this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher); + } + + /** + * {@inheritdoc} + * + * @param string|null $eventName + */ + public function dispatch($event/* , string $eventName = null */) + { + $eventName = 1 < \func_num_args() ? func_get_arg(1) : null; + + if (\is_scalar($event)) { + // deprecated + $swap = $event; + $event = $eventName ?? new Event(); + $eventName = $swap; + } + + return $this->dispatcher->dispatch($event, $eventName); + } + + /** + * {@inheritdoc} + */ + public function addListener($eventName, $listener, $priority = 0) + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + /** + * {@inheritdoc} + */ + public function addSubscriber(EventSubscriberInterface $subscriber) + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + /** + * {@inheritdoc} + */ + public function removeListener($eventName, $listener) + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + /** + * {@inheritdoc} + */ + public function removeSubscriber(EventSubscriberInterface $subscriber) + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + /** + * {@inheritdoc} + */ + public function getListeners($eventName = null) + { + return $this->dispatcher->getListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function getListenerPriority($eventName, $listener) + { + return $this->dispatcher->getListenerPriority($eventName, $listener); + } + + /** + * {@inheritdoc} + */ + public function hasListeners($eventName = null) + { + return $this->dispatcher->hasListeners($eventName); + } +} diff --git a/vendor/symfony/event-dispatcher/LICENSE b/vendor/symfony/event-dispatcher/LICENSE new file mode 100644 index 0000000000..88bf75bb4d --- /dev/null +++ b/vendor/symfony/event-dispatcher/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/event-dispatcher/LegacyEventDispatcherProxy.php b/vendor/symfony/event-dispatcher/LegacyEventDispatcherProxy.php new file mode 100644 index 0000000000..a802c99324 --- /dev/null +++ b/vendor/symfony/event-dispatcher/LegacyEventDispatcherProxy.php @@ -0,0 +1,147 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +use Psr\EventDispatcher\StoppableEventInterface; +use Symfony\Contracts\EventDispatcher\Event as ContractsEvent; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface as ContractsEventDispatcherInterface; + +/** + * A helper class to provide BC/FC with the legacy signature of EventDispatcherInterface::dispatch(). + * + * This class should be deprecated in Symfony 5.1 + * + * @author Nicolas Grekas <p@tchwork.com> + */ +final class LegacyEventDispatcherProxy implements EventDispatcherInterface +{ + private $dispatcher; + + public static function decorate(?ContractsEventDispatcherInterface $dispatcher): ?ContractsEventDispatcherInterface + { + if (null === $dispatcher) { + return null; + } + $r = new \ReflectionMethod($dispatcher, 'dispatch'); + $param2 = $r->getParameters()[1] ?? null; + + if (!$param2 || !$param2->hasType() || $param2->getType()->isBuiltin()) { + return $dispatcher; + } + + @trigger_error(sprintf('The signature of the "%s::dispatch()" method should be updated to "dispatch($event, string $eventName = null)", not doing so is deprecated since Symfony 4.3.', $r->class), \E_USER_DEPRECATED); + + $self = new self(); + $self->dispatcher = $dispatcher; + + return $self; + } + + /** + * {@inheritdoc} + * + * @param string|null $eventName + * + * @return object + */ + public function dispatch($event/* , string $eventName = null */) + { + $eventName = 1 < \func_num_args() ? func_get_arg(1) : null; + + if (\is_object($event)) { + $eventName = $eventName ?? \get_class($event); + } elseif (\is_string($event) && (null === $eventName || $eventName instanceof ContractsEvent || $eventName instanceof Event)) { + @trigger_error(sprintf('Calling the "%s::dispatch()" method with the event name as the first argument is deprecated since Symfony 4.3, pass it as the second argument and provide the event object as the first argument instead.', ContractsEventDispatcherInterface::class), \E_USER_DEPRECATED); + $swap = $event; + $event = $eventName ?? new Event(); + $eventName = $swap; + } else { + throw new \TypeError(sprintf('Argument 1 passed to "%s::dispatch()" must be an object, "%s" given.', ContractsEventDispatcherInterface::class, \is_object($event) ? \get_class($event) : \gettype($event))); + } + + $listeners = $this->getListeners($eventName); + $stoppable = $event instanceof Event || $event instanceof ContractsEvent || $event instanceof StoppableEventInterface; + + foreach ($listeners as $listener) { + if ($stoppable && $event->isPropagationStopped()) { + break; + } + $listener($event, $eventName, $this); + } + + return $event; + } + + /** + * {@inheritdoc} + */ + public function addListener($eventName, $listener, $priority = 0) + { + return $this->dispatcher->addListener($eventName, $listener, $priority); + } + + /** + * {@inheritdoc} + */ + public function addSubscriber(EventSubscriberInterface $subscriber) + { + return $this->dispatcher->addSubscriber($subscriber); + } + + /** + * {@inheritdoc} + */ + public function removeListener($eventName, $listener) + { + return $this->dispatcher->removeListener($eventName, $listener); + } + + /** + * {@inheritdoc} + */ + public function removeSubscriber(EventSubscriberInterface $subscriber) + { + return $this->dispatcher->removeSubscriber($subscriber); + } + + /** + * {@inheritdoc} + */ + public function getListeners($eventName = null): array + { + return $this->dispatcher->getListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function getListenerPriority($eventName, $listener): ?int + { + return $this->dispatcher->getListenerPriority($eventName, $listener); + } + + /** + * {@inheritdoc} + */ + public function hasListeners($eventName = null): bool + { + return $this->dispatcher->hasListeners($eventName); + } + + /** + * Proxies all method calls to the original event dispatcher. + */ + public function __call($method, $arguments) + { + return $this->dispatcher->{$method}(...$arguments); + } +} diff --git a/vendor/symfony/event-dispatcher/LegacyEventProxy.php b/vendor/symfony/event-dispatcher/LegacyEventProxy.php new file mode 100644 index 0000000000..45ee251d6a --- /dev/null +++ b/vendor/symfony/event-dispatcher/LegacyEventProxy.php @@ -0,0 +1,62 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +use Psr\EventDispatcher\StoppableEventInterface; +use Symfony\Contracts\EventDispatcher\Event as ContractsEvent; + +/** + * @internal to be removed in 5.0. + */ +final class LegacyEventProxy extends Event +{ + private $event; + + /** + * @param object $event + */ + public function __construct($event) + { + $this->event = $event; + } + + /** + * @return object $event + */ + public function getEvent() + { + return $this->event; + } + + public function isPropagationStopped(): bool + { + if (!$this->event instanceof ContractsEvent && !$this->event instanceof StoppableEventInterface) { + return false; + } + + return $this->event->isPropagationStopped(); + } + + public function stopPropagation() + { + if (!$this->event instanceof ContractsEvent) { + return; + } + + $this->event->stopPropagation(); + } + + public function __call($name, $args) + { + return $this->event->{$name}(...$args); + } +} diff --git a/vendor/symfony/event-dispatcher/README.md b/vendor/symfony/event-dispatcher/README.md new file mode 100644 index 0000000000..dcdb68d218 --- /dev/null +++ b/vendor/symfony/event-dispatcher/README.md @@ -0,0 +1,15 @@ +EventDispatcher Component +========================= + +The EventDispatcher component provides tools that allow your application +components to communicate with each other by dispatching events and listening to +them. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/event_dispatcher.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/event-dispatcher/composer.json b/vendor/symfony/event-dispatcher/composer.json new file mode 100644 index 0000000000..55c2716a63 --- /dev/null +++ b/vendor/symfony/event-dispatcher/composer.json @@ -0,0 +1,51 @@ +{ + "name": "symfony/event-dispatcher", + "type": "library", + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1.3", + "symfony/event-dispatcher-contracts": "^1.1", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/error-handler": "~3.4|~4.4", + "symfony/http-foundation": "^3.4|^4.0|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/stopwatch": "^3.4|^4.0|^5.0", + "psr/log": "^1|^2|^3" + }, + "conflict": { + "symfony/dependency-injection": "<3.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "1.1" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\EventDispatcher\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/finder/CHANGELOG.md b/vendor/symfony/finder/CHANGELOG.md new file mode 100644 index 0000000000..2045184e83 --- /dev/null +++ b/vendor/symfony/finder/CHANGELOG.md @@ -0,0 +1,74 @@ +CHANGELOG +========= + +4.3.0 +----- + + * added Finder::ignoreVCSIgnored() to ignore files based on rules listed in .gitignore + +4.2.0 +----- + + * added $useNaturalSort option to Finder::sortByName() method + * the `Finder::sortByName()` method will have a new `$useNaturalSort` + argument in version 5.0, not defining it is deprecated + * added `Finder::reverseSorting()` to reverse the sorting + +4.0.0 +----- + + * removed `ExceptionInterface` + * removed `Symfony\Component\Finder\Iterator\FilterIterator` + +3.4.0 +----- + + * deprecated `Symfony\Component\Finder\Iterator\FilterIterator` + * added Finder::hasResults() method to check if any results were found + +3.3.0 +----- + + * added double-star matching to Glob::toRegex() + +3.0.0 +----- + + * removed deprecated classes + +2.8.0 +----- + + * deprecated adapters and related classes + +2.5.0 +----- + * added support for GLOB_BRACE in the paths passed to Finder::in() + +2.3.0 +----- + + * added a way to ignore unreadable directories (via Finder::ignoreUnreadableDirs()) + * unified the way subfolders that are not executable are handled by always throwing an AccessDeniedException exception + +2.2.0 +----- + + * added Finder::path() and Finder::notPath() methods + * added finder adapters to improve performance on specific platforms + * added support for wildcard characters (glob patterns) in the paths passed + to Finder::in() + +2.1.0 +----- + + * added Finder::sortByAccessedTime(), Finder::sortByChangedTime(), and + Finder::sortByModifiedTime() + * added Countable to Finder + * added support for an array of directories as an argument to + Finder::exclude() + * added searching based on the file content via Finder::contains() and + Finder::notContains() + * added support for the != operator in the Comparator + * [BC BREAK] filter expressions (used for file name and content) are no more + considered as regexps but glob patterns when they are enclosed in '*' or '?' diff --git a/vendor/symfony/finder/Comparator/Comparator.php b/vendor/symfony/finder/Comparator/Comparator.php new file mode 100644 index 0000000000..6aee21cf09 --- /dev/null +++ b/vendor/symfony/finder/Comparator/Comparator.php @@ -0,0 +1,98 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Comparator; + +/** + * Comparator. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class Comparator +{ + private $target; + private $operator = '=='; + + /** + * Gets the target value. + * + * @return string The target value + */ + public function getTarget() + { + return $this->target; + } + + /** + * Sets the target value. + * + * @param string $target The target value + */ + public function setTarget($target) + { + $this->target = $target; + } + + /** + * Gets the comparison operator. + * + * @return string The operator + */ + public function getOperator() + { + return $this->operator; + } + + /** + * Sets the comparison operator. + * + * @param string $operator A valid operator + * + * @throws \InvalidArgumentException + */ + public function setOperator($operator) + { + if (!$operator) { + $operator = '=='; + } + + if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='])) { + throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator)); + } + + $this->operator = $operator; + } + + /** + * Tests against the target. + * + * @param mixed $test A test value + * + * @return bool + */ + public function test($test) + { + switch ($this->operator) { + case '>': + return $test > $this->target; + case '>=': + return $test >= $this->target; + case '<': + return $test < $this->target; + case '<=': + return $test <= $this->target; + case '!=': + return $test != $this->target; + } + + return $test == $this->target; + } +} diff --git a/vendor/symfony/finder/Comparator/DateComparator.php b/vendor/symfony/finder/Comparator/DateComparator.php new file mode 100644 index 0000000000..ae22c6cbec --- /dev/null +++ b/vendor/symfony/finder/Comparator/DateComparator.php @@ -0,0 +1,51 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Comparator; + +/** + * DateCompare compiles date comparisons. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class DateComparator extends Comparator +{ + /** + * @param string $test A comparison string + * + * @throws \InvalidArgumentException If the test is not understood + */ + public function __construct(string $test) + { + if (!preg_match('#^\s*(==|!=|[<>]=?|after|since|before|until)?\s*(.+?)\s*$#i', $test, $matches)) { + throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a date test.', $test)); + } + + try { + $date = new \DateTime($matches[2]); + $target = $date->format('U'); + } catch (\Exception $e) { + throw new \InvalidArgumentException(sprintf('"%s" is not a valid date.', $matches[2])); + } + + $operator = $matches[1] ?? '=='; + if ('since' === $operator || 'after' === $operator) { + $operator = '>'; + } + + if ('until' === $operator || 'before' === $operator) { + $operator = '<'; + } + + $this->setOperator($operator); + $this->setTarget($target); + } +} diff --git a/vendor/symfony/finder/Comparator/NumberComparator.php b/vendor/symfony/finder/Comparator/NumberComparator.php new file mode 100644 index 0000000000..657118fb6b --- /dev/null +++ b/vendor/symfony/finder/Comparator/NumberComparator.php @@ -0,0 +1,79 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Comparator; + +/** + * NumberComparator compiles a simple comparison to an anonymous + * subroutine, which you can call with a value to be tested again. + * + * Now this would be very pointless, if NumberCompare didn't understand + * magnitudes. + * + * The target value may use magnitudes of kilobytes (k, ki), + * megabytes (m, mi), or gigabytes (g, gi). Those suffixed + * with an i use the appropriate 2**n version in accordance with the + * IEC standard: http://physics.nist.gov/cuu/Units/binary.html + * + * Based on the Perl Number::Compare module. + * + * @author Fabien Potencier <fabien@symfony.com> PHP port + * @author Richard Clamp <richardc@unixbeard.net> Perl version + * @copyright 2004-2005 Fabien Potencier <fabien@symfony.com> + * @copyright 2002 Richard Clamp <richardc@unixbeard.net> + * + * @see http://physics.nist.gov/cuu/Units/binary.html + */ +class NumberComparator extends Comparator +{ + /** + * @param string|int $test A comparison string or an integer + * + * @throws \InvalidArgumentException If the test is not understood + */ + public function __construct(?string $test) + { + if (null === $test || !preg_match('#^\s*(==|!=|[<>]=?)?\s*([0-9\.]+)\s*([kmg]i?)?\s*$#i', $test, $matches)) { + throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a number test.', $test ?? 'null')); + } + + $target = $matches[2]; + if (!is_numeric($target)) { + throw new \InvalidArgumentException(sprintf('Invalid number "%s".', $target)); + } + if (isset($matches[3])) { + // magnitude + switch (strtolower($matches[3])) { + case 'k': + $target *= 1000; + break; + case 'ki': + $target *= 1024; + break; + case 'm': + $target *= 1000000; + break; + case 'mi': + $target *= 1024 * 1024; + break; + case 'g': + $target *= 1000000000; + break; + case 'gi': + $target *= 1024 * 1024 * 1024; + break; + } + } + + $this->setTarget($target); + $this->setOperator($matches[1] ?? '=='); + } +} diff --git a/vendor/symfony/finder/Exception/AccessDeniedException.php b/vendor/symfony/finder/Exception/AccessDeniedException.php new file mode 100644 index 0000000000..ee195ea8d7 --- /dev/null +++ b/vendor/symfony/finder/Exception/AccessDeniedException.php @@ -0,0 +1,19 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Exception; + +/** + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class AccessDeniedException extends \UnexpectedValueException +{ +} diff --git a/vendor/symfony/finder/Exception/DirectoryNotFoundException.php b/vendor/symfony/finder/Exception/DirectoryNotFoundException.php new file mode 100644 index 0000000000..c6cc0f2736 --- /dev/null +++ b/vendor/symfony/finder/Exception/DirectoryNotFoundException.php @@ -0,0 +1,19 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Exception; + +/** + * @author Andreas Erhard <andreas.erhard@i-med.ac.at> + */ +class DirectoryNotFoundException extends \InvalidArgumentException +{ +} diff --git a/vendor/symfony/finder/Finder.php b/vendor/symfony/finder/Finder.php new file mode 100644 index 0000000000..e1194ed695 --- /dev/null +++ b/vendor/symfony/finder/Finder.php @@ -0,0 +1,823 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder; + +use Symfony\Component\Finder\Comparator\DateComparator; +use Symfony\Component\Finder\Comparator\NumberComparator; +use Symfony\Component\Finder\Exception\DirectoryNotFoundException; +use Symfony\Component\Finder\Iterator\CustomFilterIterator; +use Symfony\Component\Finder\Iterator\DateRangeFilterIterator; +use Symfony\Component\Finder\Iterator\DepthRangeFilterIterator; +use Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator; +use Symfony\Component\Finder\Iterator\FilecontentFilterIterator; +use Symfony\Component\Finder\Iterator\FilenameFilterIterator; +use Symfony\Component\Finder\Iterator\LazyIterator; +use Symfony\Component\Finder\Iterator\SizeRangeFilterIterator; +use Symfony\Component\Finder\Iterator\SortableIterator; + +/** + * Finder allows to build rules to find files and directories. + * + * It is a thin wrapper around several specialized iterator classes. + * + * All rules may be invoked several times. + * + * All methods return the current Finder object to allow chaining: + * + * $finder = Finder::create()->files()->name('*.php')->in(__DIR__); + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class Finder implements \IteratorAggregate, \Countable +{ + public const IGNORE_VCS_FILES = 1; + public const IGNORE_DOT_FILES = 2; + public const IGNORE_VCS_IGNORED_FILES = 4; + + private $mode = 0; + private $names = []; + private $notNames = []; + private $exclude = []; + private $filters = []; + private $depths = []; + private $sizes = []; + private $followLinks = false; + private $reverseSorting = false; + private $sort = false; + private $ignore = 0; + private $dirs = []; + private $dates = []; + private $iterators = []; + private $contains = []; + private $notContains = []; + private $paths = []; + private $notPaths = []; + private $ignoreUnreadableDirs = false; + + private static $vcsPatterns = ['.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg']; + + public function __construct() + { + $this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES; + } + + /** + * Creates a new Finder. + * + * @return static + */ + public static function create() + { + return new static(); + } + + /** + * Restricts the matching to directories only. + * + * @return $this + */ + public function directories() + { + $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES; + + return $this; + } + + /** + * Restricts the matching to files only. + * + * @return $this + */ + public function files() + { + $this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES; + + return $this; + } + + /** + * Adds tests for the directory depth. + * + * Usage: + * + * $finder->depth('> 1') // the Finder will start matching at level 1. + * $finder->depth('< 3') // the Finder will descend at most 3 levels of directories below the starting point. + * $finder->depth(['>= 1', '< 3']) + * + * @param string|int|string[]|int[] $levels The depth level expression or an array of depth levels + * + * @return $this + * + * @see DepthRangeFilterIterator + * @see NumberComparator + */ + public function depth($levels) + { + foreach ((array) $levels as $level) { + $this->depths[] = new Comparator\NumberComparator($level); + } + + return $this; + } + + /** + * Adds tests for file dates (last modified). + * + * The date must be something that strtotime() is able to parse: + * + * $finder->date('since yesterday'); + * $finder->date('until 2 days ago'); + * $finder->date('> now - 2 hours'); + * $finder->date('>= 2005-10-15'); + * $finder->date(['>= 2005-10-15', '<= 2006-05-27']); + * + * @param string|string[] $dates A date range string or an array of date ranges + * + * @return $this + * + * @see strtotime + * @see DateRangeFilterIterator + * @see DateComparator + */ + public function date($dates) + { + foreach ((array) $dates as $date) { + $this->dates[] = new Comparator\DateComparator($date); + } + + return $this; + } + + /** + * Adds rules that files must match. + * + * You can use patterns (delimited with / sign), globs or simple strings. + * + * $finder->name('*.php') + * $finder->name('/\.php$/') // same as above + * $finder->name('test.php') + * $finder->name(['test.py', 'test.php']) + * + * @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns + * + * @return $this + * + * @see FilenameFilterIterator + */ + public function name($patterns) + { + $this->names = array_merge($this->names, (array) $patterns); + + return $this; + } + + /** + * Adds rules that files must not match. + * + * @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns + * + * @return $this + * + * @see FilenameFilterIterator + */ + public function notName($patterns) + { + $this->notNames = array_merge($this->notNames, (array) $patterns); + + return $this; + } + + /** + * Adds tests that file contents must match. + * + * Strings or PCRE patterns can be used: + * + * $finder->contains('Lorem ipsum') + * $finder->contains('/Lorem ipsum/i') + * $finder->contains(['dolor', '/ipsum/i']) + * + * @param string|string[] $patterns A pattern (string or regexp) or an array of patterns + * + * @return $this + * + * @see FilecontentFilterIterator + */ + public function contains($patterns) + { + $this->contains = array_merge($this->contains, (array) $patterns); + + return $this; + } + + /** + * Adds tests that file contents must not match. + * + * Strings or PCRE patterns can be used: + * + * $finder->notContains('Lorem ipsum') + * $finder->notContains('/Lorem ipsum/i') + * $finder->notContains(['lorem', '/dolor/i']) + * + * @param string|string[] $patterns A pattern (string or regexp) or an array of patterns + * + * @return $this + * + * @see FilecontentFilterIterator + */ + public function notContains($patterns) + { + $this->notContains = array_merge($this->notContains, (array) $patterns); + + return $this; + } + + /** + * Adds rules that filenames must match. + * + * You can use patterns (delimited with / sign) or simple strings. + * + * $finder->path('some/special/dir') + * $finder->path('/some\/special\/dir/') // same as above + * $finder->path(['some dir', 'another/dir']) + * + * Use only / as dirname separator. + * + * @param string|string[] $patterns A pattern (a regexp or a string) or an array of patterns + * + * @return $this + * + * @see FilenameFilterIterator + */ + public function path($patterns) + { + $this->paths = array_merge($this->paths, (array) $patterns); + + return $this; + } + + /** + * Adds rules that filenames must not match. + * + * You can use patterns (delimited with / sign) or simple strings. + * + * $finder->notPath('some/special/dir') + * $finder->notPath('/some\/special\/dir/') // same as above + * $finder->notPath(['some/file.txt', 'another/file.log']) + * + * Use only / as dirname separator. + * + * @param string|string[] $patterns A pattern (a regexp or a string) or an array of patterns + * + * @return $this + * + * @see FilenameFilterIterator + */ + public function notPath($patterns) + { + $this->notPaths = array_merge($this->notPaths, (array) $patterns); + + return $this; + } + + /** + * Adds tests for file sizes. + * + * $finder->size('> 10K'); + * $finder->size('<= 1Ki'); + * $finder->size(4); + * $finder->size(['> 10K', '< 20K']) + * + * @param string|int|string[]|int[] $sizes A size range string or an integer or an array of size ranges + * + * @return $this + * + * @see SizeRangeFilterIterator + * @see NumberComparator + */ + public function size($sizes) + { + foreach ((array) $sizes as $size) { + $this->sizes[] = new Comparator\NumberComparator($size); + } + + return $this; + } + + /** + * Excludes directories. + * + * Directories passed as argument must be relative to the ones defined with the `in()` method. For example: + * + * $finder->in(__DIR__)->exclude('ruby'); + * + * @param string|array $dirs A directory path or an array of directories + * + * @return $this + * + * @see ExcludeDirectoryFilterIterator + */ + public function exclude($dirs) + { + $this->exclude = array_merge($this->exclude, (array) $dirs); + + return $this; + } + + /** + * Excludes "hidden" directories and files (starting with a dot). + * + * This option is enabled by default. + * + * @param bool $ignoreDotFiles Whether to exclude "hidden" files or not + * + * @return $this + * + * @see ExcludeDirectoryFilterIterator + */ + public function ignoreDotFiles($ignoreDotFiles) + { + if ($ignoreDotFiles) { + $this->ignore |= static::IGNORE_DOT_FILES; + } else { + $this->ignore &= ~static::IGNORE_DOT_FILES; + } + + return $this; + } + + /** + * Forces the finder to ignore version control directories. + * + * This option is enabled by default. + * + * @param bool $ignoreVCS Whether to exclude VCS files or not + * + * @return $this + * + * @see ExcludeDirectoryFilterIterator + */ + public function ignoreVCS($ignoreVCS) + { + if ($ignoreVCS) { + $this->ignore |= static::IGNORE_VCS_FILES; + } else { + $this->ignore &= ~static::IGNORE_VCS_FILES; + } + + return $this; + } + + /** + * Forces Finder to obey .gitignore and ignore files based on rules listed there. + * + * This option is disabled by default. + * + * @return $this + */ + public function ignoreVCSIgnored(bool $ignoreVCSIgnored) + { + if ($ignoreVCSIgnored) { + $this->ignore |= static::IGNORE_VCS_IGNORED_FILES; + } else { + $this->ignore &= ~static::IGNORE_VCS_IGNORED_FILES; + } + + return $this; + } + + /** + * Adds VCS patterns. + * + * @see ignoreVCS() + * + * @param string|string[] $pattern VCS patterns to ignore + */ + public static function addVCSPattern($pattern) + { + foreach ((array) $pattern as $p) { + self::$vcsPatterns[] = $p; + } + + self::$vcsPatterns = array_unique(self::$vcsPatterns); + } + + /** + * Sorts files and directories by an anonymous function. + * + * The anonymous function receives two \SplFileInfo instances to compare. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sort(\Closure $closure) + { + $this->sort = $closure; + + return $this; + } + + /** + * Sorts files and directories by name. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @param bool $useNaturalSort Whether to use natural sort or not, disabled by default + * + * @return $this + * + * @see SortableIterator + */ + public function sortByName(/* bool $useNaturalSort = false */) + { + if (\func_num_args() < 1 && __CLASS__ !== static::class && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName() && !$this instanceof \PHPUnit\Framework\MockObject\MockObject && !$this instanceof \Prophecy\Prophecy\ProphecySubjectInterface && !$this instanceof \Mockery\MockInterface) { + @trigger_error(sprintf('The "%s()" method will have a new "bool $useNaturalSort = false" argument in version 5.0, not defining it is deprecated since Symfony 4.2.', __METHOD__), \E_USER_DEPRECATED); + } + $useNaturalSort = 0 < \func_num_args() && func_get_arg(0); + + $this->sort = $useNaturalSort ? Iterator\SortableIterator::SORT_BY_NAME_NATURAL : Iterator\SortableIterator::SORT_BY_NAME; + + return $this; + } + + /** + * Sorts files and directories by type (directories before files), then by name. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByType() + { + $this->sort = Iterator\SortableIterator::SORT_BY_TYPE; + + return $this; + } + + /** + * Sorts files and directories by the last accessed time. + * + * This is the time that the file was last accessed, read or written to. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByAccessedTime() + { + $this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME; + + return $this; + } + + /** + * Reverses the sorting. + * + * @return $this + */ + public function reverseSorting() + { + $this->reverseSorting = true; + + return $this; + } + + /** + * Sorts files and directories by the last inode changed time. + * + * This is the time that the inode information was last modified (permissions, owner, group or other metadata). + * + * On Windows, since inode is not available, changed time is actually the file creation time. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByChangedTime() + { + $this->sort = Iterator\SortableIterator::SORT_BY_CHANGED_TIME; + + return $this; + } + + /** + * Sorts files and directories by the last modified time. + * + * This is the last time the actual contents of the file were last modified. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByModifiedTime() + { + $this->sort = Iterator\SortableIterator::SORT_BY_MODIFIED_TIME; + + return $this; + } + + /** + * Filters the iterator with an anonymous function. + * + * The anonymous function receives a \SplFileInfo and must return false + * to remove files. + * + * @return $this + * + * @see CustomFilterIterator + */ + public function filter(\Closure $closure) + { + $this->filters[] = $closure; + + return $this; + } + + /** + * Forces the following of symlinks. + * + * @return $this + */ + public function followLinks() + { + $this->followLinks = true; + + return $this; + } + + /** + * Tells finder to ignore unreadable directories. + * + * By default, scanning unreadable directories content throws an AccessDeniedException. + * + * @param bool $ignore + * + * @return $this + */ + public function ignoreUnreadableDirs($ignore = true) + { + $this->ignoreUnreadableDirs = (bool) $ignore; + + return $this; + } + + /** + * Searches files and directories which match defined rules. + * + * @param string|string[] $dirs A directory path or an array of directories + * + * @return $this + * + * @throws DirectoryNotFoundException if one of the directories does not exist + */ + public function in($dirs) + { + $resolvedDirs = []; + + foreach ((array) $dirs as $dir) { + if (is_dir($dir)) { + $resolvedDirs[] = $this->normalizeDir($dir); + } elseif ($glob = glob($dir, (\defined('GLOB_BRACE') ? \GLOB_BRACE : 0) | \GLOB_ONLYDIR | \GLOB_NOSORT)) { + sort($glob); + $resolvedDirs = array_merge($resolvedDirs, array_map([$this, 'normalizeDir'], $glob)); + } else { + throw new DirectoryNotFoundException(sprintf('The "%s" directory does not exist.', $dir)); + } + } + + $this->dirs = array_merge($this->dirs, $resolvedDirs); + + return $this; + } + + /** + * Returns an Iterator for the current Finder configuration. + * + * This method implements the IteratorAggregate interface. + * + * @return \Iterator|SplFileInfo[] An iterator + * + * @throws \LogicException if the in() method has not been called + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + if (0 === \count($this->dirs) && 0 === \count($this->iterators)) { + throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.'); + } + + if (1 === \count($this->dirs) && 0 === \count($this->iterators)) { + $iterator = $this->searchInDirectory($this->dirs[0]); + + if ($this->sort || $this->reverseSorting) { + $iterator = (new Iterator\SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator(); + } + + return $iterator; + } + + $iterator = new \AppendIterator(); + foreach ($this->dirs as $dir) { + $iterator->append(new \IteratorIterator(new LazyIterator(function () use ($dir) { + return $this->searchInDirectory($dir); + }))); + } + + foreach ($this->iterators as $it) { + $iterator->append($it); + } + + if ($this->sort || $this->reverseSorting) { + $iterator = (new Iterator\SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator(); + } + + return $iterator; + } + + /** + * Appends an existing set of files/directories to the finder. + * + * The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array. + * + * @param iterable $iterator + * + * @return $this + * + * @throws \InvalidArgumentException when the given argument is not iterable + */ + public function append($iterator) + { + if ($iterator instanceof \IteratorAggregate) { + $this->iterators[] = $iterator->getIterator(); + } elseif ($iterator instanceof \Iterator) { + $this->iterators[] = $iterator; + } elseif (is_iterable($iterator)) { + $it = new \ArrayIterator(); + foreach ($iterator as $file) { + $file = $file instanceof \SplFileInfo ? $file : new \SplFileInfo($file); + $it[$file->getPathname()] = $file; + } + $this->iterators[] = $it; + } else { + throw new \InvalidArgumentException('Finder::append() method wrong argument type.'); + } + + return $this; + } + + /** + * Check if any results were found. + * + * @return bool + */ + public function hasResults() + { + foreach ($this->getIterator() as $_) { + return true; + } + + return false; + } + + /** + * Counts all the results collected by the iterators. + * + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + return iterator_count($this->getIterator()); + } + + private function searchInDirectory(string $dir): \Iterator + { + $exclude = $this->exclude; + $notPaths = $this->notPaths; + + if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) { + $exclude = array_merge($exclude, self::$vcsPatterns); + } + + if (static::IGNORE_DOT_FILES === (static::IGNORE_DOT_FILES & $this->ignore)) { + $notPaths[] = '#(^|/)\..+(/|$)#'; + } + + if (static::IGNORE_VCS_IGNORED_FILES === (static::IGNORE_VCS_IGNORED_FILES & $this->ignore)) { + $gitignoreFilePath = sprintf('%s/.gitignore', $dir); + if (!is_readable($gitignoreFilePath)) { + throw new \RuntimeException(sprintf('The "ignoreVCSIgnored" option cannot be used by the Finder as the "%s" file is not readable.', $gitignoreFilePath)); + } + $notPaths = array_merge($notPaths, [Gitignore::toRegex(file_get_contents($gitignoreFilePath))]); + } + + $minDepth = 0; + $maxDepth = \PHP_INT_MAX; + + foreach ($this->depths as $comparator) { + switch ($comparator->getOperator()) { + case '>': + $minDepth = $comparator->getTarget() + 1; + break; + case '>=': + $minDepth = $comparator->getTarget(); + break; + case '<': + $maxDepth = $comparator->getTarget() - 1; + break; + case '<=': + $maxDepth = $comparator->getTarget(); + break; + default: + $minDepth = $maxDepth = $comparator->getTarget(); + } + } + + $flags = \RecursiveDirectoryIterator::SKIP_DOTS; + + if ($this->followLinks) { + $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS; + } + + $iterator = new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs); + + if ($exclude) { + $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $exclude); + } + + $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST); + + if ($minDepth > 0 || $maxDepth < \PHP_INT_MAX) { + $iterator = new Iterator\DepthRangeFilterIterator($iterator, $minDepth, $maxDepth); + } + + if ($this->mode) { + $iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode); + } + + if ($this->names || $this->notNames) { + $iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames); + } + + if ($this->contains || $this->notContains) { + $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains); + } + + if ($this->sizes) { + $iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes); + } + + if ($this->dates) { + $iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates); + } + + if ($this->filters) { + $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters); + } + + if ($this->paths || $notPaths) { + $iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $notPaths); + } + + return $iterator; + } + + /** + * Normalizes given directory names by removing trailing slashes. + * + * Excluding: (s)ftp:// or ssh2.(s)ftp:// wrapper + */ + private function normalizeDir(string $dir): string + { + if ('/' === $dir) { + return $dir; + } + + $dir = rtrim($dir, '/'.\DIRECTORY_SEPARATOR); + + if (preg_match('#^(ssh2\.)?s?ftp://#', $dir)) { + $dir .= '/'; + } + + return $dir; + } +} diff --git a/vendor/symfony/finder/Gitignore.php b/vendor/symfony/finder/Gitignore.php new file mode 100644 index 0000000000..491f588ee2 --- /dev/null +++ b/vendor/symfony/finder/Gitignore.php @@ -0,0 +1,83 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder; + +/** + * Gitignore matches against text. + * + * @author Michael Voříšek <vorismi3@fel.cvut.cz> + * @author Ahmed Abdou <mail@ahmd.io> + */ +class Gitignore +{ + /** + * Returns a regexp which is the equivalent of the gitignore pattern. + * + * Format specification: https://git-scm.com/docs/gitignore#_pattern_format + */ + public static function toRegex(string $gitignoreFileContent): string + { + $gitignoreFileContent = preg_replace('~(?<!\\\\)#[^\n\r]*~', '', $gitignoreFileContent); + $gitignoreLines = preg_split('~\r\n?|\n~', $gitignoreFileContent); + + $res = self::lineToRegex(''); + foreach ($gitignoreLines as $i => $line) { + $line = preg_replace('~(?<!\\\\)[ \t]+$~', '', $line); + + if ('!' === substr($line, 0, 1)) { + $line = substr($line, 1); + $isNegative = true; + } else { + $isNegative = false; + } + + if ('' !== $line) { + if ($isNegative) { + $res = '(?!'.self::lineToRegex($line).'$)'.$res; + } else { + $res = '(?:'.$res.'|'.self::lineToRegex($line).')'; + } + } + } + + return '~^(?:'.$res.')~s'; + } + + private static function lineToRegex(string $gitignoreLine): string + { + if ('' === $gitignoreLine) { + return '$f'; // always false + } + + $slashPos = strpos($gitignoreLine, '/'); + if (false !== $slashPos && \strlen($gitignoreLine) - 1 !== $slashPos) { + if (0 === $slashPos) { + $gitignoreLine = substr($gitignoreLine, 1); + } + $isAbsolute = true; + } else { + $isAbsolute = false; + } + + $regex = preg_quote(str_replace('\\', '', $gitignoreLine), '~'); + $regex = preg_replace_callback('~\\\\\[((?:\\\\!)?)([^\[\]]*)\\\\\]~', function (array $matches): string { + return '['.('' !== $matches[1] ? '^' : '').str_replace('\\-', '-', $matches[2]).']'; + }, $regex); + $regex = preg_replace('~(?:(?:\\\\\*){2,}(/?))+~', '(?:(?:(?!//).(?<!//))+$1)?', $regex); + $regex = preg_replace('~\\\\\*~', '[^/]*', $regex); + $regex = preg_replace('~\\\\\?~', '[^/]', $regex); + + return ($isAbsolute ? '' : '(?:[^/]+/)*') + .$regex + .(!str_ends_with($gitignoreLine, '/') ? '(?:$|/)' : ''); + } +} diff --git a/vendor/symfony/finder/Glob.php b/vendor/symfony/finder/Glob.php new file mode 100644 index 0000000000..ea76d51ae0 --- /dev/null +++ b/vendor/symfony/finder/Glob.php @@ -0,0 +1,116 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder; + +/** + * Glob matches globbing patterns against text. + * + * if match_glob("foo.*", "foo.bar") echo "matched\n"; + * + * // prints foo.bar and foo.baz + * $regex = glob_to_regex("foo.*"); + * for (['foo.bar', 'foo.baz', 'foo', 'bar'] as $t) + * { + * if (/$regex/) echo "matched: $car\n"; + * } + * + * Glob implements glob(3) style matching that can be used to match + * against text, rather than fetching names from a filesystem. + * + * Based on the Perl Text::Glob module. + * + * @author Fabien Potencier <fabien@symfony.com> PHP port + * @author Richard Clamp <richardc@unixbeard.net> Perl version + * @copyright 2004-2005 Fabien Potencier <fabien@symfony.com> + * @copyright 2002 Richard Clamp <richardc@unixbeard.net> + */ +class Glob +{ + /** + * Returns a regexp which is the equivalent of the glob pattern. + * + * @param string $glob The glob pattern + * @param bool $strictLeadingDot + * @param bool $strictWildcardSlash + * @param string $delimiter Optional delimiter + * + * @return string regex The regexp + */ + public static function toRegex($glob, $strictLeadingDot = true, $strictWildcardSlash = true, $delimiter = '#') + { + $firstByte = true; + $escaping = false; + $inCurlies = 0; + $regex = ''; + $sizeGlob = \strlen($glob); + for ($i = 0; $i < $sizeGlob; ++$i) { + $car = $glob[$i]; + if ($firstByte && $strictLeadingDot && '.' !== $car) { + $regex .= '(?=[^\.])'; + } + + $firstByte = '/' === $car; + + if ($firstByte && $strictWildcardSlash && isset($glob[$i + 2]) && '**' === $glob[$i + 1].$glob[$i + 2] && (!isset($glob[$i + 3]) || '/' === $glob[$i + 3])) { + $car = '[^/]++/'; + if (!isset($glob[$i + 3])) { + $car .= '?'; + } + + if ($strictLeadingDot) { + $car = '(?=[^\.])'.$car; + } + + $car = '/(?:'.$car.')*'; + $i += 2 + isset($glob[$i + 3]); + + if ('/' === $delimiter) { + $car = str_replace('/', '\\/', $car); + } + } + + if ($delimiter === $car || '.' === $car || '(' === $car || ')' === $car || '|' === $car || '+' === $car || '^' === $car || '$' === $car) { + $regex .= "\\$car"; + } elseif ('*' === $car) { + $regex .= $escaping ? '\\*' : ($strictWildcardSlash ? '[^/]*' : '.*'); + } elseif ('?' === $car) { + $regex .= $escaping ? '\\?' : ($strictWildcardSlash ? '[^/]' : '.'); + } elseif ('{' === $car) { + $regex .= $escaping ? '\\{' : '('; + if (!$escaping) { + ++$inCurlies; + } + } elseif ('}' === $car && $inCurlies) { + $regex .= $escaping ? '}' : ')'; + if (!$escaping) { + --$inCurlies; + } + } elseif (',' === $car && $inCurlies) { + $regex .= $escaping ? ',' : '|'; + } elseif ('\\' === $car) { + if ($escaping) { + $regex .= '\\\\'; + $escaping = false; + } else { + $escaping = true; + } + + continue; + } else { + $regex .= $car; + } + $escaping = false; + } + + return $delimiter.'^'.$regex.'$'.$delimiter; + } +} diff --git a/vendor/symfony/finder/Iterator/CustomFilterIterator.php b/vendor/symfony/finder/Iterator/CustomFilterIterator.php new file mode 100644 index 0000000000..f85cb7bffb --- /dev/null +++ b/vendor/symfony/finder/Iterator/CustomFilterIterator.php @@ -0,0 +1,62 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * CustomFilterIterator filters files by applying anonymous functions. + * + * The anonymous function receives a \SplFileInfo and must return false + * to remove files. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class CustomFilterIterator extends \FilterIterator +{ + private $filters = []; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param callable[] $filters An array of PHP callbacks + * + * @throws \InvalidArgumentException + */ + public function __construct(\Iterator $iterator, array $filters) + { + foreach ($filters as $filter) { + if (!\is_callable($filter)) { + throw new \InvalidArgumentException('Invalid PHP callback.'); + } + } + $this->filters = $filters; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + #[\ReturnTypeWillChange] + public function accept() + { + $fileinfo = $this->current(); + + foreach ($this->filters as $filter) { + if (false === $filter($fileinfo)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/symfony/finder/Iterator/DateRangeFilterIterator.php b/vendor/symfony/finder/Iterator/DateRangeFilterIterator.php new file mode 100644 index 0000000000..90616f471b --- /dev/null +++ b/vendor/symfony/finder/Iterator/DateRangeFilterIterator.php @@ -0,0 +1,59 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Comparator\DateComparator; + +/** + * DateRangeFilterIterator filters out files that are not in the given date range (last modified dates). + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class DateRangeFilterIterator extends \FilterIterator +{ + private $comparators = []; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param DateComparator[] $comparators An array of DateComparator instances + */ + public function __construct(\Iterator $iterator, array $comparators) + { + $this->comparators = $comparators; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + #[\ReturnTypeWillChange] + public function accept() + { + $fileinfo = $this->current(); + + if (!file_exists($fileinfo->getPathname())) { + return false; + } + + $filedate = $fileinfo->getMTime(); + foreach ($this->comparators as $compare) { + if (!$compare->test($filedate)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php b/vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php new file mode 100644 index 0000000000..e96fefd961 --- /dev/null +++ b/vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php @@ -0,0 +1,46 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * DepthRangeFilterIterator limits the directory depth. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class DepthRangeFilterIterator extends \FilterIterator +{ + private $minDepth = 0; + + /** + * @param \RecursiveIteratorIterator $iterator The Iterator to filter + * @param int $minDepth The min depth + * @param int $maxDepth The max depth + */ + public function __construct(\RecursiveIteratorIterator $iterator, int $minDepth = 0, int $maxDepth = \PHP_INT_MAX) + { + $this->minDepth = $minDepth; + $iterator->setMaxDepth(\PHP_INT_MAX === $maxDepth ? -1 : $maxDepth); + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + #[\ReturnTypeWillChange] + public function accept() + { + return $this->getInnerIterator()->getDepth() >= $this->minDepth; + } +} diff --git a/vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php b/vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php new file mode 100644 index 0000000000..cf9e678771 --- /dev/null +++ b/vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php @@ -0,0 +1,93 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * ExcludeDirectoryFilterIterator filters out directories. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class ExcludeDirectoryFilterIterator extends \FilterIterator implements \RecursiveIterator +{ + private $iterator; + private $isRecursive; + private $excludedDirs = []; + private $excludedPattern; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param string[] $directories An array of directories to exclude + */ + public function __construct(\Iterator $iterator, array $directories) + { + $this->iterator = $iterator; + $this->isRecursive = $iterator instanceof \RecursiveIterator; + $patterns = []; + foreach ($directories as $directory) { + $directory = rtrim($directory, '/'); + if (!$this->isRecursive || str_contains($directory, '/')) { + $patterns[] = preg_quote($directory, '#'); + } else { + $this->excludedDirs[$directory] = true; + } + } + if ($patterns) { + $this->excludedPattern = '#(?:^|/)(?:'.implode('|', $patterns).')(?:/|$)#'; + } + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return bool True if the value should be kept, false otherwise + */ + #[\ReturnTypeWillChange] + public function accept() + { + if ($this->isRecursive && isset($this->excludedDirs[$this->getFilename()]) && $this->isDir()) { + return false; + } + + if ($this->excludedPattern) { + $path = $this->isDir() ? $this->current()->getRelativePathname() : $this->current()->getRelativePath(); + $path = str_replace('\\', '/', $path); + + return !preg_match($this->excludedPattern, $path); + } + + return true; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function hasChildren() + { + return $this->isRecursive && $this->iterator->hasChildren(); + } + + /** + * @return self + */ + #[\ReturnTypeWillChange] + public function getChildren() + { + $children = new self($this->iterator->getChildren(), []); + $children->excludedDirs = $this->excludedDirs; + $children->excludedPattern = $this->excludedPattern; + + return $children; + } +} diff --git a/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php b/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php new file mode 100644 index 0000000000..d054cefb9f --- /dev/null +++ b/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php @@ -0,0 +1,54 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * FileTypeFilterIterator only keeps files, directories, or both. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class FileTypeFilterIterator extends \FilterIterator +{ + public const ONLY_FILES = 1; + public const ONLY_DIRECTORIES = 2; + + private $mode; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param int $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES) + */ + public function __construct(\Iterator $iterator, int $mode) + { + $this->mode = $mode; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + #[\ReturnTypeWillChange] + public function accept() + { + $fileinfo = $this->current(); + if (self::ONLY_DIRECTORIES === (self::ONLY_DIRECTORIES & $this->mode) && $fileinfo->isFile()) { + return false; + } elseif (self::ONLY_FILES === (self::ONLY_FILES & $this->mode) && $fileinfo->isDir()) { + return false; + } + + return true; + } +} diff --git a/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php b/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php new file mode 100644 index 0000000000..41eb767f7a --- /dev/null +++ b/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php @@ -0,0 +1,59 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * FilecontentFilterIterator filters files by their contents using patterns (regexps or strings). + * + * @author Fabien Potencier <fabien@symfony.com> + * @author Włodzimierz Gajda <gajdaw@gajdaw.pl> + */ +class FilecontentFilterIterator extends MultiplePcreFilterIterator +{ + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + #[\ReturnTypeWillChange] + public function accept() + { + if (!$this->matchRegexps && !$this->noMatchRegexps) { + return true; + } + + $fileinfo = $this->current(); + + if ($fileinfo->isDir() || !$fileinfo->isReadable()) { + return false; + } + + $content = $fileinfo->getContents(); + if (!$content) { + return false; + } + + return $this->isAccepted($content); + } + + /** + * Converts string to regexp if necessary. + * + * @param string $str Pattern: string or regexp + * + * @return string regexp corresponding to a given string or regexp + */ + protected function toRegex($str) + { + return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; + } +} diff --git a/vendor/symfony/finder/Iterator/FilenameFilterIterator.php b/vendor/symfony/finder/Iterator/FilenameFilterIterator.php new file mode 100644 index 0000000000..8365756c15 --- /dev/null +++ b/vendor/symfony/finder/Iterator/FilenameFilterIterator.php @@ -0,0 +1,48 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Glob; + +/** + * FilenameFilterIterator filters files by patterns (a regexp, a glob, or a string). + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class FilenameFilterIterator extends MultiplePcreFilterIterator +{ + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + #[\ReturnTypeWillChange] + public function accept() + { + return $this->isAccepted($this->current()->getFilename()); + } + + /** + * Converts glob to regexp. + * + * PCRE patterns are left unchanged. + * Glob strings are transformed with Glob::toRegex(). + * + * @param string $str Pattern: glob or regexp + * + * @return string regexp corresponding to a given glob or regexp + */ + protected function toRegex($str) + { + return $this->isRegex($str) ? $str : Glob::toRegex($str); + } +} diff --git a/vendor/symfony/finder/Iterator/LazyIterator.php b/vendor/symfony/finder/Iterator/LazyIterator.php new file mode 100644 index 0000000000..32cc37ff14 --- /dev/null +++ b/vendor/symfony/finder/Iterator/LazyIterator.php @@ -0,0 +1,32 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * @author Jérémy Derussé <jeremy@derusse.com> + * + * @internal + */ +class LazyIterator implements \IteratorAggregate +{ + private $iteratorFactory; + + public function __construct(callable $iteratorFactory) + { + $this->iteratorFactory = $iteratorFactory; + } + + public function getIterator(): \Traversable + { + yield from ($this->iteratorFactory)(); + } +} diff --git a/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php b/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php new file mode 100644 index 0000000000..e185d13001 --- /dev/null +++ b/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php @@ -0,0 +1,118 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * MultiplePcreFilterIterator filters files using patterns (regexps, globs or strings). + * + * @author Fabien Potencier <fabien@symfony.com> + */ +abstract class MultiplePcreFilterIterator extends \FilterIterator +{ + protected $matchRegexps = []; + protected $noMatchRegexps = []; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param array $matchPatterns An array of patterns that need to match + * @param array $noMatchPatterns An array of patterns that need to not match + */ + public function __construct(\Iterator $iterator, array $matchPatterns, array $noMatchPatterns) + { + foreach ($matchPatterns as $pattern) { + $this->matchRegexps[] = $this->toRegex($pattern); + } + + foreach ($noMatchPatterns as $pattern) { + $this->noMatchRegexps[] = $this->toRegex($pattern); + } + + parent::__construct($iterator); + } + + /** + * Checks whether the string is accepted by the regex filters. + * + * If there is no regexps defined in the class, this method will accept the string. + * Such case can be handled by child classes before calling the method if they want to + * apply a different behavior. + * + * @param string $string The string to be matched against filters + * + * @return bool + */ + protected function isAccepted($string) + { + // should at least not match one rule to exclude + foreach ($this->noMatchRegexps as $regex) { + if (preg_match($regex, $string)) { + return false; + } + } + + // should at least match one rule + if ($this->matchRegexps) { + foreach ($this->matchRegexps as $regex) { + if (preg_match($regex, $string)) { + return true; + } + } + + return false; + } + + // If there is no match rules, the file is accepted + return true; + } + + /** + * Checks whether the string is a regex. + * + * @param string $str + * + * @return bool Whether the given string is a regex + */ + protected function isRegex($str) + { + $availableModifiers = 'imsxuADU'; + + if (\PHP_VERSION_ID >= 80200) { + $availableModifiers .= 'n'; + } + + if (preg_match('/^(.{3,}?)['.$availableModifiers.']*$/', $str, $m)) { + $start = substr($m[1], 0, 1); + $end = substr($m[1], -1); + + if ($start === $end) { + return !preg_match('/[*?[:alnum:] \\\\]/', $start); + } + + foreach ([['{', '}'], ['(', ')'], ['[', ']'], ['<', '>']] as $delimiters) { + if ($start === $delimiters[0] && $end === $delimiters[1]) { + return true; + } + } + } + + return false; + } + + /** + * Converts string into regexp. + * + * @param string $str Pattern + * + * @return string regexp corresponding to a given string + */ + abstract protected function toRegex($str); +} diff --git a/vendor/symfony/finder/Iterator/PathFilterIterator.php b/vendor/symfony/finder/Iterator/PathFilterIterator.php new file mode 100644 index 0000000000..f4aaa1fb00 --- /dev/null +++ b/vendor/symfony/finder/Iterator/PathFilterIterator.php @@ -0,0 +1,57 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * PathFilterIterator filters files by path patterns (e.g. some/special/dir). + * + * @author Fabien Potencier <fabien@symfony.com> + * @author Włodzimierz Gajda <gajdaw@gajdaw.pl> + */ +class PathFilterIterator extends MultiplePcreFilterIterator +{ + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + #[\ReturnTypeWillChange] + public function accept() + { + $filename = $this->current()->getRelativePathname(); + + if ('\\' === \DIRECTORY_SEPARATOR) { + $filename = str_replace('\\', '/', $filename); + } + + return $this->isAccepted($filename); + } + + /** + * Converts strings to regexp. + * + * PCRE patterns are left unchanged. + * + * Default conversion: + * 'lorem/ipsum/dolor' ==> 'lorem\/ipsum\/dolor/' + * + * Use only / as directory separator (on Windows also). + * + * @param string $str Pattern: regexp or dirname + * + * @return string regexp corresponding to a given string or regexp + */ + protected function toRegex($str) + { + return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; + } +} diff --git a/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php b/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php new file mode 100644 index 0000000000..8508ab707b --- /dev/null +++ b/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php @@ -0,0 +1,149 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Exception\AccessDeniedException; +use Symfony\Component\Finder\SplFileInfo; + +/** + * Extends the \RecursiveDirectoryIterator to support relative paths. + * + * @author Victor Berchet <victor@suumit.com> + */ +class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator +{ + /** + * @var bool + */ + private $ignoreUnreadableDirs; + + /** + * @var bool + */ + private $rewindable; + + // these 3 properties take part of the performance optimization to avoid redoing the same work in all iterations + private $rootPath; + private $subPath; + private $directorySeparator = '/'; + + /** + * @throws \RuntimeException + */ + public function __construct(string $path, int $flags, bool $ignoreUnreadableDirs = false) + { + if ($flags & (self::CURRENT_AS_PATHNAME | self::CURRENT_AS_SELF)) { + throw new \RuntimeException('This iterator only support returning current as fileinfo.'); + } + + parent::__construct($path, $flags); + $this->ignoreUnreadableDirs = $ignoreUnreadableDirs; + $this->rootPath = $path; + if ('/' !== \DIRECTORY_SEPARATOR && !($flags & self::UNIX_PATHS)) { + $this->directorySeparator = \DIRECTORY_SEPARATOR; + } + } + + /** + * Return an instance of SplFileInfo with support for relative paths. + * + * @return SplFileInfo File information + */ + #[\ReturnTypeWillChange] + public function current() + { + // the logic here avoids redoing the same work in all iterations + + if (null === $subPathname = $this->subPath) { + $subPathname = $this->subPath = $this->getSubPath(); + } + if ('' !== $subPathname) { + $subPathname .= $this->directorySeparator; + } + $subPathname .= $this->getFilename(); + + if ('/' !== $basePath = $this->rootPath) { + $basePath .= $this->directorySeparator; + } + + return new SplFileInfo($basePath.$subPathname, $this->subPath, $subPathname); + } + + /** + * @return \RecursiveIterator + * + * @throws AccessDeniedException + */ + #[\ReturnTypeWillChange] + public function getChildren() + { + try { + $children = parent::getChildren(); + + if ($children instanceof self) { + // parent method will call the constructor with default arguments, so unreadable dirs won't be ignored anymore + $children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs; + + // performance optimization to avoid redoing the same work in all children + $children->rewindable = &$this->rewindable; + $children->rootPath = $this->rootPath; + } + + return $children; + } catch (\UnexpectedValueException $e) { + if ($this->ignoreUnreadableDirs) { + // If directory is unreadable and finder is set to ignore it, a fake empty content is returned. + return new \RecursiveArrayIterator([]); + } else { + throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e); + } + } + } + + /** + * Do nothing for non rewindable stream. + * + * @return void + */ + #[\ReturnTypeWillChange] + public function rewind() + { + if (false === $this->isRewindable()) { + return; + } + + parent::rewind(); + } + + /** + * Checks if the stream is rewindable. + * + * @return bool true when the stream is rewindable, false otherwise + */ + public function isRewindable() + { + if (null !== $this->rewindable) { + return $this->rewindable; + } + + if (false !== $stream = @opendir($this->getPath())) { + $infos = stream_get_meta_data($stream); + closedir($stream); + + if ($infos['seekable']) { + return $this->rewindable = true; + } + } + + return $this->rewindable = false; + } +} diff --git a/vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php b/vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php new file mode 100644 index 0000000000..4078f3692e --- /dev/null +++ b/vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php @@ -0,0 +1,58 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Comparator\NumberComparator; + +/** + * SizeRangeFilterIterator filters out files that are not in the given size range. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class SizeRangeFilterIterator extends \FilterIterator +{ + private $comparators = []; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param NumberComparator[] $comparators An array of NumberComparator instances + */ + public function __construct(\Iterator $iterator, array $comparators) + { + $this->comparators = $comparators; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + #[\ReturnTypeWillChange] + public function accept() + { + $fileinfo = $this->current(); + if (!$fileinfo->isFile()) { + return true; + } + + $filesize = $fileinfo->getSize(); + foreach ($this->comparators as $compare) { + if (!$compare->test($filesize)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/symfony/finder/Iterator/SortableIterator.php b/vendor/symfony/finder/Iterator/SortableIterator.php new file mode 100644 index 0000000000..adc7e999db --- /dev/null +++ b/vendor/symfony/finder/Iterator/SortableIterator.php @@ -0,0 +1,101 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * SortableIterator applies a sort on a given Iterator. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class SortableIterator implements \IteratorAggregate +{ + public const SORT_BY_NONE = 0; + public const SORT_BY_NAME = 1; + public const SORT_BY_TYPE = 2; + public const SORT_BY_ACCESSED_TIME = 3; + public const SORT_BY_CHANGED_TIME = 4; + public const SORT_BY_MODIFIED_TIME = 5; + public const SORT_BY_NAME_NATURAL = 6; + + private $iterator; + private $sort; + + /** + * @param int|callable $sort The sort type (SORT_BY_NAME, SORT_BY_TYPE, or a PHP callback) + * + * @throws \InvalidArgumentException + */ + public function __construct(\Traversable $iterator, $sort, bool $reverseOrder = false) + { + $this->iterator = $iterator; + $order = $reverseOrder ? -1 : 1; + + if (self::SORT_BY_NAME === $sort) { + $this->sort = static function ($a, $b) use ($order) { + return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + }; + } elseif (self::SORT_BY_NAME_NATURAL === $sort) { + $this->sort = static function ($a, $b) use ($order) { + return $order * strnatcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + }; + } elseif (self::SORT_BY_TYPE === $sort) { + $this->sort = static function ($a, $b) use ($order) { + if ($a->isDir() && $b->isFile()) { + return -$order; + } elseif ($a->isFile() && $b->isDir()) { + return $order; + } + + return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + }; + } elseif (self::SORT_BY_ACCESSED_TIME === $sort) { + $this->sort = static function ($a, $b) use ($order) { + return $order * ($a->getATime() - $b->getATime()); + }; + } elseif (self::SORT_BY_CHANGED_TIME === $sort) { + $this->sort = static function ($a, $b) use ($order) { + return $order * ($a->getCTime() - $b->getCTime()); + }; + } elseif (self::SORT_BY_MODIFIED_TIME === $sort) { + $this->sort = static function ($a, $b) use ($order) { + return $order * ($a->getMTime() - $b->getMTime()); + }; + } elseif (self::SORT_BY_NONE === $sort) { + $this->sort = $order; + } elseif (\is_callable($sort)) { + $this->sort = $reverseOrder ? static function ($a, $b) use ($sort) { return -$sort($a, $b); } : $sort; + } else { + throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.'); + } + } + + /** + * @return \Traversable + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + if (1 === $this->sort) { + return $this->iterator; + } + + $array = iterator_to_array($this->iterator, true); + + if (-1 === $this->sort) { + $array = array_reverse($array); + } else { + uasort($array, $this->sort); + } + + return new \ArrayIterator($array); + } +} diff --git a/vendor/symfony/finder/LICENSE b/vendor/symfony/finder/LICENSE new file mode 100644 index 0000000000..88bf75bb4d --- /dev/null +++ b/vendor/symfony/finder/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/finder/README.md b/vendor/symfony/finder/README.md new file mode 100644 index 0000000000..22bdeb9bcf --- /dev/null +++ b/vendor/symfony/finder/README.md @@ -0,0 +1,14 @@ +Finder Component +================ + +The Finder component finds files and directories via an intuitive fluent +interface. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/finder.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/finder/SplFileInfo.php b/vendor/symfony/finder/SplFileInfo.php new file mode 100644 index 0000000000..62c9faa6e9 --- /dev/null +++ b/vendor/symfony/finder/SplFileInfo.php @@ -0,0 +1,85 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder; + +/** + * Extends \SplFileInfo to support relative paths. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class SplFileInfo extends \SplFileInfo +{ + private $relativePath; + private $relativePathname; + + /** + * @param string $file The file name + * @param string $relativePath The relative path + * @param string $relativePathname The relative path name + */ + public function __construct(string $file, string $relativePath, string $relativePathname) + { + parent::__construct($file); + $this->relativePath = $relativePath; + $this->relativePathname = $relativePathname; + } + + /** + * Returns the relative path. + * + * This path does not contain the file name. + * + * @return string the relative path + */ + public function getRelativePath() + { + return $this->relativePath; + } + + /** + * Returns the relative path name. + * + * This path contains the file name. + * + * @return string the relative path name + */ + public function getRelativePathname() + { + return $this->relativePathname; + } + + public function getFilenameWithoutExtension(): string + { + $filename = $this->getFilename(); + + return pathinfo($filename, \PATHINFO_FILENAME); + } + + /** + * Returns the contents of the file. + * + * @return string the contents of the file + * + * @throws \RuntimeException + */ + public function getContents() + { + set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); + $content = file_get_contents($this->getPathname()); + restore_error_handler(); + if (false === $content) { + throw new \RuntimeException($error); + } + + return $content; + } +} diff --git a/vendor/symfony/finder/composer.json b/vendor/symfony/finder/composer.json new file mode 100644 index 0000000000..5ccd80cd4a --- /dev/null +++ b/vendor/symfony/finder/composer.json @@ -0,0 +1,29 @@ +{ + "name": "symfony/finder", + "type": "library", + "description": "Finds files and directories via an intuitive fluent interface", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1.3", + "symfony/polyfill-php80": "^1.16" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Finder\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/polyfill-ctype/Ctype.php b/vendor/symfony/polyfill-ctype/Ctype.php new file mode 100644 index 0000000000..ba75a2c95f --- /dev/null +++ b/vendor/symfony/polyfill-ctype/Ctype.php @@ -0,0 +1,232 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Ctype; + +/** + * Ctype implementation through regex. + * + * @internal + * + * @author Gert de Pagter <BackEndTea@gmail.com> + */ +final class Ctype +{ + /** + * Returns TRUE if every character in text is either a letter or a digit, FALSE otherwise. + * + * @see https://php.net/ctype-alnum + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_alnum($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z0-9]/', $text); + } + + /** + * Returns TRUE if every character in text is a letter, FALSE otherwise. + * + * @see https://php.net/ctype-alpha + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_alpha($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z]/', $text); + } + + /** + * Returns TRUE if every character in text is a control character from the current locale, FALSE otherwise. + * + * @see https://php.net/ctype-cntrl + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_cntrl($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^\x00-\x1f\x7f]/', $text); + } + + /** + * Returns TRUE if every character in the string text is a decimal digit, FALSE otherwise. + * + * @see https://php.net/ctype-digit + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_digit($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^0-9]/', $text); + } + + /** + * Returns TRUE if every character in text is printable and actually creates visible output (no white space), FALSE otherwise. + * + * @see https://php.net/ctype-graph + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_graph($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^!-~]/', $text); + } + + /** + * Returns TRUE if every character in text is a lowercase letter. + * + * @see https://php.net/ctype-lower + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_lower($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^a-z]/', $text); + } + + /** + * Returns TRUE if every character in text will actually create output (including blanks). Returns FALSE if text contains control characters or characters that do not have any output or control function at all. + * + * @see https://php.net/ctype-print + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_print($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^ -~]/', $text); + } + + /** + * Returns TRUE if every character in text is printable, but neither letter, digit or blank, FALSE otherwise. + * + * @see https://php.net/ctype-punct + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_punct($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^!-\/\:-@\[-`\{-~]/', $text); + } + + /** + * Returns TRUE if every character in text creates some sort of white space, FALSE otherwise. Besides the blank character this also includes tab, vertical tab, line feed, carriage return and form feed characters. + * + * @see https://php.net/ctype-space + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_space($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^\s]/', $text); + } + + /** + * Returns TRUE if every character in text is an uppercase letter. + * + * @see https://php.net/ctype-upper + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_upper($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Z]/', $text); + } + + /** + * Returns TRUE if every character in text is a hexadecimal 'digit', that is a decimal digit or a character from [A-Fa-f] , FALSE otherwise. + * + * @see https://php.net/ctype-xdigit + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_xdigit($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Fa-f0-9]/', $text); + } + + /** + * Converts integers to their char versions according to normal ctype behaviour, if needed. + * + * If an integer between -128 and 255 inclusive is provided, + * it is interpreted as the ASCII value of a single character + * (negative values have 256 added in order to allow characters in the Extended ASCII range). + * Any other integer is interpreted as a string containing the decimal digits of the integer. + * + * @param mixed $int + * @param string $function + * + * @return mixed + */ + private static function convert_int_to_char_for_ctype($int, $function) + { + if (!\is_int($int)) { + return $int; + } + + if ($int < -128 || $int > 255) { + return (string) $int; + } + + if (\PHP_VERSION_ID >= 80100) { + @trigger_error($function.'(): Argument of type int will be interpreted as string in the future', \E_USER_DEPRECATED); + } + + if ($int < 0) { + $int += 256; + } + + return \chr($int); + } +} diff --git a/vendor/symfony/polyfill-ctype/LICENSE b/vendor/symfony/polyfill-ctype/LICENSE new file mode 100644 index 0000000000..3f853aaf35 --- /dev/null +++ b/vendor/symfony/polyfill-ctype/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-ctype/README.md b/vendor/symfony/polyfill-ctype/README.md new file mode 100644 index 0000000000..b144d03c3c --- /dev/null +++ b/vendor/symfony/polyfill-ctype/README.md @@ -0,0 +1,12 @@ +Symfony Polyfill / Ctype +======================== + +This component provides `ctype_*` functions to users who run php versions without the ctype extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-ctype/bootstrap.php b/vendor/symfony/polyfill-ctype/bootstrap.php new file mode 100644 index 0000000000..d54524b31b --- /dev/null +++ b/vendor/symfony/polyfill-ctype/bootstrap.php @@ -0,0 +1,50 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Ctype as p; + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!function_exists('ctype_alnum')) { + function ctype_alnum($text) { return p\Ctype::ctype_alnum($text); } +} +if (!function_exists('ctype_alpha')) { + function ctype_alpha($text) { return p\Ctype::ctype_alpha($text); } +} +if (!function_exists('ctype_cntrl')) { + function ctype_cntrl($text) { return p\Ctype::ctype_cntrl($text); } +} +if (!function_exists('ctype_digit')) { + function ctype_digit($text) { return p\Ctype::ctype_digit($text); } +} +if (!function_exists('ctype_graph')) { + function ctype_graph($text) { return p\Ctype::ctype_graph($text); } +} +if (!function_exists('ctype_lower')) { + function ctype_lower($text) { return p\Ctype::ctype_lower($text); } +} +if (!function_exists('ctype_print')) { + function ctype_print($text) { return p\Ctype::ctype_print($text); } +} +if (!function_exists('ctype_punct')) { + function ctype_punct($text) { return p\Ctype::ctype_punct($text); } +} +if (!function_exists('ctype_space')) { + function ctype_space($text) { return p\Ctype::ctype_space($text); } +} +if (!function_exists('ctype_upper')) { + function ctype_upper($text) { return p\Ctype::ctype_upper($text); } +} +if (!function_exists('ctype_xdigit')) { + function ctype_xdigit($text) { return p\Ctype::ctype_xdigit($text); } +} diff --git a/vendor/symfony/polyfill-ctype/bootstrap80.php b/vendor/symfony/polyfill-ctype/bootstrap80.php new file mode 100644 index 0000000000..ab2f8611da --- /dev/null +++ b/vendor/symfony/polyfill-ctype/bootstrap80.php @@ -0,0 +1,46 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Ctype as p; + +if (!function_exists('ctype_alnum')) { + function ctype_alnum(mixed $text): bool { return p\Ctype::ctype_alnum($text); } +} +if (!function_exists('ctype_alpha')) { + function ctype_alpha(mixed $text): bool { return p\Ctype::ctype_alpha($text); } +} +if (!function_exists('ctype_cntrl')) { + function ctype_cntrl(mixed $text): bool { return p\Ctype::ctype_cntrl($text); } +} +if (!function_exists('ctype_digit')) { + function ctype_digit(mixed $text): bool { return p\Ctype::ctype_digit($text); } +} +if (!function_exists('ctype_graph')) { + function ctype_graph(mixed $text): bool { return p\Ctype::ctype_graph($text); } +} +if (!function_exists('ctype_lower')) { + function ctype_lower(mixed $text): bool { return p\Ctype::ctype_lower($text); } +} +if (!function_exists('ctype_print')) { + function ctype_print(mixed $text): bool { return p\Ctype::ctype_print($text); } +} +if (!function_exists('ctype_punct')) { + function ctype_punct(mixed $text): bool { return p\Ctype::ctype_punct($text); } +} +if (!function_exists('ctype_space')) { + function ctype_space(mixed $text): bool { return p\Ctype::ctype_space($text); } +} +if (!function_exists('ctype_upper')) { + function ctype_upper(mixed $text): bool { return p\Ctype::ctype_upper($text); } +} +if (!function_exists('ctype_xdigit')) { + function ctype_xdigit(mixed $text): bool { return p\Ctype::ctype_xdigit($text); } +} diff --git a/vendor/symfony/polyfill-ctype/composer.json b/vendor/symfony/polyfill-ctype/composer.json new file mode 100644 index 0000000000..ee5c931cd1 --- /dev/null +++ b/vendor/symfony/polyfill-ctype/composer.json @@ -0,0 +1,41 @@ +{ + "name": "symfony/polyfill-ctype", + "type": "library", + "description": "Symfony polyfill for ctype functions", + "keywords": ["polyfill", "compatibility", "portable", "ctype"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Ctype\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/vendor/symfony/polyfill-mbstring/LICENSE b/vendor/symfony/polyfill-mbstring/LICENSE new file mode 100644 index 0000000000..4cd8bdd300 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-mbstring/Mbstring.php b/vendor/symfony/polyfill-mbstring/Mbstring.php new file mode 100644 index 0000000000..693749f22b --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Mbstring.php @@ -0,0 +1,873 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Mbstring; + +/** + * Partial mbstring implementation in PHP, iconv based, UTF-8 centric. + * + * Implemented: + * - mb_chr - Returns a specific character from its Unicode code point + * - mb_convert_encoding - Convert character encoding + * - mb_convert_variables - Convert character code in variable(s) + * - mb_decode_mimeheader - Decode string in MIME header field + * - mb_encode_mimeheader - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED + * - mb_decode_numericentity - Decode HTML numeric string reference to character + * - mb_encode_numericentity - Encode character to HTML numeric string reference + * - mb_convert_case - Perform case folding on a string + * - mb_detect_encoding - Detect character encoding + * - mb_get_info - Get internal settings of mbstring + * - mb_http_input - Detect HTTP input character encoding + * - mb_http_output - Set/Get HTTP output character encoding + * - mb_internal_encoding - Set/Get internal character encoding + * - mb_list_encodings - Returns an array of all supported encodings + * - mb_ord - Returns the Unicode code point of a character + * - mb_output_handler - Callback function converts character encoding in output buffer + * - mb_scrub - Replaces ill-formed byte sequences with substitute characters + * - mb_strlen - Get string length + * - mb_strpos - Find position of first occurrence of string in a string + * - mb_strrpos - Find position of last occurrence of a string in a string + * - mb_str_split - Convert a string to an array + * - mb_strtolower - Make a string lowercase + * - mb_strtoupper - Make a string uppercase + * - mb_substitute_character - Set/Get substitution character + * - mb_substr - Get part of string + * - mb_stripos - Finds position of first occurrence of a string within another, case insensitive + * - mb_stristr - Finds first occurrence of a string within another, case insensitive + * - mb_strrchr - Finds the last occurrence of a character in a string within another + * - mb_strrichr - Finds the last occurrence of a character in a string within another, case insensitive + * - mb_strripos - Finds position of last occurrence of a string within another, case insensitive + * - mb_strstr - Finds first occurrence of a string within another + * - mb_strwidth - Return width of string + * - mb_substr_count - Count the number of substring occurrences + * + * Not implemented: + * - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more) + * - mb_ereg_* - Regular expression with multibyte support + * - mb_parse_str - Parse GET/POST/COOKIE data and set global variable + * - mb_preferred_mime_name - Get MIME charset string + * - mb_regex_encoding - Returns current encoding for multibyte regex as string + * - mb_regex_set_options - Set/Get the default options for mbregex functions + * - mb_send_mail - Send encoded mail + * - mb_split - Split multibyte string using regular expression + * - mb_strcut - Get part of string + * - mb_strimwidth - Get truncated string with specified width + * + * @author Nicolas Grekas <p@tchwork.com> + * + * @internal + */ +final class Mbstring +{ + public const MB_CASE_FOLD = \PHP_INT_MAX; + + private const CASE_FOLD = [ + ['µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"], + ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'], + ]; + + private static $encodingList = ['ASCII', 'UTF-8']; + private static $language = 'neutral'; + private static $internalEncoding = 'UTF-8'; + + public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null) + { + if (\is_array($fromEncoding) || ($fromEncoding !== null && false !== strpos($fromEncoding, ','))) { + $fromEncoding = self::mb_detect_encoding($s, $fromEncoding); + } else { + $fromEncoding = self::getEncoding($fromEncoding); + } + + $toEncoding = self::getEncoding($toEncoding); + + if ('BASE64' === $fromEncoding) { + $s = base64_decode($s); + $fromEncoding = $toEncoding; + } + + if ('BASE64' === $toEncoding) { + return base64_encode($s); + } + + if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) { + if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) { + $fromEncoding = 'Windows-1252'; + } + if ('UTF-8' !== $fromEncoding) { + $s = \iconv($fromEncoding, 'UTF-8//IGNORE', $s); + } + + return preg_replace_callback('/[\x80-\xFF]+/', [__CLASS__, 'html_encoding_callback'], $s); + } + + if ('HTML-ENTITIES' === $fromEncoding) { + $s = html_entity_decode($s, \ENT_COMPAT, 'UTF-8'); + $fromEncoding = 'UTF-8'; + } + + return \iconv($fromEncoding, $toEncoding.'//IGNORE', $s); + } + + public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars) + { + $ok = true; + array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) { + if (false === $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding)) { + $ok = false; + } + }); + + return $ok ? $fromEncoding : false; + } + + public static function mb_decode_mimeheader($s) + { + return \iconv_mime_decode($s, 2, self::$internalEncoding); + } + + public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null) + { + trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', \E_USER_WARNING); + } + + public static function mb_decode_numericentity($s, $convmap, $encoding = null) + { + if (null !== $s && !is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { + trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { + return false; + } + + if (null !== $encoding && !is_scalar($encoding)) { + trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return ''; // Instead of null (cf. mb_encode_numericentity). + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @\iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = \iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $cnt = floor(\count($convmap) / 4) * 4; + + for ($i = 0; $i < $cnt; $i += 4) { + // collector_decode_htmlnumericentity ignores $convmap[$i + 3] + $convmap[$i] += $convmap[$i + 2]; + $convmap[$i + 1] += $convmap[$i + 2]; + } + + $s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) { + $c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1]; + for ($i = 0; $i < $cnt; $i += 4) { + if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) { + return self::mb_chr($c - $convmap[$i + 2]); + } + } + + return $m[0]; + }, $s); + + if (null === $encoding) { + return $s; + } + + return \iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false) + { + if (null !== $s && !is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { + trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { + return false; + } + + if (null !== $encoding && !is_scalar($encoding)) { + trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; // Instead of '' (cf. mb_decode_numericentity). + } + + if (null !== $is_hex && !is_scalar($is_hex)) { + trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @\iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = \iconv($encoding, 'UTF-8//IGNORE', $s); + } + + static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; + + $cnt = floor(\count($convmap) / 4) * 4; + $i = 0; + $len = \strlen($s); + $result = ''; + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + $c = self::mb_ord($uchr); + + for ($j = 0; $j < $cnt; $j += 4) { + if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) { + $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3]; + $result .= $is_hex ? sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';'; + continue 2; + } + } + $result .= $uchr; + } + + if (null === $encoding) { + return $result; + } + + return \iconv('UTF-8', $encoding.'//IGNORE', $result); + } + + public static function mb_convert_case($s, $mode, $encoding = null) + { + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @\iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = \iconv($encoding, 'UTF-8//IGNORE', $s); + } + + if (\MB_CASE_TITLE == $mode) { + static $titleRegexp = null; + if (null === $titleRegexp) { + $titleRegexp = self::getData('titleCaseRegexp'); + } + $s = preg_replace_callback($titleRegexp, [__CLASS__, 'title_case'], $s); + } else { + if (\MB_CASE_UPPER == $mode) { + static $upper = null; + if (null === $upper) { + $upper = self::getData('upperCase'); + } + $map = $upper; + } else { + if (self::MB_CASE_FOLD === $mode) { + $s = str_replace(self::CASE_FOLD[0], self::CASE_FOLD[1], $s); + } + + static $lower = null; + if (null === $lower) { + $lower = self::getData('lowerCase'); + } + $map = $lower; + } + + static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; + + $i = 0; + $len = \strlen($s); + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + + if (isset($map[$uchr])) { + $uchr = $map[$uchr]; + $nlen = \strlen($uchr); + + if ($nlen == $ulen) { + $nlen = $i; + do { + $s[--$nlen] = $uchr[--$ulen]; + } while ($ulen); + } else { + $s = substr_replace($s, $uchr, $i - $ulen, $ulen); + $len += $nlen - $ulen; + $i += $nlen - $ulen; + } + } + } + } + + if (null === $encoding) { + return $s; + } + + return \iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_internal_encoding($encoding = null) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + $normalizedEncoding = self::getEncoding($encoding); + + if ('UTF-8' === $normalizedEncoding || false !== @\iconv($normalizedEncoding, $normalizedEncoding, ' ')) { + self::$internalEncoding = $normalizedEncoding; + + return true; + } + + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError(sprintf('Argument #1 ($encoding) must be a valid encoding, "%s" given', $encoding)); + } + + public static function mb_language($lang = null) + { + if (null === $lang) { + return self::$language; + } + + switch ($normalizedLang = strtolower($lang)) { + case 'uni': + case 'neutral': + self::$language = $normalizedLang; + + return true; + } + + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError(sprintf('Argument #1 ($language) must be a valid language, "%s" given', $lang)); + } + + public static function mb_list_encodings() + { + return ['UTF-8']; + } + + public static function mb_encoding_aliases($encoding) + { + switch (strtoupper($encoding)) { + case 'UTF8': + case 'UTF-8': + return ['utf8']; + } + + return false; + } + + public static function mb_check_encoding($var = null, $encoding = null) + { + if (null === $encoding) { + if (null === $var) { + return false; + } + $encoding = self::$internalEncoding; + } + + return self::mb_detect_encoding($var, [$encoding]) || false !== @\iconv($encoding, $encoding, $var); + } + + public static function mb_detect_encoding($str, $encodingList = null, $strict = false) + { + if (null === $encodingList) { + $encodingList = self::$encodingList; + } else { + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + } + + foreach ($encodingList as $enc) { + switch ($enc) { + case 'ASCII': + if (!preg_match('/[\x80-\xFF]/', $str)) { + return $enc; + } + break; + + case 'UTF8': + case 'UTF-8': + if (preg_match('//u', $str)) { + return 'UTF-8'; + } + break; + + default: + if (0 === strncmp($enc, 'ISO-8859-', 9)) { + return $enc; + } + } + } + + return false; + } + + public static function mb_detect_order($encodingList = null) + { + if (null === $encodingList) { + return self::$encodingList; + } + + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + + foreach ($encodingList as $enc) { + switch ($enc) { + default: + if (strncmp($enc, 'ISO-8859-', 9)) { + return false; + } + // no break + case 'ASCII': + case 'UTF8': + case 'UTF-8': + } + } + + self::$encodingList = $encodingList; + + return true; + } + + public static function mb_strlen($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return \strlen($s); + } + + return @\iconv_strlen($s, $encoding); + } + + public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strpos($haystack, $needle, $offset); + } + + $needle = (string) $needle; + if ('' === $needle) { + if (80000 > \PHP_VERSION_ID) { + trigger_error(__METHOD__.': Empty delimiter', \E_USER_WARNING); + + return false; + } + + return 0; + } + + return \iconv_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strrpos($haystack, $needle, $offset); + } + + if ($offset != (int) $offset) { + $offset = 0; + } elseif ($offset = (int) $offset) { + if ($offset < 0) { + if (0 > $offset += self::mb_strlen($needle)) { + $haystack = self::mb_substr($haystack, 0, $offset, $encoding); + } + $offset = 0; + } else { + $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding); + } + } + + $pos = '' !== $needle || 80000 > \PHP_VERSION_ID + ? \iconv_strrpos($haystack, $needle, $encoding) + : self::mb_strlen($haystack, $encoding); + + return false !== $pos ? $offset + $pos : false; + } + + public static function mb_str_split($string, $split_length = 1, $encoding = null) + { + if (null !== $string && !is_scalar($string) && !(\is_object($string) && method_exists($string, '__toString'))) { + trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', \E_USER_WARNING); + + return null; + } + + if (1 > $split_length = (int) $split_length) { + if (80000 > \PHP_VERSION_ID) { + trigger_error('The length of each segment must be greater than zero', \E_USER_WARNING); + return false; + } + + throw new \ValueError('Argument #2 ($length) must be greater than 0'); + } + + if (null === $encoding) { + $encoding = mb_internal_encoding(); + } + + if ('UTF-8' === $encoding = self::getEncoding($encoding)) { + $rx = '/('; + while (65535 < $split_length) { + $rx .= '.{65535}'; + $split_length -= 65535; + } + $rx .= '.{'.$split_length.'})/us'; + + return preg_split($rx, $string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY); + } + + $result = []; + $length = mb_strlen($string, $encoding); + + for ($i = 0; $i < $length; $i += $split_length) { + $result[] = mb_substr($string, $i, $split_length, $encoding); + } + + return $result; + } + + public static function mb_strtolower($s, $encoding = null) + { + return self::mb_convert_case($s, \MB_CASE_LOWER, $encoding); + } + + public static function mb_strtoupper($s, $encoding = null) + { + return self::mb_convert_case($s, \MB_CASE_UPPER, $encoding); + } + + public static function mb_substitute_character($c = null) + { + if (null === $c) { + return 'none'; + } + if (0 === strcasecmp($c, 'none')) { + return true; + } + if (80000 > \PHP_VERSION_ID) { + return false; + } + if (\is_int($c) || 'long' === $c || 'entity' === $c) { + return false; + } + + throw new \ValueError('Argument #1 ($substitute_character) must be "none", "long", "entity" or a valid codepoint'); + } + + public static function mb_substr($s, $start, $length = null, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return (string) substr($s, $start, null === $length ? 2147483647 : $length); + } + + if ($start < 0) { + $start = \iconv_strlen($s, $encoding) + $start; + if ($start < 0) { + $start = 0; + } + } + + if (null === $length) { + $length = 2147483647; + } elseif ($length < 0) { + $length = \iconv_strlen($s, $encoding) + $length - $start; + if ($length < 0) { + return ''; + } + } + + return (string) \iconv_substr($s, $start, $length, $encoding); + } + + public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); + $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); + + return self::mb_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_stristr($haystack, $needle, $part = false, $encoding = null) + { + $pos = self::mb_stripos($haystack, $needle, 0, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + $pos = strrpos($haystack, $needle); + } else { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = \iconv_strrpos($haystack, $needle, $encoding); + } + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null) + { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = self::mb_strripos($haystack, $needle, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); + $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); + + return self::mb_strrpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strstr($haystack, $needle, $part = false, $encoding = null) + { + $pos = strpos($haystack, $needle); + if (false === $pos) { + return false; + } + if ($part) { + return substr($haystack, 0, $pos); + } + + return substr($haystack, $pos); + } + + public static function mb_get_info($type = 'all') + { + $info = [ + 'internal_encoding' => self::$internalEncoding, + 'http_output' => 'pass', + 'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)', + 'func_overload' => 0, + 'func_overload_list' => 'no overload', + 'mail_charset' => 'UTF-8', + 'mail_header_encoding' => 'BASE64', + 'mail_body_encoding' => 'BASE64', + 'illegal_chars' => 0, + 'encoding_translation' => 'Off', + 'language' => self::$language, + 'detect_order' => self::$encodingList, + 'substitute_character' => 'none', + 'strict_detection' => 'Off', + ]; + + if ('all' === $type) { + return $info; + } + if (isset($info[$type])) { + return $info[$type]; + } + + return false; + } + + public static function mb_http_input($type = '') + { + return false; + } + + public static function mb_http_output($encoding = null) + { + return null !== $encoding ? 'pass' === $encoding : 'pass'; + } + + public static function mb_strwidth($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + + if ('UTF-8' !== $encoding) { + $s = \iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide); + + return ($wide << 1) + \iconv_strlen($s, 'UTF-8'); + } + + public static function mb_substr_count($haystack, $needle, $encoding = null) + { + return substr_count($haystack, $needle); + } + + public static function mb_output_handler($contents, $status) + { + return $contents; + } + + public static function mb_chr($code, $encoding = null) + { + if (0x80 > $code %= 0x200000) { + $s = \chr($code); + } elseif (0x800 > $code) { + $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F); + } elseif (0x10000 > $code) { + $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } else { + $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } + + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, $encoding, 'UTF-8'); + } + + return $s; + } + + public static function mb_ord($s, $encoding = null) + { + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, 'UTF-8', $encoding); + } + + if (1 === \strlen($s)) { + return \ord($s); + } + + $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0; + if (0xF0 <= $code) { + return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80; + } + if (0xE0 <= $code) { + return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80; + } + if (0xC0 <= $code) { + return (($code - 0xC0) << 6) + $s[2] - 0x80; + } + + return $code; + } + + private static function getSubpart($pos, $part, $haystack, $encoding) + { + if (false === $pos) { + return false; + } + if ($part) { + return self::mb_substr($haystack, 0, $pos, $encoding); + } + + return self::mb_substr($haystack, $pos, null, $encoding); + } + + private static function html_encoding_callback(array $m) + { + $i = 1; + $entities = ''; + $m = unpack('C*', htmlentities($m[0], \ENT_COMPAT, 'UTF-8')); + + while (isset($m[$i])) { + if (0x80 > $m[$i]) { + $entities .= \chr($m[$i++]); + continue; + } + if (0xF0 <= $m[$i]) { + $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } elseif (0xE0 <= $m[$i]) { + $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } else { + $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80; + } + + $entities .= '&#'.$c.';'; + } + + return $entities; + } + + private static function title_case(array $s) + { + return self::mb_convert_case($s[1], \MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], \MB_CASE_LOWER, 'UTF-8'); + } + + private static function getData($file) + { + if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) { + return require $file; + } + + return false; + } + + private static function getEncoding($encoding) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + if ('UTF-8' === $encoding) { + return 'UTF-8'; + } + + $encoding = strtoupper($encoding); + + if ('8BIT' === $encoding || 'BINARY' === $encoding) { + return 'CP850'; + } + + if ('UTF8' === $encoding) { + return 'UTF-8'; + } + + return $encoding; + } +} diff --git a/vendor/symfony/polyfill-mbstring/README.md b/vendor/symfony/polyfill-mbstring/README.md new file mode 100644 index 0000000000..478b40da25 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/README.md @@ -0,0 +1,13 @@ +Symfony Polyfill / Mbstring +=========================== + +This component provides a partial, native PHP implementation for the +[Mbstring](https://php.net/mbstring) extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php new file mode 100644 index 0000000000..fac60b081a --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php @@ -0,0 +1,1397 @@ +<?php + +return array ( + 'A' => 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + 'À' => 'à', + 'Á' => 'á', + 'Â' => 'â', + 'Ã' => 'ã', + 'Ä' => 'ä', + 'Å' => 'å', + 'Æ' => 'æ', + 'Ç' => 'ç', + 'È' => 'è', + 'É' => 'é', + 'Ê' => 'ê', + 'Ë' => 'ë', + 'Ì' => 'ì', + 'Í' => 'í', + 'Î' => 'î', + 'Ï' => 'ï', + 'Ð' => 'ð', + 'Ñ' => 'ñ', + 'Ò' => 'ò', + 'Ó' => 'ó', + 'Ô' => 'ô', + 'Õ' => 'õ', + 'Ö' => 'ö', + 'Ø' => 'ø', + 'Ù' => 'ù', + 'Ú' => 'ú', + 'Û' => 'û', + 'Ü' => 'ü', + 'Ý' => 'ý', + 'Þ' => 'þ', + 'Ā' => 'ā', + 'Ă' => 'ă', + 'Ą' => 'ą', + 'Ć' => 'ć', + 'Ĉ' => 'ĉ', + 'Ċ' => 'ċ', + 'Č' => 'č', + 'Ď' => 'ď', + 'Đ' => 'đ', + 'Ē' => 'ē', + 'Ĕ' => 'ĕ', + 'Ė' => 'ė', + 'Ę' => 'ę', + 'Ě' => 'ě', + 'Ĝ' => 'ĝ', + 'Ğ' => 'ğ', + 'Ġ' => 'ġ', + 'Ģ' => 'ģ', + 'Ĥ' => 'ĥ', + 'Ħ' => 'ħ', + 'Ĩ' => 'ĩ', + 'Ī' => 'ī', + 'Ĭ' => 'ĭ', + 'Į' => 'į', + 'İ' => 'i̇', + 'IJ' => 'ij', + 'Ĵ' => 'ĵ', + 'Ķ' => 'ķ', + 'Ĺ' => 'ĺ', + 'Ļ' => 'ļ', + 'Ľ' => 'ľ', + 'Ŀ' => 'ŀ', + 'Ł' => 'ł', + 'Ń' => 'ń', + 'Ņ' => 'ņ', + 'Ň' => 'ň', + 'Ŋ' => 'ŋ', + 'Ō' => 'ō', + 'Ŏ' => 'ŏ', + 'Ő' => 'ő', + 'Œ' => 'œ', + 'Ŕ' => 'ŕ', + 'Ŗ' => 'ŗ', + 'Ř' => 'ř', + 'Ś' => 'ś', + 'Ŝ' => 'ŝ', + 'Ş' => 'ş', + 'Š' => 'š', + 'Ţ' => 'ţ', + 'Ť' => 'ť', + 'Ŧ' => 'ŧ', + 'Ũ' => 'ũ', + 'Ū' => 'ū', + 'Ŭ' => 'ŭ', + 'Ů' => 'ů', + 'Ű' => 'ű', + 'Ų' => 'ų', + 'Ŵ' => 'ŵ', + 'Ŷ' => 'ŷ', + 'Ÿ' => 'ÿ', + 'Ź' => 'ź', + 'Ż' => 'ż', + 'Ž' => 'ž', + 'Ɓ' => 'ɓ', + 'Ƃ' => 'ƃ', + 'Ƅ' => 'ƅ', + 'Ɔ' => 'ɔ', + 'Ƈ' => 'ƈ', + 'Ɖ' => 'ɖ', + 'Ɗ' => 'ɗ', + 'Ƌ' => 'ƌ', + 'Ǝ' => 'ǝ', + 'Ə' => 'ə', + 'Ɛ' => 'ɛ', + 'Ƒ' => 'ƒ', + 'Ɠ' => 'ɠ', + 'Ɣ' => 'ɣ', + 'Ɩ' => 'ɩ', + 'Ɨ' => 'ɨ', + 'Ƙ' => 'ƙ', + 'Ɯ' => 'ɯ', + 'Ɲ' => 'ɲ', + 'Ɵ' => 'ɵ', + 'Ơ' => 'ơ', + 'Ƣ' => 'ƣ', + 'Ƥ' => 'ƥ', + 'Ʀ' => 'ʀ', + 'Ƨ' => 'ƨ', + 'Ʃ' => 'ʃ', + 'Ƭ' => 'ƭ', + 'Ʈ' => 'ʈ', + 'Ư' => 'ư', + 'Ʊ' => 'ʊ', + 'Ʋ' => 'ʋ', + 'Ƴ' => 'ƴ', + 'Ƶ' => 'ƶ', + 'Ʒ' => 'ʒ', + 'Ƹ' => 'ƹ', + 'Ƽ' => 'ƽ', + 'DŽ' => 'dž', + 'Dž' => 'dž', + 'LJ' => 'lj', + 'Lj' => 'lj', + 'NJ' => 'nj', + 'Nj' => 'nj', + 'Ǎ' => 'ǎ', + 'Ǐ' => 'ǐ', + 'Ǒ' => 'ǒ', + 'Ǔ' => 'ǔ', + 'Ǖ' => 'ǖ', + 'Ǘ' => 'ǘ', + 'Ǚ' => 'ǚ', + 'Ǜ' => 'ǜ', + 'Ǟ' => 'ǟ', + 'Ǡ' => 'ǡ', + 'Ǣ' => 'ǣ', + 'Ǥ' => 'ǥ', + 'Ǧ' => 'ǧ', + 'Ǩ' => 'ǩ', + 'Ǫ' => 'ǫ', + 'Ǭ' => 'ǭ', + 'Ǯ' => 'ǯ', + 'DZ' => 'dz', + 'Dz' => 'dz', + 'Ǵ' => 'ǵ', + 'Ƕ' => 'ƕ', + 'Ƿ' => 'ƿ', + 'Ǹ' => 'ǹ', + 'Ǻ' => 'ǻ', + 'Ǽ' => 'ǽ', + 'Ǿ' => 'ǿ', + 'Ȁ' => 'ȁ', + 'Ȃ' => 'ȃ', + 'Ȅ' => 'ȅ', + 'Ȇ' => 'ȇ', + 'Ȉ' => 'ȉ', + 'Ȋ' => 'ȋ', + 'Ȍ' => 'ȍ', + 'Ȏ' => 'ȏ', + 'Ȑ' => 'ȑ', + 'Ȓ' => 'ȓ', + 'Ȕ' => 'ȕ', + 'Ȗ' => 'ȗ', + 'Ș' => 'ș', + 'Ț' => 'ț', + 'Ȝ' => 'ȝ', + 'Ȟ' => 'ȟ', + 'Ƞ' => 'ƞ', + 'Ȣ' => 'ȣ', + 'Ȥ' => 'ȥ', + 'Ȧ' => 'ȧ', + 'Ȩ' => 'ȩ', + 'Ȫ' => 'ȫ', + 'Ȭ' => 'ȭ', + 'Ȯ' => 'ȯ', + 'Ȱ' => 'ȱ', + 'Ȳ' => 'ȳ', + 'Ⱥ' => 'ⱥ', + 'Ȼ' => 'ȼ', + 'Ƚ' => 'ƚ', + 'Ⱦ' => 'ⱦ', + 'Ɂ' => 'ɂ', + 'Ƀ' => 'ƀ', + 'Ʉ' => 'ʉ', + 'Ʌ' => 'ʌ', + 'Ɇ' => 'ɇ', + 'Ɉ' => 'ɉ', + 'Ɋ' => 'ɋ', + 'Ɍ' => 'ɍ', + 'Ɏ' => 'ɏ', + 'Ͱ' => 'ͱ', + 'Ͳ' => 'ͳ', + 'Ͷ' => 'ͷ', + 'Ϳ' => 'ϳ', + 'Ά' => 'ά', + 'Έ' => 'έ', + 'Ή' => 'ή', + 'Ί' => 'ί', + 'Ό' => 'ό', + 'Ύ' => 'ύ', + 'Ώ' => 'ώ', + 'Α' => 'α', + 'Β' => 'β', + 'Γ' => 'γ', + 'Δ' => 'δ', + 'Ε' => 'ε', + 'Ζ' => 'ζ', + 'Η' => 'η', + 'Θ' => 'θ', + 'Ι' => 'ι', + 'Κ' => 'κ', + 'Λ' => 'λ', + 'Μ' => 'μ', + 'Ν' => 'ν', + 'Ξ' => 'ξ', + 'Ο' => 'ο', + 'Π' => 'π', + 'Ρ' => 'ρ', + 'Σ' => 'σ', + 'Τ' => 'τ', + 'Υ' => 'υ', + 'Φ' => 'φ', + 'Χ' => 'χ', + 'Ψ' => 'ψ', + 'Ω' => 'ω', + 'Ϊ' => 'ϊ', + 'Ϋ' => 'ϋ', + 'Ϗ' => 'ϗ', + 'Ϙ' => 'ϙ', + 'Ϛ' => 'ϛ', + 'Ϝ' => 'ϝ', + 'Ϟ' => 'ϟ', + 'Ϡ' => 'ϡ', + 'Ϣ' => 'ϣ', + 'Ϥ' => 'ϥ', + 'Ϧ' => 'ϧ', + 'Ϩ' => 'ϩ', + 'Ϫ' => 'ϫ', + 'Ϭ' => 'ϭ', + 'Ϯ' => 'ϯ', + 'ϴ' => 'θ', + 'Ϸ' => 'ϸ', + 'Ϲ' => 'ϲ', + 'Ϻ' => 'ϻ', + 'Ͻ' => 'ͻ', + 'Ͼ' => 'ͼ', + 'Ͽ' => 'ͽ', + 'Ѐ' => 'ѐ', + 'Ё' => 'ё', + 'Ђ' => 'ђ', + 'Ѓ' => 'ѓ', + 'Є' => 'є', + 'Ѕ' => 'ѕ', + 'І' => 'і', + 'Ї' => 'ї', + 'Ј' => 'ј', + 'Љ' => 'љ', + 'Њ' => 'њ', + 'Ћ' => 'ћ', + 'Ќ' => 'ќ', + 'Ѝ' => 'ѝ', + 'Ў' => 'ў', + 'Џ' => 'џ', + 'А' => 'а', + 'Б' => 'б', + 'В' => 'в', + 'Г' => 'г', + 'Д' => 'д', + 'Е' => 'е', + 'Ж' => 'ж', + 'З' => 'з', + 'И' => 'и', + 'Й' => 'й', + 'К' => 'к', + 'Л' => 'л', + 'М' => 'м', + 'Н' => 'н', + 'О' => 'о', + 'П' => 'п', + 'Р' => 'р', + 'С' => 'с', + 'Т' => 'т', + 'У' => 'у', + 'Ф' => 'ф', + 'Х' => 'х', + 'Ц' => 'ц', + 'Ч' => 'ч', + 'Ш' => 'ш', + 'Щ' => 'щ', + 'Ъ' => 'ъ', + 'Ы' => 'ы', + 'Ь' => 'ь', + 'Э' => 'э', + 'Ю' => 'ю', + 'Я' => 'я', + 'Ѡ' => 'ѡ', + 'Ѣ' => 'ѣ', + 'Ѥ' => 'ѥ', + 'Ѧ' => 'ѧ', + 'Ѩ' => 'ѩ', + 'Ѫ' => 'ѫ', + 'Ѭ' => 'ѭ', + 'Ѯ' => 'ѯ', + 'Ѱ' => 'ѱ', + 'Ѳ' => 'ѳ', + 'Ѵ' => 'ѵ', + 'Ѷ' => 'ѷ', + 'Ѹ' => 'ѹ', + 'Ѻ' => 'ѻ', + 'Ѽ' => 'ѽ', + 'Ѿ' => 'ѿ', + 'Ҁ' => 'ҁ', + 'Ҋ' => 'ҋ', + 'Ҍ' => 'ҍ', + 'Ҏ' => 'ҏ', + 'Ґ' => 'ґ', + 'Ғ' => 'ғ', + 'Ҕ' => 'ҕ', + 'Җ' => 'җ', + 'Ҙ' => 'ҙ', + 'Қ' => 'қ', + 'Ҝ' => 'ҝ', + 'Ҟ' => 'ҟ', + 'Ҡ' => 'ҡ', + 'Ң' => 'ң', + 'Ҥ' => 'ҥ', + 'Ҧ' => 'ҧ', + 'Ҩ' => 'ҩ', + 'Ҫ' => 'ҫ', + 'Ҭ' => 'ҭ', + 'Ү' => 'ү', + 'Ұ' => 'ұ', + 'Ҳ' => 'ҳ', + 'Ҵ' => 'ҵ', + 'Ҷ' => 'ҷ', + 'Ҹ' => 'ҹ', + 'Һ' => 'һ', + 'Ҽ' => 'ҽ', + 'Ҿ' => 'ҿ', + 'Ӏ' => 'ӏ', + 'Ӂ' => 'ӂ', + 'Ӄ' => 'ӄ', + 'Ӆ' => 'ӆ', + 'Ӈ' => 'ӈ', + 'Ӊ' => 'ӊ', + 'Ӌ' => 'ӌ', + 'Ӎ' => 'ӎ', + 'Ӑ' => 'ӑ', + 'Ӓ' => 'ӓ', + 'Ӕ' => 'ӕ', + 'Ӗ' => 'ӗ', + 'Ә' => 'ә', + 'Ӛ' => 'ӛ', + 'Ӝ' => 'ӝ', + 'Ӟ' => 'ӟ', + 'Ӡ' => 'ӡ', + 'Ӣ' => 'ӣ', + 'Ӥ' => 'ӥ', + 'Ӧ' => 'ӧ', + 'Ө' => 'ө', + 'Ӫ' => 'ӫ', + 'Ӭ' => 'ӭ', + 'Ӯ' => 'ӯ', + 'Ӱ' => 'ӱ', + 'Ӳ' => 'ӳ', + 'Ӵ' => 'ӵ', + 'Ӷ' => 'ӷ', + 'Ӹ' => 'ӹ', + 'Ӻ' => 'ӻ', + 'Ӽ' => 'ӽ', + 'Ӿ' => 'ӿ', + 'Ԁ' => 'ԁ', + 'Ԃ' => 'ԃ', + 'Ԅ' => 'ԅ', + 'Ԇ' => 'ԇ', + 'Ԉ' => 'ԉ', + 'Ԋ' => 'ԋ', + 'Ԍ' => 'ԍ', + 'Ԏ' => 'ԏ', + 'Ԑ' => 'ԑ', + 'Ԓ' => 'ԓ', + 'Ԕ' => 'ԕ', + 'Ԗ' => 'ԗ', + 'Ԙ' => 'ԙ', + 'Ԛ' => 'ԛ', + 'Ԝ' => 'ԝ', + 'Ԟ' => 'ԟ', + 'Ԡ' => 'ԡ', + 'Ԣ' => 'ԣ', + 'Ԥ' => 'ԥ', + 'Ԧ' => 'ԧ', + 'Ԩ' => 'ԩ', + 'Ԫ' => 'ԫ', + 'Ԭ' => 'ԭ', + 'Ԯ' => 'ԯ', + 'Ա' => 'ա', + 'Բ' => 'բ', + 'Գ' => 'գ', + 'Դ' => 'դ', + 'Ե' => 'ե', + 'Զ' => 'զ', + 'Է' => 'է', + 'Ը' => 'ը', + 'Թ' => 'թ', + 'Ժ' => 'ժ', + 'Ի' => 'ի', + 'Լ' => 'լ', + 'Խ' => 'խ', + 'Ծ' => 'ծ', + 'Կ' => 'կ', + 'Հ' => 'հ', + 'Ձ' => 'ձ', + 'Ղ' => 'ղ', + 'Ճ' => 'ճ', + 'Մ' => 'մ', + 'Յ' => 'յ', + 'Ն' => 'ն', + 'Շ' => 'շ', + 'Ո' => 'ո', + 'Չ' => 'չ', + 'Պ' => 'պ', + 'Ջ' => 'ջ', + 'Ռ' => 'ռ', + 'Ս' => 'ս', + 'Վ' => 'վ', + 'Տ' => 'տ', + 'Ր' => 'ր', + 'Ց' => 'ց', + 'Ւ' => 'ւ', + 'Փ' => 'փ', + 'Ք' => 'ք', + 'Օ' => 'օ', + 'Ֆ' => 'ֆ', + 'Ⴀ' => 'ⴀ', + 'Ⴁ' => 'ⴁ', + 'Ⴂ' => 'ⴂ', + 'Ⴃ' => 'ⴃ', + 'Ⴄ' => 'ⴄ', + 'Ⴅ' => 'ⴅ', + 'Ⴆ' => 'ⴆ', + 'Ⴇ' => 'ⴇ', + 'Ⴈ' => 'ⴈ', + 'Ⴉ' => 'ⴉ', + 'Ⴊ' => 'ⴊ', + 'Ⴋ' => 'ⴋ', + 'Ⴌ' => 'ⴌ', + 'Ⴍ' => 'ⴍ', + 'Ⴎ' => 'ⴎ', + 'Ⴏ' => 'ⴏ', + 'Ⴐ' => 'ⴐ', + 'Ⴑ' => 'ⴑ', + 'Ⴒ' => 'ⴒ', + 'Ⴓ' => 'ⴓ', + 'Ⴔ' => 'ⴔ', + 'Ⴕ' => 'ⴕ', + 'Ⴖ' => 'ⴖ', + 'Ⴗ' => 'ⴗ', + 'Ⴘ' => 'ⴘ', + 'Ⴙ' => 'ⴙ', + 'Ⴚ' => 'ⴚ', + 'Ⴛ' => 'ⴛ', + 'Ⴜ' => 'ⴜ', + 'Ⴝ' => 'ⴝ', + 'Ⴞ' => 'ⴞ', + 'Ⴟ' => 'ⴟ', + 'Ⴠ' => 'ⴠ', + 'Ⴡ' => 'ⴡ', + 'Ⴢ' => 'ⴢ', + 'Ⴣ' => 'ⴣ', + 'Ⴤ' => 'ⴤ', + 'Ⴥ' => 'ⴥ', + 'Ⴧ' => 'ⴧ', + 'Ⴭ' => 'ⴭ', + 'Ꭰ' => 'ꭰ', + 'Ꭱ' => 'ꭱ', + 'Ꭲ' => 'ꭲ', + 'Ꭳ' => 'ꭳ', + 'Ꭴ' => 'ꭴ', + 'Ꭵ' => 'ꭵ', + 'Ꭶ' => 'ꭶ', + 'Ꭷ' => 'ꭷ', + 'Ꭸ' => 'ꭸ', + 'Ꭹ' => 'ꭹ', + 'Ꭺ' => 'ꭺ', + 'Ꭻ' => 'ꭻ', + 'Ꭼ' => 'ꭼ', + 'Ꭽ' => 'ꭽ', + 'Ꭾ' => 'ꭾ', + 'Ꭿ' => 'ꭿ', + 'Ꮀ' => 'ꮀ', + 'Ꮁ' => 'ꮁ', + 'Ꮂ' => 'ꮂ', + 'Ꮃ' => 'ꮃ', + 'Ꮄ' => 'ꮄ', + 'Ꮅ' => 'ꮅ', + 'Ꮆ' => 'ꮆ', + 'Ꮇ' => 'ꮇ', + 'Ꮈ' => 'ꮈ', + 'Ꮉ' => 'ꮉ', + 'Ꮊ' => 'ꮊ', + 'Ꮋ' => 'ꮋ', + 'Ꮌ' => 'ꮌ', + 'Ꮍ' => 'ꮍ', + 'Ꮎ' => 'ꮎ', + 'Ꮏ' => 'ꮏ', + 'Ꮐ' => 'ꮐ', + 'Ꮑ' => 'ꮑ', + 'Ꮒ' => 'ꮒ', + 'Ꮓ' => 'ꮓ', + 'Ꮔ' => 'ꮔ', + 'Ꮕ' => 'ꮕ', + 'Ꮖ' => 'ꮖ', + 'Ꮗ' => 'ꮗ', + 'Ꮘ' => 'ꮘ', + 'Ꮙ' => 'ꮙ', + 'Ꮚ' => 'ꮚ', + 'Ꮛ' => 'ꮛ', + 'Ꮜ' => 'ꮜ', + 'Ꮝ' => 'ꮝ', + 'Ꮞ' => 'ꮞ', + 'Ꮟ' => 'ꮟ', + 'Ꮠ' => 'ꮠ', + 'Ꮡ' => 'ꮡ', + 'Ꮢ' => 'ꮢ', + 'Ꮣ' => 'ꮣ', + 'Ꮤ' => 'ꮤ', + 'Ꮥ' => 'ꮥ', + 'Ꮦ' => 'ꮦ', + 'Ꮧ' => 'ꮧ', + 'Ꮨ' => 'ꮨ', + 'Ꮩ' => 'ꮩ', + 'Ꮪ' => 'ꮪ', + 'Ꮫ' => 'ꮫ', + 'Ꮬ' => 'ꮬ', + 'Ꮭ' => 'ꮭ', + 'Ꮮ' => 'ꮮ', + 'Ꮯ' => 'ꮯ', + 'Ꮰ' => 'ꮰ', + 'Ꮱ' => 'ꮱ', + 'Ꮲ' => 'ꮲ', + 'Ꮳ' => 'ꮳ', + 'Ꮴ' => 'ꮴ', + 'Ꮵ' => 'ꮵ', + 'Ꮶ' => 'ꮶ', + 'Ꮷ' => 'ꮷ', + 'Ꮸ' => 'ꮸ', + 'Ꮹ' => 'ꮹ', + 'Ꮺ' => 'ꮺ', + 'Ꮻ' => 'ꮻ', + 'Ꮼ' => 'ꮼ', + 'Ꮽ' => 'ꮽ', + 'Ꮾ' => 'ꮾ', + 'Ꮿ' => 'ꮿ', + 'Ᏸ' => 'ᏸ', + 'Ᏹ' => 'ᏹ', + 'Ᏺ' => 'ᏺ', + 'Ᏻ' => 'ᏻ', + 'Ᏼ' => 'ᏼ', + 'Ᏽ' => 'ᏽ', + 'Ა' => 'ა', + 'Ბ' => 'ბ', + 'Გ' => 'გ', + 'Დ' => 'დ', + 'Ე' => 'ე', + 'Ვ' => 'ვ', + 'Ზ' => 'ზ', + 'Თ' => 'თ', + 'Ი' => 'ი', + 'Კ' => 'კ', + 'Ლ' => 'ლ', + 'Მ' => 'მ', + 'Ნ' => 'ნ', + 'Ო' => 'ო', + 'Პ' => 'პ', + 'Ჟ' => 'ჟ', + 'Რ' => 'რ', + 'Ს' => 'ს', + 'Ტ' => 'ტ', + 'Უ' => 'უ', + 'Ფ' => 'ფ', + 'Ქ' => 'ქ', + 'Ღ' => 'ღ', + 'Ყ' => 'ყ', + 'Შ' => 'შ', + 'Ჩ' => 'ჩ', + 'Ც' => 'ც', + 'Ძ' => 'ძ', + 'Წ' => 'წ', + 'Ჭ' => 'ჭ', + 'Ხ' => 'ხ', + 'Ჯ' => 'ჯ', + 'Ჰ' => 'ჰ', + 'Ჱ' => 'ჱ', + 'Ჲ' => 'ჲ', + 'Ჳ' => 'ჳ', + 'Ჴ' => 'ჴ', + 'Ჵ' => 'ჵ', + 'Ჶ' => 'ჶ', + 'Ჷ' => 'ჷ', + 'Ჸ' => 'ჸ', + 'Ჹ' => 'ჹ', + 'Ჺ' => 'ჺ', + 'Ჽ' => 'ჽ', + 'Ჾ' => 'ჾ', + 'Ჿ' => 'ჿ', + 'Ḁ' => 'ḁ', + 'Ḃ' => 'ḃ', + 'Ḅ' => 'ḅ', + 'Ḇ' => 'ḇ', + 'Ḉ' => 'ḉ', + 'Ḋ' => 'ḋ', + 'Ḍ' => 'ḍ', + 'Ḏ' => 'ḏ', + 'Ḑ' => 'ḑ', + 'Ḓ' => 'ḓ', + 'Ḕ' => 'ḕ', + 'Ḗ' => 'ḗ', + 'Ḙ' => 'ḙ', + 'Ḛ' => 'ḛ', + 'Ḝ' => 'ḝ', + 'Ḟ' => 'ḟ', + 'Ḡ' => 'ḡ', + 'Ḣ' => 'ḣ', + 'Ḥ' => 'ḥ', + 'Ḧ' => 'ḧ', + 'Ḩ' => 'ḩ', + 'Ḫ' => 'ḫ', + 'Ḭ' => 'ḭ', + 'Ḯ' => 'ḯ', + 'Ḱ' => 'ḱ', + 'Ḳ' => 'ḳ', + 'Ḵ' => 'ḵ', + 'Ḷ' => 'ḷ', + 'Ḹ' => 'ḹ', + 'Ḻ' => 'ḻ', + 'Ḽ' => 'ḽ', + 'Ḿ' => 'ḿ', + 'Ṁ' => 'ṁ', + 'Ṃ' => 'ṃ', + 'Ṅ' => 'ṅ', + 'Ṇ' => 'ṇ', + 'Ṉ' => 'ṉ', + 'Ṋ' => 'ṋ', + 'Ṍ' => 'ṍ', + 'Ṏ' => 'ṏ', + 'Ṑ' => 'ṑ', + 'Ṓ' => 'ṓ', + 'Ṕ' => 'ṕ', + 'Ṗ' => 'ṗ', + 'Ṙ' => 'ṙ', + 'Ṛ' => 'ṛ', + 'Ṝ' => 'ṝ', + 'Ṟ' => 'ṟ', + 'Ṡ' => 'ṡ', + 'Ṣ' => 'ṣ', + 'Ṥ' => 'ṥ', + 'Ṧ' => 'ṧ', + 'Ṩ' => 'ṩ', + 'Ṫ' => 'ṫ', + 'Ṭ' => 'ṭ', + 'Ṯ' => 'ṯ', + 'Ṱ' => 'ṱ', + 'Ṳ' => 'ṳ', + 'Ṵ' => 'ṵ', + 'Ṷ' => 'ṷ', + 'Ṹ' => 'ṹ', + 'Ṻ' => 'ṻ', + 'Ṽ' => 'ṽ', + 'Ṿ' => 'ṿ', + 'Ẁ' => 'ẁ', + 'Ẃ' => 'ẃ', + 'Ẅ' => 'ẅ', + 'Ẇ' => 'ẇ', + 'Ẉ' => 'ẉ', + 'Ẋ' => 'ẋ', + 'Ẍ' => 'ẍ', + 'Ẏ' => 'ẏ', + 'Ẑ' => 'ẑ', + 'Ẓ' => 'ẓ', + 'Ẕ' => 'ẕ', + 'ẞ' => 'ß', + 'Ạ' => 'ạ', + 'Ả' => 'ả', + 'Ấ' => 'ấ', + 'Ầ' => 'ầ', + 'Ẩ' => 'ẩ', + 'Ẫ' => 'ẫ', + 'Ậ' => 'ậ', + 'Ắ' => 'ắ', + 'Ằ' => 'ằ', + 'Ẳ' => 'ẳ', + 'Ẵ' => 'ẵ', + 'Ặ' => 'ặ', + 'Ẹ' => 'ẹ', + 'Ẻ' => 'ẻ', + 'Ẽ' => 'ẽ', + 'Ế' => 'ế', + 'Ề' => 'ề', + 'Ể' => 'ể', + 'Ễ' => 'ễ', + 'Ệ' => 'ệ', + 'Ỉ' => 'ỉ', + 'Ị' => 'ị', + 'Ọ' => 'ọ', + 'Ỏ' => 'ỏ', + 'Ố' => 'ố', + 'Ồ' => 'ồ', + 'Ổ' => 'ổ', + 'Ỗ' => 'ỗ', + 'Ộ' => 'ộ', + 'Ớ' => 'ớ', + 'Ờ' => 'ờ', + 'Ở' => 'ở', + 'Ỡ' => 'ỡ', + 'Ợ' => 'ợ', + 'Ụ' => 'ụ', + 'Ủ' => 'ủ', + 'Ứ' => 'ứ', + 'Ừ' => 'ừ', + 'Ử' => 'ử', + 'Ữ' => 'ữ', + 'Ự' => 'ự', + 'Ỳ' => 'ỳ', + 'Ỵ' => 'ỵ', + 'Ỷ' => 'ỷ', + 'Ỹ' => 'ỹ', + 'Ỻ' => 'ỻ', + 'Ỽ' => 'ỽ', + 'Ỿ' => 'ỿ', + 'Ἀ' => 'ἀ', + 'Ἁ' => 'ἁ', + 'Ἂ' => 'ἂ', + 'Ἃ' => 'ἃ', + 'Ἄ' => 'ἄ', + 'Ἅ' => 'ἅ', + 'Ἆ' => 'ἆ', + 'Ἇ' => 'ἇ', + 'Ἐ' => 'ἐ', + 'Ἑ' => 'ἑ', + 'Ἒ' => 'ἒ', + 'Ἓ' => 'ἓ', + 'Ἔ' => 'ἔ', + 'Ἕ' => 'ἕ', + 'Ἠ' => 'ἠ', + 'Ἡ' => 'ἡ', + 'Ἢ' => 'ἢ', + 'Ἣ' => 'ἣ', + 'Ἤ' => 'ἤ', + 'Ἥ' => 'ἥ', + 'Ἦ' => 'ἦ', + 'Ἧ' => 'ἧ', + 'Ἰ' => 'ἰ', + 'Ἱ' => 'ἱ', + 'Ἲ' => 'ἲ', + 'Ἳ' => 'ἳ', + 'Ἴ' => 'ἴ', + 'Ἵ' => 'ἵ', + 'Ἶ' => 'ἶ', + 'Ἷ' => 'ἷ', + 'Ὀ' => 'ὀ', + 'Ὁ' => 'ὁ', + 'Ὂ' => 'ὂ', + 'Ὃ' => 'ὃ', + 'Ὄ' => 'ὄ', + 'Ὅ' => 'ὅ', + 'Ὑ' => 'ὑ', + 'Ὓ' => 'ὓ', + 'Ὕ' => 'ὕ', + 'Ὗ' => 'ὗ', + 'Ὠ' => 'ὠ', + 'Ὡ' => 'ὡ', + 'Ὢ' => 'ὢ', + 'Ὣ' => 'ὣ', + 'Ὤ' => 'ὤ', + 'Ὥ' => 'ὥ', + 'Ὦ' => 'ὦ', + 'Ὧ' => 'ὧ', + 'ᾈ' => 'ᾀ', + 'ᾉ' => 'ᾁ', + 'ᾊ' => 'ᾂ', + 'ᾋ' => 'ᾃ', + 'ᾌ' => 'ᾄ', + 'ᾍ' => 'ᾅ', + 'ᾎ' => 'ᾆ', + 'ᾏ' => 'ᾇ', + 'ᾘ' => 'ᾐ', + 'ᾙ' => 'ᾑ', + 'ᾚ' => 'ᾒ', + 'ᾛ' => 'ᾓ', + 'ᾜ' => 'ᾔ', + 'ᾝ' => 'ᾕ', + 'ᾞ' => 'ᾖ', + 'ᾟ' => 'ᾗ', + 'ᾨ' => 'ᾠ', + 'ᾩ' => 'ᾡ', + 'ᾪ' => 'ᾢ', + 'ᾫ' => 'ᾣ', + 'ᾬ' => 'ᾤ', + 'ᾭ' => 'ᾥ', + 'ᾮ' => 'ᾦ', + 'ᾯ' => 'ᾧ', + 'Ᾰ' => 'ᾰ', + 'Ᾱ' => 'ᾱ', + 'Ὰ' => 'ὰ', + 'Ά' => 'ά', + 'ᾼ' => 'ᾳ', + 'Ὲ' => 'ὲ', + 'Έ' => 'έ', + 'Ὴ' => 'ὴ', + 'Ή' => 'ή', + 'ῌ' => 'ῃ', + 'Ῐ' => 'ῐ', + 'Ῑ' => 'ῑ', + 'Ὶ' => 'ὶ', + 'Ί' => 'ί', + 'Ῠ' => 'ῠ', + 'Ῡ' => 'ῡ', + 'Ὺ' => 'ὺ', + 'Ύ' => 'ύ', + 'Ῥ' => 'ῥ', + 'Ὸ' => 'ὸ', + 'Ό' => 'ό', + 'Ὼ' => 'ὼ', + 'Ώ' => 'ώ', + 'ῼ' => 'ῳ', + 'Ω' => 'ω', + 'K' => 'k', + 'Å' => 'å', + 'Ⅎ' => 'ⅎ', + 'Ⅰ' => 'ⅰ', + 'Ⅱ' => 'ⅱ', + 'Ⅲ' => 'ⅲ', + 'Ⅳ' => 'ⅳ', + 'Ⅴ' => 'ⅴ', + 'Ⅵ' => 'ⅵ', + 'Ⅶ' => 'ⅶ', + 'Ⅷ' => 'ⅷ', + 'Ⅸ' => 'ⅸ', + 'Ⅹ' => 'ⅹ', + 'Ⅺ' => 'ⅺ', + 'Ⅻ' => 'ⅻ', + 'Ⅼ' => 'ⅼ', + 'Ⅽ' => 'ⅽ', + 'Ⅾ' => 'ⅾ', + 'Ⅿ' => 'ⅿ', + 'Ↄ' => 'ↄ', + 'Ⓐ' => 'ⓐ', + 'Ⓑ' => 'ⓑ', + 'Ⓒ' => 'ⓒ', + 'Ⓓ' => 'ⓓ', + 'Ⓔ' => 'ⓔ', + 'Ⓕ' => 'ⓕ', + 'Ⓖ' => 'ⓖ', + 'Ⓗ' => 'ⓗ', + 'Ⓘ' => 'ⓘ', + 'Ⓙ' => 'ⓙ', + 'Ⓚ' => 'ⓚ', + 'Ⓛ' => 'ⓛ', + 'Ⓜ' => 'ⓜ', + 'Ⓝ' => 'ⓝ', + 'Ⓞ' => 'ⓞ', + 'Ⓟ' => 'ⓟ', + 'Ⓠ' => 'ⓠ', + 'Ⓡ' => 'ⓡ', + 'Ⓢ' => 'ⓢ', + 'Ⓣ' => 'ⓣ', + 'Ⓤ' => 'ⓤ', + 'Ⓥ' => 'ⓥ', + 'Ⓦ' => 'ⓦ', + 'Ⓧ' => 'ⓧ', + 'Ⓨ' => 'ⓨ', + 'Ⓩ' => 'ⓩ', + 'Ⰰ' => 'ⰰ', + 'Ⰱ' => 'ⰱ', + 'Ⰲ' => 'ⰲ', + 'Ⰳ' => 'ⰳ', + 'Ⰴ' => 'ⰴ', + 'Ⰵ' => 'ⰵ', + 'Ⰶ' => 'ⰶ', + 'Ⰷ' => 'ⰷ', + 'Ⰸ' => 'ⰸ', + 'Ⰹ' => 'ⰹ', + 'Ⰺ' => 'ⰺ', + 'Ⰻ' => 'ⰻ', + 'Ⰼ' => 'ⰼ', + 'Ⰽ' => 'ⰽ', + 'Ⰾ' => 'ⰾ', + 'Ⰿ' => 'ⰿ', + 'Ⱀ' => 'ⱀ', + 'Ⱁ' => 'ⱁ', + 'Ⱂ' => 'ⱂ', + 'Ⱃ' => 'ⱃ', + 'Ⱄ' => 'ⱄ', + 'Ⱅ' => 'ⱅ', + 'Ⱆ' => 'ⱆ', + 'Ⱇ' => 'ⱇ', + 'Ⱈ' => 'ⱈ', + 'Ⱉ' => 'ⱉ', + 'Ⱊ' => 'ⱊ', + 'Ⱋ' => 'ⱋ', + 'Ⱌ' => 'ⱌ', + 'Ⱍ' => 'ⱍ', + 'Ⱎ' => 'ⱎ', + 'Ⱏ' => 'ⱏ', + 'Ⱐ' => 'ⱐ', + 'Ⱑ' => 'ⱑ', + 'Ⱒ' => 'ⱒ', + 'Ⱓ' => 'ⱓ', + 'Ⱔ' => 'ⱔ', + 'Ⱕ' => 'ⱕ', + 'Ⱖ' => 'ⱖ', + 'Ⱗ' => 'ⱗ', + 'Ⱘ' => 'ⱘ', + 'Ⱙ' => 'ⱙ', + 'Ⱚ' => 'ⱚ', + 'Ⱛ' => 'ⱛ', + 'Ⱜ' => 'ⱜ', + 'Ⱝ' => 'ⱝ', + 'Ⱞ' => 'ⱞ', + 'Ⱡ' => 'ⱡ', + 'Ɫ' => 'ɫ', + 'Ᵽ' => 'ᵽ', + 'Ɽ' => 'ɽ', + 'Ⱨ' => 'ⱨ', + 'Ⱪ' => 'ⱪ', + 'Ⱬ' => 'ⱬ', + 'Ɑ' => 'ɑ', + 'Ɱ' => 'ɱ', + 'Ɐ' => 'ɐ', + 'Ɒ' => 'ɒ', + 'Ⱳ' => 'ⱳ', + 'Ⱶ' => 'ⱶ', + 'Ȿ' => 'ȿ', + 'Ɀ' => 'ɀ', + 'Ⲁ' => 'ⲁ', + 'Ⲃ' => 'ⲃ', + 'Ⲅ' => 'ⲅ', + 'Ⲇ' => 'ⲇ', + 'Ⲉ' => 'ⲉ', + 'Ⲋ' => 'ⲋ', + 'Ⲍ' => 'ⲍ', + 'Ⲏ' => 'ⲏ', + 'Ⲑ' => 'ⲑ', + 'Ⲓ' => 'ⲓ', + 'Ⲕ' => 'ⲕ', + 'Ⲗ' => 'ⲗ', + 'Ⲙ' => 'ⲙ', + 'Ⲛ' => 'ⲛ', + 'Ⲝ' => 'ⲝ', + 'Ⲟ' => 'ⲟ', + 'Ⲡ' => 'ⲡ', + 'Ⲣ' => 'ⲣ', + 'Ⲥ' => 'ⲥ', + 'Ⲧ' => 'ⲧ', + 'Ⲩ' => 'ⲩ', + 'Ⲫ' => 'ⲫ', + 'Ⲭ' => 'ⲭ', + 'Ⲯ' => 'ⲯ', + 'Ⲱ' => 'ⲱ', + 'Ⲳ' => 'ⲳ', + 'Ⲵ' => 'ⲵ', + 'Ⲷ' => 'ⲷ', + 'Ⲹ' => 'ⲹ', + 'Ⲻ' => 'ⲻ', + 'Ⲽ' => 'ⲽ', + 'Ⲿ' => 'ⲿ', + 'Ⳁ' => 'ⳁ', + 'Ⳃ' => 'ⳃ', + 'Ⳅ' => 'ⳅ', + 'Ⳇ' => 'ⳇ', + 'Ⳉ' => 'ⳉ', + 'Ⳋ' => 'ⳋ', + 'Ⳍ' => 'ⳍ', + 'Ⳏ' => 'ⳏ', + 'Ⳑ' => 'ⳑ', + 'Ⳓ' => 'ⳓ', + 'Ⳕ' => 'ⳕ', + 'Ⳗ' => 'ⳗ', + 'Ⳙ' => 'ⳙ', + 'Ⳛ' => 'ⳛ', + 'Ⳝ' => 'ⳝ', + 'Ⳟ' => 'ⳟ', + 'Ⳡ' => 'ⳡ', + 'Ⳣ' => 'ⳣ', + 'Ⳬ' => 'ⳬ', + 'Ⳮ' => 'ⳮ', + 'Ⳳ' => 'ⳳ', + 'Ꙁ' => 'ꙁ', + 'Ꙃ' => 'ꙃ', + 'Ꙅ' => 'ꙅ', + 'Ꙇ' => 'ꙇ', + 'Ꙉ' => 'ꙉ', + 'Ꙋ' => 'ꙋ', + 'Ꙍ' => 'ꙍ', + 'Ꙏ' => 'ꙏ', + 'Ꙑ' => 'ꙑ', + 'Ꙓ' => 'ꙓ', + 'Ꙕ' => 'ꙕ', + 'Ꙗ' => 'ꙗ', + 'Ꙙ' => 'ꙙ', + 'Ꙛ' => 'ꙛ', + 'Ꙝ' => 'ꙝ', + 'Ꙟ' => 'ꙟ', + 'Ꙡ' => 'ꙡ', + 'Ꙣ' => 'ꙣ', + 'Ꙥ' => 'ꙥ', + 'Ꙧ' => 'ꙧ', + 'Ꙩ' => 'ꙩ', + 'Ꙫ' => 'ꙫ', + 'Ꙭ' => 'ꙭ', + 'Ꚁ' => 'ꚁ', + 'Ꚃ' => 'ꚃ', + 'Ꚅ' => 'ꚅ', + 'Ꚇ' => 'ꚇ', + 'Ꚉ' => 'ꚉ', + 'Ꚋ' => 'ꚋ', + 'Ꚍ' => 'ꚍ', + 'Ꚏ' => 'ꚏ', + 'Ꚑ' => 'ꚑ', + 'Ꚓ' => 'ꚓ', + 'Ꚕ' => 'ꚕ', + 'Ꚗ' => 'ꚗ', + 'Ꚙ' => 'ꚙ', + 'Ꚛ' => 'ꚛ', + 'Ꜣ' => 'ꜣ', + 'Ꜥ' => 'ꜥ', + 'Ꜧ' => 'ꜧ', + 'Ꜩ' => 'ꜩ', + 'Ꜫ' => 'ꜫ', + 'Ꜭ' => 'ꜭ', + 'Ꜯ' => 'ꜯ', + 'Ꜳ' => 'ꜳ', + 'Ꜵ' => 'ꜵ', + 'Ꜷ' => 'ꜷ', + 'Ꜹ' => 'ꜹ', + 'Ꜻ' => 'ꜻ', + 'Ꜽ' => 'ꜽ', + 'Ꜿ' => 'ꜿ', + 'Ꝁ' => 'ꝁ', + 'Ꝃ' => 'ꝃ', + 'Ꝅ' => 'ꝅ', + 'Ꝇ' => 'ꝇ', + 'Ꝉ' => 'ꝉ', + 'Ꝋ' => 'ꝋ', + 'Ꝍ' => 'ꝍ', + 'Ꝏ' => 'ꝏ', + 'Ꝑ' => 'ꝑ', + 'Ꝓ' => 'ꝓ', + 'Ꝕ' => 'ꝕ', + 'Ꝗ' => 'ꝗ', + 'Ꝙ' => 'ꝙ', + 'Ꝛ' => 'ꝛ', + 'Ꝝ' => 'ꝝ', + 'Ꝟ' => 'ꝟ', + 'Ꝡ' => 'ꝡ', + 'Ꝣ' => 'ꝣ', + 'Ꝥ' => 'ꝥ', + 'Ꝧ' => 'ꝧ', + 'Ꝩ' => 'ꝩ', + 'Ꝫ' => 'ꝫ', + 'Ꝭ' => 'ꝭ', + 'Ꝯ' => 'ꝯ', + 'Ꝺ' => 'ꝺ', + 'Ꝼ' => 'ꝼ', + 'Ᵹ' => 'ᵹ', + 'Ꝿ' => 'ꝿ', + 'Ꞁ' => 'ꞁ', + 'Ꞃ' => 'ꞃ', + 'Ꞅ' => 'ꞅ', + 'Ꞇ' => 'ꞇ', + 'Ꞌ' => 'ꞌ', + 'Ɥ' => 'ɥ', + 'Ꞑ' => 'ꞑ', + 'Ꞓ' => 'ꞓ', + 'Ꞗ' => 'ꞗ', + 'Ꞙ' => 'ꞙ', + 'Ꞛ' => 'ꞛ', + 'Ꞝ' => 'ꞝ', + 'Ꞟ' => 'ꞟ', + 'Ꞡ' => 'ꞡ', + 'Ꞣ' => 'ꞣ', + 'Ꞥ' => 'ꞥ', + 'Ꞧ' => 'ꞧ', + 'Ꞩ' => 'ꞩ', + 'Ɦ' => 'ɦ', + 'Ɜ' => 'ɜ', + 'Ɡ' => 'ɡ', + 'Ɬ' => 'ɬ', + 'Ɪ' => 'ɪ', + 'Ʞ' => 'ʞ', + 'Ʇ' => 'ʇ', + 'Ʝ' => 'ʝ', + 'Ꭓ' => 'ꭓ', + 'Ꞵ' => 'ꞵ', + 'Ꞷ' => 'ꞷ', + 'Ꞹ' => 'ꞹ', + 'Ꞻ' => 'ꞻ', + 'Ꞽ' => 'ꞽ', + 'Ꞿ' => 'ꞿ', + 'Ꟃ' => 'ꟃ', + 'Ꞔ' => 'ꞔ', + 'Ʂ' => 'ʂ', + 'Ᶎ' => 'ᶎ', + 'Ꟈ' => 'ꟈ', + 'Ꟊ' => 'ꟊ', + 'Ꟶ' => 'ꟶ', + 'A' => 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + '𐐀' => '𐐨', + '𐐁' => '𐐩', + '𐐂' => '𐐪', + '𐐃' => '𐐫', + '𐐄' => '𐐬', + '𐐅' => '𐐭', + '𐐆' => '𐐮', + '𐐇' => '𐐯', + '𐐈' => '𐐰', + '𐐉' => '𐐱', + '𐐊' => '𐐲', + '𐐋' => '𐐳', + '𐐌' => '𐐴', + '𐐍' => '𐐵', + '𐐎' => '𐐶', + '𐐏' => '𐐷', + '𐐐' => '𐐸', + '𐐑' => '𐐹', + '𐐒' => '𐐺', + '𐐓' => '𐐻', + '𐐔' => '𐐼', + '𐐕' => '𐐽', + '𐐖' => '𐐾', + '𐐗' => '𐐿', + '𐐘' => '𐑀', + '𐐙' => '𐑁', + '𐐚' => '𐑂', + '𐐛' => '𐑃', + '𐐜' => '𐑄', + '𐐝' => '𐑅', + '𐐞' => '𐑆', + '𐐟' => '𐑇', + '𐐠' => '𐑈', + '𐐡' => '𐑉', + '𐐢' => '𐑊', + '𐐣' => '𐑋', + '𐐤' => '𐑌', + '𐐥' => '𐑍', + '𐐦' => '𐑎', + '𐐧' => '𐑏', + '𐒰' => '𐓘', + '𐒱' => '𐓙', + '𐒲' => '𐓚', + '𐒳' => '𐓛', + '𐒴' => '𐓜', + '𐒵' => '𐓝', + '𐒶' => '𐓞', + '𐒷' => '𐓟', + '𐒸' => '𐓠', + '𐒹' => '𐓡', + '𐒺' => '𐓢', + '𐒻' => '𐓣', + '𐒼' => '𐓤', + '𐒽' => '𐓥', + '𐒾' => '𐓦', + '𐒿' => '𐓧', + '𐓀' => '𐓨', + '𐓁' => '𐓩', + '𐓂' => '𐓪', + '𐓃' => '𐓫', + '𐓄' => '𐓬', + '𐓅' => '𐓭', + '𐓆' => '𐓮', + '𐓇' => '𐓯', + '𐓈' => '𐓰', + '𐓉' => '𐓱', + '𐓊' => '𐓲', + '𐓋' => '𐓳', + '𐓌' => '𐓴', + '𐓍' => '𐓵', + '𐓎' => '𐓶', + '𐓏' => '𐓷', + '𐓐' => '𐓸', + '𐓑' => '𐓹', + '𐓒' => '𐓺', + '𐓓' => '𐓻', + '𐲀' => '𐳀', + '𐲁' => '𐳁', + '𐲂' => '𐳂', + '𐲃' => '𐳃', + '𐲄' => '𐳄', + '𐲅' => '𐳅', + '𐲆' => '𐳆', + '𐲇' => '𐳇', + '𐲈' => '𐳈', + '𐲉' => '𐳉', + '𐲊' => '𐳊', + '𐲋' => '𐳋', + '𐲌' => '𐳌', + '𐲍' => '𐳍', + '𐲎' => '𐳎', + '𐲏' => '𐳏', + '𐲐' => '𐳐', + '𐲑' => '𐳑', + '𐲒' => '𐳒', + '𐲓' => '𐳓', + '𐲔' => '𐳔', + '𐲕' => '𐳕', + '𐲖' => '𐳖', + '𐲗' => '𐳗', + '𐲘' => '𐳘', + '𐲙' => '𐳙', + '𐲚' => '𐳚', + '𐲛' => '𐳛', + '𐲜' => '𐳜', + '𐲝' => '𐳝', + '𐲞' => '𐳞', + '𐲟' => '𐳟', + '𐲠' => '𐳠', + '𐲡' => '𐳡', + '𐲢' => '𐳢', + '𐲣' => '𐳣', + '𐲤' => '𐳤', + '𐲥' => '𐳥', + '𐲦' => '𐳦', + '𐲧' => '𐳧', + '𐲨' => '𐳨', + '𐲩' => '𐳩', + '𐲪' => '𐳪', + '𐲫' => '𐳫', + '𐲬' => '𐳬', + '𐲭' => '𐳭', + '𐲮' => '𐳮', + '𐲯' => '𐳯', + '𐲰' => '𐳰', + '𐲱' => '𐳱', + '𐲲' => '𐳲', + '𑢠' => '𑣀', + '𑢡' => '𑣁', + '𑢢' => '𑣂', + '𑢣' => '𑣃', + '𑢤' => '𑣄', + '𑢥' => '𑣅', + '𑢦' => '𑣆', + '𑢧' => '𑣇', + '𑢨' => '𑣈', + '𑢩' => '𑣉', + '𑢪' => '𑣊', + '𑢫' => '𑣋', + '𑢬' => '𑣌', + '𑢭' => '𑣍', + '𑢮' => '𑣎', + '𑢯' => '𑣏', + '𑢰' => '𑣐', + '𑢱' => '𑣑', + '𑢲' => '𑣒', + '𑢳' => '𑣓', + '𑢴' => '𑣔', + '𑢵' => '𑣕', + '𑢶' => '𑣖', + '𑢷' => '𑣗', + '𑢸' => '𑣘', + '𑢹' => '𑣙', + '𑢺' => '𑣚', + '𑢻' => '𑣛', + '𑢼' => '𑣜', + '𑢽' => '𑣝', + '𑢾' => '𑣞', + '𑢿' => '𑣟', + '𖹀' => '𖹠', + '𖹁' => '𖹡', + '𖹂' => '𖹢', + '𖹃' => '𖹣', + '𖹄' => '𖹤', + '𖹅' => '𖹥', + '𖹆' => '𖹦', + '𖹇' => '𖹧', + '𖹈' => '𖹨', + '𖹉' => '𖹩', + '𖹊' => '𖹪', + '𖹋' => '𖹫', + '𖹌' => '𖹬', + '𖹍' => '𖹭', + '𖹎' => '𖹮', + '𖹏' => '𖹯', + '𖹐' => '𖹰', + '𖹑' => '𖹱', + '𖹒' => '𖹲', + '𖹓' => '𖹳', + '𖹔' => '𖹴', + '𖹕' => '𖹵', + '𖹖' => '𖹶', + '𖹗' => '𖹷', + '𖹘' => '𖹸', + '𖹙' => '𖹹', + '𖹚' => '𖹺', + '𖹛' => '𖹻', + '𖹜' => '𖹼', + '𖹝' => '𖹽', + '𖹞' => '𖹾', + '𖹟' => '𖹿', + '𞤀' => '𞤢', + '𞤁' => '𞤣', + '𞤂' => '𞤤', + '𞤃' => '𞤥', + '𞤄' => '𞤦', + '𞤅' => '𞤧', + '𞤆' => '𞤨', + '𞤇' => '𞤩', + '𞤈' => '𞤪', + '𞤉' => '𞤫', + '𞤊' => '𞤬', + '𞤋' => '𞤭', + '𞤌' => '𞤮', + '𞤍' => '𞤯', + '𞤎' => '𞤰', + '𞤏' => '𞤱', + '𞤐' => '𞤲', + '𞤑' => '𞤳', + '𞤒' => '𞤴', + '𞤓' => '𞤵', + '𞤔' => '𞤶', + '𞤕' => '𞤷', + '𞤖' => '𞤸', + '𞤗' => '𞤹', + '𞤘' => '𞤺', + '𞤙' => '𞤻', + '𞤚' => '𞤼', + '𞤛' => '𞤽', + '𞤜' => '𞤾', + '𞤝' => '𞤿', + '𞤞' => '𞥀', + '𞤟' => '𞥁', + '𞤠' => '𞥂', + '𞤡' => '𞥃', +); diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php new file mode 100644 index 0000000000..2a8f6e73b9 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php @@ -0,0 +1,5 @@ +<?php + +// from Case_Ignorable in https://unicode.org/Public/UNIDATA/DerivedCoreProperties.txt + +return '/(?<![\x{0027}\x{002E}\x{003A}\x{005E}\x{0060}\x{00A8}\x{00AD}\x{00AF}\x{00B4}\x{00B7}\x{00B8}\x{02B0}-\x{02C1}\x{02C2}-\x{02C5}\x{02C6}-\x{02D1}\x{02D2}-\x{02DF}\x{02E0}-\x{02E4}\x{02E5}-\x{02EB}\x{02EC}\x{02ED}\x{02EE}\x{02EF}-\x{02FF}\x{0300}-\x{036F}\x{0374}\x{0375}\x{037A}\x{0384}-\x{0385}\x{0387}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{0559}\x{0591}-\x{05BD}\x{05BF}\x{05C1}-\x{05C2}\x{05C4}-\x{05C5}\x{05C7}\x{05F4}\x{0600}-\x{0605}\x{0610}-\x{061A}\x{061C}\x{0640}\x{064B}-\x{065F}\x{0670}\x{06D6}-\x{06DC}\x{06DD}\x{06DF}-\x{06E4}\x{06E5}-\x{06E6}\x{06E7}-\x{06E8}\x{06EA}-\x{06ED}\x{070F}\x{0711}\x{0730}-\x{074A}\x{07A6}-\x{07B0}\x{07EB}-\x{07F3}\x{07F4}-\x{07F5}\x{07FA}\x{07FD}\x{0816}-\x{0819}\x{081A}\x{081B}-\x{0823}\x{0824}\x{0825}-\x{0827}\x{0828}\x{0829}-\x{082D}\x{0859}-\x{085B}\x{08D3}-\x{08E1}\x{08E2}\x{08E3}-\x{0902}\x{093A}\x{093C}\x{0941}-\x{0948}\x{094D}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0971}\x{0981}\x{09BC}\x{09C1}-\x{09C4}\x{09CD}\x{09E2}-\x{09E3}\x{09FE}\x{0A01}-\x{0A02}\x{0A3C}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0ABC}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B3C}\x{0B3F}\x{0B41}-\x{0B44}\x{0B4D}\x{0B56}\x{0B62}-\x{0B63}\x{0B82}\x{0BC0}\x{0BCD}\x{0C00}\x{0C04}\x{0C3E}-\x{0C40}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C81}\x{0CBC}\x{0CBF}\x{0CC6}\x{0CCC}-\x{0CCD}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D3B}-\x{0D3C}\x{0D41}-\x{0D44}\x{0D4D}\x{0D62}-\x{0D63}\x{0DCA}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0E31}\x{0E34}-\x{0E3A}\x{0E46}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EB9}\x{0EBB}-\x{0EBC}\x{0EC6}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F71}-\x{0F7E}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102D}-\x{1030}\x{1032}-\x{1037}\x{1039}-\x{103A}\x{103D}-\x{103E}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1071}-\x{1074}\x{1082}\x{1085}-\x{1086}\x{108D}\x{109D}\x{10FC}\x{135D}-\x{135F}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B7}-\x{17BD}\x{17C6}\x{17C9}-\x{17D3}\x{17D7}\x{17DD}\x{180B}-\x{180D}\x{180E}\x{1843}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1927}-\x{1928}\x{1932}\x{1939}-\x{193B}\x{1A17}-\x{1A18}\x{1A1B}\x{1A56}\x{1A58}-\x{1A5E}\x{1A60}\x{1A62}\x{1A65}-\x{1A6C}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AA7}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1B00}-\x{1B03}\x{1B34}\x{1B36}-\x{1B3A}\x{1B3C}\x{1B42}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1BA2}-\x{1BA5}\x{1BA8}-\x{1BA9}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE8}-\x{1BE9}\x{1BED}\x{1BEF}-\x{1BF1}\x{1C2C}-\x{1C33}\x{1C36}-\x{1C37}\x{1C78}-\x{1C7D}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF8}-\x{1CF9}\x{1D2C}-\x{1D6A}\x{1D78}\x{1D9B}-\x{1DBF}\x{1DC0}-\x{1DF9}\x{1DFB}-\x{1DFF}\x{1FBD}\x{1FBF}-\x{1FC1}\x{1FCD}-\x{1FCF}\x{1FDD}-\x{1FDF}\x{1FED}-\x{1FEF}\x{1FFD}-\x{1FFE}\x{200B}-\x{200F}\x{2018}\x{2019}\x{2024}\x{2027}\x{202A}-\x{202E}\x{2060}-\x{2064}\x{2066}-\x{206F}\x{2071}\x{207F}\x{2090}-\x{209C}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2C7C}-\x{2C7D}\x{2CEF}-\x{2CF1}\x{2D6F}\x{2D7F}\x{2DE0}-\x{2DFF}\x{2E2F}\x{3005}\x{302A}-\x{302D}\x{3031}-\x{3035}\x{303B}\x{3099}-\x{309A}\x{309B}-\x{309C}\x{309D}-\x{309E}\x{30FC}-\x{30FE}\x{A015}\x{A4F8}-\x{A4FD}\x{A60C}\x{A66F}\x{A670}-\x{A672}\x{A674}-\x{A67D}\x{A67F}\x{A69C}-\x{A69D}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A700}-\x{A716}\x{A717}-\x{A71F}\x{A720}-\x{A721}\x{A770}\x{A788}\x{A789}-\x{A78A}\x{A7F8}-\x{A7F9}\x{A802}\x{A806}\x{A80B}\x{A825}-\x{A826}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A980}-\x{A982}\x{A9B3}\x{A9B6}-\x{A9B9}\x{A9BC}\x{A9CF}\x{A9E5}\x{A9E6}\x{AA29}-\x{AA2E}\x{AA31}-\x{AA32}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA70}\x{AA7C}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AADD}\x{AAEC}-\x{AAED}\x{AAF3}-\x{AAF4}\x{AAF6}\x{AB5B}\x{AB5C}-\x{AB5F}\x{ABE5}\x{ABE8}\x{ABED}\x{FB1E}\x{FBB2}-\x{FBC1}\x{FE00}-\x{FE0F}\x{FE13}\x{FE20}-\x{FE2F}\x{FE52}\x{FE55}\x{FEFF}\x{FF07}\x{FF0E}\x{FF1A}\x{FF3E}\x{FF40}\x{FF70}\x{FF9E}-\x{FF9F}\x{FFE3}\x{FFF9}-\x{FFFB}\x{101FD}\x{102E0}\x{10376}-\x{1037A}\x{10A01}-\x{10A03}\x{10A05}-\x{10A06}\x{10A0C}-\x{10A0F}\x{10A38}-\x{10A3A}\x{10A3F}\x{10AE5}-\x{10AE6}\x{10D24}-\x{10D27}\x{10F46}-\x{10F50}\x{11001}\x{11038}-\x{11046}\x{1107F}-\x{11081}\x{110B3}-\x{110B6}\x{110B9}-\x{110BA}\x{110BD}\x{110CD}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112D}-\x{11134}\x{11173}\x{11180}-\x{11181}\x{111B6}-\x{111BE}\x{111C9}-\x{111CC}\x{1122F}-\x{11231}\x{11234}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{1133B}-\x{1133C}\x{11340}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11438}-\x{1143F}\x{11442}-\x{11444}\x{11446}\x{1145E}\x{114B3}-\x{114B8}\x{114BA}\x{114BF}-\x{114C0}\x{114C2}-\x{114C3}\x{115B2}-\x{115B5}\x{115BC}-\x{115BD}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11633}-\x{1163A}\x{1163D}\x{1163F}-\x{11640}\x{116AB}\x{116AD}\x{116B0}-\x{116B5}\x{116B7}\x{1171D}-\x{1171F}\x{11722}-\x{11725}\x{11727}-\x{1172B}\x{1182F}-\x{11837}\x{11839}-\x{1183A}\x{11A01}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A98}-\x{11A99}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C3F}\x{11C92}-\x{11CA7}\x{11CAA}-\x{11CB0}\x{11CB2}-\x{11CB3}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D90}-\x{11D91}\x{11D95}\x{11D97}\x{11EF3}-\x{11EF4}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16B40}-\x{16B43}\x{16F8F}-\x{16F92}\x{16F93}-\x{16F9F}\x{16FE0}-\x{16FE1}\x{1BC9D}-\x{1BC9E}\x{1BCA0}-\x{1BCA3}\x{1D167}-\x{1D169}\x{1D173}-\x{1D17A}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D242}-\x{1D244}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E8D0}-\x{1E8D6}\x{1E944}-\x{1E94A}\x{1F3FB}-\x{1F3FF}\x{E0001}\x{E0020}-\x{E007F}\x{E0100}-\x{E01EF}])(\pL)(\pL*+)/u'; diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php new file mode 100644 index 0000000000..56b9cb8520 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php @@ -0,0 +1,1489 @@ +<?php + +return array ( + 'a' => 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + 'µ' => 'Μ', + 'à' => 'À', + 'á' => 'Á', + 'â' => 'Â', + 'ã' => 'Ã', + 'ä' => 'Ä', + 'å' => 'Å', + 'æ' => 'Æ', + 'ç' => 'Ç', + 'è' => 'È', + 'é' => 'É', + 'ê' => 'Ê', + 'ë' => 'Ë', + 'ì' => 'Ì', + 'í' => 'Í', + 'î' => 'Î', + 'ï' => 'Ï', + 'ð' => 'Ð', + 'ñ' => 'Ñ', + 'ò' => 'Ò', + 'ó' => 'Ó', + 'ô' => 'Ô', + 'õ' => 'Õ', + 'ö' => 'Ö', + 'ø' => 'Ø', + 'ù' => 'Ù', + 'ú' => 'Ú', + 'û' => 'Û', + 'ü' => 'Ü', + 'ý' => 'Ý', + 'þ' => 'Þ', + 'ÿ' => 'Ÿ', + 'ā' => 'Ā', + 'ă' => 'Ă', + 'ą' => 'Ą', + 'ć' => 'Ć', + 'ĉ' => 'Ĉ', + 'ċ' => 'Ċ', + 'č' => 'Č', + 'ď' => 'Ď', + 'đ' => 'Đ', + 'ē' => 'Ē', + 'ĕ' => 'Ĕ', + 'ė' => 'Ė', + 'ę' => 'Ę', + 'ě' => 'Ě', + 'ĝ' => 'Ĝ', + 'ğ' => 'Ğ', + 'ġ' => 'Ġ', + 'ģ' => 'Ģ', + 'ĥ' => 'Ĥ', + 'ħ' => 'Ħ', + 'ĩ' => 'Ĩ', + 'ī' => 'Ī', + 'ĭ' => 'Ĭ', + 'į' => 'Į', + 'ı' => 'I', + 'ij' => 'IJ', + 'ĵ' => 'Ĵ', + 'ķ' => 'Ķ', + 'ĺ' => 'Ĺ', + 'ļ' => 'Ļ', + 'ľ' => 'Ľ', + 'ŀ' => 'Ŀ', + 'ł' => 'Ł', + 'ń' => 'Ń', + 'ņ' => 'Ņ', + 'ň' => 'Ň', + 'ŋ' => 'Ŋ', + 'ō' => 'Ō', + 'ŏ' => 'Ŏ', + 'ő' => 'Ő', + 'œ' => 'Œ', + 'ŕ' => 'Ŕ', + 'ŗ' => 'Ŗ', + 'ř' => 'Ř', + 'ś' => 'Ś', + 'ŝ' => 'Ŝ', + 'ş' => 'Ş', + 'š' => 'Š', + 'ţ' => 'Ţ', + 'ť' => 'Ť', + 'ŧ' => 'Ŧ', + 'ũ' => 'Ũ', + 'ū' => 'Ū', + 'ŭ' => 'Ŭ', + 'ů' => 'Ů', + 'ű' => 'Ű', + 'ų' => 'Ų', + 'ŵ' => 'Ŵ', + 'ŷ' => 'Ŷ', + 'ź' => 'Ź', + 'ż' => 'Ż', + 'ž' => 'Ž', + 'ſ' => 'S', + 'ƀ' => 'Ƀ', + 'ƃ' => 'Ƃ', + 'ƅ' => 'Ƅ', + 'ƈ' => 'Ƈ', + 'ƌ' => 'Ƌ', + 'ƒ' => 'Ƒ', + 'ƕ' => 'Ƕ', + 'ƙ' => 'Ƙ', + 'ƚ' => 'Ƚ', + 'ƞ' => 'Ƞ', + 'ơ' => 'Ơ', + 'ƣ' => 'Ƣ', + 'ƥ' => 'Ƥ', + 'ƨ' => 'Ƨ', + 'ƭ' => 'Ƭ', + 'ư' => 'Ư', + 'ƴ' => 'Ƴ', + 'ƶ' => 'Ƶ', + 'ƹ' => 'Ƹ', + 'ƽ' => 'Ƽ', + 'ƿ' => 'Ƿ', + 'Dž' => 'DŽ', + 'dž' => 'DŽ', + 'Lj' => 'LJ', + 'lj' => 'LJ', + 'Nj' => 'NJ', + 'nj' => 'NJ', + 'ǎ' => 'Ǎ', + 'ǐ' => 'Ǐ', + 'ǒ' => 'Ǒ', + 'ǔ' => 'Ǔ', + 'ǖ' => 'Ǖ', + 'ǘ' => 'Ǘ', + 'ǚ' => 'Ǚ', + 'ǜ' => 'Ǜ', + 'ǝ' => 'Ǝ', + 'ǟ' => 'Ǟ', + 'ǡ' => 'Ǡ', + 'ǣ' => 'Ǣ', + 'ǥ' => 'Ǥ', + 'ǧ' => 'Ǧ', + 'ǩ' => 'Ǩ', + 'ǫ' => 'Ǫ', + 'ǭ' => 'Ǭ', + 'ǯ' => 'Ǯ', + 'Dz' => 'DZ', + 'dz' => 'DZ', + 'ǵ' => 'Ǵ', + 'ǹ' => 'Ǹ', + 'ǻ' => 'Ǻ', + 'ǽ' => 'Ǽ', + 'ǿ' => 'Ǿ', + 'ȁ' => 'Ȁ', + 'ȃ' => 'Ȃ', + 'ȅ' => 'Ȅ', + 'ȇ' => 'Ȇ', + 'ȉ' => 'Ȉ', + 'ȋ' => 'Ȋ', + 'ȍ' => 'Ȍ', + 'ȏ' => 'Ȏ', + 'ȑ' => 'Ȑ', + 'ȓ' => 'Ȓ', + 'ȕ' => 'Ȕ', + 'ȗ' => 'Ȗ', + 'ș' => 'Ș', + 'ț' => 'Ț', + 'ȝ' => 'Ȝ', + 'ȟ' => 'Ȟ', + 'ȣ' => 'Ȣ', + 'ȥ' => 'Ȥ', + 'ȧ' => 'Ȧ', + 'ȩ' => 'Ȩ', + 'ȫ' => 'Ȫ', + 'ȭ' => 'Ȭ', + 'ȯ' => 'Ȯ', + 'ȱ' => 'Ȱ', + 'ȳ' => 'Ȳ', + 'ȼ' => 'Ȼ', + 'ȿ' => 'Ȿ', + 'ɀ' => 'Ɀ', + 'ɂ' => 'Ɂ', + 'ɇ' => 'Ɇ', + 'ɉ' => 'Ɉ', + 'ɋ' => 'Ɋ', + 'ɍ' => 'Ɍ', + 'ɏ' => 'Ɏ', + 'ɐ' => 'Ɐ', + 'ɑ' => 'Ɑ', + 'ɒ' => 'Ɒ', + 'ɓ' => 'Ɓ', + 'ɔ' => 'Ɔ', + 'ɖ' => 'Ɖ', + 'ɗ' => 'Ɗ', + 'ə' => 'Ə', + 'ɛ' => 'Ɛ', + 'ɜ' => 'Ɜ', + 'ɠ' => 'Ɠ', + 'ɡ' => 'Ɡ', + 'ɣ' => 'Ɣ', + 'ɥ' => 'Ɥ', + 'ɦ' => 'Ɦ', + 'ɨ' => 'Ɨ', + 'ɩ' => 'Ɩ', + 'ɪ' => 'Ɪ', + 'ɫ' => 'Ɫ', + 'ɬ' => 'Ɬ', + 'ɯ' => 'Ɯ', + 'ɱ' => 'Ɱ', + 'ɲ' => 'Ɲ', + 'ɵ' => 'Ɵ', + 'ɽ' => 'Ɽ', + 'ʀ' => 'Ʀ', + 'ʂ' => 'Ʂ', + 'ʃ' => 'Ʃ', + 'ʇ' => 'Ʇ', + 'ʈ' => 'Ʈ', + 'ʉ' => 'Ʉ', + 'ʊ' => 'Ʊ', + 'ʋ' => 'Ʋ', + 'ʌ' => 'Ʌ', + 'ʒ' => 'Ʒ', + 'ʝ' => 'Ʝ', + 'ʞ' => 'Ʞ', + 'ͅ' => 'Ι', + 'ͱ' => 'Ͱ', + 'ͳ' => 'Ͳ', + 'ͷ' => 'Ͷ', + 'ͻ' => 'Ͻ', + 'ͼ' => 'Ͼ', + 'ͽ' => 'Ͽ', + 'ά' => 'Ά', + 'έ' => 'Έ', + 'ή' => 'Ή', + 'ί' => 'Ί', + 'α' => 'Α', + 'β' => 'Β', + 'γ' => 'Γ', + 'δ' => 'Δ', + 'ε' => 'Ε', + 'ζ' => 'Ζ', + 'η' => 'Η', + 'θ' => 'Θ', + 'ι' => 'Ι', + 'κ' => 'Κ', + 'λ' => 'Λ', + 'μ' => 'Μ', + 'ν' => 'Ν', + 'ξ' => 'Ξ', + 'ο' => 'Ο', + 'π' => 'Π', + 'ρ' => 'Ρ', + 'ς' => 'Σ', + 'σ' => 'Σ', + 'τ' => 'Τ', + 'υ' => 'Υ', + 'φ' => 'Φ', + 'χ' => 'Χ', + 'ψ' => 'Ψ', + 'ω' => 'Ω', + 'ϊ' => 'Ϊ', + 'ϋ' => 'Ϋ', + 'ό' => 'Ό', + 'ύ' => 'Ύ', + 'ώ' => 'Ώ', + 'ϐ' => 'Β', + 'ϑ' => 'Θ', + 'ϕ' => 'Φ', + 'ϖ' => 'Π', + 'ϗ' => 'Ϗ', + 'ϙ' => 'Ϙ', + 'ϛ' => 'Ϛ', + 'ϝ' => 'Ϝ', + 'ϟ' => 'Ϟ', + 'ϡ' => 'Ϡ', + 'ϣ' => 'Ϣ', + 'ϥ' => 'Ϥ', + 'ϧ' => 'Ϧ', + 'ϩ' => 'Ϩ', + 'ϫ' => 'Ϫ', + 'ϭ' => 'Ϭ', + 'ϯ' => 'Ϯ', + 'ϰ' => 'Κ', + 'ϱ' => 'Ρ', + 'ϲ' => 'Ϲ', + 'ϳ' => 'Ϳ', + 'ϵ' => 'Ε', + 'ϸ' => 'Ϸ', + 'ϻ' => 'Ϻ', + 'а' => 'А', + 'б' => 'Б', + 'в' => 'В', + 'г' => 'Г', + 'д' => 'Д', + 'е' => 'Е', + 'ж' => 'Ж', + 'з' => 'З', + 'и' => 'И', + 'й' => 'Й', + 'к' => 'К', + 'л' => 'Л', + 'м' => 'М', + 'н' => 'Н', + 'о' => 'О', + 'п' => 'П', + 'р' => 'Р', + 'с' => 'С', + 'т' => 'Т', + 'у' => 'У', + 'ф' => 'Ф', + 'х' => 'Х', + 'ц' => 'Ц', + 'ч' => 'Ч', + 'ш' => 'Ш', + 'щ' => 'Щ', + 'ъ' => 'Ъ', + 'ы' => 'Ы', + 'ь' => 'Ь', + 'э' => 'Э', + 'ю' => 'Ю', + 'я' => 'Я', + 'ѐ' => 'Ѐ', + 'ё' => 'Ё', + 'ђ' => 'Ђ', + 'ѓ' => 'Ѓ', + 'є' => 'Є', + 'ѕ' => 'Ѕ', + 'і' => 'І', + 'ї' => 'Ї', + 'ј' => 'Ј', + 'љ' => 'Љ', + 'њ' => 'Њ', + 'ћ' => 'Ћ', + 'ќ' => 'Ќ', + 'ѝ' => 'Ѝ', + 'ў' => 'Ў', + 'џ' => 'Џ', + 'ѡ' => 'Ѡ', + 'ѣ' => 'Ѣ', + 'ѥ' => 'Ѥ', + 'ѧ' => 'Ѧ', + 'ѩ' => 'Ѩ', + 'ѫ' => 'Ѫ', + 'ѭ' => 'Ѭ', + 'ѯ' => 'Ѯ', + 'ѱ' => 'Ѱ', + 'ѳ' => 'Ѳ', + 'ѵ' => 'Ѵ', + 'ѷ' => 'Ѷ', + 'ѹ' => 'Ѹ', + 'ѻ' => 'Ѻ', + 'ѽ' => 'Ѽ', + 'ѿ' => 'Ѿ', + 'ҁ' => 'Ҁ', + 'ҋ' => 'Ҋ', + 'ҍ' => 'Ҍ', + 'ҏ' => 'Ҏ', + 'ґ' => 'Ґ', + 'ғ' => 'Ғ', + 'ҕ' => 'Ҕ', + 'җ' => 'Җ', + 'ҙ' => 'Ҙ', + 'қ' => 'Қ', + 'ҝ' => 'Ҝ', + 'ҟ' => 'Ҟ', + 'ҡ' => 'Ҡ', + 'ң' => 'Ң', + 'ҥ' => 'Ҥ', + 'ҧ' => 'Ҧ', + 'ҩ' => 'Ҩ', + 'ҫ' => 'Ҫ', + 'ҭ' => 'Ҭ', + 'ү' => 'Ү', + 'ұ' => 'Ұ', + 'ҳ' => 'Ҳ', + 'ҵ' => 'Ҵ', + 'ҷ' => 'Ҷ', + 'ҹ' => 'Ҹ', + 'һ' => 'Һ', + 'ҽ' => 'Ҽ', + 'ҿ' => 'Ҿ', + 'ӂ' => 'Ӂ', + 'ӄ' => 'Ӄ', + 'ӆ' => 'Ӆ', + 'ӈ' => 'Ӈ', + 'ӊ' => 'Ӊ', + 'ӌ' => 'Ӌ', + 'ӎ' => 'Ӎ', + 'ӏ' => 'Ӏ', + 'ӑ' => 'Ӑ', + 'ӓ' => 'Ӓ', + 'ӕ' => 'Ӕ', + 'ӗ' => 'Ӗ', + 'ә' => 'Ә', + 'ӛ' => 'Ӛ', + 'ӝ' => 'Ӝ', + 'ӟ' => 'Ӟ', + 'ӡ' => 'Ӡ', + 'ӣ' => 'Ӣ', + 'ӥ' => 'Ӥ', + 'ӧ' => 'Ӧ', + 'ө' => 'Ө', + 'ӫ' => 'Ӫ', + 'ӭ' => 'Ӭ', + 'ӯ' => 'Ӯ', + 'ӱ' => 'Ӱ', + 'ӳ' => 'Ӳ', + 'ӵ' => 'Ӵ', + 'ӷ' => 'Ӷ', + 'ӹ' => 'Ӹ', + 'ӻ' => 'Ӻ', + 'ӽ' => 'Ӽ', + 'ӿ' => 'Ӿ', + 'ԁ' => 'Ԁ', + 'ԃ' => 'Ԃ', + 'ԅ' => 'Ԅ', + 'ԇ' => 'Ԇ', + 'ԉ' => 'Ԉ', + 'ԋ' => 'Ԋ', + 'ԍ' => 'Ԍ', + 'ԏ' => 'Ԏ', + 'ԑ' => 'Ԑ', + 'ԓ' => 'Ԓ', + 'ԕ' => 'Ԕ', + 'ԗ' => 'Ԗ', + 'ԙ' => 'Ԙ', + 'ԛ' => 'Ԛ', + 'ԝ' => 'Ԝ', + 'ԟ' => 'Ԟ', + 'ԡ' => 'Ԡ', + 'ԣ' => 'Ԣ', + 'ԥ' => 'Ԥ', + 'ԧ' => 'Ԧ', + 'ԩ' => 'Ԩ', + 'ԫ' => 'Ԫ', + 'ԭ' => 'Ԭ', + 'ԯ' => 'Ԯ', + 'ա' => 'Ա', + 'բ' => 'Բ', + 'գ' => 'Գ', + 'դ' => 'Դ', + 'ե' => 'Ե', + 'զ' => 'Զ', + 'է' => 'Է', + 'ը' => 'Ը', + 'թ' => 'Թ', + 'ժ' => 'Ժ', + 'ի' => 'Ի', + 'լ' => 'Լ', + 'խ' => 'Խ', + 'ծ' => 'Ծ', + 'կ' => 'Կ', + 'հ' => 'Հ', + 'ձ' => 'Ձ', + 'ղ' => 'Ղ', + 'ճ' => 'Ճ', + 'մ' => 'Մ', + 'յ' => 'Յ', + 'ն' => 'Ն', + 'շ' => 'Շ', + 'ո' => 'Ո', + 'չ' => 'Չ', + 'պ' => 'Պ', + 'ջ' => 'Ջ', + 'ռ' => 'Ռ', + 'ս' => 'Ս', + 'վ' => 'Վ', + 'տ' => 'Տ', + 'ր' => 'Ր', + 'ց' => 'Ց', + 'ւ' => 'Ւ', + 'փ' => 'Փ', + 'ք' => 'Ք', + 'օ' => 'Օ', + 'ֆ' => 'Ֆ', + 'ა' => 'Ა', + 'ბ' => 'Ბ', + 'გ' => 'Გ', + 'დ' => 'Დ', + 'ე' => 'Ე', + 'ვ' => 'Ვ', + 'ზ' => 'Ზ', + 'თ' => 'Თ', + 'ი' => 'Ი', + 'კ' => 'Კ', + 'ლ' => 'Ლ', + 'მ' => 'Მ', + 'ნ' => 'Ნ', + 'ო' => 'Ო', + 'პ' => 'Პ', + 'ჟ' => 'Ჟ', + 'რ' => 'Რ', + 'ს' => 'Ს', + 'ტ' => 'Ტ', + 'უ' => 'Უ', + 'ფ' => 'Ფ', + 'ქ' => 'Ქ', + 'ღ' => 'Ღ', + 'ყ' => 'Ყ', + 'შ' => 'Შ', + 'ჩ' => 'Ჩ', + 'ც' => 'Ც', + 'ძ' => 'Ძ', + 'წ' => 'Წ', + 'ჭ' => 'Ჭ', + 'ხ' => 'Ხ', + 'ჯ' => 'Ჯ', + 'ჰ' => 'Ჰ', + 'ჱ' => 'Ჱ', + 'ჲ' => 'Ჲ', + 'ჳ' => 'Ჳ', + 'ჴ' => 'Ჴ', + 'ჵ' => 'Ჵ', + 'ჶ' => 'Ჶ', + 'ჷ' => 'Ჷ', + 'ჸ' => 'Ჸ', + 'ჹ' => 'Ჹ', + 'ჺ' => 'Ჺ', + 'ჽ' => 'Ჽ', + 'ჾ' => 'Ჾ', + 'ჿ' => 'Ჿ', + 'ᏸ' => 'Ᏸ', + 'ᏹ' => 'Ᏹ', + 'ᏺ' => 'Ᏺ', + 'ᏻ' => 'Ᏻ', + 'ᏼ' => 'Ᏼ', + 'ᏽ' => 'Ᏽ', + 'ᲀ' => 'В', + 'ᲁ' => 'Д', + 'ᲂ' => 'О', + 'ᲃ' => 'С', + 'ᲄ' => 'Т', + 'ᲅ' => 'Т', + 'ᲆ' => 'Ъ', + 'ᲇ' => 'Ѣ', + 'ᲈ' => 'Ꙋ', + 'ᵹ' => 'Ᵹ', + 'ᵽ' => 'Ᵽ', + 'ᶎ' => 'Ᶎ', + 'ḁ' => 'Ḁ', + 'ḃ' => 'Ḃ', + 'ḅ' => 'Ḅ', + 'ḇ' => 'Ḇ', + 'ḉ' => 'Ḉ', + 'ḋ' => 'Ḋ', + 'ḍ' => 'Ḍ', + 'ḏ' => 'Ḏ', + 'ḑ' => 'Ḑ', + 'ḓ' => 'Ḓ', + 'ḕ' => 'Ḕ', + 'ḗ' => 'Ḗ', + 'ḙ' => 'Ḙ', + 'ḛ' => 'Ḛ', + 'ḝ' => 'Ḝ', + 'ḟ' => 'Ḟ', + 'ḡ' => 'Ḡ', + 'ḣ' => 'Ḣ', + 'ḥ' => 'Ḥ', + 'ḧ' => 'Ḧ', + 'ḩ' => 'Ḩ', + 'ḫ' => 'Ḫ', + 'ḭ' => 'Ḭ', + 'ḯ' => 'Ḯ', + 'ḱ' => 'Ḱ', + 'ḳ' => 'Ḳ', + 'ḵ' => 'Ḵ', + 'ḷ' => 'Ḷ', + 'ḹ' => 'Ḹ', + 'ḻ' => 'Ḻ', + 'ḽ' => 'Ḽ', + 'ḿ' => 'Ḿ', + 'ṁ' => 'Ṁ', + 'ṃ' => 'Ṃ', + 'ṅ' => 'Ṅ', + 'ṇ' => 'Ṇ', + 'ṉ' => 'Ṉ', + 'ṋ' => 'Ṋ', + 'ṍ' => 'Ṍ', + 'ṏ' => 'Ṏ', + 'ṑ' => 'Ṑ', + 'ṓ' => 'Ṓ', + 'ṕ' => 'Ṕ', + 'ṗ' => 'Ṗ', + 'ṙ' => 'Ṙ', + 'ṛ' => 'Ṛ', + 'ṝ' => 'Ṝ', + 'ṟ' => 'Ṟ', + 'ṡ' => 'Ṡ', + 'ṣ' => 'Ṣ', + 'ṥ' => 'Ṥ', + 'ṧ' => 'Ṧ', + 'ṩ' => 'Ṩ', + 'ṫ' => 'Ṫ', + 'ṭ' => 'Ṭ', + 'ṯ' => 'Ṯ', + 'ṱ' => 'Ṱ', + 'ṳ' => 'Ṳ', + 'ṵ' => 'Ṵ', + 'ṷ' => 'Ṷ', + 'ṹ' => 'Ṹ', + 'ṻ' => 'Ṻ', + 'ṽ' => 'Ṽ', + 'ṿ' => 'Ṿ', + 'ẁ' => 'Ẁ', + 'ẃ' => 'Ẃ', + 'ẅ' => 'Ẅ', + 'ẇ' => 'Ẇ', + 'ẉ' => 'Ẉ', + 'ẋ' => 'Ẋ', + 'ẍ' => 'Ẍ', + 'ẏ' => 'Ẏ', + 'ẑ' => 'Ẑ', + 'ẓ' => 'Ẓ', + 'ẕ' => 'Ẕ', + 'ẛ' => 'Ṡ', + 'ạ' => 'Ạ', + 'ả' => 'Ả', + 'ấ' => 'Ấ', + 'ầ' => 'Ầ', + 'ẩ' => 'Ẩ', + 'ẫ' => 'Ẫ', + 'ậ' => 'Ậ', + 'ắ' => 'Ắ', + 'ằ' => 'Ằ', + 'ẳ' => 'Ẳ', + 'ẵ' => 'Ẵ', + 'ặ' => 'Ặ', + 'ẹ' => 'Ẹ', + 'ẻ' => 'Ẻ', + 'ẽ' => 'Ẽ', + 'ế' => 'Ế', + 'ề' => 'Ề', + 'ể' => 'Ể', + 'ễ' => 'Ễ', + 'ệ' => 'Ệ', + 'ỉ' => 'Ỉ', + 'ị' => 'Ị', + 'ọ' => 'Ọ', + 'ỏ' => 'Ỏ', + 'ố' => 'Ố', + 'ồ' => 'Ồ', + 'ổ' => 'Ổ', + 'ỗ' => 'Ỗ', + 'ộ' => 'Ộ', + 'ớ' => 'Ớ', + 'ờ' => 'Ờ', + 'ở' => 'Ở', + 'ỡ' => 'Ỡ', + 'ợ' => 'Ợ', + 'ụ' => 'Ụ', + 'ủ' => 'Ủ', + 'ứ' => 'Ứ', + 'ừ' => 'Ừ', + 'ử' => 'Ử', + 'ữ' => 'Ữ', + 'ự' => 'Ự', + 'ỳ' => 'Ỳ', + 'ỵ' => 'Ỵ', + 'ỷ' => 'Ỷ', + 'ỹ' => 'Ỹ', + 'ỻ' => 'Ỻ', + 'ỽ' => 'Ỽ', + 'ỿ' => 'Ỿ', + 'ἀ' => 'Ἀ', + 'ἁ' => 'Ἁ', + 'ἂ' => 'Ἂ', + 'ἃ' => 'Ἃ', + 'ἄ' => 'Ἄ', + 'ἅ' => 'Ἅ', + 'ἆ' => 'Ἆ', + 'ἇ' => 'Ἇ', + 'ἐ' => 'Ἐ', + 'ἑ' => 'Ἑ', + 'ἒ' => 'Ἒ', + 'ἓ' => 'Ἓ', + 'ἔ' => 'Ἔ', + 'ἕ' => 'Ἕ', + 'ἠ' => 'Ἠ', + 'ἡ' => 'Ἡ', + 'ἢ' => 'Ἢ', + 'ἣ' => 'Ἣ', + 'ἤ' => 'Ἤ', + 'ἥ' => 'Ἥ', + 'ἦ' => 'Ἦ', + 'ἧ' => 'Ἧ', + 'ἰ' => 'Ἰ', + 'ἱ' => 'Ἱ', + 'ἲ' => 'Ἲ', + 'ἳ' => 'Ἳ', + 'ἴ' => 'Ἴ', + 'ἵ' => 'Ἵ', + 'ἶ' => 'Ἶ', + 'ἷ' => 'Ἷ', + 'ὀ' => 'Ὀ', + 'ὁ' => 'Ὁ', + 'ὂ' => 'Ὂ', + 'ὃ' => 'Ὃ', + 'ὄ' => 'Ὄ', + 'ὅ' => 'Ὅ', + 'ὑ' => 'Ὑ', + 'ὓ' => 'Ὓ', + 'ὕ' => 'Ὕ', + 'ὗ' => 'Ὗ', + 'ὠ' => 'Ὠ', + 'ὡ' => 'Ὡ', + 'ὢ' => 'Ὢ', + 'ὣ' => 'Ὣ', + 'ὤ' => 'Ὤ', + 'ὥ' => 'Ὥ', + 'ὦ' => 'Ὦ', + 'ὧ' => 'Ὧ', + 'ὰ' => 'Ὰ', + 'ά' => 'Ά', + 'ὲ' => 'Ὲ', + 'έ' => 'Έ', + 'ὴ' => 'Ὴ', + 'ή' => 'Ή', + 'ὶ' => 'Ὶ', + 'ί' => 'Ί', + 'ὸ' => 'Ὸ', + 'ό' => 'Ό', + 'ὺ' => 'Ὺ', + 'ύ' => 'Ύ', + 'ὼ' => 'Ὼ', + 'ώ' => 'Ώ', + 'ᾀ' => 'ἈΙ', + 'ᾁ' => 'ἉΙ', + 'ᾂ' => 'ἊΙ', + 'ᾃ' => 'ἋΙ', + 'ᾄ' => 'ἌΙ', + 'ᾅ' => 'ἍΙ', + 'ᾆ' => 'ἎΙ', + 'ᾇ' => 'ἏΙ', + 'ᾐ' => 'ἨΙ', + 'ᾑ' => 'ἩΙ', + 'ᾒ' => 'ἪΙ', + 'ᾓ' => 'ἫΙ', + 'ᾔ' => 'ἬΙ', + 'ᾕ' => 'ἭΙ', + 'ᾖ' => 'ἮΙ', + 'ᾗ' => 'ἯΙ', + 'ᾠ' => 'ὨΙ', + 'ᾡ' => 'ὩΙ', + 'ᾢ' => 'ὪΙ', + 'ᾣ' => 'ὫΙ', + 'ᾤ' => 'ὬΙ', + 'ᾥ' => 'ὭΙ', + 'ᾦ' => 'ὮΙ', + 'ᾧ' => 'ὯΙ', + 'ᾰ' => 'Ᾰ', + 'ᾱ' => 'Ᾱ', + 'ᾳ' => 'ΑΙ', + 'ι' => 'Ι', + 'ῃ' => 'ΗΙ', + 'ῐ' => 'Ῐ', + 'ῑ' => 'Ῑ', + 'ῠ' => 'Ῠ', + 'ῡ' => 'Ῡ', + 'ῥ' => 'Ῥ', + 'ῳ' => 'ΩΙ', + 'ⅎ' => 'Ⅎ', + 'ⅰ' => 'Ⅰ', + 'ⅱ' => 'Ⅱ', + 'ⅲ' => 'Ⅲ', + 'ⅳ' => 'Ⅳ', + 'ⅴ' => 'Ⅴ', + 'ⅵ' => 'Ⅵ', + 'ⅶ' => 'Ⅶ', + 'ⅷ' => 'Ⅷ', + 'ⅸ' => 'Ⅸ', + 'ⅹ' => 'Ⅹ', + 'ⅺ' => 'Ⅺ', + 'ⅻ' => 'Ⅻ', + 'ⅼ' => 'Ⅼ', + 'ⅽ' => 'Ⅽ', + 'ⅾ' => 'Ⅾ', + 'ⅿ' => 'Ⅿ', + 'ↄ' => 'Ↄ', + 'ⓐ' => 'Ⓐ', + 'ⓑ' => 'Ⓑ', + 'ⓒ' => 'Ⓒ', + 'ⓓ' => 'Ⓓ', + 'ⓔ' => 'Ⓔ', + 'ⓕ' => 'Ⓕ', + 'ⓖ' => 'Ⓖ', + 'ⓗ' => 'Ⓗ', + 'ⓘ' => 'Ⓘ', + 'ⓙ' => 'Ⓙ', + 'ⓚ' => 'Ⓚ', + 'ⓛ' => 'Ⓛ', + 'ⓜ' => 'Ⓜ', + 'ⓝ' => 'Ⓝ', + 'ⓞ' => 'Ⓞ', + 'ⓟ' => 'Ⓟ', + 'ⓠ' => 'Ⓠ', + 'ⓡ' => 'Ⓡ', + 'ⓢ' => 'Ⓢ', + 'ⓣ' => 'Ⓣ', + 'ⓤ' => 'Ⓤ', + 'ⓥ' => 'Ⓥ', + 'ⓦ' => 'Ⓦ', + 'ⓧ' => 'Ⓧ', + 'ⓨ' => 'Ⓨ', + 'ⓩ' => 'Ⓩ', + 'ⰰ' => 'Ⰰ', + 'ⰱ' => 'Ⰱ', + 'ⰲ' => 'Ⰲ', + 'ⰳ' => 'Ⰳ', + 'ⰴ' => 'Ⰴ', + 'ⰵ' => 'Ⰵ', + 'ⰶ' => 'Ⰶ', + 'ⰷ' => 'Ⰷ', + 'ⰸ' => 'Ⰸ', + 'ⰹ' => 'Ⰹ', + 'ⰺ' => 'Ⰺ', + 'ⰻ' => 'Ⰻ', + 'ⰼ' => 'Ⰼ', + 'ⰽ' => 'Ⰽ', + 'ⰾ' => 'Ⰾ', + 'ⰿ' => 'Ⰿ', + 'ⱀ' => 'Ⱀ', + 'ⱁ' => 'Ⱁ', + 'ⱂ' => 'Ⱂ', + 'ⱃ' => 'Ⱃ', + 'ⱄ' => 'Ⱄ', + 'ⱅ' => 'Ⱅ', + 'ⱆ' => 'Ⱆ', + 'ⱇ' => 'Ⱇ', + 'ⱈ' => 'Ⱈ', + 'ⱉ' => 'Ⱉ', + 'ⱊ' => 'Ⱊ', + 'ⱋ' => 'Ⱋ', + 'ⱌ' => 'Ⱌ', + 'ⱍ' => 'Ⱍ', + 'ⱎ' => 'Ⱎ', + 'ⱏ' => 'Ⱏ', + 'ⱐ' => 'Ⱐ', + 'ⱑ' => 'Ⱑ', + 'ⱒ' => 'Ⱒ', + 'ⱓ' => 'Ⱓ', + 'ⱔ' => 'Ⱔ', + 'ⱕ' => 'Ⱕ', + 'ⱖ' => 'Ⱖ', + 'ⱗ' => 'Ⱗ', + 'ⱘ' => 'Ⱘ', + 'ⱙ' => 'Ⱙ', + 'ⱚ' => 'Ⱚ', + 'ⱛ' => 'Ⱛ', + 'ⱜ' => 'Ⱜ', + 'ⱝ' => 'Ⱝ', + 'ⱞ' => 'Ⱞ', + 'ⱡ' => 'Ⱡ', + 'ⱥ' => 'Ⱥ', + 'ⱦ' => 'Ⱦ', + 'ⱨ' => 'Ⱨ', + 'ⱪ' => 'Ⱪ', + 'ⱬ' => 'Ⱬ', + 'ⱳ' => 'Ⱳ', + 'ⱶ' => 'Ⱶ', + 'ⲁ' => 'Ⲁ', + 'ⲃ' => 'Ⲃ', + 'ⲅ' => 'Ⲅ', + 'ⲇ' => 'Ⲇ', + 'ⲉ' => 'Ⲉ', + 'ⲋ' => 'Ⲋ', + 'ⲍ' => 'Ⲍ', + 'ⲏ' => 'Ⲏ', + 'ⲑ' => 'Ⲑ', + 'ⲓ' => 'Ⲓ', + 'ⲕ' => 'Ⲕ', + 'ⲗ' => 'Ⲗ', + 'ⲙ' => 'Ⲙ', + 'ⲛ' => 'Ⲛ', + 'ⲝ' => 'Ⲝ', + 'ⲟ' => 'Ⲟ', + 'ⲡ' => 'Ⲡ', + 'ⲣ' => 'Ⲣ', + 'ⲥ' => 'Ⲥ', + 'ⲧ' => 'Ⲧ', + 'ⲩ' => 'Ⲩ', + 'ⲫ' => 'Ⲫ', + 'ⲭ' => 'Ⲭ', + 'ⲯ' => 'Ⲯ', + 'ⲱ' => 'Ⲱ', + 'ⲳ' => 'Ⲳ', + 'ⲵ' => 'Ⲵ', + 'ⲷ' => 'Ⲷ', + 'ⲹ' => 'Ⲹ', + 'ⲻ' => 'Ⲻ', + 'ⲽ' => 'Ⲽ', + 'ⲿ' => 'Ⲿ', + 'ⳁ' => 'Ⳁ', + 'ⳃ' => 'Ⳃ', + 'ⳅ' => 'Ⳅ', + 'ⳇ' => 'Ⳇ', + 'ⳉ' => 'Ⳉ', + 'ⳋ' => 'Ⳋ', + 'ⳍ' => 'Ⳍ', + 'ⳏ' => 'Ⳏ', + 'ⳑ' => 'Ⳑ', + 'ⳓ' => 'Ⳓ', + 'ⳕ' => 'Ⳕ', + 'ⳗ' => 'Ⳗ', + 'ⳙ' => 'Ⳙ', + 'ⳛ' => 'Ⳛ', + 'ⳝ' => 'Ⳝ', + 'ⳟ' => 'Ⳟ', + 'ⳡ' => 'Ⳡ', + 'ⳣ' => 'Ⳣ', + 'ⳬ' => 'Ⳬ', + 'ⳮ' => 'Ⳮ', + 'ⳳ' => 'Ⳳ', + 'ⴀ' => 'Ⴀ', + 'ⴁ' => 'Ⴁ', + 'ⴂ' => 'Ⴂ', + 'ⴃ' => 'Ⴃ', + 'ⴄ' => 'Ⴄ', + 'ⴅ' => 'Ⴅ', + 'ⴆ' => 'Ⴆ', + 'ⴇ' => 'Ⴇ', + 'ⴈ' => 'Ⴈ', + 'ⴉ' => 'Ⴉ', + 'ⴊ' => 'Ⴊ', + 'ⴋ' => 'Ⴋ', + 'ⴌ' => 'Ⴌ', + 'ⴍ' => 'Ⴍ', + 'ⴎ' => 'Ⴎ', + 'ⴏ' => 'Ⴏ', + 'ⴐ' => 'Ⴐ', + 'ⴑ' => 'Ⴑ', + 'ⴒ' => 'Ⴒ', + 'ⴓ' => 'Ⴓ', + 'ⴔ' => 'Ⴔ', + 'ⴕ' => 'Ⴕ', + 'ⴖ' => 'Ⴖ', + 'ⴗ' => 'Ⴗ', + 'ⴘ' => 'Ⴘ', + 'ⴙ' => 'Ⴙ', + 'ⴚ' => 'Ⴚ', + 'ⴛ' => 'Ⴛ', + 'ⴜ' => 'Ⴜ', + 'ⴝ' => 'Ⴝ', + 'ⴞ' => 'Ⴞ', + 'ⴟ' => 'Ⴟ', + 'ⴠ' => 'Ⴠ', + 'ⴡ' => 'Ⴡ', + 'ⴢ' => 'Ⴢ', + 'ⴣ' => 'Ⴣ', + 'ⴤ' => 'Ⴤ', + 'ⴥ' => 'Ⴥ', + 'ⴧ' => 'Ⴧ', + 'ⴭ' => 'Ⴭ', + 'ꙁ' => 'Ꙁ', + 'ꙃ' => 'Ꙃ', + 'ꙅ' => 'Ꙅ', + 'ꙇ' => 'Ꙇ', + 'ꙉ' => 'Ꙉ', + 'ꙋ' => 'Ꙋ', + 'ꙍ' => 'Ꙍ', + 'ꙏ' => 'Ꙏ', + 'ꙑ' => 'Ꙑ', + 'ꙓ' => 'Ꙓ', + 'ꙕ' => 'Ꙕ', + 'ꙗ' => 'Ꙗ', + 'ꙙ' => 'Ꙙ', + 'ꙛ' => 'Ꙛ', + 'ꙝ' => 'Ꙝ', + 'ꙟ' => 'Ꙟ', + 'ꙡ' => 'Ꙡ', + 'ꙣ' => 'Ꙣ', + 'ꙥ' => 'Ꙥ', + 'ꙧ' => 'Ꙧ', + 'ꙩ' => 'Ꙩ', + 'ꙫ' => 'Ꙫ', + 'ꙭ' => 'Ꙭ', + 'ꚁ' => 'Ꚁ', + 'ꚃ' => 'Ꚃ', + 'ꚅ' => 'Ꚅ', + 'ꚇ' => 'Ꚇ', + 'ꚉ' => 'Ꚉ', + 'ꚋ' => 'Ꚋ', + 'ꚍ' => 'Ꚍ', + 'ꚏ' => 'Ꚏ', + 'ꚑ' => 'Ꚑ', + 'ꚓ' => 'Ꚓ', + 'ꚕ' => 'Ꚕ', + 'ꚗ' => 'Ꚗ', + 'ꚙ' => 'Ꚙ', + 'ꚛ' => 'Ꚛ', + 'ꜣ' => 'Ꜣ', + 'ꜥ' => 'Ꜥ', + 'ꜧ' => 'Ꜧ', + 'ꜩ' => 'Ꜩ', + 'ꜫ' => 'Ꜫ', + 'ꜭ' => 'Ꜭ', + 'ꜯ' => 'Ꜯ', + 'ꜳ' => 'Ꜳ', + 'ꜵ' => 'Ꜵ', + 'ꜷ' => 'Ꜷ', + 'ꜹ' => 'Ꜹ', + 'ꜻ' => 'Ꜻ', + 'ꜽ' => 'Ꜽ', + 'ꜿ' => 'Ꜿ', + 'ꝁ' => 'Ꝁ', + 'ꝃ' => 'Ꝃ', + 'ꝅ' => 'Ꝅ', + 'ꝇ' => 'Ꝇ', + 'ꝉ' => 'Ꝉ', + 'ꝋ' => 'Ꝋ', + 'ꝍ' => 'Ꝍ', + 'ꝏ' => 'Ꝏ', + 'ꝑ' => 'Ꝑ', + 'ꝓ' => 'Ꝓ', + 'ꝕ' => 'Ꝕ', + 'ꝗ' => 'Ꝗ', + 'ꝙ' => 'Ꝙ', + 'ꝛ' => 'Ꝛ', + 'ꝝ' => 'Ꝝ', + 'ꝟ' => 'Ꝟ', + 'ꝡ' => 'Ꝡ', + 'ꝣ' => 'Ꝣ', + 'ꝥ' => 'Ꝥ', + 'ꝧ' => 'Ꝧ', + 'ꝩ' => 'Ꝩ', + 'ꝫ' => 'Ꝫ', + 'ꝭ' => 'Ꝭ', + 'ꝯ' => 'Ꝯ', + 'ꝺ' => 'Ꝺ', + 'ꝼ' => 'Ꝼ', + 'ꝿ' => 'Ꝿ', + 'ꞁ' => 'Ꞁ', + 'ꞃ' => 'Ꞃ', + 'ꞅ' => 'Ꞅ', + 'ꞇ' => 'Ꞇ', + 'ꞌ' => 'Ꞌ', + 'ꞑ' => 'Ꞑ', + 'ꞓ' => 'Ꞓ', + 'ꞔ' => 'Ꞔ', + 'ꞗ' => 'Ꞗ', + 'ꞙ' => 'Ꞙ', + 'ꞛ' => 'Ꞛ', + 'ꞝ' => 'Ꞝ', + 'ꞟ' => 'Ꞟ', + 'ꞡ' => 'Ꞡ', + 'ꞣ' => 'Ꞣ', + 'ꞥ' => 'Ꞥ', + 'ꞧ' => 'Ꞧ', + 'ꞩ' => 'Ꞩ', + 'ꞵ' => 'Ꞵ', + 'ꞷ' => 'Ꞷ', + 'ꞹ' => 'Ꞹ', + 'ꞻ' => 'Ꞻ', + 'ꞽ' => 'Ꞽ', + 'ꞿ' => 'Ꞿ', + 'ꟃ' => 'Ꟃ', + 'ꟈ' => 'Ꟈ', + 'ꟊ' => 'Ꟊ', + 'ꟶ' => 'Ꟶ', + 'ꭓ' => 'Ꭓ', + 'ꭰ' => 'Ꭰ', + 'ꭱ' => 'Ꭱ', + 'ꭲ' => 'Ꭲ', + 'ꭳ' => 'Ꭳ', + 'ꭴ' => 'Ꭴ', + 'ꭵ' => 'Ꭵ', + 'ꭶ' => 'Ꭶ', + 'ꭷ' => 'Ꭷ', + 'ꭸ' => 'Ꭸ', + 'ꭹ' => 'Ꭹ', + 'ꭺ' => 'Ꭺ', + 'ꭻ' => 'Ꭻ', + 'ꭼ' => 'Ꭼ', + 'ꭽ' => 'Ꭽ', + 'ꭾ' => 'Ꭾ', + 'ꭿ' => 'Ꭿ', + 'ꮀ' => 'Ꮀ', + 'ꮁ' => 'Ꮁ', + 'ꮂ' => 'Ꮂ', + 'ꮃ' => 'Ꮃ', + 'ꮄ' => 'Ꮄ', + 'ꮅ' => 'Ꮅ', + 'ꮆ' => 'Ꮆ', + 'ꮇ' => 'Ꮇ', + 'ꮈ' => 'Ꮈ', + 'ꮉ' => 'Ꮉ', + 'ꮊ' => 'Ꮊ', + 'ꮋ' => 'Ꮋ', + 'ꮌ' => 'Ꮌ', + 'ꮍ' => 'Ꮍ', + 'ꮎ' => 'Ꮎ', + 'ꮏ' => 'Ꮏ', + 'ꮐ' => 'Ꮐ', + 'ꮑ' => 'Ꮑ', + 'ꮒ' => 'Ꮒ', + 'ꮓ' => 'Ꮓ', + 'ꮔ' => 'Ꮔ', + 'ꮕ' => 'Ꮕ', + 'ꮖ' => 'Ꮖ', + 'ꮗ' => 'Ꮗ', + 'ꮘ' => 'Ꮘ', + 'ꮙ' => 'Ꮙ', + 'ꮚ' => 'Ꮚ', + 'ꮛ' => 'Ꮛ', + 'ꮜ' => 'Ꮜ', + 'ꮝ' => 'Ꮝ', + 'ꮞ' => 'Ꮞ', + 'ꮟ' => 'Ꮟ', + 'ꮠ' => 'Ꮠ', + 'ꮡ' => 'Ꮡ', + 'ꮢ' => 'Ꮢ', + 'ꮣ' => 'Ꮣ', + 'ꮤ' => 'Ꮤ', + 'ꮥ' => 'Ꮥ', + 'ꮦ' => 'Ꮦ', + 'ꮧ' => 'Ꮧ', + 'ꮨ' => 'Ꮨ', + 'ꮩ' => 'Ꮩ', + 'ꮪ' => 'Ꮪ', + 'ꮫ' => 'Ꮫ', + 'ꮬ' => 'Ꮬ', + 'ꮭ' => 'Ꮭ', + 'ꮮ' => 'Ꮮ', + 'ꮯ' => 'Ꮯ', + 'ꮰ' => 'Ꮰ', + 'ꮱ' => 'Ꮱ', + 'ꮲ' => 'Ꮲ', + 'ꮳ' => 'Ꮳ', + 'ꮴ' => 'Ꮴ', + 'ꮵ' => 'Ꮵ', + 'ꮶ' => 'Ꮶ', + 'ꮷ' => 'Ꮷ', + 'ꮸ' => 'Ꮸ', + 'ꮹ' => 'Ꮹ', + 'ꮺ' => 'Ꮺ', + 'ꮻ' => 'Ꮻ', + 'ꮼ' => 'Ꮼ', + 'ꮽ' => 'Ꮽ', + 'ꮾ' => 'Ꮾ', + 'ꮿ' => 'Ꮿ', + 'a' => 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + '𐐨' => '𐐀', + '𐐩' => '𐐁', + '𐐪' => '𐐂', + '𐐫' => '𐐃', + '𐐬' => '𐐄', + '𐐭' => '𐐅', + '𐐮' => '𐐆', + '𐐯' => '𐐇', + '𐐰' => '𐐈', + '𐐱' => '𐐉', + '𐐲' => '𐐊', + '𐐳' => '𐐋', + '𐐴' => '𐐌', + '𐐵' => '𐐍', + '𐐶' => '𐐎', + '𐐷' => '𐐏', + '𐐸' => '𐐐', + '𐐹' => '𐐑', + '𐐺' => '𐐒', + '𐐻' => '𐐓', + '𐐼' => '𐐔', + '𐐽' => '𐐕', + '𐐾' => '𐐖', + '𐐿' => '𐐗', + '𐑀' => '𐐘', + '𐑁' => '𐐙', + '𐑂' => '𐐚', + '𐑃' => '𐐛', + '𐑄' => '𐐜', + '𐑅' => '𐐝', + '𐑆' => '𐐞', + '𐑇' => '𐐟', + '𐑈' => '𐐠', + '𐑉' => '𐐡', + '𐑊' => '𐐢', + '𐑋' => '𐐣', + '𐑌' => '𐐤', + '𐑍' => '𐐥', + '𐑎' => '𐐦', + '𐑏' => '𐐧', + '𐓘' => '𐒰', + '𐓙' => '𐒱', + '𐓚' => '𐒲', + '𐓛' => '𐒳', + '𐓜' => '𐒴', + '𐓝' => '𐒵', + '𐓞' => '𐒶', + '𐓟' => '𐒷', + '𐓠' => '𐒸', + '𐓡' => '𐒹', + '𐓢' => '𐒺', + '𐓣' => '𐒻', + '𐓤' => '𐒼', + '𐓥' => '𐒽', + '𐓦' => '𐒾', + '𐓧' => '𐒿', + '𐓨' => '𐓀', + '𐓩' => '𐓁', + '𐓪' => '𐓂', + '𐓫' => '𐓃', + '𐓬' => '𐓄', + '𐓭' => '𐓅', + '𐓮' => '𐓆', + '𐓯' => '𐓇', + '𐓰' => '𐓈', + '𐓱' => '𐓉', + '𐓲' => '𐓊', + '𐓳' => '𐓋', + '𐓴' => '𐓌', + '𐓵' => '𐓍', + '𐓶' => '𐓎', + '𐓷' => '𐓏', + '𐓸' => '𐓐', + '𐓹' => '𐓑', + '𐓺' => '𐓒', + '𐓻' => '𐓓', + '𐳀' => '𐲀', + '𐳁' => '𐲁', + '𐳂' => '𐲂', + '𐳃' => '𐲃', + '𐳄' => '𐲄', + '𐳅' => '𐲅', + '𐳆' => '𐲆', + '𐳇' => '𐲇', + '𐳈' => '𐲈', + '𐳉' => '𐲉', + '𐳊' => '𐲊', + '𐳋' => '𐲋', + '𐳌' => '𐲌', + '𐳍' => '𐲍', + '𐳎' => '𐲎', + '𐳏' => '𐲏', + '𐳐' => '𐲐', + '𐳑' => '𐲑', + '𐳒' => '𐲒', + '𐳓' => '𐲓', + '𐳔' => '𐲔', + '𐳕' => '𐲕', + '𐳖' => '𐲖', + '𐳗' => '𐲗', + '𐳘' => '𐲘', + '𐳙' => '𐲙', + '𐳚' => '𐲚', + '𐳛' => '𐲛', + '𐳜' => '𐲜', + '𐳝' => '𐲝', + '𐳞' => '𐲞', + '𐳟' => '𐲟', + '𐳠' => '𐲠', + '𐳡' => '𐲡', + '𐳢' => '𐲢', + '𐳣' => '𐲣', + '𐳤' => '𐲤', + '𐳥' => '𐲥', + '𐳦' => '𐲦', + '𐳧' => '𐲧', + '𐳨' => '𐲨', + '𐳩' => '𐲩', + '𐳪' => '𐲪', + '𐳫' => '𐲫', + '𐳬' => '𐲬', + '𐳭' => '𐲭', + '𐳮' => '𐲮', + '𐳯' => '𐲯', + '𐳰' => '𐲰', + '𐳱' => '𐲱', + '𐳲' => '𐲲', + '𑣀' => '𑢠', + '𑣁' => '𑢡', + '𑣂' => '𑢢', + '𑣃' => '𑢣', + '𑣄' => '𑢤', + '𑣅' => '𑢥', + '𑣆' => '𑢦', + '𑣇' => '𑢧', + '𑣈' => '𑢨', + '𑣉' => '𑢩', + '𑣊' => '𑢪', + '𑣋' => '𑢫', + '𑣌' => '𑢬', + '𑣍' => '𑢭', + '𑣎' => '𑢮', + '𑣏' => '𑢯', + '𑣐' => '𑢰', + '𑣑' => '𑢱', + '𑣒' => '𑢲', + '𑣓' => '𑢳', + '𑣔' => '𑢴', + '𑣕' => '𑢵', + '𑣖' => '𑢶', + '𑣗' => '𑢷', + '𑣘' => '𑢸', + '𑣙' => '𑢹', + '𑣚' => '𑢺', + '𑣛' => '𑢻', + '𑣜' => '𑢼', + '𑣝' => '𑢽', + '𑣞' => '𑢾', + '𑣟' => '𑢿', + '𖹠' => '𖹀', + '𖹡' => '𖹁', + '𖹢' => '𖹂', + '𖹣' => '𖹃', + '𖹤' => '𖹄', + '𖹥' => '𖹅', + '𖹦' => '𖹆', + '𖹧' => '𖹇', + '𖹨' => '𖹈', + '𖹩' => '𖹉', + '𖹪' => '𖹊', + '𖹫' => '𖹋', + '𖹬' => '𖹌', + '𖹭' => '𖹍', + '𖹮' => '𖹎', + '𖹯' => '𖹏', + '𖹰' => '𖹐', + '𖹱' => '𖹑', + '𖹲' => '𖹒', + '𖹳' => '𖹓', + '𖹴' => '𖹔', + '𖹵' => '𖹕', + '𖹶' => '𖹖', + '𖹷' => '𖹗', + '𖹸' => '𖹘', + '𖹹' => '𖹙', + '𖹺' => '𖹚', + '𖹻' => '𖹛', + '𖹼' => '𖹜', + '𖹽' => '𖹝', + '𖹾' => '𖹞', + '𖹿' => '𖹟', + '𞤢' => '𞤀', + '𞤣' => '𞤁', + '𞤤' => '𞤂', + '𞤥' => '𞤃', + '𞤦' => '𞤄', + '𞤧' => '𞤅', + '𞤨' => '𞤆', + '𞤩' => '𞤇', + '𞤪' => '𞤈', + '𞤫' => '𞤉', + '𞤬' => '𞤊', + '𞤭' => '𞤋', + '𞤮' => '𞤌', + '𞤯' => '𞤍', + '𞤰' => '𞤎', + '𞤱' => '𞤏', + '𞤲' => '𞤐', + '𞤳' => '𞤑', + '𞤴' => '𞤒', + '𞤵' => '𞤓', + '𞤶' => '𞤔', + '𞤷' => '𞤕', + '𞤸' => '𞤖', + '𞤹' => '𞤗', + '𞤺' => '𞤘', + '𞤻' => '𞤙', + '𞤼' => '𞤚', + '𞤽' => '𞤛', + '𞤾' => '𞤜', + '𞤿' => '𞤝', + '𞥀' => '𞤞', + '𞥁' => '𞤟', + '𞥂' => '𞤠', + '𞥃' => '𞤡', + 'ß' => 'SS', + 'ff' => 'FF', + 'fi' => 'FI', + 'fl' => 'FL', + 'ffi' => 'FFI', + 'ffl' => 'FFL', + 'ſt' => 'ST', + 'st' => 'ST', + 'և' => 'ԵՒ', + 'ﬓ' => 'ՄՆ', + 'ﬔ' => 'ՄԵ', + 'ﬕ' => 'ՄԻ', + 'ﬖ' => 'ՎՆ', + 'ﬗ' => 'ՄԽ', + 'ʼn' => 'ʼN', + 'ΐ' => 'Ϊ́', + 'ΰ' => 'Ϋ́', + 'ǰ' => 'J̌', + 'ẖ' => 'H̱', + 'ẗ' => 'T̈', + 'ẘ' => 'W̊', + 'ẙ' => 'Y̊', + 'ẚ' => 'Aʾ', + 'ὐ' => 'Υ̓', + 'ὒ' => 'Υ̓̀', + 'ὔ' => 'Υ̓́', + 'ὖ' => 'Υ̓͂', + 'ᾶ' => 'Α͂', + 'ῆ' => 'Η͂', + 'ῒ' => 'Ϊ̀', + 'ΐ' => 'Ϊ́', + 'ῖ' => 'Ι͂', + 'ῗ' => 'Ϊ͂', + 'ῢ' => 'Ϋ̀', + 'ΰ' => 'Ϋ́', + 'ῤ' => 'Ρ̓', + 'ῦ' => 'Υ͂', + 'ῧ' => 'Ϋ͂', + 'ῶ' => 'Ω͂', + 'ᾈ' => 'ἈΙ', + 'ᾉ' => 'ἉΙ', + 'ᾊ' => 'ἊΙ', + 'ᾋ' => 'ἋΙ', + 'ᾌ' => 'ἌΙ', + 'ᾍ' => 'ἍΙ', + 'ᾎ' => 'ἎΙ', + 'ᾏ' => 'ἏΙ', + 'ᾘ' => 'ἨΙ', + 'ᾙ' => 'ἩΙ', + 'ᾚ' => 'ἪΙ', + 'ᾛ' => 'ἫΙ', + 'ᾜ' => 'ἬΙ', + 'ᾝ' => 'ἭΙ', + 'ᾞ' => 'ἮΙ', + 'ᾟ' => 'ἯΙ', + 'ᾨ' => 'ὨΙ', + 'ᾩ' => 'ὩΙ', + 'ᾪ' => 'ὪΙ', + 'ᾫ' => 'ὫΙ', + 'ᾬ' => 'ὬΙ', + 'ᾭ' => 'ὭΙ', + 'ᾮ' => 'ὮΙ', + 'ᾯ' => 'ὯΙ', + 'ᾼ' => 'ΑΙ', + 'ῌ' => 'ΗΙ', + 'ῼ' => 'ΩΙ', + 'ᾲ' => 'ᾺΙ', + 'ᾴ' => 'ΆΙ', + 'ῂ' => 'ῊΙ', + 'ῄ' => 'ΉΙ', + 'ῲ' => 'ῺΙ', + 'ῴ' => 'ΏΙ', + 'ᾷ' => 'Α͂Ι', + 'ῇ' => 'Η͂Ι', + 'ῷ' => 'Ω͂Ι', +); diff --git a/vendor/symfony/polyfill-mbstring/bootstrap.php b/vendor/symfony/polyfill-mbstring/bootstrap.php new file mode 100644 index 0000000000..1fedd1f7c8 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/bootstrap.php @@ -0,0 +1,147 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Mbstring as p; + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!function_exists('mb_convert_encoding')) { + function mb_convert_encoding($string, $to_encoding, $from_encoding = null) { return p\Mbstring::mb_convert_encoding($string, $to_encoding, $from_encoding); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader($string) { return p\Mbstring::mb_decode_mimeheader($string); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader($string, $charset = null, $transfer_encoding = null, $newline = "\r\n", $indent = 0) { return p\Mbstring::mb_encode_mimeheader($string, $charset, $transfer_encoding, $newline, $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity($string, $map, $encoding = null) { return p\Mbstring::mb_decode_numericentity($string, $map, $encoding); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity($string, $map, $encoding = null, $hex = false) { return p\Mbstring::mb_encode_numericentity($string, $map, $encoding, $hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case($string, $mode, $encoding = null) { return p\Mbstring::mb_convert_case($string, $mode, $encoding); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding($encoding = null) { return p\Mbstring::mb_internal_encoding($encoding); } +} +if (!function_exists('mb_language')) { + function mb_language($language = null) { return p\Mbstring::mb_language($language); } +} +if (!function_exists('mb_list_encodings')) { + function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); } +} +if (!function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); } +} +if (!function_exists('mb_check_encoding')) { + function mb_check_encoding($value = null, $encoding = null) { return p\Mbstring::mb_check_encoding($value, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding($string, $encodings = null, $strict = false) { return p\Mbstring::mb_detect_encoding($string, $encodings, $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order($encoding = null) { return p\Mbstring::mb_detect_order($encoding); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str($string, &$result = []) { parse_str($string, $result); return (bool) $result; } +} +if (!function_exists('mb_strlen')) { + function mb_strlen($string, $encoding = null) { return p\Mbstring::mb_strlen($string, $encoding); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower($string, $encoding = null) { return p\Mbstring::mb_strtolower($string, $encoding); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper($string, $encoding = null) { return p\Mbstring::mb_strtoupper($string, $encoding); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character($substitute_character = null) { return p\Mbstring::mb_substitute_character($substitute_character); } +} +if (!function_exists('mb_substr')) { + function mb_substr($string, $start, $length = 2147483647, $encoding = null) { return p\Mbstring::mb_substr($string, $start, $length, $encoding); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_stripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_stristr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrchr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrichr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strrpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strstr')) { + function mb_strstr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strstr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_get_info')) { + function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); } +} +if (!function_exists('mb_http_output')) { + function mb_http_output($encoding = null) { return p\Mbstring::mb_http_output($encoding); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth($string, $encoding = null) { return p\Mbstring::mb_strwidth($string, $encoding); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count($haystack, $needle, $encoding = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $encoding); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler($string, $status) { return p\Mbstring::mb_output_handler($string, $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input($type = null) { return p\Mbstring::mb_http_input($type); } +} + +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables($to_encoding, $from_encoding, &...$vars) { return p\Mbstring::mb_convert_variables($to_encoding, $from_encoding, ...$vars); } +} + +if (!function_exists('mb_ord')) { + function mb_ord($string, $encoding = null) { return p\Mbstring::mb_ord($string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr($codepoint, $encoding = null) { return p\Mbstring::mb_chr($codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub($string, $encoding = null) { $encoding = null === $encoding ? mb_internal_encoding() : $encoding; return mb_convert_encoding($string, $encoding, $encoding); } +} +if (!function_exists('mb_str_split')) { + function mb_str_split($string, $length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $length, $encoding); } +} + +if (extension_loaded('mbstring')) { + return; +} + +if (!defined('MB_CASE_UPPER')) { + define('MB_CASE_UPPER', 0); +} +if (!defined('MB_CASE_LOWER')) { + define('MB_CASE_LOWER', 1); +} +if (!defined('MB_CASE_TITLE')) { + define('MB_CASE_TITLE', 2); +} diff --git a/vendor/symfony/polyfill-mbstring/bootstrap80.php b/vendor/symfony/polyfill-mbstring/bootstrap80.php new file mode 100644 index 0000000000..82f5ac4d0f --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/bootstrap80.php @@ -0,0 +1,143 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Mbstring as p; + +if (!function_exists('mb_convert_encoding')) { + function mb_convert_encoding(array|string|null $string, ?string $to_encoding, array|string|null $from_encoding = null): array|string|false { return p\Mbstring::mb_convert_encoding($string ?? '', (string) $to_encoding, $from_encoding); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader(?string $string): string { return p\Mbstring::mb_decode_mimeheader((string) $string); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader(?string $string, ?string $charset = null, ?string $transfer_encoding = null, ?string $newline = "\r\n", ?int $indent = 0): string { return p\Mbstring::mb_encode_mimeheader((string) $string, $charset, $transfer_encoding, (string) $newline, (int) $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity(?string $string, array $map, ?string $encoding = null): string { return p\Mbstring::mb_decode_numericentity((string) $string, $map, $encoding); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity(?string $string, array $map, ?string $encoding = null, ?bool $hex = false): string { return p\Mbstring::mb_encode_numericentity((string) $string, $map, $encoding, (bool) $hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case(?string $string, ?int $mode, ?string $encoding = null): string { return p\Mbstring::mb_convert_case((string) $string, (int) $mode, $encoding); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding(?string $encoding = null): string|bool { return p\Mbstring::mb_internal_encoding($encoding); } +} +if (!function_exists('mb_language')) { + function mb_language(?string $language = null): string|bool { return p\Mbstring::mb_language($language); } +} +if (!function_exists('mb_list_encodings')) { + function mb_list_encodings(): array { return p\Mbstring::mb_list_encodings(); } +} +if (!function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases(?string $encoding): array { return p\Mbstring::mb_encoding_aliases((string) $encoding); } +} +if (!function_exists('mb_check_encoding')) { + function mb_check_encoding(array|string|null $value = null, ?string $encoding = null): bool { return p\Mbstring::mb_check_encoding($value, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding(?string $string, array|string|null $encodings = null, ?bool $strict = false): string|false { return p\Mbstring::mb_detect_encoding((string) $string, $encodings, (bool) $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order(array|string|null $encoding = null): array|bool { return p\Mbstring::mb_detect_order($encoding); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str(?string $string, &$result = []): bool { parse_str((string) $string, $result); return (bool) $result; } +} +if (!function_exists('mb_strlen')) { + function mb_strlen(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strlen((string) $string, $encoding); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtolower((string) $string, $encoding); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtoupper((string) $string, $encoding); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character(string|int|null $substitute_character = null): string|int|bool { return p\Mbstring::mb_substitute_character($substitute_character); } +} +if (!function_exists('mb_substr')) { + function mb_substr(?string $string, ?int $start, ?int $length = null, ?string $encoding = null): string { return p\Mbstring::mb_substr((string) $string, (int) $start, $length, $encoding); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_stripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_stristr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrchr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrichr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strrpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strstr')) { + function mb_strstr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strstr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_get_info')) { + function mb_get_info(?string $type = 'all'): array|string|int|false { return p\Mbstring::mb_get_info((string) $type); } +} +if (!function_exists('mb_http_output')) { + function mb_http_output(?string $encoding = null): string|bool { return p\Mbstring::mb_http_output($encoding); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strwidth((string) $string, $encoding); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count(?string $haystack, ?string $needle, ?string $encoding = null): int { return p\Mbstring::mb_substr_count((string) $haystack, (string) $needle, $encoding); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler(?string $string, ?int $status): string { return p\Mbstring::mb_output_handler((string) $string, (int) $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input(?string $type = null): array|string|false { return p\Mbstring::mb_http_input($type); } +} + +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables(?string $to_encoding, array|string|null $from_encoding, mixed &$var, mixed &...$vars): string|false { return p\Mbstring::mb_convert_variables((string) $to_encoding, $from_encoding ?? '', $var, ...$vars); } +} + +if (!function_exists('mb_ord')) { + function mb_ord(?string $string, ?string $encoding = null): int|false { return p\Mbstring::mb_ord((string) $string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr(?int $codepoint, ?string $encoding = null): string|false { return p\Mbstring::mb_chr((int) $codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub(?string $string, ?string $encoding = null): string { $encoding ??= mb_internal_encoding(); return mb_convert_encoding((string) $string, $encoding, $encoding); } +} +if (!function_exists('mb_str_split')) { + function mb_str_split(?string $string, ?int $length = 1, ?string $encoding = null): array { return p\Mbstring::mb_str_split((string) $string, (int) $length, $encoding); } +} + +if (extension_loaded('mbstring')) { + return; +} + +if (!defined('MB_CASE_UPPER')) { + define('MB_CASE_UPPER', 0); +} +if (!defined('MB_CASE_LOWER')) { + define('MB_CASE_LOWER', 1); +} +if (!defined('MB_CASE_TITLE')) { + define('MB_CASE_TITLE', 2); +} diff --git a/vendor/symfony/polyfill-mbstring/composer.json b/vendor/symfony/polyfill-mbstring/composer.json new file mode 100644 index 0000000000..9cd2e924e9 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/composer.json @@ -0,0 +1,41 @@ +{ + "name": "symfony/polyfill-mbstring", + "type": "library", + "description": "Symfony polyfill for the Mbstring extension", + "keywords": ["polyfill", "shim", "compatibility", "portable", "mbstring"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Mbstring\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/vendor/symfony/polyfill-php73/LICENSE b/vendor/symfony/polyfill-php73/LICENSE new file mode 100644 index 0000000000..3f853aaf35 --- /dev/null +++ b/vendor/symfony/polyfill-php73/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-php73/Php73.php b/vendor/symfony/polyfill-php73/Php73.php new file mode 100644 index 0000000000..65c35a6a11 --- /dev/null +++ b/vendor/symfony/polyfill-php73/Php73.php @@ -0,0 +1,43 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php73; + +/** + * @author Gabriel Caruso <carusogabriel34@gmail.com> + * @author Ion Bazan <ion.bazan@gmail.com> + * + * @internal + */ +final class Php73 +{ + public static $startAt = 1533462603; + + /** + * @param bool $asNum + * + * @return array|float|int + */ + public static function hrtime($asNum = false) + { + $ns = microtime(false); + $s = substr($ns, 11) - self::$startAt; + $ns = 1E9 * (float) $ns; + + if ($asNum) { + $ns += $s * 1E9; + + return \PHP_INT_SIZE === 4 ? $ns : (int) $ns; + } + + return [$s, (int) $ns]; + } +} diff --git a/vendor/symfony/polyfill-php73/README.md b/vendor/symfony/polyfill-php73/README.md new file mode 100644 index 0000000000..032fafbda0 --- /dev/null +++ b/vendor/symfony/polyfill-php73/README.md @@ -0,0 +1,18 @@ +Symfony Polyfill / Php73 +======================== + +This component provides functions added to PHP 7.3 core: + +- [`array_key_first`](https://php.net/array_key_first) +- [`array_key_last`](https://php.net/array_key_last) +- [`hrtime`](https://php.net/function.hrtime) +- [`is_countable`](https://php.net/is_countable) +- [`JsonException`](https://php.net/JsonException) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-php73/Resources/stubs/JsonException.php b/vendor/symfony/polyfill-php73/Resources/stubs/JsonException.php new file mode 100644 index 0000000000..f06d6c2694 --- /dev/null +++ b/vendor/symfony/polyfill-php73/Resources/stubs/JsonException.php @@ -0,0 +1,16 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 70300) { + class JsonException extends Exception + { + } +} diff --git a/vendor/symfony/polyfill-php73/bootstrap.php b/vendor/symfony/polyfill-php73/bootstrap.php new file mode 100644 index 0000000000..d6b2153823 --- /dev/null +++ b/vendor/symfony/polyfill-php73/bootstrap.php @@ -0,0 +1,31 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php73 as p; + +if (\PHP_VERSION_ID >= 70300) { + return; +} + +if (!function_exists('is_countable')) { + function is_countable($value) { return is_array($value) || $value instanceof Countable || $value instanceof ResourceBundle || $value instanceof SimpleXmlElement; } +} +if (!function_exists('hrtime')) { + require_once __DIR__.'/Php73.php'; + p\Php73::$startAt = (int) microtime(true); + function hrtime($as_number = false) { return p\Php73::hrtime($as_number); } +} +if (!function_exists('array_key_first')) { + function array_key_first(array $array) { foreach ($array as $key => $value) { return $key; } } +} +if (!function_exists('array_key_last')) { + function array_key_last(array $array) { return key(array_slice($array, -1, 1, true)); } +} diff --git a/vendor/symfony/polyfill-php73/composer.json b/vendor/symfony/polyfill-php73/composer.json new file mode 100644 index 0000000000..af0cf42d23 --- /dev/null +++ b/vendor/symfony/polyfill-php73/composer.json @@ -0,0 +1,36 @@ +{ + "name": "symfony/polyfill-php73", + "type": "library", + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php73\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/vendor/symfony/polyfill-php80/LICENSE b/vendor/symfony/polyfill-php80/LICENSE new file mode 100644 index 0000000000..5593b1d84f --- /dev/null +++ b/vendor/symfony/polyfill-php80/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-php80/Php80.php b/vendor/symfony/polyfill-php80/Php80.php new file mode 100644 index 0000000000..362dd1a959 --- /dev/null +++ b/vendor/symfony/polyfill-php80/Php80.php @@ -0,0 +1,115 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php80; + +/** + * @author Ion Bazan <ion.bazan@gmail.com> + * @author Nico Oelgart <nicoswd@gmail.com> + * @author Nicolas Grekas <p@tchwork.com> + * + * @internal + */ +final class Php80 +{ + public static function fdiv(float $dividend, float $divisor): float + { + return @($dividend / $divisor); + } + + public static function get_debug_type($value): string + { + switch (true) { + case null === $value: return 'null'; + case \is_bool($value): return 'bool'; + case \is_string($value): return 'string'; + case \is_array($value): return 'array'; + case \is_int($value): return 'int'; + case \is_float($value): return 'float'; + case \is_object($value): break; + case $value instanceof \__PHP_Incomplete_Class: return '__PHP_Incomplete_Class'; + default: + if (null === $type = @get_resource_type($value)) { + return 'unknown'; + } + + if ('Unknown' === $type) { + $type = 'closed'; + } + + return "resource ($type)"; + } + + $class = \get_class($value); + + if (false === strpos($class, '@')) { + return $class; + } + + return (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous'; + } + + public static function get_resource_id($res): int + { + if (!\is_resource($res) && null === @get_resource_type($res)) { + throw new \TypeError(sprintf('Argument 1 passed to get_resource_id() must be of the type resource, %s given', get_debug_type($res))); + } + + return (int) $res; + } + + public static function preg_last_error_msg(): string + { + switch (preg_last_error()) { + case \PREG_INTERNAL_ERROR: + return 'Internal error'; + case \PREG_BAD_UTF8_ERROR: + return 'Malformed UTF-8 characters, possibly incorrectly encoded'; + case \PREG_BAD_UTF8_OFFSET_ERROR: + return 'The offset did not correspond to the beginning of a valid UTF-8 code point'; + case \PREG_BACKTRACK_LIMIT_ERROR: + return 'Backtrack limit exhausted'; + case \PREG_RECURSION_LIMIT_ERROR: + return 'Recursion limit exhausted'; + case \PREG_JIT_STACKLIMIT_ERROR: + return 'JIT stack limit exhausted'; + case \PREG_NO_ERROR: + return 'No error'; + default: + return 'Unknown error'; + } + } + + public static function str_contains(string $haystack, string $needle): bool + { + return '' === $needle || false !== strpos($haystack, $needle); + } + + public static function str_starts_with(string $haystack, string $needle): bool + { + return 0 === strncmp($haystack, $needle, \strlen($needle)); + } + + public static function str_ends_with(string $haystack, string $needle): bool + { + if ('' === $needle || $needle === $haystack) { + return true; + } + + if ('' === $haystack) { + return false; + } + + $needleLength = \strlen($needle); + + return $needleLength <= \strlen($haystack) && 0 === substr_compare($haystack, $needle, -$needleLength); + } +} diff --git a/vendor/symfony/polyfill-php80/PhpToken.php b/vendor/symfony/polyfill-php80/PhpToken.php new file mode 100644 index 0000000000..fe6e691056 --- /dev/null +++ b/vendor/symfony/polyfill-php80/PhpToken.php @@ -0,0 +1,103 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php80; + +/** + * @author Fedonyuk Anton <info@ensostudio.ru> + * + * @internal + */ +class PhpToken implements \Stringable +{ + /** + * @var int + */ + public $id; + + /** + * @var string + */ + public $text; + + /** + * @var int + */ + public $line; + + /** + * @var int + */ + public $pos; + + public function __construct(int $id, string $text, int $line = -1, int $position = -1) + { + $this->id = $id; + $this->text = $text; + $this->line = $line; + $this->pos = $position; + } + + public function getTokenName(): ?string + { + if ('UNKNOWN' === $name = token_name($this->id)) { + $name = \strlen($this->text) > 1 || \ord($this->text) < 32 ? null : $this->text; + } + + return $name; + } + + /** + * @param int|string|array $kind + */ + public function is($kind): bool + { + foreach ((array) $kind as $value) { + if (\in_array($value, [$this->id, $this->text], true)) { + return true; + } + } + + return false; + } + + public function isIgnorable(): bool + { + return \in_array($this->id, [\T_WHITESPACE, \T_COMMENT, \T_DOC_COMMENT, \T_OPEN_TAG], true); + } + + public function __toString(): string + { + return (string) $this->text; + } + + /** + * @return static[] + */ + public static function tokenize(string $code, int $flags = 0): array + { + $line = 1; + $position = 0; + $tokens = token_get_all($code, $flags); + foreach ($tokens as $index => $token) { + if (\is_string($token)) { + $id = \ord($token); + $text = $token; + } else { + [$id, $text, $line] = $token; + } + $tokens[$index] = new static($id, $text, $line, $position); + $position += \strlen($text); + } + + return $tokens; + } +} diff --git a/vendor/symfony/polyfill-php80/README.md b/vendor/symfony/polyfill-php80/README.md new file mode 100644 index 0000000000..3816c559d5 --- /dev/null +++ b/vendor/symfony/polyfill-php80/README.md @@ -0,0 +1,25 @@ +Symfony Polyfill / Php80 +======================== + +This component provides features added to PHP 8.0 core: + +- [`Stringable`](https://php.net/stringable) interface +- [`fdiv`](https://php.net/fdiv) +- [`ValueError`](https://php.net/valueerror) class +- [`UnhandledMatchError`](https://php.net/unhandledmatcherror) class +- `FILTER_VALIDATE_BOOL` constant +- [`get_debug_type`](https://php.net/get_debug_type) +- [`PhpToken`](https://php.net/phptoken) class +- [`preg_last_error_msg`](https://php.net/preg_last_error_msg) +- [`str_contains`](https://php.net/str_contains) +- [`str_starts_with`](https://php.net/str_starts_with) +- [`str_ends_with`](https://php.net/str_ends_with) +- [`get_resource_id`](https://php.net/get_resource_id) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php b/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php new file mode 100644 index 0000000000..7ea6d2772d --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php @@ -0,0 +1,22 @@ +<?php + +#[Attribute(Attribute::TARGET_CLASS)] +final class Attribute +{ + public const TARGET_CLASS = 1; + public const TARGET_FUNCTION = 2; + public const TARGET_METHOD = 4; + public const TARGET_PROPERTY = 8; + public const TARGET_CLASS_CONSTANT = 16; + public const TARGET_PARAMETER = 32; + public const TARGET_ALL = 63; + public const IS_REPEATABLE = 64; + + /** @var int */ + public $flags; + + public function __construct(int $flags = self::TARGET_ALL) + { + $this->flags = $flags; + } +} diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php b/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php new file mode 100644 index 0000000000..72f10812b3 --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php @@ -0,0 +1,7 @@ +<?php + +if (\PHP_VERSION_ID < 80000 && \extension_loaded('tokenizer')) { + class PhpToken extends Symfony\Polyfill\Php80\PhpToken + { + } +} diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php b/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php new file mode 100644 index 0000000000..77e037cb58 --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php @@ -0,0 +1,11 @@ +<?php + +if (\PHP_VERSION_ID < 80000) { + interface Stringable + { + /** + * @return string + */ + public function __toString(); + } +} diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php b/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php new file mode 100644 index 0000000000..37937cbfae --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php @@ -0,0 +1,7 @@ +<?php + +if (\PHP_VERSION_ID < 80000) { + class UnhandledMatchError extends Error + { + } +} diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php b/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php new file mode 100644 index 0000000000..a3a9b88b09 --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php @@ -0,0 +1,7 @@ +<?php + +if (\PHP_VERSION_ID < 80000) { + class ValueError extends Error + { + } +} diff --git a/vendor/symfony/polyfill-php80/bootstrap.php b/vendor/symfony/polyfill-php80/bootstrap.php new file mode 100644 index 0000000000..e5f7dbc1a4 --- /dev/null +++ b/vendor/symfony/polyfill-php80/bootstrap.php @@ -0,0 +1,42 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php80 as p; + +if (\PHP_VERSION_ID >= 80000) { + return; +} + +if (!defined('FILTER_VALIDATE_BOOL') && defined('FILTER_VALIDATE_BOOLEAN')) { + define('FILTER_VALIDATE_BOOL', \FILTER_VALIDATE_BOOLEAN); +} + +if (!function_exists('fdiv')) { + function fdiv(float $num1, float $num2): float { return p\Php80::fdiv($num1, $num2); } +} +if (!function_exists('preg_last_error_msg')) { + function preg_last_error_msg(): string { return p\Php80::preg_last_error_msg(); } +} +if (!function_exists('str_contains')) { + function str_contains(?string $haystack, ?string $needle): bool { return p\Php80::str_contains($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('str_starts_with')) { + function str_starts_with(?string $haystack, ?string $needle): bool { return p\Php80::str_starts_with($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('str_ends_with')) { + function str_ends_with(?string $haystack, ?string $needle): bool { return p\Php80::str_ends_with($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('get_debug_type')) { + function get_debug_type($value): string { return p\Php80::get_debug_type($value); } +} +if (!function_exists('get_resource_id')) { + function get_resource_id($resource): int { return p\Php80::get_resource_id($resource); } +} diff --git a/vendor/symfony/polyfill-php80/composer.json b/vendor/symfony/polyfill-php80/composer.json new file mode 100644 index 0000000000..cd3e9b65f4 --- /dev/null +++ b/vendor/symfony/polyfill-php80/composer.json @@ -0,0 +1,40 @@ +{ + "name": "symfony/polyfill-php80", + "type": "library", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php80\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/vendor/symfony/process/CHANGELOG.md b/vendor/symfony/process/CHANGELOG.md new file mode 100644 index 0000000000..69d4cbd77b --- /dev/null +++ b/vendor/symfony/process/CHANGELOG.md @@ -0,0 +1,96 @@ +CHANGELOG +========= + +4.4.0 +----- + + * deprecated `Process::inheritEnvironmentVariables()`: env variables are always inherited. + * added `Process::getLastOutputTime()` method + +4.2.0 +----- + + * added the `Process::fromShellCommandline()` to run commands in a shell wrapper + * deprecated passing a command as string when creating a `Process` instance + * deprecated the `Process::setCommandline()` and the `PhpProcess::setPhpBinary()` methods + * added the `Process::waitUntil()` method to wait for the process only for a + specific output, then continue the normal execution of your application + +4.1.0 +----- + + * added the `Process::isTtySupported()` method that allows to check for TTY support + * made `PhpExecutableFinder` look for the `PHP_BINARY` env var when searching the php binary + * added the `ProcessSignaledException` class to properly catch signaled process errors + +4.0.0 +----- + + * environment variables will always be inherited + * added a second `array $env = []` argument to the `start()`, `run()`, + `mustRun()`, and `restart()` methods of the `Process` class + * added a second `array $env = []` argument to the `start()` method of the + `PhpProcess` class + * the `ProcessUtils::escapeArgument()` method has been removed + * the `areEnvironmentVariablesInherited()`, `getOptions()`, and `setOptions()` + methods of the `Process` class have been removed + * support for passing `proc_open()` options has been removed + * removed the `ProcessBuilder` class, use the `Process` class instead + * removed the `getEnhanceWindowsCompatibility()` and `setEnhanceWindowsCompatibility()` methods of the `Process` class + * passing a not existing working directory to the constructor of the `Symfony\Component\Process\Process` class is not + supported anymore + +3.4.0 +----- + + * deprecated the ProcessBuilder class + * deprecated calling `Process::start()` without setting a valid working directory beforehand (via `setWorkingDirectory()` or constructor) + +3.3.0 +----- + + * added command line arrays in the `Process` class + * added `$env` argument to `Process::start()`, `run()`, `mustRun()` and `restart()` methods + * deprecated the `ProcessUtils::escapeArgument()` method + * deprecated not inheriting environment variables + * deprecated configuring `proc_open()` options + * deprecated configuring enhanced Windows compatibility + * deprecated configuring enhanced sigchild compatibility + +2.5.0 +----- + + * added support for PTY mode + * added the convenience method "mustRun" + * deprecation: Process::setStdin() is deprecated in favor of Process::setInput() + * deprecation: Process::getStdin() is deprecated in favor of Process::getInput() + * deprecation: Process::setInput() and ProcessBuilder::setInput() do not accept non-scalar types + +2.4.0 +----- + + * added the ability to define an idle timeout + +2.3.0 +----- + + * added ProcessUtils::escapeArgument() to fix the bug in escapeshellarg() function on Windows + * added Process::signal() + * added Process::getPid() + * added support for a TTY mode + +2.2.0 +----- + + * added ProcessBuilder::setArguments() to reset the arguments on a builder + * added a way to retrieve the standard and error output incrementally + * added Process:restart() + +2.1.0 +----- + + * added support for non-blocking processes (start(), wait(), isRunning(), stop()) + * enhanced Windows compatibility + * added Process::getExitCodeText() that returns a string representation for + the exit code returned by the process + * added ProcessBuilder diff --git a/vendor/symfony/process/Exception/ExceptionInterface.php b/vendor/symfony/process/Exception/ExceptionInterface.php new file mode 100644 index 0000000000..bd4a60403b --- /dev/null +++ b/vendor/symfony/process/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * Marker Interface for the Process Component. + * + * @author Johannes M. Schmitt <schmittjoh@gmail.com> + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/process/Exception/InvalidArgumentException.php b/vendor/symfony/process/Exception/InvalidArgumentException.php new file mode 100644 index 0000000000..926ee2118b --- /dev/null +++ b/vendor/symfony/process/Exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * InvalidArgumentException for the Process Component. + * + * @author Romain Neutron <imprec@gmail.com> + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/process/Exception/LogicException.php b/vendor/symfony/process/Exception/LogicException.php new file mode 100644 index 0000000000..be3d490dde --- /dev/null +++ b/vendor/symfony/process/Exception/LogicException.php @@ -0,0 +1,21 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * LogicException for the Process Component. + * + * @author Romain Neutron <imprec@gmail.com> + */ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/process/Exception/ProcessFailedException.php b/vendor/symfony/process/Exception/ProcessFailedException.php new file mode 100644 index 0000000000..328acfde5e --- /dev/null +++ b/vendor/symfony/process/Exception/ProcessFailedException.php @@ -0,0 +1,54 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +use Symfony\Component\Process\Process; + +/** + * Exception for failed processes. + * + * @author Johannes M. Schmitt <schmittjoh@gmail.com> + */ +class ProcessFailedException extends RuntimeException +{ + private $process; + + public function __construct(Process $process) + { + if ($process->isSuccessful()) { + throw new InvalidArgumentException('Expected a failed process, but the given process was successful.'); + } + + $error = sprintf('The command "%s" failed.'."\n\nExit Code: %s(%s)\n\nWorking directory: %s", + $process->getCommandLine(), + $process->getExitCode(), + $process->getExitCodeText(), + $process->getWorkingDirectory() + ); + + if (!$process->isOutputDisabled()) { + $error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s", + $process->getOutput(), + $process->getErrorOutput() + ); + } + + parent::__construct($error); + + $this->process = $process; + } + + public function getProcess() + { + return $this->process; + } +} diff --git a/vendor/symfony/process/Exception/ProcessSignaledException.php b/vendor/symfony/process/Exception/ProcessSignaledException.php new file mode 100644 index 0000000000..d4d322756f --- /dev/null +++ b/vendor/symfony/process/Exception/ProcessSignaledException.php @@ -0,0 +1,41 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +use Symfony\Component\Process\Process; + +/** + * Exception that is thrown when a process has been signaled. + * + * @author Sullivan Senechal <soullivaneuh@gmail.com> + */ +final class ProcessSignaledException extends RuntimeException +{ + private $process; + + public function __construct(Process $process) + { + $this->process = $process; + + parent::__construct(sprintf('The process has been signaled with signal "%s".', $process->getTermSignal())); + } + + public function getProcess(): Process + { + return $this->process; + } + + public function getSignal(): int + { + return $this->getProcess()->getTermSignal(); + } +} diff --git a/vendor/symfony/process/Exception/ProcessTimedOutException.php b/vendor/symfony/process/Exception/ProcessTimedOutException.php new file mode 100644 index 0000000000..94391a4596 --- /dev/null +++ b/vendor/symfony/process/Exception/ProcessTimedOutException.php @@ -0,0 +1,69 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +use Symfony\Component\Process\Process; + +/** + * Exception that is thrown when a process times out. + * + * @author Johannes M. Schmitt <schmittjoh@gmail.com> + */ +class ProcessTimedOutException extends RuntimeException +{ + public const TYPE_GENERAL = 1; + public const TYPE_IDLE = 2; + + private $process; + private $timeoutType; + + public function __construct(Process $process, int $timeoutType) + { + $this->process = $process; + $this->timeoutType = $timeoutType; + + parent::__construct(sprintf( + 'The process "%s" exceeded the timeout of %s seconds.', + $process->getCommandLine(), + $this->getExceededTimeout() + )); + } + + public function getProcess() + { + return $this->process; + } + + public function isGeneralTimeout() + { + return self::TYPE_GENERAL === $this->timeoutType; + } + + public function isIdleTimeout() + { + return self::TYPE_IDLE === $this->timeoutType; + } + + public function getExceededTimeout() + { + switch ($this->timeoutType) { + case self::TYPE_GENERAL: + return $this->process->getTimeout(); + + case self::TYPE_IDLE: + return $this->process->getIdleTimeout(); + + default: + throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType)); + } + } +} diff --git a/vendor/symfony/process/Exception/RuntimeException.php b/vendor/symfony/process/Exception/RuntimeException.php new file mode 100644 index 0000000000..adead2536b --- /dev/null +++ b/vendor/symfony/process/Exception/RuntimeException.php @@ -0,0 +1,21 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * RuntimeException for the Process Component. + * + * @author Johannes M. Schmitt <schmittjoh@gmail.com> + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/process/ExecutableFinder.php b/vendor/symfony/process/ExecutableFinder.php new file mode 100644 index 0000000000..e2dd064d60 --- /dev/null +++ b/vendor/symfony/process/ExecutableFinder.php @@ -0,0 +1,88 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +/** + * Generic executable finder. + * + * @author Fabien Potencier <fabien@symfony.com> + * @author Johannes M. Schmitt <schmittjoh@gmail.com> + */ +class ExecutableFinder +{ + private $suffixes = ['.exe', '.bat', '.cmd', '.com']; + + /** + * Replaces default suffixes of executable. + */ + public function setSuffixes(array $suffixes) + { + $this->suffixes = $suffixes; + } + + /** + * Adds new possible suffix to check for executable. + * + * @param string $suffix + */ + public function addSuffix($suffix) + { + $this->suffixes[] = $suffix; + } + + /** + * Finds an executable by name. + * + * @param string $name The executable name (without the extension) + * @param string|null $default The default to return if no executable is found + * @param array $extraDirs Additional dirs to check into + * + * @return string|null The executable path or default value + */ + public function find($name, $default = null, array $extraDirs = []) + { + if (\ini_get('open_basedir')) { + $searchPath = array_merge(explode(\PATH_SEPARATOR, \ini_get('open_basedir')), $extraDirs); + $dirs = []; + foreach ($searchPath as $path) { + // Silencing against https://bugs.php.net/69240 + if (@is_dir($path)) { + $dirs[] = $path; + } else { + if (basename($path) == $name && @is_executable($path)) { + return $path; + } + } + } + } else { + $dirs = array_merge( + explode(\PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')), + $extraDirs + ); + } + + $suffixes = ['']; + if ('\\' === \DIRECTORY_SEPARATOR) { + $pathExt = getenv('PATHEXT'); + $suffixes = array_merge($pathExt ? explode(\PATH_SEPARATOR, $pathExt) : $this->suffixes, $suffixes); + } + foreach ($suffixes as $suffix) { + foreach ($dirs as $dir) { + if (@is_file($file = $dir.\DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === \DIRECTORY_SEPARATOR || @is_executable($file))) { + return $file; + } + } + } + + return $default; + } +} diff --git a/vendor/symfony/process/InputStream.php b/vendor/symfony/process/InputStream.php new file mode 100644 index 0000000000..4f8f71331a --- /dev/null +++ b/vendor/symfony/process/InputStream.php @@ -0,0 +1,94 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\RuntimeException; + +/** + * Provides a way to continuously write to the input of a Process until the InputStream is closed. + * + * @author Nicolas Grekas <p@tchwork.com> + */ +class InputStream implements \IteratorAggregate +{ + /** @var callable|null */ + private $onEmpty = null; + private $input = []; + private $open = true; + + /** + * Sets a callback that is called when the write buffer becomes empty. + */ + public function onEmpty(callable $onEmpty = null) + { + $this->onEmpty = $onEmpty; + } + + /** + * Appends an input to the write buffer. + * + * @param resource|string|int|float|bool|\Traversable|null $input The input to append as scalar, + * stream resource or \Traversable + */ + public function write($input) + { + if (null === $input) { + return; + } + if ($this->isClosed()) { + throw new RuntimeException(sprintf('"%s" is closed.', static::class)); + } + $this->input[] = ProcessUtils::validateInput(__METHOD__, $input); + } + + /** + * Closes the write buffer. + */ + public function close() + { + $this->open = false; + } + + /** + * Tells whether the write buffer is closed or not. + */ + public function isClosed() + { + return !$this->open; + } + + /** + * @return \Traversable + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + $this->open = true; + + while ($this->open || $this->input) { + if (!$this->input) { + yield ''; + continue; + } + $current = array_shift($this->input); + + if ($current instanceof \Iterator) { + yield from $current; + } else { + yield $current; + } + if (!$this->input && $this->open && null !== $onEmpty = $this->onEmpty) { + $this->write($onEmpty($this)); + } + } + } +} diff --git a/vendor/symfony/process/LICENSE b/vendor/symfony/process/LICENSE new file mode 100644 index 0000000000..88bf75bb4d --- /dev/null +++ b/vendor/symfony/process/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/process/PhpExecutableFinder.php b/vendor/symfony/process/PhpExecutableFinder.php new file mode 100644 index 0000000000..92e0262ad7 --- /dev/null +++ b/vendor/symfony/process/PhpExecutableFinder.php @@ -0,0 +1,105 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +/** + * An executable finder specifically designed for the PHP executable. + * + * @author Fabien Potencier <fabien@symfony.com> + * @author Johannes M. Schmitt <schmittjoh@gmail.com> + */ +class PhpExecutableFinder +{ + private $executableFinder; + + public function __construct() + { + $this->executableFinder = new ExecutableFinder(); + } + + /** + * Finds The PHP executable. + * + * @param bool $includeArgs Whether or not include command arguments + * + * @return string|false The PHP executable path or false if it cannot be found + */ + public function find($includeArgs = true) + { + if ($php = getenv('PHP_BINARY')) { + if (!is_executable($php)) { + $command = '\\' === \DIRECTORY_SEPARATOR ? 'where' : 'command -v'; + if ($php = strtok(exec($command.' '.escapeshellarg($php)), \PHP_EOL)) { + if (!is_executable($php)) { + return false; + } + } else { + return false; + } + } + + if (@is_dir($php)) { + return false; + } + + return $php; + } + + $args = $this->findArguments(); + $args = $includeArgs && $args ? ' '.implode(' ', $args) : ''; + + // PHP_BINARY return the current sapi executable + if (\PHP_BINARY && \in_array(\PHP_SAPI, ['cgi-fcgi', 'cli', 'cli-server', 'phpdbg'], true)) { + return \PHP_BINARY.$args; + } + + if ($php = getenv('PHP_PATH')) { + if (!@is_executable($php) || @is_dir($php)) { + return false; + } + + return $php; + } + + if ($php = getenv('PHP_PEAR_PHP_BIN')) { + if (@is_executable($php) && !@is_dir($php)) { + return $php; + } + } + + if (@is_executable($php = \PHP_BINDIR.('\\' === \DIRECTORY_SEPARATOR ? '\\php.exe' : '/php')) && !@is_dir($php)) { + return $php; + } + + $dirs = [\PHP_BINDIR]; + if ('\\' === \DIRECTORY_SEPARATOR) { + $dirs[] = 'C:\xampp\php\\'; + } + + return $this->executableFinder->find('php', false, $dirs); + } + + /** + * Finds the PHP executable arguments. + * + * @return array The PHP executable arguments + */ + public function findArguments() + { + $arguments = []; + if ('phpdbg' === \PHP_SAPI) { + $arguments[] = '-qrr'; + } + + return $arguments; + } +} diff --git a/vendor/symfony/process/PhpProcess.php b/vendor/symfony/process/PhpProcess.php new file mode 100644 index 0000000000..dc064e0b8f --- /dev/null +++ b/vendor/symfony/process/PhpProcess.php @@ -0,0 +1,84 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\LogicException; +use Symfony\Component\Process\Exception\RuntimeException; + +/** + * PhpProcess runs a PHP script in an independent process. + * + * $p = new PhpProcess('<?php echo "foo"; ?>'); + * $p->run(); + * print $p->getOutput()."\n"; + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class PhpProcess extends Process +{ + /** + * @param string $script The PHP script to run (as a string) + * @param string|null $cwd The working directory or null to use the working dir of the current PHP process + * @param array|null $env The environment variables or null to use the same environment as the current PHP process + * @param int $timeout The timeout in seconds + * @param array|null $php Path to the PHP binary to use with any additional arguments + */ + public function __construct(string $script, string $cwd = null, array $env = null, int $timeout = 60, array $php = null) + { + if (null === $php) { + $executableFinder = new PhpExecutableFinder(); + $php = $executableFinder->find(false); + $php = false === $php ? null : array_merge([$php], $executableFinder->findArguments()); + } + if ('phpdbg' === \PHP_SAPI) { + $file = tempnam(sys_get_temp_dir(), 'dbg'); + file_put_contents($file, $script); + register_shutdown_function('unlink', $file); + $php[] = $file; + $script = null; + } + + parent::__construct($php, $cwd, $env, $script, $timeout); + } + + /** + * {@inheritdoc} + */ + public static function fromShellCommandline(string $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60) + { + throw new LogicException(sprintf('The "%s()" method cannot be called when using "%s".', __METHOD__, self::class)); + } + + /** + * Sets the path to the PHP binary to use. + * + * @deprecated since Symfony 4.2, use the $php argument of the constructor instead. + */ + public function setPhpBinary($php) + { + @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the $php argument of the constructor instead.', __METHOD__), \E_USER_DEPRECATED); + + $this->setCommandLine($php); + } + + /** + * {@inheritdoc} + */ + public function start(callable $callback = null, array $env = []) + { + if (null === $this->getCommandLine()) { + throw new RuntimeException('Unable to find the PHP executable.'); + } + + parent::start($callback, $env); + } +} diff --git a/vendor/symfony/process/Pipes/AbstractPipes.php b/vendor/symfony/process/Pipes/AbstractPipes.php new file mode 100644 index 0000000000..9532e3ef67 --- /dev/null +++ b/vendor/symfony/process/Pipes/AbstractPipes.php @@ -0,0 +1,180 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Pipes; + +use Symfony\Component\Process\Exception\InvalidArgumentException; + +/** + * @author Romain Neutron <imprec@gmail.com> + * + * @internal + */ +abstract class AbstractPipes implements PipesInterface +{ + public $pipes = []; + + private $inputBuffer = ''; + private $input; + private $blocked = true; + private $lastError; + + /** + * @param resource|string|int|float|bool|\Iterator|null $input + */ + public function __construct($input) + { + if (\is_resource($input) || $input instanceof \Iterator) { + $this->input = $input; + } elseif (\is_string($input)) { + $this->inputBuffer = $input; + } else { + $this->inputBuffer = (string) $input; + } + } + + /** + * {@inheritdoc} + */ + public function close() + { + foreach ($this->pipes as $pipe) { + if (\is_resource($pipe)) { + fclose($pipe); + } + } + $this->pipes = []; + } + + /** + * Returns true if a system call has been interrupted. + */ + protected function hasSystemCallBeenInterrupted(): bool + { + $lastError = $this->lastError; + $this->lastError = null; + + // stream_select returns false when the `select` system call is interrupted by an incoming signal + return null !== $lastError && false !== stripos($lastError, 'interrupted system call'); + } + + /** + * Unblocks streams. + */ + protected function unblock() + { + if (!$this->blocked) { + return; + } + + foreach ($this->pipes as $pipe) { + stream_set_blocking($pipe, 0); + } + if (\is_resource($this->input)) { + stream_set_blocking($this->input, 0); + } + + $this->blocked = false; + } + + /** + * Writes input to stdin. + * + * @throws InvalidArgumentException When an input iterator yields a non supported value + */ + protected function write(): ?array + { + if (!isset($this->pipes[0])) { + return null; + } + $input = $this->input; + + if ($input instanceof \Iterator) { + if (!$input->valid()) { + $input = null; + } elseif (\is_resource($input = $input->current())) { + stream_set_blocking($input, 0); + } elseif (!isset($this->inputBuffer[0])) { + if (!\is_string($input)) { + if (!\is_scalar($input)) { + throw new InvalidArgumentException(sprintf('"%s" yielded a value of type "%s", but only scalars and stream resources are supported.', \get_class($this->input), \gettype($input))); + } + $input = (string) $input; + } + $this->inputBuffer = $input; + $this->input->next(); + $input = null; + } else { + $input = null; + } + } + + $r = $e = []; + $w = [$this->pipes[0]]; + + // let's have a look if something changed in streams + if (false === @stream_select($r, $w, $e, 0, 0)) { + return null; + } + + foreach ($w as $stdin) { + if (isset($this->inputBuffer[0])) { + $written = fwrite($stdin, $this->inputBuffer); + $this->inputBuffer = substr($this->inputBuffer, $written); + if (isset($this->inputBuffer[0])) { + return [$this->pipes[0]]; + } + } + + if ($input) { + while (true) { + $data = fread($input, self::CHUNK_SIZE); + if (!isset($data[0])) { + break; + } + $written = fwrite($stdin, $data); + $data = substr($data, $written); + if (isset($data[0])) { + $this->inputBuffer = $data; + + return [$this->pipes[0]]; + } + } + if (feof($input)) { + if ($this->input instanceof \Iterator) { + $this->input->next(); + } else { + $this->input = null; + } + } + } + } + + // no input to read on resource, buffer is empty + if (!isset($this->inputBuffer[0]) && !($this->input instanceof \Iterator ? $this->input->valid() : $this->input)) { + $this->input = null; + fclose($this->pipes[0]); + unset($this->pipes[0]); + } elseif (!$w) { + return [$this->pipes[0]]; + } + + return null; + } + + /** + * @internal + */ + public function handleError(int $type, string $msg) + { + $this->lastError = $msg; + } +} diff --git a/vendor/symfony/process/Pipes/PipesInterface.php b/vendor/symfony/process/Pipes/PipesInterface.php new file mode 100644 index 0000000000..50eb5c47e1 --- /dev/null +++ b/vendor/symfony/process/Pipes/PipesInterface.php @@ -0,0 +1,61 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Pipes; + +/** + * PipesInterface manages descriptors and pipes for the use of proc_open. + * + * @author Romain Neutron <imprec@gmail.com> + * + * @internal + */ +interface PipesInterface +{ + public const CHUNK_SIZE = 16384; + + /** + * Returns an array of descriptors for the use of proc_open. + */ + public function getDescriptors(): array; + + /** + * Returns an array of filenames indexed by their related stream in case these pipes use temporary files. + * + * @return string[] + */ + public function getFiles(): array; + + /** + * Reads data in file handles and pipes. + * + * @param bool $blocking Whether to use blocking calls or not + * @param bool $close Whether to close pipes if they've reached EOF + * + * @return string[] An array of read data indexed by their fd + */ + public function readAndWrite(bool $blocking, bool $close = false): array; + + /** + * Returns if the current state has open file handles or pipes. + */ + public function areOpen(): bool; + + /** + * Returns if pipes are able to read output. + */ + public function haveReadSupport(): bool; + + /** + * Closes file handles and pipes. + */ + public function close(); +} diff --git a/vendor/symfony/process/Pipes/UnixPipes.php b/vendor/symfony/process/Pipes/UnixPipes.php new file mode 100644 index 0000000000..58a8da07c7 --- /dev/null +++ b/vendor/symfony/process/Pipes/UnixPipes.php @@ -0,0 +1,166 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Pipes; + +use Symfony\Component\Process\Process; + +/** + * UnixPipes implementation uses unix pipes as handles. + * + * @author Romain Neutron <imprec@gmail.com> + * + * @internal + */ +class UnixPipes extends AbstractPipes +{ + private $ttyMode; + private $ptyMode; + private $haveReadSupport; + + public function __construct(?bool $ttyMode, bool $ptyMode, $input, bool $haveReadSupport) + { + $this->ttyMode = $ttyMode; + $this->ptyMode = $ptyMode; + $this->haveReadSupport = $haveReadSupport; + + parent::__construct($input); + } + + /** + * @return array + */ + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + $this->close(); + } + + /** + * {@inheritdoc} + */ + public function getDescriptors(): array + { + if (!$this->haveReadSupport) { + $nullstream = fopen('/dev/null', 'c'); + + return [ + ['pipe', 'r'], + $nullstream, + $nullstream, + ]; + } + + if ($this->ttyMode) { + return [ + ['file', '/dev/tty', 'r'], + ['file', '/dev/tty', 'w'], + ['file', '/dev/tty', 'w'], + ]; + } + + if ($this->ptyMode && Process::isPtySupported()) { + return [ + ['pty'], + ['pty'], + ['pty'], + ]; + } + + return [ + ['pipe', 'r'], + ['pipe', 'w'], // stdout + ['pipe', 'w'], // stderr + ]; + } + + /** + * {@inheritdoc} + */ + public function getFiles(): array + { + return []; + } + + /** + * {@inheritdoc} + */ + public function readAndWrite(bool $blocking, bool $close = false): array + { + $this->unblock(); + $w = $this->write(); + + $read = $e = []; + $r = $this->pipes; + unset($r[0]); + + // let's have a look if something changed in streams + set_error_handler([$this, 'handleError']); + if (($r || $w) && false === stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { + restore_error_handler(); + // if a system call has been interrupted, forget about it, let's try again + // otherwise, an error occurred, let's reset pipes + if (!$this->hasSystemCallBeenInterrupted()) { + $this->pipes = []; + } + + return $read; + } + restore_error_handler(); + + foreach ($r as $pipe) { + // prior PHP 5.4 the array passed to stream_select is modified and + // lose key association, we have to find back the key + $read[$type = array_search($pipe, $this->pipes, true)] = ''; + + do { + $data = @fread($pipe, self::CHUNK_SIZE); + $read[$type] .= $data; + } while (isset($data[0]) && ($close || isset($data[self::CHUNK_SIZE - 1]))); + + if (!isset($read[$type][0])) { + unset($read[$type]); + } + + if ($close && feof($pipe)) { + fclose($pipe); + unset($this->pipes[$type]); + } + } + + return $read; + } + + /** + * {@inheritdoc} + */ + public function haveReadSupport(): bool + { + return $this->haveReadSupport; + } + + /** + * {@inheritdoc} + */ + public function areOpen(): bool + { + return (bool) $this->pipes; + } +} diff --git a/vendor/symfony/process/Pipes/WindowsPipes.php b/vendor/symfony/process/Pipes/WindowsPipes.php new file mode 100644 index 0000000000..69768f3d8a --- /dev/null +++ b/vendor/symfony/process/Pipes/WindowsPipes.php @@ -0,0 +1,207 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Pipes; + +use Symfony\Component\Process\Exception\RuntimeException; +use Symfony\Component\Process\Process; + +/** + * WindowsPipes implementation uses temporary files as handles. + * + * @see https://bugs.php.net/51800 + * @see https://bugs.php.net/65650 + * + * @author Romain Neutron <imprec@gmail.com> + * + * @internal + */ +class WindowsPipes extends AbstractPipes +{ + private $files = []; + private $fileHandles = []; + private $lockHandles = []; + private $readBytes = [ + Process::STDOUT => 0, + Process::STDERR => 0, + ]; + private $haveReadSupport; + + public function __construct($input, bool $haveReadSupport) + { + $this->haveReadSupport = $haveReadSupport; + + if ($this->haveReadSupport) { + // Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big. + // Workaround for this problem is to use temporary files instead of pipes on Windows platform. + // + // @see https://bugs.php.net/51800 + $pipes = [ + Process::STDOUT => Process::OUT, + Process::STDERR => Process::ERR, + ]; + $tmpDir = sys_get_temp_dir(); + $lastError = 'unknown reason'; + set_error_handler(function ($type, $msg) use (&$lastError) { $lastError = $msg; }); + for ($i = 0;; ++$i) { + foreach ($pipes as $pipe => $name) { + $file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name); + + if (!$h = fopen($file.'.lock', 'w')) { + if (file_exists($file.'.lock')) { + continue 2; + } + restore_error_handler(); + throw new RuntimeException('A temporary file could not be opened to write the process output: '.$lastError); + } + if (!flock($h, \LOCK_EX | \LOCK_NB)) { + continue 2; + } + if (isset($this->lockHandles[$pipe])) { + flock($this->lockHandles[$pipe], \LOCK_UN); + fclose($this->lockHandles[$pipe]); + } + $this->lockHandles[$pipe] = $h; + + if (!($h = fopen($file, 'w')) || !fclose($h) || !$h = fopen($file, 'r')) { + flock($this->lockHandles[$pipe], \LOCK_UN); + fclose($this->lockHandles[$pipe]); + unset($this->lockHandles[$pipe]); + continue 2; + } + $this->fileHandles[$pipe] = $h; + $this->files[$pipe] = $file; + } + break; + } + restore_error_handler(); + } + + parent::__construct($input); + } + + /** + * @return array + */ + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + $this->close(); + } + + /** + * {@inheritdoc} + */ + public function getDescriptors(): array + { + if (!$this->haveReadSupport) { + $nullstream = fopen('NUL', 'c'); + + return [ + ['pipe', 'r'], + $nullstream, + $nullstream, + ]; + } + + // We're not using pipe on Windows platform as it hangs (https://bugs.php.net/51800) + // We're not using file handles as it can produce corrupted output https://bugs.php.net/65650 + // So we redirect output within the commandline and pass the nul device to the process + return [ + ['pipe', 'r'], + ['file', 'NUL', 'w'], + ['file', 'NUL', 'w'], + ]; + } + + /** + * {@inheritdoc} + */ + public function getFiles(): array + { + return $this->files; + } + + /** + * {@inheritdoc} + */ + public function readAndWrite(bool $blocking, bool $close = false): array + { + $this->unblock(); + $w = $this->write(); + $read = $r = $e = []; + + if ($blocking) { + if ($w) { + @stream_select($r, $w, $e, 0, Process::TIMEOUT_PRECISION * 1E6); + } elseif ($this->fileHandles) { + usleep(Process::TIMEOUT_PRECISION * 1E6); + } + } + foreach ($this->fileHandles as $type => $fileHandle) { + $data = stream_get_contents($fileHandle, -1, $this->readBytes[$type]); + + if (isset($data[0])) { + $this->readBytes[$type] += \strlen($data); + $read[$type] = $data; + } + if ($close) { + ftruncate($fileHandle, 0); + fclose($fileHandle); + flock($this->lockHandles[$type], \LOCK_UN); + fclose($this->lockHandles[$type]); + unset($this->fileHandles[$type], $this->lockHandles[$type]); + } + } + + return $read; + } + + /** + * {@inheritdoc} + */ + public function haveReadSupport(): bool + { + return $this->haveReadSupport; + } + + /** + * {@inheritdoc} + */ + public function areOpen(): bool + { + return $this->pipes && $this->fileHandles; + } + + /** + * {@inheritdoc} + */ + public function close() + { + parent::close(); + foreach ($this->fileHandles as $type => $handle) { + ftruncate($handle, 0); + fclose($handle); + flock($this->lockHandles[$type], \LOCK_UN); + fclose($this->lockHandles[$type]); + } + $this->fileHandles = $this->lockHandles = []; + } +} diff --git a/vendor/symfony/process/Process.php b/vendor/symfony/process/Process.php new file mode 100644 index 0000000000..09cd9602a1 --- /dev/null +++ b/vendor/symfony/process/Process.php @@ -0,0 +1,1662 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\InvalidArgumentException; +use Symfony\Component\Process\Exception\LogicException; +use Symfony\Component\Process\Exception\ProcessFailedException; +use Symfony\Component\Process\Exception\ProcessSignaledException; +use Symfony\Component\Process\Exception\ProcessTimedOutException; +use Symfony\Component\Process\Exception\RuntimeException; +use Symfony\Component\Process\Pipes\PipesInterface; +use Symfony\Component\Process\Pipes\UnixPipes; +use Symfony\Component\Process\Pipes\WindowsPipes; + +/** + * Process is a thin wrapper around proc_* functions to easily + * start independent PHP processes. + * + * @author Fabien Potencier <fabien@symfony.com> + * @author Romain Neutron <imprec@gmail.com> + */ +class Process implements \IteratorAggregate +{ + public const ERR = 'err'; + public const OUT = 'out'; + + public const STATUS_READY = 'ready'; + public const STATUS_STARTED = 'started'; + public const STATUS_TERMINATED = 'terminated'; + + public const STDIN = 0; + public const STDOUT = 1; + public const STDERR = 2; + + // Timeout Precision in seconds. + public const TIMEOUT_PRECISION = 0.2; + + public const ITER_NON_BLOCKING = 1; // By default, iterating over outputs is a blocking call, use this flag to make it non-blocking + public const ITER_KEEP_OUTPUT = 2; // By default, outputs are cleared while iterating, use this flag to keep them in memory + public const ITER_SKIP_OUT = 4; // Use this flag to skip STDOUT while iterating + public const ITER_SKIP_ERR = 8; // Use this flag to skip STDERR while iterating + + private $callback; + private $hasCallback = false; + private $commandline; + private $cwd; + private $env = []; + private $input; + private $starttime; + private $lastOutputTime; + private $timeout; + private $idleTimeout; + private $exitcode; + private $fallbackStatus = []; + private $processInformation; + private $outputDisabled = false; + private $stdout; + private $stderr; + private $process; + private $status = self::STATUS_READY; + private $incrementalOutputOffset = 0; + private $incrementalErrorOutputOffset = 0; + private $tty = false; + private $pty; + + private $useFileHandles = false; + /** @var PipesInterface */ + private $processPipes; + + private $latestSignal; + + private static $sigchild; + + /** + * Exit codes translation table. + * + * User-defined errors must use exit codes in the 64-113 range. + */ + public static $exitCodes = [ + 0 => 'OK', + 1 => 'General error', + 2 => 'Misuse of shell builtins', + + 126 => 'Invoked command cannot execute', + 127 => 'Command not found', + 128 => 'Invalid exit argument', + + // signals + 129 => 'Hangup', + 130 => 'Interrupt', + 131 => 'Quit and dump core', + 132 => 'Illegal instruction', + 133 => 'Trace/breakpoint trap', + 134 => 'Process aborted', + 135 => 'Bus error: "access to undefined portion of memory object"', + 136 => 'Floating point exception: "erroneous arithmetic operation"', + 137 => 'Kill (terminate immediately)', + 138 => 'User-defined 1', + 139 => 'Segmentation violation', + 140 => 'User-defined 2', + 141 => 'Write to pipe with no one reading', + 142 => 'Signal raised by alarm', + 143 => 'Termination (request to terminate)', + // 144 - not defined + 145 => 'Child process terminated, stopped (or continued*)', + 146 => 'Continue if stopped', + 147 => 'Stop executing temporarily', + 148 => 'Terminal stop signal', + 149 => 'Background process attempting to read from tty ("in")', + 150 => 'Background process attempting to write to tty ("out")', + 151 => 'Urgent data available on socket', + 152 => 'CPU time limit exceeded', + 153 => 'File size limit exceeded', + 154 => 'Signal raised by timer counting virtual time: "virtual timer expired"', + 155 => 'Profiling timer expired', + // 156 - not defined + 157 => 'Pollable event', + // 158 - not defined + 159 => 'Bad syscall', + ]; + + /** + * @param array $command The command to run and its arguments listed as separate entries + * @param string|null $cwd The working directory or null to use the working dir of the current PHP process + * @param array|null $env The environment variables or null to use the same environment as the current PHP process + * @param mixed $input The input as stream resource, scalar or \Traversable, or null for no input + * @param int|float|null $timeout The timeout in seconds or null to disable + * + * @throws LogicException When proc_open is not installed + */ + public function __construct($command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60) + { + if (!\function_exists('proc_open')) { + throw new LogicException('The Process class relies on proc_open, which is not available on your PHP installation.'); + } + + if (!\is_array($command)) { + @trigger_error(sprintf('Passing a command as string when creating a "%s" instance is deprecated since Symfony 4.2, pass it as an array of its arguments instead, or use the "Process::fromShellCommandline()" constructor if you need features provided by the shell.', __CLASS__), \E_USER_DEPRECATED); + } + + $this->commandline = $command; + $this->cwd = $cwd; + + // on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started + // on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected + // @see : https://bugs.php.net/51800 + // @see : https://bugs.php.net/50524 + if (null === $this->cwd && (\defined('ZEND_THREAD_SAFE') || '\\' === \DIRECTORY_SEPARATOR)) { + $this->cwd = getcwd(); + } + if (null !== $env) { + $this->setEnv($env); + } + + $this->setInput($input); + $this->setTimeout($timeout); + $this->useFileHandles = '\\' === \DIRECTORY_SEPARATOR; + $this->pty = false; + } + + /** + * Creates a Process instance as a command-line to be run in a shell wrapper. + * + * Command-lines are parsed by the shell of your OS (/bin/sh on Unix-like, cmd.exe on Windows.) + * This allows using e.g. pipes or conditional execution. In this mode, signals are sent to the + * shell wrapper and not to your commands. + * + * In order to inject dynamic values into command-lines, we strongly recommend using placeholders. + * This will save escaping values, which is not portable nor secure anyway: + * + * $process = Process::fromShellCommandline('my_command "${:MY_VAR}"'); + * $process->run(null, ['MY_VAR' => $theValue]); + * + * @param string $command The command line to pass to the shell of the OS + * @param string|null $cwd The working directory or null to use the working dir of the current PHP process + * @param array|null $env The environment variables or null to use the same environment as the current PHP process + * @param mixed $input The input as stream resource, scalar or \Traversable, or null for no input + * @param int|float|null $timeout The timeout in seconds or null to disable + * + * @return static + * + * @throws LogicException When proc_open is not installed + */ + public static function fromShellCommandline(string $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60) + { + $process = new static([], $cwd, $env, $input, $timeout); + $process->commandline = $command; + + return $process; + } + + /** + * @return array + */ + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + $this->stop(0); + } + + public function __clone() + { + $this->resetProcessData(); + } + + /** + * Runs the process. + * + * The callback receives the type of output (out or err) and + * some bytes from the output in real-time. It allows to have feedback + * from the independent process during execution. + * + * The STDOUT and STDERR are also available after the process is finished + * via the getOutput() and getErrorOutput() methods. + * + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * + * @return int The exit status code + * + * @throws RuntimeException When process can't be launched + * @throws RuntimeException When process is already running + * @throws ProcessTimedOutException When process timed out + * @throws ProcessSignaledException When process stopped after receiving signal + * @throws LogicException In case a callback is provided and output has been disabled + * + * @final + */ + public function run(callable $callback = null, array $env = []): int + { + $this->start($callback, $env); + + return $this->wait(); + } + + /** + * Runs the process. + * + * This is identical to run() except that an exception is thrown if the process + * exits with a non-zero exit code. + * + * @return $this + * + * @throws ProcessFailedException if the process didn't terminate successfully + * + * @final + */ + public function mustRun(callable $callback = null, array $env = []): self + { + if (0 !== $this->run($callback, $env)) { + throw new ProcessFailedException($this); + } + + return $this; + } + + /** + * Starts the process and returns after writing the input to STDIN. + * + * This method blocks until all STDIN data is sent to the process then it + * returns while the process runs in the background. + * + * The termination of the process can be awaited with wait(). + * + * The callback receives the type of output (out or err) and some bytes from + * the output in real-time while writing the standard input to the process. + * It allows to have feedback from the independent process during execution. + * + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * + * @throws RuntimeException When process can't be launched + * @throws RuntimeException When process is already running + * @throws LogicException In case a callback is provided and output has been disabled + */ + public function start(callable $callback = null, array $env = []) + { + if ($this->isRunning()) { + throw new RuntimeException('Process is already running.'); + } + + $this->resetProcessData(); + $this->starttime = $this->lastOutputTime = microtime(true); + $this->callback = $this->buildCallback($callback); + $this->hasCallback = null !== $callback; + $descriptors = $this->getDescriptors(); + + if ($this->env) { + $env += '\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($this->env, $env, 'strcasecmp') : $this->env; + } + + $env += '\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($this->getDefaultEnv(), $env, 'strcasecmp') : $this->getDefaultEnv(); + + if (\is_array($commandline = $this->commandline)) { + $commandline = implode(' ', array_map([$this, 'escapeArgument'], $commandline)); + + if ('\\' !== \DIRECTORY_SEPARATOR) { + // exec is mandatory to deal with sending a signal to the process + $commandline = 'exec '.$commandline; + } + } else { + $commandline = $this->replacePlaceholders($commandline, $env); + } + + $options = ['suppress_errors' => true]; + + if ('\\' === \DIRECTORY_SEPARATOR) { + $options['bypass_shell'] = true; + $commandline = $this->prepareWindowsCommandLine($commandline, $env); + } elseif (!$this->useFileHandles && $this->isSigchildEnabled()) { + // last exit code is output on the fourth pipe and caught to work around --enable-sigchild + $descriptors[3] = ['pipe', 'w']; + + // See https://unix.stackexchange.com/questions/71205/background-process-pipe-input + $commandline = '{ ('.$commandline.') <&3 3<&- 3>/dev/null & } 3<&0;'; + $commandline .= 'pid=$!; echo $pid >&3; wait $pid; code=$?; echo $code >&3; exit $code'; + + // Workaround for the bug, when PTS functionality is enabled. + // @see : https://bugs.php.net/69442 + $ptsWorkaround = fopen(__FILE__, 'r'); + } + + $envPairs = []; + foreach ($env as $k => $v) { + if (false !== $v && false === \in_array($k, ['argc', 'argv', 'ARGC', 'ARGV'], true)) { + $envPairs[] = $k.'='.$v; + } + } + + if (!is_dir($this->cwd)) { + throw new RuntimeException(sprintf('The provided cwd "%s" does not exist.', $this->cwd)); + } + + $this->process = @proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $options); + + if (!\is_resource($this->process)) { + throw new RuntimeException('Unable to launch a new process.'); + } + $this->status = self::STATUS_STARTED; + + if (isset($descriptors[3])) { + $this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]); + } + + if ($this->tty) { + return; + } + + $this->updateStatus(false); + $this->checkTimeout(); + } + + /** + * Restarts the process. + * + * Be warned that the process is cloned before being started. + * + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * + * @return static + * + * @throws RuntimeException When process can't be launched + * @throws RuntimeException When process is already running + * + * @see start() + * + * @final + */ + public function restart(callable $callback = null, array $env = []): self + { + if ($this->isRunning()) { + throw new RuntimeException('Process is already running.'); + } + + $process = clone $this; + $process->start($callback, $env); + + return $process; + } + + /** + * Waits for the process to terminate. + * + * The callback receives the type of output (out or err) and some bytes + * from the output in real-time while writing the standard input to the process. + * It allows to have feedback from the independent process during execution. + * + * @param callable|null $callback A valid PHP callback + * + * @return int The exitcode of the process + * + * @throws ProcessTimedOutException When process timed out + * @throws ProcessSignaledException When process stopped after receiving signal + * @throws LogicException When process is not yet started + */ + public function wait(callable $callback = null) + { + $this->requireProcessIsStarted(__FUNCTION__); + + $this->updateStatus(false); + + if (null !== $callback) { + if (!$this->processPipes->haveReadSupport()) { + $this->stop(0); + throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::wait".'); + } + $this->callback = $this->buildCallback($callback); + } + + do { + $this->checkTimeout(); + $running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen(); + $this->readPipes($running, '\\' !== \DIRECTORY_SEPARATOR || !$running); + } while ($running); + + while ($this->isRunning()) { + $this->checkTimeout(); + usleep(1000); + } + + if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) { + throw new ProcessSignaledException($this); + } + + return $this->exitcode; + } + + /** + * Waits until the callback returns true. + * + * The callback receives the type of output (out or err) and some bytes + * from the output in real-time while writing the standard input to the process. + * It allows to have feedback from the independent process during execution. + * + * @throws RuntimeException When process timed out + * @throws LogicException When process is not yet started + * @throws ProcessTimedOutException In case the timeout was reached + */ + public function waitUntil(callable $callback): bool + { + $this->requireProcessIsStarted(__FUNCTION__); + $this->updateStatus(false); + + if (!$this->processPipes->haveReadSupport()) { + $this->stop(0); + throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::waitUntil".'); + } + $callback = $this->buildCallback($callback); + + $ready = false; + while (true) { + $this->checkTimeout(); + $running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen(); + $output = $this->processPipes->readAndWrite($running, '\\' !== \DIRECTORY_SEPARATOR || !$running); + + foreach ($output as $type => $data) { + if (3 !== $type) { + $ready = $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data) || $ready; + } elseif (!isset($this->fallbackStatus['signaled'])) { + $this->fallbackStatus['exitcode'] = (int) $data; + } + } + if ($ready) { + return true; + } + if (!$running) { + return false; + } + + usleep(1000); + } + } + + /** + * Returns the Pid (process identifier), if applicable. + * + * @return int|null The process id if running, null otherwise + */ + public function getPid() + { + return $this->isRunning() ? $this->processInformation['pid'] : null; + } + + /** + * Sends a POSIX signal to the process. + * + * @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants) + * + * @return $this + * + * @throws LogicException In case the process is not running + * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed + * @throws RuntimeException In case of failure + */ + public function signal($signal) + { + $this->doSignal($signal, true); + + return $this; + } + + /** + * Disables fetching output and error output from the underlying process. + * + * @return $this + * + * @throws RuntimeException In case the process is already running + * @throws LogicException if an idle timeout is set + */ + public function disableOutput() + { + if ($this->isRunning()) { + throw new RuntimeException('Disabling output while the process is running is not possible.'); + } + if (null !== $this->idleTimeout) { + throw new LogicException('Output can not be disabled while an idle timeout is set.'); + } + + $this->outputDisabled = true; + + return $this; + } + + /** + * Enables fetching output and error output from the underlying process. + * + * @return $this + * + * @throws RuntimeException In case the process is already running + */ + public function enableOutput() + { + if ($this->isRunning()) { + throw new RuntimeException('Enabling output while the process is running is not possible.'); + } + + $this->outputDisabled = false; + + return $this; + } + + /** + * Returns true in case the output is disabled, false otherwise. + * + * @return bool + */ + public function isOutputDisabled() + { + return $this->outputDisabled; + } + + /** + * Returns the current output of the process (STDOUT). + * + * @return string The process output + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getOutput() + { + $this->readPipesForOutput(__FUNCTION__); + + if (false === $ret = stream_get_contents($this->stdout, -1, 0)) { + return ''; + } + + return $ret; + } + + /** + * Returns the output incrementally. + * + * In comparison with the getOutput method which always return the whole + * output, this one returns the new output since the last call. + * + * @return string The process output since the last call + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getIncrementalOutput() + { + $this->readPipesForOutput(__FUNCTION__); + + $latest = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset); + $this->incrementalOutputOffset = ftell($this->stdout); + + if (false === $latest) { + return ''; + } + + return $latest; + } + + /** + * Returns an iterator to the output of the process, with the output type as keys (Process::OUT/ERR). + * + * @param int $flags A bit field of Process::ITER_* flags + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + * + * @return \Generator + */ + #[\ReturnTypeWillChange] + public function getIterator($flags = 0) + { + $this->readPipesForOutput(__FUNCTION__, false); + + $clearOutput = !(self::ITER_KEEP_OUTPUT & $flags); + $blocking = !(self::ITER_NON_BLOCKING & $flags); + $yieldOut = !(self::ITER_SKIP_OUT & $flags); + $yieldErr = !(self::ITER_SKIP_ERR & $flags); + + while (null !== $this->callback || ($yieldOut && !feof($this->stdout)) || ($yieldErr && !feof($this->stderr))) { + if ($yieldOut) { + $out = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset); + + if (isset($out[0])) { + if ($clearOutput) { + $this->clearOutput(); + } else { + $this->incrementalOutputOffset = ftell($this->stdout); + } + + yield self::OUT => $out; + } + } + + if ($yieldErr) { + $err = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset); + + if (isset($err[0])) { + if ($clearOutput) { + $this->clearErrorOutput(); + } else { + $this->incrementalErrorOutputOffset = ftell($this->stderr); + } + + yield self::ERR => $err; + } + } + + if (!$blocking && !isset($out[0]) && !isset($err[0])) { + yield self::OUT => ''; + } + + $this->checkTimeout(); + $this->readPipesForOutput(__FUNCTION__, $blocking); + } + } + + /** + * Clears the process output. + * + * @return $this + */ + public function clearOutput() + { + ftruncate($this->stdout, 0); + fseek($this->stdout, 0); + $this->incrementalOutputOffset = 0; + + return $this; + } + + /** + * Returns the current error output of the process (STDERR). + * + * @return string The process error output + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getErrorOutput() + { + $this->readPipesForOutput(__FUNCTION__); + + if (false === $ret = stream_get_contents($this->stderr, -1, 0)) { + return ''; + } + + return $ret; + } + + /** + * Returns the errorOutput incrementally. + * + * In comparison with the getErrorOutput method which always return the + * whole error output, this one returns the new error output since the last + * call. + * + * @return string The process error output since the last call + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getIncrementalErrorOutput() + { + $this->readPipesForOutput(__FUNCTION__); + + $latest = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset); + $this->incrementalErrorOutputOffset = ftell($this->stderr); + + if (false === $latest) { + return ''; + } + + return $latest; + } + + /** + * Clears the process output. + * + * @return $this + */ + public function clearErrorOutput() + { + ftruncate($this->stderr, 0); + fseek($this->stderr, 0); + $this->incrementalErrorOutputOffset = 0; + + return $this; + } + + /** + * Returns the exit code returned by the process. + * + * @return int|null The exit status code, null if the Process is not terminated + */ + public function getExitCode() + { + $this->updateStatus(false); + + return $this->exitcode; + } + + /** + * Returns a string representation for the exit code returned by the process. + * + * This method relies on the Unix exit code status standardization + * and might not be relevant for other operating systems. + * + * @return string|null A string representation for the exit status code, null if the Process is not terminated + * + * @see http://tldp.org/LDP/abs/html/exitcodes.html + * @see http://en.wikipedia.org/wiki/Unix_signal + */ + public function getExitCodeText() + { + if (null === $exitcode = $this->getExitCode()) { + return null; + } + + return self::$exitCodes[$exitcode] ?? 'Unknown error'; + } + + /** + * Checks if the process ended successfully. + * + * @return bool true if the process ended successfully, false otherwise + */ + public function isSuccessful() + { + return 0 === $this->getExitCode(); + } + + /** + * Returns true if the child process has been terminated by an uncaught signal. + * + * It always returns false on Windows. + * + * @return bool + * + * @throws LogicException In case the process is not terminated + */ + public function hasBeenSignaled() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + return $this->processInformation['signaled']; + } + + /** + * Returns the number of the signal that caused the child process to terminate its execution. + * + * It is only meaningful if hasBeenSignaled() returns true. + * + * @return int + * + * @throws RuntimeException In case --enable-sigchild is activated + * @throws LogicException In case the process is not terminated + */ + public function getTermSignal() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + if ($this->isSigchildEnabled() && -1 === $this->processInformation['termsig']) { + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); + } + + return $this->processInformation['termsig']; + } + + /** + * Returns true if the child process has been stopped by a signal. + * + * It always returns false on Windows. + * + * @return bool + * + * @throws LogicException In case the process is not terminated + */ + public function hasBeenStopped() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + return $this->processInformation['stopped']; + } + + /** + * Returns the number of the signal that caused the child process to stop its execution. + * + * It is only meaningful if hasBeenStopped() returns true. + * + * @return int + * + * @throws LogicException In case the process is not terminated + */ + public function getStopSignal() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + return $this->processInformation['stopsig']; + } + + /** + * Checks if the process is currently running. + * + * @return bool true if the process is currently running, false otherwise + */ + public function isRunning() + { + if (self::STATUS_STARTED !== $this->status) { + return false; + } + + $this->updateStatus(false); + + return $this->processInformation['running']; + } + + /** + * Checks if the process has been started with no regard to the current state. + * + * @return bool true if status is ready, false otherwise + */ + public function isStarted() + { + return self::STATUS_READY != $this->status; + } + + /** + * Checks if the process is terminated. + * + * @return bool true if process is terminated, false otherwise + */ + public function isTerminated() + { + $this->updateStatus(false); + + return self::STATUS_TERMINATED == $this->status; + } + + /** + * Gets the process status. + * + * The status is one of: ready, started, terminated. + * + * @return string The current process status + */ + public function getStatus() + { + $this->updateStatus(false); + + return $this->status; + } + + /** + * Stops the process. + * + * @param int|float $timeout The timeout in seconds + * @param int $signal A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL (9) + * + * @return int|null The exit-code of the process or null if it's not running + */ + public function stop($timeout = 10, $signal = null) + { + $timeoutMicro = microtime(true) + $timeout; + if ($this->isRunning()) { + // given SIGTERM may not be defined and that "proc_terminate" uses the constant value and not the constant itself, we use the same here + $this->doSignal(15, false); + do { + usleep(1000); + } while ($this->isRunning() && microtime(true) < $timeoutMicro); + + if ($this->isRunning()) { + // Avoid exception here: process is supposed to be running, but it might have stopped just + // after this line. In any case, let's silently discard the error, we cannot do anything. + $this->doSignal($signal ?: 9, false); + } + } + + if ($this->isRunning()) { + if (isset($this->fallbackStatus['pid'])) { + unset($this->fallbackStatus['pid']); + + return $this->stop(0, $signal); + } + $this->close(); + } + + return $this->exitcode; + } + + /** + * Adds a line to the STDOUT stream. + * + * @internal + */ + public function addOutput(string $line) + { + $this->lastOutputTime = microtime(true); + + fseek($this->stdout, 0, \SEEK_END); + fwrite($this->stdout, $line); + fseek($this->stdout, $this->incrementalOutputOffset); + } + + /** + * Adds a line to the STDERR stream. + * + * @internal + */ + public function addErrorOutput(string $line) + { + $this->lastOutputTime = microtime(true); + + fseek($this->stderr, 0, \SEEK_END); + fwrite($this->stderr, $line); + fseek($this->stderr, $this->incrementalErrorOutputOffset); + } + + /** + * Gets the last output time in seconds. + */ + public function getLastOutputTime(): ?float + { + return $this->lastOutputTime; + } + + /** + * Gets the command line to be executed. + * + * @return string The command to execute + */ + public function getCommandLine() + { + return \is_array($this->commandline) ? implode(' ', array_map([$this, 'escapeArgument'], $this->commandline)) : $this->commandline; + } + + /** + * Sets the command line to be executed. + * + * @param string|array $commandline The command to execute + * + * @return $this + * + * @deprecated since Symfony 4.2. + */ + public function setCommandLine($commandline) + { + @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2.', __METHOD__), \E_USER_DEPRECATED); + + $this->commandline = $commandline; + + return $this; + } + + /** + * Gets the process timeout (max. runtime). + * + * @return float|null The timeout in seconds or null if it's disabled + */ + public function getTimeout() + { + return $this->timeout; + } + + /** + * Gets the process idle timeout (max. time since last output). + * + * @return float|null The timeout in seconds or null if it's disabled + */ + public function getIdleTimeout() + { + return $this->idleTimeout; + } + + /** + * Sets the process timeout (max. runtime) in seconds. + * + * To disable the timeout, set this value to null. + * + * @param int|float|null $timeout The timeout in seconds + * + * @return $this + * + * @throws InvalidArgumentException if the timeout is negative + */ + public function setTimeout($timeout) + { + $this->timeout = $this->validateTimeout($timeout); + + return $this; + } + + /** + * Sets the process idle timeout (max. time since last output). + * + * To disable the timeout, set this value to null. + * + * @param int|float|null $timeout The timeout in seconds + * + * @return $this + * + * @throws LogicException if the output is disabled + * @throws InvalidArgumentException if the timeout is negative + */ + public function setIdleTimeout($timeout) + { + if (null !== $timeout && $this->outputDisabled) { + throw new LogicException('Idle timeout can not be set while the output is disabled.'); + } + + $this->idleTimeout = $this->validateTimeout($timeout); + + return $this; + } + + /** + * Enables or disables the TTY mode. + * + * @param bool $tty True to enabled and false to disable + * + * @return $this + * + * @throws RuntimeException In case the TTY mode is not supported + */ + public function setTty($tty) + { + if ('\\' === \DIRECTORY_SEPARATOR && $tty) { + throw new RuntimeException('TTY mode is not supported on Windows platform.'); + } + + if ($tty && !self::isTtySupported()) { + throw new RuntimeException('TTY mode requires /dev/tty to be read/writable.'); + } + + $this->tty = (bool) $tty; + + return $this; + } + + /** + * Checks if the TTY mode is enabled. + * + * @return bool true if the TTY mode is enabled, false otherwise + */ + public function isTty() + { + return $this->tty; + } + + /** + * Sets PTY mode. + * + * @param bool $bool + * + * @return $this + */ + public function setPty($bool) + { + $this->pty = (bool) $bool; + + return $this; + } + + /** + * Returns PTY state. + * + * @return bool + */ + public function isPty() + { + return $this->pty; + } + + /** + * Gets the working directory. + * + * @return string|null The current working directory or null on failure + */ + public function getWorkingDirectory() + { + if (null === $this->cwd) { + // getcwd() will return false if any one of the parent directories does not have + // the readable or search mode set, even if the current directory does + return getcwd() ?: null; + } + + return $this->cwd; + } + + /** + * Sets the current working directory. + * + * @param string $cwd The new working directory + * + * @return $this + */ + public function setWorkingDirectory($cwd) + { + $this->cwd = $cwd; + + return $this; + } + + /** + * Gets the environment variables. + * + * @return array The current environment variables + */ + public function getEnv() + { + return $this->env; + } + + /** + * Sets the environment variables. + * + * @param array<string|\Stringable> $env The new environment variables + * + * @return $this + */ + public function setEnv(array $env) + { + $this->env = $env; + + return $this; + } + + /** + * Gets the Process input. + * + * @return resource|string|\Iterator|null The Process input + */ + public function getInput() + { + return $this->input; + } + + /** + * Sets the input. + * + * This content will be passed to the underlying process standard input. + * + * @param string|int|float|bool|resource|\Traversable|null $input The content + * + * @return $this + * + * @throws LogicException In case the process is running + */ + public function setInput($input) + { + if ($this->isRunning()) { + throw new LogicException('Input can not be set while the process is running.'); + } + + $this->input = ProcessUtils::validateInput(__METHOD__, $input); + + return $this; + } + + /** + * Sets whether environment variables will be inherited or not. + * + * @param bool $inheritEnv + * + * @return $this + * + * @deprecated since Symfony 4.4, env variables are always inherited + */ + public function inheritEnvironmentVariables($inheritEnv = true) + { + @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.4, env variables are always inherited.', __METHOD__), \E_USER_DEPRECATED); + + if (!$inheritEnv) { + throw new InvalidArgumentException('Not inheriting environment variables is not supported.'); + } + + return $this; + } + + /** + * Performs a check between the timeout definition and the time the process started. + * + * In case you run a background process (with the start method), you should + * trigger this method regularly to ensure the process timeout + * + * @throws ProcessTimedOutException In case the timeout was reached + */ + public function checkTimeout() + { + if (self::STATUS_STARTED !== $this->status) { + return; + } + + if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) { + $this->stop(0); + + throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_GENERAL); + } + + if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) { + $this->stop(0); + + throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_IDLE); + } + } + + /** + * Returns whether TTY is supported on the current operating system. + */ + public static function isTtySupported(): bool + { + static $isTtySupported; + + if (null === $isTtySupported) { + $isTtySupported = (bool) @proc_open('echo 1 >/dev/null', [['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w']], $pipes); + } + + return $isTtySupported; + } + + /** + * Returns whether PTY is supported on the current operating system. + * + * @return bool + */ + public static function isPtySupported() + { + static $result; + + if (null !== $result) { + return $result; + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + return $result = false; + } + + return $result = (bool) @proc_open('echo 1 >/dev/null', [['pty'], ['pty'], ['pty']], $pipes); + } + + /** + * Creates the descriptors needed by the proc_open. + */ + private function getDescriptors(): array + { + if ($this->input instanceof \Iterator) { + $this->input->rewind(); + } + if ('\\' === \DIRECTORY_SEPARATOR) { + $this->processPipes = new WindowsPipes($this->input, !$this->outputDisabled || $this->hasCallback); + } else { + $this->processPipes = new UnixPipes($this->isTty(), $this->isPty(), $this->input, !$this->outputDisabled || $this->hasCallback); + } + + return $this->processPipes->getDescriptors(); + } + + /** + * Builds up the callback used by wait(). + * + * The callbacks adds all occurred output to the specific buffer and calls + * the user callback (if present) with the received output. + * + * @param callable|null $callback The user defined PHP callback + * + * @return \Closure A PHP closure + */ + protected function buildCallback(callable $callback = null) + { + if ($this->outputDisabled) { + return function ($type, $data) use ($callback): bool { + return null !== $callback && $callback($type, $data); + }; + } + + $out = self::OUT; + + return function ($type, $data) use ($callback, $out): bool { + if ($out == $type) { + $this->addOutput($data); + } else { + $this->addErrorOutput($data); + } + + return null !== $callback && $callback($type, $data); + }; + } + + /** + * Updates the status of the process, reads pipes. + * + * @param bool $blocking Whether to use a blocking read call + */ + protected function updateStatus($blocking) + { + if (self::STATUS_STARTED !== $this->status) { + return; + } + + $this->processInformation = proc_get_status($this->process); + $running = $this->processInformation['running']; + + $this->readPipes($running && $blocking, '\\' !== \DIRECTORY_SEPARATOR || !$running); + + if ($this->fallbackStatus && $this->isSigchildEnabled()) { + $this->processInformation = $this->fallbackStatus + $this->processInformation; + } + + if (!$running) { + $this->close(); + } + } + + /** + * Returns whether PHP has been compiled with the '--enable-sigchild' option or not. + * + * @return bool + */ + protected function isSigchildEnabled() + { + if (null !== self::$sigchild) { + return self::$sigchild; + } + + if (!\function_exists('phpinfo')) { + return self::$sigchild = false; + } + + ob_start(); + phpinfo(\INFO_GENERAL); + + return self::$sigchild = str_contains(ob_get_clean(), '--enable-sigchild'); + } + + /** + * Reads pipes for the freshest output. + * + * @param string $caller The name of the method that needs fresh outputs + * @param bool $blocking Whether to use blocking calls or not + * + * @throws LogicException in case output has been disabled or process is not started + */ + private function readPipesForOutput(string $caller, bool $blocking = false) + { + if ($this->outputDisabled) { + throw new LogicException('Output has been disabled.'); + } + + $this->requireProcessIsStarted($caller); + + $this->updateStatus($blocking); + } + + /** + * Validates and returns the filtered timeout. + * + * @throws InvalidArgumentException if the given timeout is a negative number + */ + private function validateTimeout(?float $timeout): ?float + { + $timeout = (float) $timeout; + + if (0.0 === $timeout) { + $timeout = null; + } elseif ($timeout < 0) { + throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); + } + + return $timeout; + } + + /** + * Reads pipes, executes callback. + * + * @param bool $blocking Whether to use blocking calls or not + * @param bool $close Whether to close file handles or not + */ + private function readPipes(bool $blocking, bool $close) + { + $result = $this->processPipes->readAndWrite($blocking, $close); + + $callback = $this->callback; + foreach ($result as $type => $data) { + if (3 !== $type) { + $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data); + } elseif (!isset($this->fallbackStatus['signaled'])) { + $this->fallbackStatus['exitcode'] = (int) $data; + } + } + } + + /** + * Closes process resource, closes file handles, sets the exitcode. + * + * @return int The exitcode + */ + private function close(): int + { + $this->processPipes->close(); + if (\is_resource($this->process)) { + proc_close($this->process); + } + $this->exitcode = $this->processInformation['exitcode']; + $this->status = self::STATUS_TERMINATED; + + if (-1 === $this->exitcode) { + if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) { + // if process has been signaled, no exitcode but a valid termsig, apply Unix convention + $this->exitcode = 128 + $this->processInformation['termsig']; + } elseif ($this->isSigchildEnabled()) { + $this->processInformation['signaled'] = true; + $this->processInformation['termsig'] = -1; + } + } + + // Free memory from self-reference callback created by buildCallback + // Doing so in other contexts like __destruct or by garbage collector is ineffective + // Now pipes are closed, so the callback is no longer necessary + $this->callback = null; + + return $this->exitcode; + } + + /** + * Resets data related to the latest run of the process. + */ + private function resetProcessData() + { + $this->starttime = null; + $this->callback = null; + $this->exitcode = null; + $this->fallbackStatus = []; + $this->processInformation = null; + $this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+'); + $this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+'); + $this->process = null; + $this->latestSignal = null; + $this->status = self::STATUS_READY; + $this->incrementalOutputOffset = 0; + $this->incrementalErrorOutputOffset = 0; + } + + /** + * Sends a POSIX signal to the process. + * + * @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants) + * @param bool $throwException Whether to throw exception in case signal failed + * + * @throws LogicException In case the process is not running + * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed + * @throws RuntimeException In case of failure + */ + private function doSignal(int $signal, bool $throwException): bool + { + if (null === $pid = $this->getPid()) { + if ($throwException) { + throw new LogicException('Can not send signal on a non running process.'); + } + + return false; + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + exec(sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode); + if ($exitCode && $this->isRunning()) { + if ($throwException) { + throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output))); + } + + return false; + } + } else { + if (!$this->isSigchildEnabled()) { + $ok = @proc_terminate($this->process, $signal); + } elseif (\function_exists('posix_kill')) { + $ok = @posix_kill($pid, $signal); + } elseif ($ok = proc_open(sprintf('kill -%d %d', $signal, $pid), [2 => ['pipe', 'w']], $pipes)) { + $ok = false === fgets($pipes[2]); + } + if (!$ok) { + if ($throwException) { + throw new RuntimeException(sprintf('Error while sending signal "%s".', $signal)); + } + + return false; + } + } + + $this->latestSignal = $signal; + $this->fallbackStatus['signaled'] = true; + $this->fallbackStatus['exitcode'] = -1; + $this->fallbackStatus['termsig'] = $this->latestSignal; + + return true; + } + + private function prepareWindowsCommandLine(string $cmd, array &$env): string + { + $uid = uniqid('', true); + $varCount = 0; + $varCache = []; + $cmd = preg_replace_callback( + '/"(?:( + [^"%!^]*+ + (?: + (?: !LF! | "(?:\^[%!^])?+" ) + [^"%!^]*+ + )++ + ) | [^"]*+ )"/x', + function ($m) use (&$env, &$varCache, &$varCount, $uid) { + if (!isset($m[1])) { + return $m[0]; + } + if (isset($varCache[$m[0]])) { + return $varCache[$m[0]]; + } + if (str_contains($value = $m[1], "\0")) { + $value = str_replace("\0", '?', $value); + } + if (false === strpbrk($value, "\"%!\n")) { + return '"'.$value.'"'; + } + + $value = str_replace(['!LF!', '"^!"', '"^%"', '"^^"', '""'], ["\n", '!', '%', '^', '"'], $value); + $value = '"'.preg_replace('/(\\\\*)"/', '$1$1\\"', $value).'"'; + $var = $uid.++$varCount; + + $env[$var] = $value; + + return $varCache[$m[0]] = '!'.$var.'!'; + }, + $cmd + ); + + $cmd = 'cmd /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $cmd).')'; + foreach ($this->processPipes->getFiles() as $offset => $filename) { + $cmd .= ' '.$offset.'>"'.$filename.'"'; + } + + return $cmd; + } + + /** + * Ensures the process is running or terminated, throws a LogicException if the process has a not started. + * + * @throws LogicException if the process has not run + */ + private function requireProcessIsStarted(string $functionName) + { + if (!$this->isStarted()) { + throw new LogicException(sprintf('Process must be started before calling "%s()".', $functionName)); + } + } + + /** + * Ensures the process is terminated, throws a LogicException if the process has a status different than "terminated". + * + * @throws LogicException if the process is not yet terminated + */ + private function requireProcessIsTerminated(string $functionName) + { + if (!$this->isTerminated()) { + throw new LogicException(sprintf('Process must be terminated before calling "%s()".', $functionName)); + } + } + + /** + * Escapes a string to be used as a shell argument. + */ + private function escapeArgument(?string $argument): string + { + if ('' === $argument || null === $argument) { + return '""'; + } + if ('\\' !== \DIRECTORY_SEPARATOR) { + return "'".str_replace("'", "'\\''", $argument)."'"; + } + if (str_contains($argument, "\0")) { + $argument = str_replace("\0", '?', $argument); + } + if (!preg_match('/[\/()%!^"<>&|\s]/', $argument)) { + return $argument; + } + $argument = preg_replace('/(\\\\+)$/', '$1$1', $argument); + + return '"'.str_replace(['"', '^', '%', '!', "\n"], ['""', '"^^"', '"^%"', '"^!"', '!LF!'], $argument).'"'; + } + + private function replacePlaceholders(string $commandline, array $env) + { + return preg_replace_callback('/"\$\{:([_a-zA-Z]++[_a-zA-Z0-9]*+)\}"/', function ($matches) use ($commandline, $env) { + if (!isset($env[$matches[1]]) || false === $env[$matches[1]]) { + throw new InvalidArgumentException(sprintf('Command line is missing a value for parameter "%s": ', $matches[1]).$commandline); + } + + return $this->escapeArgument($env[$matches[1]]); + }, $commandline); + } + + private function getDefaultEnv(): array + { + $env = getenv(); + $env = ('\\' === \DIRECTORY_SEPARATOR ? array_intersect_ukey($env, $_SERVER, 'strcasecmp') : array_intersect_key($env, $_SERVER)) ?: $env; + + return $_ENV + ('\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($env, $_ENV, 'strcasecmp') : $env); + } +} diff --git a/vendor/symfony/process/ProcessUtils.php b/vendor/symfony/process/ProcessUtils.php new file mode 100644 index 0000000000..121693baaa --- /dev/null +++ b/vendor/symfony/process/ProcessUtils.php @@ -0,0 +1,69 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\InvalidArgumentException; + +/** + * ProcessUtils is a bunch of utility methods. + * + * This class contains static methods only and is not meant to be instantiated. + * + * @author Martin Hasoň <martin.hason@gmail.com> + */ +class ProcessUtils +{ + /** + * This class should not be instantiated. + */ + private function __construct() + { + } + + /** + * Validates and normalizes a Process input. + * + * @param string $caller The name of method call that validates the input + * @param mixed $input The input to validate + * + * @return mixed The validated input + * + * @throws InvalidArgumentException In case the input is not valid + */ + public static function validateInput($caller, $input) + { + if (null !== $input) { + if (\is_resource($input)) { + return $input; + } + if (\is_string($input)) { + return $input; + } + if (\is_scalar($input)) { + return (string) $input; + } + if ($input instanceof Process) { + return $input->getIterator($input::ITER_SKIP_ERR); + } + if ($input instanceof \Iterator) { + return $input; + } + if ($input instanceof \Traversable) { + return new \IteratorIterator($input); + } + + throw new InvalidArgumentException(sprintf('"%s" only accepts strings, Traversable objects or stream resources.', $caller)); + } + + return $input; + } +} diff --git a/vendor/symfony/process/README.md b/vendor/symfony/process/README.md new file mode 100644 index 0000000000..afce5e45ee --- /dev/null +++ b/vendor/symfony/process/README.md @@ -0,0 +1,13 @@ +Process Component +================= + +The Process component executes commands in sub-processes. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/process.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/process/composer.json b/vendor/symfony/process/composer.json new file mode 100644 index 0000000000..c0f7599f21 --- /dev/null +++ b/vendor/symfony/process/composer.json @@ -0,0 +1,29 @@ +{ + "name": "symfony/process", + "type": "library", + "description": "Executes commands in sub-processes", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1.3", + "symfony/polyfill-php80": "^1.16" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Process\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/service-contracts/.gitignore b/vendor/symfony/service-contracts/.gitignore new file mode 100644 index 0000000000..c49a5d8df5 --- /dev/null +++ b/vendor/symfony/service-contracts/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/service-contracts/Attribute/Required.php b/vendor/symfony/service-contracts/Attribute/Required.php new file mode 100644 index 0000000000..9df851189a --- /dev/null +++ b/vendor/symfony/service-contracts/Attribute/Required.php @@ -0,0 +1,25 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Attribute; + +/** + * A required dependency. + * + * This attribute indicates that a property holds a required dependency. The annotated property or method should be + * considered during the instantiation process of the containing class. + * + * @author Alexander M. Turek <me@derrabus.de> + */ +#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] +final class Required +{ +} diff --git a/vendor/symfony/service-contracts/Attribute/SubscribedService.php b/vendor/symfony/service-contracts/Attribute/SubscribedService.php new file mode 100644 index 0000000000..10d1bc38e8 --- /dev/null +++ b/vendor/symfony/service-contracts/Attribute/SubscribedService.php @@ -0,0 +1,33 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Attribute; + +use Symfony\Contracts\Service\ServiceSubscriberTrait; + +/** + * Use with {@see ServiceSubscriberTrait} to mark a method's return type + * as a subscribed service. + * + * @author Kevin Bond <kevinbond@gmail.com> + */ +#[\Attribute(\Attribute::TARGET_METHOD)] +final class SubscribedService +{ + /** + * @param string|null $key The key to use for the service + * If null, use "ClassName::methodName" + */ + public function __construct( + public ?string $key = null + ) { + } +} diff --git a/vendor/symfony/service-contracts/CHANGELOG.md b/vendor/symfony/service-contracts/CHANGELOG.md new file mode 100644 index 0000000000..7932e26132 --- /dev/null +++ b/vendor/symfony/service-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/vendor/symfony/service-contracts/LICENSE b/vendor/symfony/service-contracts/LICENSE new file mode 100644 index 0000000000..74cdc2dbf6 --- /dev/null +++ b/vendor/symfony/service-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/service-contracts/README.md b/vendor/symfony/service-contracts/README.md new file mode 100644 index 0000000000..41e054a101 --- /dev/null +++ b/vendor/symfony/service-contracts/README.md @@ -0,0 +1,9 @@ +Symfony Service Contracts +========================= + +A set of abstractions extracted out of the Symfony components. + +Can be used to build on semantics that the Symfony components proved useful - and +that already have battle tested implementations. + +See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/vendor/symfony/service-contracts/ResetInterface.php b/vendor/symfony/service-contracts/ResetInterface.php new file mode 100644 index 0000000000..1af1075eee --- /dev/null +++ b/vendor/symfony/service-contracts/ResetInterface.php @@ -0,0 +1,30 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +/** + * Provides a way to reset an object to its initial state. + * + * When calling the "reset()" method on an object, it should be put back to its + * initial state. This usually means clearing any internal buffers and forwarding + * the call to internal dependencies. All properties of the object should be put + * back to the same state it had when it was first ready to use. + * + * This method could be called, for example, to recycle objects that are used as + * services, so that they can be used to handle several requests in the same + * process loop (note that we advise making your services stateless instead of + * implementing this interface when possible.) + */ +interface ResetInterface +{ + public function reset(); +} diff --git a/vendor/symfony/service-contracts/ServiceLocatorTrait.php b/vendor/symfony/service-contracts/ServiceLocatorTrait.php new file mode 100644 index 0000000000..74dfa4362e --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceLocatorTrait.php @@ -0,0 +1,128 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(ContainerExceptionInterface::class); +class_exists(NotFoundExceptionInterface::class); + +/** + * A trait to help implement ServiceProviderInterface. + * + * @author Robin Chalas <robin.chalas@gmail.com> + * @author Nicolas Grekas <p@tchwork.com> + */ +trait ServiceLocatorTrait +{ + private $factories; + private $loading = []; + private $providedTypes; + + /** + * @param callable[] $factories + */ + public function __construct(array $factories) + { + $this->factories = $factories; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function has(string $id) + { + return isset($this->factories[$id]); + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function get(string $id) + { + if (!isset($this->factories[$id])) { + throw $this->createNotFoundException($id); + } + + if (isset($this->loading[$id])) { + $ids = array_values($this->loading); + $ids = \array_slice($this->loading, array_search($id, $ids)); + $ids[] = $id; + + throw $this->createCircularReferenceException($id, $ids); + } + + $this->loading[$id] = $id; + try { + return $this->factories[$id]($this); + } finally { + unset($this->loading[$id]); + } + } + + /** + * {@inheritdoc} + */ + public function getProvidedServices(): array + { + if (null === $this->providedTypes) { + $this->providedTypes = []; + + foreach ($this->factories as $name => $factory) { + if (!\is_callable($factory)) { + $this->providedTypes[$name] = '?'; + } else { + $type = (new \ReflectionFunction($factory))->getReturnType(); + + $this->providedTypes[$name] = $type ? ($type->allowsNull() ? '?' : '').($type instanceof \ReflectionNamedType ? $type->getName() : $type) : '?'; + } + } + } + + return $this->providedTypes; + } + + private function createNotFoundException(string $id): NotFoundExceptionInterface + { + if (!$alternatives = array_keys($this->factories)) { + $message = 'is empty...'; + } else { + $last = array_pop($alternatives); + if ($alternatives) { + $message = sprintf('only knows about the "%s" and "%s" services.', implode('", "', $alternatives), $last); + } else { + $message = sprintf('only knows about the "%s" service.', $last); + } + } + + if ($this->loading) { + $message = sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $message); + } else { + $message = sprintf('Service "%s" not found: the current service locator %s', $id, $message); + } + + return new class($message) extends \InvalidArgumentException implements NotFoundExceptionInterface { + }; + } + + private function createCircularReferenceException(string $id, array $path): ContainerExceptionInterface + { + return new class(sprintf('Circular reference detected for service "%s", path: "%s".', $id, implode(' -> ', $path))) extends \RuntimeException implements ContainerExceptionInterface { + }; + } +} diff --git a/vendor/symfony/service-contracts/ServiceProviderInterface.php b/vendor/symfony/service-contracts/ServiceProviderInterface.php new file mode 100644 index 0000000000..c60ad0bd4b --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceProviderInterface.php @@ -0,0 +1,36 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerInterface; + +/** + * A ServiceProviderInterface exposes the identifiers and the types of services provided by a container. + * + * @author Nicolas Grekas <p@tchwork.com> + * @author Mateusz Sip <mateusz.sip@gmail.com> + */ +interface ServiceProviderInterface extends ContainerInterface +{ + /** + * Returns an associative array of service types keyed by the identifiers provided by the current container. + * + * Examples: + * + * * ['logger' => 'Psr\Log\LoggerInterface'] means the object provides a service named "logger" that implements Psr\Log\LoggerInterface + * * ['foo' => '?'] means the container provides service name "foo" of unspecified type + * * ['bar' => '?Bar\Baz'] means the container provides a service "bar" of type Bar\Baz|null + * + * @return string[] The provided service types, keyed by service names + */ + public function getProvidedServices(): array; +} diff --git a/vendor/symfony/service-contracts/ServiceSubscriberInterface.php b/vendor/symfony/service-contracts/ServiceSubscriberInterface.php new file mode 100644 index 0000000000..098ab908cd --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceSubscriberInterface.php @@ -0,0 +1,53 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +/** + * A ServiceSubscriber exposes its dependencies via the static {@link getSubscribedServices} method. + * + * The getSubscribedServices method returns an array of service types required by such instances, + * optionally keyed by the service names used internally. Service types that start with an interrogation + * mark "?" are optional, while the other ones are mandatory service dependencies. + * + * The injected service locators SHOULD NOT allow access to any other services not specified by the method. + * + * It is expected that ServiceSubscriber instances consume PSR-11-based service locators internally. + * This interface does not dictate any injection method for these service locators, although constructor + * injection is recommended. + * + * @author Nicolas Grekas <p@tchwork.com> + */ +interface ServiceSubscriberInterface +{ + /** + * Returns an array of service types required by such instances, optionally keyed by the service names used internally. + * + * For mandatory dependencies: + * + * * ['logger' => 'Psr\Log\LoggerInterface'] means the objects use the "logger" name + * internally to fetch a service which must implement Psr\Log\LoggerInterface. + * * ['loggers' => 'Psr\Log\LoggerInterface[]'] means the objects use the "loggers" name + * internally to fetch an iterable of Psr\Log\LoggerInterface instances. + * * ['Psr\Log\LoggerInterface'] is a shortcut for + * * ['Psr\Log\LoggerInterface' => 'Psr\Log\LoggerInterface'] + * + * otherwise: + * + * * ['logger' => '?Psr\Log\LoggerInterface'] denotes an optional dependency + * * ['loggers' => '?Psr\Log\LoggerInterface[]'] denotes an optional iterable dependency + * * ['?Psr\Log\LoggerInterface'] is a shortcut for + * * ['Psr\Log\LoggerInterface' => '?Psr\Log\LoggerInterface'] + * + * @return string[] The required service types, optionally keyed by service names + */ + public static function getSubscribedServices(); +} diff --git a/vendor/symfony/service-contracts/ServiceSubscriberTrait.php b/vendor/symfony/service-contracts/ServiceSubscriberTrait.php new file mode 100644 index 0000000000..16e3eb2c19 --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceSubscriberTrait.php @@ -0,0 +1,109 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerInterface; +use Symfony\Contracts\Service\Attribute\SubscribedService; + +/** + * Implementation of ServiceSubscriberInterface that determines subscribed services from + * method return types. Service ids are available as "ClassName::methodName". + * + * @author Kevin Bond <kevinbond@gmail.com> + */ +trait ServiceSubscriberTrait +{ + /** @var ContainerInterface */ + protected $container; + + /** + * {@inheritdoc} + */ + public static function getSubscribedServices(): array + { + $services = method_exists(get_parent_class(self::class) ?: '', __FUNCTION__) ? parent::getSubscribedServices() : []; + $attributeOptIn = false; + + if (\PHP_VERSION_ID >= 80000) { + foreach ((new \ReflectionClass(self::class))->getMethods() as $method) { + if (self::class !== $method->getDeclaringClass()->name) { + continue; + } + + if (!$attribute = $method->getAttributes(SubscribedService::class)[0] ?? null) { + continue; + } + + if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) { + throw new \LogicException(sprintf('Cannot use "%s" on method "%s::%s()" (can only be used on non-static, non-abstract methods with no parameters).', SubscribedService::class, self::class, $method->name)); + } + + if (!$returnType = $method->getReturnType()) { + throw new \LogicException(sprintf('Cannot use "%s" on methods without a return type in "%s::%s()".', SubscribedService::class, $method->name, self::class)); + } + + $serviceId = $returnType instanceof \ReflectionNamedType ? $returnType->getName() : (string) $returnType; + + if ($returnType->allowsNull()) { + $serviceId = '?'.$serviceId; + } + + $services[$attribute->newInstance()->key ?? self::class.'::'.$method->name] = $serviceId; + $attributeOptIn = true; + } + } + + if (!$attributeOptIn) { + foreach ((new \ReflectionClass(self::class))->getMethods() as $method) { + if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) { + continue; + } + + if (self::class !== $method->getDeclaringClass()->name) { + continue; + } + + if (!($returnType = $method->getReturnType()) instanceof \ReflectionNamedType) { + continue; + } + + if ($returnType->isBuiltin()) { + continue; + } + + if (\PHP_VERSION_ID >= 80000) { + trigger_deprecation('symfony/service-contracts', '2.5', 'Using "%s" in "%s" without using the "%s" attribute on any method is deprecated.', ServiceSubscriberTrait::class, self::class, SubscribedService::class); + } + + $services[self::class.'::'.$method->name] = '?'.($returnType instanceof \ReflectionNamedType ? $returnType->getName() : $returnType); + } + } + + return $services; + } + + /** + * @required + * + * @return ContainerInterface|null + */ + public function setContainer(ContainerInterface $container) + { + $this->container = $container; + + if (method_exists(get_parent_class(self::class) ?: '', __FUNCTION__)) { + return parent::setContainer($container); + } + + return null; + } +} diff --git a/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php b/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php new file mode 100644 index 0000000000..2a1b565f50 --- /dev/null +++ b/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php @@ -0,0 +1,95 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Test; + +use PHPUnit\Framework\TestCase; +use Psr\Container\ContainerInterface; +use Symfony\Contracts\Service\ServiceLocatorTrait; + +abstract class ServiceLocatorTest extends TestCase +{ + /** + * @return ContainerInterface + */ + protected function getServiceLocator(array $factories) + { + return new class($factories) implements ContainerInterface { + use ServiceLocatorTrait; + }; + } + + public function testHas() + { + $locator = $this->getServiceLocator([ + 'foo' => function () { return 'bar'; }, + 'bar' => function () { return 'baz'; }, + function () { return 'dummy'; }, + ]); + + $this->assertTrue($locator->has('foo')); + $this->assertTrue($locator->has('bar')); + $this->assertFalse($locator->has('dummy')); + } + + public function testGet() + { + $locator = $this->getServiceLocator([ + 'foo' => function () { return 'bar'; }, + 'bar' => function () { return 'baz'; }, + ]); + + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame('baz', $locator->get('bar')); + } + + public function testGetDoesNotMemoize() + { + $i = 0; + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$i) { + ++$i; + + return 'bar'; + }, + ]); + + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame(2, $i); + } + + public function testThrowsOnUndefinedInternalService() + { + if (!$this->getExpectedException()) { + $this->expectException(\Psr\Container\NotFoundExceptionInterface::class); + $this->expectExceptionMessage('The service "foo" has a dependency on a non-existent service "bar". This locator only knows about the "foo" service.'); + } + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$locator) { return $locator->get('bar'); }, + ]); + + $locator->get('foo'); + } + + public function testThrowsOnCircularReference() + { + $this->expectException(\Psr\Container\ContainerExceptionInterface::class); + $this->expectExceptionMessage('Circular reference detected for service "bar", path: "bar -> baz -> bar".'); + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$locator) { return $locator->get('bar'); }, + 'bar' => function () use (&$locator) { return $locator->get('baz'); }, + 'baz' => function () use (&$locator) { return $locator->get('bar'); }, + ]); + + $locator->get('foo'); + } +} diff --git a/vendor/symfony/service-contracts/composer.json b/vendor/symfony/service-contracts/composer.json new file mode 100644 index 0000000000..f058637010 --- /dev/null +++ b/vendor/symfony/service-contracts/composer.json @@ -0,0 +1,42 @@ +{ + "name": "symfony/service-contracts", + "type": "library", + "description": "Generic abstractions related to writing services", + "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "autoload": { + "psr-4": { "Symfony\\Contracts\\Service\\": "" } + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/vendor/symfony/var-dumper/CHANGELOG.md b/vendor/symfony/var-dumper/CHANGELOG.md new file mode 100644 index 0000000000..f58ed31706 --- /dev/null +++ b/vendor/symfony/var-dumper/CHANGELOG.md @@ -0,0 +1,72 @@ +CHANGELOG +========= + +5.4 +--- + + * Add ability to style integer and double values independently + * Add casters for Symfony's UUIDs and ULIDs + * Add support for `Fiber` + +5.2.0 +----- + + * added support for PHPUnit `--colors` option + * added `VAR_DUMPER_FORMAT=server` env var value support + * prevent replacing the handler when the `VAR_DUMPER_FORMAT` env var is set + +5.1.0 +----- + + * added `RdKafka` support + +4.4.0 +----- + + * added `VarDumperTestTrait::setUpVarDumper()` and `VarDumperTestTrait::tearDownVarDumper()` + to configure casters & flags to use in tests + * added `ImagineCaster` and infrastructure to dump images + * added the stamps of a message after it is dispatched in `TraceableMessageBus` and `MessengerDataCollector` collected data + * added `UuidCaster` + * made all casters final + * added support for the `NO_COLOR` env var (https://no-color.org/) + +4.3.0 +----- + + * added `DsCaster` to support dumping the contents of data structures from the Ds extension + +4.2.0 +----- + + * support selecting the format to use by setting the environment variable `VAR_DUMPER_FORMAT` to `html` or `cli` + +4.1.0 +----- + + * added a `ServerDumper` to send serialized Data clones to a server + * added a `ServerDumpCommand` and `DumpServer` to run a server collecting + and displaying dumps on a single place with multiple formats support + * added `CliDescriptor` and `HtmlDescriptor` descriptors for `server:dump` CLI and HTML formats support + +4.0.0 +----- + + * support for passing `\ReflectionClass` instances to the `Caster::castObject()` + method has been dropped, pass class names as strings instead + * the `Data::getRawData()` method has been removed + * the `VarDumperTestTrait::assertDumpEquals()` method expects a 3rd `$filter = 0` + argument and moves `$message = ''` argument at 4th position. + * the `VarDumperTestTrait::assertDumpMatchesFormat()` method expects a 3rd `$filter = 0` + argument and moves `$message = ''` argument at 4th position. + +3.4.0 +----- + + * added `AbstractCloner::setMinDepth()` function to ensure minimum tree depth + * deprecated `MongoCaster` + +2.7.0 +----- + + * deprecated `Cloner\Data::getLimitedClone()`. Use `withMaxDepth`, `withMaxItemsPerDepth` or `withRefHandles` instead. diff --git a/vendor/symfony/var-dumper/Caster/AmqpCaster.php b/vendor/symfony/var-dumper/Caster/AmqpCaster.php new file mode 100644 index 0000000000..dc3b62198a --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/AmqpCaster.php @@ -0,0 +1,212 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts Amqp related classes to array representation. + * + * @author Grégoire Pineau <lyrixx@lyrixx.info> + * + * @final + */ +class AmqpCaster +{ + private const FLAGS = [ + \AMQP_DURABLE => 'AMQP_DURABLE', + \AMQP_PASSIVE => 'AMQP_PASSIVE', + \AMQP_EXCLUSIVE => 'AMQP_EXCLUSIVE', + \AMQP_AUTODELETE => 'AMQP_AUTODELETE', + \AMQP_INTERNAL => 'AMQP_INTERNAL', + \AMQP_NOLOCAL => 'AMQP_NOLOCAL', + \AMQP_AUTOACK => 'AMQP_AUTOACK', + \AMQP_IFEMPTY => 'AMQP_IFEMPTY', + \AMQP_IFUNUSED => 'AMQP_IFUNUSED', + \AMQP_MANDATORY => 'AMQP_MANDATORY', + \AMQP_IMMEDIATE => 'AMQP_IMMEDIATE', + \AMQP_MULTIPLE => 'AMQP_MULTIPLE', + \AMQP_NOWAIT => 'AMQP_NOWAIT', + \AMQP_REQUEUE => 'AMQP_REQUEUE', + ]; + + private const EXCHANGE_TYPES = [ + \AMQP_EX_TYPE_DIRECT => 'AMQP_EX_TYPE_DIRECT', + \AMQP_EX_TYPE_FANOUT => 'AMQP_EX_TYPE_FANOUT', + \AMQP_EX_TYPE_TOPIC => 'AMQP_EX_TYPE_TOPIC', + \AMQP_EX_TYPE_HEADERS => 'AMQP_EX_TYPE_HEADERS', + ]; + + public static function castConnection(\AMQPConnection $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'is_connected' => $c->isConnected(), + ]; + + // Recent version of the extension already expose private properties + if (isset($a["\x00AMQPConnection\x00login"])) { + return $a; + } + + // BC layer in the amqp lib + if (method_exists($c, 'getReadTimeout')) { + $timeout = $c->getReadTimeout(); + } else { + $timeout = $c->getTimeout(); + } + + $a += [ + $prefix.'is_connected' => $c->isConnected(), + $prefix.'login' => $c->getLogin(), + $prefix.'password' => $c->getPassword(), + $prefix.'host' => $c->getHost(), + $prefix.'vhost' => $c->getVhost(), + $prefix.'port' => $c->getPort(), + $prefix.'read_timeout' => $timeout, + ]; + + return $a; + } + + public static function castChannel(\AMQPChannel $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'is_connected' => $c->isConnected(), + $prefix.'channel_id' => $c->getChannelId(), + ]; + + // Recent version of the extension already expose private properties + if (isset($a["\x00AMQPChannel\x00connection"])) { + return $a; + } + + $a += [ + $prefix.'connection' => $c->getConnection(), + $prefix.'prefetch_size' => $c->getPrefetchSize(), + $prefix.'prefetch_count' => $c->getPrefetchCount(), + ]; + + return $a; + } + + public static function castQueue(\AMQPQueue $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'flags' => self::extractFlags($c->getFlags()), + ]; + + // Recent version of the extension already expose private properties + if (isset($a["\x00AMQPQueue\x00name"])) { + return $a; + } + + $a += [ + $prefix.'connection' => $c->getConnection(), + $prefix.'channel' => $c->getChannel(), + $prefix.'name' => $c->getName(), + $prefix.'arguments' => $c->getArguments(), + ]; + + return $a; + } + + public static function castExchange(\AMQPExchange $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'flags' => self::extractFlags($c->getFlags()), + ]; + + $type = isset(self::EXCHANGE_TYPES[$c->getType()]) ? new ConstStub(self::EXCHANGE_TYPES[$c->getType()], $c->getType()) : $c->getType(); + + // Recent version of the extension already expose private properties + if (isset($a["\x00AMQPExchange\x00name"])) { + $a["\x00AMQPExchange\x00type"] = $type; + + return $a; + } + + $a += [ + $prefix.'connection' => $c->getConnection(), + $prefix.'channel' => $c->getChannel(), + $prefix.'name' => $c->getName(), + $prefix.'type' => $type, + $prefix.'arguments' => $c->getArguments(), + ]; + + return $a; + } + + public static function castEnvelope(\AMQPEnvelope $c, array $a, Stub $stub, bool $isNested, int $filter = 0) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $deliveryMode = new ConstStub($c->getDeliveryMode().(2 === $c->getDeliveryMode() ? ' (persistent)' : ' (non-persistent)'), $c->getDeliveryMode()); + + // Recent version of the extension already expose private properties + if (isset($a["\x00AMQPEnvelope\x00body"])) { + $a["\0AMQPEnvelope\0delivery_mode"] = $deliveryMode; + + return $a; + } + + if (!($filter & Caster::EXCLUDE_VERBOSE)) { + $a += [$prefix.'body' => $c->getBody()]; + } + + $a += [ + $prefix.'delivery_tag' => $c->getDeliveryTag(), + $prefix.'is_redelivery' => $c->isRedelivery(), + $prefix.'exchange_name' => $c->getExchangeName(), + $prefix.'routing_key' => $c->getRoutingKey(), + $prefix.'content_type' => $c->getContentType(), + $prefix.'content_encoding' => $c->getContentEncoding(), + $prefix.'headers' => $c->getHeaders(), + $prefix.'delivery_mode' => $deliveryMode, + $prefix.'priority' => $c->getPriority(), + $prefix.'correlation_id' => $c->getCorrelationId(), + $prefix.'reply_to' => $c->getReplyTo(), + $prefix.'expiration' => $c->getExpiration(), + $prefix.'message_id' => $c->getMessageId(), + $prefix.'timestamp' => $c->getTimeStamp(), + $prefix.'type' => $c->getType(), + $prefix.'user_id' => $c->getUserId(), + $prefix.'app_id' => $c->getAppId(), + ]; + + return $a; + } + + private static function extractFlags(int $flags): ConstStub + { + $flagsArray = []; + + foreach (self::FLAGS as $value => $name) { + if ($flags & $value) { + $flagsArray[] = $name; + } + } + + if (!$flagsArray) { + $flagsArray = ['AMQP_NOPARAM']; + } + + return new ConstStub(implode('|', $flagsArray), $flags); + } +} diff --git a/vendor/symfony/var-dumper/Caster/ArgsStub.php b/vendor/symfony/var-dumper/Caster/ArgsStub.php new file mode 100644 index 0000000000..b3f7bbee3a --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ArgsStub.php @@ -0,0 +1,80 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Represents a list of function arguments. + * + * @author Nicolas Grekas <p@tchwork.com> + */ +class ArgsStub extends EnumStub +{ + private static $parameters = []; + + public function __construct(array $args, string $function, ?string $class) + { + [$variadic, $params] = self::getParameters($function, $class); + + $values = []; + foreach ($args as $k => $v) { + $values[$k] = !\is_scalar($v) && !$v instanceof Stub ? new CutStub($v) : $v; + } + if (null === $params) { + parent::__construct($values, false); + + return; + } + if (\count($values) < \count($params)) { + $params = \array_slice($params, 0, \count($values)); + } elseif (\count($values) > \count($params)) { + $values[] = new EnumStub(array_splice($values, \count($params)), false); + $params[] = $variadic; + } + if (['...'] === $params) { + $this->dumpKeys = false; + $this->value = $values[0]->value; + } else { + $this->value = array_combine($params, $values); + } + } + + private static function getParameters(string $function, ?string $class): array + { + if (isset(self::$parameters[$k = $class.'::'.$function])) { + return self::$parameters[$k]; + } + + try { + $r = null !== $class ? new \ReflectionMethod($class, $function) : new \ReflectionFunction($function); + } catch (\ReflectionException $e) { + return [null, null]; + } + + $variadic = '...'; + $params = []; + foreach ($r->getParameters() as $v) { + $k = '$'.$v->name; + if ($v->isPassedByReference()) { + $k = '&'.$k; + } + if ($v->isVariadic()) { + $variadic .= $k; + } else { + $params[] = $k; + } + } + + return self::$parameters[$k] = [$variadic, $params]; + } +} diff --git a/vendor/symfony/var-dumper/Caster/Caster.php b/vendor/symfony/var-dumper/Caster/Caster.php new file mode 100644 index 0000000000..53f4461d0d --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/Caster.php @@ -0,0 +1,170 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Helper for filtering out properties in casters. + * + * @author Nicolas Grekas <p@tchwork.com> + * + * @final + */ +class Caster +{ + public const EXCLUDE_VERBOSE = 1; + public const EXCLUDE_VIRTUAL = 2; + public const EXCLUDE_DYNAMIC = 4; + public const EXCLUDE_PUBLIC = 8; + public const EXCLUDE_PROTECTED = 16; + public const EXCLUDE_PRIVATE = 32; + public const EXCLUDE_NULL = 64; + public const EXCLUDE_EMPTY = 128; + public const EXCLUDE_NOT_IMPORTANT = 256; + public const EXCLUDE_STRICT = 512; + + public const PREFIX_VIRTUAL = "\0~\0"; + public const PREFIX_DYNAMIC = "\0+\0"; + public const PREFIX_PROTECTED = "\0*\0"; + + /** + * Casts objects to arrays and adds the dynamic property prefix. + * + * @param bool $hasDebugInfo Whether the __debugInfo method exists on $obj or not + */ + public static function castObject(object $obj, string $class, bool $hasDebugInfo = false, string $debugClass = null): array + { + if ($hasDebugInfo) { + try { + $debugInfo = $obj->__debugInfo(); + } catch (\Exception $e) { + // ignore failing __debugInfo() + $hasDebugInfo = false; + } + } + + $a = $obj instanceof \Closure ? [] : (array) $obj; + + if ($obj instanceof \__PHP_Incomplete_Class) { + return $a; + } + + if ($a) { + static $publicProperties = []; + $debugClass = $debugClass ?? get_debug_type($obj); + + $i = 0; + $prefixedKeys = []; + foreach ($a as $k => $v) { + if ("\0" !== ($k[0] ?? '')) { + if (!isset($publicProperties[$class])) { + foreach ((new \ReflectionClass($class))->getProperties(\ReflectionProperty::IS_PUBLIC) as $prop) { + $publicProperties[$class][$prop->name] = true; + } + } + if (!isset($publicProperties[$class][$k])) { + $prefixedKeys[$i] = self::PREFIX_DYNAMIC.$k; + } + } elseif ($debugClass !== $class && 1 === strpos($k, $class)) { + $prefixedKeys[$i] = "\0".$debugClass.strrchr($k, "\0"); + } + ++$i; + } + if ($prefixedKeys) { + $keys = array_keys($a); + foreach ($prefixedKeys as $i => $k) { + $keys[$i] = $k; + } + $a = array_combine($keys, $a); + } + } + + if ($hasDebugInfo && \is_array($debugInfo)) { + foreach ($debugInfo as $k => $v) { + if (!isset($k[0]) || "\0" !== $k[0]) { + if (\array_key_exists(self::PREFIX_DYNAMIC.$k, $a)) { + continue; + } + $k = self::PREFIX_VIRTUAL.$k; + } + + unset($a[$k]); + $a[$k] = $v; + } + } + + return $a; + } + + /** + * Filters out the specified properties. + * + * By default, a single match in the $filter bit field filters properties out, following an "or" logic. + * When EXCLUDE_STRICT is set, an "and" logic is applied: all bits must match for a property to be removed. + * + * @param array $a The array containing the properties to filter + * @param int $filter A bit field of Caster::EXCLUDE_* constants specifying which properties to filter out + * @param string[] $listedProperties List of properties to exclude when Caster::EXCLUDE_VERBOSE is set, and to preserve when Caster::EXCLUDE_NOT_IMPORTANT is set + * @param int &$count Set to the number of removed properties + */ + public static function filter(array $a, int $filter, array $listedProperties = [], ?int &$count = 0): array + { + $count = 0; + + foreach ($a as $k => $v) { + $type = self::EXCLUDE_STRICT & $filter; + + if (null === $v) { + $type |= self::EXCLUDE_NULL & $filter; + $type |= self::EXCLUDE_EMPTY & $filter; + } elseif (false === $v || '' === $v || '0' === $v || 0 === $v || 0.0 === $v || [] === $v) { + $type |= self::EXCLUDE_EMPTY & $filter; + } + if ((self::EXCLUDE_NOT_IMPORTANT & $filter) && !\in_array($k, $listedProperties, true)) { + $type |= self::EXCLUDE_NOT_IMPORTANT; + } + if ((self::EXCLUDE_VERBOSE & $filter) && \in_array($k, $listedProperties, true)) { + $type |= self::EXCLUDE_VERBOSE; + } + + if (!isset($k[1]) || "\0" !== $k[0]) { + $type |= self::EXCLUDE_PUBLIC & $filter; + } elseif ('~' === $k[1]) { + $type |= self::EXCLUDE_VIRTUAL & $filter; + } elseif ('+' === $k[1]) { + $type |= self::EXCLUDE_DYNAMIC & $filter; + } elseif ('*' === $k[1]) { + $type |= self::EXCLUDE_PROTECTED & $filter; + } else { + $type |= self::EXCLUDE_PRIVATE & $filter; + } + + if ((self::EXCLUDE_STRICT & $filter) ? $type === $filter : $type) { + unset($a[$k]); + ++$count; + } + } + + return $a; + } + + public static function castPhpIncompleteClass(\__PHP_Incomplete_Class $c, array $a, Stub $stub, bool $isNested): array + { + if (isset($a['__PHP_Incomplete_Class_Name'])) { + $stub->class .= '('.$a['__PHP_Incomplete_Class_Name'].')'; + unset($a['__PHP_Incomplete_Class_Name']); + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/ClassStub.php b/vendor/symfony/var-dumper/Caster/ClassStub.php new file mode 100644 index 0000000000..48f848354b --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ClassStub.php @@ -0,0 +1,106 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Represents a PHP class identifier. + * + * @author Nicolas Grekas <p@tchwork.com> + */ +class ClassStub extends ConstStub +{ + /** + * @param string $identifier A PHP identifier, e.g. a class, method, interface, etc. name + * @param callable $callable The callable targeted by the identifier when it is ambiguous or not a real PHP identifier + */ + public function __construct(string $identifier, $callable = null) + { + $this->value = $identifier; + + try { + if (null !== $callable) { + if ($callable instanceof \Closure) { + $r = new \ReflectionFunction($callable); + } elseif (\is_object($callable)) { + $r = [$callable, '__invoke']; + } elseif (\is_array($callable)) { + $r = $callable; + } elseif (false !== $i = strpos($callable, '::')) { + $r = [substr($callable, 0, $i), substr($callable, 2 + $i)]; + } else { + $r = new \ReflectionFunction($callable); + } + } elseif (0 < $i = strpos($identifier, '::') ?: strpos($identifier, '->')) { + $r = [substr($identifier, 0, $i), substr($identifier, 2 + $i)]; + } else { + $r = new \ReflectionClass($identifier); + } + + if (\is_array($r)) { + try { + $r = new \ReflectionMethod($r[0], $r[1]); + } catch (\ReflectionException $e) { + $r = new \ReflectionClass($r[0]); + } + } + + if (str_contains($identifier, "@anonymous\0")) { + $this->value = $identifier = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) { + return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0]; + }, $identifier); + } + + if (null !== $callable && $r instanceof \ReflectionFunctionAbstract) { + $s = ReflectionCaster::castFunctionAbstract($r, [], new Stub(), true, Caster::EXCLUDE_VERBOSE); + $s = ReflectionCaster::getSignature($s); + + if (str_ends_with($identifier, '()')) { + $this->value = substr_replace($identifier, $s, -2); + } else { + $this->value .= $s; + } + } + } catch (\ReflectionException $e) { + return; + } finally { + if (0 < $i = strrpos($this->value, '\\')) { + $this->attr['ellipsis'] = \strlen($this->value) - $i; + $this->attr['ellipsis-type'] = 'class'; + $this->attr['ellipsis-tail'] = 1; + } + } + + if ($f = $r->getFileName()) { + $this->attr['file'] = $f; + $this->attr['line'] = $r->getStartLine(); + } + } + + public static function wrapCallable($callable) + { + if (\is_object($callable) || !\is_callable($callable)) { + return $callable; + } + + if (!\is_array($callable)) { + $callable = new static($callable, $callable); + } elseif (\is_string($callable[0])) { + $callable[0] = new static($callable[0], $callable); + } else { + $callable[1] = new static($callable[1], $callable); + } + + return $callable; + } +} diff --git a/vendor/symfony/var-dumper/Caster/ConstStub.php b/vendor/symfony/var-dumper/Caster/ConstStub.php new file mode 100644 index 0000000000..8b0179745f --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ConstStub.php @@ -0,0 +1,36 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Represents a PHP constant and its value. + * + * @author Nicolas Grekas <p@tchwork.com> + */ +class ConstStub extends Stub +{ + public function __construct(string $name, $value = null) + { + $this->class = $name; + $this->value = 1 < \func_num_args() ? $value : $name; + } + + /** + * @return string + */ + public function __toString() + { + return (string) $this->value; + } +} diff --git a/vendor/symfony/var-dumper/Caster/CutArrayStub.php b/vendor/symfony/var-dumper/Caster/CutArrayStub.php new file mode 100644 index 0000000000..0e4fb363d2 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/CutArrayStub.php @@ -0,0 +1,30 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +/** + * Represents a cut array. + * + * @author Nicolas Grekas <p@tchwork.com> + */ +class CutArrayStub extends CutStub +{ + public $preservedSubset; + + public function __construct(array $value, array $preservedKeys) + { + parent::__construct($value); + + $this->preservedSubset = array_intersect_key($value, array_flip($preservedKeys)); + $this->cut -= \count($this->preservedSubset); + } +} diff --git a/vendor/symfony/var-dumper/Caster/CutStub.php b/vendor/symfony/var-dumper/Caster/CutStub.php new file mode 100644 index 0000000000..464c6dbd19 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/CutStub.php @@ -0,0 +1,64 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Represents the main properties of a PHP variable, pre-casted by a caster. + * + * @author Nicolas Grekas <p@tchwork.com> + */ +class CutStub extends Stub +{ + public function __construct($value) + { + $this->value = $value; + + switch (\gettype($value)) { + case 'object': + $this->type = self::TYPE_OBJECT; + $this->class = \get_class($value); + + if ($value instanceof \Closure) { + ReflectionCaster::castClosure($value, [], $this, true, Caster::EXCLUDE_VERBOSE); + } + + $this->cut = -1; + break; + + case 'array': + $this->type = self::TYPE_ARRAY; + $this->class = self::ARRAY_ASSOC; + $this->cut = $this->value = \count($value); + break; + + case 'resource': + case 'unknown type': + case 'resource (closed)': + $this->type = self::TYPE_RESOURCE; + $this->handle = (int) $value; + if ('Unknown' === $this->class = @get_resource_type($value)) { + $this->class = 'Closed'; + } + $this->cut = -1; + break; + + case 'string': + $this->type = self::TYPE_STRING; + $this->class = preg_match('//u', $value) ? self::STRING_UTF8 : self::STRING_BINARY; + $this->cut = self::STRING_BINARY === $this->class ? \strlen($value) : mb_strlen($value, 'UTF-8'); + $this->value = ''; + break; + } + } +} diff --git a/vendor/symfony/var-dumper/Caster/DOMCaster.php b/vendor/symfony/var-dumper/Caster/DOMCaster.php new file mode 100644 index 0000000000..4dd16e0ee7 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/DOMCaster.php @@ -0,0 +1,304 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts DOM related classes to array representation. + * + * @author Nicolas Grekas <p@tchwork.com> + * + * @final + */ +class DOMCaster +{ + private const ERROR_CODES = [ + \DOM_PHP_ERR => 'DOM_PHP_ERR', + \DOM_INDEX_SIZE_ERR => 'DOM_INDEX_SIZE_ERR', + \DOMSTRING_SIZE_ERR => 'DOMSTRING_SIZE_ERR', + \DOM_HIERARCHY_REQUEST_ERR => 'DOM_HIERARCHY_REQUEST_ERR', + \DOM_WRONG_DOCUMENT_ERR => 'DOM_WRONG_DOCUMENT_ERR', + \DOM_INVALID_CHARACTER_ERR => 'DOM_INVALID_CHARACTER_ERR', + \DOM_NO_DATA_ALLOWED_ERR => 'DOM_NO_DATA_ALLOWED_ERR', + \DOM_NO_MODIFICATION_ALLOWED_ERR => 'DOM_NO_MODIFICATION_ALLOWED_ERR', + \DOM_NOT_FOUND_ERR => 'DOM_NOT_FOUND_ERR', + \DOM_NOT_SUPPORTED_ERR => 'DOM_NOT_SUPPORTED_ERR', + \DOM_INUSE_ATTRIBUTE_ERR => 'DOM_INUSE_ATTRIBUTE_ERR', + \DOM_INVALID_STATE_ERR => 'DOM_INVALID_STATE_ERR', + \DOM_SYNTAX_ERR => 'DOM_SYNTAX_ERR', + \DOM_INVALID_MODIFICATION_ERR => 'DOM_INVALID_MODIFICATION_ERR', + \DOM_NAMESPACE_ERR => 'DOM_NAMESPACE_ERR', + \DOM_INVALID_ACCESS_ERR => 'DOM_INVALID_ACCESS_ERR', + \DOM_VALIDATION_ERR => 'DOM_VALIDATION_ERR', + ]; + + private const NODE_TYPES = [ + \XML_ELEMENT_NODE => 'XML_ELEMENT_NODE', + \XML_ATTRIBUTE_NODE => 'XML_ATTRIBUTE_NODE', + \XML_TEXT_NODE => 'XML_TEXT_NODE', + \XML_CDATA_SECTION_NODE => 'XML_CDATA_SECTION_NODE', + \XML_ENTITY_REF_NODE => 'XML_ENTITY_REF_NODE', + \XML_ENTITY_NODE => 'XML_ENTITY_NODE', + \XML_PI_NODE => 'XML_PI_NODE', + \XML_COMMENT_NODE => 'XML_COMMENT_NODE', + \XML_DOCUMENT_NODE => 'XML_DOCUMENT_NODE', + \XML_DOCUMENT_TYPE_NODE => 'XML_DOCUMENT_TYPE_NODE', + \XML_DOCUMENT_FRAG_NODE => 'XML_DOCUMENT_FRAG_NODE', + \XML_NOTATION_NODE => 'XML_NOTATION_NODE', + \XML_HTML_DOCUMENT_NODE => 'XML_HTML_DOCUMENT_NODE', + \XML_DTD_NODE => 'XML_DTD_NODE', + \XML_ELEMENT_DECL_NODE => 'XML_ELEMENT_DECL_NODE', + \XML_ATTRIBUTE_DECL_NODE => 'XML_ATTRIBUTE_DECL_NODE', + \XML_ENTITY_DECL_NODE => 'XML_ENTITY_DECL_NODE', + \XML_NAMESPACE_DECL_NODE => 'XML_NAMESPACE_DECL_NODE', + ]; + + public static function castException(\DOMException $e, array $a, Stub $stub, bool $isNested) + { + $k = Caster::PREFIX_PROTECTED.'code'; + if (isset($a[$k], self::ERROR_CODES[$a[$k]])) { + $a[$k] = new ConstStub(self::ERROR_CODES[$a[$k]], $a[$k]); + } + + return $a; + } + + public static function castLength($dom, array $a, Stub $stub, bool $isNested) + { + $a += [ + 'length' => $dom->length, + ]; + + return $a; + } + + public static function castImplementation(\DOMImplementation $dom, array $a, Stub $stub, bool $isNested) + { + $a += [ + Caster::PREFIX_VIRTUAL.'Core' => '1.0', + Caster::PREFIX_VIRTUAL.'XML' => '2.0', + ]; + + return $a; + } + + public static function castNode(\DOMNode $dom, array $a, Stub $stub, bool $isNested) + { + $a += [ + 'nodeName' => $dom->nodeName, + 'nodeValue' => new CutStub($dom->nodeValue), + 'nodeType' => new ConstStub(self::NODE_TYPES[$dom->nodeType], $dom->nodeType), + 'parentNode' => new CutStub($dom->parentNode), + 'childNodes' => $dom->childNodes, + 'firstChild' => new CutStub($dom->firstChild), + 'lastChild' => new CutStub($dom->lastChild), + 'previousSibling' => new CutStub($dom->previousSibling), + 'nextSibling' => new CutStub($dom->nextSibling), + 'attributes' => $dom->attributes, + 'ownerDocument' => new CutStub($dom->ownerDocument), + 'namespaceURI' => $dom->namespaceURI, + 'prefix' => $dom->prefix, + 'localName' => $dom->localName, + 'baseURI' => $dom->baseURI ? new LinkStub($dom->baseURI) : $dom->baseURI, + 'textContent' => new CutStub($dom->textContent), + ]; + + return $a; + } + + public static function castNameSpaceNode(\DOMNameSpaceNode $dom, array $a, Stub $stub, bool $isNested) + { + $a += [ + 'nodeName' => $dom->nodeName, + 'nodeValue' => new CutStub($dom->nodeValue), + 'nodeType' => new ConstStub(self::NODE_TYPES[$dom->nodeType], $dom->nodeType), + 'prefix' => $dom->prefix, + 'localName' => $dom->localName, + 'namespaceURI' => $dom->namespaceURI, + 'ownerDocument' => new CutStub($dom->ownerDocument), + 'parentNode' => new CutStub($dom->parentNode), + ]; + + return $a; + } + + public static function castDocument(\DOMDocument $dom, array $a, Stub $stub, bool $isNested, int $filter = 0) + { + $a += [ + 'doctype' => $dom->doctype, + 'implementation' => $dom->implementation, + 'documentElement' => new CutStub($dom->documentElement), + 'actualEncoding' => $dom->actualEncoding, + 'encoding' => $dom->encoding, + 'xmlEncoding' => $dom->xmlEncoding, + 'standalone' => $dom->standalone, + 'xmlStandalone' => $dom->xmlStandalone, + 'version' => $dom->version, + 'xmlVersion' => $dom->xmlVersion, + 'strictErrorChecking' => $dom->strictErrorChecking, + 'documentURI' => $dom->documentURI ? new LinkStub($dom->documentURI) : $dom->documentURI, + 'config' => $dom->config, + 'formatOutput' => $dom->formatOutput, + 'validateOnParse' => $dom->validateOnParse, + 'resolveExternals' => $dom->resolveExternals, + 'preserveWhiteSpace' => $dom->preserveWhiteSpace, + 'recover' => $dom->recover, + 'substituteEntities' => $dom->substituteEntities, + ]; + + if (!($filter & Caster::EXCLUDE_VERBOSE)) { + $formatOutput = $dom->formatOutput; + $dom->formatOutput = true; + $a += [Caster::PREFIX_VIRTUAL.'xml' => $dom->saveXML()]; + $dom->formatOutput = $formatOutput; + } + + return $a; + } + + public static function castCharacterData(\DOMCharacterData $dom, array $a, Stub $stub, bool $isNested) + { + $a += [ + 'data' => $dom->data, + 'length' => $dom->length, + ]; + + return $a; + } + + public static function castAttr(\DOMAttr $dom, array $a, Stub $stub, bool $isNested) + { + $a += [ + 'name' => $dom->name, + 'specified' => $dom->specified, + 'value' => $dom->value, + 'ownerElement' => $dom->ownerElement, + 'schemaTypeInfo' => $dom->schemaTypeInfo, + ]; + + return $a; + } + + public static function castElement(\DOMElement $dom, array $a, Stub $stub, bool $isNested) + { + $a += [ + 'tagName' => $dom->tagName, + 'schemaTypeInfo' => $dom->schemaTypeInfo, + ]; + + return $a; + } + + public static function castText(\DOMText $dom, array $a, Stub $stub, bool $isNested) + { + $a += [ + 'wholeText' => $dom->wholeText, + ]; + + return $a; + } + + public static function castTypeinfo(\DOMTypeinfo $dom, array $a, Stub $stub, bool $isNested) + { + $a += [ + 'typeName' => $dom->typeName, + 'typeNamespace' => $dom->typeNamespace, + ]; + + return $a; + } + + public static function castDomError(\DOMDomError $dom, array $a, Stub $stub, bool $isNested) + { + $a += [ + 'severity' => $dom->severity, + 'message' => $dom->message, + 'type' => $dom->type, + 'relatedException' => $dom->relatedException, + 'related_data' => $dom->related_data, + 'location' => $dom->location, + ]; + + return $a; + } + + public static function castLocator(\DOMLocator $dom, array $a, Stub $stub, bool $isNested) + { + $a += [ + 'lineNumber' => $dom->lineNumber, + 'columnNumber' => $dom->columnNumber, + 'offset' => $dom->offset, + 'relatedNode' => $dom->relatedNode, + 'uri' => $dom->uri ? new LinkStub($dom->uri, $dom->lineNumber) : $dom->uri, + ]; + + return $a; + } + + public static function castDocumentType(\DOMDocumentType $dom, array $a, Stub $stub, bool $isNested) + { + $a += [ + 'name' => $dom->name, + 'entities' => $dom->entities, + 'notations' => $dom->notations, + 'publicId' => $dom->publicId, + 'systemId' => $dom->systemId, + 'internalSubset' => $dom->internalSubset, + ]; + + return $a; + } + + public static function castNotation(\DOMNotation $dom, array $a, Stub $stub, bool $isNested) + { + $a += [ + 'publicId' => $dom->publicId, + 'systemId' => $dom->systemId, + ]; + + return $a; + } + + public static function castEntity(\DOMEntity $dom, array $a, Stub $stub, bool $isNested) + { + $a += [ + 'publicId' => $dom->publicId, + 'systemId' => $dom->systemId, + 'notationName' => $dom->notationName, + 'actualEncoding' => $dom->actualEncoding, + 'encoding' => $dom->encoding, + 'version' => $dom->version, + ]; + + return $a; + } + + public static function castProcessingInstruction(\DOMProcessingInstruction $dom, array $a, Stub $stub, bool $isNested) + { + $a += [ + 'target' => $dom->target, + 'data' => $dom->data, + ]; + + return $a; + } + + public static function castXPath(\DOMXPath $dom, array $a, Stub $stub, bool $isNested) + { + $a += [ + 'document' => $dom->document, + ]; + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/DateCaster.php b/vendor/symfony/var-dumper/Caster/DateCaster.php new file mode 100644 index 0000000000..18641fbc1d --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/DateCaster.php @@ -0,0 +1,127 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts DateTimeInterface related classes to array representation. + * + * @author Dany Maillard <danymaillard93b@gmail.com> + * + * @final + */ +class DateCaster +{ + private const PERIOD_LIMIT = 3; + + public static function castDateTime(\DateTimeInterface $d, array $a, Stub $stub, bool $isNested, int $filter) + { + $prefix = Caster::PREFIX_VIRTUAL; + $location = $d->getTimezone()->getLocation(); + $fromNow = (new \DateTime())->diff($d); + + $title = $d->format('l, F j, Y') + ."\n".self::formatInterval($fromNow).' from now' + .($location ? ($d->format('I') ? "\nDST On" : "\nDST Off") : '') + ; + + unset( + $a[Caster::PREFIX_DYNAMIC.'date'], + $a[Caster::PREFIX_DYNAMIC.'timezone'], + $a[Caster::PREFIX_DYNAMIC.'timezone_type'] + ); + $a[$prefix.'date'] = new ConstStub(self::formatDateTime($d, $location ? ' e (P)' : ' P'), $title); + + $stub->class .= $d->format(' @U'); + + return $a; + } + + public static function castInterval(\DateInterval $interval, array $a, Stub $stub, bool $isNested, int $filter) + { + $now = new \DateTimeImmutable('@0', new \DateTimeZone('UTC')); + $numberOfSeconds = $now->add($interval)->getTimestamp() - $now->getTimestamp(); + $title = number_format($numberOfSeconds, 0, '.', ' ').'s'; + + $i = [Caster::PREFIX_VIRTUAL.'interval' => new ConstStub(self::formatInterval($interval), $title)]; + + return $filter & Caster::EXCLUDE_VERBOSE ? $i : $i + $a; + } + + private static function formatInterval(\DateInterval $i): string + { + $format = '%R '; + + if (0 === $i->y && 0 === $i->m && ($i->h >= 24 || $i->i >= 60 || $i->s >= 60)) { + $d = new \DateTimeImmutable('@0', new \DateTimeZone('UTC')); + $i = $d->diff($d->add($i)); // recalculate carry over points + $format .= 0 < $i->days ? '%ad ' : ''; + } else { + $format .= ($i->y ? '%yy ' : '').($i->m ? '%mm ' : '').($i->d ? '%dd ' : ''); + } + + $format .= $i->h || $i->i || $i->s || $i->f ? '%H:%I:'.self::formatSeconds($i->s, substr($i->f, 2)) : ''; + $format = '%R ' === $format ? '0s' : $format; + + return $i->format(rtrim($format)); + } + + public static function castTimeZone(\DateTimeZone $timeZone, array $a, Stub $stub, bool $isNested, int $filter) + { + $location = $timeZone->getLocation(); + $formatted = (new \DateTime('now', $timeZone))->format($location ? 'e (P)' : 'P'); + $title = $location && \extension_loaded('intl') ? \Locale::getDisplayRegion('-'.$location['country_code']) : ''; + + $z = [Caster::PREFIX_VIRTUAL.'timezone' => new ConstStub($formatted, $title)]; + + return $filter & Caster::EXCLUDE_VERBOSE ? $z : $z + $a; + } + + public static function castPeriod(\DatePeriod $p, array $a, Stub $stub, bool $isNested, int $filter) + { + $dates = []; + foreach (clone $p as $i => $d) { + if (self::PERIOD_LIMIT === $i) { + $now = new \DateTimeImmutable('now', new \DateTimeZone('UTC')); + $dates[] = sprintf('%s more', ($end = $p->getEndDate()) + ? ceil(($end->format('U.u') - $d->format('U.u')) / ((int) $now->add($p->getDateInterval())->format('U.u') - (int) $now->format('U.u'))) + : $p->recurrences - $i + ); + break; + } + $dates[] = sprintf('%s) %s', $i + 1, self::formatDateTime($d)); + } + + $period = sprintf( + 'every %s, from %s%s %s', + self::formatInterval($p->getDateInterval()), + $p->include_start_date ? '[' : ']', + self::formatDateTime($p->getStartDate()), + ($end = $p->getEndDate()) ? 'to '.self::formatDateTime($end).(\PHP_VERSION_ID >= 80200 && $p->include_end_date ? ']' : '[') : 'recurring '.$p->recurrences.' time/s' + ); + + $p = [Caster::PREFIX_VIRTUAL.'period' => new ConstStub($period, implode("\n", $dates))]; + + return $filter & Caster::EXCLUDE_VERBOSE ? $p : $p + $a; + } + + private static function formatDateTime(\DateTimeInterface $d, string $extra = ''): string + { + return $d->format('Y-m-d H:i:'.self::formatSeconds($d->format('s'), $d->format('u')).$extra); + } + + private static function formatSeconds(string $s, string $us): string + { + return sprintf('%02d.%s', $s, 0 === ($len = \strlen($t = rtrim($us, '0'))) ? '0' : ($len <= 3 ? str_pad($t, 3, '0') : $us)); + } +} diff --git a/vendor/symfony/var-dumper/Caster/DoctrineCaster.php b/vendor/symfony/var-dumper/Caster/DoctrineCaster.php new file mode 100644 index 0000000000..129b2cb47b --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/DoctrineCaster.php @@ -0,0 +1,62 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Doctrine\Common\Proxy\Proxy as CommonProxy; +use Doctrine\ORM\PersistentCollection; +use Doctrine\ORM\Proxy\Proxy as OrmProxy; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts Doctrine related classes to array representation. + * + * @author Nicolas Grekas <p@tchwork.com> + * + * @final + */ +class DoctrineCaster +{ + public static function castCommonProxy(CommonProxy $proxy, array $a, Stub $stub, bool $isNested) + { + foreach (['__cloner__', '__initializer__'] as $k) { + if (\array_key_exists($k, $a)) { + unset($a[$k]); + ++$stub->cut; + } + } + + return $a; + } + + public static function castOrmProxy(OrmProxy $proxy, array $a, Stub $stub, bool $isNested) + { + foreach (['_entityPersister', '_identifier'] as $k) { + if (\array_key_exists($k = "\0Doctrine\\ORM\\Proxy\\Proxy\0".$k, $a)) { + unset($a[$k]); + ++$stub->cut; + } + } + + return $a; + } + + public static function castPersistentCollection(PersistentCollection $coll, array $a, Stub $stub, bool $isNested) + { + foreach (['snapshot', 'association', 'typeClass'] as $k) { + if (\array_key_exists($k = "\0Doctrine\\ORM\\PersistentCollection\0".$k, $a)) { + $a[$k] = new CutStub($a[$k]); + } + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/DsCaster.php b/vendor/symfony/var-dumper/Caster/DsCaster.php new file mode 100644 index 0000000000..b34b67004b --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/DsCaster.php @@ -0,0 +1,70 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Ds\Collection; +use Ds\Map; +use Ds\Pair; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts Ds extension classes to array representation. + * + * @author Jáchym Toušek <enumag@gmail.com> + * + * @final + */ +class DsCaster +{ + public static function castCollection(Collection $c, array $a, Stub $stub, bool $isNested): array + { + $a[Caster::PREFIX_VIRTUAL.'count'] = $c->count(); + $a[Caster::PREFIX_VIRTUAL.'capacity'] = $c->capacity(); + + if (!$c instanceof Map) { + $a += $c->toArray(); + } + + return $a; + } + + public static function castMap(Map $c, array $a, Stub $stub, bool $isNested): array + { + foreach ($c as $k => $v) { + $a[] = new DsPairStub($k, $v); + } + + return $a; + } + + public static function castPair(Pair $c, array $a, Stub $stub, bool $isNested): array + { + foreach ($c->toArray() as $k => $v) { + $a[Caster::PREFIX_VIRTUAL.$k] = $v; + } + + return $a; + } + + public static function castPairStub(DsPairStub $c, array $a, Stub $stub, bool $isNested): array + { + if ($isNested) { + $stub->class = Pair::class; + $stub->value = null; + $stub->handle = 0; + + $a = $c->value; + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/DsPairStub.php b/vendor/symfony/var-dumper/Caster/DsPairStub.php new file mode 100644 index 0000000000..a1dcc15618 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/DsPairStub.php @@ -0,0 +1,28 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Nicolas Grekas <p@tchwork.com> + */ +class DsPairStub extends Stub +{ + public function __construct($key, $value) + { + $this->value = [ + Caster::PREFIX_VIRTUAL.'key' => $key, + Caster::PREFIX_VIRTUAL.'value' => $value, + ]; + } +} diff --git a/vendor/symfony/var-dumper/Caster/EnumStub.php b/vendor/symfony/var-dumper/Caster/EnumStub.php new file mode 100644 index 0000000000..7a4e98a21b --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/EnumStub.php @@ -0,0 +1,30 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Represents an enumeration of values. + * + * @author Nicolas Grekas <p@tchwork.com> + */ +class EnumStub extends Stub +{ + public $dumpKeys = true; + + public function __construct(array $values, bool $dumpKeys = true) + { + $this->value = $values; + $this->dumpKeys = $dumpKeys; + } +} diff --git a/vendor/symfony/var-dumper/Caster/ExceptionCaster.php b/vendor/symfony/var-dumper/Caster/ExceptionCaster.php new file mode 100644 index 0000000000..7f5cb65eb2 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ExceptionCaster.php @@ -0,0 +1,388 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext; +use Symfony\Component\VarDumper\Cloner\Stub; +use Symfony\Component\VarDumper\Exception\ThrowingCasterException; + +/** + * Casts common Exception classes to array representation. + * + * @author Nicolas Grekas <p@tchwork.com> + * + * @final + */ +class ExceptionCaster +{ + public static $srcContext = 1; + public static $traceArgs = true; + public static $errorTypes = [ + \E_DEPRECATED => 'E_DEPRECATED', + \E_USER_DEPRECATED => 'E_USER_DEPRECATED', + \E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR', + \E_ERROR => 'E_ERROR', + \E_WARNING => 'E_WARNING', + \E_PARSE => 'E_PARSE', + \E_NOTICE => 'E_NOTICE', + \E_CORE_ERROR => 'E_CORE_ERROR', + \E_CORE_WARNING => 'E_CORE_WARNING', + \E_COMPILE_ERROR => 'E_COMPILE_ERROR', + \E_COMPILE_WARNING => 'E_COMPILE_WARNING', + \E_USER_ERROR => 'E_USER_ERROR', + \E_USER_WARNING => 'E_USER_WARNING', + \E_USER_NOTICE => 'E_USER_NOTICE', + \E_STRICT => 'E_STRICT', + ]; + + private static $framesCache = []; + + public static function castError(\Error $e, array $a, Stub $stub, bool $isNested, int $filter = 0) + { + return self::filterExceptionArray($stub->class, $a, "\0Error\0", $filter); + } + + public static function castException(\Exception $e, array $a, Stub $stub, bool $isNested, int $filter = 0) + { + return self::filterExceptionArray($stub->class, $a, "\0Exception\0", $filter); + } + + public static function castErrorException(\ErrorException $e, array $a, Stub $stub, bool $isNested) + { + if (isset($a[$s = Caster::PREFIX_PROTECTED.'severity'], self::$errorTypes[$a[$s]])) { + $a[$s] = new ConstStub(self::$errorTypes[$a[$s]], $a[$s]); + } + + return $a; + } + + public static function castThrowingCasterException(ThrowingCasterException $e, array $a, Stub $stub, bool $isNested) + { + $trace = Caster::PREFIX_VIRTUAL.'trace'; + $prefix = Caster::PREFIX_PROTECTED; + $xPrefix = "\0Exception\0"; + + if (isset($a[$xPrefix.'previous'], $a[$trace]) && $a[$xPrefix.'previous'] instanceof \Exception) { + $b = (array) $a[$xPrefix.'previous']; + $class = get_debug_type($a[$xPrefix.'previous']); + self::traceUnshift($b[$xPrefix.'trace'], $class, $b[$prefix.'file'], $b[$prefix.'line']); + $a[$trace] = new TraceStub($b[$xPrefix.'trace'], false, 0, -\count($a[$trace]->value)); + } + + unset($a[$xPrefix.'previous'], $a[$prefix.'code'], $a[$prefix.'file'], $a[$prefix.'line']); + + return $a; + } + + public static function castSilencedErrorContext(SilencedErrorContext $e, array $a, Stub $stub, bool $isNested) + { + $sPrefix = "\0".SilencedErrorContext::class."\0"; + + if (!isset($a[$s = $sPrefix.'severity'])) { + return $a; + } + + if (isset(self::$errorTypes[$a[$s]])) { + $a[$s] = new ConstStub(self::$errorTypes[$a[$s]], $a[$s]); + } + + $trace = [[ + 'file' => $a[$sPrefix.'file'], + 'line' => $a[$sPrefix.'line'], + ]]; + + if (isset($a[$sPrefix.'trace'])) { + $trace = array_merge($trace, $a[$sPrefix.'trace']); + } + + unset($a[$sPrefix.'file'], $a[$sPrefix.'line'], $a[$sPrefix.'trace']); + $a[Caster::PREFIX_VIRTUAL.'trace'] = new TraceStub($trace, self::$traceArgs); + + return $a; + } + + public static function castTraceStub(TraceStub $trace, array $a, Stub $stub, bool $isNested) + { + if (!$isNested) { + return $a; + } + $stub->class = ''; + $stub->handle = 0; + $frames = $trace->value; + $prefix = Caster::PREFIX_VIRTUAL; + + $a = []; + $j = \count($frames); + if (0 > $i = $trace->sliceOffset) { + $i = max(0, $j + $i); + } + if (!isset($trace->value[$i])) { + return []; + } + $lastCall = isset($frames[$i]['function']) ? (isset($frames[$i]['class']) ? $frames[0]['class'].$frames[$i]['type'] : '').$frames[$i]['function'].'()' : ''; + $frames[] = ['function' => '']; + $collapse = false; + + for ($j += $trace->numberingOffset - $i++; isset($frames[$i]); ++$i, --$j) { + $f = $frames[$i]; + $call = isset($f['function']) ? (isset($f['class']) ? $f['class'].$f['type'] : '').$f['function'] : '???'; + + $frame = new FrameStub( + [ + 'object' => $f['object'] ?? null, + 'class' => $f['class'] ?? null, + 'type' => $f['type'] ?? null, + 'function' => $f['function'] ?? null, + ] + $frames[$i - 1], + false, + true + ); + $f = self::castFrameStub($frame, [], $frame, true); + if (isset($f[$prefix.'src'])) { + foreach ($f[$prefix.'src']->value as $label => $frame) { + if (str_starts_with($label, "\0~collapse=0")) { + if ($collapse) { + $label = substr_replace($label, '1', 11, 1); + } else { + $collapse = true; + } + } + $label = substr_replace($label, "title=Stack level $j.&", 2, 0); + } + $f = $frames[$i - 1]; + if ($trace->keepArgs && !empty($f['args']) && $frame instanceof EnumStub) { + $frame->value['arguments'] = new ArgsStub($f['args'], $f['function'] ?? null, $f['class'] ?? null); + } + } elseif ('???' !== $lastCall) { + $label = new ClassStub($lastCall); + if (isset($label->attr['ellipsis'])) { + $label->attr['ellipsis'] += 2; + $label = substr_replace($prefix, "ellipsis-type=class&ellipsis={$label->attr['ellipsis']}&ellipsis-tail=1&title=Stack level $j.", 2, 0).$label->value.'()'; + } else { + $label = substr_replace($prefix, "title=Stack level $j.", 2, 0).$label->value.'()'; + } + } else { + $label = substr_replace($prefix, "title=Stack level $j.", 2, 0).$lastCall; + } + $a[substr_replace($label, sprintf('separator=%s&', $frame instanceof EnumStub ? ' ' : ':'), 2, 0)] = $frame; + + $lastCall = $call; + } + if (null !== $trace->sliceLength) { + $a = \array_slice($a, 0, $trace->sliceLength, true); + } + + return $a; + } + + public static function castFrameStub(FrameStub $frame, array $a, Stub $stub, bool $isNested) + { + if (!$isNested) { + return $a; + } + $f = $frame->value; + $prefix = Caster::PREFIX_VIRTUAL; + + if (isset($f['file'], $f['line'])) { + $cacheKey = $f; + unset($cacheKey['object'], $cacheKey['args']); + $cacheKey[] = self::$srcContext; + $cacheKey = implode('-', $cacheKey); + + if (isset(self::$framesCache[$cacheKey])) { + $a[$prefix.'src'] = self::$framesCache[$cacheKey]; + } else { + if (preg_match('/\((\d+)\)(?:\([\da-f]{32}\))? : (?:eval\(\)\'d code|runtime-created function)$/', $f['file'], $match)) { + $f['file'] = substr($f['file'], 0, -\strlen($match[0])); + $f['line'] = (int) $match[1]; + } + $src = $f['line']; + $srcKey = $f['file']; + $ellipsis = new LinkStub($srcKey, 0); + $srcAttr = 'collapse='.(int) $ellipsis->inVendor; + $ellipsisTail = $ellipsis->attr['ellipsis-tail'] ?? 0; + $ellipsis = $ellipsis->attr['ellipsis'] ?? 0; + + if (is_file($f['file']) && 0 <= self::$srcContext) { + if (!empty($f['class']) && (is_subclass_of($f['class'], 'Twig\Template') || is_subclass_of($f['class'], 'Twig_Template')) && method_exists($f['class'], 'getDebugInfo')) { + $template = null; + if (isset($f['object'])) { + $template = $f['object']; + } elseif ((new \ReflectionClass($f['class']))->isInstantiable()) { + $template = unserialize(sprintf('O:%d:"%s":0:{}', \strlen($f['class']), $f['class'])); + } + if (null !== $template) { + $ellipsis = 0; + $templateSrc = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : ''); + $templateInfo = $template->getDebugInfo(); + if (isset($templateInfo[$f['line']])) { + if (!method_exists($template, 'getSourceContext') || !is_file($templatePath = $template->getSourceContext()->getPath())) { + $templatePath = null; + } + if ($templateSrc) { + $src = self::extractSource($templateSrc, $templateInfo[$f['line']], self::$srcContext, 'twig', $templatePath, $f); + $srcKey = ($templatePath ?: $template->getTemplateName()).':'.$templateInfo[$f['line']]; + } + } + } + } + if ($srcKey == $f['file']) { + $src = self::extractSource(file_get_contents($f['file']), $f['line'], self::$srcContext, 'php', $f['file'], $f); + $srcKey .= ':'.$f['line']; + if ($ellipsis) { + $ellipsis += 1 + \strlen($f['line']); + } + } + $srcAttr .= sprintf('&separator= &file=%s&line=%d', rawurlencode($f['file']), $f['line']); + } else { + $srcAttr .= '&separator=:'; + } + $srcAttr .= $ellipsis ? '&ellipsis-type=path&ellipsis='.$ellipsis.'&ellipsis-tail='.$ellipsisTail : ''; + self::$framesCache[$cacheKey] = $a[$prefix.'src'] = new EnumStub(["\0~$srcAttr\0$srcKey" => $src]); + } + } + + unset($a[$prefix.'args'], $a[$prefix.'line'], $a[$prefix.'file']); + if ($frame->inTraceStub) { + unset($a[$prefix.'class'], $a[$prefix.'type'], $a[$prefix.'function']); + } + foreach ($a as $k => $v) { + if (!$v) { + unset($a[$k]); + } + } + if ($frame->keepArgs && !empty($f['args'])) { + $a[$prefix.'arguments'] = new ArgsStub($f['args'], $f['function'], $f['class']); + } + + return $a; + } + + private static function filterExceptionArray(string $xClass, array $a, string $xPrefix, int $filter): array + { + if (isset($a[$xPrefix.'trace'])) { + $trace = $a[$xPrefix.'trace']; + unset($a[$xPrefix.'trace']); // Ensures the trace is always last + } else { + $trace = []; + } + + if (!($filter & Caster::EXCLUDE_VERBOSE) && $trace) { + if (isset($a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line'])) { + self::traceUnshift($trace, $xClass, $a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line']); + } + $a[Caster::PREFIX_VIRTUAL.'trace'] = new TraceStub($trace, self::$traceArgs); + } + if (empty($a[$xPrefix.'previous'])) { + unset($a[$xPrefix.'previous']); + } + unset($a[$xPrefix.'string'], $a[Caster::PREFIX_DYNAMIC.'xdebug_message'], $a[Caster::PREFIX_DYNAMIC.'__destructorException']); + + if (isset($a[Caster::PREFIX_PROTECTED.'message']) && str_contains($a[Caster::PREFIX_PROTECTED.'message'], "@anonymous\0")) { + $a[Caster::PREFIX_PROTECTED.'message'] = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) { + return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0]; + }, $a[Caster::PREFIX_PROTECTED.'message']); + } + + if (isset($a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line'])) { + $a[Caster::PREFIX_PROTECTED.'file'] = new LinkStub($a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line']); + } + + return $a; + } + + private static function traceUnshift(array &$trace, ?string $class, string $file, int $line): void + { + if (isset($trace[0]['file'], $trace[0]['line']) && $trace[0]['file'] === $file && $trace[0]['line'] === $line) { + return; + } + array_unshift($trace, [ + 'function' => $class ? 'new '.$class : null, + 'file' => $file, + 'line' => $line, + ]); + } + + private static function extractSource(string $srcLines, int $line, int $srcContext, string $lang, ?string $file, array $frame): EnumStub + { + $srcLines = explode("\n", $srcLines); + $src = []; + + for ($i = $line - 1 - $srcContext; $i <= $line - 1 + $srcContext; ++$i) { + $src[] = ($srcLines[$i] ?? '')."\n"; + } + + if ($frame['function'] ?? false) { + $stub = new CutStub(new \stdClass()); + $stub->class = (isset($frame['class']) ? $frame['class'].$frame['type'] : '').$frame['function']; + $stub->type = Stub::TYPE_OBJECT; + $stub->attr['cut_hash'] = true; + $stub->attr['file'] = $frame['file']; + $stub->attr['line'] = $frame['line']; + + try { + $caller = isset($frame['class']) ? new \ReflectionMethod($frame['class'], $frame['function']) : new \ReflectionFunction($frame['function']); + $stub->class .= ReflectionCaster::getSignature(ReflectionCaster::castFunctionAbstract($caller, [], $stub, true, Caster::EXCLUDE_VERBOSE)); + + if ($f = $caller->getFileName()) { + $stub->attr['file'] = $f; + $stub->attr['line'] = $caller->getStartLine(); + } + } catch (\ReflectionException $e) { + // ignore fake class/function + } + + $srcLines = ["\0~separator=\0" => $stub]; + } else { + $stub = null; + $srcLines = []; + } + + $ltrim = 0; + do { + $pad = null; + for ($i = $srcContext << 1; $i >= 0; --$i) { + if (isset($src[$i][$ltrim]) && "\r" !== ($c = $src[$i][$ltrim]) && "\n" !== $c) { + if (null === $pad) { + $pad = $c; + } + if ((' ' !== $c && "\t" !== $c) || $pad !== $c) { + break; + } + } + } + ++$ltrim; + } while (0 > $i && null !== $pad); + + --$ltrim; + + foreach ($src as $i => $c) { + if ($ltrim) { + $c = isset($c[$ltrim]) && "\r" !== $c[$ltrim] ? substr($c, $ltrim) : ltrim($c, " \t"); + } + $c = substr($c, 0, -1); + if ($i !== $srcContext) { + $c = new ConstStub('default', $c); + } else { + $c = new ConstStub($c, $stub ? 'in '.$stub->class : ''); + if (null !== $file) { + $c->attr['file'] = $file; + $c->attr['line'] = $line; + } + } + $c->attr['lang'] = $lang; + $srcLines[sprintf("\0~separator=› &%d\0", $i + $line - $srcContext)] = $c; + } + + return new EnumStub($srcLines); + } +} diff --git a/vendor/symfony/var-dumper/Caster/FiberCaster.php b/vendor/symfony/var-dumper/Caster/FiberCaster.php new file mode 100644 index 0000000000..c74a9e59c4 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/FiberCaster.php @@ -0,0 +1,43 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts Fiber related classes to array representation. + * + * @author Grégoire Pineau <lyrixx@lyrixx.info> + */ +final class FiberCaster +{ + public static function castFiber(\Fiber $fiber, array $a, Stub $stub, bool $isNested, int $filter = 0) + { + $prefix = Caster::PREFIX_VIRTUAL; + + if ($fiber->isTerminated()) { + $status = 'terminated'; + } elseif ($fiber->isRunning()) { + $status = 'running'; + } elseif ($fiber->isSuspended()) { + $status = 'suspended'; + } elseif ($fiber->isStarted()) { + $status = 'started'; + } else { + $status = 'not started'; + } + + $a[$prefix.'status'] = $status; + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/FrameStub.php b/vendor/symfony/var-dumper/Caster/FrameStub.php new file mode 100644 index 0000000000..878675528f --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/FrameStub.php @@ -0,0 +1,30 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +/** + * Represents a single backtrace frame as returned by debug_backtrace() or Exception->getTrace(). + * + * @author Nicolas Grekas <p@tchwork.com> + */ +class FrameStub extends EnumStub +{ + public $keepArgs; + public $inTraceStub; + + public function __construct(array $frame, bool $keepArgs = true, bool $inTraceStub = false) + { + $this->value = $frame; + $this->keepArgs = $keepArgs; + $this->inTraceStub = $inTraceStub; + } +} diff --git a/vendor/symfony/var-dumper/Caster/GmpCaster.php b/vendor/symfony/var-dumper/Caster/GmpCaster.php new file mode 100644 index 0000000000..b018cc7f87 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/GmpCaster.php @@ -0,0 +1,32 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts GMP objects to array representation. + * + * @author Hamza Amrouche <hamza.simperfit@gmail.com> + * @author Nicolas Grekas <p@tchwork.com> + * + * @final + */ +class GmpCaster +{ + public static function castGmp(\GMP $gmp, array $a, Stub $stub, bool $isNested, int $filter): array + { + $a[Caster::PREFIX_VIRTUAL.'value'] = new ConstStub(gmp_strval($gmp), gmp_strval($gmp)); + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/ImagineCaster.php b/vendor/symfony/var-dumper/Caster/ImagineCaster.php new file mode 100644 index 0000000000..d1289da337 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ImagineCaster.php @@ -0,0 +1,37 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Imagine\Image\ImageInterface; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Grégoire Pineau <lyrixx@lyrixx.info> + */ +final class ImagineCaster +{ + public static function castImage(ImageInterface $c, array $a, Stub $stub, bool $isNested): array + { + $imgData = $c->get('png'); + if (\strlen($imgData) > 1 * 1000 * 1000) { + $a += [ + Caster::PREFIX_VIRTUAL.'image' => new ConstStub($c->getSize()), + ]; + } else { + $a += [ + Caster::PREFIX_VIRTUAL.'image' => new ImgStub($imgData, 'image/png', $c->getSize()), + ]; + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/ImgStub.php b/vendor/symfony/var-dumper/Caster/ImgStub.php new file mode 100644 index 0000000000..a16681f736 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ImgStub.php @@ -0,0 +1,26 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +/** + * @author Grégoire Pineau <lyrixx@lyrixx.info> + */ +class ImgStub extends ConstStub +{ + public function __construct(string $data, string $contentType, string $size = '') + { + $this->value = ''; + $this->attr['img-data'] = $data; + $this->attr['img-size'] = $size; + $this->attr['content-type'] = $contentType; + } +} diff --git a/vendor/symfony/var-dumper/Caster/IntlCaster.php b/vendor/symfony/var-dumper/Caster/IntlCaster.php new file mode 100644 index 0000000000..1ed91d4d6a --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/IntlCaster.php @@ -0,0 +1,172 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Nicolas Grekas <p@tchwork.com> + * @author Jan Schädlich <jan.schaedlich@sensiolabs.de> + * + * @final + */ +class IntlCaster +{ + public static function castMessageFormatter(\MessageFormatter $c, array $a, Stub $stub, bool $isNested) + { + $a += [ + Caster::PREFIX_VIRTUAL.'locale' => $c->getLocale(), + Caster::PREFIX_VIRTUAL.'pattern' => $c->getPattern(), + ]; + + return self::castError($c, $a); + } + + public static function castNumberFormatter(\NumberFormatter $c, array $a, Stub $stub, bool $isNested, int $filter = 0) + { + $a += [ + Caster::PREFIX_VIRTUAL.'locale' => $c->getLocale(), + Caster::PREFIX_VIRTUAL.'pattern' => $c->getPattern(), + ]; + + if ($filter & Caster::EXCLUDE_VERBOSE) { + $stub->cut += 3; + + return self::castError($c, $a); + } + + $a += [ + Caster::PREFIX_VIRTUAL.'attributes' => new EnumStub( + [ + 'PARSE_INT_ONLY' => $c->getAttribute(\NumberFormatter::PARSE_INT_ONLY), + 'GROUPING_USED' => $c->getAttribute(\NumberFormatter::GROUPING_USED), + 'DECIMAL_ALWAYS_SHOWN' => $c->getAttribute(\NumberFormatter::DECIMAL_ALWAYS_SHOWN), + 'MAX_INTEGER_DIGITS' => $c->getAttribute(\NumberFormatter::MAX_INTEGER_DIGITS), + 'MIN_INTEGER_DIGITS' => $c->getAttribute(\NumberFormatter::MIN_INTEGER_DIGITS), + 'INTEGER_DIGITS' => $c->getAttribute(\NumberFormatter::INTEGER_DIGITS), + 'MAX_FRACTION_DIGITS' => $c->getAttribute(\NumberFormatter::MAX_FRACTION_DIGITS), + 'MIN_FRACTION_DIGITS' => $c->getAttribute(\NumberFormatter::MIN_FRACTION_DIGITS), + 'FRACTION_DIGITS' => $c->getAttribute(\NumberFormatter::FRACTION_DIGITS), + 'MULTIPLIER' => $c->getAttribute(\NumberFormatter::MULTIPLIER), + 'GROUPING_SIZE' => $c->getAttribute(\NumberFormatter::GROUPING_SIZE), + 'ROUNDING_MODE' => $c->getAttribute(\NumberFormatter::ROUNDING_MODE), + 'ROUNDING_INCREMENT' => $c->getAttribute(\NumberFormatter::ROUNDING_INCREMENT), + 'FORMAT_WIDTH' => $c->getAttribute(\NumberFormatter::FORMAT_WIDTH), + 'PADDING_POSITION' => $c->getAttribute(\NumberFormatter::PADDING_POSITION), + 'SECONDARY_GROUPING_SIZE' => $c->getAttribute(\NumberFormatter::SECONDARY_GROUPING_SIZE), + 'SIGNIFICANT_DIGITS_USED' => $c->getAttribute(\NumberFormatter::SIGNIFICANT_DIGITS_USED), + 'MIN_SIGNIFICANT_DIGITS' => $c->getAttribute(\NumberFormatter::MIN_SIGNIFICANT_DIGITS), + 'MAX_SIGNIFICANT_DIGITS' => $c->getAttribute(\NumberFormatter::MAX_SIGNIFICANT_DIGITS), + 'LENIENT_PARSE' => $c->getAttribute(\NumberFormatter::LENIENT_PARSE), + ] + ), + Caster::PREFIX_VIRTUAL.'text_attributes' => new EnumStub( + [ + 'POSITIVE_PREFIX' => $c->getTextAttribute(\NumberFormatter::POSITIVE_PREFIX), + 'POSITIVE_SUFFIX' => $c->getTextAttribute(\NumberFormatter::POSITIVE_SUFFIX), + 'NEGATIVE_PREFIX' => $c->getTextAttribute(\NumberFormatter::NEGATIVE_PREFIX), + 'NEGATIVE_SUFFIX' => $c->getTextAttribute(\NumberFormatter::NEGATIVE_SUFFIX), + 'PADDING_CHARACTER' => $c->getTextAttribute(\NumberFormatter::PADDING_CHARACTER), + 'CURRENCY_CODE' => $c->getTextAttribute(\NumberFormatter::CURRENCY_CODE), + 'DEFAULT_RULESET' => $c->getTextAttribute(\NumberFormatter::DEFAULT_RULESET), + 'PUBLIC_RULESETS' => $c->getTextAttribute(\NumberFormatter::PUBLIC_RULESETS), + ] + ), + Caster::PREFIX_VIRTUAL.'symbols' => new EnumStub( + [ + 'DECIMAL_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::DECIMAL_SEPARATOR_SYMBOL), + 'GROUPING_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::GROUPING_SEPARATOR_SYMBOL), + 'PATTERN_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::PATTERN_SEPARATOR_SYMBOL), + 'PERCENT_SYMBOL' => $c->getSymbol(\NumberFormatter::PERCENT_SYMBOL), + 'ZERO_DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::ZERO_DIGIT_SYMBOL), + 'DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::DIGIT_SYMBOL), + 'MINUS_SIGN_SYMBOL' => $c->getSymbol(\NumberFormatter::MINUS_SIGN_SYMBOL), + 'PLUS_SIGN_SYMBOL' => $c->getSymbol(\NumberFormatter::PLUS_SIGN_SYMBOL), + 'CURRENCY_SYMBOL' => $c->getSymbol(\NumberFormatter::CURRENCY_SYMBOL), + 'INTL_CURRENCY_SYMBOL' => $c->getSymbol(\NumberFormatter::INTL_CURRENCY_SYMBOL), + 'MONETARY_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::MONETARY_SEPARATOR_SYMBOL), + 'EXPONENTIAL_SYMBOL' => $c->getSymbol(\NumberFormatter::EXPONENTIAL_SYMBOL), + 'PERMILL_SYMBOL' => $c->getSymbol(\NumberFormatter::PERMILL_SYMBOL), + 'PAD_ESCAPE_SYMBOL' => $c->getSymbol(\NumberFormatter::PAD_ESCAPE_SYMBOL), + 'INFINITY_SYMBOL' => $c->getSymbol(\NumberFormatter::INFINITY_SYMBOL), + 'NAN_SYMBOL' => $c->getSymbol(\NumberFormatter::NAN_SYMBOL), + 'SIGNIFICANT_DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::SIGNIFICANT_DIGIT_SYMBOL), + 'MONETARY_GROUPING_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL), + ] + ), + ]; + + return self::castError($c, $a); + } + + public static function castIntlTimeZone(\IntlTimeZone $c, array $a, Stub $stub, bool $isNested) + { + $a += [ + Caster::PREFIX_VIRTUAL.'display_name' => $c->getDisplayName(), + Caster::PREFIX_VIRTUAL.'id' => $c->getID(), + Caster::PREFIX_VIRTUAL.'raw_offset' => $c->getRawOffset(), + ]; + + if ($c->useDaylightTime()) { + $a += [ + Caster::PREFIX_VIRTUAL.'dst_savings' => $c->getDSTSavings(), + ]; + } + + return self::castError($c, $a); + } + + public static function castIntlCalendar(\IntlCalendar $c, array $a, Stub $stub, bool $isNested, int $filter = 0) + { + $a += [ + Caster::PREFIX_VIRTUAL.'type' => $c->getType(), + Caster::PREFIX_VIRTUAL.'first_day_of_week' => $c->getFirstDayOfWeek(), + Caster::PREFIX_VIRTUAL.'minimal_days_in_first_week' => $c->getMinimalDaysInFirstWeek(), + Caster::PREFIX_VIRTUAL.'repeated_wall_time_option' => $c->getRepeatedWallTimeOption(), + Caster::PREFIX_VIRTUAL.'skipped_wall_time_option' => $c->getSkippedWallTimeOption(), + Caster::PREFIX_VIRTUAL.'time' => $c->getTime(), + Caster::PREFIX_VIRTUAL.'in_daylight_time' => $c->inDaylightTime(), + Caster::PREFIX_VIRTUAL.'is_lenient' => $c->isLenient(), + Caster::PREFIX_VIRTUAL.'time_zone' => ($filter & Caster::EXCLUDE_VERBOSE) ? new CutStub($c->getTimeZone()) : $c->getTimeZone(), + ]; + + return self::castError($c, $a); + } + + public static function castIntlDateFormatter(\IntlDateFormatter $c, array $a, Stub $stub, bool $isNested, int $filter = 0) + { + $a += [ + Caster::PREFIX_VIRTUAL.'locale' => $c->getLocale(), + Caster::PREFIX_VIRTUAL.'pattern' => $c->getPattern(), + Caster::PREFIX_VIRTUAL.'calendar' => $c->getCalendar(), + Caster::PREFIX_VIRTUAL.'time_zone_id' => $c->getTimeZoneId(), + Caster::PREFIX_VIRTUAL.'time_type' => $c->getTimeType(), + Caster::PREFIX_VIRTUAL.'date_type' => $c->getDateType(), + Caster::PREFIX_VIRTUAL.'calendar_object' => ($filter & Caster::EXCLUDE_VERBOSE) ? new CutStub($c->getCalendarObject()) : $c->getCalendarObject(), + Caster::PREFIX_VIRTUAL.'time_zone' => ($filter & Caster::EXCLUDE_VERBOSE) ? new CutStub($c->getTimeZone()) : $c->getTimeZone(), + ]; + + return self::castError($c, $a); + } + + private static function castError(object $c, array $a): array + { + if ($errorCode = $c->getErrorCode()) { + $a += [ + Caster::PREFIX_VIRTUAL.'error_code' => $errorCode, + Caster::PREFIX_VIRTUAL.'error_message' => $c->getErrorMessage(), + ]; + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/LinkStub.php b/vendor/symfony/var-dumper/Caster/LinkStub.php new file mode 100644 index 0000000000..7e0780339a --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/LinkStub.php @@ -0,0 +1,108 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +/** + * Represents a file or a URL. + * + * @author Nicolas Grekas <p@tchwork.com> + */ +class LinkStub extends ConstStub +{ + public $inVendor = false; + + private static $vendorRoots; + private static $composerRoots; + + public function __construct(string $label, int $line = 0, string $href = null) + { + $this->value = $label; + + if (null === $href) { + $href = $label; + } + if (!\is_string($href)) { + return; + } + if (str_starts_with($href, 'file://')) { + if ($href === $label) { + $label = substr($label, 7); + } + $href = substr($href, 7); + } elseif (str_contains($href, '://')) { + $this->attr['href'] = $href; + + return; + } + if (!is_file($href)) { + return; + } + if ($line) { + $this->attr['line'] = $line; + } + if ($label !== $this->attr['file'] = realpath($href) ?: $href) { + return; + } + if ($composerRoot = $this->getComposerRoot($href, $this->inVendor)) { + $this->attr['ellipsis'] = \strlen($href) - \strlen($composerRoot) + 1; + $this->attr['ellipsis-type'] = 'path'; + $this->attr['ellipsis-tail'] = 1 + ($this->inVendor ? 2 + \strlen(implode('', \array_slice(explode(\DIRECTORY_SEPARATOR, substr($href, 1 - $this->attr['ellipsis'])), 0, 2))) : 0); + } elseif (3 < \count($ellipsis = explode(\DIRECTORY_SEPARATOR, $href))) { + $this->attr['ellipsis'] = 2 + \strlen(implode('', \array_slice($ellipsis, -2))); + $this->attr['ellipsis-type'] = 'path'; + $this->attr['ellipsis-tail'] = 1; + } + } + + private function getComposerRoot(string $file, bool &$inVendor) + { + if (null === self::$vendorRoots) { + self::$vendorRoots = []; + + foreach (get_declared_classes() as $class) { + if ('C' === $class[0] && str_starts_with($class, 'ComposerAutoloaderInit')) { + $r = new \ReflectionClass($class); + $v = \dirname($r->getFileName(), 2); + if (is_file($v.'/composer/installed.json')) { + self::$vendorRoots[] = $v.\DIRECTORY_SEPARATOR; + } + } + } + } + $inVendor = false; + + if (isset(self::$composerRoots[$dir = \dirname($file)])) { + return self::$composerRoots[$dir]; + } + + foreach (self::$vendorRoots as $root) { + if ($inVendor = str_starts_with($file, $root)) { + return $root; + } + } + + $parent = $dir; + while (!@is_file($parent.'/composer.json')) { + if (!@file_exists($parent)) { + // open_basedir restriction in effect + break; + } + if ($parent === \dirname($parent)) { + return self::$composerRoots[$dir] = false; + } + + $parent = \dirname($parent); + } + + return self::$composerRoots[$dir] = $parent.\DIRECTORY_SEPARATOR; + } +} diff --git a/vendor/symfony/var-dumper/Caster/MemcachedCaster.php b/vendor/symfony/var-dumper/Caster/MemcachedCaster.php new file mode 100644 index 0000000000..cfef19acc3 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/MemcachedCaster.php @@ -0,0 +1,81 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Jan Schädlich <jan.schaedlich@sensiolabs.de> + * + * @final + */ +class MemcachedCaster +{ + private static $optionConstants; + private static $defaultOptions; + + public static function castMemcached(\Memcached $c, array $a, Stub $stub, bool $isNested) + { + $a += [ + Caster::PREFIX_VIRTUAL.'servers' => $c->getServerList(), + Caster::PREFIX_VIRTUAL.'options' => new EnumStub( + self::getNonDefaultOptions($c) + ), + ]; + + return $a; + } + + private static function getNonDefaultOptions(\Memcached $c): array + { + self::$defaultOptions = self::$defaultOptions ?? self::discoverDefaultOptions(); + self::$optionConstants = self::$optionConstants ?? self::getOptionConstants(); + + $nonDefaultOptions = []; + foreach (self::$optionConstants as $constantKey => $value) { + if (self::$defaultOptions[$constantKey] !== $option = $c->getOption($value)) { + $nonDefaultOptions[$constantKey] = $option; + } + } + + return $nonDefaultOptions; + } + + private static function discoverDefaultOptions(): array + { + $defaultMemcached = new \Memcached(); + $defaultMemcached->addServer('127.0.0.1', 11211); + + $defaultOptions = []; + self::$optionConstants = self::$optionConstants ?? self::getOptionConstants(); + + foreach (self::$optionConstants as $constantKey => $value) { + $defaultOptions[$constantKey] = $defaultMemcached->getOption($value); + } + + return $defaultOptions; + } + + private static function getOptionConstants(): array + { + $reflectedMemcached = new \ReflectionClass(\Memcached::class); + + $optionConstants = []; + foreach ($reflectedMemcached->getConstants() as $constantKey => $value) { + if (str_starts_with($constantKey, 'OPT_')) { + $optionConstants[$constantKey] = $value; + } + } + + return $optionConstants; + } +} diff --git a/vendor/symfony/var-dumper/Caster/MysqliCaster.php b/vendor/symfony/var-dumper/Caster/MysqliCaster.php new file mode 100644 index 0000000000..bfe6f0822d --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/MysqliCaster.php @@ -0,0 +1,33 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Nicolas Grekas <p@tchwork.com> + * + * @internal + */ +final class MysqliCaster +{ + public static function castMysqliDriver(\mysqli_driver $c, array $a, Stub $stub, bool $isNested): array + { + foreach ($a as $k => $v) { + if (isset($c->$k)) { + $a[$k] = $c->$k; + } + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/PdoCaster.php b/vendor/symfony/var-dumper/Caster/PdoCaster.php new file mode 100644 index 0000000000..140473b536 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/PdoCaster.php @@ -0,0 +1,122 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts PDO related classes to array representation. + * + * @author Nicolas Grekas <p@tchwork.com> + * + * @final + */ +class PdoCaster +{ + private const PDO_ATTRIBUTES = [ + 'CASE' => [ + \PDO::CASE_LOWER => 'LOWER', + \PDO::CASE_NATURAL => 'NATURAL', + \PDO::CASE_UPPER => 'UPPER', + ], + 'ERRMODE' => [ + \PDO::ERRMODE_SILENT => 'SILENT', + \PDO::ERRMODE_WARNING => 'WARNING', + \PDO::ERRMODE_EXCEPTION => 'EXCEPTION', + ], + 'TIMEOUT', + 'PREFETCH', + 'AUTOCOMMIT', + 'PERSISTENT', + 'DRIVER_NAME', + 'SERVER_INFO', + 'ORACLE_NULLS' => [ + \PDO::NULL_NATURAL => 'NATURAL', + \PDO::NULL_EMPTY_STRING => 'EMPTY_STRING', + \PDO::NULL_TO_STRING => 'TO_STRING', + ], + 'CLIENT_VERSION', + 'SERVER_VERSION', + 'STATEMENT_CLASS', + 'EMULATE_PREPARES', + 'CONNECTION_STATUS', + 'STRINGIFY_FETCHES', + 'DEFAULT_FETCH_MODE' => [ + \PDO::FETCH_ASSOC => 'ASSOC', + \PDO::FETCH_BOTH => 'BOTH', + \PDO::FETCH_LAZY => 'LAZY', + \PDO::FETCH_NUM => 'NUM', + \PDO::FETCH_OBJ => 'OBJ', + ], + ]; + + public static function castPdo(\PDO $c, array $a, Stub $stub, bool $isNested) + { + $attr = []; + $errmode = $c->getAttribute(\PDO::ATTR_ERRMODE); + $c->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + + foreach (self::PDO_ATTRIBUTES as $k => $v) { + if (!isset($k[0])) { + $k = $v; + $v = []; + } + + try { + $attr[$k] = 'ERRMODE' === $k ? $errmode : $c->getAttribute(\constant('PDO::ATTR_'.$k)); + if ($v && isset($v[$attr[$k]])) { + $attr[$k] = new ConstStub($v[$attr[$k]], $attr[$k]); + } + } catch (\Exception $e) { + } + } + if (isset($attr[$k = 'STATEMENT_CLASS'][1])) { + if ($attr[$k][1]) { + $attr[$k][1] = new ArgsStub($attr[$k][1], '__construct', $attr[$k][0]); + } + $attr[$k][0] = new ClassStub($attr[$k][0]); + } + + $prefix = Caster::PREFIX_VIRTUAL; + $a += [ + $prefix.'inTransaction' => method_exists($c, 'inTransaction'), + $prefix.'errorInfo' => $c->errorInfo(), + $prefix.'attributes' => new EnumStub($attr), + ]; + + if ($a[$prefix.'inTransaction']) { + $a[$prefix.'inTransaction'] = $c->inTransaction(); + } else { + unset($a[$prefix.'inTransaction']); + } + + if (!isset($a[$prefix.'errorInfo'][1], $a[$prefix.'errorInfo'][2])) { + unset($a[$prefix.'errorInfo']); + } + + $c->setAttribute(\PDO::ATTR_ERRMODE, $errmode); + + return $a; + } + + public static function castPdoStatement(\PDOStatement $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + $a[$prefix.'errorInfo'] = $c->errorInfo(); + + if (!isset($a[$prefix.'errorInfo'][1], $a[$prefix.'errorInfo'][2])) { + unset($a[$prefix.'errorInfo']); + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/PgSqlCaster.php b/vendor/symfony/var-dumper/Caster/PgSqlCaster.php new file mode 100644 index 0000000000..d8e5b52534 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/PgSqlCaster.php @@ -0,0 +1,156 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts pqsql resources to array representation. + * + * @author Nicolas Grekas <p@tchwork.com> + * + * @final + */ +class PgSqlCaster +{ + private const PARAM_CODES = [ + 'server_encoding', + 'client_encoding', + 'is_superuser', + 'session_authorization', + 'DateStyle', + 'TimeZone', + 'IntervalStyle', + 'integer_datetimes', + 'application_name', + 'standard_conforming_strings', + ]; + + private const TRANSACTION_STATUS = [ + \PGSQL_TRANSACTION_IDLE => 'PGSQL_TRANSACTION_IDLE', + \PGSQL_TRANSACTION_ACTIVE => 'PGSQL_TRANSACTION_ACTIVE', + \PGSQL_TRANSACTION_INTRANS => 'PGSQL_TRANSACTION_INTRANS', + \PGSQL_TRANSACTION_INERROR => 'PGSQL_TRANSACTION_INERROR', + \PGSQL_TRANSACTION_UNKNOWN => 'PGSQL_TRANSACTION_UNKNOWN', + ]; + + private const RESULT_STATUS = [ + \PGSQL_EMPTY_QUERY => 'PGSQL_EMPTY_QUERY', + \PGSQL_COMMAND_OK => 'PGSQL_COMMAND_OK', + \PGSQL_TUPLES_OK => 'PGSQL_TUPLES_OK', + \PGSQL_COPY_OUT => 'PGSQL_COPY_OUT', + \PGSQL_COPY_IN => 'PGSQL_COPY_IN', + \PGSQL_BAD_RESPONSE => 'PGSQL_BAD_RESPONSE', + \PGSQL_NONFATAL_ERROR => 'PGSQL_NONFATAL_ERROR', + \PGSQL_FATAL_ERROR => 'PGSQL_FATAL_ERROR', + ]; + + private const DIAG_CODES = [ + 'severity' => \PGSQL_DIAG_SEVERITY, + 'sqlstate' => \PGSQL_DIAG_SQLSTATE, + 'message' => \PGSQL_DIAG_MESSAGE_PRIMARY, + 'detail' => \PGSQL_DIAG_MESSAGE_DETAIL, + 'hint' => \PGSQL_DIAG_MESSAGE_HINT, + 'statement position' => \PGSQL_DIAG_STATEMENT_POSITION, + 'internal position' => \PGSQL_DIAG_INTERNAL_POSITION, + 'internal query' => \PGSQL_DIAG_INTERNAL_QUERY, + 'context' => \PGSQL_DIAG_CONTEXT, + 'file' => \PGSQL_DIAG_SOURCE_FILE, + 'line' => \PGSQL_DIAG_SOURCE_LINE, + 'function' => \PGSQL_DIAG_SOURCE_FUNCTION, + ]; + + public static function castLargeObject($lo, array $a, Stub $stub, bool $isNested) + { + $a['seek position'] = pg_lo_tell($lo); + + return $a; + } + + public static function castLink($link, array $a, Stub $stub, bool $isNested) + { + $a['status'] = pg_connection_status($link); + $a['status'] = new ConstStub(\PGSQL_CONNECTION_OK === $a['status'] ? 'PGSQL_CONNECTION_OK' : 'PGSQL_CONNECTION_BAD', $a['status']); + $a['busy'] = pg_connection_busy($link); + + $a['transaction'] = pg_transaction_status($link); + if (isset(self::TRANSACTION_STATUS[$a['transaction']])) { + $a['transaction'] = new ConstStub(self::TRANSACTION_STATUS[$a['transaction']], $a['transaction']); + } + + $a['pid'] = pg_get_pid($link); + $a['last error'] = pg_last_error($link); + $a['last notice'] = pg_last_notice($link); + $a['host'] = pg_host($link); + $a['port'] = pg_port($link); + $a['dbname'] = pg_dbname($link); + $a['options'] = pg_options($link); + $a['version'] = pg_version($link); + + foreach (self::PARAM_CODES as $v) { + if (false !== $s = pg_parameter_status($link, $v)) { + $a['param'][$v] = $s; + } + } + + $a['param']['client_encoding'] = pg_client_encoding($link); + $a['param'] = new EnumStub($a['param']); + + return $a; + } + + public static function castResult($result, array $a, Stub $stub, bool $isNested) + { + $a['num rows'] = pg_num_rows($result); + $a['status'] = pg_result_status($result); + if (isset(self::RESULT_STATUS[$a['status']])) { + $a['status'] = new ConstStub(self::RESULT_STATUS[$a['status']], $a['status']); + } + $a['command-completion tag'] = pg_result_status($result, \PGSQL_STATUS_STRING); + + if (-1 === $a['num rows']) { + foreach (self::DIAG_CODES as $k => $v) { + $a['error'][$k] = pg_result_error_field($result, $v); + } + } + + $a['affected rows'] = pg_affected_rows($result); + $a['last OID'] = pg_last_oid($result); + + $fields = pg_num_fields($result); + + for ($i = 0; $i < $fields; ++$i) { + $field = [ + 'name' => pg_field_name($result, $i), + 'table' => sprintf('%s (OID: %s)', pg_field_table($result, $i), pg_field_table($result, $i, true)), + 'type' => sprintf('%s (OID: %s)', pg_field_type($result, $i), pg_field_type_oid($result, $i)), + 'nullable' => (bool) pg_field_is_null($result, $i), + 'storage' => pg_field_size($result, $i).' bytes', + 'display' => pg_field_prtlen($result, $i).' chars', + ]; + if (' (OID: )' === $field['table']) { + $field['table'] = null; + } + if ('-1 bytes' === $field['storage']) { + $field['storage'] = 'variable size'; + } elseif ('1 bytes' === $field['storage']) { + $field['storage'] = '1 byte'; + } + if ('1 chars' === $field['display']) { + $field['display'] = '1 char'; + } + $a['fields'][] = new EnumStub($field); + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/ProxyManagerCaster.php b/vendor/symfony/var-dumper/Caster/ProxyManagerCaster.php new file mode 100644 index 0000000000..e7120191fe --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ProxyManagerCaster.php @@ -0,0 +1,33 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use ProxyManager\Proxy\ProxyInterface; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Nicolas Grekas <p@tchwork.com> + * + * @final + */ +class ProxyManagerCaster +{ + public static function castProxy(ProxyInterface $c, array $a, Stub $stub, bool $isNested) + { + if ($parent = get_parent_class($c)) { + $stub->class .= ' - '.$parent; + } + $stub->class .= '@proxy'; + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/RdKafkaCaster.php b/vendor/symfony/var-dumper/Caster/RdKafkaCaster.php new file mode 100644 index 0000000000..db4bba8d38 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/RdKafkaCaster.php @@ -0,0 +1,186 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use RdKafka\Conf; +use RdKafka\Exception as RdKafkaException; +use RdKafka\KafkaConsumer; +use RdKafka\Message; +use RdKafka\Metadata\Broker as BrokerMetadata; +use RdKafka\Metadata\Collection as CollectionMetadata; +use RdKafka\Metadata\Partition as PartitionMetadata; +use RdKafka\Metadata\Topic as TopicMetadata; +use RdKafka\Topic; +use RdKafka\TopicConf; +use RdKafka\TopicPartition; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts RdKafka related classes to array representation. + * + * @author Romain Neutron <imprec@gmail.com> + */ +class RdKafkaCaster +{ + public static function castKafkaConsumer(KafkaConsumer $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + try { + $assignment = $c->getAssignment(); + } catch (RdKafkaException $e) { + $assignment = []; + } + + $a += [ + $prefix.'subscription' => $c->getSubscription(), + $prefix.'assignment' => $assignment, + ]; + + $a += self::extractMetadata($c); + + return $a; + } + + public static function castTopic(Topic $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'name' => $c->getName(), + ]; + + return $a; + } + + public static function castTopicPartition(TopicPartition $c, array $a) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'offset' => $c->getOffset(), + $prefix.'partition' => $c->getPartition(), + $prefix.'topic' => $c->getTopic(), + ]; + + return $a; + } + + public static function castMessage(Message $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'errstr' => $c->errstr(), + ]; + + return $a; + } + + public static function castConf(Conf $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + foreach ($c->dump() as $key => $value) { + $a[$prefix.$key] = $value; + } + + return $a; + } + + public static function castTopicConf(TopicConf $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + foreach ($c->dump() as $key => $value) { + $a[$prefix.$key] = $value; + } + + return $a; + } + + public static function castRdKafka(\RdKafka $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'out_q_len' => $c->getOutQLen(), + ]; + + $a += self::extractMetadata($c); + + return $a; + } + + public static function castCollectionMetadata(CollectionMetadata $c, array $a, Stub $stub, bool $isNested) + { + $a += iterator_to_array($c); + + return $a; + } + + public static function castTopicMetadata(TopicMetadata $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'name' => $c->getTopic(), + $prefix.'partitions' => $c->getPartitions(), + ]; + + return $a; + } + + public static function castPartitionMetadata(PartitionMetadata $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'id' => $c->getId(), + $prefix.'err' => $c->getErr(), + $prefix.'leader' => $c->getLeader(), + ]; + + return $a; + } + + public static function castBrokerMetadata(BrokerMetadata $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'id' => $c->getId(), + $prefix.'host' => $c->getHost(), + $prefix.'port' => $c->getPort(), + ]; + + return $a; + } + + private static function extractMetadata($c) + { + $prefix = Caster::PREFIX_VIRTUAL; + + try { + $m = $c->getMetadata(true, null, 500); + } catch (RdKafkaException $e) { + return []; + } + + return [ + $prefix.'orig_broker_id' => $m->getOrigBrokerId(), + $prefix.'orig_broker_name' => $m->getOrigBrokerName(), + $prefix.'brokers' => $m->getBrokers(), + $prefix.'topics' => $m->getTopics(), + ]; + } +} diff --git a/vendor/symfony/var-dumper/Caster/RedisCaster.php b/vendor/symfony/var-dumper/Caster/RedisCaster.php new file mode 100644 index 0000000000..8f97eaad3b --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/RedisCaster.php @@ -0,0 +1,152 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts Redis class from ext-redis to array representation. + * + * @author Nicolas Grekas <p@tchwork.com> + * + * @final + */ +class RedisCaster +{ + private const SERIALIZERS = [ + \Redis::SERIALIZER_NONE => 'NONE', + \Redis::SERIALIZER_PHP => 'PHP', + 2 => 'IGBINARY', // Optional Redis::SERIALIZER_IGBINARY + ]; + + private const MODES = [ + \Redis::ATOMIC => 'ATOMIC', + \Redis::MULTI => 'MULTI', + \Redis::PIPELINE => 'PIPELINE', + ]; + + private const COMPRESSION_MODES = [ + 0 => 'NONE', // Redis::COMPRESSION_NONE + 1 => 'LZF', // Redis::COMPRESSION_LZF + ]; + + private const FAILOVER_OPTIONS = [ + \RedisCluster::FAILOVER_NONE => 'NONE', + \RedisCluster::FAILOVER_ERROR => 'ERROR', + \RedisCluster::FAILOVER_DISTRIBUTE => 'DISTRIBUTE', + \RedisCluster::FAILOVER_DISTRIBUTE_SLAVES => 'DISTRIBUTE_SLAVES', + ]; + + public static function castRedis(\Redis $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + if (!$connected = $c->isConnected()) { + return $a + [ + $prefix.'isConnected' => $connected, + ]; + } + + $mode = $c->getMode(); + + return $a + [ + $prefix.'isConnected' => $connected, + $prefix.'host' => $c->getHost(), + $prefix.'port' => $c->getPort(), + $prefix.'auth' => $c->getAuth(), + $prefix.'mode' => isset(self::MODES[$mode]) ? new ConstStub(self::MODES[$mode], $mode) : $mode, + $prefix.'dbNum' => $c->getDbNum(), + $prefix.'timeout' => $c->getTimeout(), + $prefix.'lastError' => $c->getLastError(), + $prefix.'persistentId' => $c->getPersistentID(), + $prefix.'options' => self::getRedisOptions($c), + ]; + } + + public static function castRedisArray(\RedisArray $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + return $a + [ + $prefix.'hosts' => $c->_hosts(), + $prefix.'function' => ClassStub::wrapCallable($c->_function()), + $prefix.'lastError' => $c->getLastError(), + $prefix.'options' => self::getRedisOptions($c), + ]; + } + + public static function castRedisCluster(\RedisCluster $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + $failover = $c->getOption(\RedisCluster::OPT_SLAVE_FAILOVER); + + $a += [ + $prefix.'_masters' => $c->_masters(), + $prefix.'_redir' => $c->_redir(), + $prefix.'mode' => new ConstStub($c->getMode() ? 'MULTI' : 'ATOMIC', $c->getMode()), + $prefix.'lastError' => $c->getLastError(), + $prefix.'options' => self::getRedisOptions($c, [ + 'SLAVE_FAILOVER' => isset(self::FAILOVER_OPTIONS[$failover]) ? new ConstStub(self::FAILOVER_OPTIONS[$failover], $failover) : $failover, + ]), + ]; + + return $a; + } + + /** + * @param \Redis|\RedisArray|\RedisCluster $redis + */ + private static function getRedisOptions($redis, array $options = []): EnumStub + { + $serializer = $redis->getOption(\Redis::OPT_SERIALIZER); + if (\is_array($serializer)) { + foreach ($serializer as &$v) { + if (isset(self::SERIALIZERS[$v])) { + $v = new ConstStub(self::SERIALIZERS[$v], $v); + } + } + } elseif (isset(self::SERIALIZERS[$serializer])) { + $serializer = new ConstStub(self::SERIALIZERS[$serializer], $serializer); + } + + $compression = \defined('Redis::OPT_COMPRESSION') ? $redis->getOption(\Redis::OPT_COMPRESSION) : 0; + if (\is_array($compression)) { + foreach ($compression as &$v) { + if (isset(self::COMPRESSION_MODES[$v])) { + $v = new ConstStub(self::COMPRESSION_MODES[$v], $v); + } + } + } elseif (isset(self::COMPRESSION_MODES[$compression])) { + $compression = new ConstStub(self::COMPRESSION_MODES[$compression], $compression); + } + + $retry = \defined('Redis::OPT_SCAN') ? $redis->getOption(\Redis::OPT_SCAN) : 0; + if (\is_array($retry)) { + foreach ($retry as &$v) { + $v = new ConstStub($v ? 'RETRY' : 'NORETRY', $v); + } + } else { + $retry = new ConstStub($retry ? 'RETRY' : 'NORETRY', $retry); + } + + $options += [ + 'TCP_KEEPALIVE' => \defined('Redis::OPT_TCP_KEEPALIVE') ? $redis->getOption(\Redis::OPT_TCP_KEEPALIVE) : 0, + 'READ_TIMEOUT' => $redis->getOption(\Redis::OPT_READ_TIMEOUT), + 'COMPRESSION' => $compression, + 'SERIALIZER' => $serializer, + 'PREFIX' => $redis->getOption(\Redis::OPT_PREFIX), + 'SCAN' => $retry, + ]; + + return new EnumStub($options); + } +} diff --git a/vendor/symfony/var-dumper/Caster/ReflectionCaster.php b/vendor/symfony/var-dumper/Caster/ReflectionCaster.php new file mode 100644 index 0000000000..274ee0d98f --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ReflectionCaster.php @@ -0,0 +1,442 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts Reflector related classes to array representation. + * + * @author Nicolas Grekas <p@tchwork.com> + * + * @final + */ +class ReflectionCaster +{ + public const UNSET_CLOSURE_FILE_INFO = ['Closure' => __CLASS__.'::unsetClosureFileInfo']; + + private const EXTRA_MAP = [ + 'docComment' => 'getDocComment', + 'extension' => 'getExtensionName', + 'isDisabled' => 'isDisabled', + 'isDeprecated' => 'isDeprecated', + 'isInternal' => 'isInternal', + 'isUserDefined' => 'isUserDefined', + 'isGenerator' => 'isGenerator', + 'isVariadic' => 'isVariadic', + ]; + + public static function castClosure(\Closure $c, array $a, Stub $stub, bool $isNested, int $filter = 0) + { + $prefix = Caster::PREFIX_VIRTUAL; + $c = new \ReflectionFunction($c); + + $a = static::castFunctionAbstract($c, $a, $stub, $isNested, $filter); + + if (!str_contains($c->name, '{closure}')) { + $stub->class = isset($a[$prefix.'class']) ? $a[$prefix.'class']->value.'::'.$c->name : $c->name; + unset($a[$prefix.'class']); + } + unset($a[$prefix.'extra']); + + $stub->class .= self::getSignature($a); + + if ($f = $c->getFileName()) { + $stub->attr['file'] = $f; + $stub->attr['line'] = $c->getStartLine(); + } + + unset($a[$prefix.'parameters']); + + if ($filter & Caster::EXCLUDE_VERBOSE) { + $stub->cut += ($c->getFileName() ? 2 : 0) + \count($a); + + return []; + } + + if ($f) { + $a[$prefix.'file'] = new LinkStub($f, $c->getStartLine()); + $a[$prefix.'line'] = $c->getStartLine().' to '.$c->getEndLine(); + } + + return $a; + } + + public static function unsetClosureFileInfo(\Closure $c, array $a) + { + unset($a[Caster::PREFIX_VIRTUAL.'file'], $a[Caster::PREFIX_VIRTUAL.'line']); + + return $a; + } + + public static function castGenerator(\Generator $c, array $a, Stub $stub, bool $isNested) + { + // Cannot create ReflectionGenerator based on a terminated Generator + try { + $reflectionGenerator = new \ReflectionGenerator($c); + } catch (\Exception $e) { + $a[Caster::PREFIX_VIRTUAL.'closed'] = true; + + return $a; + } + + return self::castReflectionGenerator($reflectionGenerator, $a, $stub, $isNested); + } + + public static function castType(\ReflectionType $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + if ($c instanceof \ReflectionNamedType || \PHP_VERSION_ID < 80000) { + $a += [ + $prefix.'name' => $c instanceof \ReflectionNamedType ? $c->getName() : (string) $c, + $prefix.'allowsNull' => $c->allowsNull(), + $prefix.'isBuiltin' => $c->isBuiltin(), + ]; + } elseif ($c instanceof \ReflectionUnionType || $c instanceof \ReflectionIntersectionType) { + $a[$prefix.'allowsNull'] = $c->allowsNull(); + self::addMap($a, $c, [ + 'types' => 'getTypes', + ]); + } else { + $a[$prefix.'allowsNull'] = $c->allowsNull(); + } + + return $a; + } + + public static function castAttribute(\ReflectionAttribute $c, array $a, Stub $stub, bool $isNested) + { + self::addMap($a, $c, [ + 'name' => 'getName', + 'arguments' => 'getArguments', + ]); + + return $a; + } + + public static function castReflectionGenerator(\ReflectionGenerator $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + if ($c->getThis()) { + $a[$prefix.'this'] = new CutStub($c->getThis()); + } + $function = $c->getFunction(); + $frame = [ + 'class' => $function->class ?? null, + 'type' => isset($function->class) ? ($function->isStatic() ? '::' : '->') : null, + 'function' => $function->name, + 'file' => $c->getExecutingFile(), + 'line' => $c->getExecutingLine(), + ]; + if ($trace = $c->getTrace(\DEBUG_BACKTRACE_IGNORE_ARGS)) { + $function = new \ReflectionGenerator($c->getExecutingGenerator()); + array_unshift($trace, [ + 'function' => 'yield', + 'file' => $function->getExecutingFile(), + 'line' => $function->getExecutingLine() - (int) (\PHP_VERSION_ID < 80100), + ]); + $trace[] = $frame; + $a[$prefix.'trace'] = new TraceStub($trace, false, 0, -1, -1); + } else { + $function = new FrameStub($frame, false, true); + $function = ExceptionCaster::castFrameStub($function, [], $function, true); + $a[$prefix.'executing'] = $function[$prefix.'src']; + } + + $a[Caster::PREFIX_VIRTUAL.'closed'] = false; + + return $a; + } + + public static function castClass(\ReflectionClass $c, array $a, Stub $stub, bool $isNested, int $filter = 0) + { + $prefix = Caster::PREFIX_VIRTUAL; + + if ($n = \Reflection::getModifierNames($c->getModifiers())) { + $a[$prefix.'modifiers'] = implode(' ', $n); + } + + self::addMap($a, $c, [ + 'extends' => 'getParentClass', + 'implements' => 'getInterfaceNames', + 'constants' => 'getReflectionConstants', + ]); + + foreach ($c->getProperties() as $n) { + $a[$prefix.'properties'][$n->name] = $n; + } + + foreach ($c->getMethods() as $n) { + $a[$prefix.'methods'][$n->name] = $n; + } + + self::addAttributes($a, $c, $prefix); + + if (!($filter & Caster::EXCLUDE_VERBOSE) && !$isNested) { + self::addExtra($a, $c); + } + + return $a; + } + + public static function castFunctionAbstract(\ReflectionFunctionAbstract $c, array $a, Stub $stub, bool $isNested, int $filter = 0) + { + $prefix = Caster::PREFIX_VIRTUAL; + + self::addMap($a, $c, [ + 'returnsReference' => 'returnsReference', + 'returnType' => 'getReturnType', + 'class' => 'getClosureScopeClass', + 'this' => 'getClosureThis', + ]); + + if (isset($a[$prefix.'returnType'])) { + $v = $a[$prefix.'returnType']; + $v = $v instanceof \ReflectionNamedType ? $v->getName() : (string) $v; + $a[$prefix.'returnType'] = new ClassStub($a[$prefix.'returnType'] instanceof \ReflectionNamedType && $a[$prefix.'returnType']->allowsNull() && 'mixed' !== $v ? '?'.$v : $v, [class_exists($v, false) || interface_exists($v, false) || trait_exists($v, false) ? $v : '', '']); + } + if (isset($a[$prefix.'class'])) { + $a[$prefix.'class'] = new ClassStub($a[$prefix.'class']); + } + if (isset($a[$prefix.'this'])) { + $a[$prefix.'this'] = new CutStub($a[$prefix.'this']); + } + + foreach ($c->getParameters() as $v) { + $k = '$'.$v->name; + if ($v->isVariadic()) { + $k = '...'.$k; + } + if ($v->isPassedByReference()) { + $k = '&'.$k; + } + $a[$prefix.'parameters'][$k] = $v; + } + if (isset($a[$prefix.'parameters'])) { + $a[$prefix.'parameters'] = new EnumStub($a[$prefix.'parameters']); + } + + self::addAttributes($a, $c, $prefix); + + if (!($filter & Caster::EXCLUDE_VERBOSE) && $v = $c->getStaticVariables()) { + foreach ($v as $k => &$v) { + if (\is_object($v)) { + $a[$prefix.'use']['$'.$k] = new CutStub($v); + } else { + $a[$prefix.'use']['$'.$k] = &$v; + } + } + unset($v); + $a[$prefix.'use'] = new EnumStub($a[$prefix.'use']); + } + + if (!($filter & Caster::EXCLUDE_VERBOSE) && !$isNested) { + self::addExtra($a, $c); + } + + return $a; + } + + public static function castClassConstant(\ReflectionClassConstant $c, array $a, Stub $stub, bool $isNested) + { + $a[Caster::PREFIX_VIRTUAL.'modifiers'] = implode(' ', \Reflection::getModifierNames($c->getModifiers())); + $a[Caster::PREFIX_VIRTUAL.'value'] = $c->getValue(); + + self::addAttributes($a, $c); + + return $a; + } + + public static function castMethod(\ReflectionMethod $c, array $a, Stub $stub, bool $isNested) + { + $a[Caster::PREFIX_VIRTUAL.'modifiers'] = implode(' ', \Reflection::getModifierNames($c->getModifiers())); + + return $a; + } + + public static function castParameter(\ReflectionParameter $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + self::addMap($a, $c, [ + 'position' => 'getPosition', + 'isVariadic' => 'isVariadic', + 'byReference' => 'isPassedByReference', + 'allowsNull' => 'allowsNull', + ]); + + self::addAttributes($a, $c, $prefix); + + if ($v = $c->getType()) { + $a[$prefix.'typeHint'] = $v instanceof \ReflectionNamedType ? $v->getName() : (string) $v; + } + + if (isset($a[$prefix.'typeHint'])) { + $v = $a[$prefix.'typeHint']; + $a[$prefix.'typeHint'] = new ClassStub($v, [class_exists($v, false) || interface_exists($v, false) || trait_exists($v, false) ? $v : '', '']); + } else { + unset($a[$prefix.'allowsNull']); + } + + if ($c->isOptional()) { + try { + $a[$prefix.'default'] = $v = $c->getDefaultValue(); + if ($c->isDefaultValueConstant()) { + $a[$prefix.'default'] = new ConstStub($c->getDefaultValueConstantName(), $v); + } + if (null === $v) { + unset($a[$prefix.'allowsNull']); + } + } catch (\ReflectionException $e) { + } + } + + return $a; + } + + public static function castProperty(\ReflectionProperty $c, array $a, Stub $stub, bool $isNested) + { + $a[Caster::PREFIX_VIRTUAL.'modifiers'] = implode(' ', \Reflection::getModifierNames($c->getModifiers())); + + self::addAttributes($a, $c); + self::addExtra($a, $c); + + return $a; + } + + public static function castReference(\ReflectionReference $c, array $a, Stub $stub, bool $isNested) + { + $a[Caster::PREFIX_VIRTUAL.'id'] = $c->getId(); + + return $a; + } + + public static function castExtension(\ReflectionExtension $c, array $a, Stub $stub, bool $isNested) + { + self::addMap($a, $c, [ + 'version' => 'getVersion', + 'dependencies' => 'getDependencies', + 'iniEntries' => 'getIniEntries', + 'isPersistent' => 'isPersistent', + 'isTemporary' => 'isTemporary', + 'constants' => 'getConstants', + 'functions' => 'getFunctions', + 'classes' => 'getClasses', + ]); + + return $a; + } + + public static function castZendExtension(\ReflectionZendExtension $c, array $a, Stub $stub, bool $isNested) + { + self::addMap($a, $c, [ + 'version' => 'getVersion', + 'author' => 'getAuthor', + 'copyright' => 'getCopyright', + 'url' => 'getURL', + ]); + + return $a; + } + + public static function getSignature(array $a) + { + $prefix = Caster::PREFIX_VIRTUAL; + $signature = ''; + + if (isset($a[$prefix.'parameters'])) { + foreach ($a[$prefix.'parameters']->value as $k => $param) { + $signature .= ', '; + if ($type = $param->getType()) { + if (!$type instanceof \ReflectionNamedType) { + $signature .= $type.' '; + } else { + if (!$param->isOptional() && $param->allowsNull() && 'mixed' !== $type->getName()) { + $signature .= '?'; + } + $signature .= substr(strrchr('\\'.$type->getName(), '\\'), 1).' '; + } + } + $signature .= $k; + + if (!$param->isDefaultValueAvailable()) { + continue; + } + $v = $param->getDefaultValue(); + $signature .= ' = '; + + if ($param->isDefaultValueConstant()) { + $signature .= substr(strrchr('\\'.$param->getDefaultValueConstantName(), '\\'), 1); + } elseif (null === $v) { + $signature .= 'null'; + } elseif (\is_array($v)) { + $signature .= $v ? '[…'.\count($v).']' : '[]'; + } elseif (\is_string($v)) { + $signature .= 10 > \strlen($v) && !str_contains($v, '\\') ? "'{$v}'" : "'…".\strlen($v)."'"; + } elseif (\is_bool($v)) { + $signature .= $v ? 'true' : 'false'; + } elseif (\is_object($v)) { + $signature .= 'new '.substr(strrchr('\\'.get_debug_type($v), '\\'), 1); + } else { + $signature .= $v; + } + } + } + $signature = (empty($a[$prefix.'returnsReference']) ? '' : '&').'('.substr($signature, 2).')'; + + if (isset($a[$prefix.'returnType'])) { + $signature .= ': '.substr(strrchr('\\'.$a[$prefix.'returnType'], '\\'), 1); + } + + return $signature; + } + + private static function addExtra(array &$a, \Reflector $c) + { + $x = isset($a[Caster::PREFIX_VIRTUAL.'extra']) ? $a[Caster::PREFIX_VIRTUAL.'extra']->value : []; + + if (method_exists($c, 'getFileName') && $m = $c->getFileName()) { + $x['file'] = new LinkStub($m, $c->getStartLine()); + $x['line'] = $c->getStartLine().' to '.$c->getEndLine(); + } + + self::addMap($x, $c, self::EXTRA_MAP, ''); + + if ($x) { + $a[Caster::PREFIX_VIRTUAL.'extra'] = new EnumStub($x); + } + } + + private static function addMap(array &$a, object $c, array $map, string $prefix = Caster::PREFIX_VIRTUAL) + { + foreach ($map as $k => $m) { + if (\PHP_VERSION_ID >= 80000 && 'isDisabled' === $k) { + continue; + } + + if (method_exists($c, $m) && false !== ($m = $c->$m()) && null !== $m) { + $a[$prefix.$k] = $m instanceof \Reflector ? $m->name : $m; + } + } + } + + private static function addAttributes(array &$a, \Reflector $c, string $prefix = Caster::PREFIX_VIRTUAL): void + { + if (\PHP_VERSION_ID >= 80000) { + foreach ($c->getAttributes() as $n) { + $a[$prefix.'attributes'][] = $n; + } + } + } +} diff --git a/vendor/symfony/var-dumper/Caster/ResourceCaster.php b/vendor/symfony/var-dumper/Caster/ResourceCaster.php new file mode 100644 index 0000000000..2c34ca9171 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ResourceCaster.php @@ -0,0 +1,103 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts common resource types to array representation. + * + * @author Nicolas Grekas <p@tchwork.com> + * + * @final + */ +class ResourceCaster +{ + /** + * @param \CurlHandle|resource $h + */ + public static function castCurl($h, array $a, Stub $stub, bool $isNested): array + { + return curl_getinfo($h); + } + + public static function castDba($dba, array $a, Stub $stub, bool $isNested) + { + $list = dba_list(); + $a['file'] = $list[(int) $dba]; + + return $a; + } + + public static function castProcess($process, array $a, Stub $stub, bool $isNested) + { + return proc_get_status($process); + } + + public static function castStream($stream, array $a, Stub $stub, bool $isNested) + { + $a = stream_get_meta_data($stream) + static::castStreamContext($stream, $a, $stub, $isNested); + if ($a['uri'] ?? false) { + $a['uri'] = new LinkStub($a['uri']); + } + + return $a; + } + + public static function castStreamContext($stream, array $a, Stub $stub, bool $isNested) + { + return @stream_context_get_params($stream) ?: $a; + } + + public static function castGd($gd, array $a, Stub $stub, bool $isNested) + { + $a['size'] = imagesx($gd).'x'.imagesy($gd); + $a['trueColor'] = imageistruecolor($gd); + + return $a; + } + + public static function castMysqlLink($h, array $a, Stub $stub, bool $isNested) + { + $a['host'] = mysql_get_host_info($h); + $a['protocol'] = mysql_get_proto_info($h); + $a['server'] = mysql_get_server_info($h); + + return $a; + } + + public static function castOpensslX509($h, array $a, Stub $stub, bool $isNested) + { + $stub->cut = -1; + $info = openssl_x509_parse($h, false); + + $pin = openssl_pkey_get_public($h); + $pin = openssl_pkey_get_details($pin)['key']; + $pin = \array_slice(explode("\n", $pin), 1, -2); + $pin = base64_decode(implode('', $pin)); + $pin = base64_encode(hash('sha256', $pin, true)); + + $a += [ + 'subject' => new EnumStub(array_intersect_key($info['subject'], ['organizationName' => true, 'commonName' => true])), + 'issuer' => new EnumStub(array_intersect_key($info['issuer'], ['organizationName' => true, 'commonName' => true])), + 'expiry' => new ConstStub(date(\DateTime::ISO8601, $info['validTo_time_t']), $info['validTo_time_t']), + 'fingerprint' => new EnumStub([ + 'md5' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'md5')), 2, ':', true)), + 'sha1' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'sha1')), 2, ':', true)), + 'sha256' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'sha256')), 2, ':', true)), + 'pin-sha256' => new ConstStub($pin), + ]), + ]; + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/SplCaster.php b/vendor/symfony/var-dumper/Caster/SplCaster.php new file mode 100644 index 0000000000..07f445116f --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/SplCaster.php @@ -0,0 +1,245 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts SPL related classes to array representation. + * + * @author Nicolas Grekas <p@tchwork.com> + * + * @final + */ +class SplCaster +{ + private const SPL_FILE_OBJECT_FLAGS = [ + \SplFileObject::DROP_NEW_LINE => 'DROP_NEW_LINE', + \SplFileObject::READ_AHEAD => 'READ_AHEAD', + \SplFileObject::SKIP_EMPTY => 'SKIP_EMPTY', + \SplFileObject::READ_CSV => 'READ_CSV', + ]; + + public static function castArrayObject(\ArrayObject $c, array $a, Stub $stub, bool $isNested) + { + return self::castSplArray($c, $a, $stub, $isNested); + } + + public static function castArrayIterator(\ArrayIterator $c, array $a, Stub $stub, bool $isNested) + { + return self::castSplArray($c, $a, $stub, $isNested); + } + + public static function castHeap(\Iterator $c, array $a, Stub $stub, bool $isNested) + { + $a += [ + Caster::PREFIX_VIRTUAL.'heap' => iterator_to_array(clone $c), + ]; + + return $a; + } + + public static function castDoublyLinkedList(\SplDoublyLinkedList $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + $mode = $c->getIteratorMode(); + $c->setIteratorMode(\SplDoublyLinkedList::IT_MODE_KEEP | $mode & ~\SplDoublyLinkedList::IT_MODE_DELETE); + + $a += [ + $prefix.'mode' => new ConstStub((($mode & \SplDoublyLinkedList::IT_MODE_LIFO) ? 'IT_MODE_LIFO' : 'IT_MODE_FIFO').' | '.(($mode & \SplDoublyLinkedList::IT_MODE_DELETE) ? 'IT_MODE_DELETE' : 'IT_MODE_KEEP'), $mode), + $prefix.'dllist' => iterator_to_array($c), + ]; + $c->setIteratorMode($mode); + + return $a; + } + + public static function castFileInfo(\SplFileInfo $c, array $a, Stub $stub, bool $isNested) + { + static $map = [ + 'path' => 'getPath', + 'filename' => 'getFilename', + 'basename' => 'getBasename', + 'pathname' => 'getPathname', + 'extension' => 'getExtension', + 'realPath' => 'getRealPath', + 'aTime' => 'getATime', + 'mTime' => 'getMTime', + 'cTime' => 'getCTime', + 'inode' => 'getInode', + 'size' => 'getSize', + 'perms' => 'getPerms', + 'owner' => 'getOwner', + 'group' => 'getGroup', + 'type' => 'getType', + 'writable' => 'isWritable', + 'readable' => 'isReadable', + 'executable' => 'isExecutable', + 'file' => 'isFile', + 'dir' => 'isDir', + 'link' => 'isLink', + 'linkTarget' => 'getLinkTarget', + ]; + + $prefix = Caster::PREFIX_VIRTUAL; + unset($a["\0SplFileInfo\0fileName"]); + unset($a["\0SplFileInfo\0pathName"]); + + if (\PHP_VERSION_ID < 80000) { + if (false === $c->getPathname()) { + $a[$prefix.'⚠'] = 'The parent constructor was not called: the object is in an invalid state'; + + return $a; + } + } else { + try { + $c->isReadable(); + } catch (\RuntimeException $e) { + if ('Object not initialized' !== $e->getMessage()) { + throw $e; + } + + $a[$prefix.'⚠'] = 'The parent constructor was not called: the object is in an invalid state'; + + return $a; + } catch (\Error $e) { + if ('Object not initialized' !== $e->getMessage()) { + throw $e; + } + + $a[$prefix.'⚠'] = 'The parent constructor was not called: the object is in an invalid state'; + + return $a; + } + } + + foreach ($map as $key => $accessor) { + try { + $a[$prefix.$key] = $c->$accessor(); + } catch (\Exception $e) { + } + } + + if ($a[$prefix.'realPath'] ?? false) { + $a[$prefix.'realPath'] = new LinkStub($a[$prefix.'realPath']); + } + + if (isset($a[$prefix.'perms'])) { + $a[$prefix.'perms'] = new ConstStub(sprintf('0%o', $a[$prefix.'perms']), $a[$prefix.'perms']); + } + + static $mapDate = ['aTime', 'mTime', 'cTime']; + foreach ($mapDate as $key) { + if (isset($a[$prefix.$key])) { + $a[$prefix.$key] = new ConstStub(date('Y-m-d H:i:s', $a[$prefix.$key]), $a[$prefix.$key]); + } + } + + return $a; + } + + public static function castFileObject(\SplFileObject $c, array $a, Stub $stub, bool $isNested) + { + static $map = [ + 'csvControl' => 'getCsvControl', + 'flags' => 'getFlags', + 'maxLineLen' => 'getMaxLineLen', + 'fstat' => 'fstat', + 'eof' => 'eof', + 'key' => 'key', + ]; + + $prefix = Caster::PREFIX_VIRTUAL; + + foreach ($map as $key => $accessor) { + try { + $a[$prefix.$key] = $c->$accessor(); + } catch (\Exception $e) { + } + } + + if (isset($a[$prefix.'flags'])) { + $flagsArray = []; + foreach (self::SPL_FILE_OBJECT_FLAGS as $value => $name) { + if ($a[$prefix.'flags'] & $value) { + $flagsArray[] = $name; + } + } + $a[$prefix.'flags'] = new ConstStub(implode('|', $flagsArray), $a[$prefix.'flags']); + } + + if (isset($a[$prefix.'fstat'])) { + $a[$prefix.'fstat'] = new CutArrayStub($a[$prefix.'fstat'], ['dev', 'ino', 'nlink', 'rdev', 'blksize', 'blocks']); + } + + return $a; + } + + public static function castObjectStorage(\SplObjectStorage $c, array $a, Stub $stub, bool $isNested) + { + $storage = []; + unset($a[Caster::PREFIX_DYNAMIC."\0gcdata"]); // Don't hit https://bugs.php.net/65967 + unset($a["\0SplObjectStorage\0storage"]); + + $clone = clone $c; + foreach ($clone as $obj) { + $storage[] = [ + 'object' => $obj, + 'info' => $clone->getInfo(), + ]; + } + + $a += [ + Caster::PREFIX_VIRTUAL.'storage' => $storage, + ]; + + return $a; + } + + public static function castOuterIterator(\OuterIterator $c, array $a, Stub $stub, bool $isNested) + { + $a[Caster::PREFIX_VIRTUAL.'innerIterator'] = $c->getInnerIterator(); + + return $a; + } + + public static function castWeakReference(\WeakReference $c, array $a, Stub $stub, bool $isNested) + { + $a[Caster::PREFIX_VIRTUAL.'object'] = $c->get(); + + return $a; + } + + private static function castSplArray($c, array $a, Stub $stub, bool $isNested): array + { + $prefix = Caster::PREFIX_VIRTUAL; + $flags = $c->getFlags(); + + if (!($flags & \ArrayObject::STD_PROP_LIST)) { + $c->setFlags(\ArrayObject::STD_PROP_LIST); + $a = Caster::castObject($c, \get_class($c), method_exists($c, '__debugInfo'), $stub->class); + $c->setFlags($flags); + } + if (\PHP_VERSION_ID < 70400) { + $a[$prefix.'storage'] = $c->getArrayCopy(); + } + $a += [ + $prefix.'flag::STD_PROP_LIST' => (bool) ($flags & \ArrayObject::STD_PROP_LIST), + $prefix.'flag::ARRAY_AS_PROPS' => (bool) ($flags & \ArrayObject::ARRAY_AS_PROPS), + ]; + if ($c instanceof \ArrayObject) { + $a[$prefix.'iteratorClass'] = new ClassStub($c->getIteratorClass()); + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/StubCaster.php b/vendor/symfony/var-dumper/Caster/StubCaster.php new file mode 100644 index 0000000000..32ead7c277 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/StubCaster.php @@ -0,0 +1,84 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts a caster's Stub. + * + * @author Nicolas Grekas <p@tchwork.com> + * + * @final + */ +class StubCaster +{ + public static function castStub(Stub $c, array $a, Stub $stub, bool $isNested) + { + if ($isNested) { + $stub->type = $c->type; + $stub->class = $c->class; + $stub->value = $c->value; + $stub->handle = $c->handle; + $stub->cut = $c->cut; + $stub->attr = $c->attr; + + if (Stub::TYPE_REF === $c->type && !$c->class && \is_string($c->value) && !preg_match('//u', $c->value)) { + $stub->type = Stub::TYPE_STRING; + $stub->class = Stub::STRING_BINARY; + } + + $a = []; + } + + return $a; + } + + public static function castCutArray(CutArrayStub $c, array $a, Stub $stub, bool $isNested) + { + return $isNested ? $c->preservedSubset : $a; + } + + public static function cutInternals($obj, array $a, Stub $stub, bool $isNested) + { + if ($isNested) { + $stub->cut += \count($a); + + return []; + } + + return $a; + } + + public static function castEnum(EnumStub $c, array $a, Stub $stub, bool $isNested) + { + if ($isNested) { + $stub->class = $c->dumpKeys ? '' : null; + $stub->handle = 0; + $stub->value = null; + $stub->cut = $c->cut; + $stub->attr = $c->attr; + + $a = []; + + if ($c->value) { + foreach (array_keys($c->value) as $k) { + $keys[] = !isset($k[0]) || "\0" !== $k[0] ? Caster::PREFIX_VIRTUAL.$k : $k; + } + // Preserve references with array_combine() + $a = array_combine($keys, $c->value); + } + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/SymfonyCaster.php b/vendor/symfony/var-dumper/Caster/SymfonyCaster.php new file mode 100644 index 0000000000..08428b9274 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/SymfonyCaster.php @@ -0,0 +1,97 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Uid\Ulid; +use Symfony\Component\Uid\Uuid; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @final + */ +class SymfonyCaster +{ + private const REQUEST_GETTERS = [ + 'pathInfo' => 'getPathInfo', + 'requestUri' => 'getRequestUri', + 'baseUrl' => 'getBaseUrl', + 'basePath' => 'getBasePath', + 'method' => 'getMethod', + 'format' => 'getRequestFormat', + ]; + + public static function castRequest(Request $request, array $a, Stub $stub, bool $isNested) + { + $clone = null; + + foreach (self::REQUEST_GETTERS as $prop => $getter) { + $key = Caster::PREFIX_PROTECTED.$prop; + if (\array_key_exists($key, $a) && null === $a[$key]) { + if (null === $clone) { + $clone = clone $request; + } + $a[Caster::PREFIX_VIRTUAL.$prop] = $clone->{$getter}(); + } + } + + return $a; + } + + public static function castHttpClient($client, array $a, Stub $stub, bool $isNested) + { + $multiKey = sprintf("\0%s\0multi", \get_class($client)); + if (isset($a[$multiKey])) { + $a[$multiKey] = new CutStub($a[$multiKey]); + } + + return $a; + } + + public static function castHttpClientResponse($response, array $a, Stub $stub, bool $isNested) + { + $stub->cut += \count($a); + $a = []; + + foreach ($response->getInfo() as $k => $v) { + $a[Caster::PREFIX_VIRTUAL.$k] = $v; + } + + return $a; + } + + public static function castUuid(Uuid $uuid, array $a, Stub $stub, bool $isNested) + { + $a[Caster::PREFIX_VIRTUAL.'toBase58'] = $uuid->toBase58(); + $a[Caster::PREFIX_VIRTUAL.'toBase32'] = $uuid->toBase32(); + + // symfony/uid >= 5.3 + if (method_exists($uuid, 'getDateTime')) { + $a[Caster::PREFIX_VIRTUAL.'time'] = $uuid->getDateTime()->format('Y-m-d H:i:s.u \U\T\C'); + } + + return $a; + } + + public static function castUlid(Ulid $ulid, array $a, Stub $stub, bool $isNested) + { + $a[Caster::PREFIX_VIRTUAL.'toBase58'] = $ulid->toBase58(); + $a[Caster::PREFIX_VIRTUAL.'toRfc4122'] = $ulid->toRfc4122(); + + // symfony/uid >= 5.3 + if (method_exists($ulid, 'getDateTime')) { + $a[Caster::PREFIX_VIRTUAL.'time'] = $ulid->getDateTime()->format('Y-m-d H:i:s.v \U\T\C'); + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/TraceStub.php b/vendor/symfony/var-dumper/Caster/TraceStub.php new file mode 100644 index 0000000000..5eea1c8766 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/TraceStub.php @@ -0,0 +1,36 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Represents a backtrace as returned by debug_backtrace() or Exception->getTrace(). + * + * @author Nicolas Grekas <p@tchwork.com> + */ +class TraceStub extends Stub +{ + public $keepArgs; + public $sliceOffset; + public $sliceLength; + public $numberingOffset; + + public function __construct(array $trace, bool $keepArgs = true, int $sliceOffset = 0, int $sliceLength = null, int $numberingOffset = 0) + { + $this->value = $trace; + $this->keepArgs = $keepArgs; + $this->sliceOffset = $sliceOffset; + $this->sliceLength = $sliceLength; + $this->numberingOffset = $numberingOffset; + } +} diff --git a/vendor/symfony/var-dumper/Caster/UuidCaster.php b/vendor/symfony/var-dumper/Caster/UuidCaster.php new file mode 100644 index 0000000000..b102774571 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/UuidCaster.php @@ -0,0 +1,30 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Ramsey\Uuid\UuidInterface; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Grégoire Pineau <lyrixx@lyrixx.info> + */ +final class UuidCaster +{ + public static function castRamseyUuid(UuidInterface $c, array $a, Stub $stub, bool $isNested): array + { + $a += [ + Caster::PREFIX_VIRTUAL.'uuid' => (string) $c, + ]; + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/XmlReaderCaster.php b/vendor/symfony/var-dumper/Caster/XmlReaderCaster.php new file mode 100644 index 0000000000..5b455651bd --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/XmlReaderCaster.php @@ -0,0 +1,91 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts XmlReader class to array representation. + * + * @author Baptiste Clavié <clavie.b@gmail.com> + * + * @final + */ +class XmlReaderCaster +{ + private const NODE_TYPES = [ + \XMLReader::NONE => 'NONE', + \XMLReader::ELEMENT => 'ELEMENT', + \XMLReader::ATTRIBUTE => 'ATTRIBUTE', + \XMLReader::TEXT => 'TEXT', + \XMLReader::CDATA => 'CDATA', + \XMLReader::ENTITY_REF => 'ENTITY_REF', + \XMLReader::ENTITY => 'ENTITY', + \XMLReader::PI => 'PI (Processing Instruction)', + \XMLReader::COMMENT => 'COMMENT', + \XMLReader::DOC => 'DOC', + \XMLReader::DOC_TYPE => 'DOC_TYPE', + \XMLReader::DOC_FRAGMENT => 'DOC_FRAGMENT', + \XMLReader::NOTATION => 'NOTATION', + \XMLReader::WHITESPACE => 'WHITESPACE', + \XMLReader::SIGNIFICANT_WHITESPACE => 'SIGNIFICANT_WHITESPACE', + \XMLReader::END_ELEMENT => 'END_ELEMENT', + \XMLReader::END_ENTITY => 'END_ENTITY', + \XMLReader::XML_DECLARATION => 'XML_DECLARATION', + ]; + + public static function castXmlReader(\XMLReader $reader, array $a, Stub $stub, bool $isNested) + { + try { + $properties = [ + 'LOADDTD' => @$reader->getParserProperty(\XMLReader::LOADDTD), + 'DEFAULTATTRS' => @$reader->getParserProperty(\XMLReader::DEFAULTATTRS), + 'VALIDATE' => @$reader->getParserProperty(\XMLReader::VALIDATE), + 'SUBST_ENTITIES' => @$reader->getParserProperty(\XMLReader::SUBST_ENTITIES), + ]; + } catch (\Error $e) { + $properties = [ + 'LOADDTD' => false, + 'DEFAULTATTRS' => false, + 'VALIDATE' => false, + 'SUBST_ENTITIES' => false, + ]; + } + + $props = Caster::PREFIX_VIRTUAL.'parserProperties'; + $info = [ + 'localName' => $reader->localName, + 'prefix' => $reader->prefix, + 'nodeType' => new ConstStub(self::NODE_TYPES[$reader->nodeType], $reader->nodeType), + 'depth' => $reader->depth, + 'isDefault' => $reader->isDefault, + 'isEmptyElement' => \XMLReader::NONE === $reader->nodeType ? null : $reader->isEmptyElement, + 'xmlLang' => $reader->xmlLang, + 'attributeCount' => $reader->attributeCount, + 'value' => $reader->value, + 'namespaceURI' => $reader->namespaceURI, + 'baseURI' => $reader->baseURI ? new LinkStub($reader->baseURI) : $reader->baseURI, + $props => $properties, + ]; + + if ($info[$props] = Caster::filter($info[$props], Caster::EXCLUDE_EMPTY, [], $count)) { + $info[$props] = new EnumStub($info[$props]); + $info[$props]->cut = $count; + } + + $info = Caster::filter($info, Caster::EXCLUDE_EMPTY, [], $count); + // +2 because hasValue and hasAttributes are always filtered + $stub->cut += $count + 2; + + return $a + $info; + } +} diff --git a/vendor/symfony/var-dumper/Caster/XmlResourceCaster.php b/vendor/symfony/var-dumper/Caster/XmlResourceCaster.php new file mode 100644 index 0000000000..ba55fcedd9 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/XmlResourceCaster.php @@ -0,0 +1,63 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts XML resources to array representation. + * + * @author Nicolas Grekas <p@tchwork.com> + * + * @final + */ +class XmlResourceCaster +{ + private const XML_ERRORS = [ + \XML_ERROR_NONE => 'XML_ERROR_NONE', + \XML_ERROR_NO_MEMORY => 'XML_ERROR_NO_MEMORY', + \XML_ERROR_SYNTAX => 'XML_ERROR_SYNTAX', + \XML_ERROR_NO_ELEMENTS => 'XML_ERROR_NO_ELEMENTS', + \XML_ERROR_INVALID_TOKEN => 'XML_ERROR_INVALID_TOKEN', + \XML_ERROR_UNCLOSED_TOKEN => 'XML_ERROR_UNCLOSED_TOKEN', + \XML_ERROR_PARTIAL_CHAR => 'XML_ERROR_PARTIAL_CHAR', + \XML_ERROR_TAG_MISMATCH => 'XML_ERROR_TAG_MISMATCH', + \XML_ERROR_DUPLICATE_ATTRIBUTE => 'XML_ERROR_DUPLICATE_ATTRIBUTE', + \XML_ERROR_JUNK_AFTER_DOC_ELEMENT => 'XML_ERROR_JUNK_AFTER_DOC_ELEMENT', + \XML_ERROR_PARAM_ENTITY_REF => 'XML_ERROR_PARAM_ENTITY_REF', + \XML_ERROR_UNDEFINED_ENTITY => 'XML_ERROR_UNDEFINED_ENTITY', + \XML_ERROR_RECURSIVE_ENTITY_REF => 'XML_ERROR_RECURSIVE_ENTITY_REF', + \XML_ERROR_ASYNC_ENTITY => 'XML_ERROR_ASYNC_ENTITY', + \XML_ERROR_BAD_CHAR_REF => 'XML_ERROR_BAD_CHAR_REF', + \XML_ERROR_BINARY_ENTITY_REF => 'XML_ERROR_BINARY_ENTITY_REF', + \XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF => 'XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF', + \XML_ERROR_MISPLACED_XML_PI => 'XML_ERROR_MISPLACED_XML_PI', + \XML_ERROR_UNKNOWN_ENCODING => 'XML_ERROR_UNKNOWN_ENCODING', + \XML_ERROR_INCORRECT_ENCODING => 'XML_ERROR_INCORRECT_ENCODING', + \XML_ERROR_UNCLOSED_CDATA_SECTION => 'XML_ERROR_UNCLOSED_CDATA_SECTION', + \XML_ERROR_EXTERNAL_ENTITY_HANDLING => 'XML_ERROR_EXTERNAL_ENTITY_HANDLING', + ]; + + public static function castXml($h, array $a, Stub $stub, bool $isNested) + { + $a['current_byte_index'] = xml_get_current_byte_index($h); + $a['current_column_number'] = xml_get_current_column_number($h); + $a['current_line_number'] = xml_get_current_line_number($h); + $a['error_code'] = xml_get_error_code($h); + + if (isset(self::XML_ERRORS[$a['error_code']])) { + $a['error_code'] = new ConstStub(self::XML_ERRORS[$a['error_code']], $a['error_code']); + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Cloner/AbstractCloner.php b/vendor/symfony/var-dumper/Cloner/AbstractCloner.php new file mode 100644 index 0000000000..f74a61d7a6 --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/AbstractCloner.php @@ -0,0 +1,400 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +use Symfony\Component\VarDumper\Caster\Caster; +use Symfony\Component\VarDumper\Exception\ThrowingCasterException; + +/** + * AbstractCloner implements a generic caster mechanism for objects and resources. + * + * @author Nicolas Grekas <p@tchwork.com> + */ +abstract class AbstractCloner implements ClonerInterface +{ + public static $defaultCasters = [ + '__PHP_Incomplete_Class' => ['Symfony\Component\VarDumper\Caster\Caster', 'castPhpIncompleteClass'], + + 'Symfony\Component\VarDumper\Caster\CutStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castStub'], + 'Symfony\Component\VarDumper\Caster\CutArrayStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castCutArray'], + 'Symfony\Component\VarDumper\Caster\ConstStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castStub'], + 'Symfony\Component\VarDumper\Caster\EnumStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castEnum'], + + 'Fiber' => ['Symfony\Component\VarDumper\Caster\FiberCaster', 'castFiber'], + + 'Closure' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClosure'], + 'Generator' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castGenerator'], + 'ReflectionType' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castType'], + 'ReflectionAttribute' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castAttribute'], + 'ReflectionGenerator' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castReflectionGenerator'], + 'ReflectionClass' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClass'], + 'ReflectionClassConstant' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClassConstant'], + 'ReflectionFunctionAbstract' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castFunctionAbstract'], + 'ReflectionMethod' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castMethod'], + 'ReflectionParameter' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castParameter'], + 'ReflectionProperty' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castProperty'], + 'ReflectionReference' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castReference'], + 'ReflectionExtension' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castExtension'], + 'ReflectionZendExtension' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castZendExtension'], + + 'Doctrine\Common\Persistence\ObjectManager' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Doctrine\Common\Proxy\Proxy' => ['Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castCommonProxy'], + 'Doctrine\ORM\Proxy\Proxy' => ['Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castOrmProxy'], + 'Doctrine\ORM\PersistentCollection' => ['Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castPersistentCollection'], + 'Doctrine\Persistence\ObjectManager' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + + 'DOMException' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castException'], + 'DOMStringList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], + 'DOMNameList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], + 'DOMImplementation' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castImplementation'], + 'DOMImplementationList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], + 'DOMNode' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNode'], + 'DOMNameSpaceNode' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNameSpaceNode'], + 'DOMDocument' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDocument'], + 'DOMNodeList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], + 'DOMNamedNodeMap' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], + 'DOMCharacterData' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castCharacterData'], + 'DOMAttr' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castAttr'], + 'DOMElement' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castElement'], + 'DOMText' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castText'], + 'DOMTypeinfo' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castTypeinfo'], + 'DOMDomError' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDomError'], + 'DOMLocator' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLocator'], + 'DOMDocumentType' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDocumentType'], + 'DOMNotation' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNotation'], + 'DOMEntity' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castEntity'], + 'DOMProcessingInstruction' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castProcessingInstruction'], + 'DOMXPath' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castXPath'], + + 'XMLReader' => ['Symfony\Component\VarDumper\Caster\XmlReaderCaster', 'castXmlReader'], + + 'ErrorException' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castErrorException'], + 'Exception' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castException'], + 'Error' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castError'], + 'Symfony\Bridge\Monolog\Logger' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Symfony\Component\DependencyInjection\ContainerInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Symfony\Component\EventDispatcher\EventDispatcherInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Symfony\Component\HttpClient\AmpHttpClient' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClient'], + 'Symfony\Component\HttpClient\CurlHttpClient' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClient'], + 'Symfony\Component\HttpClient\NativeHttpClient' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClient'], + 'Symfony\Component\HttpClient\Response\AmpResponse' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClientResponse'], + 'Symfony\Component\HttpClient\Response\CurlResponse' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClientResponse'], + 'Symfony\Component\HttpClient\Response\NativeResponse' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClientResponse'], + 'Symfony\Component\HttpFoundation\Request' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castRequest'], + 'Symfony\Component\Uid\Ulid' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castUlid'], + 'Symfony\Component\Uid\Uuid' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castUuid'], + 'Symfony\Component\VarDumper\Exception\ThrowingCasterException' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castThrowingCasterException'], + 'Symfony\Component\VarDumper\Caster\TraceStub' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castTraceStub'], + 'Symfony\Component\VarDumper\Caster\FrameStub' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castFrameStub'], + 'Symfony\Component\VarDumper\Cloner\AbstractCloner' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Symfony\Component\ErrorHandler\Exception\SilencedErrorContext' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castSilencedErrorContext'], + + 'Imagine\Image\ImageInterface' => ['Symfony\Component\VarDumper\Caster\ImagineCaster', 'castImage'], + + 'Ramsey\Uuid\UuidInterface' => ['Symfony\Component\VarDumper\Caster\UuidCaster', 'castRamseyUuid'], + + 'ProxyManager\Proxy\ProxyInterface' => ['Symfony\Component\VarDumper\Caster\ProxyManagerCaster', 'castProxy'], + 'PHPUnit_Framework_MockObject_MockObject' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'PHPUnit\Framework\MockObject\MockObject' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'PHPUnit\Framework\MockObject\Stub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Prophecy\Prophecy\ProphecySubjectInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Mockery\MockInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + + 'PDO' => ['Symfony\Component\VarDumper\Caster\PdoCaster', 'castPdo'], + 'PDOStatement' => ['Symfony\Component\VarDumper\Caster\PdoCaster', 'castPdoStatement'], + + 'AMQPConnection' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castConnection'], + 'AMQPChannel' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castChannel'], + 'AMQPQueue' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castQueue'], + 'AMQPExchange' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castExchange'], + 'AMQPEnvelope' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castEnvelope'], + + 'ArrayObject' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castArrayObject'], + 'ArrayIterator' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castArrayIterator'], + 'SplDoublyLinkedList' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castDoublyLinkedList'], + 'SplFileInfo' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castFileInfo'], + 'SplFileObject' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castFileObject'], + 'SplHeap' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castHeap'], + 'SplObjectStorage' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castObjectStorage'], + 'SplPriorityQueue' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castHeap'], + 'OuterIterator' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castOuterIterator'], + 'WeakReference' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castWeakReference'], + + 'Redis' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedis'], + 'RedisArray' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedisArray'], + 'RedisCluster' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedisCluster'], + + 'DateTimeInterface' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castDateTime'], + 'DateInterval' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castInterval'], + 'DateTimeZone' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castTimeZone'], + 'DatePeriod' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castPeriod'], + + 'GMP' => ['Symfony\Component\VarDumper\Caster\GmpCaster', 'castGmp'], + + 'MessageFormatter' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castMessageFormatter'], + 'NumberFormatter' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castNumberFormatter'], + 'IntlTimeZone' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castIntlTimeZone'], + 'IntlCalendar' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castIntlCalendar'], + 'IntlDateFormatter' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castIntlDateFormatter'], + + 'Memcached' => ['Symfony\Component\VarDumper\Caster\MemcachedCaster', 'castMemcached'], + + 'Ds\Collection' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castCollection'], + 'Ds\Map' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castMap'], + 'Ds\Pair' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castPair'], + 'Symfony\Component\VarDumper\Caster\DsPairStub' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castPairStub'], + + 'mysqli_driver' => ['Symfony\Component\VarDumper\Caster\MysqliCaster', 'castMysqliDriver'], + + 'CurlHandle' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castCurl'], + ':curl' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castCurl'], + + ':dba' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'], + ':dba persistent' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'], + + 'GdImage' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castGd'], + ':gd' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castGd'], + + ':mysql link' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castMysqlLink'], + ':pgsql large object' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLargeObject'], + ':pgsql link' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLink'], + ':pgsql link persistent' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLink'], + ':pgsql result' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castResult'], + ':process' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castProcess'], + ':stream' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStream'], + + 'OpenSSLCertificate' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castOpensslX509'], + ':OpenSSL X.509' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castOpensslX509'], + + ':persistent stream' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStream'], + ':stream-context' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStreamContext'], + + 'XmlParser' => ['Symfony\Component\VarDumper\Caster\XmlResourceCaster', 'castXml'], + ':xml' => ['Symfony\Component\VarDumper\Caster\XmlResourceCaster', 'castXml'], + + 'RdKafka' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castRdKafka'], + 'RdKafka\Conf' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castConf'], + 'RdKafka\KafkaConsumer' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castKafkaConsumer'], + 'RdKafka\Metadata\Broker' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castBrokerMetadata'], + 'RdKafka\Metadata\Collection' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castCollectionMetadata'], + 'RdKafka\Metadata\Partition' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castPartitionMetadata'], + 'RdKafka\Metadata\Topic' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castTopicMetadata'], + 'RdKafka\Message' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castMessage'], + 'RdKafka\Topic' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castTopic'], + 'RdKafka\TopicPartition' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castTopicPartition'], + 'RdKafka\TopicConf' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castTopicConf'], + ]; + + protected $maxItems = 2500; + protected $maxString = -1; + protected $minDepth = 1; + + /** + * @var array<string, list<callable>> + */ + private $casters = []; + + /** + * @var callable|null + */ + private $prevErrorHandler; + + private $classInfo = []; + private $filter = 0; + + /** + * @param callable[]|null $casters A map of casters + * + * @see addCasters + */ + public function __construct(array $casters = null) + { + if (null === $casters) { + $casters = static::$defaultCasters; + } + $this->addCasters($casters); + } + + /** + * Adds casters for resources and objects. + * + * Maps resources or objects types to a callback. + * Types are in the key, with a callable caster for value. + * Resource types are to be prefixed with a `:`, + * see e.g. static::$defaultCasters. + * + * @param callable[] $casters A map of casters + */ + public function addCasters(array $casters) + { + foreach ($casters as $type => $callback) { + $this->casters[$type][] = $callback; + } + } + + /** + * Sets the maximum number of items to clone past the minimum depth in nested structures. + */ + public function setMaxItems(int $maxItems) + { + $this->maxItems = $maxItems; + } + + /** + * Sets the maximum cloned length for strings. + */ + public function setMaxString(int $maxString) + { + $this->maxString = $maxString; + } + + /** + * Sets the minimum tree depth where we are guaranteed to clone all the items. After this + * depth is reached, only setMaxItems items will be cloned. + */ + public function setMinDepth(int $minDepth) + { + $this->minDepth = $minDepth; + } + + /** + * Clones a PHP variable. + * + * @param mixed $var Any PHP variable + * @param int $filter A bit field of Caster::EXCLUDE_* constants + * + * @return Data + */ + public function cloneVar($var, int $filter = 0) + { + $this->prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = []) { + if (\E_RECOVERABLE_ERROR === $type || \E_USER_ERROR === $type) { + // Cloner never dies + throw new \ErrorException($msg, 0, $type, $file, $line); + } + + if ($this->prevErrorHandler) { + return ($this->prevErrorHandler)($type, $msg, $file, $line, $context); + } + + return false; + }); + $this->filter = $filter; + + if ($gc = gc_enabled()) { + gc_disable(); + } + try { + return new Data($this->doClone($var)); + } finally { + if ($gc) { + gc_enable(); + } + restore_error_handler(); + $this->prevErrorHandler = null; + } + } + + /** + * Effectively clones the PHP variable. + * + * @param mixed $var Any PHP variable + * + * @return array + */ + abstract protected function doClone($var); + + /** + * Casts an object to an array representation. + * + * @param bool $isNested True if the object is nested in the dumped structure + * + * @return array + */ + protected function castObject(Stub $stub, bool $isNested) + { + $obj = $stub->value; + $class = $stub->class; + + if (\PHP_VERSION_ID < 80000 ? "\0" === ($class[15] ?? null) : str_contains($class, "@anonymous\0")) { + $stub->class = get_debug_type($obj); + } + if (isset($this->classInfo[$class])) { + [$i, $parents, $hasDebugInfo, $fileInfo] = $this->classInfo[$class]; + } else { + $i = 2; + $parents = [$class]; + $hasDebugInfo = method_exists($class, '__debugInfo'); + + foreach (class_parents($class) as $p) { + $parents[] = $p; + ++$i; + } + foreach (class_implements($class) as $p) { + $parents[] = $p; + ++$i; + } + $parents[] = '*'; + + $r = new \ReflectionClass($class); + $fileInfo = $r->isInternal() || $r->isSubclassOf(Stub::class) ? [] : [ + 'file' => $r->getFileName(), + 'line' => $r->getStartLine(), + ]; + + $this->classInfo[$class] = [$i, $parents, $hasDebugInfo, $fileInfo]; + } + + $stub->attr += $fileInfo; + $a = Caster::castObject($obj, $class, $hasDebugInfo, $stub->class); + + try { + while ($i--) { + if (!empty($this->casters[$p = $parents[$i]])) { + foreach ($this->casters[$p] as $callback) { + $a = $callback($obj, $a, $stub, $isNested, $this->filter); + } + } + } + } catch (\Exception $e) { + $a = [(Stub::TYPE_OBJECT === $stub->type ? Caster::PREFIX_VIRTUAL : '').'⚠' => new ThrowingCasterException($e)] + $a; + } + + return $a; + } + + /** + * Casts a resource to an array representation. + * + * @param bool $isNested True if the object is nested in the dumped structure + * + * @return array + */ + protected function castResource(Stub $stub, bool $isNested) + { + $a = []; + $res = $stub->value; + $type = $stub->class; + + try { + if (!empty($this->casters[':'.$type])) { + foreach ($this->casters[':'.$type] as $callback) { + $a = $callback($res, $a, $stub, $isNested, $this->filter); + } + } + } catch (\Exception $e) { + $a = [(Stub::TYPE_OBJECT === $stub->type ? Caster::PREFIX_VIRTUAL : '').'⚠' => new ThrowingCasterException($e)] + $a; + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Cloner/ClonerInterface.php b/vendor/symfony/var-dumper/Cloner/ClonerInterface.php new file mode 100644 index 0000000000..90b1495324 --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/ClonerInterface.php @@ -0,0 +1,27 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +/** + * @author Nicolas Grekas <p@tchwork.com> + */ +interface ClonerInterface +{ + /** + * Clones a PHP variable. + * + * @param mixed $var Any PHP variable + * + * @return Data + */ + public function cloneVar($var); +} diff --git a/vendor/symfony/var-dumper/Cloner/Cursor.php b/vendor/symfony/var-dumper/Cloner/Cursor.php new file mode 100644 index 0000000000..1fd796d675 --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/Cursor.php @@ -0,0 +1,43 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +/** + * Represents the current state of a dumper while dumping. + * + * @author Nicolas Grekas <p@tchwork.com> + */ +class Cursor +{ + public const HASH_INDEXED = Stub::ARRAY_INDEXED; + public const HASH_ASSOC = Stub::ARRAY_ASSOC; + public const HASH_OBJECT = Stub::TYPE_OBJECT; + public const HASH_RESOURCE = Stub::TYPE_RESOURCE; + + public $depth = 0; + public $refIndex = 0; + public $softRefTo = 0; + public $softRefCount = 0; + public $softRefHandle = 0; + public $hardRefTo = 0; + public $hardRefCount = 0; + public $hardRefHandle = 0; + public $hashType; + public $hashKey; + public $hashKeyIsBinary; + public $hashIndex = 0; + public $hashLength = 0; + public $hashCut = 0; + public $stop = false; + public $attr = []; + public $skipChildren = false; +} diff --git a/vendor/symfony/var-dumper/Cloner/Data.php b/vendor/symfony/var-dumper/Cloner/Data.php new file mode 100644 index 0000000000..ea8f0f33ab --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/Data.php @@ -0,0 +1,468 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +use Symfony\Component\VarDumper\Caster\Caster; +use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider; + +/** + * @author Nicolas Grekas <p@tchwork.com> + */ +class Data implements \ArrayAccess, \Countable, \IteratorAggregate +{ + private $data; + private $position = 0; + private $key = 0; + private $maxDepth = 20; + private $maxItemsPerDepth = -1; + private $useRefHandles = -1; + private $context = []; + + /** + * @param array $data An array as returned by ClonerInterface::cloneVar() + */ + public function __construct(array $data) + { + $this->data = $data; + } + + /** + * @return string|null + */ + public function getType() + { + $item = $this->data[$this->position][$this->key]; + + if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) { + $item = $item->value; + } + if (!$item instanceof Stub) { + return \gettype($item); + } + if (Stub::TYPE_STRING === $item->type) { + return 'string'; + } + if (Stub::TYPE_ARRAY === $item->type) { + return 'array'; + } + if (Stub::TYPE_OBJECT === $item->type) { + return $item->class; + } + if (Stub::TYPE_RESOURCE === $item->type) { + return $item->class.' resource'; + } + + return null; + } + + /** + * Returns a native representation of the original value. + * + * @param array|bool $recursive Whether values should be resolved recursively or not + * + * @return string|int|float|bool|array|Data[]|null + */ + public function getValue($recursive = false) + { + $item = $this->data[$this->position][$this->key]; + + if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) { + $item = $item->value; + } + if (!($item = $this->getStub($item)) instanceof Stub) { + return $item; + } + if (Stub::TYPE_STRING === $item->type) { + return $item->value; + } + + $children = $item->position ? $this->data[$item->position] : []; + + foreach ($children as $k => $v) { + if ($recursive && !($v = $this->getStub($v)) instanceof Stub) { + continue; + } + $children[$k] = clone $this; + $children[$k]->key = $k; + $children[$k]->position = $item->position; + + if ($recursive) { + if (Stub::TYPE_REF === $v->type && ($v = $this->getStub($v->value)) instanceof Stub) { + $recursive = (array) $recursive; + if (isset($recursive[$v->position])) { + continue; + } + $recursive[$v->position] = true; + } + $children[$k] = $children[$k]->getValue($recursive); + } + } + + return $children; + } + + /** + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + return \count($this->getValue()); + } + + /** + * @return \Traversable + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + if (!\is_array($value = $this->getValue())) { + throw new \LogicException(sprintf('"%s" object holds non-iterable type "%s".', self::class, get_debug_type($value))); + } + + yield from $value; + } + + public function __get(string $key) + { + if (null !== $data = $this->seek($key)) { + $item = $this->getStub($data->data[$data->position][$data->key]); + + return $item instanceof Stub || [] === $item ? $data : $item; + } + + return null; + } + + /** + * @return bool + */ + public function __isset(string $key) + { + return null !== $this->seek($key); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function offsetExists($key) + { + return $this->__isset($key); + } + + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($key) + { + return $this->__get($key); + } + + /** + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetSet($key, $value) + { + throw new \BadMethodCallException(self::class.' objects are immutable.'); + } + + /** + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetUnset($key) + { + throw new \BadMethodCallException(self::class.' objects are immutable.'); + } + + /** + * @return string + */ + public function __toString() + { + $value = $this->getValue(); + + if (!\is_array($value)) { + return (string) $value; + } + + return sprintf('%s (count=%d)', $this->getType(), \count($value)); + } + + /** + * Returns a depth limited clone of $this. + * + * @return static + */ + public function withMaxDepth(int $maxDepth) + { + $data = clone $this; + $data->maxDepth = $maxDepth; + + return $data; + } + + /** + * Limits the number of elements per depth level. + * + * @return static + */ + public function withMaxItemsPerDepth(int $maxItemsPerDepth) + { + $data = clone $this; + $data->maxItemsPerDepth = $maxItemsPerDepth; + + return $data; + } + + /** + * Enables/disables objects' identifiers tracking. + * + * @param bool $useRefHandles False to hide global ref. handles + * + * @return static + */ + public function withRefHandles(bool $useRefHandles) + { + $data = clone $this; + $data->useRefHandles = $useRefHandles ? -1 : 0; + + return $data; + } + + /** + * @return static + */ + public function withContext(array $context) + { + $data = clone $this; + $data->context = $context; + + return $data; + } + + /** + * Seeks to a specific key in nested data structures. + * + * @param string|int $key The key to seek to + * + * @return static|null + */ + public function seek($key) + { + $item = $this->data[$this->position][$this->key]; + + if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) { + $item = $item->value; + } + if (!($item = $this->getStub($item)) instanceof Stub || !$item->position) { + return null; + } + $keys = [$key]; + + switch ($item->type) { + case Stub::TYPE_OBJECT: + $keys[] = Caster::PREFIX_DYNAMIC.$key; + $keys[] = Caster::PREFIX_PROTECTED.$key; + $keys[] = Caster::PREFIX_VIRTUAL.$key; + $keys[] = "\0$item->class\0$key"; + // no break + case Stub::TYPE_ARRAY: + case Stub::TYPE_RESOURCE: + break; + default: + return null; + } + + $data = null; + $children = $this->data[$item->position]; + + foreach ($keys as $key) { + if (isset($children[$key]) || \array_key_exists($key, $children)) { + $data = clone $this; + $data->key = $key; + $data->position = $item->position; + break; + } + } + + return $data; + } + + /** + * Dumps data with a DumperInterface dumper. + */ + public function dump(DumperInterface $dumper) + { + $refs = [0]; + $cursor = new Cursor(); + + if ($cursor->attr = $this->context[SourceContextProvider::class] ?? []) { + $cursor->attr['if_links'] = true; + $cursor->hashType = -1; + $dumper->dumpScalar($cursor, 'default', '^'); + $cursor->attr = ['if_links' => true]; + $dumper->dumpScalar($cursor, 'default', ' '); + $cursor->hashType = 0; + } + + $this->dumpItem($dumper, $cursor, $refs, $this->data[$this->position][$this->key]); + } + + /** + * Depth-first dumping of items. + * + * @param mixed $item A Stub object or the original value being dumped + */ + private function dumpItem(DumperInterface $dumper, Cursor $cursor, array &$refs, $item) + { + $cursor->refIndex = 0; + $cursor->softRefTo = $cursor->softRefHandle = $cursor->softRefCount = 0; + $cursor->hardRefTo = $cursor->hardRefHandle = $cursor->hardRefCount = 0; + $firstSeen = true; + + if (!$item instanceof Stub) { + $cursor->attr = []; + $type = \gettype($item); + if ($item && 'array' === $type) { + $item = $this->getStub($item); + } + } elseif (Stub::TYPE_REF === $item->type) { + if ($item->handle) { + if (!isset($refs[$r = $item->handle - (\PHP_INT_MAX >> 1)])) { + $cursor->refIndex = $refs[$r] = $cursor->refIndex ?: ++$refs[0]; + } else { + $firstSeen = false; + } + $cursor->hardRefTo = $refs[$r]; + $cursor->hardRefHandle = $this->useRefHandles & $item->handle; + $cursor->hardRefCount = 0 < $item->handle ? $item->refCount : 0; + } + $cursor->attr = $item->attr; + $type = $item->class ?: \gettype($item->value); + $item = $this->getStub($item->value); + } + if ($item instanceof Stub) { + if ($item->refCount) { + if (!isset($refs[$r = $item->handle])) { + $cursor->refIndex = $refs[$r] = $cursor->refIndex ?: ++$refs[0]; + } else { + $firstSeen = false; + } + $cursor->softRefTo = $refs[$r]; + } + $cursor->softRefHandle = $this->useRefHandles & $item->handle; + $cursor->softRefCount = $item->refCount; + $cursor->attr = $item->attr; + $cut = $item->cut; + + if ($item->position && $firstSeen) { + $children = $this->data[$item->position]; + + if ($cursor->stop) { + if ($cut >= 0) { + $cut += \count($children); + } + $children = []; + } + } else { + $children = []; + } + switch ($item->type) { + case Stub::TYPE_STRING: + $dumper->dumpString($cursor, $item->value, Stub::STRING_BINARY === $item->class, $cut); + break; + + case Stub::TYPE_ARRAY: + $item = clone $item; + $item->type = $item->class; + $item->class = $item->value; + // no break + case Stub::TYPE_OBJECT: + case Stub::TYPE_RESOURCE: + $withChildren = $children && $cursor->depth !== $this->maxDepth && $this->maxItemsPerDepth; + $dumper->enterHash($cursor, $item->type, $item->class, $withChildren); + if ($withChildren) { + if ($cursor->skipChildren) { + $withChildren = false; + $cut = -1; + } else { + $cut = $this->dumpChildren($dumper, $cursor, $refs, $children, $cut, $item->type, null !== $item->class); + } + } elseif ($children && 0 <= $cut) { + $cut += \count($children); + } + $cursor->skipChildren = false; + $dumper->leaveHash($cursor, $item->type, $item->class, $withChildren, $cut); + break; + + default: + throw new \RuntimeException(sprintf('Unexpected Stub type: "%s".', $item->type)); + } + } elseif ('array' === $type) { + $dumper->enterHash($cursor, Cursor::HASH_INDEXED, 0, false); + $dumper->leaveHash($cursor, Cursor::HASH_INDEXED, 0, false, 0); + } elseif ('string' === $type) { + $dumper->dumpString($cursor, $item, false, 0); + } else { + $dumper->dumpScalar($cursor, $type, $item); + } + } + + /** + * Dumps children of hash structures. + * + * @return int The final number of removed items + */ + private function dumpChildren(DumperInterface $dumper, Cursor $parentCursor, array &$refs, array $children, int $hashCut, int $hashType, bool $dumpKeys): int + { + $cursor = clone $parentCursor; + ++$cursor->depth; + $cursor->hashType = $hashType; + $cursor->hashIndex = 0; + $cursor->hashLength = \count($children); + $cursor->hashCut = $hashCut; + foreach ($children as $key => $child) { + $cursor->hashKeyIsBinary = isset($key[0]) && !preg_match('//u', $key); + $cursor->hashKey = $dumpKeys ? $key : null; + $this->dumpItem($dumper, $cursor, $refs, $child); + if (++$cursor->hashIndex === $this->maxItemsPerDepth || $cursor->stop) { + $parentCursor->stop = true; + + return $hashCut >= 0 ? $hashCut + $cursor->hashLength - $cursor->hashIndex : $hashCut; + } + } + + return $hashCut; + } + + private function getStub($item) + { + if (!$item || !\is_array($item)) { + return $item; + } + + $stub = new Stub(); + $stub->type = Stub::TYPE_ARRAY; + foreach ($item as $stub->class => $stub->position) { + } + if (isset($item[0])) { + $stub->cut = $item[0]; + } + $stub->value = $stub->cut + ($stub->position ? \count($this->data[$stub->position]) : 0); + + return $stub; + } +} diff --git a/vendor/symfony/var-dumper/Cloner/DumperInterface.php b/vendor/symfony/var-dumper/Cloner/DumperInterface.php new file mode 100644 index 0000000000..6d60b723c7 --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/DumperInterface.php @@ -0,0 +1,56 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +/** + * DumperInterface used by Data objects. + * + * @author Nicolas Grekas <p@tchwork.com> + */ +interface DumperInterface +{ + /** + * Dumps a scalar value. + * + * @param string $type The PHP type of the value being dumped + * @param string|int|float|bool $value The scalar value being dumped + */ + public function dumpScalar(Cursor $cursor, string $type, $value); + + /** + * Dumps a string. + * + * @param string $str The string being dumped + * @param bool $bin Whether $str is UTF-8 or binary encoded + * @param int $cut The number of characters $str has been cut by + */ + public function dumpString(Cursor $cursor, string $str, bool $bin, int $cut); + + /** + * Dumps while entering an hash. + * + * @param int $type A Cursor::HASH_* const for the type of hash + * @param string|int $class The object class, resource type or array count + * @param bool $hasChild When the dump of the hash has child item + */ + public function enterHash(Cursor $cursor, int $type, $class, bool $hasChild); + + /** + * Dumps while leaving an hash. + * + * @param int $type A Cursor::HASH_* const for the type of hash + * @param string|int $class The object class, resource type or array count + * @param bool $hasChild When the dump of the hash has child item + * @param int $cut The number of items the hash has been cut by + */ + public function leaveHash(Cursor $cursor, int $type, $class, bool $hasChild, int $cut); +} diff --git a/vendor/symfony/var-dumper/Cloner/Stub.php b/vendor/symfony/var-dumper/Cloner/Stub.php new file mode 100644 index 0000000000..073c56efbd --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/Stub.php @@ -0,0 +1,67 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +/** + * Represents the main properties of a PHP variable. + * + * @author Nicolas Grekas <p@tchwork.com> + */ +class Stub +{ + public const TYPE_REF = 1; + public const TYPE_STRING = 2; + public const TYPE_ARRAY = 3; + public const TYPE_OBJECT = 4; + public const TYPE_RESOURCE = 5; + + public const STRING_BINARY = 1; + public const STRING_UTF8 = 2; + + public const ARRAY_ASSOC = 1; + public const ARRAY_INDEXED = 2; + + public $type = self::TYPE_REF; + public $class = ''; + public $value; + public $cut = 0; + public $handle = 0; + public $refCount = 0; + public $position = 0; + public $attr = []; + + private static $defaultProperties = []; + + /** + * @internal + */ + public function __sleep(): array + { + $properties = []; + + if (!isset(self::$defaultProperties[$c = static::class])) { + self::$defaultProperties[$c] = get_class_vars($c); + + foreach ((new \ReflectionClass($c))->getStaticProperties() as $k => $v) { + unset(self::$defaultProperties[$c][$k]); + } + } + + foreach (self::$defaultProperties[$c] as $k => $v) { + if ($this->$k !== $v) { + $properties[] = $k; + } + } + + return $properties; + } +} diff --git a/vendor/symfony/var-dumper/Cloner/VarCloner.php b/vendor/symfony/var-dumper/Cloner/VarCloner.php new file mode 100644 index 0000000000..80c4a2f839 --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/VarCloner.php @@ -0,0 +1,311 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +/** + * @author Nicolas Grekas <p@tchwork.com> + */ +class VarCloner extends AbstractCloner +{ + private static $gid; + private static $arrayCache = []; + + /** + * {@inheritdoc} + */ + protected function doClone($var) + { + $len = 1; // Length of $queue + $pos = 0; // Number of cloned items past the minimum depth + $refsCounter = 0; // Hard references counter + $queue = [[$var]]; // This breadth-first queue is the return value + $hardRefs = []; // Map of original zval ids to stub objects + $objRefs = []; // Map of original object handles to their stub object counterpart + $objects = []; // Keep a ref to objects to ensure their handle cannot be reused while cloning + $resRefs = []; // Map of original resource handles to their stub object counterpart + $values = []; // Map of stub objects' ids to original values + $maxItems = $this->maxItems; + $maxString = $this->maxString; + $minDepth = $this->minDepth; + $currentDepth = 0; // Current tree depth + $currentDepthFinalIndex = 0; // Final $queue index for current tree depth + $minimumDepthReached = 0 === $minDepth; // Becomes true when minimum tree depth has been reached + $cookie = (object) []; // Unique object used to detect hard references + $a = null; // Array cast for nested structures + $stub = null; // Stub capturing the main properties of an original item value + // or null if the original value is used directly + + if (!$gid = self::$gid) { + $gid = self::$gid = md5(random_bytes(6)); // Unique string used to detect the special $GLOBALS variable + } + $arrayStub = new Stub(); + $arrayStub->type = Stub::TYPE_ARRAY; + $fromObjCast = false; + + for ($i = 0; $i < $len; ++$i) { + // Detect when we move on to the next tree depth + if ($i > $currentDepthFinalIndex) { + ++$currentDepth; + $currentDepthFinalIndex = $len - 1; + if ($currentDepth >= $minDepth) { + $minimumDepthReached = true; + } + } + + $refs = $vals = $queue[$i]; + foreach ($vals as $k => $v) { + // $v is the original value or a stub object in case of hard references + + if (\PHP_VERSION_ID >= 70400) { + $zvalRef = ($r = \ReflectionReference::fromArrayElement($vals, $k)) ? $r->getId() : null; + } else { + $refs[$k] = $cookie; + $zvalRef = $vals[$k] === $cookie; + } + + if ($zvalRef) { + $vals[$k] = &$stub; // Break hard references to make $queue completely + unset($stub); // independent from the original structure + if (\PHP_VERSION_ID >= 70400 ? null !== $vals[$k] = $hardRefs[$zvalRef] ?? null : $v instanceof Stub && isset($hardRefs[spl_object_id($v)])) { + if (\PHP_VERSION_ID >= 70400) { + $v = $vals[$k]; + } else { + $refs[$k] = $vals[$k] = $v; + } + if ($v->value instanceof Stub && (Stub::TYPE_OBJECT === $v->value->type || Stub::TYPE_RESOURCE === $v->value->type)) { + ++$v->value->refCount; + } + ++$v->refCount; + continue; + } + $vals[$k] = new Stub(); + $vals[$k]->value = $v; + $vals[$k]->handle = ++$refsCounter; + + if (\PHP_VERSION_ID >= 70400) { + $hardRefs[$zvalRef] = $vals[$k]; + } else { + $refs[$k] = $vals[$k]; + $h = spl_object_id($refs[$k]); + $hardRefs[$h] = &$refs[$k]; + $values[$h] = $v; + } + } + // Create $stub when the original value $v cannot be used directly + // If $v is a nested structure, put that structure in array $a + switch (true) { + case null === $v: + case \is_bool($v): + case \is_int($v): + case \is_float($v): + continue 2; + case \is_string($v): + if ('' === $v) { + continue 2; + } + if (!preg_match('//u', $v)) { + $stub = new Stub(); + $stub->type = Stub::TYPE_STRING; + $stub->class = Stub::STRING_BINARY; + if (0 <= $maxString && 0 < $cut = \strlen($v) - $maxString) { + $stub->cut = $cut; + $stub->value = substr($v, 0, -$cut); + } else { + $stub->value = $v; + } + } elseif (0 <= $maxString && isset($v[1 + ($maxString >> 2)]) && 0 < $cut = mb_strlen($v, 'UTF-8') - $maxString) { + $stub = new Stub(); + $stub->type = Stub::TYPE_STRING; + $stub->class = Stub::STRING_UTF8; + $stub->cut = $cut; + $stub->value = mb_substr($v, 0, $maxString, 'UTF-8'); + } else { + continue 2; + } + $a = null; + break; + + case \is_array($v): + if (!$v) { + continue 2; + } + $stub = $arrayStub; + + if (\PHP_VERSION_ID >= 80100) { + $stub->class = array_is_list($v) ? Stub::ARRAY_INDEXED : Stub::ARRAY_ASSOC; + $a = $v; + break; + } + + $stub->class = Stub::ARRAY_INDEXED; + + $j = -1; + foreach ($v as $gk => $gv) { + if ($gk !== ++$j) { + $stub->class = Stub::ARRAY_ASSOC; + $a = $v; + $a[$gid] = true; + break; + } + } + + // Copies of $GLOBALS have very strange behavior, + // let's detect them with some black magic + if (isset($v[$gid])) { + unset($v[$gid]); + $a = []; + foreach ($v as $gk => &$gv) { + if ($v === $gv && (\PHP_VERSION_ID < 70400 || !isset($hardRefs[\ReflectionReference::fromArrayElement($v, $gk)->getId()]))) { + unset($v); + $v = new Stub(); + $v->value = [$v->cut = \count($gv), Stub::TYPE_ARRAY => 0]; + $v->handle = -1; + if (\PHP_VERSION_ID >= 70400) { + $gv = &$a[$gk]; + $hardRefs[\ReflectionReference::fromArrayElement($a, $gk)->getId()] = &$gv; + } else { + $gv = &$hardRefs[spl_object_id($v)]; + } + $gv = $v; + } + + $a[$gk] = &$gv; + } + unset($gv); + } else { + $a = $v; + } + break; + + case \is_object($v): + if (empty($objRefs[$h = spl_object_id($v)])) { + $stub = new Stub(); + $stub->type = Stub::TYPE_OBJECT; + $stub->class = \get_class($v); + $stub->value = $v; + $stub->handle = $h; + $a = $this->castObject($stub, 0 < $i); + if ($v !== $stub->value) { + if (Stub::TYPE_OBJECT !== $stub->type || null === $stub->value) { + break; + } + $stub->handle = $h = spl_object_id($stub->value); + } + $stub->value = null; + if (0 <= $maxItems && $maxItems <= $pos && $minimumDepthReached) { + $stub->cut = \count($a); + $a = null; + } + } + if (empty($objRefs[$h])) { + $objRefs[$h] = $stub; + $objects[] = $v; + } else { + $stub = $objRefs[$h]; + ++$stub->refCount; + $a = null; + } + break; + + default: // resource + if (empty($resRefs[$h = (int) $v])) { + $stub = new Stub(); + $stub->type = Stub::TYPE_RESOURCE; + if ('Unknown' === $stub->class = @get_resource_type($v)) { + $stub->class = 'Closed'; + } + $stub->value = $v; + $stub->handle = $h; + $a = $this->castResource($stub, 0 < $i); + $stub->value = null; + if (0 <= $maxItems && $maxItems <= $pos && $minimumDepthReached) { + $stub->cut = \count($a); + $a = null; + } + } + if (empty($resRefs[$h])) { + $resRefs[$h] = $stub; + } else { + $stub = $resRefs[$h]; + ++$stub->refCount; + $a = null; + } + break; + } + + if ($a) { + if (!$minimumDepthReached || 0 > $maxItems) { + $queue[$len] = $a; + $stub->position = $len++; + } elseif ($pos < $maxItems) { + if ($maxItems < $pos += \count($a)) { + $a = \array_slice($a, 0, $maxItems - $pos, true); + if ($stub->cut >= 0) { + $stub->cut += $pos - $maxItems; + } + } + $queue[$len] = $a; + $stub->position = $len++; + } elseif ($stub->cut >= 0) { + $stub->cut += \count($a); + $stub->position = 0; + } + } + + if ($arrayStub === $stub) { + if ($arrayStub->cut) { + $stub = [$arrayStub->cut, $arrayStub->class => $arrayStub->position]; + $arrayStub->cut = 0; + } elseif (isset(self::$arrayCache[$arrayStub->class][$arrayStub->position])) { + $stub = self::$arrayCache[$arrayStub->class][$arrayStub->position]; + } else { + self::$arrayCache[$arrayStub->class][$arrayStub->position] = $stub = [$arrayStub->class => $arrayStub->position]; + } + } + + if (!$zvalRef) { + $vals[$k] = $stub; + } elseif (\PHP_VERSION_ID >= 70400) { + $hardRefs[$zvalRef]->value = $stub; + } else { + $refs[$k]->value = $stub; + } + } + + if ($fromObjCast) { + $fromObjCast = false; + $refs = $vals; + $vals = []; + $j = -1; + foreach ($queue[$i] as $k => $v) { + foreach ([$k => true] as $gk => $gv) { + } + if ($gk !== $k) { + $vals = (object) $vals; + $vals->{$k} = $refs[++$j]; + $vals = (array) $vals; + } else { + $vals[$k] = $refs[++$j]; + } + } + } + + $queue[$i] = $vals; + } + + foreach ($values as $h => $v) { + $hardRefs[$h] = $v; + } + + return $queue; + } +} diff --git a/vendor/symfony/var-dumper/Command/Descriptor/CliDescriptor.php b/vendor/symfony/var-dumper/Command/Descriptor/CliDescriptor.php new file mode 100644 index 0000000000..2afaa7bf39 --- /dev/null +++ b/vendor/symfony/var-dumper/Command/Descriptor/CliDescriptor.php @@ -0,0 +1,79 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Command\Descriptor; + +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\CliDumper; + +/** + * Describe collected data clones for cli output. + * + * @author Maxime Steinhausser <maxime.steinhausser@gmail.com> + * + * @final + */ +class CliDescriptor implements DumpDescriptorInterface +{ + private $dumper; + private $lastIdentifier; + + public function __construct(CliDumper $dumper) + { + $this->dumper = $dumper; + } + + public function describe(OutputInterface $output, Data $data, array $context, int $clientId): void + { + $io = $output instanceof SymfonyStyle ? $output : new SymfonyStyle(new ArrayInput([]), $output); + $this->dumper->setColors($output->isDecorated()); + + $rows = [['date', date('r', (int) $context['timestamp'])]]; + $lastIdentifier = $this->lastIdentifier; + $this->lastIdentifier = $clientId; + + $section = "Received from client #$clientId"; + if (isset($context['request'])) { + $request = $context['request']; + $this->lastIdentifier = $request['identifier']; + $section = sprintf('%s %s', $request['method'], $request['uri']); + if ($controller = $request['controller']) { + $rows[] = ['controller', rtrim($this->dumper->dump($controller, true), "\n")]; + } + } elseif (isset($context['cli'])) { + $this->lastIdentifier = $context['cli']['identifier']; + $section = '$ '.$context['cli']['command_line']; + } + + if ($this->lastIdentifier !== $lastIdentifier) { + $io->section($section); + } + + if (isset($context['source'])) { + $source = $context['source']; + $sourceInfo = sprintf('%s on line %d', $source['name'], $source['line']); + if ($fileLink = $source['file_link'] ?? null) { + $sourceInfo = sprintf('<href=%s>%s</>', $fileLink, $sourceInfo); + } + $rows[] = ['source', $sourceInfo]; + $file = $source['file_relative'] ?? $source['file']; + $rows[] = ['file', $file]; + } + + $io->table([], $rows); + + $this->dumper->dump($data); + $io->newLine(); + } +} diff --git a/vendor/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php b/vendor/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php new file mode 100644 index 0000000000..267d27bfac --- /dev/null +++ b/vendor/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php @@ -0,0 +1,23 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Command\Descriptor; + +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * @author Maxime Steinhausser <maxime.steinhausser@gmail.com> + */ +interface DumpDescriptorInterface +{ + public function describe(OutputInterface $output, Data $data, array $context, int $clientId): void; +} diff --git a/vendor/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php b/vendor/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php new file mode 100644 index 0000000000..636b61828d --- /dev/null +++ b/vendor/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php @@ -0,0 +1,119 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Command\Descriptor; + +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; + +/** + * Describe collected data clones for html output. + * + * @author Maxime Steinhausser <maxime.steinhausser@gmail.com> + * + * @final + */ +class HtmlDescriptor implements DumpDescriptorInterface +{ + private $dumper; + private $initialized = false; + + public function __construct(HtmlDumper $dumper) + { + $this->dumper = $dumper; + } + + public function describe(OutputInterface $output, Data $data, array $context, int $clientId): void + { + if (!$this->initialized) { + $styles = file_get_contents(__DIR__.'/../../Resources/css/htmlDescriptor.css'); + $scripts = file_get_contents(__DIR__.'/../../Resources/js/htmlDescriptor.js'); + $output->writeln("<style>$styles</style><script>$scripts</script>"); + $this->initialized = true; + } + + $title = '-'; + if (isset($context['request'])) { + $request = $context['request']; + $controller = "<span class='dumped-tag'>{$this->dumper->dump($request['controller'], true, ['maxDepth' => 0])}</span>"; + $title = sprintf('<code>%s</code> <a href="%s">%s</a>', $request['method'], $uri = $request['uri'], $uri); + $dedupIdentifier = $request['identifier']; + } elseif (isset($context['cli'])) { + $title = '<code>$ </code>'.$context['cli']['command_line']; + $dedupIdentifier = $context['cli']['identifier']; + } else { + $dedupIdentifier = uniqid('', true); + } + + $sourceDescription = ''; + if (isset($context['source'])) { + $source = $context['source']; + $projectDir = $source['project_dir'] ?? null; + $sourceDescription = sprintf('%s on line %d', $source['name'], $source['line']); + if (isset($source['file_link'])) { + $sourceDescription = sprintf('<a href="%s">%s</a>', $source['file_link'], $sourceDescription); + } + } + + $isoDate = $this->extractDate($context, 'c'); + $tags = array_filter([ + 'controller' => $controller ?? null, + 'project dir' => $projectDir ?? null, + ]); + + $output->writeln(<<<HTML +<article data-dedup-id="$dedupIdentifier"> + <header> + <div class="row"> + <h2 class="col">$title</h2> + <time class="col text-small" title="$isoDate" datetime="$isoDate"> + {$this->extractDate($context)} + </time> + </div> + {$this->renderTags($tags)} + </header> + <section class="body"> + <p class="text-small"> + $sourceDescription + </p> + {$this->dumper->dump($data, true)} + </section> +</article> +HTML + ); + } + + private function extractDate(array $context, string $format = 'r'): string + { + return date($format, (int) $context['timestamp']); + } + + private function renderTags(array $tags): string + { + if (!$tags) { + return ''; + } + + $renderedTags = ''; + foreach ($tags as $key => $value) { + $renderedTags .= sprintf('<li><span class="badge">%s</span>%s</li>', $key, $value); + } + + return <<<HTML +<div class="row"> + <ul class="tags"> + $renderedTags + </ul> +</div> +HTML; + } +} diff --git a/vendor/symfony/var-dumper/Command/ServerDumpCommand.php b/vendor/symfony/var-dumper/Command/ServerDumpCommand.php new file mode 100644 index 0000000000..3a6959522e --- /dev/null +++ b/vendor/symfony/var-dumper/Command/ServerDumpCommand.php @@ -0,0 +1,114 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Command\Descriptor\CliDescriptor; +use Symfony\Component\VarDumper\Command\Descriptor\DumpDescriptorInterface; +use Symfony\Component\VarDumper\Command\Descriptor\HtmlDescriptor; +use Symfony\Component\VarDumper\Dumper\CliDumper; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; +use Symfony\Component\VarDumper\Server\DumpServer; + +/** + * Starts a dump server to collect and output dumps on a single place with multiple formats support. + * + * @author Maxime Steinhausser <maxime.steinhausser@gmail.com> + * + * @final + */ +class ServerDumpCommand extends Command +{ + protected static $defaultName = 'server:dump'; + protected static $defaultDescription = 'Start a dump server that collects and displays dumps in a single place'; + + private $server; + + /** @var DumpDescriptorInterface[] */ + private $descriptors; + + public function __construct(DumpServer $server, array $descriptors = []) + { + $this->server = $server; + $this->descriptors = $descriptors + [ + 'cli' => new CliDescriptor(new CliDumper()), + 'html' => new HtmlDescriptor(new HtmlDumper()), + ]; + + parent::__construct(); + } + + protected function configure() + { + $this + ->addOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format (%s)', implode(', ', $this->getAvailableFormats())), 'cli') + ->setDescription(self::$defaultDescription) + ->setHelp(<<<'EOF' +<info>%command.name%</info> starts a dump server that collects and displays +dumps in a single place for debugging you application: + + <info>php %command.full_name%</info> + +You can consult dumped data in HTML format in your browser by providing the <comment>--format=html</comment> option +and redirecting the output to a file: + + <info>php %command.full_name% --format="html" > dump.html</info> + +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $format = $input->getOption('format'); + + if (!$descriptor = $this->descriptors[$format] ?? null) { + throw new InvalidArgumentException(sprintf('Unsupported format "%s".', $format)); + } + + $errorIo = $io->getErrorStyle(); + $errorIo->title('Symfony Var Dumper Server'); + + $this->server->start(); + + $errorIo->success(sprintf('Server listening on %s', $this->server->getHost())); + $errorIo->comment('Quit the server with CONTROL-C.'); + + $this->server->listen(function (Data $data, array $context, int $clientId) use ($descriptor, $io) { + $descriptor->describe($io, $data, $context, $clientId); + }); + + return 0; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues($this->getAvailableFormats()); + } + } + + private function getAvailableFormats(): array + { + return array_keys($this->descriptors); + } +} diff --git a/vendor/symfony/var-dumper/Dumper/AbstractDumper.php b/vendor/symfony/var-dumper/Dumper/AbstractDumper.php new file mode 100644 index 0000000000..ae19faf613 --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/AbstractDumper.php @@ -0,0 +1,202 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper; + +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Cloner\DumperInterface; + +/** + * Abstract mechanism for dumping a Data object. + * + * @author Nicolas Grekas <p@tchwork.com> + */ +abstract class AbstractDumper implements DataDumperInterface, DumperInterface +{ + public const DUMP_LIGHT_ARRAY = 1; + public const DUMP_STRING_LENGTH = 2; + public const DUMP_COMMA_SEPARATOR = 4; + public const DUMP_TRAILING_COMMA = 8; + + public static $defaultOutput = 'php://output'; + + protected $line = ''; + protected $lineDumper; + protected $outputStream; + protected $decimalPoint; // This is locale dependent + protected $indentPad = ' '; + protected $flags; + + private $charset = ''; + + /** + * @param callable|resource|string|null $output A line dumper callable, an opened stream or an output path, defaults to static::$defaultOutput + * @param string|null $charset The default character encoding to use for non-UTF8 strings + * @param int $flags A bit field of static::DUMP_* constants to fine tune dumps representation + */ + public function __construct($output = null, string $charset = null, int $flags = 0) + { + $this->flags = $flags; + $this->setCharset($charset ?: \ini_get('php.output_encoding') ?: \ini_get('default_charset') ?: 'UTF-8'); + $this->decimalPoint = \PHP_VERSION_ID >= 80000 ? '.' : localeconv()['decimal_point']; + $this->setOutput($output ?: static::$defaultOutput); + if (!$output && \is_string(static::$defaultOutput)) { + static::$defaultOutput = $this->outputStream; + } + } + + /** + * Sets the output destination of the dumps. + * + * @param callable|resource|string $output A line dumper callable, an opened stream or an output path + * + * @return callable|resource|string The previous output destination + */ + public function setOutput($output) + { + $prev = $this->outputStream ?? $this->lineDumper; + + if (\is_callable($output)) { + $this->outputStream = null; + $this->lineDumper = $output; + } else { + if (\is_string($output)) { + $output = fopen($output, 'w'); + } + $this->outputStream = $output; + $this->lineDumper = [$this, 'echoLine']; + } + + return $prev; + } + + /** + * Sets the default character encoding to use for non-UTF8 strings. + * + * @return string The previous charset + */ + public function setCharset(string $charset) + { + $prev = $this->charset; + + $charset = strtoupper($charset); + $charset = null === $charset || 'UTF-8' === $charset || 'UTF8' === $charset ? 'CP1252' : $charset; + + $this->charset = $charset; + + return $prev; + } + + /** + * Sets the indentation pad string. + * + * @param string $pad A string that will be prepended to dumped lines, repeated by nesting level + * + * @return string The previous indent pad + */ + public function setIndentPad(string $pad) + { + $prev = $this->indentPad; + $this->indentPad = $pad; + + return $prev; + } + + /** + * Dumps a Data object. + * + * @param callable|resource|string|true|null $output A line dumper callable, an opened stream, an output path or true to return the dump + * + * @return string|null The dump as string when $output is true + */ + public function dump(Data $data, $output = null) + { + $this->decimalPoint = \PHP_VERSION_ID >= 80000 ? '.' : localeconv()['decimal_point']; + + if ($locale = $this->flags & (self::DUMP_COMMA_SEPARATOR | self::DUMP_TRAILING_COMMA) ? setlocale(\LC_NUMERIC, 0) : null) { + setlocale(\LC_NUMERIC, 'C'); + } + + if ($returnDump = true === $output) { + $output = fopen('php://memory', 'r+'); + } + if ($output) { + $prevOutput = $this->setOutput($output); + } + try { + $data->dump($this); + $this->dumpLine(-1); + + if ($returnDump) { + $result = stream_get_contents($output, -1, 0); + fclose($output); + + return $result; + } + } finally { + if ($output) { + $this->setOutput($prevOutput); + } + if ($locale) { + setlocale(\LC_NUMERIC, $locale); + } + } + + return null; + } + + /** + * Dumps the current line. + * + * @param int $depth The recursive depth in the dumped structure for the line being dumped, + * or -1 to signal the end-of-dump to the line dumper callable + */ + protected function dumpLine(int $depth) + { + ($this->lineDumper)($this->line, $depth, $this->indentPad); + $this->line = ''; + } + + /** + * Generic line dumper callback. + */ + protected function echoLine(string $line, int $depth, string $indentPad) + { + if (-1 !== $depth) { + fwrite($this->outputStream, str_repeat($indentPad, $depth).$line."\n"); + } + } + + /** + * Converts a non-UTF-8 string to UTF-8. + * + * @return string|null + */ + protected function utf8Encode(?string $s) + { + if (null === $s || preg_match('//u', $s)) { + return $s; + } + + if (!\function_exists('iconv')) { + throw new \RuntimeException('Unable to convert a non-UTF-8 string to UTF-8: required function iconv() does not exist. You should install ext-iconv or symfony/polyfill-iconv.'); + } + + if (false !== $c = @iconv($this->charset, 'UTF-8', $s)) { + return $c; + } + if ('CP1252' !== $this->charset && false !== $c = @iconv('CP1252', 'UTF-8', $s)) { + return $c; + } + + return iconv('CP850', 'UTF-8', $s); + } +} diff --git a/vendor/symfony/var-dumper/Dumper/CliDumper.php b/vendor/symfony/var-dumper/Dumper/CliDumper.php new file mode 100644 index 0000000000..94dc8ee973 --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/CliDumper.php @@ -0,0 +1,652 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper; + +use Symfony\Component\VarDumper\Cloner\Cursor; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * CliDumper dumps variables for command line output. + * + * @author Nicolas Grekas <p@tchwork.com> + */ +class CliDumper extends AbstractDumper +{ + public static $defaultColors; + public static $defaultOutput = 'php://stdout'; + + protected $colors; + protected $maxStringWidth = 0; + protected $styles = [ + // See http://en.wikipedia.org/wiki/ANSI_escape_code#graphics + 'default' => '0;38;5;208', + 'num' => '1;38;5;38', + 'const' => '1;38;5;208', + 'str' => '1;38;5;113', + 'note' => '38;5;38', + 'ref' => '38;5;247', + 'public' => '', + 'protected' => '', + 'private' => '', + 'meta' => '38;5;170', + 'key' => '38;5;113', + 'index' => '38;5;38', + ]; + + protected static $controlCharsRx = '/[\x00-\x1F\x7F]+/'; + protected static $controlCharsMap = [ + "\t" => '\t', + "\n" => '\n', + "\v" => '\v', + "\f" => '\f', + "\r" => '\r', + "\033" => '\e', + ]; + + protected $collapseNextHash = false; + protected $expandNextHash = false; + + private $displayOptions = [ + 'fileLinkFormat' => null, + ]; + + private $handlesHrefGracefully; + + /** + * {@inheritdoc} + */ + public function __construct($output = null, string $charset = null, int $flags = 0) + { + parent::__construct($output, $charset, $flags); + + if ('\\' === \DIRECTORY_SEPARATOR && !$this->isWindowsTrueColor()) { + // Use only the base 16 xterm colors when using ANSICON or standard Windows 10 CLI + $this->setStyles([ + 'default' => '31', + 'num' => '1;34', + 'const' => '1;31', + 'str' => '1;32', + 'note' => '34', + 'ref' => '1;30', + 'meta' => '35', + 'key' => '32', + 'index' => '34', + ]); + } + + $this->displayOptions['fileLinkFormat'] = \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format') ?: 'file://%f#L%l'; + } + + /** + * Enables/disables colored output. + */ + public function setColors(bool $colors) + { + $this->colors = $colors; + } + + /** + * Sets the maximum number of characters per line for dumped strings. + */ + public function setMaxStringWidth(int $maxStringWidth) + { + $this->maxStringWidth = $maxStringWidth; + } + + /** + * Configures styles. + * + * @param array $styles A map of style names to style definitions + */ + public function setStyles(array $styles) + { + $this->styles = $styles + $this->styles; + } + + /** + * Configures display options. + * + * @param array $displayOptions A map of display options to customize the behavior + */ + public function setDisplayOptions(array $displayOptions) + { + $this->displayOptions = $displayOptions + $this->displayOptions; + } + + /** + * {@inheritdoc} + */ + public function dumpScalar(Cursor $cursor, string $type, $value) + { + $this->dumpKey($cursor); + + $style = 'const'; + $attr = $cursor->attr; + + switch ($type) { + case 'default': + $style = 'default'; + break; + + case 'integer': + $style = 'num'; + + if (isset($this->styles['integer'])) { + $style = 'integer'; + } + + break; + + case 'double': + $style = 'num'; + + if (isset($this->styles['float'])) { + $style = 'float'; + } + + switch (true) { + case \INF === $value: $value = 'INF'; break; + case -\INF === $value: $value = '-INF'; break; + case is_nan($value): $value = 'NAN'; break; + default: + $value = (string) $value; + if (!str_contains($value, $this->decimalPoint)) { + $value .= $this->decimalPoint.'0'; + } + break; + } + break; + + case 'NULL': + $value = 'null'; + break; + + case 'boolean': + $value = $value ? 'true' : 'false'; + break; + + default: + $attr += ['value' => $this->utf8Encode($value)]; + $value = $this->utf8Encode($type); + break; + } + + $this->line .= $this->style($style, $value, $attr); + + $this->endValue($cursor); + } + + /** + * {@inheritdoc} + */ + public function dumpString(Cursor $cursor, string $str, bool $bin, int $cut) + { + $this->dumpKey($cursor); + $attr = $cursor->attr; + + if ($bin) { + $str = $this->utf8Encode($str); + } + if ('' === $str) { + $this->line .= '""'; + $this->endValue($cursor); + } else { + $attr += [ + 'length' => 0 <= $cut ? mb_strlen($str, 'UTF-8') + $cut : 0, + 'binary' => $bin, + ]; + $str = $bin && false !== strpos($str, "\0") ? [$str] : explode("\n", $str); + if (isset($str[1]) && !isset($str[2]) && !isset($str[1][0])) { + unset($str[1]); + $str[0] .= "\n"; + } + $m = \count($str) - 1; + $i = $lineCut = 0; + + if (self::DUMP_STRING_LENGTH & $this->flags) { + $this->line .= '('.$attr['length'].') '; + } + if ($bin) { + $this->line .= 'b'; + } + + if ($m) { + $this->line .= '"""'; + $this->dumpLine($cursor->depth); + } else { + $this->line .= '"'; + } + + foreach ($str as $str) { + if ($i < $m) { + $str .= "\n"; + } + if (0 < $this->maxStringWidth && $this->maxStringWidth < $len = mb_strlen($str, 'UTF-8')) { + $str = mb_substr($str, 0, $this->maxStringWidth, 'UTF-8'); + $lineCut = $len - $this->maxStringWidth; + } + if ($m && 0 < $cursor->depth) { + $this->line .= $this->indentPad; + } + if ('' !== $str) { + $this->line .= $this->style('str', $str, $attr); + } + if ($i++ == $m) { + if ($m) { + if ('' !== $str) { + $this->dumpLine($cursor->depth); + if (0 < $cursor->depth) { + $this->line .= $this->indentPad; + } + } + $this->line .= '"""'; + } else { + $this->line .= '"'; + } + if ($cut < 0) { + $this->line .= '…'; + $lineCut = 0; + } elseif ($cut) { + $lineCut += $cut; + } + } + if ($lineCut) { + $this->line .= '…'.$lineCut; + $lineCut = 0; + } + + if ($i > $m) { + $this->endValue($cursor); + } else { + $this->dumpLine($cursor->depth); + } + } + } + } + + /** + * {@inheritdoc} + */ + public function enterHash(Cursor $cursor, int $type, $class, bool $hasChild) + { + if (null === $this->colors) { + $this->colors = $this->supportsColors(); + } + + $this->dumpKey($cursor); + $attr = $cursor->attr; + + if ($this->collapseNextHash) { + $cursor->skipChildren = true; + $this->collapseNextHash = $hasChild = false; + } + + $class = $this->utf8Encode($class); + if (Cursor::HASH_OBJECT === $type) { + $prefix = $class && 'stdClass' !== $class ? $this->style('note', $class, $attr).(empty($attr['cut_hash']) ? ' {' : '') : '{'; + } elseif (Cursor::HASH_RESOURCE === $type) { + $prefix = $this->style('note', $class.' resource', $attr).($hasChild ? ' {' : ' '); + } else { + $prefix = $class && !(self::DUMP_LIGHT_ARRAY & $this->flags) ? $this->style('note', 'array:'.$class).' [' : '['; + } + + if (($cursor->softRefCount || 0 < $cursor->softRefHandle) && empty($attr['cut_hash'])) { + $prefix .= $this->style('ref', (Cursor::HASH_RESOURCE === $type ? '@' : '#').(0 < $cursor->softRefHandle ? $cursor->softRefHandle : $cursor->softRefTo), ['count' => $cursor->softRefCount]); + } elseif ($cursor->hardRefTo && !$cursor->refIndex && $class) { + $prefix .= $this->style('ref', '&'.$cursor->hardRefTo, ['count' => $cursor->hardRefCount]); + } elseif (!$hasChild && Cursor::HASH_RESOURCE === $type) { + $prefix = substr($prefix, 0, -1); + } + + $this->line .= $prefix; + + if ($hasChild) { + $this->dumpLine($cursor->depth); + } + } + + /** + * {@inheritdoc} + */ + public function leaveHash(Cursor $cursor, int $type, $class, bool $hasChild, int $cut) + { + if (empty($cursor->attr['cut_hash'])) { + $this->dumpEllipsis($cursor, $hasChild, $cut); + $this->line .= Cursor::HASH_OBJECT === $type ? '}' : (Cursor::HASH_RESOURCE !== $type ? ']' : ($hasChild ? '}' : '')); + } + + $this->endValue($cursor); + } + + /** + * Dumps an ellipsis for cut children. + * + * @param bool $hasChild When the dump of the hash has child item + * @param int $cut The number of items the hash has been cut by + */ + protected function dumpEllipsis(Cursor $cursor, bool $hasChild, int $cut) + { + if ($cut) { + $this->line .= ' …'; + if (0 < $cut) { + $this->line .= $cut; + } + if ($hasChild) { + $this->dumpLine($cursor->depth + 1); + } + } + } + + /** + * Dumps a key in a hash structure. + */ + protected function dumpKey(Cursor $cursor) + { + if (null !== $key = $cursor->hashKey) { + if ($cursor->hashKeyIsBinary) { + $key = $this->utf8Encode($key); + } + $attr = ['binary' => $cursor->hashKeyIsBinary]; + $bin = $cursor->hashKeyIsBinary ? 'b' : ''; + $style = 'key'; + switch ($cursor->hashType) { + default: + case Cursor::HASH_INDEXED: + if (self::DUMP_LIGHT_ARRAY & $this->flags) { + break; + } + $style = 'index'; + // no break + case Cursor::HASH_ASSOC: + if (\is_int($key)) { + $this->line .= $this->style($style, $key).' => '; + } else { + $this->line .= $bin.'"'.$this->style($style, $key).'" => '; + } + break; + + case Cursor::HASH_RESOURCE: + $key = "\0~\0".$key; + // no break + case Cursor::HASH_OBJECT: + if (!isset($key[0]) || "\0" !== $key[0]) { + $this->line .= '+'.$bin.$this->style('public', $key).': '; + } elseif (0 < strpos($key, "\0", 1)) { + $key = explode("\0", substr($key, 1), 2); + + switch ($key[0][0]) { + case '+': // User inserted keys + $attr['dynamic'] = true; + $this->line .= '+'.$bin.'"'.$this->style('public', $key[1], $attr).'": '; + break 2; + case '~': + $style = 'meta'; + if (isset($key[0][1])) { + parse_str(substr($key[0], 1), $attr); + $attr += ['binary' => $cursor->hashKeyIsBinary]; + } + break; + case '*': + $style = 'protected'; + $bin = '#'.$bin; + break; + default: + $attr['class'] = $key[0]; + $style = 'private'; + $bin = '-'.$bin; + break; + } + + if (isset($attr['collapse'])) { + if ($attr['collapse']) { + $this->collapseNextHash = true; + } else { + $this->expandNextHash = true; + } + } + + $this->line .= $bin.$this->style($style, $key[1], $attr).($attr['separator'] ?? ': '); + } else { + // This case should not happen + $this->line .= '-'.$bin.'"'.$this->style('private', $key, ['class' => '']).'": '; + } + break; + } + + if ($cursor->hardRefTo) { + $this->line .= $this->style('ref', '&'.($cursor->hardRefCount ? $cursor->hardRefTo : ''), ['count' => $cursor->hardRefCount]).' '; + } + } + } + + /** + * Decorates a value with some style. + * + * @param string $style The type of style being applied + * @param string $value The value being styled + * @param array $attr Optional context information + * + * @return string + */ + protected function style(string $style, string $value, array $attr = []) + { + if (null === $this->colors) { + $this->colors = $this->supportsColors(); + } + + if (null === $this->handlesHrefGracefully) { + $this->handlesHrefGracefully = 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR') + && (!getenv('KONSOLE_VERSION') || (int) getenv('KONSOLE_VERSION') > 201100); + } + + if (isset($attr['ellipsis'], $attr['ellipsis-type'])) { + $prefix = substr($value, 0, -$attr['ellipsis']); + if ('cli' === \PHP_SAPI && 'path' === $attr['ellipsis-type'] && isset($_SERVER[$pwd = '\\' === \DIRECTORY_SEPARATOR ? 'CD' : 'PWD']) && str_starts_with($prefix, $_SERVER[$pwd])) { + $prefix = '.'.substr($prefix, \strlen($_SERVER[$pwd])); + } + if (!empty($attr['ellipsis-tail'])) { + $prefix .= substr($value, -$attr['ellipsis'], $attr['ellipsis-tail']); + $value = substr($value, -$attr['ellipsis'] + $attr['ellipsis-tail']); + } else { + $value = substr($value, -$attr['ellipsis']); + } + + $value = $this->style('default', $prefix).$this->style($style, $value); + + goto href; + } + + $map = static::$controlCharsMap; + $startCchr = $this->colors ? "\033[m\033[{$this->styles['default']}m" : ''; + $endCchr = $this->colors ? "\033[m\033[{$this->styles[$style]}m" : ''; + $value = preg_replace_callback(static::$controlCharsRx, function ($c) use ($map, $startCchr, $endCchr) { + $s = $startCchr; + $c = $c[$i = 0]; + do { + $s .= $map[$c[$i]] ?? sprintf('\x%02X', \ord($c[$i])); + } while (isset($c[++$i])); + + return $s.$endCchr; + }, $value, -1, $cchrCount); + + if ($this->colors) { + if ($cchrCount && "\033" === $value[0]) { + $value = substr($value, \strlen($startCchr)); + } else { + $value = "\033[{$this->styles[$style]}m".$value; + } + if ($cchrCount && str_ends_with($value, $endCchr)) { + $value = substr($value, 0, -\strlen($endCchr)); + } else { + $value .= "\033[{$this->styles['default']}m"; + } + } + + href: + if ($this->colors && $this->handlesHrefGracefully) { + if (isset($attr['file']) && $href = $this->getSourceLink($attr['file'], $attr['line'] ?? 0)) { + if ('note' === $style) { + $value .= "\033]8;;{$href}\033\\^\033]8;;\033\\"; + } else { + $attr['href'] = $href; + } + } + if (isset($attr['href'])) { + $value = "\033]8;;{$attr['href']}\033\\{$value}\033]8;;\033\\"; + } + } elseif ($attr['if_links'] ?? false) { + return ''; + } + + return $value; + } + + /** + * @return bool + */ + protected function supportsColors() + { + if ($this->outputStream !== static::$defaultOutput) { + return $this->hasColorSupport($this->outputStream); + } + if (null !== static::$defaultColors) { + return static::$defaultColors; + } + if (isset($_SERVER['argv'][1])) { + $colors = $_SERVER['argv']; + $i = \count($colors); + while (--$i > 0) { + if (isset($colors[$i][5])) { + switch ($colors[$i]) { + case '--ansi': + case '--color': + case '--color=yes': + case '--color=force': + case '--color=always': + case '--colors=always': + return static::$defaultColors = true; + + case '--no-ansi': + case '--color=no': + case '--color=none': + case '--color=never': + case '--colors=never': + return static::$defaultColors = false; + } + } + } + } + + $h = stream_get_meta_data($this->outputStream) + ['wrapper_type' => null]; + $h = 'Output' === $h['stream_type'] && 'PHP' === $h['wrapper_type'] ? fopen('php://stdout', 'w') : $this->outputStream; + + return static::$defaultColors = $this->hasColorSupport($h); + } + + /** + * {@inheritdoc} + */ + protected function dumpLine(int $depth, bool $endOfValue = false) + { + if ($this->colors) { + $this->line = sprintf("\033[%sm%s\033[m", $this->styles['default'], $this->line); + } + parent::dumpLine($depth); + } + + protected function endValue(Cursor $cursor) + { + if (-1 === $cursor->hashType) { + return; + } + + if (Stub::ARRAY_INDEXED === $cursor->hashType || Stub::ARRAY_ASSOC === $cursor->hashType) { + if (self::DUMP_TRAILING_COMMA & $this->flags && 0 < $cursor->depth) { + $this->line .= ','; + } elseif (self::DUMP_COMMA_SEPARATOR & $this->flags && 1 < $cursor->hashLength - $cursor->hashIndex) { + $this->line .= ','; + } + } + + $this->dumpLine($cursor->depth, true); + } + + /** + * Returns true if the stream supports colorization. + * + * Reference: Composer\XdebugHandler\Process::supportsColor + * https://github.com/composer/xdebug-handler + * + * @param mixed $stream A CLI output stream + */ + private function hasColorSupport($stream): bool + { + if (!\is_resource($stream) || 'stream' !== get_resource_type($stream)) { + return false; + } + + // Follow https://no-color.org/ + if (isset($_SERVER['NO_COLOR']) || false !== getenv('NO_COLOR')) { + return false; + } + + if ('Hyper' === getenv('TERM_PROGRAM')) { + return true; + } + + if (\DIRECTORY_SEPARATOR === '\\') { + return (\function_exists('sapi_windows_vt100_support') + && @sapi_windows_vt100_support($stream)) + || false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM'); + } + + return stream_isatty($stream); + } + + /** + * Returns true if the Windows terminal supports true color. + * + * Note that this does not check an output stream, but relies on environment + * variables from known implementations, or a PHP and Windows version that + * supports true color. + */ + private function isWindowsTrueColor(): bool + { + $result = 183 <= getenv('ANSICON_VER') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM') + || 'Hyper' === getenv('TERM_PROGRAM'); + + if (!$result) { + $version = sprintf( + '%s.%s.%s', + PHP_WINDOWS_VERSION_MAJOR, + PHP_WINDOWS_VERSION_MINOR, + PHP_WINDOWS_VERSION_BUILD + ); + $result = $version >= '10.0.15063'; + } + + return $result; + } + + private function getSourceLink(string $file, int $line) + { + if ($fmt = $this->displayOptions['fileLinkFormat']) { + return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : ($fmt->format($file, $line) ?: 'file://'.$file.'#L'.$line); + } + + return false; + } +} diff --git a/vendor/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php b/vendor/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php new file mode 100644 index 0000000000..38f878971c --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php @@ -0,0 +1,32 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper\ContextProvider; + +/** + * Tries to provide context on CLI. + * + * @author Maxime Steinhausser <maxime.steinhausser@gmail.com> + */ +final class CliContextProvider implements ContextProviderInterface +{ + public function getContext(): ?array + { + if ('cli' !== \PHP_SAPI) { + return null; + } + + return [ + 'command_line' => $commandLine = implode(' ', $_SERVER['argv'] ?? []), + 'identifier' => hash('crc32b', $commandLine.$_SERVER['REQUEST_TIME_FLOAT']), + ]; + } +} diff --git a/vendor/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php b/vendor/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php new file mode 100644 index 0000000000..532aa0f96f --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php @@ -0,0 +1,22 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper\ContextProvider; + +/** + * Interface to provide contextual data about dump data clones sent to a server. + * + * @author Maxime Steinhausser <maxime.steinhausser@gmail.com> + */ +interface ContextProviderInterface +{ + public function getContext(): ?array; +} diff --git a/vendor/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php b/vendor/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php new file mode 100644 index 0000000000..3684a47535 --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php @@ -0,0 +1,51 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper\ContextProvider; + +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\VarDumper\Caster\ReflectionCaster; +use Symfony\Component\VarDumper\Cloner\VarCloner; + +/** + * Tries to provide context from a request. + * + * @author Maxime Steinhausser <maxime.steinhausser@gmail.com> + */ +final class RequestContextProvider implements ContextProviderInterface +{ + private $requestStack; + private $cloner; + + public function __construct(RequestStack $requestStack) + { + $this->requestStack = $requestStack; + $this->cloner = new VarCloner(); + $this->cloner->setMaxItems(0); + $this->cloner->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO); + } + + public function getContext(): ?array + { + if (null === $request = $this->requestStack->getCurrentRequest()) { + return null; + } + + $controller = $request->attributes->get('_controller'); + + return [ + 'uri' => $request->getUri(), + 'method' => $request->getMethod(), + 'controller' => $controller ? $this->cloner->cloneVar($controller) : $controller, + 'identifier' => spl_object_hash($request), + ]; + } +} diff --git a/vendor/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php b/vendor/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php new file mode 100644 index 0000000000..2e2c818161 --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php @@ -0,0 +1,126 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper\ContextProvider; + +use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; +use Symfony\Component\VarDumper\VarDumper; +use Twig\Template; + +/** + * Tries to provide context from sources (class name, file, line, code excerpt, ...). + * + * @author Nicolas Grekas <p@tchwork.com> + * @author Maxime Steinhausser <maxime.steinhausser@gmail.com> + */ +final class SourceContextProvider implements ContextProviderInterface +{ + private $limit; + private $charset; + private $projectDir; + private $fileLinkFormatter; + + public function __construct(string $charset = null, string $projectDir = null, FileLinkFormatter $fileLinkFormatter = null, int $limit = 9) + { + $this->charset = $charset; + $this->projectDir = $projectDir; + $this->fileLinkFormatter = $fileLinkFormatter; + $this->limit = $limit; + } + + public function getContext(): ?array + { + $trace = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, $this->limit); + + $file = $trace[1]['file']; + $line = $trace[1]['line']; + $name = false; + $fileExcerpt = false; + + for ($i = 2; $i < $this->limit; ++$i) { + if (isset($trace[$i]['class'], $trace[$i]['function']) + && 'dump' === $trace[$i]['function'] + && VarDumper::class === $trace[$i]['class'] + ) { + $file = $trace[$i]['file'] ?? $file; + $line = $trace[$i]['line'] ?? $line; + + while (++$i < $this->limit) { + if (isset($trace[$i]['function'], $trace[$i]['file']) && empty($trace[$i]['class']) && !str_starts_with($trace[$i]['function'], 'call_user_func')) { + $file = $trace[$i]['file']; + $line = $trace[$i]['line']; + + break; + } elseif (isset($trace[$i]['object']) && $trace[$i]['object'] instanceof Template) { + $template = $trace[$i]['object']; + $name = $template->getTemplateName(); + $src = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : false); + $info = $template->getDebugInfo(); + if (isset($info[$trace[$i - 1]['line']])) { + $line = $info[$trace[$i - 1]['line']]; + $file = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getPath() : null; + + if ($src) { + $src = explode("\n", $src); + $fileExcerpt = []; + + for ($i = max($line - 3, 1), $max = min($line + 3, \count($src)); $i <= $max; ++$i) { + $fileExcerpt[] = '<li'.($i === $line ? ' class="selected"' : '').'><code>'.$this->htmlEncode($src[$i - 1]).'</code></li>'; + } + + $fileExcerpt = '<ol start="'.max($line - 3, 1).'">'.implode("\n", $fileExcerpt).'</ol>'; + } + } + break; + } + } + break; + } + } + + if (false === $name) { + $name = str_replace('\\', '/', $file); + $name = substr($name, strrpos($name, '/') + 1); + } + + $context = ['name' => $name, 'file' => $file, 'line' => $line]; + $context['file_excerpt'] = $fileExcerpt; + + if (null !== $this->projectDir) { + $context['project_dir'] = $this->projectDir; + if (str_starts_with($file, $this->projectDir)) { + $context['file_relative'] = ltrim(substr($file, \strlen($this->projectDir)), \DIRECTORY_SEPARATOR); + } + } + + if ($this->fileLinkFormatter && $fileLink = $this->fileLinkFormatter->format($context['file'], $context['line'])) { + $context['file_link'] = $fileLink; + } + + return $context; + } + + private function htmlEncode(string $s): string + { + $html = ''; + + $dumper = new HtmlDumper(function ($line) use (&$html) { $html .= $line; }, $this->charset); + $dumper->setDumpHeader(''); + $dumper->setDumpBoundaries('', ''); + + $cloner = new VarCloner(); + $dumper->dump($cloner->cloneVar($s)); + + return substr(strip_tags($html), 1, -1); + } +} diff --git a/vendor/symfony/var-dumper/Dumper/ContextualizedDumper.php b/vendor/symfony/var-dumper/Dumper/ContextualizedDumper.php new file mode 100644 index 0000000000..76384176ef --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/ContextualizedDumper.php @@ -0,0 +1,43 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper; + +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface; + +/** + * @author Kévin Thérage <therage.kevin@gmail.com> + */ +class ContextualizedDumper implements DataDumperInterface +{ + private $wrappedDumper; + private $contextProviders; + + /** + * @param ContextProviderInterface[] $contextProviders + */ + public function __construct(DataDumperInterface $wrappedDumper, array $contextProviders) + { + $this->wrappedDumper = $wrappedDumper; + $this->contextProviders = $contextProviders; + } + + public function dump(Data $data) + { + $context = []; + foreach ($this->contextProviders as $contextProvider) { + $context[\get_class($contextProvider)] = $contextProvider->getContext(); + } + + $this->wrappedDumper->dump($data->withContext($context)); + } +} diff --git a/vendor/symfony/var-dumper/Dumper/DataDumperInterface.php b/vendor/symfony/var-dumper/Dumper/DataDumperInterface.php new file mode 100644 index 0000000000..b173bccf38 --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/DataDumperInterface.php @@ -0,0 +1,24 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper; + +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * DataDumperInterface for dumping Data objects. + * + * @author Nicolas Grekas <p@tchwork.com> + */ +interface DataDumperInterface +{ + public function dump(Data $data); +} diff --git a/vendor/symfony/var-dumper/Dumper/HtmlDumper.php b/vendor/symfony/var-dumper/Dumper/HtmlDumper.php new file mode 100644 index 0000000000..af4de96136 --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/HtmlDumper.php @@ -0,0 +1,986 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper; + +use Symfony\Component\VarDumper\Cloner\Cursor; +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * HtmlDumper dumps variables as HTML. + * + * @author Nicolas Grekas <p@tchwork.com> + */ +class HtmlDumper extends CliDumper +{ + public static $defaultOutput = 'php://output'; + + protected static $themes = [ + 'dark' => [ + 'default' => 'background-color:#18171B; color:#FF8400; line-height:1.2em; font:12px Menlo, Monaco, Consolas, monospace; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: break-all', + 'num' => 'font-weight:bold; color:#1299DA', + 'const' => 'font-weight:bold', + 'str' => 'font-weight:bold; color:#56DB3A', + 'note' => 'color:#1299DA', + 'ref' => 'color:#A0A0A0', + 'public' => 'color:#FFFFFF', + 'protected' => 'color:#FFFFFF', + 'private' => 'color:#FFFFFF', + 'meta' => 'color:#B729D9', + 'key' => 'color:#56DB3A', + 'index' => 'color:#1299DA', + 'ellipsis' => 'color:#FF8400', + 'ns' => 'user-select:none;', + ], + 'light' => [ + 'default' => 'background:none; color:#CC7832; line-height:1.2em; font:12px Menlo, Monaco, Consolas, monospace; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: break-all', + 'num' => 'font-weight:bold; color:#1299DA', + 'const' => 'font-weight:bold', + 'str' => 'font-weight:bold; color:#629755;', + 'note' => 'color:#6897BB', + 'ref' => 'color:#6E6E6E', + 'public' => 'color:#262626', + 'protected' => 'color:#262626', + 'private' => 'color:#262626', + 'meta' => 'color:#B729D9', + 'key' => 'color:#789339', + 'index' => 'color:#1299DA', + 'ellipsis' => 'color:#CC7832', + 'ns' => 'user-select:none;', + ], + ]; + + protected $dumpHeader; + protected $dumpPrefix = '<pre class=sf-dump id=%s data-indent-pad="%s">'; + protected $dumpSuffix = '</pre><script>Sfdump(%s)</script>'; + protected $dumpId = 'sf-dump'; + protected $colors = true; + protected $headerIsDumped = false; + protected $lastDepth = -1; + protected $styles; + + private $displayOptions = [ + 'maxDepth' => 1, + 'maxStringLength' => 160, + 'fileLinkFormat' => null, + ]; + private $extraDisplayOptions = []; + + /** + * {@inheritdoc} + */ + public function __construct($output = null, string $charset = null, int $flags = 0) + { + AbstractDumper::__construct($output, $charset, $flags); + $this->dumpId = 'sf-dump-'.mt_rand(); + $this->displayOptions['fileLinkFormat'] = \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); + $this->styles = static::$themes['dark'] ?? self::$themes['dark']; + } + + /** + * {@inheritdoc} + */ + public function setStyles(array $styles) + { + $this->headerIsDumped = false; + $this->styles = $styles + $this->styles; + } + + public function setTheme(string $themeName) + { + if (!isset(static::$themes[$themeName])) { + throw new \InvalidArgumentException(sprintf('Theme "%s" does not exist in class "%s".', $themeName, static::class)); + } + + $this->setStyles(static::$themes[$themeName]); + } + + /** + * Configures display options. + * + * @param array $displayOptions A map of display options to customize the behavior + */ + public function setDisplayOptions(array $displayOptions) + { + $this->headerIsDumped = false; + $this->displayOptions = $displayOptions + $this->displayOptions; + } + + /** + * Sets an HTML header that will be dumped once in the output stream. + */ + public function setDumpHeader(?string $header) + { + $this->dumpHeader = $header; + } + + /** + * Sets an HTML prefix and suffix that will encapse every single dump. + */ + public function setDumpBoundaries(string $prefix, string $suffix) + { + $this->dumpPrefix = $prefix; + $this->dumpSuffix = $suffix; + } + + /** + * {@inheritdoc} + */ + public function dump(Data $data, $output = null, array $extraDisplayOptions = []) + { + $this->extraDisplayOptions = $extraDisplayOptions; + $result = parent::dump($data, $output); + $this->dumpId = 'sf-dump-'.mt_rand(); + + return $result; + } + + /** + * Dumps the HTML header. + */ + protected function getDumpHeader() + { + $this->headerIsDumped = $this->outputStream ?? $this->lineDumper; + + if (null !== $this->dumpHeader) { + return $this->dumpHeader; + } + + $line = str_replace('{$options}', json_encode($this->displayOptions, \JSON_FORCE_OBJECT), <<<'EOHTML' +<script> +Sfdump = window.Sfdump || (function (doc) { + +var refStyle = doc.createElement('style'), + rxEsc = /([.*+?^${}()|\[\]\/\\])/g, + idRx = /\bsf-dump-\d+-ref[012]\w+\b/, + keyHint = 0 <= navigator.platform.toUpperCase().indexOf('MAC') ? 'Cmd' : 'Ctrl', + addEventListener = function (e, n, cb) { + e.addEventListener(n, cb, false); + }; + +refStyle.innerHTML = 'pre.sf-dump .sf-dump-compact, .sf-dump-str-collapse .sf-dump-str-collapse, .sf-dump-str-expand .sf-dump-str-expand { display: none; }'; +(doc.documentElement.firstElementChild || doc.documentElement.children[0]).appendChild(refStyle); +refStyle = doc.createElement('style'); +(doc.documentElement.firstElementChild || doc.documentElement.children[0]).appendChild(refStyle); + +if (!doc.addEventListener) { + addEventListener = function (element, eventName, callback) { + element.attachEvent('on' + eventName, function (e) { + e.preventDefault = function () {e.returnValue = false;}; + e.target = e.srcElement; + callback(e); + }); + }; +} + +function toggle(a, recursive) { + var s = a.nextSibling || {}, oldClass = s.className, arrow, newClass; + + if (/\bsf-dump-compact\b/.test(oldClass)) { + arrow = '▼'; + newClass = 'sf-dump-expanded'; + } else if (/\bsf-dump-expanded\b/.test(oldClass)) { + arrow = '▶'; + newClass = 'sf-dump-compact'; + } else { + return false; + } + + if (doc.createEvent && s.dispatchEvent) { + var event = doc.createEvent('Event'); + event.initEvent('sf-dump-expanded' === newClass ? 'sfbeforedumpexpand' : 'sfbeforedumpcollapse', true, false); + + s.dispatchEvent(event); + } + + a.lastChild.innerHTML = arrow; + s.className = s.className.replace(/\bsf-dump-(compact|expanded)\b/, newClass); + + if (recursive) { + try { + a = s.querySelectorAll('.'+oldClass); + for (s = 0; s < a.length; ++s) { + if (-1 == a[s].className.indexOf(newClass)) { + a[s].className = newClass; + a[s].previousSibling.lastChild.innerHTML = arrow; + } + } + } catch (e) { + } + } + + return true; +}; + +function collapse(a, recursive) { + var s = a.nextSibling || {}, oldClass = s.className; + + if (/\bsf-dump-expanded\b/.test(oldClass)) { + toggle(a, recursive); + + return true; + } + + return false; +}; + +function expand(a, recursive) { + var s = a.nextSibling || {}, oldClass = s.className; + + if (/\bsf-dump-compact\b/.test(oldClass)) { + toggle(a, recursive); + + return true; + } + + return false; +}; + +function collapseAll(root) { + var a = root.querySelector('a.sf-dump-toggle'); + if (a) { + collapse(a, true); + expand(a); + + return true; + } + + return false; +} + +function reveal(node) { + var previous, parents = []; + + while ((node = node.parentNode || {}) && (previous = node.previousSibling) && 'A' === previous.tagName) { + parents.push(previous); + } + + if (0 !== parents.length) { + parents.forEach(function (parent) { + expand(parent); + }); + + return true; + } + + return false; +} + +function highlight(root, activeNode, nodes) { + resetHighlightedNodes(root); + + Array.from(nodes||[]).forEach(function (node) { + if (!/\bsf-dump-highlight\b/.test(node.className)) { + node.className = node.className + ' sf-dump-highlight'; + } + }); + + if (!/\bsf-dump-highlight-active\b/.test(activeNode.className)) { + activeNode.className = activeNode.className + ' sf-dump-highlight-active'; + } +} + +function resetHighlightedNodes(root) { + Array.from(root.querySelectorAll('.sf-dump-str, .sf-dump-key, .sf-dump-public, .sf-dump-protected, .sf-dump-private')).forEach(function (strNode) { + strNode.className = strNode.className.replace(/\bsf-dump-highlight\b/, ''); + strNode.className = strNode.className.replace(/\bsf-dump-highlight-active\b/, ''); + }); +} + +return function (root, x) { + root = doc.getElementById(root); + + var indentRx = new RegExp('^('+(root.getAttribute('data-indent-pad') || ' ').replace(rxEsc, '\\$1')+')+', 'm'), + options = {$options}, + elt = root.getElementsByTagName('A'), + len = elt.length, + i = 0, s, h, + t = []; + + while (i < len) t.push(elt[i++]); + + for (i in x) { + options[i] = x[i]; + } + + function a(e, f) { + addEventListener(root, e, function (e, n) { + if ('A' == e.target.tagName) { + f(e.target, e); + } else if ('A' == e.target.parentNode.tagName) { + f(e.target.parentNode, e); + } else { + n = /\bsf-dump-ellipsis\b/.test(e.target.className) ? e.target.parentNode : e.target; + + if ((n = n.nextElementSibling) && 'A' == n.tagName) { + if (!/\bsf-dump-toggle\b/.test(n.className)) { + n = n.nextElementSibling || n; + } + + f(n, e, true); + } + } + }); + }; + function isCtrlKey(e) { + return e.ctrlKey || e.metaKey; + } + function xpathString(str) { + var parts = str.match(/[^'"]+|['"]/g).map(function (part) { + if ("'" == part) { + return '"\'"'; + } + if ('"' == part) { + return "'\"'"; + } + + return "'" + part + "'"; + }); + + return "concat(" + parts.join(",") + ", '')"; + } + function xpathHasClass(className) { + return "contains(concat(' ', normalize-space(@class), ' '), ' " + className +" ')"; + } + addEventListener(root, 'mouseover', function (e) { + if ('' != refStyle.innerHTML) { + refStyle.innerHTML = ''; + } + }); + a('mouseover', function (a, e, c) { + if (c) { + e.target.style.cursor = "pointer"; + } else if (a = idRx.exec(a.className)) { + try { + refStyle.innerHTML = 'pre.sf-dump .'+a[0]+'{background-color: #B729D9; color: #FFF !important; border-radius: 2px}'; + } catch (e) { + } + } + }); + a('click', function (a, e, c) { + if (/\bsf-dump-toggle\b/.test(a.className)) { + e.preventDefault(); + if (!toggle(a, isCtrlKey(e))) { + var r = doc.getElementById(a.getAttribute('href').slice(1)), + s = r.previousSibling, + f = r.parentNode, + t = a.parentNode; + t.replaceChild(r, a); + f.replaceChild(a, s); + t.insertBefore(s, r); + f = f.firstChild.nodeValue.match(indentRx); + t = t.firstChild.nodeValue.match(indentRx); + if (f && t && f[0] !== t[0]) { + r.innerHTML = r.innerHTML.replace(new RegExp('^'+f[0].replace(rxEsc, '\\$1'), 'mg'), t[0]); + } + if (/\bsf-dump-compact\b/.test(r.className)) { + toggle(s, isCtrlKey(e)); + } + } + + if (c) { + } else if (doc.getSelection) { + try { + doc.getSelection().removeAllRanges(); + } catch (e) { + doc.getSelection().empty(); + } + } else { + doc.selection.empty(); + } + } else if (/\bsf-dump-str-toggle\b/.test(a.className)) { + e.preventDefault(); + e = a.parentNode.parentNode; + e.className = e.className.replace(/\bsf-dump-str-(expand|collapse)\b/, a.parentNode.className); + } + }); + + elt = root.getElementsByTagName('SAMP'); + len = elt.length; + i = 0; + + while (i < len) t.push(elt[i++]); + len = t.length; + + for (i = 0; i < len; ++i) { + elt = t[i]; + if ('SAMP' == elt.tagName) { + a = elt.previousSibling || {}; + if ('A' != a.tagName) { + a = doc.createElement('A'); + a.className = 'sf-dump-ref'; + elt.parentNode.insertBefore(a, elt); + } else { + a.innerHTML += ' '; + } + a.title = (a.title ? a.title+'\n[' : '[')+keyHint+'+click] Expand all children'; + a.innerHTML += elt.className == 'sf-dump-compact' ? '<span>▶</span>' : '<span>▼</span>'; + a.className += ' sf-dump-toggle'; + + x = 1; + if ('sf-dump' != elt.parentNode.className) { + x += elt.parentNode.getAttribute('data-depth')/1; + } + } else if (/\bsf-dump-ref\b/.test(elt.className) && (a = elt.getAttribute('href'))) { + a = a.slice(1); + elt.className += ' '+a; + + if (/[\[{]$/.test(elt.previousSibling.nodeValue)) { + a = a != elt.nextSibling.id && doc.getElementById(a); + try { + s = a.nextSibling; + elt.appendChild(a); + s.parentNode.insertBefore(a, s); + if (/^[@#]/.test(elt.innerHTML)) { + elt.innerHTML += ' <span>▶</span>'; + } else { + elt.innerHTML = '<span>▶</span>'; + elt.className = 'sf-dump-ref'; + } + elt.className += ' sf-dump-toggle'; + } catch (e) { + if ('&' == elt.innerHTML.charAt(0)) { + elt.innerHTML = '…'; + elt.className = 'sf-dump-ref'; + } + } + } + } + } + + if (doc.evaluate && Array.from && root.children.length > 1) { + root.setAttribute('tabindex', 0); + + SearchState = function () { + this.nodes = []; + this.idx = 0; + }; + SearchState.prototype = { + next: function () { + if (this.isEmpty()) { + return this.current(); + } + this.idx = this.idx < (this.nodes.length - 1) ? this.idx + 1 : 0; + + return this.current(); + }, + previous: function () { + if (this.isEmpty()) { + return this.current(); + } + this.idx = this.idx > 0 ? this.idx - 1 : (this.nodes.length - 1); + + return this.current(); + }, + isEmpty: function () { + return 0 === this.count(); + }, + current: function () { + if (this.isEmpty()) { + return null; + } + return this.nodes[this.idx]; + }, + reset: function () { + this.nodes = []; + this.idx = 0; + }, + count: function () { + return this.nodes.length; + }, + }; + + function showCurrent(state) + { + var currentNode = state.current(), currentRect, searchRect; + if (currentNode) { + reveal(currentNode); + highlight(root, currentNode, state.nodes); + if ('scrollIntoView' in currentNode) { + currentNode.scrollIntoView(true); + currentRect = currentNode.getBoundingClientRect(); + searchRect = search.getBoundingClientRect(); + if (currentRect.top < (searchRect.top + searchRect.height)) { + window.scrollBy(0, -(searchRect.top + searchRect.height + 5)); + } + } + } + counter.textContent = (state.isEmpty() ? 0 : state.idx + 1) + ' of ' + state.count(); + } + + var search = doc.createElement('div'); + search.className = 'sf-dump-search-wrapper sf-dump-search-hidden'; + search.innerHTML = ' + <input type="text" class="sf-dump-search-input"> + <span class="sf-dump-search-count">0 of 0<\/span> + <button type="button" class="sf-dump-search-input-previous" tabindex="-1"> + <svg viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1683 1331l-166 165q-19 19-45 19t-45-19L896 965l-531 531q-19 19-45 19t-45-19l-166-165q-19-19-19-45.5t19-45.5l742-741q19-19 45-19t45 19l742 741q19 19 19 45.5t-19 45.5z"\/><\/svg> + <\/button> + <button type="button" class="sf-dump-search-input-next" tabindex="-1"> + <svg viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1683 808l-742 741q-19 19-45 19t-45-19L109 808q-19-19-19-45.5t19-45.5l166-165q19-19 45-19t45 19l531 531 531-531q19-19 45-19t45 19l166 165q19 19 19 45.5t-19 45.5z"\/><\/svg> + <\/button> + '; + root.insertBefore(search, root.firstChild); + + var state = new SearchState(); + var searchInput = search.querySelector('.sf-dump-search-input'); + var counter = search.querySelector('.sf-dump-search-count'); + var searchInputTimer = 0; + var previousSearchQuery = ''; + + addEventListener(searchInput, 'keyup', function (e) { + var searchQuery = e.target.value; + /* Don't perform anything if the pressed key didn't change the query */ + if (searchQuery === previousSearchQuery) { + return; + } + previousSearchQuery = searchQuery; + clearTimeout(searchInputTimer); + searchInputTimer = setTimeout(function () { + state.reset(); + collapseAll(root); + resetHighlightedNodes(root); + if ('' === searchQuery) { + counter.textContent = '0 of 0'; + + return; + } + + var classMatches = [ + "sf-dump-str", + "sf-dump-key", + "sf-dump-public", + "sf-dump-protected", + "sf-dump-private", + ].map(xpathHasClass).join(' or '); + + var xpathResult = doc.evaluate('.//span[' + classMatches + '][contains(translate(child::text(), ' + xpathString(searchQuery.toUpperCase()) + ', ' + xpathString(searchQuery.toLowerCase()) + '), ' + xpathString(searchQuery.toLowerCase()) + ')]', root, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null); + + while (node = xpathResult.iterateNext()) state.nodes.push(node); + + showCurrent(state); + }, 400); + }); + + Array.from(search.querySelectorAll('.sf-dump-search-input-next, .sf-dump-search-input-previous')).forEach(function (btn) { + addEventListener(btn, 'click', function (e) { + e.preventDefault(); + -1 !== e.target.className.indexOf('next') ? state.next() : state.previous(); + searchInput.focus(); + collapseAll(root); + showCurrent(state); + }) + }); + + addEventListener(root, 'keydown', function (e) { + var isSearchActive = !/\bsf-dump-search-hidden\b/.test(search.className); + if ((114 === e.keyCode && !isSearchActive) || (isCtrlKey(e) && 70 === e.keyCode)) { + /* F3 or CMD/CTRL + F */ + if (70 === e.keyCode && document.activeElement === searchInput) { + /* + * If CMD/CTRL + F is hit while having focus on search input, + * the user probably meant to trigger browser search instead. + * Let the browser execute its behavior: + */ + return; + } + + e.preventDefault(); + search.className = search.className.replace(/\bsf-dump-search-hidden\b/, ''); + searchInput.focus(); + } else if (isSearchActive) { + if (27 === e.keyCode) { + /* ESC key */ + search.className += ' sf-dump-search-hidden'; + e.preventDefault(); + resetHighlightedNodes(root); + searchInput.value = ''; + } else if ( + (isCtrlKey(e) && 71 === e.keyCode) /* CMD/CTRL + G */ + || 13 === e.keyCode /* Enter */ + || 114 === e.keyCode /* F3 */ + ) { + e.preventDefault(); + e.shiftKey ? state.previous() : state.next(); + collapseAll(root); + showCurrent(state); + } + } + }); + } + + if (0 >= options.maxStringLength) { + return; + } + try { + elt = root.querySelectorAll('.sf-dump-str'); + len = elt.length; + i = 0; + t = []; + + while (i < len) t.push(elt[i++]); + len = t.length; + + for (i = 0; i < len; ++i) { + elt = t[i]; + s = elt.innerText || elt.textContent; + x = s.length - options.maxStringLength; + if (0 < x) { + h = elt.innerHTML; + elt[elt.innerText ? 'innerText' : 'textContent'] = s.substring(0, options.maxStringLength); + elt.className += ' sf-dump-str-collapse'; + elt.innerHTML = '<span class=sf-dump-str-collapse>'+h+'<a class="sf-dump-ref sf-dump-str-toggle" title="Collapse"> ◀</a></span>'+ + '<span class=sf-dump-str-expand>'+elt.innerHTML+'<a class="sf-dump-ref sf-dump-str-toggle" title="'+x+' remaining characters"> ▶</a></span>'; + } + } + } catch (e) { + } +}; + +})(document); +</script><style> +pre.sf-dump { + display: block; + white-space: pre; + padding: 5px; + overflow: initial !important; +} +pre.sf-dump:after { + content: ""; + visibility: hidden; + display: block; + height: 0; + clear: both; +} +pre.sf-dump span { + display: inline; +} +pre.sf-dump a { + text-decoration: none; + cursor: pointer; + border: 0; + outline: none; + color: inherit; +} +pre.sf-dump img { + max-width: 50em; + max-height: 50em; + margin: .5em 0 0 0; + padding: 0; + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAAHUlEQVQY02O8zAABilCaiQEN0EeA8QuUcX9g3QEAAjcC5piyhyEAAAAASUVORK5CYII=) #D3D3D3; +} +pre.sf-dump .sf-dump-ellipsis { + display: inline-block; + overflow: visible; + text-overflow: ellipsis; + max-width: 5em; + white-space: nowrap; + overflow: hidden; + vertical-align: top; +} +pre.sf-dump .sf-dump-ellipsis+.sf-dump-ellipsis { + max-width: none; +} +pre.sf-dump code { + display:inline; + padding:0; + background:none; +} +.sf-dump-public.sf-dump-highlight, +.sf-dump-protected.sf-dump-highlight, +.sf-dump-private.sf-dump-highlight, +.sf-dump-str.sf-dump-highlight, +.sf-dump-key.sf-dump-highlight { + background: rgba(111, 172, 204, 0.3); + border: 1px solid #7DA0B1; + border-radius: 3px; +} +.sf-dump-public.sf-dump-highlight-active, +.sf-dump-protected.sf-dump-highlight-active, +.sf-dump-private.sf-dump-highlight-active, +.sf-dump-str.sf-dump-highlight-active, +.sf-dump-key.sf-dump-highlight-active { + background: rgba(253, 175, 0, 0.4); + border: 1px solid #ffa500; + border-radius: 3px; +} +pre.sf-dump .sf-dump-search-hidden { + display: none !important; +} +pre.sf-dump .sf-dump-search-wrapper { + font-size: 0; + white-space: nowrap; + margin-bottom: 5px; + display: flex; + position: -webkit-sticky; + position: sticky; + top: 5px; +} +pre.sf-dump .sf-dump-search-wrapper > * { + vertical-align: top; + box-sizing: border-box; + height: 21px; + font-weight: normal; + border-radius: 0; + background: #FFF; + color: #757575; + border: 1px solid #BBB; +} +pre.sf-dump .sf-dump-search-wrapper > input.sf-dump-search-input { + padding: 3px; + height: 21px; + font-size: 12px; + border-right: none; + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; + color: #000; + min-width: 15px; + width: 100%; +} +pre.sf-dump .sf-dump-search-wrapper > .sf-dump-search-input-next, +pre.sf-dump .sf-dump-search-wrapper > .sf-dump-search-input-previous { + background: #F2F2F2; + outline: none; + border-left: none; + font-size: 0; + line-height: 0; +} +pre.sf-dump .sf-dump-search-wrapper > .sf-dump-search-input-next { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} +pre.sf-dump .sf-dump-search-wrapper > .sf-dump-search-input-next > svg, +pre.sf-dump .sf-dump-search-wrapper > .sf-dump-search-input-previous > svg { + pointer-events: none; + width: 12px; + height: 12px; +} +pre.sf-dump .sf-dump-search-wrapper > .sf-dump-search-count { + display: inline-block; + padding: 0 5px; + margin: 0; + border-left: none; + line-height: 21px; + font-size: 12px; +} +EOHTML + ); + + foreach ($this->styles as $class => $style) { + $line .= 'pre.sf-dump'.('default' === $class ? ', pre.sf-dump' : '').' .sf-dump-'.$class.'{'.$style.'}'; + } + $line .= 'pre.sf-dump .sf-dump-ellipsis-note{'.$this->styles['note'].'}'; + + return $this->dumpHeader = preg_replace('/\s+/', ' ', $line).'</style>'.$this->dumpHeader; + } + + /** + * {@inheritdoc} + */ + public function dumpString(Cursor $cursor, string $str, bool $bin, int $cut) + { + if ('' === $str && isset($cursor->attr['img-data'], $cursor->attr['content-type'])) { + $this->dumpKey($cursor); + $this->line .= $this->style('default', $cursor->attr['img-size'] ?? '', []); + $this->line .= $cursor->depth >= $this->displayOptions['maxDepth'] ? ' <samp class=sf-dump-compact>' : ' <samp class=sf-dump-expanded>'; + $this->endValue($cursor); + $this->line .= $this->indentPad; + $this->line .= sprintf('<img src="data:%s;base64,%s" /></samp>', $cursor->attr['content-type'], base64_encode($cursor->attr['img-data'])); + $this->endValue($cursor); + } else { + parent::dumpString($cursor, $str, $bin, $cut); + } + } + + /** + * {@inheritdoc} + */ + public function enterHash(Cursor $cursor, int $type, $class, bool $hasChild) + { + if (Cursor::HASH_OBJECT === $type) { + $cursor->attr['depth'] = $cursor->depth; + } + parent::enterHash($cursor, $type, $class, false); + + if ($cursor->skipChildren || $cursor->depth >= $this->displayOptions['maxDepth']) { + $cursor->skipChildren = false; + $eol = ' class=sf-dump-compact>'; + } else { + $this->expandNextHash = false; + $eol = ' class=sf-dump-expanded>'; + } + + if ($hasChild) { + $this->line .= '<samp data-depth='.($cursor->depth + 1); + if ($cursor->refIndex) { + $r = Cursor::HASH_OBJECT !== $type ? 1 - (Cursor::HASH_RESOURCE !== $type) : 2; + $r .= $r && 0 < $cursor->softRefHandle ? $cursor->softRefHandle : $cursor->refIndex; + + $this->line .= sprintf(' id=%s-ref%s', $this->dumpId, $r); + } + $this->line .= $eol; + $this->dumpLine($cursor->depth); + } + } + + /** + * {@inheritdoc} + */ + public function leaveHash(Cursor $cursor, int $type, $class, bool $hasChild, int $cut) + { + $this->dumpEllipsis($cursor, $hasChild, $cut); + if ($hasChild) { + $this->line .= '</samp>'; + } + parent::leaveHash($cursor, $type, $class, $hasChild, 0); + } + + /** + * {@inheritdoc} + */ + protected function style(string $style, string $value, array $attr = []) + { + if ('' === $value) { + return ''; + } + + $v = esc($value); + + if ('ref' === $style) { + if (empty($attr['count'])) { + return sprintf('<a class=sf-dump-ref>%s</a>', $v); + } + $r = ('#' !== $v[0] ? 1 - ('@' !== $v[0]) : 2).substr($value, 1); + + return sprintf('<a class=sf-dump-ref href=#%s-ref%s title="%d occurrences">%s</a>', $this->dumpId, $r, 1 + $attr['count'], $v); + } + + if ('const' === $style && isset($attr['value'])) { + $style .= sprintf(' title="%s"', esc(\is_scalar($attr['value']) ? $attr['value'] : json_encode($attr['value']))); + } elseif ('public' === $style) { + $style .= sprintf(' title="%s"', empty($attr['dynamic']) ? 'Public property' : 'Runtime added dynamic property'); + } elseif ('str' === $style && 1 < $attr['length']) { + $style .= sprintf(' title="%d%s characters"', $attr['length'], $attr['binary'] ? ' binary or non-UTF-8' : ''); + } elseif ('note' === $style && 0 < ($attr['depth'] ?? 0) && false !== $c = strrpos($value, '\\')) { + $style .= ' title=""'; + $attr += [ + 'ellipsis' => \strlen($value) - $c, + 'ellipsis-type' => 'note', + 'ellipsis-tail' => 1, + ]; + } elseif ('protected' === $style) { + $style .= ' title="Protected property"'; + } elseif ('meta' === $style && isset($attr['title'])) { + $style .= sprintf(' title="%s"', esc($this->utf8Encode($attr['title']))); + } elseif ('private' === $style) { + $style .= sprintf(' title="Private property defined in class: `%s`"', esc($this->utf8Encode($attr['class']))); + } + $map = static::$controlCharsMap; + + if (isset($attr['ellipsis'])) { + $class = 'sf-dump-ellipsis'; + if (isset($attr['ellipsis-type'])) { + $class = sprintf('"%s sf-dump-ellipsis-%s"', $class, $attr['ellipsis-type']); + } + $label = esc(substr($value, -$attr['ellipsis'])); + $style = str_replace(' title="', " title=\"$v\n", $style); + $v = sprintf('<span class=%s>%s</span>', $class, substr($v, 0, -\strlen($label))); + + if (!empty($attr['ellipsis-tail'])) { + $tail = \strlen(esc(substr($value, -$attr['ellipsis'], $attr['ellipsis-tail']))); + $v .= sprintf('<span class=%s>%s</span>%s', $class, substr($label, 0, $tail), substr($label, $tail)); + } else { + $v .= $label; + } + } + + $v = "<span class=sf-dump-{$style}>".preg_replace_callback(static::$controlCharsRx, function ($c) use ($map) { + $s = $b = '<span class="sf-dump-default'; + $c = $c[$i = 0]; + if ($ns = "\r" === $c[$i] || "\n" === $c[$i]) { + $s .= ' sf-dump-ns'; + } + $s .= '">'; + do { + if (("\r" === $c[$i] || "\n" === $c[$i]) !== $ns) { + $s .= '</span>'.$b; + if ($ns = !$ns) { + $s .= ' sf-dump-ns'; + } + $s .= '">'; + } + + $s .= $map[$c[$i]] ?? sprintf('\x%02X', \ord($c[$i])); + } while (isset($c[++$i])); + + return $s.'</span>'; + }, $v).'</span>'; + + if (isset($attr['file']) && $href = $this->getSourceLink($attr['file'], $attr['line'] ?? 0)) { + $attr['href'] = $href; + } + if (isset($attr['href'])) { + $target = isset($attr['file']) ? '' : ' target="_blank"'; + $v = sprintf('<a href="%s"%s rel="noopener noreferrer">%s</a>', esc($this->utf8Encode($attr['href'])), $target, $v); + } + if (isset($attr['lang'])) { + $v = sprintf('<code class="%s">%s</code>', esc($attr['lang']), $v); + } + + return $v; + } + + /** + * {@inheritdoc} + */ + protected function dumpLine(int $depth, bool $endOfValue = false) + { + if (-1 === $this->lastDepth) { + $this->line = sprintf($this->dumpPrefix, $this->dumpId, $this->indentPad).$this->line; + } + if ($this->headerIsDumped !== ($this->outputStream ?? $this->lineDumper)) { + $this->line = $this->getDumpHeader().$this->line; + } + + if (-1 === $depth) { + $args = ['"'.$this->dumpId.'"']; + if ($this->extraDisplayOptions) { + $args[] = json_encode($this->extraDisplayOptions, \JSON_FORCE_OBJECT); + } + // Replace is for BC + $this->line .= sprintf(str_replace('"%s"', '%s', $this->dumpSuffix), implode(', ', $args)); + } + $this->lastDepth = $depth; + + $this->line = mb_encode_numericentity($this->line, [0x80, 0x10FFFF, 0, 0x1FFFFF], 'UTF-8'); + + if (-1 === $depth) { + AbstractDumper::dumpLine(0); + } + AbstractDumper::dumpLine($depth); + } + + private function getSourceLink(string $file, int $line) + { + $options = $this->extraDisplayOptions + $this->displayOptions; + + if ($fmt = $options['fileLinkFormat']) { + return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : $fmt->format($file, $line); + } + + return false; + } +} + +function esc(string $str) +{ + return htmlspecialchars($str, \ENT_QUOTES, 'UTF-8'); +} diff --git a/vendor/symfony/var-dumper/Dumper/ServerDumper.php b/vendor/symfony/var-dumper/Dumper/ServerDumper.php new file mode 100644 index 0000000000..94795bf6d6 --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/ServerDumper.php @@ -0,0 +1,53 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper; + +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface; +use Symfony\Component\VarDumper\Server\Connection; + +/** + * ServerDumper forwards serialized Data clones to a server. + * + * @author Maxime Steinhausser <maxime.steinhausser@gmail.com> + */ +class ServerDumper implements DataDumperInterface +{ + private $connection; + private $wrappedDumper; + + /** + * @param string $host The server host + * @param DataDumperInterface|null $wrappedDumper A wrapped instance used whenever we failed contacting the server + * @param ContextProviderInterface[] $contextProviders Context providers indexed by context name + */ + public function __construct(string $host, DataDumperInterface $wrappedDumper = null, array $contextProviders = []) + { + $this->connection = new Connection($host, $contextProviders); + $this->wrappedDumper = $wrappedDumper; + } + + public function getContextProviders(): array + { + return $this->connection->getContextProviders(); + } + + /** + * {@inheritdoc} + */ + public function dump(Data $data) + { + if (!$this->connection->write($data) && $this->wrappedDumper) { + $this->wrappedDumper->dump($data); + } + } +} diff --git a/vendor/symfony/var-dumper/Exception/ThrowingCasterException.php b/vendor/symfony/var-dumper/Exception/ThrowingCasterException.php new file mode 100644 index 0000000000..122f0d358a --- /dev/null +++ b/vendor/symfony/var-dumper/Exception/ThrowingCasterException.php @@ -0,0 +1,26 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Exception; + +/** + * @author Nicolas Grekas <p@tchwork.com> + */ +class ThrowingCasterException extends \Exception +{ + /** + * @param \Throwable $prev The exception thrown from the caster + */ + public function __construct(\Throwable $prev) + { + parent::__construct('Unexpected '.\get_class($prev).' thrown from a caster: '.$prev->getMessage(), 0, $prev); + } +} diff --git a/vendor/symfony/var-dumper/LICENSE b/vendor/symfony/var-dumper/LICENSE new file mode 100644 index 0000000000..a843ec124e --- /dev/null +++ b/vendor/symfony/var-dumper/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014-2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/var-dumper/README.md b/vendor/symfony/var-dumper/README.md new file mode 100644 index 0000000000..a0da8c9ab3 --- /dev/null +++ b/vendor/symfony/var-dumper/README.md @@ -0,0 +1,15 @@ +VarDumper Component +=================== + +The VarDumper component provides mechanisms for walking through any arbitrary +PHP variable. It provides a better `dump()` function that you can use instead +of `var_dump()`. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/var_dumper/introduction.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/var-dumper/Resources/bin/var-dump-server b/vendor/symfony/var-dumper/Resources/bin/var-dump-server new file mode 100755 index 0000000000..98c813a063 --- /dev/null +++ b/vendor/symfony/var-dumper/Resources/bin/var-dump-server @@ -0,0 +1,63 @@ +#!/usr/bin/env php +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Starts a dump server to collect and output dumps on a single place with multiple formats support. + * + * @author Maxime Steinhausser <maxime.steinhausser@gmail.com> + */ + +use Psr\Log\LoggerInterface; +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Logger\ConsoleLogger; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\VarDumper\Command\ServerDumpCommand; +use Symfony\Component\VarDumper\Server\DumpServer; + +function includeIfExists(string $file): bool +{ + return file_exists($file) && include $file; +} + +if ( + !includeIfExists(__DIR__ . '/../../../../autoload.php') && + !includeIfExists(__DIR__ . '/../../vendor/autoload.php') && + !includeIfExists(__DIR__ . '/../../../../../../vendor/autoload.php') +) { + fwrite(STDERR, 'Install dependencies using Composer.'.PHP_EOL); + exit(1); +} + +if (!class_exists(Application::class)) { + fwrite(STDERR, 'You need the "symfony/console" component in order to run the VarDumper server.'.PHP_EOL); + exit(1); +} + +$input = new ArgvInput(); +$output = new ConsoleOutput(); +$defaultHost = '127.0.0.1:9912'; +$host = $input->getParameterOption(['--host'], $_SERVER['VAR_DUMPER_SERVER'] ?? $defaultHost, true); +$logger = interface_exists(LoggerInterface::class) ? new ConsoleLogger($output->getErrorOutput()) : null; + +$app = new Application(); + +$app->getDefinition()->addOption( + new InputOption('--host', null, InputOption::VALUE_REQUIRED, 'The address the server should listen to', $defaultHost) +); + +$app->add($command = new ServerDumpCommand(new DumpServer($host, $logger))) + ->getApplication() + ->setDefaultCommand($command->getName(), true) + ->run($input, $output) +; diff --git a/vendor/symfony/var-dumper/Resources/css/htmlDescriptor.css b/vendor/symfony/var-dumper/Resources/css/htmlDescriptor.css new file mode 100644 index 0000000000..8f706d640f --- /dev/null +++ b/vendor/symfony/var-dumper/Resources/css/htmlDescriptor.css @@ -0,0 +1,130 @@ +body { + display: flex; + flex-direction: column-reverse; + justify-content: flex-end; + max-width: 1140px; + margin: auto; + padding: 15px; + word-wrap: break-word; + background-color: #F9F9F9; + color: #222; + font-family: Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.4; +} +p { + margin: 0; +} +a { + color: #218BC3; + text-decoration: none; +} +a:hover { + text-decoration: underline; +} +.text-small { + font-size: 12px !important; +} +article { + margin: 5px; + margin-bottom: 10px; +} +article > header > .row { + display: flex; + flex-direction: row; + align-items: baseline; + margin-bottom: 10px; +} +article > header > .row > .col { + flex: 1; + display: flex; + align-items: baseline; +} +article > header > .row > h2 { + font-size: 14px; + color: #222; + font-weight: normal; + font-family: "Lucida Console", monospace, sans-serif; + word-break: break-all; + margin: 20px 5px 0 0; + user-select: all; +} +article > header > .row > h2 > code { + white-space: nowrap; + user-select: none; + color: #cc2255; + background-color: #f7f7f9; + border: 1px solid #e1e1e8; + border-radius: 3px; + margin-right: 5px; + padding: 0 3px; +} +article > header > .row > time.col { + flex: 0; + text-align: right; + white-space: nowrap; + color: #999; + font-style: italic; +} +article > header ul.tags { + list-style: none; + padding: 0; + margin: 0; + font-size: 12px; +} +article > header ul.tags > li { + user-select: all; + margin-bottom: 2px; +} +article > header ul.tags > li > span.badge { + display: inline-block; + padding: .25em .4em; + margin-right: 5px; + border-radius: 4px; + background-color: #6c757d3b; + color: #524d4d; + font-size: 12px; + text-align: center; + font-weight: 700; + line-height: 1; + white-space: nowrap; + vertical-align: baseline; + user-select: none; +} +article > section.body { + border: 1px solid #d8d8d8; + background: #FFF; + padding: 10px; + border-radius: 3px; +} +pre.sf-dump { + border-radius: 3px; + margin-bottom: 0; +} +.hidden { + display: none !important; +} +.dumped-tag > .sf-dump { + display: inline-block; + margin: 0; + padding: 1px 5px; + line-height: 1.4; + vertical-align: top; + background-color: transparent; + user-select: auto; +} +.dumped-tag > pre.sf-dump, +.dumped-tag > .sf-dump-default { + color: #CC7832; + background: none; +} +.dumped-tag > .sf-dump .sf-dump-str { color: #629755; } +.dumped-tag > .sf-dump .sf-dump-private, +.dumped-tag > .sf-dump .sf-dump-protected, +.dumped-tag > .sf-dump .sf-dump-public { color: #262626; } +.dumped-tag > .sf-dump .sf-dump-note { color: #6897BB; } +.dumped-tag > .sf-dump .sf-dump-key { color: #789339; } +.dumped-tag > .sf-dump .sf-dump-ref { color: #6E6E6E; } +.dumped-tag > .sf-dump .sf-dump-ellipsis { color: #CC7832; max-width: 100em; } +.dumped-tag > .sf-dump .sf-dump-ellipsis-path { max-width: 5em; } +.dumped-tag > .sf-dump .sf-dump-ns { user-select: none; } diff --git a/vendor/symfony/var-dumper/Resources/functions/dump.php b/vendor/symfony/var-dumper/Resources/functions/dump.php new file mode 100644 index 0000000000..f26aad5bdf --- /dev/null +++ b/vendor/symfony/var-dumper/Resources/functions/dump.php @@ -0,0 +1,50 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Component\VarDumper\VarDumper; + +if (!function_exists('dump')) { + /** + * @author Nicolas Grekas <p@tchwork.com> + */ + function dump($var, ...$moreVars) + { + VarDumper::dump($var); + + foreach ($moreVars as $v) { + VarDumper::dump($v); + } + + if (1 < func_num_args()) { + return func_get_args(); + } + + return $var; + } +} + +if (!function_exists('dd')) { + /** + * @return never + */ + function dd(...$vars) + { + if (!in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && !headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + + foreach ($vars as $v) { + VarDumper::dump($v); + } + + exit(1); + } +} diff --git a/vendor/symfony/var-dumper/Resources/js/htmlDescriptor.js b/vendor/symfony/var-dumper/Resources/js/htmlDescriptor.js new file mode 100644 index 0000000000..63101e57c3 --- /dev/null +++ b/vendor/symfony/var-dumper/Resources/js/htmlDescriptor.js @@ -0,0 +1,10 @@ +document.addEventListener('DOMContentLoaded', function() { + let prev = null; + Array.from(document.getElementsByTagName('article')).reverse().forEach(function (article) { + const dedupId = article.dataset.dedupId; + if (dedupId === prev) { + article.getElementsByTagName('header')[0].classList.add('hidden'); + } + prev = dedupId; + }); +}); diff --git a/vendor/symfony/var-dumper/Server/Connection.php b/vendor/symfony/var-dumper/Server/Connection.php new file mode 100644 index 0000000000..d0611a1f6c --- /dev/null +++ b/vendor/symfony/var-dumper/Server/Connection.php @@ -0,0 +1,99 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Server; + +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface; + +/** + * Forwards serialized Data clones to a server. + * + * @author Maxime Steinhausser <maxime.steinhausser@gmail.com> + */ +class Connection +{ + private $host; + private $contextProviders; + + /** + * @var resource|null + */ + private $socket; + + /** + * @param string $host The server host + * @param ContextProviderInterface[] $contextProviders Context providers indexed by context name + */ + public function __construct(string $host, array $contextProviders = []) + { + if (!str_contains($host, '://')) { + $host = 'tcp://'.$host; + } + + $this->host = $host; + $this->contextProviders = $contextProviders; + } + + public function getContextProviders(): array + { + return $this->contextProviders; + } + + public function write(Data $data): bool + { + $socketIsFresh = !$this->socket; + if (!$this->socket = $this->socket ?: $this->createSocket()) { + return false; + } + + $context = ['timestamp' => microtime(true)]; + foreach ($this->contextProviders as $name => $provider) { + $context[$name] = $provider->getContext(); + } + $context = array_filter($context); + $encodedPayload = base64_encode(serialize([$data, $context]))."\n"; + + set_error_handler([self::class, 'nullErrorHandler']); + try { + if (-1 !== stream_socket_sendto($this->socket, $encodedPayload)) { + return true; + } + if (!$socketIsFresh) { + stream_socket_shutdown($this->socket, \STREAM_SHUT_RDWR); + fclose($this->socket); + $this->socket = $this->createSocket(); + } + if (-1 !== stream_socket_sendto($this->socket, $encodedPayload)) { + return true; + } + } finally { + restore_error_handler(); + } + + return false; + } + + private static function nullErrorHandler(int $t, string $m) + { + // no-op + } + + private function createSocket() + { + set_error_handler([self::class, 'nullErrorHandler']); + try { + return stream_socket_client($this->host, $errno, $errstr, 3, \STREAM_CLIENT_CONNECT | \STREAM_CLIENT_ASYNC_CONNECT); + } finally { + restore_error_handler(); + } + } +} diff --git a/vendor/symfony/var-dumper/Server/DumpServer.php b/vendor/symfony/var-dumper/Server/DumpServer.php new file mode 100644 index 0000000000..f9735db785 --- /dev/null +++ b/vendor/symfony/var-dumper/Server/DumpServer.php @@ -0,0 +1,115 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Server; + +use Psr\Log\LoggerInterface; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * A server collecting Data clones sent by a ServerDumper. + * + * @author Maxime Steinhausser <maxime.steinhausser@gmail.com> + * + * @final + */ +class DumpServer +{ + private $host; + private $logger; + + /** + * @var resource|null + */ + private $socket; + + public function __construct(string $host, LoggerInterface $logger = null) + { + if (!str_contains($host, '://')) { + $host = 'tcp://'.$host; + } + + $this->host = $host; + $this->logger = $logger; + } + + public function start(): void + { + if (!$this->socket = stream_socket_server($this->host, $errno, $errstr)) { + throw new \RuntimeException(sprintf('Server start failed on "%s": ', $this->host).$errstr.' '.$errno); + } + } + + public function listen(callable $callback): void + { + if (null === $this->socket) { + $this->start(); + } + + foreach ($this->getMessages() as $clientId => $message) { + if ($this->logger) { + $this->logger->info('Received a payload from client {clientId}', ['clientId' => $clientId]); + } + + $payload = @unserialize(base64_decode($message), ['allowed_classes' => [Data::class, Stub::class]]); + + // Impossible to decode the message, give up. + if (false === $payload) { + if ($this->logger) { + $this->logger->warning('Unable to decode a message from {clientId} client.', ['clientId' => $clientId]); + } + + continue; + } + + if (!\is_array($payload) || \count($payload) < 2 || !$payload[0] instanceof Data || !\is_array($payload[1])) { + if ($this->logger) { + $this->logger->warning('Invalid payload from {clientId} client. Expected an array of two elements (Data $data, array $context)', ['clientId' => $clientId]); + } + + continue; + } + + [$data, $context] = $payload; + + $callback($data, $context, $clientId); + } + } + + public function getHost(): string + { + return $this->host; + } + + private function getMessages(): iterable + { + $sockets = [(int) $this->socket => $this->socket]; + $write = []; + + while (true) { + $read = $sockets; + stream_select($read, $write, $write, null); + + foreach ($read as $stream) { + if ($this->socket === $stream) { + $stream = stream_socket_accept($this->socket); + $sockets[(int) $stream] = $stream; + } elseif (feof($stream)) { + unset($sockets[(int) $stream]); + fclose($stream); + } else { + yield (int) $stream => fgets($stream); + } + } + } + } +} diff --git a/vendor/symfony/var-dumper/Test/VarDumperTestTrait.php b/vendor/symfony/var-dumper/Test/VarDumperTestTrait.php new file mode 100644 index 0000000000..33d60c0201 --- /dev/null +++ b/vendor/symfony/var-dumper/Test/VarDumperTestTrait.php @@ -0,0 +1,84 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Test; + +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\CliDumper; + +/** + * @author Nicolas Grekas <p@tchwork.com> + */ +trait VarDumperTestTrait +{ + /** + * @internal + */ + private $varDumperConfig = [ + 'casters' => [], + 'flags' => null, + ]; + + protected function setUpVarDumper(array $casters, int $flags = null): void + { + $this->varDumperConfig['casters'] = $casters; + $this->varDumperConfig['flags'] = $flags; + } + + /** + * @after + */ + protected function tearDownVarDumper(): void + { + $this->varDumperConfig['casters'] = []; + $this->varDumperConfig['flags'] = null; + } + + public function assertDumpEquals($expected, $data, int $filter = 0, string $message = '') + { + $this->assertSame($this->prepareExpectation($expected, $filter), $this->getDump($data, null, $filter), $message); + } + + public function assertDumpMatchesFormat($expected, $data, int $filter = 0, string $message = '') + { + $this->assertStringMatchesFormat($this->prepareExpectation($expected, $filter), $this->getDump($data, null, $filter), $message); + } + + protected function getDump($data, $key = null, int $filter = 0): ?string + { + if (null === $flags = $this->varDumperConfig['flags']) { + $flags = getenv('DUMP_LIGHT_ARRAY') ? CliDumper::DUMP_LIGHT_ARRAY : 0; + $flags |= getenv('DUMP_STRING_LENGTH') ? CliDumper::DUMP_STRING_LENGTH : 0; + $flags |= getenv('DUMP_COMMA_SEPARATOR') ? CliDumper::DUMP_COMMA_SEPARATOR : 0; + } + + $cloner = new VarCloner(); + $cloner->addCasters($this->varDumperConfig['casters']); + $cloner->setMaxItems(-1); + $dumper = new CliDumper(null, null, $flags); + $dumper->setColors(false); + $data = $cloner->cloneVar($data, $filter)->withRefHandles(false); + if (null !== $key && null === $data = $data->seek($key)) { + return null; + } + + return rtrim($dumper->dump($data, true)); + } + + private function prepareExpectation($expected, int $filter): string + { + if (!\is_string($expected)) { + $expected = $this->getDump($expected, null, $filter); + } + + return rtrim($expected); + } +} diff --git a/vendor/symfony/var-dumper/VarDumper.php b/vendor/symfony/var-dumper/VarDumper.php new file mode 100644 index 0000000000..20429ac788 --- /dev/null +++ b/vendor/symfony/var-dumper/VarDumper.php @@ -0,0 +1,115 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; +use Symfony\Component\VarDumper\Caster\ReflectionCaster; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\CliDumper; +use Symfony\Component\VarDumper\Dumper\ContextProvider\CliContextProvider; +use Symfony\Component\VarDumper\Dumper\ContextProvider\RequestContextProvider; +use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider; +use Symfony\Component\VarDumper\Dumper\ContextualizedDumper; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; +use Symfony\Component\VarDumper\Dumper\ServerDumper; + +// Load the global dump() function +require_once __DIR__.'/Resources/functions/dump.php'; + +/** + * @author Nicolas Grekas <p@tchwork.com> + */ +class VarDumper +{ + /** + * @var callable|null + */ + private static $handler; + + public static function dump($var) + { + if (null === self::$handler) { + self::register(); + } + + return (self::$handler)($var); + } + + /** + * @return callable|null + */ + public static function setHandler(callable $callable = null) + { + $prevHandler = self::$handler; + + // Prevent replacing the handler with expected format as soon as the env var was set: + if (isset($_SERVER['VAR_DUMPER_FORMAT'])) { + return $prevHandler; + } + + self::$handler = $callable; + + return $prevHandler; + } + + private static function register(): void + { + $cloner = new VarCloner(); + $cloner->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO); + + $format = $_SERVER['VAR_DUMPER_FORMAT'] ?? null; + switch (true) { + case 'html' === $format: + $dumper = new HtmlDumper(); + break; + case 'cli' === $format: + $dumper = new CliDumper(); + break; + case 'server' === $format: + case $format && 'tcp' === parse_url($format, \PHP_URL_SCHEME): + $host = 'server' === $format ? $_SERVER['VAR_DUMPER_SERVER'] ?? '127.0.0.1:9912' : $format; + $dumper = \in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) ? new CliDumper() : new HtmlDumper(); + $dumper = new ServerDumper($host, $dumper, self::getDefaultContextProviders()); + break; + default: + $dumper = \in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) ? new CliDumper() : new HtmlDumper(); + } + + if (!$dumper instanceof ServerDumper) { + $dumper = new ContextualizedDumper($dumper, [new SourceContextProvider()]); + } + + self::$handler = function ($var) use ($cloner, $dumper) { + $dumper->dump($cloner->cloneVar($var)); + }; + } + + private static function getDefaultContextProviders(): array + { + $contextProviders = []; + + if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && class_exists(Request::class)) { + $requestStack = new RequestStack(); + $requestStack->push(Request::createFromGlobals()); + $contextProviders['request'] = new RequestContextProvider($requestStack); + } + + $fileLinkFormatter = class_exists(FileLinkFormatter::class) ? new FileLinkFormatter(null, $requestStack ?? null) : null; + + return $contextProviders + [ + 'cli' => new CliContextProvider(), + 'source' => new SourceContextProvider(null, null, $fileLinkFormatter), + ]; + } +} diff --git a/vendor/symfony/var-dumper/composer.json b/vendor/symfony/var-dumper/composer.json new file mode 100644 index 0000000000..dc46f58d99 --- /dev/null +++ b/vendor/symfony/var-dumper/composer.json @@ -0,0 +1,50 @@ +{ + "name": "symfony/var-dumper", + "type": "library", + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "keywords": ["dump", "debug"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2.5", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/uid": "^5.1|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<4.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "autoload": { + "files": [ "Resources/functions/dump.php" ], + "psr-4": { "Symfony\\Component\\VarDumper\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "minimum-stability": "dev" +} diff --git a/vendor/symfony/yaml/CHANGELOG.md b/vendor/symfony/yaml/CHANGELOG.md new file mode 100644 index 0000000000..ff78af2feb --- /dev/null +++ b/vendor/symfony/yaml/CHANGELOG.md @@ -0,0 +1,196 @@ +CHANGELOG +========= + +4.4.0 +----- + + * Added support for parsing the inline notation spanning multiple lines. + * Added support to dump `null` as `~` by using the `Yaml::DUMP_NULL_AS_TILDE` flag. + * deprecated accepting STDIN implicitly when using the `lint:yaml` command, use `lint:yaml -` (append a dash) instead to make it explicit. + +4.3.0 +----- + + * Using a mapping inside a multi-line string is deprecated and will throw a `ParseException` in 5.0. + +4.2.0 +----- + + * added support for multiple files or directories in `LintCommand` + +4.0.0 +----- + + * The behavior of the non-specific tag `!` is changed and now forces + non-evaluating your values. + * complex mappings will throw a `ParseException` + * support for the comma as a group separator for floats has been dropped, use + the underscore instead + * support for the `!!php/object` tag has been dropped, use the `!php/object` + tag instead + * duplicate mapping keys throw a `ParseException` + * non-string mapping keys throw a `ParseException`, use the `Yaml::PARSE_KEYS_AS_STRINGS` + flag to cast them to strings + * `%` at the beginning of an unquoted string throw a `ParseException` + * mappings with a colon (`:`) that is not followed by a whitespace throw a + `ParseException` + * the `Dumper::setIndentation()` method has been removed + * being able to pass boolean options to the `Yaml::parse()`, `Yaml::dump()`, + `Parser::parse()`, and `Dumper::dump()` methods to configure the behavior of + the parser and dumper is no longer supported, pass bitmask flags instead + * the constructor arguments of the `Parser` class have been removed + * the `Inline` class is internal and no longer part of the BC promise + * removed support for the `!str` tag, use the `!!str` tag instead + * added support for tagged scalars. + + ```yml + Yaml::parse('!foo bar', Yaml::PARSE_CUSTOM_TAGS); + // returns TaggedValue('foo', 'bar'); + ``` + +3.4.0 +----- + + * added support for parsing YAML files using the `Yaml::parseFile()` or `Parser::parseFile()` method + + * the `Dumper`, `Parser`, and `Yaml` classes are marked as final + + * Deprecated the `!php/object:` tag which will be replaced by the + `!php/object` tag (without the colon) in 4.0. + + * Deprecated the `!php/const:` tag which will be replaced by the + `!php/const` tag (without the colon) in 4.0. + + * Support for the `!str` tag is deprecated, use the `!!str` tag instead. + + * Deprecated using the non-specific tag `!` as its behavior will change in 4.0. + It will force non-evaluating your values in 4.0. Use plain integers or `!!float` instead. + +3.3.0 +----- + + * Starting an unquoted string with a question mark followed by a space is + deprecated and will throw a `ParseException` in Symfony 4.0. + + * Deprecated support for implicitly parsing non-string mapping keys as strings. + Mapping keys that are no strings will lead to a `ParseException` in Symfony + 4.0. Use quotes to opt-in for keys to be parsed as strings. + + Before: + + ```php + $yaml = <<<YAML + null: null key + true: boolean true + 2.0: float key + YAML; + + Yaml::parse($yaml); + ``` + + After: + + ```php + + $yaml = <<<YAML + "null": null key + "true": boolean true + "2.0": float key + YAML; + + Yaml::parse($yaml); + ``` + + * Omitted mapping values will be parsed as `null`. + + * Omitting the key of a mapping is deprecated and will throw a `ParseException` in Symfony 4.0. + + * Added support for dumping empty PHP arrays as YAML sequences: + + ```php + Yaml::dump([], 0, 0, Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE); + ``` + +3.2.0 +----- + + * Mappings with a colon (`:`) that is not followed by a whitespace are deprecated + when the mapping key is not quoted and will lead to a `ParseException` in + Symfony 4.0 (e.g. `foo:bar` must be `foo: bar`). + + * Added support for parsing PHP constants: + + ```php + Yaml::parse('!php/const:PHP_INT_MAX', Yaml::PARSE_CONSTANT); + ``` + + * Support for silently ignoring duplicate mapping keys in YAML has been + deprecated and will lead to a `ParseException` in Symfony 4.0. + +3.1.0 +----- + + * Added support to dump `stdClass` and `ArrayAccess` objects as YAML mappings + through the `Yaml::DUMP_OBJECT_AS_MAP` flag. + + * Strings that are not UTF-8 encoded will be dumped as base64 encoded binary + data. + + * Added support for dumping multi line strings as literal blocks. + + * Added support for parsing base64 encoded binary data when they are tagged + with the `!!binary` tag. + + * Added support for parsing timestamps as `\DateTime` objects: + + ```php + Yaml::parse('2001-12-15 21:59:43.10 -5', Yaml::PARSE_DATETIME); + ``` + + * `\DateTime` and `\DateTimeImmutable` objects are dumped as YAML timestamps. + + * Deprecated usage of `%` at the beginning of an unquoted string. + + * Added support for customizing the YAML parser behavior through an optional bit field: + + ```php + Yaml::parse('{ "foo": "bar", "fiz": "cat" }', Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE | Yaml::PARSE_OBJECT | Yaml::PARSE_OBJECT_FOR_MAP); + ``` + + * Added support for customizing the dumped YAML string through an optional bit field: + + ```php + Yaml::dump(['foo' => new A(), 'bar' => 1], 0, 0, Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE | Yaml::DUMP_OBJECT); + ``` + +3.0.0 +----- + + * Yaml::parse() now throws an exception when a blackslash is not escaped + in double-quoted strings + +2.8.0 +----- + + * Deprecated usage of a colon in an unquoted mapping value + * Deprecated usage of @, \`, | and > at the beginning of an unquoted string + * When surrounding strings with double-quotes, you must now escape `\` characters. Not + escaping those characters (when surrounded by double-quotes) is deprecated. + + Before: + + ```yml + class: "Foo\Var" + ``` + + After: + + ```yml + class: "Foo\\Var" + ``` + +2.1.0 +----- + + * Yaml::parse() does not evaluate loaded files as PHP files by default + anymore (call Yaml::enablePhpParsing() to get back the old behavior) diff --git a/vendor/symfony/yaml/Command/LintCommand.php b/vendor/symfony/yaml/Command/LintCommand.php new file mode 100644 index 0000000000..98eb8e15c4 --- /dev/null +++ b/vendor/symfony/yaml/Command/LintCommand.php @@ -0,0 +1,255 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Parser; +use Symfony\Component\Yaml\Yaml; + +/** + * Validates YAML files syntax and outputs encountered errors. + * + * @author Grégoire Pineau <lyrixx@lyrixx.info> + * @author Robin Chalas <robin.chalas@gmail.com> + */ +class LintCommand extends Command +{ + protected static $defaultName = 'lint:yaml'; + + private $parser; + private $format; + private $displayCorrectFiles; + private $directoryIteratorProvider; + private $isReadableProvider; + + public function __construct(string $name = null, callable $directoryIteratorProvider = null, callable $isReadableProvider = null) + { + parent::__construct($name); + + $this->directoryIteratorProvider = $directoryIteratorProvider; + $this->isReadableProvider = $isReadableProvider; + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setDescription('Lint a file and outputs encountered errors') + ->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN') + ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt') + ->addOption('parse-tags', null, InputOption::VALUE_NONE, 'Parse custom tags') + ->setHelp(<<<EOF +The <info>%command.name%</info> command lints a YAML file and outputs to STDOUT +the first encountered syntax error. + +You can validates YAML contents passed from STDIN: + + <info>cat filename | php %command.full_name% -</info> + +You can also validate the syntax of a file: + + <info>php %command.full_name% filename</info> + +Or of a whole directory: + + <info>php %command.full_name% dirname</info> + <info>php %command.full_name% dirname --format=json</info> + +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + $filenames = (array) $input->getArgument('filename'); + $this->format = $input->getOption('format'); + $this->displayCorrectFiles = $output->isVerbose(); + $flags = $input->getOption('parse-tags') ? Yaml::PARSE_CUSTOM_TAGS : 0; + + if (['-'] === $filenames) { + return $this->display($io, [$this->validate(file_get_contents('php://stdin'), $flags)]); + } + + // @deprecated to be removed in 5.0 + if (!$filenames) { + if (0 === ftell(\STDIN)) { + @trigger_error('Piping content from STDIN to the "lint:yaml" command without passing the dash symbol "-" as argument is deprecated since Symfony 4.4.', \E_USER_DEPRECATED); + + return $this->display($io, [$this->validate(file_get_contents('php://stdin'), $flags)]); + } + + throw new RuntimeException('Please provide a filename or pipe file content to STDIN.'); + } + + $filesInfo = []; + foreach ($filenames as $filename) { + if (!$this->isReadable($filename)) { + throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename)); + } + + foreach ($this->getFiles($filename) as $file) { + $filesInfo[] = $this->validate(file_get_contents($file), $flags, $file); + } + } + + return $this->display($io, $filesInfo); + } + + private function validate(string $content, int $flags, string $file = null) + { + $prevErrorHandler = set_error_handler(function ($level, $message, $file, $line) use (&$prevErrorHandler) { + if (\E_USER_DEPRECATED === $level) { + throw new ParseException($message, $this->getParser()->getRealCurrentLineNb() + 1); + } + + return $prevErrorHandler ? $prevErrorHandler($level, $message, $file, $line) : false; + }); + + try { + $this->getParser()->parse($content, Yaml::PARSE_CONSTANT | $flags); + } catch (ParseException $e) { + return ['file' => $file, 'line' => $e->getParsedLine(), 'valid' => false, 'message' => $e->getMessage()]; + } finally { + restore_error_handler(); + } + + return ['file' => $file, 'valid' => true]; + } + + private function display(SymfonyStyle $io, array $files): int + { + switch ($this->format) { + case 'txt': + return $this->displayTxt($io, $files); + case 'json': + return $this->displayJson($io, $files); + default: + throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $this->format)); + } + } + + private function displayTxt(SymfonyStyle $io, array $filesInfo): int + { + $countFiles = \count($filesInfo); + $erroredFiles = 0; + $suggestTagOption = false; + + foreach ($filesInfo as $info) { + if ($info['valid'] && $this->displayCorrectFiles) { + $io->comment('<info>OK</info>'.($info['file'] ? sprintf(' in %s', $info['file']) : '')); + } elseif (!$info['valid']) { + ++$erroredFiles; + $io->text('<error> ERROR </error>'.($info['file'] ? sprintf(' in %s', $info['file']) : '')); + $io->text(sprintf('<error> >> %s</error>', $info['message'])); + + if (false !== strpos($info['message'], 'PARSE_CUSTOM_TAGS')) { + $suggestTagOption = true; + } + } + } + + if (0 === $erroredFiles) { + $io->success(sprintf('All %d YAML files contain valid syntax.', $countFiles)); + } else { + $io->warning(sprintf('%d YAML files have valid syntax and %d contain errors.%s', $countFiles - $erroredFiles, $erroredFiles, $suggestTagOption ? ' Use the --parse-tags option if you want parse custom tags.' : '')); + } + + return min($erroredFiles, 1); + } + + private function displayJson(SymfonyStyle $io, array $filesInfo): int + { + $errors = 0; + + array_walk($filesInfo, function (&$v) use (&$errors) { + $v['file'] = (string) $v['file']; + if (!$v['valid']) { + ++$errors; + } + + if (isset($v['message']) && false !== strpos($v['message'], 'PARSE_CUSTOM_TAGS')) { + $v['message'] .= ' Use the --parse-tags option if you want parse custom tags.'; + } + }); + + $io->writeln(json_encode($filesInfo, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)); + + return min($errors, 1); + } + + private function getFiles(string $fileOrDirectory): iterable + { + if (is_file($fileOrDirectory)) { + yield new \SplFileInfo($fileOrDirectory); + + return; + } + + foreach ($this->getDirectoryIterator($fileOrDirectory) as $file) { + if (!\in_array($file->getExtension(), ['yml', 'yaml'])) { + continue; + } + + yield $file; + } + } + + private function getParser(): Parser + { + if (!$this->parser) { + $this->parser = new Parser(); + } + + return $this->parser; + } + + private function getDirectoryIterator(string $directory): iterable + { + $default = function ($directory) { + return new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS), + \RecursiveIteratorIterator::LEAVES_ONLY + ); + }; + + if (null !== $this->directoryIteratorProvider) { + return ($this->directoryIteratorProvider)($directory, $default); + } + + return $default($directory); + } + + private function isReadable(string $fileOrDirectory): bool + { + $default = function ($fileOrDirectory) { + return is_readable($fileOrDirectory); + }; + + if (null !== $this->isReadableProvider) { + return ($this->isReadableProvider)($fileOrDirectory, $default); + } + + return $default($fileOrDirectory); + } +} diff --git a/vendor/symfony/yaml/Dumper.php b/vendor/symfony/yaml/Dumper.php new file mode 100644 index 0000000000..52db38c3b0 --- /dev/null +++ b/vendor/symfony/yaml/Dumper.php @@ -0,0 +1,142 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +use Symfony\Component\Yaml\Tag\TaggedValue; + +/** + * Dumper dumps PHP variables to YAML strings. + * + * @author Fabien Potencier <fabien@symfony.com> + * + * @final + */ +class Dumper +{ + /** + * The amount of spaces to use for indentation of nested nodes. + * + * @var int + */ + protected $indentation; + + public function __construct(int $indentation = 4) + { + if ($indentation < 1) { + throw new \InvalidArgumentException('The indentation must be greater than zero.'); + } + + $this->indentation = $indentation; + } + + /** + * Dumps a PHP value to YAML. + * + * @param mixed $input The PHP value + * @param int $inline The level where you switch to inline YAML + * @param int $indent The level of indentation (used internally) + * @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string + * + * @return string The YAML representation of the PHP value + */ + public function dump($input, int $inline = 0, int $indent = 0, int $flags = 0): string + { + $output = ''; + $prefix = $indent ? str_repeat(' ', $indent) : ''; + $dumpObjectAsInlineMap = true; + + if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($input instanceof \ArrayObject || $input instanceof \stdClass)) { + $dumpObjectAsInlineMap = empty((array) $input); + } + + if ($inline <= 0 || (!\is_array($input) && !$input instanceof TaggedValue && $dumpObjectAsInlineMap) || empty($input)) { + $output .= $prefix.Inline::dump($input, $flags); + } else { + $dumpAsMap = Inline::isHash($input); + + foreach ($input as $key => $value) { + if ('' !== $output && "\n" !== $output[-1]) { + $output .= "\n"; + } + + if (Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value) && false !== strpos($value, "\n") && false === strpos($value, "\r")) { + // If the first line starts with a space character, the spec requires a blockIndicationIndicator + // http://www.yaml.org/spec/1.2/spec.html#id2793979 + $blockIndentationIndicator = (' ' === substr($value, 0, 1)) ? (string) $this->indentation : ''; + + if (isset($value[-2]) && "\n" === $value[-2] && "\n" === $value[-1]) { + $blockChompingIndicator = '+'; + } elseif ("\n" === $value[-1]) { + $blockChompingIndicator = ''; + } else { + $blockChompingIndicator = '-'; + } + + $output .= sprintf('%s%s%s |%s%s', $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', '', $blockIndentationIndicator, $blockChompingIndicator); + + foreach (explode("\n", $value) as $row) { + if ('' === $row) { + $output .= "\n"; + } else { + $output .= sprintf("\n%s%s%s", $prefix, str_repeat(' ', $this->indentation), $row); + } + } + + continue; + } + + if ($value instanceof TaggedValue) { + $output .= sprintf('%s%s !%s', $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', $value->getTag()); + + if (Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value->getValue()) && false !== strpos($value->getValue(), "\n") && false === strpos($value->getValue(), "\r\n")) { + // If the first line starts with a space character, the spec requires a blockIndicationIndicator + // http://www.yaml.org/spec/1.2/spec.html#id2793979 + $blockIndentationIndicator = (' ' === substr($value->getValue(), 0, 1)) ? (string) $this->indentation : ''; + $output .= sprintf(' |%s', $blockIndentationIndicator); + + foreach (explode("\n", $value->getValue()) as $row) { + $output .= sprintf("\n%s%s%s", $prefix, str_repeat(' ', $this->indentation), $row); + } + + continue; + } + + if ($inline - 1 <= 0 || null === $value->getValue() || \is_scalar($value->getValue())) { + $output .= ' '.$this->dump($value->getValue(), $inline - 1, 0, $flags)."\n"; + } else { + $output .= "\n"; + $output .= $this->dump($value->getValue(), $inline - 1, $dumpAsMap ? $indent + $this->indentation : $indent + 2, $flags); + } + + continue; + } + + $dumpObjectAsInlineMap = true; + + if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \ArrayObject || $value instanceof \stdClass)) { + $dumpObjectAsInlineMap = empty((array) $value); + } + + $willBeInlined = $inline - 1 <= 0 || !\is_array($value) && $dumpObjectAsInlineMap || empty($value); + + $output .= sprintf('%s%s%s%s', + $prefix, + $dumpAsMap ? Inline::dump($key, $flags).':' : '-', + $willBeInlined ? ' ' : "\n", + $this->dump($value, $inline - 1, $willBeInlined ? 0 : $indent + $this->indentation, $flags) + ).($willBeInlined ? "\n" : ''); + } + } + + return $output; + } +} diff --git a/vendor/symfony/yaml/Escaper.php b/vendor/symfony/yaml/Escaper.php new file mode 100644 index 0000000000..9b809df874 --- /dev/null +++ b/vendor/symfony/yaml/Escaper.php @@ -0,0 +1,103 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +/** + * Escaper encapsulates escaping rules for single and double-quoted + * YAML strings. + * + * @author Matthew Lewinski <matthew@lewinski.org> + * + * @internal + */ +class Escaper +{ + // Characters that would cause a dumped string to require double quoting. + public const REGEX_CHARACTER_TO_ESCAPE = "[\\x00-\\x1f]|\x7f|\xc2\x85|\xc2\xa0|\xe2\x80\xa8|\xe2\x80\xa9"; + + // Mapping arrays for escaping a double quoted string. The backslash is + // first to ensure proper escaping because str_replace operates iteratively + // on the input arrays. This ordering of the characters avoids the use of strtr, + // which performs more slowly. + private const ESCAPEES = ['\\', '\\\\', '\\"', '"', + "\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", + "\x08", "\x09", "\x0a", "\x0b", "\x0c", "\x0d", "\x0e", "\x0f", + "\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17", + "\x18", "\x19", "\x1a", "\x1b", "\x1c", "\x1d", "\x1e", "\x1f", + "\x7f", + "\xc2\x85", "\xc2\xa0", "\xe2\x80\xa8", "\xe2\x80\xa9", + ]; + private const ESCAPED = ['\\\\', '\\"', '\\\\', '\\"', + '\\0', '\\x01', '\\x02', '\\x03', '\\x04', '\\x05', '\\x06', '\\a', + '\\b', '\\t', '\\n', '\\v', '\\f', '\\r', '\\x0e', '\\x0f', + '\\x10', '\\x11', '\\x12', '\\x13', '\\x14', '\\x15', '\\x16', '\\x17', + '\\x18', '\\x19', '\\x1a', '\\e', '\\x1c', '\\x1d', '\\x1e', '\\x1f', + '\\x7f', + '\\N', '\\_', '\\L', '\\P', + ]; + + /** + * Determines if a PHP value would require double quoting in YAML. + * + * @param string $value A PHP value + * + * @return bool True if the value would require double quotes + */ + public static function requiresDoubleQuoting(string $value): bool + { + return 0 < preg_match('/'.self::REGEX_CHARACTER_TO_ESCAPE.'/u', $value); + } + + /** + * Escapes and surrounds a PHP value with double quotes. + * + * @param string $value A PHP value + * + * @return string The quoted, escaped string + */ + public static function escapeWithDoubleQuotes(string $value): string + { + return sprintf('"%s"', str_replace(self::ESCAPEES, self::ESCAPED, $value)); + } + + /** + * Determines if a PHP value would require single quoting in YAML. + * + * @param string $value A PHP value + * + * @return bool True if the value would require single quotes + */ + public static function requiresSingleQuoting(string $value): bool + { + // Determines if a PHP value is entirely composed of a value that would + // require single quoting in YAML. + if (\in_array(strtolower($value), ['null', '~', 'true', 'false', 'y', 'n', 'yes', 'no', 'on', 'off'])) { + return true; + } + + // Determines if the PHP value contains any single characters that would + // cause it to require single quoting in YAML. + return 0 < preg_match('/[ \s \' " \: \{ \} \[ \] , & \* \# \?] | \A[ \- ? | < > = ! % @ ` \p{Zs}]/xu', $value); + } + + /** + * Escapes and surrounds a PHP value with single quotes. + * + * @param string $value A PHP value + * + * @return string The quoted, escaped string + */ + public static function escapeWithSingleQuotes(string $value): string + { + return sprintf("'%s'", str_replace('\'', '\'\'', $value)); + } +} diff --git a/vendor/symfony/yaml/Exception/DumpException.php b/vendor/symfony/yaml/Exception/DumpException.php new file mode 100644 index 0000000000..cce972f246 --- /dev/null +++ b/vendor/symfony/yaml/Exception/DumpException.php @@ -0,0 +1,21 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Exception; + +/** + * Exception class thrown when an error occurs during dumping. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class DumpException extends RuntimeException +{ +} diff --git a/vendor/symfony/yaml/Exception/ExceptionInterface.php b/vendor/symfony/yaml/Exception/ExceptionInterface.php new file mode 100644 index 0000000000..909131684c --- /dev/null +++ b/vendor/symfony/yaml/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Exception; + +/** + * Exception interface for all exceptions thrown by the component. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/yaml/Exception/ParseException.php b/vendor/symfony/yaml/Exception/ParseException.php new file mode 100644 index 0000000000..82c05a714f --- /dev/null +++ b/vendor/symfony/yaml/Exception/ParseException.php @@ -0,0 +1,138 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Exception; + +/** + * Exception class thrown when an error occurs during parsing. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class ParseException extends RuntimeException +{ + private $parsedFile; + private $parsedLine; + private $snippet; + private $rawMessage; + + /** + * @param string $message The error message + * @param int $parsedLine The line where the error occurred + * @param string|null $snippet The snippet of code near the problem + * @param string|null $parsedFile The file name where the error occurred + */ + public function __construct(string $message, int $parsedLine = -1, string $snippet = null, string $parsedFile = null, \Throwable $previous = null) + { + $this->parsedFile = $parsedFile; + $this->parsedLine = $parsedLine; + $this->snippet = $snippet; + $this->rawMessage = $message; + + $this->updateRepr(); + + parent::__construct($this->message, 0, $previous); + } + + /** + * Gets the snippet of code near the error. + * + * @return string The snippet of code + */ + public function getSnippet() + { + return $this->snippet; + } + + /** + * Sets the snippet of code near the error. + * + * @param string $snippet The code snippet + */ + public function setSnippet($snippet) + { + $this->snippet = $snippet; + + $this->updateRepr(); + } + + /** + * Gets the filename where the error occurred. + * + * This method returns null if a string is parsed. + * + * @return string The filename + */ + public function getParsedFile() + { + return $this->parsedFile; + } + + /** + * Sets the filename where the error occurred. + * + * @param string $parsedFile The filename + */ + public function setParsedFile($parsedFile) + { + $this->parsedFile = $parsedFile; + + $this->updateRepr(); + } + + /** + * Gets the line where the error occurred. + * + * @return int The file line + */ + public function getParsedLine() + { + return $this->parsedLine; + } + + /** + * Sets the line where the error occurred. + * + * @param int $parsedLine The file line + */ + public function setParsedLine($parsedLine) + { + $this->parsedLine = $parsedLine; + + $this->updateRepr(); + } + + private function updateRepr() + { + $this->message = $this->rawMessage; + + $dot = false; + if ('.' === substr($this->message, -1)) { + $this->message = substr($this->message, 0, -1); + $dot = true; + } + + if (null !== $this->parsedFile) { + $this->message .= sprintf(' in %s', json_encode($this->parsedFile, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE)); + } + + if ($this->parsedLine >= 0) { + $this->message .= sprintf(' at line %d', $this->parsedLine); + } + + if ($this->snippet) { + $this->message .= sprintf(' (near "%s")', $this->snippet); + } + + if ($dot) { + $this->message .= '.'; + } + } +} diff --git a/vendor/symfony/yaml/Exception/RuntimeException.php b/vendor/symfony/yaml/Exception/RuntimeException.php new file mode 100644 index 0000000000..3f36b73bec --- /dev/null +++ b/vendor/symfony/yaml/Exception/RuntimeException.php @@ -0,0 +1,21 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Exception; + +/** + * Exception class thrown when an error occurs during parsing. + * + * @author Romain Neutron <imprec@gmail.com> + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/yaml/Inline.php b/vendor/symfony/yaml/Inline.php new file mode 100644 index 0000000000..1d4bbda697 --- /dev/null +++ b/vendor/symfony/yaml/Inline.php @@ -0,0 +1,803 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +use Symfony\Component\Yaml\Exception\DumpException; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Tag\TaggedValue; + +/** + * Inline implements a YAML parser/dumper for the YAML inline syntax. + * + * @author Fabien Potencier <fabien@symfony.com> + * + * @internal + */ +class Inline +{ + public const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+)"|\'([^\']*+(?:\'\'[^\']*+)*+)\')'; + + public static $parsedLineNumber = -1; + public static $parsedFilename; + + private static $exceptionOnInvalidType = false; + private static $objectSupport = false; + private static $objectForMap = false; + private static $constantSupport = false; + + public static function initialize(int $flags, int $parsedLineNumber = null, string $parsedFilename = null) + { + self::$exceptionOnInvalidType = (bool) (Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE & $flags); + self::$objectSupport = (bool) (Yaml::PARSE_OBJECT & $flags); + self::$objectForMap = (bool) (Yaml::PARSE_OBJECT_FOR_MAP & $flags); + self::$constantSupport = (bool) (Yaml::PARSE_CONSTANT & $flags); + self::$parsedFilename = $parsedFilename; + + if (null !== $parsedLineNumber) { + self::$parsedLineNumber = $parsedLineNumber; + } + } + + /** + * Converts a YAML string to a PHP value. + * + * @param string $value A YAML string + * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior + * @param array $references Mapping of variable names to values + * + * @return mixed A PHP value + * + * @throws ParseException + */ + public static function parse(string $value = null, int $flags = 0, array &$references = []) + { + self::initialize($flags); + + $value = trim($value); + + if ('' === $value) { + return ''; + } + + if (2 /* MB_OVERLOAD_STRING */ & (int) \ini_get('mbstring.func_overload')) { + $mbEncoding = mb_internal_encoding(); + mb_internal_encoding('ASCII'); + } + + try { + $i = 0; + $tag = self::parseTag($value, $i, $flags); + switch ($value[$i]) { + case '[': + $result = self::parseSequence($value, $flags, $i, $references); + ++$i; + break; + case '{': + $result = self::parseMapping($value, $flags, $i, $references); + ++$i; + break; + default: + $result = self::parseScalar($value, $flags, null, $i, null === $tag, $references); + } + + // some comments are allowed at the end + if (preg_replace('/\s*#.*$/A', '', substr($value, $i))) { + throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i)), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + if (null !== $tag && '' !== $tag) { + return new TaggedValue($tag, $result); + } + + return $result; + } finally { + if (isset($mbEncoding)) { + mb_internal_encoding($mbEncoding); + } + } + } + + /** + * Dumps a given PHP variable to a YAML string. + * + * @param mixed $value The PHP variable to convert + * @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string + * + * @return string The YAML string representing the PHP value + * + * @throws DumpException When trying to dump PHP resource + */ + public static function dump($value, int $flags = 0): string + { + switch (true) { + case \is_resource($value): + if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) { + throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value))); + } + + return self::dumpNull($flags); + case $value instanceof \DateTimeInterface: + return $value->format('c'); + case $value instanceof \UnitEnum: + return sprintf('!php/const %s::%s', \get_class($value), $value->name); + case \is_object($value): + if ($value instanceof TaggedValue) { + return '!'.$value->getTag().' '.self::dump($value->getValue(), $flags); + } + + if (Yaml::DUMP_OBJECT & $flags) { + return '!php/object '.self::dump(serialize($value)); + } + + if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \stdClass || $value instanceof \ArrayObject)) { + $output = []; + + foreach ($value as $key => $val) { + $output[] = sprintf('%s: %s', self::dump($key, $flags), self::dump($val, $flags)); + } + + return sprintf('{ %s }', implode(', ', $output)); + } + + if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) { + throw new DumpException('Object support when dumping a YAML file has been disabled.'); + } + + return self::dumpNull($flags); + case \is_array($value): + return self::dumpArray($value, $flags); + case null === $value: + return self::dumpNull($flags); + case true === $value: + return 'true'; + case false === $value: + return 'false'; + case \is_int($value): + return $value; + case is_numeric($value) && false === strpbrk($value, "\f\n\r\t\v"): + $locale = setlocale(\LC_NUMERIC, 0); + if (false !== $locale) { + setlocale(\LC_NUMERIC, 'C'); + } + if (\is_float($value)) { + $repr = (string) $value; + if (is_infinite($value)) { + $repr = str_ireplace('INF', '.Inf', $repr); + } elseif (floor($value) == $value && $repr == $value) { + // Preserve float data type since storing a whole number will result in integer value. + $repr = '!!float '.$repr; + } + } else { + $repr = \is_string($value) ? "'$value'" : (string) $value; + } + if (false !== $locale) { + setlocale(\LC_NUMERIC, $locale); + } + + return $repr; + case '' == $value: + return "''"; + case self::isBinaryString($value): + return '!!binary '.base64_encode($value); + case Escaper::requiresDoubleQuoting($value): + return Escaper::escapeWithDoubleQuotes($value); + case Escaper::requiresSingleQuoting($value): + case Parser::preg_match('{^[0-9]+[_0-9]*$}', $value): + case Parser::preg_match(self::getHexRegex(), $value): + case Parser::preg_match(self::getTimestampRegex(), $value): + return Escaper::escapeWithSingleQuotes($value); + default: + return $value; + } + } + + /** + * Check if given array is hash or just normal indexed array. + * + * @param array|\ArrayObject|\stdClass $value The PHP array or array-like object to check + * + * @return bool true if value is hash array, false otherwise + */ + public static function isHash($value): bool + { + if ($value instanceof \stdClass || $value instanceof \ArrayObject) { + return true; + } + + $expectedKey = 0; + + foreach ($value as $key => $val) { + if ($key !== $expectedKey++) { + return true; + } + } + + return false; + } + + /** + * Dumps a PHP array to a YAML string. + * + * @param array $value The PHP array to dump + * @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string + * + * @return string The YAML string representing the PHP array + */ + private static function dumpArray(array $value, int $flags): string + { + // array + if (($value || Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE & $flags) && !self::isHash($value)) { + $output = []; + foreach ($value as $val) { + $output[] = self::dump($val, $flags); + } + + return sprintf('[%s]', implode(', ', $output)); + } + + // hash + $output = []; + foreach ($value as $key => $val) { + $output[] = sprintf('%s: %s', self::dump($key, $flags), self::dump($val, $flags)); + } + + return sprintf('{ %s }', implode(', ', $output)); + } + + private static function dumpNull(int $flags): string + { + if (Yaml::DUMP_NULL_AS_TILDE & $flags) { + return '~'; + } + + return 'null'; + } + + /** + * Parses a YAML scalar. + * + * @return mixed + * + * @throws ParseException When malformed inline YAML string is parsed + */ + public static function parseScalar(string $scalar, int $flags = 0, array $delimiters = null, int &$i = 0, bool $evaluate = true, array &$references = [], bool &$isQuoted = null) + { + if (\in_array($scalar[$i], ['"', "'"])) { + // quoted scalar + $isQuoted = true; + $output = self::parseQuotedScalar($scalar, $i); + + if (null !== $delimiters) { + $tmp = ltrim(substr($scalar, $i), " \n"); + if ('' === $tmp) { + throw new ParseException(sprintf('Unexpected end of line, expected one of "%s".', implode('', $delimiters)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + if (!\in_array($tmp[0], $delimiters)) { + throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + } + } else { + // "normal" string + $isQuoted = false; + + if (!$delimiters) { + $output = substr($scalar, $i); + $i += \strlen($output); + + // remove comments + if (Parser::preg_match('/[ \t]+#/', $output, $match, \PREG_OFFSET_CAPTURE)) { + $output = substr($output, 0, $match[0][1]); + } + } elseif (Parser::preg_match('/^(.*?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) { + $output = $match[1]; + $i += \strlen($output); + $output = trim($output); + } else { + throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $scalar), self::$parsedLineNumber + 1, null, self::$parsedFilename); + } + + // a non-quoted string cannot start with @ or ` (reserved) nor with a scalar indicator (| or >) + if ($output && ('@' === $output[0] || '`' === $output[0] || '|' === $output[0] || '>' === $output[0] || '%' === $output[0])) { + throw new ParseException(sprintf('The reserved indicator "%s" cannot start a plain scalar; you need to quote the scalar.', $output[0]), self::$parsedLineNumber + 1, $output, self::$parsedFilename); + } + + if ($evaluate) { + $output = self::evaluateScalar($output, $flags, $references, $isQuoted); + } + } + + return $output; + } + + /** + * Parses a YAML quoted scalar. + * + * @throws ParseException When malformed inline YAML string is parsed + */ + private static function parseQuotedScalar(string $scalar, int &$i = 0): string + { + if (!Parser::preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) { + throw new ParseException(sprintf('Malformed inline YAML string: "%s".', substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + $output = substr($match[0], 1, \strlen($match[0]) - 2); + + $unescaper = new Unescaper(); + if ('"' == $scalar[$i]) { + $output = $unescaper->unescapeDoubleQuotedString($output); + } else { + $output = $unescaper->unescapeSingleQuotedString($output); + } + + $i += \strlen($match[0]); + + return $output; + } + + /** + * Parses a YAML sequence. + * + * @throws ParseException When malformed inline YAML string is parsed + */ + private static function parseSequence(string $sequence, int $flags, int &$i = 0, array &$references = []): array + { + $output = []; + $len = \strlen($sequence); + ++$i; + + // [foo, bar, ...] + while ($i < $len) { + if (']' === $sequence[$i]) { + return $output; + } + if (',' === $sequence[$i] || ' ' === $sequence[$i]) { + ++$i; + + continue; + } + + $tag = self::parseTag($sequence, $i, $flags); + switch ($sequence[$i]) { + case '[': + // nested sequence + $value = self::parseSequence($sequence, $flags, $i, $references); + break; + case '{': + // nested mapping + $value = self::parseMapping($sequence, $flags, $i, $references); + break; + default: + $value = self::parseScalar($sequence, $flags, [',', ']'], $i, null === $tag, $references, $isQuoted); + + // the value can be an array if a reference has been resolved to an array var + if (\is_string($value) && !$isQuoted && false !== strpos($value, ': ')) { + // embedded mapping? + try { + $pos = 0; + $value = self::parseMapping('{'.$value.'}', $flags, $pos, $references); + } catch (\InvalidArgumentException $e) { + // no, it's not + } + } + + if (!$isQuoted && \is_string($value) && '' !== $value && '&' === $value[0] && Parser::preg_match(Parser::REFERENCE_PATTERN, $value, $matches)) { + $references[$matches['ref']] = $matches['value']; + $value = $matches['value']; + } + + --$i; + } + + if (null !== $tag && '' !== $tag) { + $value = new TaggedValue($tag, $value); + } + + $output[] = $value; + + ++$i; + } + + throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $sequence), self::$parsedLineNumber + 1, null, self::$parsedFilename); + } + + /** + * Parses a YAML mapping. + * + * @return array|\stdClass + * + * @throws ParseException When malformed inline YAML string is parsed + */ + private static function parseMapping(string $mapping, int $flags, int &$i = 0, array &$references = []) + { + $output = []; + $len = \strlen($mapping); + ++$i; + $allowOverwrite = false; + + // {foo: bar, bar:foo, ...} + while ($i < $len) { + switch ($mapping[$i]) { + case ' ': + case ',': + case "\n": + ++$i; + continue 2; + case '}': + if (self::$objectForMap) { + return (object) $output; + } + + return $output; + } + + // key + $offsetBeforeKeyParsing = $i; + $isKeyQuoted = \in_array($mapping[$i], ['"', "'"], true); + $key = self::parseScalar($mapping, $flags, [':', ' '], $i, false); + + if ($offsetBeforeKeyParsing === $i) { + throw new ParseException('Missing mapping key.', self::$parsedLineNumber + 1, $mapping); + } + + if ('!php/const' === $key) { + $key .= ' '.self::parseScalar($mapping, $flags, [':'], $i, false); + $key = self::evaluateScalar($key, $flags); + } + + if (false === $i = strpos($mapping, ':', $i)) { + break; + } + + if (!$isKeyQuoted) { + $evaluatedKey = self::evaluateScalar($key, $flags, $references); + + if ('' !== $key && $evaluatedKey !== $key && !\is_string($evaluatedKey) && !\is_int($evaluatedKey)) { + throw new ParseException('Implicit casting of incompatible mapping keys to strings is not supported. Quote your evaluable mapping keys instead.', self::$parsedLineNumber + 1, $mapping); + } + } + + if (!$isKeyQuoted && (!isset($mapping[$i + 1]) || !\in_array($mapping[$i + 1], [' ', ',', '[', ']', '{', '}', "\n"], true))) { + throw new ParseException('Colons must be followed by a space or an indication character (i.e. " ", ",", "[", "]", "{", "}").', self::$parsedLineNumber + 1, $mapping); + } + + if ('<<' === $key) { + $allowOverwrite = true; + } + + while ($i < $len) { + if (':' === $mapping[$i] || ' ' === $mapping[$i] || "\n" === $mapping[$i]) { + ++$i; + + continue; + } + + $tag = self::parseTag($mapping, $i, $flags); + switch ($mapping[$i]) { + case '[': + // nested sequence + $value = self::parseSequence($mapping, $flags, $i, $references); + // Spec: Keys MUST be unique; first one wins. + // Parser cannot abort this mapping earlier, since lines + // are processed sequentially. + // But overwriting is allowed when a merge node is used in current block. + if ('<<' === $key) { + foreach ($value as $parsedValue) { + $output += $parsedValue; + } + } elseif ($allowOverwrite || !isset($output[$key])) { + if (null !== $tag) { + $output[$key] = new TaggedValue($tag, $value); + } else { + $output[$key] = $value; + } + } elseif (isset($output[$key])) { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping); + } + break; + case '{': + // nested mapping + $value = self::parseMapping($mapping, $flags, $i, $references); + // Spec: Keys MUST be unique; first one wins. + // Parser cannot abort this mapping earlier, since lines + // are processed sequentially. + // But overwriting is allowed when a merge node is used in current block. + if ('<<' === $key) { + $output += $value; + } elseif ($allowOverwrite || !isset($output[$key])) { + if (null !== $tag) { + $output[$key] = new TaggedValue($tag, $value); + } else { + $output[$key] = $value; + } + } elseif (isset($output[$key])) { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping); + } + break; + default: + $value = self::parseScalar($mapping, $flags, [',', '}', "\n"], $i, null === $tag, $references, $isValueQuoted); + // Spec: Keys MUST be unique; first one wins. + // Parser cannot abort this mapping earlier, since lines + // are processed sequentially. + // But overwriting is allowed when a merge node is used in current block. + if ('<<' === $key) { + $output += $value; + } elseif ($allowOverwrite || !isset($output[$key])) { + if (!$isValueQuoted && \is_string($value) && '' !== $value && '&' === $value[0] && Parser::preg_match(Parser::REFERENCE_PATTERN, $value, $matches)) { + $references[$matches['ref']] = $matches['value']; + $value = $matches['value']; + } + + if (null !== $tag) { + $output[$key] = new TaggedValue($tag, $value); + } else { + $output[$key] = $value; + } + } elseif (isset($output[$key])) { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping); + } + --$i; + } + ++$i; + + continue 2; + } + } + + throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $mapping), self::$parsedLineNumber + 1, null, self::$parsedFilename); + } + + /** + * Evaluates scalars and replaces magic values. + * + * @return mixed The evaluated YAML string + * + * @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved + */ + private static function evaluateScalar(string $scalar, int $flags, array &$references = [], bool &$isQuotedString = null) + { + $isQuotedString = false; + $scalar = trim($scalar); + $scalarLower = strtolower($scalar); + + if (0 === strpos($scalar, '*')) { + if (false !== $pos = strpos($scalar, '#')) { + $value = substr($scalar, 1, $pos - 2); + } else { + $value = substr($scalar, 1); + } + + // an unquoted * + if (false === $value || '' === $value) { + throw new ParseException('A reference must contain at least one character.', self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + if (!\array_key_exists($value, $references)) { + throw new ParseException(sprintf('Reference "%s" does not exist.', $value), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + return $references[$value]; + } + + switch (true) { + case 'null' === $scalarLower: + case '' === $scalar: + case '~' === $scalar: + return null; + case 'true' === $scalarLower: + return true; + case 'false' === $scalarLower: + return false; + case '!' === $scalar[0]: + switch (true) { + case 0 === strpos($scalar, '!!str '): + $s = (string) substr($scalar, 6); + + if (\in_array($s[0] ?? '', ['"', "'"], true)) { + $isQuotedString = true; + $s = self::parseQuotedScalar($s); + } + + return $s; + case 0 === strpos($scalar, '! '): + return substr($scalar, 2); + case 0 === strpos($scalar, '!php/object'): + if (self::$objectSupport) { + if (!isset($scalar[12])) { + return false; + } + + return unserialize(self::parseScalar(substr($scalar, 12))); + } + + if (self::$exceptionOnInvalidType) { + throw new ParseException('Object support when parsing a YAML file has been disabled.', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + return null; + case 0 === strpos($scalar, '!php/const'): + if (self::$constantSupport) { + if (!isset($scalar[11])) { + return ''; + } + + $i = 0; + if (\defined($const = self::parseScalar(substr($scalar, 11), 0, null, $i, false))) { + return \constant($const); + } + + throw new ParseException(sprintf('The constant "%s" is not defined.', $const), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + if (self::$exceptionOnInvalidType) { + throw new ParseException(sprintf('The string "%s" could not be parsed as a constant. Did you forget to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + return null; + case 0 === strpos($scalar, '!!float '): + return (float) substr($scalar, 8); + case 0 === strpos($scalar, '!!binary '): + return self::evaluateBinaryScalar(substr($scalar, 9)); + default: + throw new ParseException(sprintf('The string "%s" could not be parsed as it uses an unsupported built-in tag.', $scalar), self::$parsedLineNumber, $scalar, self::$parsedFilename); + } + + // Optimize for returning strings. + // no break + case '+' === $scalar[0] || '-' === $scalar[0] || '.' === $scalar[0] || is_numeric($scalar[0]): + if (Parser::preg_match('{^[+-]?[0-9][0-9_]*$}', $scalar)) { + $scalar = str_replace('_', '', $scalar); + } + + switch (true) { + case ctype_digit($scalar): + if (preg_match('/^0[0-7]+$/', $scalar)) { + return octdec($scalar); + } + + $cast = (int) $scalar; + + return ($scalar === (string) $cast) ? $cast : $scalar; + case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)): + if (preg_match('/^-0[0-7]+$/', $scalar)) { + return -octdec(substr($scalar, 1)); + } + + $cast = (int) $scalar; + + return ($scalar === (string) $cast) ? $cast : $scalar; + case is_numeric($scalar): + case Parser::preg_match(self::getHexRegex(), $scalar): + $scalar = str_replace('_', '', $scalar); + + return '0x' === $scalar[0].$scalar[1] ? hexdec($scalar) : (float) $scalar; + case '.inf' === $scalarLower: + case '.nan' === $scalarLower: + return -log(0); + case '-.inf' === $scalarLower: + return log(0); + case Parser::preg_match('/^(-|\+)?[0-9][0-9_]*(\.[0-9_]+)?$/', $scalar): + return (float) str_replace('_', '', $scalar); + case Parser::preg_match(self::getTimestampRegex(), $scalar): + // When no timezone is provided in the parsed date, YAML spec says we must assume UTC. + $time = new \DateTime($scalar, new \DateTimeZone('UTC')); + + if (Yaml::PARSE_DATETIME & $flags) { + return $time; + } + + try { + if (false !== $scalar = $time->getTimestamp()) { + return $scalar; + } + } catch (\ValueError $e) { + // no-op + } + + return $time->format('U'); + } + } + + return (string) $scalar; + } + + private static function parseTag(string $value, int &$i, int $flags): ?string + { + if ('!' !== $value[$i]) { + return null; + } + + $tagLength = strcspn($value, " \t\n[]{},", $i + 1); + $tag = substr($value, $i + 1, $tagLength); + + $nextOffset = $i + $tagLength + 1; + $nextOffset += strspn($value, ' ', $nextOffset); + + if ('' === $tag && (!isset($value[$nextOffset]) || \in_array($value[$nextOffset], [']', '}', ','], true))) { + throw new ParseException('Using the unquoted scalar value "!" is not supported. You must quote it.', self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + // Is followed by a scalar and is a built-in tag + if ('' !== $tag && (!isset($value[$nextOffset]) || !\in_array($value[$nextOffset], ['[', '{'], true)) && ('!' === $tag[0] || 'str' === $tag || 'php/const' === $tag || 'php/object' === $tag)) { + // Manage in {@link self::evaluateScalar()} + return null; + } + + $i = $nextOffset; + + // Built-in tags + if ('' !== $tag && '!' === $tag[0]) { + throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + if ('' !== $tag && !isset($value[$i])) { + throw new ParseException(sprintf('Missing value for tag "%s".', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + if ('' === $tag || Yaml::PARSE_CUSTOM_TAGS & $flags) { + return $tag; + } + + throw new ParseException(sprintf('Tags support is not enabled. Enable the "Yaml::PARSE_CUSTOM_TAGS" flag to use "!%s".', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + public static function evaluateBinaryScalar(string $scalar): string + { + $parsedBinaryData = self::parseScalar(preg_replace('/\s/', '', $scalar)); + + if (0 !== (\strlen($parsedBinaryData) % 4)) { + throw new ParseException(sprintf('The normalized base64 encoded data (data without whitespace characters) length must be a multiple of four (%d bytes given).', \strlen($parsedBinaryData)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + if (!Parser::preg_match('#^[A-Z0-9+/]+={0,2}$#i', $parsedBinaryData)) { + throw new ParseException(sprintf('The base64 encoded data (%s) contains invalid characters.', $parsedBinaryData), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + return base64_decode($parsedBinaryData, true); + } + + private static function isBinaryString(string $value) + { + return !preg_match('//u', $value) || preg_match('/[^\x00\x07-\x0d\x1B\x20-\xff]/', $value); + } + + /** + * Gets a regex that matches a YAML date. + * + * @return string The regular expression + * + * @see http://www.yaml.org/spec/1.2/spec.html#id2761573 + */ + private static function getTimestampRegex(): string + { + return <<<EOF + ~^ + (?P<year>[0-9][0-9][0-9][0-9]) + -(?P<month>[0-9][0-9]?) + -(?P<day>[0-9][0-9]?) + (?:(?:[Tt]|[ \t]+) + (?P<hour>[0-9][0-9]?) + :(?P<minute>[0-9][0-9]) + :(?P<second>[0-9][0-9]) + (?:\.(?P<fraction>[0-9]*))? + (?:[ \t]*(?P<tz>Z|(?P<tz_sign>[-+])(?P<tz_hour>[0-9][0-9]?) + (?::(?P<tz_minute>[0-9][0-9]))?))?)? + $~x +EOF; + } + + /** + * Gets a regex that matches a YAML number in hexadecimal notation. + */ + private static function getHexRegex(): string + { + return '~^0x[0-9a-f_]++$~i'; + } +} diff --git a/vendor/symfony/yaml/LICENSE b/vendor/symfony/yaml/LICENSE new file mode 100644 index 0000000000..88bf75bb4d --- /dev/null +++ b/vendor/symfony/yaml/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/yaml/Parser.php b/vendor/symfony/yaml/Parser.php new file mode 100644 index 0000000000..5c385f4fa7 --- /dev/null +++ b/vendor/symfony/yaml/Parser.php @@ -0,0 +1,1317 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Tag\TaggedValue; + +/** + * Parser parses YAML strings to convert them to PHP arrays. + * + * @author Fabien Potencier <fabien@symfony.com> + * + * @final + */ +class Parser +{ + public const TAG_PATTERN = '(?P<tag>![\w!.\/:-]+)'; + public const BLOCK_SCALAR_HEADER_PATTERN = '(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?'; + public const REFERENCE_PATTERN = '#^&(?P<ref>[^ ]++) *+(?P<value>.*)#u'; + + private $filename; + private $offset = 0; + private $totalNumberOfLines; + private $lines = []; + private $currentLineNb = -1; + private $currentLine = ''; + private $refs = []; + private $skippedLineNumbers = []; + private $locallySkippedLineNumbers = []; + private $refsBeingParsed = []; + + /** + * Parses a YAML file into a PHP value. + * + * @param string $filename The path to the YAML file to be parsed + * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior + * + * @return mixed The YAML converted to a PHP value + * + * @throws ParseException If the file could not be read or the YAML is not valid + */ + public function parseFile(string $filename, int $flags = 0) + { + if (!is_file($filename)) { + throw new ParseException(sprintf('File "%s" does not exist.', $filename)); + } + + if (!is_readable($filename)) { + throw new ParseException(sprintf('File "%s" cannot be read.', $filename)); + } + + $this->filename = $filename; + + try { + return $this->parse(file_get_contents($filename), $flags); + } finally { + $this->filename = null; + } + } + + /** + * Parses a YAML string to a PHP value. + * + * @param string $value A YAML string + * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior + * + * @return mixed A PHP value + * + * @throws ParseException If the YAML is not valid + */ + public function parse(string $value, int $flags = 0) + { + if (false === preg_match('//u', $value)) { + throw new ParseException('The YAML value does not appear to be valid UTF-8.', -1, null, $this->filename); + } + + $this->refs = []; + + $mbEncoding = null; + + if (2 /* MB_OVERLOAD_STRING */ & (int) \ini_get('mbstring.func_overload')) { + $mbEncoding = mb_internal_encoding(); + mb_internal_encoding('UTF-8'); + } + + try { + $data = $this->doParse($value, $flags); + } finally { + if (null !== $mbEncoding) { + mb_internal_encoding($mbEncoding); + } + $this->refsBeingParsed = []; + $this->offset = 0; + $this->lines = []; + $this->currentLine = ''; + $this->refs = []; + $this->skippedLineNumbers = []; + $this->locallySkippedLineNumbers = []; + $this->totalNumberOfLines = null; + } + + return $data; + } + + private function doParse(string $value, int $flags) + { + $this->currentLineNb = -1; + $this->currentLine = ''; + $value = $this->cleanup($value); + $this->lines = explode("\n", $value); + $this->locallySkippedLineNumbers = []; + + if (null === $this->totalNumberOfLines) { + $this->totalNumberOfLines = \count($this->lines); + } + + if (!$this->moveToNextLine()) { + return null; + } + + $data = []; + $context = null; + $allowOverwrite = false; + + while ($this->isCurrentLineEmpty()) { + if (!$this->moveToNextLine()) { + return null; + } + } + + // Resolves the tag and returns if end of the document + if (null !== ($tag = $this->getLineTag($this->currentLine, $flags, false)) && !$this->moveToNextLine()) { + return new TaggedValue($tag, ''); + } + + do { + if ($this->isCurrentLineEmpty()) { + continue; + } + + // tab? + if ("\t" === $this->currentLine[0]) { + throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + Inline::initialize($flags, $this->getRealCurrentLineNb(), $this->filename); + + $isRef = $mergeNode = false; + if ('-' === $this->currentLine[0] && self::preg_match('#^\-((?P<leadspaces>\s+)(?P<value>.+))?$#u', rtrim($this->currentLine), $values)) { + if ($context && 'mapping' == $context) { + throw new ParseException('You cannot define a sequence item when in a mapping.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + $context = 'sequence'; + + if (isset($values['value']) && '&' === $values['value'][0] && self::preg_match(self::REFERENCE_PATTERN, $values['value'], $matches)) { + $isRef = $matches['ref']; + $this->refsBeingParsed[] = $isRef; + $values['value'] = $matches['value']; + } + + if (isset($values['value'][1]) && '?' === $values['value'][0] && ' ' === $values['value'][1]) { + throw new ParseException('Complex mappings are not supported.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + // array + if (isset($values['value']) && 0 === strpos(ltrim($values['value'], ' '), '-')) { + // Inline first child + $currentLineNumber = $this->getRealCurrentLineNb(); + + $sequenceIndentation = \strlen($values['leadspaces']) + 1; + $sequenceYaml = substr($this->currentLine, $sequenceIndentation); + $sequenceYaml .= "\n".$this->getNextEmbedBlock($sequenceIndentation, true); + + $data[] = $this->parseBlock($currentLineNumber, rtrim($sequenceYaml), $flags); + } elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) { + $data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true) ?? '', $flags); + } elseif (null !== $subTag = $this->getLineTag(ltrim($values['value'], ' '), $flags)) { + $data[] = new TaggedValue( + $subTag, + $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $flags) + ); + } else { + if ( + isset($values['leadspaces']) + && ( + '!' === $values['value'][0] + || self::preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P<value>.+?))?\s*$#u', $this->trimTag($values['value']), $matches) + ) + ) { + // this is a compact notation element, add to next block and parse + $block = $values['value']; + if ($this->isNextLineIndented()) { + $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + \strlen($values['leadspaces']) + 1); + } + + $data[] = $this->parseBlock($this->getRealCurrentLineNb(), $block, $flags); + } else { + $data[] = $this->parseValue($values['value'], $flags, $context); + } + } + if ($isRef) { + $this->refs[$isRef] = end($data); + array_pop($this->refsBeingParsed); + } + } elseif ( + self::preg_match('#^(?P<key>(?:![^\s]++\s++)?(?:'.Inline::REGEX_QUOTED_STRING.'|(?:!?!php/const:)?[^ \'"\[\{!].*?)) *\:(( |\t)++(?P<value>.+))?$#u', rtrim($this->currentLine), $values) + && (false === strpos($values['key'], ' #') || \in_array($values['key'][0], ['"', "'"])) + ) { + if ($context && 'sequence' == $context) { + throw new ParseException('You cannot define a mapping item when in a sequence.', $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + $context = 'mapping'; + + try { + $key = Inline::parseScalar($values['key']); + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + + if (!\is_string($key) && !\is_int($key)) { + throw new ParseException((is_numeric($key) ? 'Numeric' : 'Non-string').' keys are not supported. Quote your evaluable mapping keys instead.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + // Convert float keys to strings, to avoid being converted to integers by PHP + if (\is_float($key)) { + $key = (string) $key; + } + + if ('<<' === $key && (!isset($values['value']) || '&' !== $values['value'][0] || !self::preg_match('#^&(?P<ref>[^ ]+)#u', $values['value'], $refMatches))) { + $mergeNode = true; + $allowOverwrite = true; + if (isset($values['value'][0]) && '*' === $values['value'][0]) { + $refName = substr(rtrim($values['value']), 1); + if (!\array_key_exists($refName, $this->refs)) { + if (false !== $pos = array_search($refName, $this->refsBeingParsed, true)) { + throw new ParseException(sprintf('Circular reference [%s] detected for reference "%s".', implode(', ', array_merge(\array_slice($this->refsBeingParsed, $pos), [$refName])), $refName), $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + + throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + $refValue = $this->refs[$refName]; + + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $refValue instanceof \stdClass) { + $refValue = (array) $refValue; + } + + if (!\is_array($refValue)) { + throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + $data += $refValue; // array union + } else { + if (isset($values['value']) && '' !== $values['value']) { + $value = $values['value']; + } else { + $value = $this->getNextEmbedBlock(); + } + $parsed = $this->parseBlock($this->getRealCurrentLineNb() + 1, $value, $flags); + + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsed instanceof \stdClass) { + $parsed = (array) $parsed; + } + + if (!\is_array($parsed)) { + throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + if (isset($parsed[0])) { + // If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes + // and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier + // in the sequence override keys specified in later mapping nodes. + foreach ($parsed as $parsedItem) { + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsedItem instanceof \stdClass) { + $parsedItem = (array) $parsedItem; + } + + if (!\is_array($parsedItem)) { + throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem, $this->filename); + } + + $data += $parsedItem; // array union + } + } else { + // If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the + // current mapping, unless the key already exists in it. + $data += $parsed; // array union + } + } + } elseif ('<<' !== $key && isset($values['value']) && '&' === $values['value'][0] && self::preg_match(self::REFERENCE_PATTERN, $values['value'], $matches)) { + $isRef = $matches['ref']; + $this->refsBeingParsed[] = $isRef; + $values['value'] = $matches['value']; + } + + $subTag = null; + if ($mergeNode) { + // Merge keys + } elseif (!isset($values['value']) || '' === $values['value'] || 0 === strpos($values['value'], '#') || (null !== $subTag = $this->getLineTag($values['value'], $flags)) || '<<' === $key) { + // hash + // if next line is less indented or equal, then it means that the current value is null + if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) { + // Spec: Keys MUST be unique; first one wins. + // But overwriting is allowed when a merge node is used in current block. + if ($allowOverwrite || !isset($data[$key])) { + if (null !== $subTag) { + $data[$key] = new TaggedValue($subTag, ''); + } else { + $data[$key] = null; + } + } else { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + } else { + // remember the parsed line number here in case we need it to provide some contexts in error messages below + $realCurrentLineNbKey = $this->getRealCurrentLineNb(); + $value = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(), $flags); + if ('<<' === $key) { + $this->refs[$refMatches['ref']] = $value; + + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $value instanceof \stdClass) { + $value = (array) $value; + } + + $data += $value; + } elseif ($allowOverwrite || !isset($data[$key])) { + // Spec: Keys MUST be unique; first one wins. + // But overwriting is allowed when a merge node is used in current block. + if (null !== $subTag) { + $data[$key] = new TaggedValue($subTag, $value); + } else { + $data[$key] = $value; + } + } else { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), $realCurrentLineNbKey + 1, $this->currentLine); + } + } + } else { + $value = $this->parseValue(rtrim($values['value']), $flags, $context); + // Spec: Keys MUST be unique; first one wins. + // But overwriting is allowed when a merge node is used in current block. + if ($allowOverwrite || !isset($data[$key])) { + $data[$key] = $value; + } else { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + } + if ($isRef) { + $this->refs[$isRef] = $data[$key]; + array_pop($this->refsBeingParsed); + } + } elseif ('"' === $this->currentLine[0] || "'" === $this->currentLine[0]) { + if (null !== $context) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + try { + return Inline::parse($this->lexInlineQuotedString(), $flags, $this->refs); + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + } elseif ('{' === $this->currentLine[0]) { + if (null !== $context) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + try { + $parsedMapping = Inline::parse($this->lexInlineMapping(), $flags, $this->refs); + + while ($this->moveToNextLine()) { + if (!$this->isCurrentLineEmpty()) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } + + return $parsedMapping; + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + } elseif ('[' === $this->currentLine[0]) { + if (null !== $context) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + try { + $parsedSequence = Inline::parse($this->lexInlineSequence(), $flags, $this->refs); + + while ($this->moveToNextLine()) { + if (!$this->isCurrentLineEmpty()) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } + + return $parsedSequence; + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + } else { + // multiple documents are not supported + if ('---' === $this->currentLine) { + throw new ParseException('Multiple documents are not supported.', $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + + if ($deprecatedUsage = (isset($this->currentLine[1]) && '?' === $this->currentLine[0] && ' ' === $this->currentLine[1])) { + throw new ParseException('Complex mappings are not supported.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + // 1-liner optionally followed by newline(s) + if (\is_string($value) && $this->lines[0] === trim($value)) { + try { + $value = Inline::parse($this->lines[0], $flags, $this->refs); + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + + return $value; + } + + // try to parse the value as a multi-line string as a last resort + if (0 === $this->currentLineNb) { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = false; + $value = ''; + + foreach ($this->lines as $line) { + if ('' !== ltrim($line) && '#' === ltrim($line)[0]) { + continue; + } + // If the indentation is not consistent at offset 0, it is to be considered as a ParseError + if (0 === $this->offset && !$deprecatedUsage && isset($line[0]) && ' ' === $line[0]) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + if (false !== strpos($line, ': ')) { + @trigger_error('Support for mapping keys in multi-line blocks is deprecated since Symfony 4.3 and will throw a ParseException in 5.0.', \E_USER_DEPRECATED); + } + + if ('' === trim($line)) { + $value .= "\n"; + } elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) { + $value .= ' '; + } + + if ('' !== trim($line) && '\\' === substr($line, -1)) { + $value .= ltrim(substr($line, 0, -1)); + } elseif ('' !== trim($line)) { + $value .= trim($line); + } + + if ('' === trim($line)) { + $previousLineWasNewline = true; + $previousLineWasTerminatedWithBackslash = false; + } elseif ('\\' === substr($line, -1)) { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = true; + } else { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = false; + } + } + + try { + return Inline::parse(trim($value)); + } catch (ParseException $e) { + // fall-through to the ParseException thrown below + } + } + + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } while ($this->moveToNextLine()); + + if (null !== $tag) { + $data = new TaggedValue($tag, $data); + } + + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && !\is_object($data) && 'mapping' === $context) { + $object = new \stdClass(); + + foreach ($data as $key => $value) { + $object->$key = $value; + } + + $data = $object; + } + + return empty($data) ? null : $data; + } + + private function parseBlock(int $offset, string $yaml, int $flags) + { + $skippedLineNumbers = $this->skippedLineNumbers; + + foreach ($this->locallySkippedLineNumbers as $lineNumber) { + if ($lineNumber < $offset) { + continue; + } + + $skippedLineNumbers[] = $lineNumber; + } + + $parser = new self(); + $parser->offset = $offset; + $parser->totalNumberOfLines = $this->totalNumberOfLines; + $parser->skippedLineNumbers = $skippedLineNumbers; + $parser->refs = &$this->refs; + $parser->refsBeingParsed = $this->refsBeingParsed; + + return $parser->doParse($yaml, $flags); + } + + /** + * Returns the current line number (takes the offset into account). + * + * @internal + * + * @return int The current line number + */ + public function getRealCurrentLineNb(): int + { + $realCurrentLineNumber = $this->currentLineNb + $this->offset; + + foreach ($this->skippedLineNumbers as $skippedLineNumber) { + if ($skippedLineNumber > $realCurrentLineNumber) { + break; + } + + ++$realCurrentLineNumber; + } + + return $realCurrentLineNumber; + } + + /** + * Returns the current line indentation. + * + * @return int The current line indentation + */ + private function getCurrentLineIndentation(): int + { + return \strlen($this->currentLine) - \strlen(ltrim($this->currentLine, ' ')); + } + + /** + * Returns the next embed block of YAML. + * + * @param int|null $indentation The indent level at which the block is to be read, or null for default + * @param bool $inSequence True if the enclosing data structure is a sequence + * + * @return string A YAML string + * + * @throws ParseException When indentation problem are detected + */ + private function getNextEmbedBlock(int $indentation = null, bool $inSequence = false): string + { + $oldLineIndentation = $this->getCurrentLineIndentation(); + + if (!$this->moveToNextLine()) { + return ''; + } + + if (null === $indentation) { + $newIndent = null; + $movements = 0; + + do { + $EOF = false; + + // empty and comment-like lines do not influence the indentation depth + if ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) { + $EOF = !$this->moveToNextLine(); + + if (!$EOF) { + ++$movements; + } + } else { + $newIndent = $this->getCurrentLineIndentation(); + } + } while (!$EOF && null === $newIndent); + + for ($i = 0; $i < $movements; ++$i) { + $this->moveToPreviousLine(); + } + + $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem(); + + if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) { + throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } else { + $newIndent = $indentation; + } + + $data = []; + + if ($this->getCurrentLineIndentation() >= $newIndent) { + $data[] = substr($this->currentLine, $newIndent ?? 0); + } elseif ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) { + $data[] = $this->currentLine; + } else { + $this->moveToPreviousLine(); + + return ''; + } + + if ($inSequence && $oldLineIndentation === $newIndent && isset($data[0][0]) && '-' === $data[0][0]) { + // the previous line contained a dash but no item content, this line is a sequence item with the same indentation + // and therefore no nested list or mapping + $this->moveToPreviousLine(); + + return ''; + } + + $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem(); + $isItComment = $this->isCurrentLineComment(); + + while ($this->moveToNextLine()) { + if ($isItComment && !$isItUnindentedCollection) { + $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem(); + $isItComment = $this->isCurrentLineComment(); + } + + $indent = $this->getCurrentLineIndentation(); + + if ($isItUnindentedCollection && !$this->isCurrentLineEmpty() && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) { + $this->moveToPreviousLine(); + break; + } + + if ($this->isCurrentLineBlank()) { + $data[] = substr($this->currentLine, $newIndent); + continue; + } + + if ($indent >= $newIndent) { + $data[] = substr($this->currentLine, $newIndent); + } elseif ($this->isCurrentLineComment()) { + $data[] = $this->currentLine; + } elseif (0 == $indent) { + $this->moveToPreviousLine(); + + break; + } else { + throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } + + return implode("\n", $data); + } + + private function hasMoreLines(): bool + { + return (\count($this->lines) - 1) > $this->currentLineNb; + } + + /** + * Moves the parser to the next line. + */ + private function moveToNextLine(): bool + { + if ($this->currentLineNb >= \count($this->lines) - 1) { + return false; + } + + $this->currentLine = $this->lines[++$this->currentLineNb]; + + return true; + } + + /** + * Moves the parser to the previous line. + */ + private function moveToPreviousLine(): bool + { + if ($this->currentLineNb < 1) { + return false; + } + + $this->currentLine = $this->lines[--$this->currentLineNb]; + + return true; + } + + /** + * Parses a YAML value. + * + * @param string $value A YAML value + * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior + * @param string $context The parser context (either sequence or mapping) + * + * @return mixed A PHP value + * + * @throws ParseException When reference does not exist + */ + private function parseValue(string $value, int $flags, string $context) + { + if (0 === strpos($value, '*')) { + if (false !== $pos = strpos($value, '#')) { + $value = substr($value, 1, $pos - 2); + } else { + $value = substr($value, 1); + } + + if (!\array_key_exists($value, $this->refs)) { + if (false !== $pos = array_search($value, $this->refsBeingParsed, true)) { + throw new ParseException(sprintf('Circular reference [%s] detected for reference "%s".', implode(', ', array_merge(\array_slice($this->refsBeingParsed, $pos), [$value])), $value), $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + + throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + + return $this->refs[$value]; + } + + if (\in_array($value[0], ['!', '|', '>'], true) && self::preg_match('/^(?:'.self::TAG_PATTERN.' +)?'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, $matches)) { + $modifiers = $matches['modifiers'] ?? ''; + + $data = $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), abs((int) $modifiers)); + + if ('' !== $matches['tag'] && '!' !== $matches['tag']) { + if ('!!binary' === $matches['tag']) { + return Inline::evaluateBinaryScalar($data); + } + + return new TaggedValue(substr($matches['tag'], 1), $data); + } + + return $data; + } + + try { + if ('' !== $value && '{' === $value[0]) { + $cursor = \strlen(rtrim($this->currentLine)) - \strlen(rtrim($value)); + + return Inline::parse($this->lexInlineMapping($cursor), $flags, $this->refs); + } elseif ('' !== $value && '[' === $value[0]) { + $cursor = \strlen(rtrim($this->currentLine)) - \strlen(rtrim($value)); + + return Inline::parse($this->lexInlineSequence($cursor), $flags, $this->refs); + } + + switch ($value[0] ?? '') { + case '"': + case "'": + $cursor = \strlen(rtrim($this->currentLine)) - \strlen(rtrim($value)); + $parsedValue = Inline::parse($this->lexInlineQuotedString($cursor), $flags, $this->refs); + + if (isset($this->currentLine[$cursor]) && preg_replace('/\s*(#.*)?$/A', '', substr($this->currentLine, $cursor))) { + throw new ParseException(sprintf('Unexpected characters near "%s".', substr($this->currentLine, $cursor))); + } + + return $parsedValue; + default: + $lines = []; + + while ($this->moveToNextLine()) { + // unquoted strings end before the first unindented line + if (0 === $this->getCurrentLineIndentation()) { + $this->moveToPreviousLine(); + + break; + } + + $lines[] = trim($this->currentLine); + } + + for ($i = 0, $linesCount = \count($lines), $previousLineBlank = false; $i < $linesCount; ++$i) { + if ('' === $lines[$i]) { + $value .= "\n"; + $previousLineBlank = true; + } elseif ($previousLineBlank) { + $value .= $lines[$i]; + $previousLineBlank = false; + } else { + $value .= ' '.$lines[$i]; + $previousLineBlank = false; + } + } + + Inline::$parsedLineNumber = $this->getRealCurrentLineNb(); + + $parsedValue = Inline::parse($value, $flags, $this->refs); + + if ('mapping' === $context && \is_string($parsedValue) && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) { + throw new ParseException('A colon cannot be used in an unquoted mapping value.', $this->getRealCurrentLineNb() + 1, $value, $this->filename); + } + + return $parsedValue; + } + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + } + + /** + * Parses a block scalar. + * + * @param string $style The style indicator that was used to begin this block scalar (| or >) + * @param string $chomping The chomping indicator that was used to begin this block scalar (+ or -) + * @param int $indentation The indentation indicator that was used to begin this block scalar + */ + private function parseBlockScalar(string $style, string $chomping = '', int $indentation = 0): string + { + $notEOF = $this->moveToNextLine(); + if (!$notEOF) { + return ''; + } + + $isCurrentLineBlank = $this->isCurrentLineBlank(); + $blockLines = []; + + // leading blank lines are consumed before determining indentation + while ($notEOF && $isCurrentLineBlank) { + // newline only if not EOF + if ($notEOF = $this->moveToNextLine()) { + $blockLines[] = ''; + $isCurrentLineBlank = $this->isCurrentLineBlank(); + } + } + + // determine indentation if not specified + if (0 === $indentation) { + $currentLineLength = \strlen($this->currentLine); + + for ($i = 0; $i < $currentLineLength && ' ' === $this->currentLine[$i]; ++$i) { + ++$indentation; + } + } + + if ($indentation > 0) { + $pattern = sprintf('/^ {%d}(.*)$/', $indentation); + + while ( + $notEOF && ( + $isCurrentLineBlank || + self::preg_match($pattern, $this->currentLine, $matches) + ) + ) { + if ($isCurrentLineBlank && \strlen($this->currentLine) > $indentation) { + $blockLines[] = substr($this->currentLine, $indentation); + } elseif ($isCurrentLineBlank) { + $blockLines[] = ''; + } else { + $blockLines[] = $matches[1]; + } + + // newline only if not EOF + if ($notEOF = $this->moveToNextLine()) { + $isCurrentLineBlank = $this->isCurrentLineBlank(); + } + } + } elseif ($notEOF) { + $blockLines[] = ''; + } + + if ($notEOF) { + $blockLines[] = ''; + $this->moveToPreviousLine(); + } elseif (!$notEOF && !$this->isCurrentLineLastLineInDocument()) { + $blockLines[] = ''; + } + + // folded style + if ('>' === $style) { + $text = ''; + $previousLineIndented = false; + $previousLineBlank = false; + + for ($i = 0, $blockLinesCount = \count($blockLines); $i < $blockLinesCount; ++$i) { + if ('' === $blockLines[$i]) { + $text .= "\n"; + $previousLineIndented = false; + $previousLineBlank = true; + } elseif (' ' === $blockLines[$i][0]) { + $text .= "\n".$blockLines[$i]; + $previousLineIndented = true; + $previousLineBlank = false; + } elseif ($previousLineIndented) { + $text .= "\n".$blockLines[$i]; + $previousLineIndented = false; + $previousLineBlank = false; + } elseif ($previousLineBlank || 0 === $i) { + $text .= $blockLines[$i]; + $previousLineIndented = false; + $previousLineBlank = false; + } else { + $text .= ' '.$blockLines[$i]; + $previousLineIndented = false; + $previousLineBlank = false; + } + } + } else { + $text = implode("\n", $blockLines); + } + + // deal with trailing newlines + if ('' === $chomping) { + $text = preg_replace('/\n+$/', "\n", $text); + } elseif ('-' === $chomping) { + $text = preg_replace('/\n+$/', '', $text); + } + + return $text; + } + + /** + * Returns true if the next line is indented. + * + * @return bool Returns true if the next line is indented, false otherwise + */ + private function isNextLineIndented(): bool + { + $currentIndentation = $this->getCurrentLineIndentation(); + $movements = 0; + + do { + $EOF = !$this->moveToNextLine(); + + if (!$EOF) { + ++$movements; + } + } while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment())); + + if ($EOF) { + return false; + } + + $ret = $this->getCurrentLineIndentation() > $currentIndentation; + + for ($i = 0; $i < $movements; ++$i) { + $this->moveToPreviousLine(); + } + + return $ret; + } + + /** + * Returns true if the current line is blank or if it is a comment line. + * + * @return bool Returns true if the current line is empty or if it is a comment line, false otherwise + */ + private function isCurrentLineEmpty(): bool + { + return $this->isCurrentLineBlank() || $this->isCurrentLineComment(); + } + + /** + * Returns true if the current line is blank. + * + * @return bool Returns true if the current line is blank, false otherwise + */ + private function isCurrentLineBlank(): bool + { + return '' == trim($this->currentLine, ' '); + } + + /** + * Returns true if the current line is a comment line. + * + * @return bool Returns true if the current line is a comment line, false otherwise + */ + private function isCurrentLineComment(): bool + { + // checking explicitly the first char of the trim is faster than loops or strpos + $ltrimmedLine = ltrim($this->currentLine, ' '); + + return '' !== $ltrimmedLine && '#' === $ltrimmedLine[0]; + } + + private function isCurrentLineLastLineInDocument(): bool + { + return ($this->offset + $this->currentLineNb) >= ($this->totalNumberOfLines - 1); + } + + /** + * Cleanups a YAML string to be parsed. + * + * @param string $value The input YAML string + * + * @return string A cleaned up YAML string + */ + private function cleanup(string $value): string + { + $value = str_replace(["\r\n", "\r"], "\n", $value); + + // strip YAML header + $count = 0; + $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#u', '', $value, -1, $count); + $this->offset += $count; + + // remove leading comments + $trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count); + if (1 === $count) { + // items have been removed, update the offset + $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); + $value = $trimmedValue; + } + + // remove start of the document marker (---) + $trimmedValue = preg_replace('#^\-\-\-.*?\n#s', '', $value, -1, $count); + if (1 === $count) { + // items have been removed, update the offset + $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); + $value = $trimmedValue; + + // remove end of the document marker (...) + $value = preg_replace('#\.\.\.\s*$#', '', $value); + } + + return $value; + } + + /** + * Returns true if the next line starts unindented collection. + * + * @return bool Returns true if the next line starts unindented collection, false otherwise + */ + private function isNextLineUnIndentedCollection(): bool + { + $currentIndentation = $this->getCurrentLineIndentation(); + $movements = 0; + + do { + $EOF = !$this->moveToNextLine(); + + if (!$EOF) { + ++$movements; + } + } while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment())); + + if ($EOF) { + return false; + } + + $ret = $this->getCurrentLineIndentation() === $currentIndentation && $this->isStringUnIndentedCollectionItem(); + + for ($i = 0; $i < $movements; ++$i) { + $this->moveToPreviousLine(); + } + + return $ret; + } + + /** + * Returns true if the string is un-indented collection item. + * + * @return bool Returns true if the string is un-indented collection item, false otherwise + */ + private function isStringUnIndentedCollectionItem(): bool + { + return '-' === rtrim($this->currentLine) || 0 === strpos($this->currentLine, '- '); + } + + /** + * A local wrapper for "preg_match" which will throw a ParseException if there + * is an internal error in the PCRE engine. + * + * This avoids us needing to check for "false" every time PCRE is used + * in the YAML engine + * + * @throws ParseException on a PCRE internal error + * + * @see preg_last_error() + * + * @internal + */ + public static function preg_match(string $pattern, string $subject, array &$matches = null, int $flags = 0, int $offset = 0): int + { + if (false === $ret = preg_match($pattern, $subject, $matches, $flags, $offset)) { + switch (preg_last_error()) { + case \PREG_INTERNAL_ERROR: + $error = 'Internal PCRE error.'; + break; + case \PREG_BACKTRACK_LIMIT_ERROR: + $error = 'pcre.backtrack_limit reached.'; + break; + case \PREG_RECURSION_LIMIT_ERROR: + $error = 'pcre.recursion_limit reached.'; + break; + case \PREG_BAD_UTF8_ERROR: + $error = 'Malformed UTF-8 data.'; + break; + case \PREG_BAD_UTF8_OFFSET_ERROR: + $error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.'; + break; + default: + $error = 'Error.'; + } + + throw new ParseException($error); + } + + return $ret; + } + + /** + * Trim the tag on top of the value. + * + * Prevent values such as "!foo {quz: bar}" to be considered as + * a mapping block. + */ + private function trimTag(string $value): string + { + if ('!' === $value[0]) { + return ltrim(substr($value, 1, strcspn($value, " \r\n", 1)), ' '); + } + + return $value; + } + + private function getLineTag(string $value, int $flags, bool $nextLineCheck = true): ?string + { + if ('' === $value || '!' !== $value[0] || 1 !== self::preg_match('/^'.self::TAG_PATTERN.' *( +#.*)?$/', $value, $matches)) { + return null; + } + + if ($nextLineCheck && !$this->isNextLineIndented()) { + return null; + } + + $tag = substr($matches['tag'], 1); + + // Built-in tags + if ($tag && '!' === $tag[0]) { + throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag), $this->getRealCurrentLineNb() + 1, $value, $this->filename); + } + + if (Yaml::PARSE_CUSTOM_TAGS & $flags) { + return $tag; + } + + throw new ParseException(sprintf('Tags support is not enabled. You must use the flag "Yaml::PARSE_CUSTOM_TAGS" to use "%s".', $matches['tag']), $this->getRealCurrentLineNb() + 1, $value, $this->filename); + } + + private function lexInlineQuotedString(int &$cursor = 0): string + { + $quotation = $this->currentLine[$cursor]; + $value = $quotation; + ++$cursor; + + $previousLineWasNewline = true; + $previousLineWasTerminatedWithBackslash = false; + $lineNumber = 0; + + do { + if (++$lineNumber > 1) { + $cursor += strspn($this->currentLine, ' ', $cursor); + } + + if ($this->isCurrentLineBlank()) { + $value .= "\n"; + } elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) { + $value .= ' '; + } + + for (; \strlen($this->currentLine) > $cursor; ++$cursor) { + switch ($this->currentLine[$cursor]) { + case '\\': + if ("'" === $quotation) { + $value .= '\\'; + } elseif (isset($this->currentLine[++$cursor])) { + $value .= '\\'.$this->currentLine[$cursor]; + } + + break; + case $quotation: + ++$cursor; + + if ("'" === $quotation && isset($this->currentLine[$cursor]) && "'" === $this->currentLine[$cursor]) { + $value .= "''"; + break; + } + + return $value.$quotation; + default: + $value .= $this->currentLine[$cursor]; + } + } + + if ($this->isCurrentLineBlank()) { + $previousLineWasNewline = true; + $previousLineWasTerminatedWithBackslash = false; + } elseif ('\\' === $this->currentLine[-1]) { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = true; + } else { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = false; + } + + if ($this->hasMoreLines()) { + $cursor = 0; + } + } while ($this->moveToNextLine()); + + throw new ParseException('Malformed inline YAML string.'); + } + + private function lexUnquotedString(int &$cursor): string + { + $offset = $cursor; + $cursor += strcspn($this->currentLine, '[]{},: ', $cursor); + + if ($cursor === $offset) { + throw new ParseException('Malformed unquoted YAML string.'); + } + + return substr($this->currentLine, $offset, $cursor - $offset); + } + + private function lexInlineMapping(int &$cursor = 0): string + { + return $this->lexInlineStructure($cursor, '}'); + } + + private function lexInlineSequence(int &$cursor = 0): string + { + return $this->lexInlineStructure($cursor, ']'); + } + + private function lexInlineStructure(int &$cursor, string $closingTag): string + { + $value = $this->currentLine[$cursor]; + ++$cursor; + + do { + $this->consumeWhitespaces($cursor); + + while (isset($this->currentLine[$cursor])) { + switch ($this->currentLine[$cursor]) { + case '"': + case "'": + $value .= $this->lexInlineQuotedString($cursor); + break; + case ':': + case ',': + $value .= $this->currentLine[$cursor]; + ++$cursor; + break; + case '{': + $value .= $this->lexInlineMapping($cursor); + break; + case '[': + $value .= $this->lexInlineSequence($cursor); + break; + case $closingTag: + $value .= $this->currentLine[$cursor]; + ++$cursor; + + return $value; + case '#': + break 2; + default: + $value .= $this->lexUnquotedString($cursor); + } + + if ($this->consumeWhitespaces($cursor)) { + $value .= ' '; + } + } + + if ($this->hasMoreLines()) { + $cursor = 0; + } + } while ($this->moveToNextLine()); + + throw new ParseException('Malformed inline YAML string.'); + } + + private function consumeWhitespaces(int &$cursor): bool + { + $whitespacesConsumed = 0; + + do { + $whitespaceOnlyTokenLength = strspn($this->currentLine, ' ', $cursor); + $whitespacesConsumed += $whitespaceOnlyTokenLength; + $cursor += $whitespaceOnlyTokenLength; + + if (isset($this->currentLine[$cursor])) { + return 0 < $whitespacesConsumed; + } + + if ($this->hasMoreLines()) { + $cursor = 0; + } + } while ($this->moveToNextLine()); + + return 0 < $whitespacesConsumed; + } +} diff --git a/vendor/symfony/yaml/README.md b/vendor/symfony/yaml/README.md new file mode 100644 index 0000000000..ac25024b63 --- /dev/null +++ b/vendor/symfony/yaml/README.md @@ -0,0 +1,13 @@ +Yaml Component +============== + +The Yaml component loads and dumps YAML files. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/yaml.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/yaml/Tag/TaggedValue.php b/vendor/symfony/yaml/Tag/TaggedValue.php new file mode 100644 index 0000000000..4ea3406135 --- /dev/null +++ b/vendor/symfony/yaml/Tag/TaggedValue.php @@ -0,0 +1,38 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Tag; + +/** + * @author Nicolas Grekas <p@tchwork.com> + * @author Guilhem N. <egetick@gmail.com> + */ +final class TaggedValue +{ + private $tag; + private $value; + + public function __construct(string $tag, $value) + { + $this->tag = $tag; + $this->value = $value; + } + + public function getTag(): string + { + return $this->tag; + } + + public function getValue() + { + return $this->value; + } +} diff --git a/vendor/symfony/yaml/Unescaper.php b/vendor/symfony/yaml/Unescaper.php new file mode 100644 index 0000000000..6bdf216ae9 --- /dev/null +++ b/vendor/symfony/yaml/Unescaper.php @@ -0,0 +1,138 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +use Symfony\Component\Yaml\Exception\ParseException; + +/** + * Unescaper encapsulates unescaping rules for single and double-quoted + * YAML strings. + * + * @author Matthew Lewinski <matthew@lewinski.org> + * + * @internal + */ +class Unescaper +{ + /** + * Regex fragment that matches an escaped character in a double quoted string. + */ + public const REGEX_ESCAPED_CHARACTER = '\\\\(x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8}|.)'; + + /** + * Unescapes a single quoted string. + * + * @param string $value A single quoted string + * + * @return string The unescaped string + */ + public function unescapeSingleQuotedString(string $value): string + { + return str_replace('\'\'', '\'', $value); + } + + /** + * Unescapes a double quoted string. + * + * @param string $value A double quoted string + * + * @return string The unescaped string + */ + public function unescapeDoubleQuotedString(string $value): string + { + $callback = function ($match) { + return $this->unescapeCharacter($match[0]); + }; + + // evaluate the string + return preg_replace_callback('/'.self::REGEX_ESCAPED_CHARACTER.'/u', $callback, $value); + } + + /** + * Unescapes a character that was found in a double-quoted string. + * + * @param string $value An escaped character + * + * @return string The unescaped character + */ + private function unescapeCharacter(string $value): string + { + switch ($value[1]) { + case '0': + return "\x0"; + case 'a': + return "\x7"; + case 'b': + return "\x8"; + case 't': + return "\t"; + case "\t": + return "\t"; + case 'n': + return "\n"; + case 'v': + return "\xB"; + case 'f': + return "\xC"; + case 'r': + return "\r"; + case 'e': + return "\x1B"; + case ' ': + return ' '; + case '"': + return '"'; + case '/': + return '/'; + case '\\': + return '\\'; + case 'N': + // U+0085 NEXT LINE + return "\xC2\x85"; + case '_': + // U+00A0 NO-BREAK SPACE + return "\xC2\xA0"; + case 'L': + // U+2028 LINE SEPARATOR + return "\xE2\x80\xA8"; + case 'P': + // U+2029 PARAGRAPH SEPARATOR + return "\xE2\x80\xA9"; + case 'x': + return self::utf8chr(hexdec(substr($value, 2, 2))); + case 'u': + return self::utf8chr(hexdec(substr($value, 2, 4))); + case 'U': + return self::utf8chr(hexdec(substr($value, 2, 8))); + default: + throw new ParseException(sprintf('Found unknown escape character "%s".', $value)); + } + } + + /** + * Get the UTF-8 character for the given code point. + */ + private static function utf8chr(int $c): string + { + if (0x80 > $c %= 0x200000) { + return \chr($c); + } + if (0x800 > $c) { + return \chr(0xC0 | $c >> 6).\chr(0x80 | $c & 0x3F); + } + if (0x10000 > $c) { + return \chr(0xE0 | $c >> 12).\chr(0x80 | $c >> 6 & 0x3F).\chr(0x80 | $c & 0x3F); + } + + return \chr(0xF0 | $c >> 18).\chr(0x80 | $c >> 12 & 0x3F).\chr(0x80 | $c >> 6 & 0x3F).\chr(0x80 | $c & 0x3F); + } +} diff --git a/vendor/symfony/yaml/Yaml.php b/vendor/symfony/yaml/Yaml.php new file mode 100644 index 0000000000..4fea47f9a7 --- /dev/null +++ b/vendor/symfony/yaml/Yaml.php @@ -0,0 +1,102 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +use Symfony\Component\Yaml\Exception\ParseException; + +/** + * Yaml offers convenience methods to load and dump YAML. + * + * @author Fabien Potencier <fabien@symfony.com> + * + * @final + */ +class Yaml +{ + public const DUMP_OBJECT = 1; + public const PARSE_EXCEPTION_ON_INVALID_TYPE = 2; + public const PARSE_OBJECT = 4; + public const PARSE_OBJECT_FOR_MAP = 8; + public const DUMP_EXCEPTION_ON_INVALID_TYPE = 16; + public const PARSE_DATETIME = 32; + public const DUMP_OBJECT_AS_MAP = 64; + public const DUMP_MULTI_LINE_LITERAL_BLOCK = 128; + public const PARSE_CONSTANT = 256; + public const PARSE_CUSTOM_TAGS = 512; + public const DUMP_EMPTY_ARRAY_AS_SEQUENCE = 1024; + public const DUMP_NULL_AS_TILDE = 2048; + + /** + * Parses a YAML file into a PHP value. + * + * Usage: + * + * $array = Yaml::parseFile('config.yml'); + * print_r($array); + * + * @param string $filename The path to the YAML file to be parsed + * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior + * + * @return mixed The YAML converted to a PHP value + * + * @throws ParseException If the file could not be read or the YAML is not valid + */ + public static function parseFile(string $filename, int $flags = 0) + { + $yaml = new Parser(); + + return $yaml->parseFile($filename, $flags); + } + + /** + * Parses YAML into a PHP value. + * + * Usage: + * <code> + * $array = Yaml::parse(file_get_contents('config.yml')); + * print_r($array); + * </code> + * + * @param string $input A string containing YAML + * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior + * + * @return mixed The YAML converted to a PHP value + * + * @throws ParseException If the YAML is not valid + */ + public static function parse(string $input, int $flags = 0) + { + $yaml = new Parser(); + + return $yaml->parse($input, $flags); + } + + /** + * Dumps a PHP value to a YAML string. + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. + * + * @param mixed $input The PHP value + * @param int $inline The level where you switch to inline YAML + * @param int $indent The amount of spaces to use for indentation of nested nodes + * @param int $flags A bit field of DUMP_* constants to customize the dumped YAML string + * + * @return string A YAML string representing the original PHP value + */ + public static function dump($input, int $inline = 2, int $indent = 4, int $flags = 0): string + { + $yaml = new Dumper($indent); + + return $yaml->dump($input, $inline, 0, $flags); + } +} diff --git a/vendor/symfony/yaml/composer.json b/vendor/symfony/yaml/composer.json new file mode 100644 index 0000000000..10120348a6 --- /dev/null +++ b/vendor/symfony/yaml/composer.json @@ -0,0 +1,38 @@ +{ + "name": "symfony/yaml", + "type": "library", + "description": "Loads and dumps YAML files", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1.3", + "symfony/polyfill-ctype": "~1.8" + }, + "require-dev": { + "symfony/console": "^3.4|^4.0|^5.0" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Yaml\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/webflo/drupal-finder/.gitignore b/vendor/webflo/drupal-finder/.gitignore new file mode 100644 index 0000000000..c8153b5782 --- /dev/null +++ b/vendor/webflo/drupal-finder/.gitignore @@ -0,0 +1,2 @@ +/composer.lock +/vendor/ diff --git a/vendor/webflo/drupal-finder/.styleci.yml b/vendor/webflo/drupal-finder/.styleci.yml new file mode 100644 index 0000000000..247a09c5d2 --- /dev/null +++ b/vendor/webflo/drupal-finder/.styleci.yml @@ -0,0 +1 @@ +preset: psr2 diff --git a/vendor/webflo/drupal-finder/.travis.yml b/vendor/webflo/drupal-finder/.travis.yml new file mode 100644 index 0000000000..e54f94b5d5 --- /dev/null +++ b/vendor/webflo/drupal-finder/.travis.yml @@ -0,0 +1,15 @@ +language: php +php: + - 5.6 + - 7.0 + +sudo: false + +before_install: + - phpenv config-rm xdebug.ini + +install: + - composer --verbose install + +script: + - ./vendor/bin/phpunit diff --git a/vendor/webflo/drupal-finder/LICENSE.txt b/vendor/webflo/drupal-finder/LICENSE.txt new file mode 100644 index 0000000000..94fb84639c --- /dev/null +++ b/vendor/webflo/drupal-finder/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/vendor/webflo/drupal-finder/README.md b/vendor/webflo/drupal-finder/README.md new file mode 100644 index 0000000000..fd35fabcda --- /dev/null +++ b/vendor/webflo/drupal-finder/README.md @@ -0,0 +1,25 @@ +# Drupal Finder + +[![Travis](https://img.shields.io/travis/webflo/drupal-finder.svg)](https://travis-ci.org/webflo/drupal-finder) [![Packagist](https://img.shields.io/packagist/v/webflo/drupal-finder.svg)](https://packagist.org/packages/webflo/drupal-finder) + +Drupal Finder provides a class to locate a Drupal installation in a given path. + +## Usage + +```PHP +$drupalFinder = new \DrupalFinder\DrupalFinder(); +if ($drupalFinder->locateRoot(getcwd())) { + $drupalRoot = $drupalFinder->getDrupalRoot(); + $composerRoot = $drupalFinder->getComposerRoot(); + ... +} +``` + +## Examples + +- [Drupal Console Launcher](https://github.com/hechoendrupal/drupal-console-launcher) +- [Drush Launcher](https://github.com/drush-ops/drush-launcher) + +## License + +GPL-2.0+ diff --git a/vendor/webflo/drupal-finder/composer.json b/vendor/webflo/drupal-finder/composer.json new file mode 100644 index 0000000000..d07d9de710 --- /dev/null +++ b/vendor/webflo/drupal-finder/composer.json @@ -0,0 +1,29 @@ +{ + "name": "webflo/drupal-finder", + "description": "Helper class to locate a Drupal installation from a given path.", + "license": "GPL-2.0-or-later", + "type": "library", + "authors": [ + { + "name": "Florian Weber", + "email": "florian@webflo.org" + } + ], + "require": { + "ext-json": "*" + }, + "autoload": { + "classmap": [ + "src/DrupalFinder.php" + ] + }, + "autoload-dev": { + "psr-4": { + "DrupalFinder\\Tests\\": "tests/" + } + }, + "require-dev": { + "phpunit/phpunit": "^4.8", + "mikey179/vfsstream": "^1.6" + } +} diff --git a/vendor/webflo/drupal-finder/phpunit.xml.dist b/vendor/webflo/drupal-finder/phpunit.xml.dist new file mode 100644 index 0000000000..92f27db69c --- /dev/null +++ b/vendor/webflo/drupal-finder/phpunit.xml.dist @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<phpunit colors="true"> + <testsuites> + <testsuite name="DrupalFinder Test Suite"> + <directory>./tests</directory> + </testsuite> + </testsuites> +</phpunit> diff --git a/vendor/webflo/drupal-finder/src/DrupalFinder.php b/vendor/webflo/drupal-finder/src/DrupalFinder.php new file mode 100644 index 0000000000..fe62ec45d4 --- /dev/null +++ b/vendor/webflo/drupal-finder/src/DrupalFinder.php @@ -0,0 +1,174 @@ +<?php + +/** + * @file + * Contains \DrupalFinder\DrupalFinder. + */ + +namespace DrupalFinder; + +class DrupalFinder +{ + /** + * Drupal web public directory. + * + * @var string + */ + private $drupalRoot; + + /** + * Drupal package composer directory. + * + * @var bool + */ + private $composerRoot; + + /** + * Composer vendor directory. + * + * @var string + * + * @see https://getcomposer.org/doc/06-config.md#vendor-dir + */ + private $vendorDir; + + public function locateRoot($start_path) + { + $this->drupalRoot = false; + $this->composerRoot = false; + $this->vendorDir = false; + + foreach (array(true, false) as $follow_symlinks) { + $path = $start_path; + if ($follow_symlinks && is_link($path)) { + $path = realpath($path); + } + // Check the start path. + if ($this->isValidRoot($path)) { + return true; + } else { + // Move up dir by dir and check each. + while ($path = $this->shiftPathUp($path)) { + if ($follow_symlinks && is_link($path)) { + $path = realpath($path); + } + if ($this->isValidRoot($path)) { + return true; + } + } + } + } + + return false; + } + + /** + * Returns parent directory. + * + * @param string + * Path to start from + * + * @return string|false + * Parent path of given path or false when $path is filesystem root + */ + private function shiftPathUp($path) + { + $parent = dirname($path); + + return in_array($parent, ['.', $path]) ? false : $parent; + } + + /** + * @param $path + * + * @return bool + */ + protected function isValidRoot($path) + { + if (!empty($path) && is_dir($path) && file_exists($path . '/autoload.php') && file_exists($path . '/' . $this->getComposerFileName())) { + // Additional check for the presence of core/composer.json to + // grant it is not a Drupal 7 site with a base folder named "core". + $candidate = 'core/includes/common.inc'; + if (file_exists($path . '/' . $candidate) && file_exists($path . '/core/core.services.yml')) { + if (file_exists($path . '/core/misc/drupal.js') || file_exists($path . '/core/assets/js/drupal.js')) { + $this->composerRoot = $path; + $this->drupalRoot = $path; + $this->vendorDir = $this->composerRoot . '/vendor'; + } + } + } + if (!empty($path) && is_dir($path) && file_exists($path . '/' . $this->getComposerFileName())) { + $json = json_decode( + file_get_contents($path . '/' . $this->getComposerFileName()), + true + ); + + if (is_null($json)) { + throw new \Exception('Unable to decode ' . $path . '/' . $this->getComposerFileName()); + } + + if (is_array($json)) { + if (isset($json['extra']['installer-paths']) && is_array($json['extra']['installer-paths'])) { + foreach ($json['extra']['installer-paths'] as $install_path => $items) { + if (in_array('type:drupal-core', $items) || + in_array('drupal/core', $items) || + in_array('drupal/drupal', $items)) { + $this->composerRoot = $path; + // @todo: Remove this magic and detect the major version instead. + if (($install_path == 'core') || ((isset($json['name'])) && ($json['name'] == 'drupal/drupal'))) { + $install_path = ''; + } elseif (substr($install_path, -5) == '/core') { + $install_path = substr($install_path, 0, -5); + } + $this->drupalRoot = rtrim($path . '/' . $install_path, '/'); + $this->vendorDir = $this->composerRoot . '/vendor'; + } + } + } + } + } + if ($this->composerRoot && file_exists($this->composerRoot . '/' . $this->getComposerFileName())) { + $json = json_decode( + file_get_contents($path . '/' . $this->getComposerFileName()), + true + ); + if (is_array($json) && isset($json['config']['vendor-dir'])) { + $this->vendorDir = $this->composerRoot . '/' . $json['config']['vendor-dir']; + } + } + + return $this->drupalRoot && $this->composerRoot && $this->vendorDir; + } + + /** + * @return string + */ + public function getDrupalRoot() + { + return $this->drupalRoot; + } + + /** + * @return string + */ + public function getComposerRoot() + { + return $this->composerRoot; + } + + /** + * @return string + */ + protected function getComposerFileName() + { + return trim(getenv('COMPOSER')) ?: 'composer.json'; + } + + /** + * @return string + */ + public function getVendorDir() + { + return $this->vendorDir; + } +} diff --git a/vendor/webflo/drupal-finder/tests/Drupal7FinderTest.php b/vendor/webflo/drupal-finder/tests/Drupal7FinderTest.php new file mode 100644 index 0000000000..294ae1582a --- /dev/null +++ b/vendor/webflo/drupal-finder/tests/Drupal7FinderTest.php @@ -0,0 +1,168 @@ +<?php + +namespace DrupalFinder\Tests; + +use org\bovigo\vfs\vfsStream; + +class Drupal7FinderTest extends DrupalFinderTestBase +{ + /** + * @var \DrupalFinder\DrupalFinder + */ + protected $finder; + + protected static $fileStructure = [ + 'includes' => [ + 'common.inc' => '', + ], + 'misc' => [ + 'drupal.js' => '', + ], + 'sites' => [ + 'all' => [ + 'modules' => [] + ] + ] + ]; + + /** + * @return array + */ + protected function getDrupalComposerStructure() + { + $fileStructure = [ + 'web' => static::$fileStructure, + 'composer.json' => [ + 'require' => [ + 'drupal/drupal' => '*', + ], + 'extra' => [ + 'installer-paths' => [ + 'web/' => [ + 'type:drupal-core', + ], + ], + ], + ], + 'vendor' => [], + ]; + return $fileStructure; + } + + public function testDrupalComposerStructure() + { + $fileStructure = $this->getDrupalComposerStructure(); + $this->assertComposerStructure($fileStructure); + } + + public function testDrupalComposerStructureWithoutRequire() + { + $fileStructure = [ + 'web' => static::$fileStructure, + 'composer.json' => [ + 'extra' => [ + 'installer-paths' => [ + 'web' => [ + 'drupal/drupal', + ], + ], + ], + ], + ]; + $this->assertComposerStructure($fileStructure); + } + + public function testNoDrupalRootWithRealFilesystem() + { + $root = $this->tempdir(sys_get_temp_dir()); + + $this->assertFalse($this->finder->locateRoot($root)); + $this->assertFalse($this->finder->getDrupalRoot()); + $this->assertFalse($this->finder->getComposerRoot()); + $this->assertFalse($this->finder->getVendorDir()); + } + + public function testDrupalComposerStructureWithRealFilesystem() + { + $root = $this->tempdir(sys_get_temp_dir()); + $this->dumpToFileSystem($this->getDrupalComposerStructure(), $root); + + $this->assertTrue($this->finder->locateRoot($root)); + $this->assertSame($root . '/web', $this->finder->getDrupalRoot()); + $this->assertSame($root, $this->finder->getComposerRoot()); + $this->assertSame($root . '/vendor', $this->finder->getVendorDir()); + + // Test symlink implementation + $symlink = $this->tempdir(sys_get_temp_dir()); + $this->symlink($root, $symlink . '/foo'); + + $this->assertTrue($this->finder->locateRoot($symlink . '/foo')); + $this->assertSame($root . '/web', $this->finder->getDrupalRoot()); + $this->assertSame($root, $this->finder->getComposerRoot()); + $this->assertSame($root . '/vendor', $this->finder->getVendorDir()); + } + + public function testDrupalWithLinkedModule() + { + $root = $this->tempdir(sys_get_temp_dir()); + $this->dumpToFileSystem($this->getDrupalComposerStructure(), $root); + + $module = $this->tempdir(sys_get_temp_dir()); + $module_link = $root . '/web/sites/all/modules/foo'; + $this->symlink($module, $module_link); + + $this->assertTrue($this->finder->locateRoot($module_link)); + $this->assertSame($root . '/web', realpath($this->finder->getDrupalRoot())); + $this->assertSame($root, realpath($this->finder->getComposerRoot())); + $this->assertSame($root . '/vendor', realpath($this->finder->getVendorDir())); + } + + public function testDrupalWithCustomVendor() + { + $root = $this->tempdir(sys_get_temp_dir()); + $fileStructure = $this->getDrupalComposerStructure(); + $composerJson = $fileStructure['composer.json']; + $composerJson['config']['vendor-dir'] = 'vendor-foo'; + $fileStructure['composer.json'] = $composerJson; + $fileStructure['vendor-foo'] = []; + $this->dumpToFileSystem($fileStructure, $root); + + $this->assertTrue($this->finder->locateRoot($root)); + $this->assertSame($root . '/web', realpath($this->finder->getDrupalRoot())); + $this->assertSame($root, realpath($this->finder->getComposerRoot())); + $this->assertSame($root . '/vendor-foo', realpath($this->finder->getVendorDir())); + } + + /** + * @param $fileStructure + */ + protected function assertComposerStructure($fileStructure) + { + $fileStructure = $this->prepareFileStructure($fileStructure); + $root = vfsStream::setup('root', null, $fileStructure); + $this->assertTrue($this->finder->locateRoot($root->url() . '/web')); + $this->assertSame('vfs://root/web', $this->finder->getDrupalRoot()); + $this->assertSame('vfs://root', $this->finder->getComposerRoot()); + $this->assertSame('vfs://root/vendor', $this->finder->getVendorDir()); + + $this->assertTrue($this->finder->locateRoot($root->url() . '/web/misc')); + $this->assertSame('vfs://root/web', $this->finder->getDrupalRoot()); + $this->assertSame('vfs://root', $this->finder->getComposerRoot()); + $this->assertSame('vfs://root/vendor', $this->finder->getVendorDir()); + + $this->assertTrue($this->finder->locateRoot($root->url())); + $this->assertSame('vfs://root/web', $this->finder->getDrupalRoot()); + $this->assertSame('vfs://root', $this->finder->getComposerRoot()); + $this->assertSame('vfs://root/vendor', $this->finder->getVendorDir()); + + $root = vfsStream::setup( + 'root', + null, + ['nested_folder' => $fileStructure] + ); + $this->assertFalse($this->finder->locateRoot($root->url())); + $this->assertFalse($this->finder->getDrupalRoot()); + $this->assertFalse($this->finder->getComposerRoot()); + $this->assertFalse($this->finder->getVendorDir()); + } +} diff --git a/vendor/webflo/drupal-finder/tests/Drupal8FinderTest.php b/vendor/webflo/drupal-finder/tests/Drupal8FinderTest.php new file mode 100644 index 0000000000..87a0cca323 --- /dev/null +++ b/vendor/webflo/drupal-finder/tests/Drupal8FinderTest.php @@ -0,0 +1,338 @@ +<?php + +namespace DrupalFinder\Tests; + +use org\bovigo\vfs\vfsStream; + +class Drupal8FinderTest extends DrupalFinderTestBase +{ + protected static $fileStructure = [ + 'autoload.php' => '', + 'composer.json' => [ + 'extra' => [ + 'installer-paths' => [ + 'core' => [ + 'type:drupal-core' + ] + ] + ] + ], + 'core' => [ + 'includes' => [ + 'common.inc' => '', + ], + 'misc' => [ + 'drupal.js' => '', + ], + 'core.services.yml' => '', + ], + 'modules' => [], + 'vendor' => [], + ]; + + protected static $fileStructureDrupal_8_8_x = [ + 'autoload.php' => '', + 'composer.json' => [ + 'name' => 'drupal/drupal', + 'require' => [ + 'drupal/core' => 'self.version', + ], + 'extra' => [ + 'installer-paths' => [ + 'vendor/drupal/core' => [ + 'type:drupal-core', + ], + ], + ], + ], + 'core' => [ + 'includes' => [ + 'common.inc' => '', + ], + 'misc' => [ + 'drupal.js' => '', + ], + 'core.services.yml' => '', + ], + 'modules' => [], + 'vendor' => [], + ]; + + /** + * @return array + */ + protected function getDrupalComposerStructure() + { + $fileStructure = [ + 'web' => static::$fileStructure, + 'composer.json' => [ + 'require' => [ + 'drupal/core' => '*', + ], + 'extra' => [ + 'installer-paths' => [ + 'web/core' => [ + 'type:drupal-core', + ], + ], + ], + ], + 'vendor' => [], + ]; + unset($fileStructure['web']['composer.json']); + unset($fileStructure['web']['vendor']); + + return $fileStructure; + } + + protected function setUp() + { + parent::setUp(); + $this->finder = new \DrupalFinder\DrupalFinder(); + } + + public function testDrupalDefaultStructure() + { + $root = vfsStream::setup('root', null, $this->prepareFileStructure(static::$fileStructure)); + + $this->assertTrue($this->finder->locateRoot($root->url())); + $this->assertSame('vfs://root', $this->finder->getDrupalRoot()); + $this->assertSame('vfs://root', $this->finder->getComposerRoot()); + $this->assertSame('vfs://root/vendor', $this->finder->getVendorDir()); + + $this->assertTrue($this->finder->locateRoot($root->url() . '/misc')); + $this->assertSame('vfs://root', $this->finder->getDrupalRoot()); + $this->assertSame('vfs://root', $this->finder->getComposerRoot()); + $this->assertSame('vfs://root/vendor', $this->finder->getVendorDir()); + + $root = vfsStream::setup( + 'root', + null, + ['project' => $this->prepareFileStructure(static::$fileStructure)] + ); + $this->assertFalse( + $this->finder->locateRoot($root->url()), + 'Not in the scope of the project' + ); + $this->assertFalse($this->finder->getDrupalRoot()); + $this->assertFalse($this->finder->getComposerRoot()); + $this->assertFalse($this->finder->getVendorDir()); + } + + public function testDrupalDefaultStructure_8_8_x() + { + $root = vfsStream::setup('root', null, $this->prepareFileStructure(static::$fileStructureDrupal_8_8_x)); + + $this->assertTrue($this->finder->locateRoot($root->url())); + $this->assertSame('vfs://root', $this->finder->getDrupalRoot()); + $this->assertSame('vfs://root', $this->finder->getComposerRoot()); + $this->assertSame('vfs://root/vendor', $this->finder->getVendorDir()); + + $this->assertTrue($this->finder->locateRoot($root->url() . '/misc')); + $this->assertSame('vfs://root', $this->finder->getDrupalRoot()); + $this->assertSame('vfs://root', $this->finder->getComposerRoot()); + $this->assertSame('vfs://root/vendor', $this->finder->getVendorDir()); + + $root = vfsStream::setup( + 'root', + null, + ['project' => $this->prepareFileStructure(static::$fileStructure)] + ); + $this->assertFalse( + $this->finder->locateRoot($root->url()), + 'Not in the scope of the project' + ); + $this->assertFalse($this->finder->getDrupalRoot()); + $this->assertFalse($this->finder->getComposerRoot()); + $this->assertFalse($this->finder->getVendorDir()); + } + + public function testDrupalComposerStructure() + { + $fileStructure = $this->getDrupalComposerStructure(); + $this->assertComposerStructure($fileStructure); + } + + public function testDrupalComposerStructureWithCustomRoot() + { + $fileStructure = [ + 'src' => static::$fileStructure, + 'composer.json' => [ + 'require' => [ + 'drupal/core' => '*', + ], + 'extra' => [ + 'installer-paths' => [ + 'src/core' => [ + 'type:drupal-core', + ], + ], + ], + ], + 'vendor' => [], + ]; + unset($fileStructure['src']['composer.json']); + unset($fileStructure['src']['vendor']); + + $fileStructure = $this->prepareFileStructure($fileStructure); + $root = vfsStream::setup('root', null, $fileStructure); + $this->assertTrue($this->finder->locateRoot($root->url() . '/src')); + $this->assertSame('vfs://root/src', $this->finder->getDrupalRoot()); + $this->assertSame('vfs://root', $this->finder->getComposerRoot()); + $this->assertSame('vfs://root/vendor', $this->finder->getVendorDir()); + + $this->assertTrue($this->finder->locateRoot($root->url() . '/src/misc')); + $this->assertSame('vfs://root/src', $this->finder->getDrupalRoot()); + $this->assertSame('vfs://root', $this->finder->getComposerRoot()); + $this->assertSame('vfs://root/vendor', $this->finder->getVendorDir()); + + $this->assertTrue($this->finder->locateRoot($root->url())); + $this->assertSame('vfs://root/src', $this->finder->getDrupalRoot()); + $this->assertSame('vfs://root', $this->finder->getComposerRoot()); + $this->assertSame('vfs://root/vendor', $this->finder->getVendorDir()); + + $root = vfsStream::setup( + 'root', + null, + ['nested_folder' => $fileStructure] + ); + $this->assertFalse($this->finder->locateRoot($root->url())); + $this->assertFalse($this->finder->getDrupalRoot()); + $this->assertFalse($this->finder->getComposerRoot()); + $this->assertFalse($this->finder->getVendorDir()); + } + + public function testDrupalComposerStructureWithoutRequire() + { + $fileStructure = [ + 'web' => static::$fileStructure, + 'composer.json' => [ + 'extra' => [ + 'installer-paths' => [ + 'web/core' => [ + 'drupal/core', + ], + ], + ], + ], + ]; + unset($fileStructure['web']['composer.json']); + $this->assertComposerStructure($fileStructure); + } + + public function testNoDrupalRootWithRealFilesystem() + { + $root = $this->tempdir(sys_get_temp_dir()); + + $this->assertFalse($this->finder->locateRoot($root)); + $this->assertFalse($this->finder->getDrupalRoot()); + $this->assertFalse($this->finder->getComposerRoot()); + $this->assertFalse($this->finder->getVendorDir()); + } + + public function testDrupalDefaultStructureWithRealFilesystem() + { + $root = $this->tempdir(sys_get_temp_dir()); + $this->dumpToFileSystem(static::$fileStructure, $root); + + $this->assertTrue($this->finder->locateRoot($root)); + $this->assertSame($root, $this->finder->getDrupalRoot()); + $this->assertSame($root, $this->finder->getComposerRoot()); + $this->assertSame($root . '/vendor', $this->finder->getVendorDir()); + + // Test symlink implementation + $symlink = $this->tempdir(sys_get_temp_dir()); + $this->symlink($root, $symlink . '/foo'); + + $this->assertTrue($this->finder->locateRoot($symlink . '/foo')); + $this->assertSame($root, $this->finder->getDrupalRoot()); + $this->assertSame($root, $this->finder->getComposerRoot()); + $this->assertSame($root . '/vendor', $this->finder->getVendorDir()); + } + + public function testDrupalComposerStructureWithRealFilesystem() + { + $root = $this->tempdir(sys_get_temp_dir()); + $this->dumpToFileSystem($this->getDrupalComposerStructure(), $root); + + $this->assertTrue($this->finder->locateRoot($root)); + $this->assertSame($root . '/web', $this->finder->getDrupalRoot()); + $this->assertSame($root, $this->finder->getComposerRoot()); + $this->assertSame($root . '/vendor', $this->finder->getVendorDir()); + + // Test symlink implementation + $symlink = $this->tempdir(sys_get_temp_dir()); + $this->symlink($root, $symlink . '/foo'); + + $this->assertTrue($this->finder->locateRoot($symlink . '/foo')); + $this->assertSame($root . '/web', $this->finder->getDrupalRoot()); + $this->assertSame($root, $this->finder->getComposerRoot()); + $this->assertSame($root . '/vendor', $this->finder->getVendorDir()); + } + + public function testDrupalWithLinkedModule() + { + $root = $this->tempdir(sys_get_temp_dir()); + $this->dumpToFileSystem(static::$fileStructure, $root); + + $module = $this->tempdir(sys_get_temp_dir()); + $module_link = $root . '/modules/foo'; + $this->symlink($module, $module_link); + + $this->assertTrue($this->finder->locateRoot($module_link)); + $this->assertSame($root, realpath($this->finder->getDrupalRoot())); + $this->assertSame($root, realpath($this->finder->getComposerRoot())); + $this->assertSame($root . '/vendor', realpath($this->finder->getVendorDir())); + } + + public function testDrupalWithCustomVendor() + { + $root = $this->tempdir(sys_get_temp_dir()); + $fileStructure = static::$fileStructure; + $fileStructure['composer.json'] = [ + 'config' => [ + 'vendor-dir' => 'vendor-foo' + ] + ]; + $fileStructure['vendor-foo'] = []; + $this->dumpToFileSystem($fileStructure, $root); + + $this->assertTrue($this->finder->locateRoot($root)); + $this->assertSame($root, realpath($this->finder->getDrupalRoot())); + $this->assertSame($root, realpath($this->finder->getComposerRoot())); + $this->assertSame($root . '/vendor-foo', realpath($this->finder->getVendorDir())); + } + + /** + * @param $fileStructure + */ + protected function assertComposerStructure($fileStructure) + { + $fileStructure = $this->prepareFileStructure($fileStructure); + $root = vfsStream::setup('root', null, $fileStructure); + $this->assertTrue($this->finder->locateRoot($root->url() . '/web')); + $this->assertSame('vfs://root/web', $this->finder->getDrupalRoot()); + $this->assertSame('vfs://root', $this->finder->getComposerRoot()); + $this->assertSame('vfs://root/vendor', $this->finder->getVendorDir()); + + $this->assertTrue($this->finder->locateRoot($root->url() . '/web/misc')); + $this->assertSame('vfs://root/web', $this->finder->getDrupalRoot()); + $this->assertSame('vfs://root', $this->finder->getComposerRoot()); + $this->assertSame('vfs://root/vendor', $this->finder->getVendorDir()); + + $this->assertTrue($this->finder->locateRoot($root->url())); + $this->assertSame('vfs://root/web', $this->finder->getDrupalRoot()); + $this->assertSame('vfs://root', $this->finder->getComposerRoot()); + $this->assertSame('vfs://root/vendor', $this->finder->getVendorDir()); + + $root = vfsStream::setup( + 'root', + null, + ['nested_folder' => $fileStructure] + ); + $this->assertFalse($this->finder->locateRoot($root->url())); + $this->assertFalse($this->finder->getDrupalRoot()); + $this->assertFalse($this->finder->getComposerRoot()); + $this->assertFalse($this->finder->getVendorDir()); + } +} diff --git a/vendor/webflo/drupal-finder/tests/DrupalFinderTestBase.php b/vendor/webflo/drupal-finder/tests/DrupalFinderTestBase.php new file mode 100644 index 0000000000..83d0dd06a3 --- /dev/null +++ b/vendor/webflo/drupal-finder/tests/DrupalFinderTestBase.php @@ -0,0 +1,109 @@ +<?php + +namespace DrupalFinder\Tests; + +use DrupalFinder\DrupalFinder; +use Exception; +use PHPUnit_Framework_TestCase; + +abstract class DrupalFinderTestBase extends PHPUnit_Framework_TestCase +{ + /** + * @var \DrupalFinder\DrupalFinder + */ + protected $finder; + + protected function setUp() + { + parent::setUp(); + $this->finder = new DrupalFinder(); + } + + protected function dumpToFileSystem($fileStructure, $root) + { + $fileStructure = $this->prepareFileStructure($fileStructure); + foreach ($fileStructure as $name => $content) { + if (is_array($content)) { + mkdir($root . '/' . $name); + $this->dumpToFileSystem($content, $root . '/' . $name); + } else { + file_put_contents($root . '/' . $name, $content); + } + } + } + + protected function prepareFileStructure($fileStructure) + { + foreach ($fileStructure as $name => $content) { + if (($name === 'composer.json' || $name === 'composer.lock') && is_array($content)) { + $fileStructure[$name] = json_encode($content, JSON_UNESCAPED_SLASHES); + } elseif (is_array($content)) { + $fileStructure[$name] = $this->prepareFileStructure($content); + } + } + return $fileStructure; + } + + protected function tempdir($dir, $prefix = '', $mode = 0700) + { + if (substr($dir, -1) != '/') { + $dir .= '/'; + } + do { + $path = $dir . $prefix . mt_rand(0, 9999999); + } while (!mkdir($path, $mode)); + register_shutdown_function( + [get_called_class(), 'tempdir_remove'], + $path + ); + + return realpath($path); + } + + public static function tempdir_remove($path) + { + if (is_link($path)) { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + rmdir($path); + } else { + unlink($path); + } + + return; + } + + foreach (scandir($path) as $child) { + if (in_array($child, ['.', '..'])) { + continue; + } + $child = "$path/$child"; + is_dir($child) ? static::tempdir_remove($child) : unlink($child); + } + rmdir($path); + } + + /** + * @param $target + * @param $link + * + * @throws \PHPUnit_Framework_SkippedTestError + */ + protected function symlink($target, $link) + { + try { + return symlink($target, $link); + } catch (Exception $e) { + if (defined('PHP_WINDOWS_VERSION_BUILD') + && strstr($e->getMessage(), WIN_ERROR_PRIVILEGE_NOT_HELD) + ) { + $this->markTestSkipped(<<<'MESSAGE' +No privilege to create symlinks. Run test as Administrator (elevated process). +MESSAGE + ); + } + throw $e; + } + } +} + +define('WIN_ERROR_PRIVILEGE_NOT_HELD', '1314'); diff --git a/vendor/webmozart/assert/CHANGELOG.md b/vendor/webmozart/assert/CHANGELOG.md new file mode 100644 index 0000000000..56c8011dee --- /dev/null +++ b/vendor/webmozart/assert/CHANGELOG.md @@ -0,0 +1,207 @@ +Changelog +========= + +## UNRELEASED + +## 1.11.0 + +### Added + +* Added explicit (non magic) `allNullOr*` methods, with `@psalm-assert` annotations, for better Psalm support. + +### Changed + +* Trait methods will now check the assertion themselves, instead of using `__callStatic` +* `isList` will now deal correctly with (modified) lists that contain `NaN` +* `reportInvalidArgument` now has a return type of `never`. + +### Removed + +* Removed `symfony/polyfill-ctype` as a dependency, and require `ext-cytpe` instead. + * You can still require the `symfony/polyfill-ctype` in your project if you need it, as it provides `ext-ctype` + +## 1.10.0 + +### Added + +* On invalid assertion, we throw a `Webmozart\Assert\InvalidArgumentException` +* Added `Assert::positiveInteger()` + +### Changed + +* Using a trait with real implementations of `all*()` and `nullOr*()` methods to improve psalm compatibility. + +### Removed + +* Support for PHP <7.2 + +## 1.9.1 + +## Fixed + +* provisional support for PHP 8.0 + +## 1.9.0 + +* added better Psalm support for `all*` & `nullOr*` methods +* These methods are now understood by Psalm through a mixin. You may need a newer version of Psalm in order to use this +* added `@psalm-pure` annotation to `Assert::notFalse()` +* added more `@psalm-assert` annotations where appropriate + +## Changed + +* the `all*` & `nullOr*` methods are now declared on an interface, instead of `@method` annotations. +This interface is linked to the `Assert` class with a `@mixin` annotation. Most IDE's have supported this +for a long time, and you should not lose any autocompletion capabilities. PHPStan has supported this since +version `0.12.20`. This package is marked incompatible (with a composer conflict) with phpstan version prior to that. +If you do not use PHPStan than this does not matter. + +## 1.8.0 + +### Added + +* added `Assert::notStartsWith()` +* added `Assert::notEndsWith()` +* added `Assert::inArray()` +* added `@psalm-pure` annotations to pure assertions + +### Fixed + +* Exception messages of comparisons between `DateTime(Immutable)` objects now display their date & time. +* Custom Exception messages for `Assert::count()` now use the values to render the exception message. + +## 1.7.0 (2020-02-14) + +### Added + +* added `Assert::notFalse()` +* added `Assert::isAOf()` +* added `Assert::isAnyOf()` +* added `Assert::isNotA()` + +## 1.6.0 (2019-11-24) + +### Added + +* added `Assert::validArrayKey()` +* added `Assert::isNonEmptyList()` +* added `Assert::isNonEmptyMap()` +* added `@throws InvalidArgumentException` annotations to all methods that throw. +* added `@psalm-assert` for the list type to the `isList` assertion. + +### Fixed + +* `ResourceBundle` & `SimpleXMLElement` now pass the `isCountable` assertions. +They are countable, without implementing the `Countable` interface. +* The doc block of `range` now has the proper variables. +* An empty array will now pass `isList` and `isMap`. As it is a valid form of both. +If a non-empty variant is needed, use `isNonEmptyList` or `isNonEmptyMap`. + +### Changed + +* Removed some `@psalm-assert` annotations, that were 'side effect' assertions See: + * [#144](https://github.com/webmozart/assert/pull/144) + * [#145](https://github.com/webmozart/assert/issues/145) + * [#146](https://github.com/webmozart/assert/pull/146) + * [#150](https://github.com/webmozart/assert/pull/150) +* If you use Psalm, the minimum version needed is `3.6.0`. Which is enforced through a composer conflict. +If you don't use Psalm, then this has no impact. + +## 1.5.0 (2019-08-24) + +### Added + +* added `Assert::uniqueValues()` +* added `Assert::unicodeLetters()` +* added: `Assert::email()` +* added support for [Psalm](https://github.com/vimeo/psalm), by adding `@psalm-assert` annotations where appropriate. + +### Fixed + +* `Assert::endsWith()` would not give the correct result when dealing with a multibyte suffix. +* `Assert::length(), minLength, maxLength, lengthBetween` would not give the correct result when dealing with multibyte characters. + +**NOTE**: These 2 changes may break your assertions if you relied on the fact that multibyte characters didn't behave correctly. + +### Changed + +* The names of some variables have been updated to better reflect what they are. +* All function calls are now in their FQN form, slightly increasing performance. +* Tests are now properly ran against HHVM-3.30 and PHP nightly. + +### Deprecation + +* deprecated `Assert::isTraversable()` in favor of `Assert::isIterable()` + * This was already done in 1.3.0, but it was only done through a silenced `trigger_error`. It is now annotated as well. + +## 1.4.0 (2018-12-25) + +### Added + +* added `Assert::ip()` +* added `Assert::ipv4()` +* added `Assert::ipv6()` +* added `Assert::notRegex()` +* added `Assert::interfaceExists()` +* added `Assert::isList()` +* added `Assert::isMap()` +* added polyfill for ctype + +### Fixed + +* Special case when comparing objects implementing `__toString()` + +## 1.3.0 (2018-01-29) + +### Added + +* added `Assert::minCount()` +* added `Assert::maxCount()` +* added `Assert::countBetween()` +* added `Assert::isCountable()` +* added `Assert::notWhitespaceOnly()` +* added `Assert::natural()` +* added `Assert::notContains()` +* added `Assert::isArrayAccessible()` +* added `Assert::isInstanceOfAny()` +* added `Assert::isIterable()` + +### Fixed + +* `stringNotEmpty` will no longer report "0" is an empty string + +### Deprecation + +* deprecated `Assert::isTraversable()` in favor of `Assert::isIterable()` + +## 1.2.0 (2016-11-23) + + * added `Assert::throws()` + * added `Assert::count()` + * added extension point `Assert::reportInvalidArgument()` for custom subclasses + +## 1.1.0 (2016-08-09) + + * added `Assert::object()` + * added `Assert::propertyExists()` + * added `Assert::propertyNotExists()` + * added `Assert::methodExists()` + * added `Assert::methodNotExists()` + * added `Assert::uuid()` + +## 1.0.2 (2015-08-24) + + * integrated Style CI + * add tests for minimum package dependencies on Travis CI + +## 1.0.1 (2015-05-12) + + * added support for PHP 5.3.3 + +## 1.0.0 (2015-05-12) + + * first stable release + +## 1.0.0-beta (2015-03-19) + + * first beta release diff --git a/vendor/webmozart/assert/LICENSE b/vendor/webmozart/assert/LICENSE new file mode 100644 index 0000000000..9e2e3075eb --- /dev/null +++ b/vendor/webmozart/assert/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014 Bernhard Schussek + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/webmozart/assert/README.md b/vendor/webmozart/assert/README.md new file mode 100644 index 0000000000..3b2397a1ab --- /dev/null +++ b/vendor/webmozart/assert/README.md @@ -0,0 +1,287 @@ +Webmozart Assert +================ + +[![Latest Stable Version](https://poser.pugx.org/webmozart/assert/v/stable.svg)](https://packagist.org/packages/webmozart/assert) +[![Total Downloads](https://poser.pugx.org/webmozart/assert/downloads.svg)](https://packagist.org/packages/webmozart/assert) + +This library contains efficient assertions to test the input and output of +your methods. With these assertions, you can greatly reduce the amount of coding +needed to write a safe implementation. + +All assertions in the [`Assert`] class throw an `Webmozart\Assert\InvalidArgumentException` if +they fail. + +FAQ +--- + +**What's the difference to [beberlei/assert]?** + +This library is heavily inspired by Benjamin Eberlei's wonderful [assert package], +but fixes a usability issue with error messages that can't be fixed there without +breaking backwards compatibility. + +This package features usable error messages by default. However, you can also +easily write custom error messages: + +``` +Assert::string($path, 'The path is expected to be a string. Got: %s'); +``` + +In [beberlei/assert], the ordering of the `%s` placeholders is different for +every assertion. This package, on the contrary, provides consistent placeholder +ordering for all assertions: + +* `%s`: The tested value as string, e.g. `"/foo/bar"`. +* `%2$s`, `%3$s`, ...: Additional assertion-specific values, e.g. the + minimum/maximum length, allowed values, etc. + +Check the source code of the assertions to find out details about the additional +available placeholders. + +Installation +------------ + +Use [Composer] to install the package: + +```bash +composer require webmozart/assert +``` + +Example +------- + +```php +use Webmozart\Assert\Assert; + +class Employee +{ + public function __construct($id) + { + Assert::integer($id, 'The employee ID must be an integer. Got: %s'); + Assert::greaterThan($id, 0, 'The employee ID must be a positive integer. Got: %s'); + } +} +``` + +If you create an employee with an invalid ID, an exception is thrown: + +```php +new Employee('foobar'); +// => Webmozart\Assert\InvalidArgumentException: +// The employee ID must be an integer. Got: string + +new Employee(-10); +// => Webmozart\Assert\InvalidArgumentException: +// The employee ID must be a positive integer. Got: -10 +``` + +Assertions +---------- + +The [`Assert`] class provides the following assertions: + +### Type Assertions + +Method | Description +-------------------------------------------------------- | -------------------------------------------------- +`string($value, $message = '')` | Check that a value is a string +`stringNotEmpty($value, $message = '')` | Check that a value is a non-empty string +`integer($value, $message = '')` | Check that a value is an integer +`integerish($value, $message = '')` | Check that a value casts to an integer +`positiveInteger($value, $message = '')` | Check that a value is a positive (non-zero) integer +`float($value, $message = '')` | Check that a value is a float +`numeric($value, $message = '')` | Check that a value is numeric +`natural($value, $message= ''')` | Check that a value is a non-negative integer +`boolean($value, $message = '')` | Check that a value is a boolean +`scalar($value, $message = '')` | Check that a value is a scalar +`object($value, $message = '')` | Check that a value is an object +`resource($value, $type = null, $message = '')` | Check that a value is a resource +`isCallable($value, $message = '')` | Check that a value is a callable +`isArray($value, $message = '')` | Check that a value is an array +`isTraversable($value, $message = '')` (deprecated) | Check that a value is an array or a `\Traversable` +`isIterable($value, $message = '')` | Check that a value is an array or a `\Traversable` +`isCountable($value, $message = '')` | Check that a value is an array or a `\Countable` +`isInstanceOf($value, $class, $message = '')` | Check that a value is an `instanceof` a class +`isInstanceOfAny($value, array $classes, $message = '')` | Check that a value is an `instanceof` at least one class on the array of classes +`notInstanceOf($value, $class, $message = '')` | Check that a value is not an `instanceof` a class +`isAOf($value, $class, $message = '')` | Check that a value is of the class or has one of its parents +`isAnyOf($value, array $classes, $message = '')` | Check that a value is of at least one of the classes or has one of its parents +`isNotA($value, $class, $message = '')` | Check that a value is not of the class or has not one of its parents +`isArrayAccessible($value, $message = '')` | Check that a value can be accessed as an array +`uniqueValues($values, $message = '')` | Check that the given array contains unique values + +### Comparison Assertions + +Method | Description +----------------------------------------------- | ------------------------------------------------------------------ +`true($value, $message = '')` | Check that a value is `true` +`false($value, $message = '')` | Check that a value is `false` +`notFalse($value, $message = '')` | Check that a value is not `false` +`null($value, $message = '')` | Check that a value is `null` +`notNull($value, $message = '')` | Check that a value is not `null` +`isEmpty($value, $message = '')` | Check that a value is `empty()` +`notEmpty($value, $message = '')` | Check that a value is not `empty()` +`eq($value, $value2, $message = '')` | Check that a value equals another (`==`) +`notEq($value, $value2, $message = '')` | Check that a value does not equal another (`!=`) +`same($value, $value2, $message = '')` | Check that a value is identical to another (`===`) +`notSame($value, $value2, $message = '')` | Check that a value is not identical to another (`!==`) +`greaterThan($value, $value2, $message = '')` | Check that a value is greater than another +`greaterThanEq($value, $value2, $message = '')` | Check that a value is greater than or equal to another +`lessThan($value, $value2, $message = '')` | Check that a value is less than another +`lessThanEq($value, $value2, $message = '')` | Check that a value is less than or equal to another +`range($value, $min, $max, $message = '')` | Check that a value is within a range +`inArray($value, array $values, $message = '')` | Check that a value is one of a list of values +`oneOf($value, array $values, $message = '')` | Check that a value is one of a list of values (alias of `inArray`) + +### String Assertions + +You should check that a value is a string with `Assert::string()` before making +any of the following assertions. + +Method | Description +--------------------------------------------------- | ----------------------------------------------------------------- +`contains($value, $subString, $message = '')` | Check that a string contains a substring +`notContains($value, $subString, $message = '')` | Check that a string does not contain a substring +`startsWith($value, $prefix, $message = '')` | Check that a string has a prefix +`notStartsWith($value, $prefix, $message = '')` | Check that a string does not have a prefix +`startsWithLetter($value, $message = '')` | Check that a string starts with a letter +`endsWith($value, $suffix, $message = '')` | Check that a string has a suffix +`notEndsWith($value, $suffix, $message = '')` | Check that a string does not have a suffix +`regex($value, $pattern, $message = '')` | Check that a string matches a regular expression +`notRegex($value, $pattern, $message = '')` | Check that a string does not match a regular expression +`unicodeLetters($value, $message = '')` | Check that a string contains Unicode letters only +`alpha($value, $message = '')` | Check that a string contains letters only +`digits($value, $message = '')` | Check that a string contains digits only +`alnum($value, $message = '')` | Check that a string contains letters and digits only +`lower($value, $message = '')` | Check that a string contains lowercase characters only +`upper($value, $message = '')` | Check that a string contains uppercase characters only +`length($value, $length, $message = '')` | Check that a string has a certain number of characters +`minLength($value, $min, $message = '')` | Check that a string has at least a certain number of characters +`maxLength($value, $max, $message = '')` | Check that a string has at most a certain number of characters +`lengthBetween($value, $min, $max, $message = '')` | Check that a string has a length in the given range +`uuid($value, $message = '')` | Check that a string is a valid UUID +`ip($value, $message = '')` | Check that a string is a valid IP (either IPv4 or IPv6) +`ipv4($value, $message = '')` | Check that a string is a valid IPv4 +`ipv6($value, $message = '')` | Check that a string is a valid IPv6 +`email($value, $message = '')` | Check that a string is a valid e-mail address +`notWhitespaceOnly($value, $message = '')` | Check that a string contains at least one non-whitespace character + +### File Assertions + +Method | Description +----------------------------------- | -------------------------------------------------- +`fileExists($value, $message = '')` | Check that a value is an existing path +`file($value, $message = '')` | Check that a value is an existing file +`directory($value, $message = '')` | Check that a value is an existing directory +`readable($value, $message = '')` | Check that a value is a readable path +`writable($value, $message = '')` | Check that a value is a writable path + +### Object Assertions + +Method | Description +----------------------------------------------------- | -------------------------------------------------- +`classExists($value, $message = '')` | Check that a value is an existing class name +`subclassOf($value, $class, $message = '')` | Check that a class is a subclass of another +`interfaceExists($value, $message = '')` | Check that a value is an existing interface name +`implementsInterface($value, $class, $message = '')` | Check that a class implements an interface +`propertyExists($value, $property, $message = '')` | Check that a property exists in a class/object +`propertyNotExists($value, $property, $message = '')` | Check that a property does not exist in a class/object +`methodExists($value, $method, $message = '')` | Check that a method exists in a class/object +`methodNotExists($value, $method, $message = '')` | Check that a method does not exist in a class/object + +### Array Assertions + +Method | Description +-------------------------------------------------- | ------------------------------------------------------------------ +`keyExists($array, $key, $message = '')` | Check that a key exists in an array +`keyNotExists($array, $key, $message = '')` | Check that a key does not exist in an array +`validArrayKey($key, $message = '')` | Check that a value is a valid array key (int or string) +`count($array, $number, $message = '')` | Check that an array contains a specific number of elements +`minCount($array, $min, $message = '')` | Check that an array contains at least a certain number of elements +`maxCount($array, $max, $message = '')` | Check that an array contains at most a certain number of elements +`countBetween($array, $min, $max, $message = '')` | Check that an array has a count in the given range +`isList($array, $message = '')` | Check that an array is a non-associative list +`isNonEmptyList($array, $message = '')` | Check that an array is a non-associative list, and not empty +`isMap($array, $message = '')` | Check that an array is associative and has strings as keys +`isNonEmptyMap($array, $message = '')` | Check that an array is associative and has strings as keys, and is not empty + +### Function Assertions + +Method | Description +------------------------------------------- | ----------------------------------------------------------------------------------------------------- +`throws($closure, $class, $message = '')` | Check that a function throws a certain exception. Subclasses of the exception class will be accepted. + +### Collection Assertions + +All of the above assertions can be prefixed with `all*()` to test the contents +of an array or a `\Traversable`: + +```php +Assert::allIsInstanceOf($employees, 'Acme\Employee'); +``` + +### Nullable Assertions + +All of the above assertions can be prefixed with `nullOr*()` to run the +assertion only if it the value is not `null`: + +```php +Assert::nullOrString($middleName, 'The middle name must be a string or null. Got: %s'); +``` + +### Extending Assert + +The `Assert` class comes with a few methods, which can be overridden to change the class behaviour. You can also extend it to +add your own assertions. + +#### Overriding methods + +Overriding the following methods in your assertion class allows you to change the behaviour of the assertions: + +* `public static function __callStatic($name, $arguments)` + * This method is used to 'create' the `nullOr` and `all` versions of the assertions. +* `protected static function valueToString($value)` + * This method is used for error messages, to convert the value to a string value for displaying. You could use this for representing a value object with a `__toString` method for example. +* `protected static function typeToString($value)` + * This method is used for error messages, to convert the a value to a string representing its type. +* `protected static function strlen($value)` + * This method is used to calculate string length for relevant methods, using the `mb_strlen` if available and useful. +* `protected static function reportInvalidArgument($message)` + * This method is called when an assertion fails, with the specified error message. Here you can throw your own exception, or log something. + +## Static analysis support + +Where applicable, assertion functions are annotated to support Psalm's +[Assertion syntax](https://psalm.dev/docs/annotating_code/assertion_syntax/). +A dedicated [PHPStan Plugin](https://github.com/phpstan/phpstan-webmozart-assert) is +required for proper type support. + +Authors +------- + +* [Bernhard Schussek] a.k.a. [@webmozart] +* [The Community Contributors] + +Contribute +---------- + +Contributions to the package are always welcome! + +* Report any bugs or issues you find on the [issue tracker]. +* You can grab the source code at the package's [Git repository]. + +License +------- + +All contents of this package are licensed under the [MIT license]. + +[beberlei/assert]: https://github.com/beberlei/assert +[assert package]: https://github.com/beberlei/assert +[Composer]: https://getcomposer.org +[Bernhard Schussek]: https://webmozarts.com +[The Community Contributors]: https://github.com/webmozart/assert/graphs/contributors +[issue tracker]: https://github.com/webmozart/assert/issues +[Git repository]: https://github.com/webmozart/assert +[@webmozart]: https://twitter.com/webmozart +[MIT license]: LICENSE +[`Assert`]: src/Assert.php diff --git a/vendor/webmozart/assert/composer.json b/vendor/webmozart/assert/composer.json new file mode 100644 index 0000000000..b340452c7f --- /dev/null +++ b/vendor/webmozart/assert/composer.json @@ -0,0 +1,43 @@ +{ + "name": "webmozart/assert", + "description": "Assertions to validate method input/output with nice error messages.", + "license": "MIT", + "keywords": [ + "assert", + "check", + "validate" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "require": { + "php": "^7.2 || ^8.0", + "ext-ctype": "*" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Webmozart\\Assert\\Tests\\": "tests/", + "Webmozart\\Assert\\Bin\\": "bin/src" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + } +} diff --git a/vendor/webmozart/assert/src/Assert.php b/vendor/webmozart/assert/src/Assert.php new file mode 100644 index 0000000000..db1f3a51a3 --- /dev/null +++ b/vendor/webmozart/assert/src/Assert.php @@ -0,0 +1,2080 @@ +<?php + +/* + * This file is part of the webmozart/assert package. + * + * (c) Bernhard Schussek <bschussek@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Webmozart\Assert; + +use ArrayAccess; +use BadMethodCallException; +use Closure; +use Countable; +use DateTime; +use DateTimeImmutable; +use Exception; +use ResourceBundle; +use SimpleXMLElement; +use Throwable; +use Traversable; + +/** + * Efficient assertions to validate the input/output of your methods. + * + * @since 1.0 + * + * @author Bernhard Schussek <bschussek@gmail.com> + */ +class Assert +{ + use Mixin; + + /** + * @psalm-pure + * @psalm-assert string $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function string($value, $message = '') + { + if (!\is_string($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a string. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert non-empty-string $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function stringNotEmpty($value, $message = '') + { + static::string($value, $message); + static::notEq($value, '', $message); + } + + /** + * @psalm-pure + * @psalm-assert int $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function integer($value, $message = '') + { + if (!\is_int($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an integer. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert numeric $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function integerish($value, $message = '') + { + if (!\is_numeric($value) || $value != (int) $value) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an integerish value. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert positive-int $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function positiveInteger($value, $message = '') + { + if (!(\is_int($value) && $value > 0)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a positive integer. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert float $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function float($value, $message = '') + { + if (!\is_float($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a float. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert numeric $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function numeric($value, $message = '') + { + if (!\is_numeric($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a numeric. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert positive-int|0 $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function natural($value, $message = '') + { + if (!\is_int($value) || $value < 0) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a non-negative integer. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert bool $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function boolean($value, $message = '') + { + if (!\is_bool($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a boolean. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert scalar $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function scalar($value, $message = '') + { + if (!\is_scalar($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a scalar. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert object $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function object($value, $message = '') + { + if (!\is_object($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an object. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert resource $value + * + * @param mixed $value + * @param string|null $type type of resource this should be. @see https://www.php.net/manual/en/function.get-resource-type.php + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function resource($value, $type = null, $message = '') + { + if (!\is_resource($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a resource. Got: %s', + static::typeToString($value) + )); + } + + if ($type && $type !== \get_resource_type($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a resource of type %2$s. Got: %s', + static::typeToString($value), + $type + )); + } + } + + /** + * @psalm-pure + * @psalm-assert callable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isCallable($value, $message = '') + { + if (!\is_callable($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a callable. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert array $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isArray($value, $message = '') + { + if (!\is_array($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an array. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @deprecated use "isIterable" or "isInstanceOf" instead + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isTraversable($value, $message = '') + { + @\trigger_error( + \sprintf( + 'The "%s" assertion is deprecated. You should stop using it, as it will soon be removed in 2.0 version. Use "isIterable" or "isInstanceOf" instead.', + __METHOD__ + ), + \E_USER_DEPRECATED + ); + + if (!\is_array($value) && !($value instanceof Traversable)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a traversable. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert array|ArrayAccess $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isArrayAccessible($value, $message = '') + { + if (!\is_array($value) && !($value instanceof ArrayAccess)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an array accessible. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert countable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isCountable($value, $message = '') + { + if ( + !\is_array($value) + && !($value instanceof Countable) + && !($value instanceof ResourceBundle) + && !($value instanceof SimpleXMLElement) + ) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a countable. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isIterable($value, $message = '') + { + if (!\is_array($value) && !($value instanceof Traversable)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an iterable. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string<ExpectedType> $class + * @psalm-assert ExpectedType $value + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isInstanceOf($value, $class, $message = '') + { + if (!($value instanceof $class)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an instance of %2$s. Got: %s', + static::typeToString($value), + $class + )); + } + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string<ExpectedType> $class + * @psalm-assert !ExpectedType $value + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notInstanceOf($value, $class, $message = '') + { + if ($value instanceof $class) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an instance other than %2$s. Got: %s', + static::typeToString($value), + $class + )); + } + } + + /** + * @psalm-pure + * @psalm-param array<class-string> $classes + * + * @param mixed $value + * @param array<object|string> $classes + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isInstanceOfAny($value, array $classes, $message = '') + { + foreach ($classes as $class) { + if ($value instanceof $class) { + return; + } + } + + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an instance of any of %2$s. Got: %s', + static::typeToString($value), + \implode(', ', \array_map(array(static::class, 'valueToString'), $classes)) + )); + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string<ExpectedType> $class + * @psalm-assert ExpectedType|class-string<ExpectedType> $value + * + * @param object|string $value + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isAOf($value, $class, $message = '') + { + static::string($class, 'Expected class as a string. Got: %s'); + + if (!\is_a($value, $class, \is_string($value))) { + static::reportInvalidArgument(sprintf( + $message ?: 'Expected an instance of this class or to this class among its parents "%2$s". Got: %s', + static::valueToString($value), + $class + )); + } + } + + /** + * @psalm-pure + * @psalm-template UnexpectedType of object + * @psalm-param class-string<UnexpectedType> $class + * @psalm-assert !UnexpectedType $value + * @psalm-assert !class-string<UnexpectedType> $value + * + * @param object|string $value + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isNotA($value, $class, $message = '') + { + static::string($class, 'Expected class as a string. Got: %s'); + + if (\is_a($value, $class, \is_string($value))) { + static::reportInvalidArgument(sprintf( + $message ?: 'Expected an instance of this class or to this class among its parents other than "%2$s". Got: %s', + static::valueToString($value), + $class + )); + } + } + + /** + * @psalm-pure + * @psalm-param array<class-string> $classes + * + * @param object|string $value + * @param string[] $classes + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isAnyOf($value, array $classes, $message = '') + { + foreach ($classes as $class) { + static::string($class, 'Expected class as a string. Got: %s'); + + if (\is_a($value, $class, \is_string($value))) { + return; + } + } + + static::reportInvalidArgument(sprintf( + $message ?: 'Expected an instance of any of this classes or any of those classes among their parents "%2$s". Got: %s', + static::valueToString($value), + \implode(', ', $classes) + )); + } + + /** + * @psalm-pure + * @psalm-assert empty $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isEmpty($value, $message = '') + { + if (!empty($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an empty value. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert !empty $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notEmpty($value, $message = '') + { + if (empty($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a non-empty value. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function null($value, $message = '') + { + if (null !== $value) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected null. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert !null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notNull($value, $message = '') + { + if (null === $value) { + static::reportInvalidArgument( + $message ?: 'Expected a value other than null.' + ); + } + } + + /** + * @psalm-pure + * @psalm-assert true $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function true($value, $message = '') + { + if (true !== $value) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to be true. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert false $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function false($value, $message = '') + { + if (false !== $value) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to be false. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert !false $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notFalse($value, $message = '') + { + if (false === $value) { + static::reportInvalidArgument( + $message ?: 'Expected a value other than false.' + ); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function ip($value, $message = '') + { + if (false === \filter_var($value, \FILTER_VALIDATE_IP)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to be an IP. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function ipv4($value, $message = '') + { + if (false === \filter_var($value, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to be an IPv4. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function ipv6($value, $message = '') + { + if (false === \filter_var($value, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to be an IPv6. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function email($value, $message = '') + { + if (false === \filter_var($value, FILTER_VALIDATE_EMAIL)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to be a valid e-mail address. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * Does non strict comparisons on the items, so ['3', 3] will not pass the assertion. + * + * @param array $values + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function uniqueValues(array $values, $message = '') + { + $allValues = \count($values); + $uniqueValues = \count(\array_unique($values)); + + if ($allValues !== $uniqueValues) { + $difference = $allValues - $uniqueValues; + + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an array of unique values, but %s of them %s duplicated', + $difference, + (1 === $difference ? 'is' : 'are') + )); + } + } + + /** + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function eq($value, $expect, $message = '') + { + if ($expect != $value) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value equal to %2$s. Got: %s', + static::valueToString($value), + static::valueToString($expect) + )); + } + } + + /** + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notEq($value, $expect, $message = '') + { + if ($expect == $value) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a different value than %s.', + static::valueToString($expect) + )); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function same($value, $expect, $message = '') + { + if ($expect !== $value) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value identical to %2$s. Got: %s', + static::valueToString($value), + static::valueToString($expect) + )); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notSame($value, $expect, $message = '') + { + if ($expect === $value) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value not identical to %s.', + static::valueToString($expect) + )); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function greaterThan($value, $limit, $message = '') + { + if ($value <= $limit) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value greater than %2$s. Got: %s', + static::valueToString($value), + static::valueToString($limit) + )); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function greaterThanEq($value, $limit, $message = '') + { + if ($value < $limit) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value greater than or equal to %2$s. Got: %s', + static::valueToString($value), + static::valueToString($limit) + )); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function lessThan($value, $limit, $message = '') + { + if ($value >= $limit) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value less than %2$s. Got: %s', + static::valueToString($value), + static::valueToString($limit) + )); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function lessThanEq($value, $limit, $message = '') + { + if ($value > $limit) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value less than or equal to %2$s. Got: %s', + static::valueToString($value), + static::valueToString($limit) + )); + } + } + + /** + * Inclusive range, so Assert::(3, 3, 5) passes. + * + * @psalm-pure + * + * @param mixed $value + * @param mixed $min + * @param mixed $max + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function range($value, $min, $max, $message = '') + { + if ($value < $min || $value > $max) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value between %2$s and %3$s. Got: %s', + static::valueToString($value), + static::valueToString($min), + static::valueToString($max) + )); + } + } + + /** + * A more human-readable alias of Assert::inArray(). + * + * @psalm-pure + * + * @param mixed $value + * @param array $values + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function oneOf($value, array $values, $message = '') + { + static::inArray($value, $values, $message); + } + + /** + * Does strict comparison, so Assert::inArray(3, ['3']) does not pass the assertion. + * + * @psalm-pure + * + * @param mixed $value + * @param array $values + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function inArray($value, array $values, $message = '') + { + if (!\in_array($value, $values, true)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected one of: %2$s. Got: %s', + static::valueToString($value), + \implode(', ', \array_map(array(static::class, 'valueToString'), $values)) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $subString + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function contains($value, $subString, $message = '') + { + if (false === \strpos($value, $subString)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to contain %2$s. Got: %s', + static::valueToString($value), + static::valueToString($subString) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $subString + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notContains($value, $subString, $message = '') + { + if (false !== \strpos($value, $subString)) { + static::reportInvalidArgument(\sprintf( + $message ?: '%2$s was not expected to be contained in a value. Got: %s', + static::valueToString($value), + static::valueToString($subString) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notWhitespaceOnly($value, $message = '') + { + if (\preg_match('/^\s*$/', $value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a non-whitespace string. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $prefix + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function startsWith($value, $prefix, $message = '') + { + if (0 !== \strpos($value, $prefix)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to start with %2$s. Got: %s', + static::valueToString($value), + static::valueToString($prefix) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $prefix + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notStartsWith($value, $prefix, $message = '') + { + if (0 === \strpos($value, $prefix)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value not to start with %2$s. Got: %s', + static::valueToString($value), + static::valueToString($prefix) + )); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function startsWithLetter($value, $message = '') + { + static::string($value); + + $valid = isset($value[0]); + + if ($valid) { + $locale = \setlocale(LC_CTYPE, 0); + \setlocale(LC_CTYPE, 'C'); + $valid = \ctype_alpha($value[0]); + \setlocale(LC_CTYPE, $locale); + } + + if (!$valid) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to start with a letter. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $suffix + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function endsWith($value, $suffix, $message = '') + { + if ($suffix !== \substr($value, -\strlen($suffix))) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to end with %2$s. Got: %s', + static::valueToString($value), + static::valueToString($suffix) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $suffix + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notEndsWith($value, $suffix, $message = '') + { + if ($suffix === \substr($value, -\strlen($suffix))) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value not to end with %2$s. Got: %s', + static::valueToString($value), + static::valueToString($suffix) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $pattern + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function regex($value, $pattern, $message = '') + { + if (!\preg_match($pattern, $value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'The value %s does not match the expected pattern.', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $pattern + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notRegex($value, $pattern, $message = '') + { + if (\preg_match($pattern, $value, $matches, PREG_OFFSET_CAPTURE)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'The value %s matches the pattern %s (at offset %d).', + static::valueToString($value), + static::valueToString($pattern), + $matches[0][1] + )); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function unicodeLetters($value, $message = '') + { + static::string($value); + + if (!\preg_match('/^\p{L}+$/u', $value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to contain only Unicode letters. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function alpha($value, $message = '') + { + static::string($value); + + $locale = \setlocale(LC_CTYPE, 0); + \setlocale(LC_CTYPE, 'C'); + $valid = !\ctype_alpha($value); + \setlocale(LC_CTYPE, $locale); + + if ($valid) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to contain only letters. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function digits($value, $message = '') + { + $locale = \setlocale(LC_CTYPE, 0); + \setlocale(LC_CTYPE, 'C'); + $valid = !\ctype_digit($value); + \setlocale(LC_CTYPE, $locale); + + if ($valid) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to contain digits only. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function alnum($value, $message = '') + { + $locale = \setlocale(LC_CTYPE, 0); + \setlocale(LC_CTYPE, 'C'); + $valid = !\ctype_alnum($value); + \setlocale(LC_CTYPE, $locale); + + if ($valid) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to contain letters and digits only. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert lowercase-string $value + * + * @param string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function lower($value, $message = '') + { + $locale = \setlocale(LC_CTYPE, 0); + \setlocale(LC_CTYPE, 'C'); + $valid = !\ctype_lower($value); + \setlocale(LC_CTYPE, $locale); + + if ($valid) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to contain lowercase characters only. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert !lowercase-string $value + * + * @param string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function upper($value, $message = '') + { + $locale = \setlocale(LC_CTYPE, 0); + \setlocale(LC_CTYPE, 'C'); + $valid = !\ctype_upper($value); + \setlocale(LC_CTYPE, $locale); + + if ($valid) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to contain uppercase characters only. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param int $length + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function length($value, $length, $message = '') + { + if ($length !== static::strlen($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to contain %2$s characters. Got: %s', + static::valueToString($value), + $length + )); + } + } + + /** + * Inclusive min. + * + * @psalm-pure + * + * @param string $value + * @param int|float $min + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function minLength($value, $min, $message = '') + { + if (static::strlen($value) < $min) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to contain at least %2$s characters. Got: %s', + static::valueToString($value), + $min + )); + } + } + + /** + * Inclusive max. + * + * @psalm-pure + * + * @param string $value + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function maxLength($value, $max, $message = '') + { + if (static::strlen($value) > $max) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to contain at most %2$s characters. Got: %s', + static::valueToString($value), + $max + )); + } + } + + /** + * Inclusive , so Assert::lengthBetween('asd', 3, 5); passes the assertion. + * + * @psalm-pure + * + * @param string $value + * @param int|float $min + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function lengthBetween($value, $min, $max, $message = '') + { + $length = static::strlen($value); + + if ($length < $min || $length > $max) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to contain between %2$s and %3$s characters. Got: %s', + static::valueToString($value), + $min, + $max + )); + } + } + + /** + * Will also pass if $value is a directory, use Assert::file() instead if you need to be sure it is a file. + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function fileExists($value, $message = '') + { + static::string($value); + + if (!\file_exists($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'The file %s does not exist.', + static::valueToString($value) + )); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function file($value, $message = '') + { + static::fileExists($value, $message); + + if (!\is_file($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'The path %s is not a file.', + static::valueToString($value) + )); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function directory($value, $message = '') + { + static::fileExists($value, $message); + + if (!\is_dir($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'The path %s is no directory.', + static::valueToString($value) + )); + } + } + + /** + * @param string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function readable($value, $message = '') + { + if (!\is_readable($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'The path %s is not readable.', + static::valueToString($value) + )); + } + } + + /** + * @param string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function writable($value, $message = '') + { + if (!\is_writable($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'The path %s is not writable.', + static::valueToString($value) + )); + } + } + + /** + * @psalm-assert class-string $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function classExists($value, $message = '') + { + if (!\class_exists($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an existing class name. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string<ExpectedType> $class + * @psalm-assert class-string<ExpectedType>|ExpectedType $value + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function subclassOf($value, $class, $message = '') + { + if (!\is_subclass_of($value, $class)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a sub-class of %2$s. Got: %s', + static::valueToString($value), + static::valueToString($class) + )); + } + } + + /** + * @psalm-assert class-string $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function interfaceExists($value, $message = '') + { + if (!\interface_exists($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an existing interface name. got %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string<ExpectedType> $interface + * @psalm-assert class-string<ExpectedType> $value + * + * @param mixed $value + * @param mixed $interface + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function implementsInterface($value, $interface, $message = '') + { + if (!\in_array($interface, \class_implements($value))) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an implementation of %2$s. Got: %s', + static::valueToString($value), + static::valueToString($interface) + )); + } + } + + /** + * @psalm-pure + * @psalm-param class-string|object $classOrObject + * + * @param string|object $classOrObject + * @param mixed $property + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function propertyExists($classOrObject, $property, $message = '') + { + if (!\property_exists($classOrObject, $property)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected the property %s to exist.', + static::valueToString($property) + )); + } + } + + /** + * @psalm-pure + * @psalm-param class-string|object $classOrObject + * + * @param string|object $classOrObject + * @param mixed $property + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function propertyNotExists($classOrObject, $property, $message = '') + { + if (\property_exists($classOrObject, $property)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected the property %s to not exist.', + static::valueToString($property) + )); + } + } + + /** + * @psalm-pure + * @psalm-param class-string|object $classOrObject + * + * @param string|object $classOrObject + * @param mixed $method + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function methodExists($classOrObject, $method, $message = '') + { + if (!(\is_string($classOrObject) || \is_object($classOrObject)) || !\method_exists($classOrObject, $method)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected the method %s to exist.', + static::valueToString($method) + )); + } + } + + /** + * @psalm-pure + * @psalm-param class-string|object $classOrObject + * + * @param string|object $classOrObject + * @param mixed $method + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function methodNotExists($classOrObject, $method, $message = '') + { + if ((\is_string($classOrObject) || \is_object($classOrObject)) && \method_exists($classOrObject, $method)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected the method %s to not exist.', + static::valueToString($method) + )); + } + } + + /** + * @psalm-pure + * + * @param array $array + * @param string|int $key + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function keyExists($array, $key, $message = '') + { + if (!(isset($array[$key]) || \array_key_exists($key, $array))) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected the key %s to exist.', + static::valueToString($key) + )); + } + } + + /** + * @psalm-pure + * + * @param array $array + * @param string|int $key + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function keyNotExists($array, $key, $message = '') + { + if (isset($array[$key]) || \array_key_exists($key, $array)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected the key %s to not exist.', + static::valueToString($key) + )); + } + } + + /** + * Checks if a value is a valid array key (int or string). + * + * @psalm-pure + * @psalm-assert array-key $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function validArrayKey($value, $message = '') + { + if (!(\is_int($value) || \is_string($value))) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected string or integer. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * Does not check if $array is countable, this can generate a warning on php versions after 7.2. + * + * @param Countable|array $array + * @param int $number + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function count($array, $number, $message = '') + { + static::eq( + \count($array), + $number, + \sprintf( + $message ?: 'Expected an array to contain %d elements. Got: %d.', + $number, + \count($array) + ) + ); + } + + /** + * Does not check if $array is countable, this can generate a warning on php versions after 7.2. + * + * @param Countable|array $array + * @param int|float $min + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function minCount($array, $min, $message = '') + { + if (\count($array) < $min) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an array to contain at least %2$d elements. Got: %d', + \count($array), + $min + )); + } + } + + /** + * Does not check if $array is countable, this can generate a warning on php versions after 7.2. + * + * @param Countable|array $array + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function maxCount($array, $max, $message = '') + { + if (\count($array) > $max) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an array to contain at most %2$d elements. Got: %d', + \count($array), + $max + )); + } + } + + /** + * Does not check if $array is countable, this can generate a warning on php versions after 7.2. + * + * @param Countable|array $array + * @param int|float $min + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function countBetween($array, $min, $max, $message = '') + { + $count = \count($array); + + if ($count < $min || $count > $max) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an array to contain between %2$d and %3$d elements. Got: %d', + $count, + $min, + $max + )); + } + } + + /** + * @psalm-pure + * @psalm-assert list $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isList($array, $message = '') + { + if (!\is_array($array)) { + static::reportInvalidArgument( + $message ?: 'Expected list - non-associative array.' + ); + } + + if ($array === \array_values($array)) { + return; + } + + $nextKey = -1; + foreach ($array as $k => $v) { + if ($k !== ++$nextKey) { + static::reportInvalidArgument( + $message ?: 'Expected list - non-associative array.' + ); + } + } + } + + /** + * @psalm-pure + * @psalm-assert non-empty-list $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isNonEmptyList($array, $message = '') + { + static::isList($array, $message); + static::notEmpty($array, $message); + } + + /** + * @psalm-pure + * @psalm-template T + * @psalm-param mixed|array<T> $array + * @psalm-assert array<string, T> $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isMap($array, $message = '') + { + if ( + !\is_array($array) || + \array_keys($array) !== \array_filter(\array_keys($array), '\is_string') + ) { + static::reportInvalidArgument( + $message ?: 'Expected map - associative array with string keys.' + ); + } + } + + /** + * @psalm-pure + * @psalm-template T + * @psalm-param mixed|array<T> $array + * @psalm-assert array<string, T> $array + * @psalm-assert !empty $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isNonEmptyMap($array, $message = '') + { + static::isMap($array, $message); + static::notEmpty($array, $message); + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function uuid($value, $message = '') + { + $value = \str_replace(array('urn:', 'uuid:', '{', '}'), '', $value); + + // The nil UUID is special form of UUID that is specified to have all + // 128 bits set to zero. + if ('00000000-0000-0000-0000-000000000000' === $value) { + return; + } + + if (!\preg_match('/^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/', $value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Value %s is not a valid UUID.', + static::valueToString($value) + )); + } + } + + /** + * @psalm-param class-string<Throwable> $class + * + * @param Closure $expression + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function throws(Closure $expression, $class = 'Exception', $message = '') + { + static::string($class); + + $actual = 'none'; + + try { + $expression(); + } catch (Exception $e) { + $actual = \get_class($e); + if ($e instanceof $class) { + return; + } + } catch (Throwable $e) { + $actual = \get_class($e); + if ($e instanceof $class) { + return; + } + } + + static::reportInvalidArgument($message ?: \sprintf( + 'Expected to throw "%s", got "%s"', + $class, + $actual + )); + } + + /** + * @throws BadMethodCallException + */ + public static function __callStatic($name, $arguments) + { + if ('nullOr' === \substr($name, 0, 6)) { + if (null !== $arguments[0]) { + $method = \lcfirst(\substr($name, 6)); + \call_user_func_array(array(static::class, $method), $arguments); + } + + return; + } + + if ('all' === \substr($name, 0, 3)) { + static::isIterable($arguments[0]); + + $method = \lcfirst(\substr($name, 3)); + $args = $arguments; + + foreach ($arguments[0] as $entry) { + $args[0] = $entry; + + \call_user_func_array(array(static::class, $method), $args); + } + + return; + } + + throw new BadMethodCallException('No such method: '.$name); + } + + /** + * @param mixed $value + * + * @return string + */ + protected static function valueToString($value) + { + if (null === $value) { + return 'null'; + } + + if (true === $value) { + return 'true'; + } + + if (false === $value) { + return 'false'; + } + + if (\is_array($value)) { + return 'array'; + } + + if (\is_object($value)) { + if (\method_exists($value, '__toString')) { + return \get_class($value).': '.self::valueToString($value->__toString()); + } + + if ($value instanceof DateTime || $value instanceof DateTimeImmutable) { + return \get_class($value).': '.self::valueToString($value->format('c')); + } + + return \get_class($value); + } + + if (\is_resource($value)) { + return 'resource'; + } + + if (\is_string($value)) { + return '"'.$value.'"'; + } + + return (string) $value; + } + + /** + * @param mixed $value + * + * @return string + */ + protected static function typeToString($value) + { + return \is_object($value) ? \get_class($value) : \gettype($value); + } + + protected static function strlen($value) + { + if (!\function_exists('mb_detect_encoding')) { + return \strlen($value); + } + + if (false === $encoding = \mb_detect_encoding($value)) { + return \strlen($value); + } + + return \mb_strlen($value, $encoding); + } + + /** + * @param string $message + * + * @throws InvalidArgumentException + * + * @psalm-pure this method is not supposed to perform side-effects + * @psalm-return never + */ + protected static function reportInvalidArgument($message) + { + throw new InvalidArgumentException($message); + } + + private function __construct() + { + } +} diff --git a/vendor/webmozart/assert/src/InvalidArgumentException.php b/vendor/webmozart/assert/src/InvalidArgumentException.php new file mode 100644 index 0000000000..9d95a58c50 --- /dev/null +++ b/vendor/webmozart/assert/src/InvalidArgumentException.php @@ -0,0 +1,16 @@ +<?php + +/* + * This file is part of the webmozart/assert package. + * + * (c) Bernhard Schussek <bschussek@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Webmozart\Assert; + +class InvalidArgumentException extends \InvalidArgumentException +{ +} diff --git a/vendor/webmozart/assert/src/Mixin.php b/vendor/webmozart/assert/src/Mixin.php new file mode 100644 index 0000000000..0f0a75e330 --- /dev/null +++ b/vendor/webmozart/assert/src/Mixin.php @@ -0,0 +1,5089 @@ +<?php + +namespace Webmozart\Assert; + +use ArrayAccess; +use Closure; +use Countable; +use Throwable; + +/** + * This trait provides nurllOr*, all* and allNullOr* variants of assertion base methods. + * Do not use this trait directly: it will change, and is not designed for reuse. + */ +trait Mixin +{ + /** + * @psalm-pure + * @psalm-assert string|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrString($value, $message = '') + { + null === $value || static::string($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable<string> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allString($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::string($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable<string|null> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrString($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::string($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert non-empty-string|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrStringNotEmpty($value, $message = '') + { + null === $value || static::stringNotEmpty($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable<non-empty-string> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allStringNotEmpty($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::stringNotEmpty($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable<non-empty-string|null> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrStringNotEmpty($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::stringNotEmpty($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert int|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrInteger($value, $message = '') + { + null === $value || static::integer($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable<int> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allInteger($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::integer($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable<int|null> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrInteger($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::integer($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert numeric|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIntegerish($value, $message = '') + { + null === $value || static::integerish($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable<numeric> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIntegerish($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::integerish($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable<numeric|null> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIntegerish($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::integerish($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert positive-int|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrPositiveInteger($value, $message = '') + { + null === $value || static::positiveInteger($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable<positive-int> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allPositiveInteger($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::positiveInteger($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable<positive-int|null> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrPositiveInteger($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::positiveInteger($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert float|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrFloat($value, $message = '') + { + null === $value || static::float($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable<float> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allFloat($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::float($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable<float|null> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrFloat($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::float($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert numeric|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrNumeric($value, $message = '') + { + null === $value || static::numeric($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable<numeric> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNumeric($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::numeric($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable<numeric|null> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrNumeric($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::numeric($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert positive-int|0|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrNatural($value, $message = '') + { + null === $value || static::natural($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable<positive-int|0> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNatural($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::natural($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable<positive-int|0|null> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrNatural($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::natural($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert bool|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrBoolean($value, $message = '') + { + null === $value || static::boolean($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable<bool> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allBoolean($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::boolean($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable<bool|null> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrBoolean($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::boolean($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert scalar|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrScalar($value, $message = '') + { + null === $value || static::scalar($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable<scalar> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allScalar($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::scalar($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable<scalar|null> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrScalar($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::scalar($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert object|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrObject($value, $message = '') + { + null === $value || static::object($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable<object> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allObject($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::object($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable<object|null> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrObject($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::object($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert resource|null $value + * + * @param mixed $value + * @param string|null $type type of resource this should be. @see https://www.php.net/manual/en/function.get-resource-type.php + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrResource($value, $type = null, $message = '') + { + null === $value || static::resource($value, $type, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable<resource> $value + * + * @param mixed $value + * @param string|null $type type of resource this should be. @see https://www.php.net/manual/en/function.get-resource-type.php + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allResource($value, $type = null, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::resource($entry, $type, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable<resource|null> $value + * + * @param mixed $value + * @param string|null $type type of resource this should be. @see https://www.php.net/manual/en/function.get-resource-type.php + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrResource($value, $type = null, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::resource($entry, $type, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert callable|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsCallable($value, $message = '') + { + null === $value || static::isCallable($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable<callable> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsCallable($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::isCallable($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable<callable|null> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsCallable($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::isCallable($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert array|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsArray($value, $message = '') + { + null === $value || static::isArray($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable<array> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsArray($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::isArray($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable<array|null> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsArray($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::isArray($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable|null $value + * + * @deprecated use "isIterable" or "isInstanceOf" instead + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsTraversable($value, $message = '') + { + null === $value || static::isTraversable($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable<iterable> $value + * + * @deprecated use "isIterable" or "isInstanceOf" instead + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsTraversable($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::isTraversable($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable<iterable|null> $value + * + * @deprecated use "isIterable" or "isInstanceOf" instead + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsTraversable($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::isTraversable($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert array|ArrayAccess|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsArrayAccessible($value, $message = '') + { + null === $value || static::isArrayAccessible($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable<array|ArrayAccess> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsArrayAccessible($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::isArrayAccessible($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable<array|ArrayAccess|null> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsArrayAccessible($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::isArrayAccessible($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert countable|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsCountable($value, $message = '') + { + null === $value || static::isCountable($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable<countable> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsCountable($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::isCountable($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable<countable|null> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsCountable($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::isCountable($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsIterable($value, $message = '') + { + null === $value || static::isIterable($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable<iterable> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsIterable($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::isIterable($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable<iterable|null> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsIterable($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::isIterable($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string<ExpectedType> $class + * @psalm-assert ExpectedType|null $value + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsInstanceOf($value, $class, $message = '') + { + null === $value || static::isInstanceOf($value, $class, $message); + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string<ExpectedType> $class + * @psalm-assert iterable<ExpectedType> $value + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsInstanceOf($value, $class, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::isInstanceOf($entry, $class, $message); + } + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string<ExpectedType> $class + * @psalm-assert iterable<ExpectedType|null> $value + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsInstanceOf($value, $class, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::isInstanceOf($entry, $class, $message); + } + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string<ExpectedType> $class + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrNotInstanceOf($value, $class, $message = '') + { + null === $value || static::notInstanceOf($value, $class, $message); + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string<ExpectedType> $class + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNotInstanceOf($value, $class, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::notInstanceOf($entry, $class, $message); + } + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string<ExpectedType> $class + * @psalm-assert iterable<!ExpectedType|null> $value + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrNotInstanceOf($value, $class, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::notInstanceOf($entry, $class, $message); + } + } + + /** + * @psalm-pure + * @psalm-param array<class-string> $classes + * + * @param mixed $value + * @param array<object|string> $classes + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsInstanceOfAny($value, $classes, $message = '') + { + null === $value || static::isInstanceOfAny($value, $classes, $message); + } + + /** + * @psalm-pure + * @psalm-param array<class-string> $classes + * + * @param mixed $value + * @param array<object|string> $classes + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsInstanceOfAny($value, $classes, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::isInstanceOfAny($entry, $classes, $message); + } + } + + /** + * @psalm-pure + * @psalm-param array<class-string> $classes + * + * @param mixed $value + * @param array<object|string> $classes + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsInstanceOfAny($value, $classes, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::isInstanceOfAny($entry, $classes, $message); + } + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string<ExpectedType> $class + * @psalm-assert ExpectedType|class-string<ExpectedType>|null $value + * + * @param object|string|null $value + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsAOf($value, $class, $message = '') + { + null === $value || static::isAOf($value, $class, $message); + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string<ExpectedType> $class + * @psalm-assert iterable<ExpectedType|class-string<ExpectedType>> $value + * + * @param iterable<object|string> $value + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsAOf($value, $class, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::isAOf($entry, $class, $message); + } + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string<ExpectedType> $class + * @psalm-assert iterable<ExpectedType|class-string<ExpectedType>|null> $value + * + * @param iterable<object|string|null> $value + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsAOf($value, $class, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::isAOf($entry, $class, $message); + } + } + + /** + * @psalm-pure + * @psalm-template UnexpectedType of object + * @psalm-param class-string<UnexpectedType> $class + * + * @param object|string|null $value + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsNotA($value, $class, $message = '') + { + null === $value || static::isNotA($value, $class, $message); + } + + /** + * @psalm-pure + * @psalm-template UnexpectedType of object + * @psalm-param class-string<UnexpectedType> $class + * + * @param iterable<object|string> $value + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsNotA($value, $class, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::isNotA($entry, $class, $message); + } + } + + /** + * @psalm-pure + * @psalm-template UnexpectedType of object + * @psalm-param class-string<UnexpectedType> $class + * @psalm-assert iterable<!UnexpectedType|null> $value + * @psalm-assert iterable<!class-string<UnexpectedType>|null> $value + * + * @param iterable<object|string|null> $value + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsNotA($value, $class, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::isNotA($entry, $class, $message); + } + } + + /** + * @psalm-pure + * @psalm-param array<class-string> $classes + * + * @param object|string|null $value + * @param string[] $classes + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsAnyOf($value, $classes, $message = '') + { + null === $value || static::isAnyOf($value, $classes, $message); + } + + /** + * @psalm-pure + * @psalm-param array<class-string> $classes + * + * @param iterable<object|string> $value + * @param string[] $classes + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsAnyOf($value, $classes, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::isAnyOf($entry, $classes, $message); + } + } + + /** + * @psalm-pure + * @psalm-param array<class-string> $classes + * + * @param iterable<object|string|null> $value + * @param string[] $classes + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsAnyOf($value, $classes, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::isAnyOf($entry, $classes, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert empty $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsEmpty($value, $message = '') + { + null === $value || static::isEmpty($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable<empty> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsEmpty($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::isEmpty($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable<empty|null> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsEmpty($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::isEmpty($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrNotEmpty($value, $message = '') + { + null === $value || static::notEmpty($value, $message); + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNotEmpty($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::notEmpty($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable<!empty|null> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrNotEmpty($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::notEmpty($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable<null> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNull($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::null($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNotNull($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::notNull($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert true|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrTrue($value, $message = '') + { + null === $value || static::true($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable<true> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allTrue($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::true($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable<true|null> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrTrue($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::true($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert false|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrFalse($value, $message = '') + { + null === $value || static::false($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable<false> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allFalse($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::false($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable<false|null> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrFalse($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::false($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrNotFalse($value, $message = '') + { + null === $value || static::notFalse($value, $message); + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNotFalse($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::notFalse($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable<!false|null> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrNotFalse($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::notFalse($entry, $message); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIp($value, $message = '') + { + null === $value || static::ip($value, $message); + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIp($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::ip($entry, $message); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIp($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::ip($entry, $message); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIpv4($value, $message = '') + { + null === $value || static::ipv4($value, $message); + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIpv4($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::ipv4($entry, $message); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIpv4($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::ipv4($entry, $message); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIpv6($value, $message = '') + { + null === $value || static::ipv6($value, $message); + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIpv6($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::ipv6($entry, $message); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIpv6($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::ipv6($entry, $message); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrEmail($value, $message = '') + { + null === $value || static::email($value, $message); + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allEmail($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::email($entry, $message); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrEmail($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::email($entry, $message); + } + } + + /** + * @param array|null $values + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrUniqueValues($values, $message = '') + { + null === $values || static::uniqueValues($values, $message); + } + + /** + * @param iterable<array> $values + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allUniqueValues($values, $message = '') + { + static::isIterable($values); + + foreach ($values as $entry) { + static::uniqueValues($entry, $message); + } + } + + /** + * @param iterable<array|null> $values + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrUniqueValues($values, $message = '') + { + static::isIterable($values); + + foreach ($values as $entry) { + null === $entry || static::uniqueValues($entry, $message); + } + } + + /** + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrEq($value, $expect, $message = '') + { + null === $value || static::eq($value, $expect, $message); + } + + /** + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allEq($value, $expect, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::eq($entry, $expect, $message); + } + } + + /** + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrEq($value, $expect, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::eq($entry, $expect, $message); + } + } + + /** + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrNotEq($value, $expect, $message = '') + { + null === $value || static::notEq($value, $expect, $message); + } + + /** + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNotEq($value, $expect, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::notEq($entry, $expect, $message); + } + } + + /** + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrNotEq($value, $expect, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::notEq($entry, $expect, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrSame($value, $expect, $message = '') + { + null === $value || static::same($value, $expect, $message); + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allSame($value, $expect, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::same($entry, $expect, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrSame($value, $expect, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::same($entry, $expect, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrNotSame($value, $expect, $message = '') + { + null === $value || static::notSame($value, $expect, $message); + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNotSame($value, $expect, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::notSame($entry, $expect, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrNotSame($value, $expect, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::notSame($entry, $expect, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrGreaterThan($value, $limit, $message = '') + { + null === $value || static::greaterThan($value, $limit, $message); + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allGreaterThan($value, $limit, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::greaterThan($entry, $limit, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrGreaterThan($value, $limit, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::greaterThan($entry, $limit, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrGreaterThanEq($value, $limit, $message = '') + { + null === $value || static::greaterThanEq($value, $limit, $message); + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allGreaterThanEq($value, $limit, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::greaterThanEq($entry, $limit, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrGreaterThanEq($value, $limit, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::greaterThanEq($entry, $limit, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrLessThan($value, $limit, $message = '') + { + null === $value || static::lessThan($value, $limit, $message); + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allLessThan($value, $limit, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::lessThan($entry, $limit, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrLessThan($value, $limit, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::lessThan($entry, $limit, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrLessThanEq($value, $limit, $message = '') + { + null === $value || static::lessThanEq($value, $limit, $message); + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allLessThanEq($value, $limit, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::lessThanEq($entry, $limit, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrLessThanEq($value, $limit, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::lessThanEq($entry, $limit, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $min + * @param mixed $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrRange($value, $min, $max, $message = '') + { + null === $value || static::range($value, $min, $max, $message); + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $min + * @param mixed $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allRange($value, $min, $max, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::range($entry, $min, $max, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $min + * @param mixed $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrRange($value, $min, $max, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::range($entry, $min, $max, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param array $values + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrOneOf($value, $values, $message = '') + { + null === $value || static::oneOf($value, $values, $message); + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param array $values + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allOneOf($value, $values, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::oneOf($entry, $values, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param array $values + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrOneOf($value, $values, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::oneOf($entry, $values, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param array $values + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrInArray($value, $values, $message = '') + { + null === $value || static::inArray($value, $values, $message); + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param array $values + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allInArray($value, $values, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::inArray($entry, $values, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param array $values + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrInArray($value, $values, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::inArray($entry, $values, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param string $subString + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrContains($value, $subString, $message = '') + { + null === $value || static::contains($value, $subString, $message); + } + + /** + * @psalm-pure + * + * @param iterable<string> $value + * @param string $subString + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allContains($value, $subString, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::contains($entry, $subString, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable<string|null> $value + * @param string $subString + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrContains($value, $subString, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::contains($entry, $subString, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param string $subString + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrNotContains($value, $subString, $message = '') + { + null === $value || static::notContains($value, $subString, $message); + } + + /** + * @psalm-pure + * + * @param iterable<string> $value + * @param string $subString + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNotContains($value, $subString, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::notContains($entry, $subString, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable<string|null> $value + * @param string $subString + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrNotContains($value, $subString, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::notContains($entry, $subString, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrNotWhitespaceOnly($value, $message = '') + { + null === $value || static::notWhitespaceOnly($value, $message); + } + + /** + * @psalm-pure + * + * @param iterable<string> $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNotWhitespaceOnly($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::notWhitespaceOnly($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable<string|null> $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrNotWhitespaceOnly($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::notWhitespaceOnly($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param string $prefix + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrStartsWith($value, $prefix, $message = '') + { + null === $value || static::startsWith($value, $prefix, $message); + } + + /** + * @psalm-pure + * + * @param iterable<string> $value + * @param string $prefix + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allStartsWith($value, $prefix, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::startsWith($entry, $prefix, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable<string|null> $value + * @param string $prefix + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrStartsWith($value, $prefix, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::startsWith($entry, $prefix, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param string $prefix + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrNotStartsWith($value, $prefix, $message = '') + { + null === $value || static::notStartsWith($value, $prefix, $message); + } + + /** + * @psalm-pure + * + * @param iterable<string> $value + * @param string $prefix + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNotStartsWith($value, $prefix, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::notStartsWith($entry, $prefix, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable<string|null> $value + * @param string $prefix + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrNotStartsWith($value, $prefix, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::notStartsWith($entry, $prefix, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrStartsWithLetter($value, $message = '') + { + null === $value || static::startsWithLetter($value, $message); + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allStartsWithLetter($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::startsWithLetter($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrStartsWithLetter($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::startsWithLetter($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param string $suffix + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrEndsWith($value, $suffix, $message = '') + { + null === $value || static::endsWith($value, $suffix, $message); + } + + /** + * @psalm-pure + * + * @param iterable<string> $value + * @param string $suffix + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allEndsWith($value, $suffix, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::endsWith($entry, $suffix, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable<string|null> $value + * @param string $suffix + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrEndsWith($value, $suffix, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::endsWith($entry, $suffix, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param string $suffix + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrNotEndsWith($value, $suffix, $message = '') + { + null === $value || static::notEndsWith($value, $suffix, $message); + } + + /** + * @psalm-pure + * + * @param iterable<string> $value + * @param string $suffix + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNotEndsWith($value, $suffix, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::notEndsWith($entry, $suffix, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable<string|null> $value + * @param string $suffix + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrNotEndsWith($value, $suffix, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::notEndsWith($entry, $suffix, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param string $pattern + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrRegex($value, $pattern, $message = '') + { + null === $value || static::regex($value, $pattern, $message); + } + + /** + * @psalm-pure + * + * @param iterable<string> $value + * @param string $pattern + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allRegex($value, $pattern, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::regex($entry, $pattern, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable<string|null> $value + * @param string $pattern + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrRegex($value, $pattern, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::regex($entry, $pattern, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param string $pattern + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrNotRegex($value, $pattern, $message = '') + { + null === $value || static::notRegex($value, $pattern, $message); + } + + /** + * @psalm-pure + * + * @param iterable<string> $value + * @param string $pattern + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNotRegex($value, $pattern, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::notRegex($entry, $pattern, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable<string|null> $value + * @param string $pattern + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrNotRegex($value, $pattern, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::notRegex($entry, $pattern, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrUnicodeLetters($value, $message = '') + { + null === $value || static::unicodeLetters($value, $message); + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allUnicodeLetters($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::unicodeLetters($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrUnicodeLetters($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::unicodeLetters($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrAlpha($value, $message = '') + { + null === $value || static::alpha($value, $message); + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allAlpha($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::alpha($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrAlpha($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::alpha($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrDigits($value, $message = '') + { + null === $value || static::digits($value, $message); + } + + /** + * @psalm-pure + * + * @param iterable<string> $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allDigits($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::digits($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable<string|null> $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrDigits($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::digits($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrAlnum($value, $message = '') + { + null === $value || static::alnum($value, $message); + } + + /** + * @psalm-pure + * + * @param iterable<string> $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allAlnum($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::alnum($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable<string|null> $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrAlnum($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::alnum($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert lowercase-string|null $value + * + * @param string|null $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrLower($value, $message = '') + { + null === $value || static::lower($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable<lowercase-string> $value + * + * @param iterable<string> $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allLower($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::lower($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable<lowercase-string|null> $value + * + * @param iterable<string|null> $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrLower($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::lower($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrUpper($value, $message = '') + { + null === $value || static::upper($value, $message); + } + + /** + * @psalm-pure + * + * @param iterable<string> $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allUpper($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::upper($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable<!lowercase-string|null> $value + * + * @param iterable<string|null> $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrUpper($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::upper($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param int $length + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrLength($value, $length, $message = '') + { + null === $value || static::length($value, $length, $message); + } + + /** + * @psalm-pure + * + * @param iterable<string> $value + * @param int $length + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allLength($value, $length, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::length($entry, $length, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable<string|null> $value + * @param int $length + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrLength($value, $length, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::length($entry, $length, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param int|float $min + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrMinLength($value, $min, $message = '') + { + null === $value || static::minLength($value, $min, $message); + } + + /** + * @psalm-pure + * + * @param iterable<string> $value + * @param int|float $min + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allMinLength($value, $min, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::minLength($entry, $min, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable<string|null> $value + * @param int|float $min + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrMinLength($value, $min, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::minLength($entry, $min, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrMaxLength($value, $max, $message = '') + { + null === $value || static::maxLength($value, $max, $message); + } + + /** + * @psalm-pure + * + * @param iterable<string> $value + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allMaxLength($value, $max, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::maxLength($entry, $max, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable<string|null> $value + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrMaxLength($value, $max, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::maxLength($entry, $max, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param int|float $min + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrLengthBetween($value, $min, $max, $message = '') + { + null === $value || static::lengthBetween($value, $min, $max, $message); + } + + /** + * @psalm-pure + * + * @param iterable<string> $value + * @param int|float $min + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allLengthBetween($value, $min, $max, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::lengthBetween($entry, $min, $max, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable<string|null> $value + * @param int|float $min + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrLengthBetween($value, $min, $max, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::lengthBetween($entry, $min, $max, $message); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrFileExists($value, $message = '') + { + null === $value || static::fileExists($value, $message); + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allFileExists($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::fileExists($entry, $message); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrFileExists($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::fileExists($entry, $message); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrFile($value, $message = '') + { + null === $value || static::file($value, $message); + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allFile($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::file($entry, $message); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrFile($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::file($entry, $message); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrDirectory($value, $message = '') + { + null === $value || static::directory($value, $message); + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allDirectory($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::directory($entry, $message); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrDirectory($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::directory($entry, $message); + } + } + + /** + * @param string|null $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrReadable($value, $message = '') + { + null === $value || static::readable($value, $message); + } + + /** + * @param iterable<string> $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allReadable($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::readable($entry, $message); + } + } + + /** + * @param iterable<string|null> $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrReadable($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::readable($entry, $message); + } + } + + /** + * @param string|null $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrWritable($value, $message = '') + { + null === $value || static::writable($value, $message); + } + + /** + * @param iterable<string> $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allWritable($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::writable($entry, $message); + } + } + + /** + * @param iterable<string|null> $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrWritable($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::writable($entry, $message); + } + } + + /** + * @psalm-assert class-string|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrClassExists($value, $message = '') + { + null === $value || static::classExists($value, $message); + } + + /** + * @psalm-assert iterable<class-string> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allClassExists($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::classExists($entry, $message); + } + } + + /** + * @psalm-assert iterable<class-string|null> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrClassExists($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::classExists($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string<ExpectedType> $class + * @psalm-assert class-string<ExpectedType>|ExpectedType|null $value + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrSubclassOf($value, $class, $message = '') + { + null === $value || static::subclassOf($value, $class, $message); + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string<ExpectedType> $class + * @psalm-assert iterable<class-string<ExpectedType>|ExpectedType> $value + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allSubclassOf($value, $class, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::subclassOf($entry, $class, $message); + } + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string<ExpectedType> $class + * @psalm-assert iterable<class-string<ExpectedType>|ExpectedType|null> $value + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrSubclassOf($value, $class, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::subclassOf($entry, $class, $message); + } + } + + /** + * @psalm-assert class-string|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrInterfaceExists($value, $message = '') + { + null === $value || static::interfaceExists($value, $message); + } + + /** + * @psalm-assert iterable<class-string> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allInterfaceExists($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::interfaceExists($entry, $message); + } + } + + /** + * @psalm-assert iterable<class-string|null> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrInterfaceExists($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::interfaceExists($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string<ExpectedType> $interface + * @psalm-assert class-string<ExpectedType>|null $value + * + * @param mixed $value + * @param mixed $interface + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrImplementsInterface($value, $interface, $message = '') + { + null === $value || static::implementsInterface($value, $interface, $message); + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string<ExpectedType> $interface + * @psalm-assert iterable<class-string<ExpectedType>> $value + * + * @param mixed $value + * @param mixed $interface + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allImplementsInterface($value, $interface, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::implementsInterface($entry, $interface, $message); + } + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string<ExpectedType> $interface + * @psalm-assert iterable<class-string<ExpectedType>|null> $value + * + * @param mixed $value + * @param mixed $interface + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrImplementsInterface($value, $interface, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::implementsInterface($entry, $interface, $message); + } + } + + /** + * @psalm-pure + * @psalm-param class-string|object|null $classOrObject + * + * @param string|object|null $classOrObject + * @param mixed $property + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrPropertyExists($classOrObject, $property, $message = '') + { + null === $classOrObject || static::propertyExists($classOrObject, $property, $message); + } + + /** + * @psalm-pure + * @psalm-param iterable<class-string|object> $classOrObject + * + * @param iterable<string|object> $classOrObject + * @param mixed $property + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allPropertyExists($classOrObject, $property, $message = '') + { + static::isIterable($classOrObject); + + foreach ($classOrObject as $entry) { + static::propertyExists($entry, $property, $message); + } + } + + /** + * @psalm-pure + * @psalm-param iterable<class-string|object|null> $classOrObject + * + * @param iterable<string|object|null> $classOrObject + * @param mixed $property + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrPropertyExists($classOrObject, $property, $message = '') + { + static::isIterable($classOrObject); + + foreach ($classOrObject as $entry) { + null === $entry || static::propertyExists($entry, $property, $message); + } + } + + /** + * @psalm-pure + * @psalm-param class-string|object|null $classOrObject + * + * @param string|object|null $classOrObject + * @param mixed $property + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrPropertyNotExists($classOrObject, $property, $message = '') + { + null === $classOrObject || static::propertyNotExists($classOrObject, $property, $message); + } + + /** + * @psalm-pure + * @psalm-param iterable<class-string|object> $classOrObject + * + * @param iterable<string|object> $classOrObject + * @param mixed $property + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allPropertyNotExists($classOrObject, $property, $message = '') + { + static::isIterable($classOrObject); + + foreach ($classOrObject as $entry) { + static::propertyNotExists($entry, $property, $message); + } + } + + /** + * @psalm-pure + * @psalm-param iterable<class-string|object|null> $classOrObject + * + * @param iterable<string|object|null> $classOrObject + * @param mixed $property + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrPropertyNotExists($classOrObject, $property, $message = '') + { + static::isIterable($classOrObject); + + foreach ($classOrObject as $entry) { + null === $entry || static::propertyNotExists($entry, $property, $message); + } + } + + /** + * @psalm-pure + * @psalm-param class-string|object|null $classOrObject + * + * @param string|object|null $classOrObject + * @param mixed $method + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrMethodExists($classOrObject, $method, $message = '') + { + null === $classOrObject || static::methodExists($classOrObject, $method, $message); + } + + /** + * @psalm-pure + * @psalm-param iterable<class-string|object> $classOrObject + * + * @param iterable<string|object> $classOrObject + * @param mixed $method + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allMethodExists($classOrObject, $method, $message = '') + { + static::isIterable($classOrObject); + + foreach ($classOrObject as $entry) { + static::methodExists($entry, $method, $message); + } + } + + /** + * @psalm-pure + * @psalm-param iterable<class-string|object|null> $classOrObject + * + * @param iterable<string|object|null> $classOrObject + * @param mixed $method + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrMethodExists($classOrObject, $method, $message = '') + { + static::isIterable($classOrObject); + + foreach ($classOrObject as $entry) { + null === $entry || static::methodExists($entry, $method, $message); + } + } + + /** + * @psalm-pure + * @psalm-param class-string|object|null $classOrObject + * + * @param string|object|null $classOrObject + * @param mixed $method + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrMethodNotExists($classOrObject, $method, $message = '') + { + null === $classOrObject || static::methodNotExists($classOrObject, $method, $message); + } + + /** + * @psalm-pure + * @psalm-param iterable<class-string|object> $classOrObject + * + * @param iterable<string|object> $classOrObject + * @param mixed $method + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allMethodNotExists($classOrObject, $method, $message = '') + { + static::isIterable($classOrObject); + + foreach ($classOrObject as $entry) { + static::methodNotExists($entry, $method, $message); + } + } + + /** + * @psalm-pure + * @psalm-param iterable<class-string|object|null> $classOrObject + * + * @param iterable<string|object|null> $classOrObject + * @param mixed $method + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrMethodNotExists($classOrObject, $method, $message = '') + { + static::isIterable($classOrObject); + + foreach ($classOrObject as $entry) { + null === $entry || static::methodNotExists($entry, $method, $message); + } + } + + /** + * @psalm-pure + * + * @param array|null $array + * @param string|int $key + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrKeyExists($array, $key, $message = '') + { + null === $array || static::keyExists($array, $key, $message); + } + + /** + * @psalm-pure + * + * @param iterable<array> $array + * @param string|int $key + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allKeyExists($array, $key, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + static::keyExists($entry, $key, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable<array|null> $array + * @param string|int $key + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrKeyExists($array, $key, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + null === $entry || static::keyExists($entry, $key, $message); + } + } + + /** + * @psalm-pure + * + * @param array|null $array + * @param string|int $key + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrKeyNotExists($array, $key, $message = '') + { + null === $array || static::keyNotExists($array, $key, $message); + } + + /** + * @psalm-pure + * + * @param iterable<array> $array + * @param string|int $key + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allKeyNotExists($array, $key, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + static::keyNotExists($entry, $key, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable<array|null> $array + * @param string|int $key + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrKeyNotExists($array, $key, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + null === $entry || static::keyNotExists($entry, $key, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert array-key|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrValidArrayKey($value, $message = '') + { + null === $value || static::validArrayKey($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable<array-key> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allValidArrayKey($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::validArrayKey($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable<array-key|null> $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrValidArrayKey($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::validArrayKey($entry, $message); + } + } + + /** + * @param Countable|array|null $array + * @param int $number + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrCount($array, $number, $message = '') + { + null === $array || static::count($array, $number, $message); + } + + /** + * @param iterable<Countable|array> $array + * @param int $number + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allCount($array, $number, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + static::count($entry, $number, $message); + } + } + + /** + * @param iterable<Countable|array|null> $array + * @param int $number + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrCount($array, $number, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + null === $entry || static::count($entry, $number, $message); + } + } + + /** + * @param Countable|array|null $array + * @param int|float $min + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrMinCount($array, $min, $message = '') + { + null === $array || static::minCount($array, $min, $message); + } + + /** + * @param iterable<Countable|array> $array + * @param int|float $min + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allMinCount($array, $min, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + static::minCount($entry, $min, $message); + } + } + + /** + * @param iterable<Countable|array|null> $array + * @param int|float $min + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrMinCount($array, $min, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + null === $entry || static::minCount($entry, $min, $message); + } + } + + /** + * @param Countable|array|null $array + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrMaxCount($array, $max, $message = '') + { + null === $array || static::maxCount($array, $max, $message); + } + + /** + * @param iterable<Countable|array> $array + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allMaxCount($array, $max, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + static::maxCount($entry, $max, $message); + } + } + + /** + * @param iterable<Countable|array|null> $array + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrMaxCount($array, $max, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + null === $entry || static::maxCount($entry, $max, $message); + } + } + + /** + * @param Countable|array|null $array + * @param int|float $min + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrCountBetween($array, $min, $max, $message = '') + { + null === $array || static::countBetween($array, $min, $max, $message); + } + + /** + * @param iterable<Countable|array> $array + * @param int|float $min + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allCountBetween($array, $min, $max, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + static::countBetween($entry, $min, $max, $message); + } + } + + /** + * @param iterable<Countable|array|null> $array + * @param int|float $min + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrCountBetween($array, $min, $max, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + null === $entry || static::countBetween($entry, $min, $max, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert list|null $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsList($array, $message = '') + { + null === $array || static::isList($array, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable<list> $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsList($array, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + static::isList($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable<list|null> $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsList($array, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + null === $entry || static::isList($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert non-empty-list|null $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsNonEmptyList($array, $message = '') + { + null === $array || static::isNonEmptyList($array, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable<non-empty-list> $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsNonEmptyList($array, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + static::isNonEmptyList($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable<non-empty-list|null> $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsNonEmptyList($array, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + null === $entry || static::isNonEmptyList($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-template T + * @psalm-param mixed|array<T>|null $array + * @psalm-assert array<string, T>|null $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsMap($array, $message = '') + { + null === $array || static::isMap($array, $message); + } + + /** + * @psalm-pure + * @psalm-template T + * @psalm-param iterable<mixed|array<T>> $array + * @psalm-assert iterable<array<string, T>> $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsMap($array, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + static::isMap($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-template T + * @psalm-param iterable<mixed|array<T>|null> $array + * @psalm-assert iterable<array<string, T>|null> $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsMap($array, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + null === $entry || static::isMap($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-template T + * @psalm-param mixed|array<T>|null $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsNonEmptyMap($array, $message = '') + { + null === $array || static::isNonEmptyMap($array, $message); + } + + /** + * @psalm-pure + * @psalm-template T + * @psalm-param iterable<mixed|array<T>> $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsNonEmptyMap($array, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + static::isNonEmptyMap($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-template T + * @psalm-param iterable<mixed|array<T>|null> $array + * @psalm-assert iterable<array<string, T>|null> $array + * @psalm-assert iterable<!empty|null> $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsNonEmptyMap($array, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + null === $entry || static::isNonEmptyMap($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrUuid($value, $message = '') + { + null === $value || static::uuid($value, $message); + } + + /** + * @psalm-pure + * + * @param iterable<string> $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allUuid($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::uuid($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable<string|null> $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrUuid($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::uuid($entry, $message); + } + } + + /** + * @psalm-param class-string<Throwable> $class + * + * @param Closure|null $expression + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrThrows($expression, $class = 'Exception', $message = '') + { + null === $expression || static::throws($expression, $class, $message); + } + + /** + * @psalm-param class-string<Throwable> $class + * + * @param iterable<Closure> $expression + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allThrows($expression, $class = 'Exception', $message = '') + { + static::isIterable($expression); + + foreach ($expression as $entry) { + static::throws($entry, $class, $message); + } + } + + /** + * @psalm-param class-string<Throwable> $class + * + * @param iterable<Closure|null> $expression + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrThrows($expression, $class = 'Exception', $message = '') + { + static::isIterable($expression); + + foreach ($expression as $entry) { + null === $entry || static::throws($entry, $class, $message); + } + } +} diff --git a/vendor/webmozart/path-util/.gitignore b/vendor/webmozart/path-util/.gitignore new file mode 100644 index 0000000000..3a9875b460 --- /dev/null +++ b/vendor/webmozart/path-util/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +composer.lock diff --git a/vendor/webmozart/path-util/.styleci.yml b/vendor/webmozart/path-util/.styleci.yml new file mode 100644 index 0000000000..295fafec0a --- /dev/null +++ b/vendor/webmozart/path-util/.styleci.yml @@ -0,0 +1,8 @@ +preset: symfony + +enabled: + - ordered_use + - strict + +disabled: + - empty_return diff --git a/vendor/webmozart/path-util/.travis.yml b/vendor/webmozart/path-util/.travis.yml new file mode 100644 index 0000000000..d103428ec2 --- /dev/null +++ b/vendor/webmozart/path-util/.travis.yml @@ -0,0 +1,29 @@ +language: php + +sudo: false + +cache: + directories: + - $HOME/.composer/cache/files + +matrix: + include: + - php: 5.3 + - php: 5.4 + - php: 5.5 + - php: 5.6 + - php: 5.6 + env: COMPOSER_FLAGS='--prefer-lowest --prefer-stable' + - php: hhvm + - php: nightly + allow_failures: + - php: hhvm + - php: nightly + fast_finish: true + +install: composer update $COMPOSER_FLAGS -n + +script: vendor/bin/phpunit --verbose --coverage-clover=coverage.clover + +after_script: + - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then wget https://scrutinizer-ci.com/ocular.phar && php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi;' diff --git a/vendor/webmozart/path-util/CHANGELOG.md b/vendor/webmozart/path-util/CHANGELOG.md new file mode 100644 index 0000000000..b669a191cf --- /dev/null +++ b/vendor/webmozart/path-util/CHANGELOG.md @@ -0,0 +1,68 @@ +Changelog +========= + +* 2.3.0 (2015-12-17) + + * added `Url::makeRelative()` for calculating relative paths between URLs + * fixed `Path::makeRelative()` to trim leading dots when moving outside of + the base path + +* 2.2.3 (2015-10-05) + + * fixed `Path::makeRelative()` to produce `..` when called with the parent + directory of a path + +* 2.2.2 (2015-08-24) + + * `Path::makeAbsolute()` does not fail anymore if an absolute path is passed + with a different root (partition) than the base path + +* 2.2.1 (2015-08-24) + + * fixed minimum versions in composer.json + +* 2.2.0 (2015-08-14) + + * added `Path::normalize()` + +* 2.1.0 (2015-07-14) + + * `Path::canonicalize()` now turns `~` into the user's home directory on + Unix and Windows 8 or later. + +* 2.0.0 (2015-05-21) + + * added support for streams, e.g. "phar://C:/path/to/file" + * added `Path::join()` + * all `Path` methods now throw exceptions if parameters with invalid types are + passed + * added an internal buffer to `Path::canonicalize()` in order to increase the + performance of the `Path` class + +* 1.1.0 (2015-03-19) + + * added `Path::getFilename()` + * added `Path::getFilenameWithoutExtension()` + * added `Path::getExtension()` + * added `Path::hasExtension()` + * added `Path::changeExtension()` + * `Path::makeRelative()` now works when the absolute path and the base path + have equal directory names beneath different base directories + (e.g. "/webmozart/css/style.css" relative to "/puli/css") + +* 1.0.2 (2015-01-12) + + * `Path::makeAbsolute()` fails now if the base path is not absolute + * `Path::makeRelative()` now works when a relative path is passed and the base + path is empty + +* 1.0.1 (2014-12-03) + + * Added PHP 5.6 to Travis. + * Fixed bug in `Path::makeRelative()` when first argument is shorter than second + * Made HHVM compatibility mandatory in .travis.yml + * Added PHP 5.3.3 to travis.yml + +* 1.0.0 (2014-11-26) + + * first release diff --git a/vendor/webmozart/path-util/LICENSE b/vendor/webmozart/path-util/LICENSE new file mode 100644 index 0000000000..9e2e3075eb --- /dev/null +++ b/vendor/webmozart/path-util/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014 Bernhard Schussek + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/webmozart/path-util/README.md b/vendor/webmozart/path-util/README.md new file mode 100644 index 0000000000..de86ca112e --- /dev/null +++ b/vendor/webmozart/path-util/README.md @@ -0,0 +1,143 @@ +File Path Utility +================= + +[![Build Status](https://travis-ci.org/webmozart/path-util.svg?branch=2.3.0)](https://travis-ci.org/webmozart/path-util) +[![Build status](https://ci.appveyor.com/api/projects/status/d5uuypr6p162gpxf/branch/master?svg=true)](https://ci.appveyor.com/project/webmozart/path-util/branch/master) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/webmozart/path-util/badges/quality-score.png?b=2.3.0)](https://scrutinizer-ci.com/g/webmozart/path-util/?branch=2.3.0) +[![Latest Stable Version](https://poser.pugx.org/webmozart/path-util/v/stable.svg)](https://packagist.org/packages/webmozart/path-util) +[![Total Downloads](https://poser.pugx.org/webmozart/path-util/downloads.svg)](https://packagist.org/packages/webmozart/path-util) +[![Dependency Status](https://www.versioneye.com/php/webmozart:path-util/2.3.0/badge.svg)](https://www.versioneye.com/php/webmozart:path-util/2.3.0) + +Latest release: [2.3.0](https://packagist.org/packages/webmozart/path-util#2.3.0) + +PHP >= 5.3.3 + +This package provides robust, cross-platform utility functions for normalizing, +comparing and modifying file paths and URLs. + +Installation +------------ + +The utility can be installed with [Composer]: + +``` +$ composer require webmozart/path-util +``` + +Usage +----- + +Use the `Path` class to handle file paths: + +```php +use Webmozart\PathUtil\Path; + +echo Path::canonicalize('/var/www/vhost/webmozart/../config.ini'); +// => /var/www/vhost/config.ini + +echo Path::canonicalize('C:\Programs\Webmozart\..\config.ini'); +// => C:/Programs/config.ini + +echo Path::canonicalize('~/config.ini'); +// => /home/webmozart/config.ini + +echo Path::makeAbsolute('config/config.yml', '/var/www/project'); +// => /var/www/project/config/config.yml + +echo Path::makeRelative('/var/www/project/config/config.yml', '/var/www/project/uploads'); +// => ../config/config.yml + +$paths = array( + '/var/www/vhosts/project/httpdocs/config/config.yml', + '/var/www/vhosts/project/httpdocs/images/banana.gif', + '/var/www/vhosts/project/httpdocs/uploads/../images/nicer-banana.gif', +); + +Path::getLongestCommonBasePath($paths); +// => /var/www/vhosts/project/httpdocs + +Path::getFilename('/views/index.html.twig'); +// => index.html.twig + +Path::getFilenameWithoutExtension('/views/index.html.twig'); +// => index.html + +Path::getFilenameWithoutExtension('/views/index.html.twig', 'html.twig'); +Path::getFilenameWithoutExtension('/views/index.html.twig', '.html.twig'); +// => index + +Path::getExtension('/views/index.html.twig'); +// => twig + +Path::hasExtension('/views/index.html.twig'); +// => true + +Path::hasExtension('/views/index.html.twig', 'twig'); +// => true + +Path::hasExtension('/images/profile.jpg', array('jpg', 'png', 'gif')); +// => true + +Path::changeExtension('/images/profile.jpeg', 'jpg'); +// => /images/profile.jpg + +Path::join('phar://C:/Documents', 'projects/my-project.phar', 'composer.json'); +// => phar://C:/Documents/projects/my-project.phar/composer.json + +Path::getHomeDirectory(); +// => /home/webmozart +``` + +Use the `Url` class to handle URLs: + +```php +use Webmozart\PathUtil\Url; + +echo Url::makeRelative('http://example.com/css/style.css', 'http://example.com/puli'); +// => ../css/style.css + +echo Url::makeRelative('http://cdn.example.com/css/style.css', 'http://example.com/puli'); +// => http://cdn.example.com/css/style.css +``` + +Learn more in the [Documentation] and the [API Docs]. + +Authors +------- + +* [Bernhard Schussek] a.k.a. [@webmozart] +* [The Community Contributors] + +Documentation +------------- + +Read the [Documentation] if you want to learn more about the contained functions. + +Contribute +---------- + +Contributions are always welcome! + +* Report any bugs or issues you find on the [issue tracker]. +* You can grab the source code at the [Git repository]. + +Support +------- + +If you are having problems, send a mail to bschussek@gmail.com or shout out to +[@webmozart] on Twitter. + +License +------- + +All contents of this package are licensed under the [MIT license]. + +[Bernhard Schussek]: http://webmozarts.com +[The Community Contributors]: https://github.com/webmozart/path-util/graphs/contributors +[Composer]: https://getcomposer.org +[Documentation]: docs/usage.md +[API Docs]: https://webmozart.github.io/path-util/api/latest/class-Webmozart.PathUtil.Path.html +[issue tracker]: https://github.com/webmozart/path-util/issues +[Git repository]: https://github.com/webmozart/path-util +[@webmozart]: https://twitter.com/webmozart +[MIT license]: LICENSE diff --git a/vendor/webmozart/path-util/appveyor.yml b/vendor/webmozart/path-util/appveyor.yml new file mode 100644 index 0000000000..e32482d405 --- /dev/null +++ b/vendor/webmozart/path-util/appveyor.yml @@ -0,0 +1,34 @@ +build: false +shallow_clone: true +platform: x86 +clone_folder: c:\projects\webmozart\path-util + +cache: + - '%LOCALAPPDATA%\Composer\files' + +init: + - SET PATH=C:\Program Files\OpenSSL;c:\tools\php;%PATH% + +environment: + matrix: + - COMPOSER_FLAGS: "" + - COMPOSER_FLAGS: --prefer-lowest --prefer-stable + +install: + - cinst -y OpenSSL.Light + - cinst -y php + - cd c:\tools\php + - copy php.ini-production php.ini /Y + - echo date.timezone="UTC" >> php.ini + - echo extension_dir=ext >> php.ini + - echo extension=php_openssl.dll >> php.ini + - echo extension=php_mbstring.dll >> php.ini + - echo extension=php_fileinfo.dll >> php.ini + - echo memory_limit=1G >> php.ini + - cd c:\projects\webmozart\path-util + - php -r "readfile('http://getcomposer.org/installer');" | php + - php composer.phar update %COMPOSER_FLAGS% --no-interaction --no-progress + +test_script: + - cd c:\projects\webmozart\path-util + - vendor\bin\phpunit.bat --verbose diff --git a/vendor/webmozart/path-util/composer.json b/vendor/webmozart/path-util/composer.json new file mode 100644 index 0000000000..884ccac043 --- /dev/null +++ b/vendor/webmozart/path-util/composer.json @@ -0,0 +1,34 @@ +{ + "name": "webmozart/path-util", + "description": "A robust cross-platform utility for normalizing, comparing and modifying file paths.", + "license": "MIT", + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "require": { + "php": ">=5.3.3", + "webmozart/assert": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "autoload": { + "psr-4": { + "Webmozart\\PathUtil\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Webmozart\\PathUtil\\Tests\\": "tests/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + } +} diff --git a/vendor/webmozart/path-util/docs/usage.md b/vendor/webmozart/path-util/docs/usage.md new file mode 100644 index 0000000000..5135ca0b35 --- /dev/null +++ b/vendor/webmozart/path-util/docs/usage.md @@ -0,0 +1,203 @@ +Painfree Handling of File Paths +=============================== + +Dealing with file paths usually involves some difficulties: + +* **System Heterogeneity**: File paths look different on different platforms. + UNIX file paths start with a slash ("/"), while Windows file paths start with + a system drive ("C:"). UNIX uses forward slashes, while Windows uses + backslashes by default ("\"). + +* **Absolute/Relative Paths**: Web applications frequently need to deal with + absolute and relative paths. Converting one to the other properly is tricky + and repetitive. + +This package provides few, but robust utility methods to simplify your life +when dealing with file paths. + +Canonicalization +---------------- + +*Canonicalization* is the transformation of a path into a normalized (the +"canonical") format. You can canonicalize a path with `Path::canonicalize()`: + +```php +echo Path::canonicalize('/var/www/vhost/webmozart/../config.ini'); +// => /var/www/vhost/config.ini +``` + +The following modifications happen during canonicalization: + +* "." segments are removed; +* ".." segments are resolved; +* backslashes ("\") are converted into forward slashes ("/"); +* root paths ("/" and "C:/") always terminate with a slash; +* non-root paths never terminate with a slash; +* schemes (such as "phar://") are kept; +* replace "~" with the user's home directory. + +You can pass absolute paths and relative paths to `canonicalize()`. When a +relative path is passed, ".." segments at the beginning of the path are kept: + +```php +echo Path::canonicalize('../uploads/../config/config.yml'); +// => ../config/config.yml +``` + +Malformed paths are returned unchanged: + +```php +echo Path::canonicalize('C:Programs/PHP/php.ini'); +// => C:Programs/PHP/php.ini +``` + +Converting Absolute/Relative Paths +---------------------------------- + +Absolute/relative paths can be converted with the methods `Path::makeAbsolute()` +and `Path::makeRelative()`. + +`makeAbsolute()` expects a relative path and a base path to base that relative +path upon: + +```php +echo Path::makeAbsolute('config/config.yml', '/var/www/project'); +// => /var/www/project/config/config.yml +``` + +If an absolute path is passed in the first argument, the absolute path is +returned unchanged: + +```php +echo Path::makeAbsolute('/usr/share/lib/config.ini', '/var/www/project'); +// => /usr/share/lib/config.ini +``` + +The method resolves ".." segments, if there are any: + +```php +echo Path::makeAbsolute('../config/config.yml', '/var/www/project/uploads'); +// => /var/www/project/config/config.yml +``` + +This method is very useful if you want to be able to accept relative paths (for +example, relative to the root directory of your project) and absolute paths at +the same time. + +`makeRelative()` is the inverse operation to `makeAbsolute()`: + +```php +echo Path::makeRelative('/var/www/project/config/config.yml', '/var/www/project'); +// => config/config.yml +``` + +If the path is not within the base path, the method will prepend ".." segments +as necessary: + +```php +echo Path::makeRelative('/var/www/project/config/config.yml', '/var/www/project/uploads'); +// => ../config/config.yml +``` + +Use `isAbsolute()` and `isRelative()` to check whether a path is absolute or +relative: + +```php +Path::isAbsolute('C:\Programs\PHP\php.ini') +// => true +``` + +All four methods internally canonicalize the passed path. + +Finding Longest Common Base Paths +--------------------------------- + +When you store absolute file paths on the file system, this leads to a lot of +duplicated information: + +```php +return array( + '/var/www/vhosts/project/httpdocs/config/config.yml', + '/var/www/vhosts/project/httpdocs/config/routing.yml', + '/var/www/vhosts/project/httpdocs/config/services.yml', + '/var/www/vhosts/project/httpdocs/images/banana.gif', + '/var/www/vhosts/project/httpdocs/uploads/images/nicer-banana.gif', +); +``` + +Especially when storing many paths, the amount of duplicated information is +noticeable. You can use `Path::getLongestCommonBasePath()` to check a list of +paths for a common base path: + +```php +$paths = array( + '/var/www/vhosts/project/httpdocs/config/config.yml', + '/var/www/vhosts/project/httpdocs/config/routing.yml', + '/var/www/vhosts/project/httpdocs/config/services.yml', + '/var/www/vhosts/project/httpdocs/images/banana.gif', + '/var/www/vhosts/project/httpdocs/uploads/images/nicer-banana.gif', +); + +Path::getLongestCommonBasePath($paths); +// => /var/www/vhosts/project/httpdocs +``` + +Use this path together with `Path::makeRelative()` to shorten the stored paths: + +```php +$bp = '/var/www/vhosts/project/httpdocs'; + +return array( + $bp.'/config/config.yml', + $bp.'/config/routing.yml', + $bp.'/config/services.yml', + $bp.'/images/banana.gif', + $bp.'/uploads/images/nicer-banana.gif', +); +``` + +`getLongestCommonBasePath()` always returns canonical paths. + +Use `Path::isBasePath()` to test whether a path is a base path of another path: + +```php +Path::isBasePath("/var/www", "/var/www/project"); +// => true + +Path::isBasePath("/var/www", "/var/www/project/.."); +// => true + +Path::isBasePath("/var/www", "/var/www/project/../.."); +// => false +``` + +Finding Directories/Root Directories +------------------------------------ + +PHP offers the function `dirname()` to obtain the directory path of a file path. +This method has a few quirks: + +* `dirname()` does not accept backslashes on UNIX +* `dirname("C:/Programs")` returns "C:", not "C:/" +* `dirname("C:/")` returns ".", not "C:/" +* `dirname("C:")` returns ".", not "C:/" +* `dirname("Programs")` returns ".", not "" +* `dirname()` does not canonicalize the result + +`Path::getDirectory()` fixes these shortcomings: + +```php +echo Path::getDirectory("C:\Programs"); +// => C:/ +``` + +Additionally, you can use `Path::getRoot()` to obtain the root of a path: + +```php +echo Path::getRoot("/etc/apache2/sites-available"); +// => / + +echo Path::getRoot("C:\Programs\Apache\Config"); +// => C:/ +``` + diff --git a/vendor/webmozart/path-util/phpunit.xml.dist b/vendor/webmozart/path-util/phpunit.xml.dist new file mode 100644 index 0000000000..68cf2d3360 --- /dev/null +++ b/vendor/webmozart/path-util/phpunit.xml.dist @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<phpunit bootstrap="vendor/autoload.php" colors="true"> + <testsuites> + <testsuite name="Path-Util Test Suite"> + <directory suffix="Test.php">./tests/</directory> + </testsuite> + </testsuites> + + <!-- Whitelist for code coverage --> + <filter> + <whitelist> + <directory suffix=".php">./src/</directory> + </whitelist> + </filter> +</phpunit> diff --git a/vendor/webmozart/path-util/src/Path.php b/vendor/webmozart/path-util/src/Path.php new file mode 100644 index 0000000000..2f4a177950 --- /dev/null +++ b/vendor/webmozart/path-util/src/Path.php @@ -0,0 +1,1008 @@ +<?php + +/* + * This file is part of the webmozart/path-util package. + * + * (c) Bernhard Schussek <bschussek@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Webmozart\PathUtil; + +use InvalidArgumentException; +use RuntimeException; +use Webmozart\Assert\Assert; + +/** + * Contains utility methods for handling path strings. + * + * The methods in this class are able to deal with both UNIX and Windows paths + * with both forward and backward slashes. All methods return normalized parts + * containing only forward slashes and no excess "." and ".." segments. + * + * @since 1.0 + * + * @author Bernhard Schussek <bschussek@gmail.com> + * @author Thomas Schulz <mail@king2500.net> + */ +final class Path +{ + /** + * The number of buffer entries that triggers a cleanup operation. + */ + const CLEANUP_THRESHOLD = 1250; + + /** + * The buffer size after the cleanup operation. + */ + const CLEANUP_SIZE = 1000; + + /** + * Buffers input/output of {@link canonicalize()}. + * + * @var array + */ + private static $buffer = array(); + + /** + * The size of the buffer. + * + * @var int + */ + private static $bufferSize = 0; + + /** + * Canonicalizes the given path. + * + * During normalization, all slashes are replaced by forward slashes ("/"). + * Furthermore, all "." and ".." segments are removed as far as possible. + * ".." segments at the beginning of relative paths are not removed. + * + * ```php + * echo Path::canonicalize("\webmozart\puli\..\css\style.css"); + * // => /webmozart/css/style.css + * + * echo Path::canonicalize("../css/./style.css"); + * // => ../css/style.css + * ``` + * + * This method is able to deal with both UNIX and Windows paths. + * + * @param string $path A path string. + * + * @return string The canonical path. + * + * @since 1.0 Added method. + * @since 2.0 Method now fails if $path is not a string. + * @since 2.1 Added support for `~`. + */ + public static function canonicalize($path) + { + if ('' === $path) { + return ''; + } + + Assert::string($path, 'The path must be a string. Got: %s'); + + // This method is called by many other methods in this class. Buffer + // the canonicalized paths to make up for the severe performance + // decrease. + if (isset(self::$buffer[$path])) { + return self::$buffer[$path]; + } + + // Replace "~" with user's home directory. + if ('~' === $path[0]) { + $path = static::getHomeDirectory().substr($path, 1); + } + + $path = str_replace('\\', '/', $path); + + list($root, $pathWithoutRoot) = self::split($path); + + $parts = explode('/', $pathWithoutRoot); + $canonicalParts = array(); + + // Collapse "." and "..", if possible + foreach ($parts as $part) { + if ('.' === $part || '' === $part) { + continue; + } + + // Collapse ".." with the previous part, if one exists + // Don't collapse ".." if the previous part is also ".." + if ('..' === $part && count($canonicalParts) > 0 + && '..' !== $canonicalParts[count($canonicalParts) - 1]) { + array_pop($canonicalParts); + + continue; + } + + // Only add ".." prefixes for relative paths + if ('..' !== $part || '' === $root) { + $canonicalParts[] = $part; + } + } + + // Add the root directory again + self::$buffer[$path] = $canonicalPath = $root.implode('/', $canonicalParts); + ++self::$bufferSize; + + // Clean up regularly to prevent memory leaks + if (self::$bufferSize > self::CLEANUP_THRESHOLD) { + self::$buffer = array_slice(self::$buffer, -self::CLEANUP_SIZE, null, true); + self::$bufferSize = self::CLEANUP_SIZE; + } + + return $canonicalPath; + } + + /** + * Normalizes the given path. + * + * During normalization, all slashes are replaced by forward slashes ("/"). + * Contrary to {@link canonicalize()}, this method does not remove invalid + * or dot path segments. Consequently, it is much more efficient and should + * be used whenever the given path is known to be a valid, absolute system + * path. + * + * This method is able to deal with both UNIX and Windows paths. + * + * @param string $path A path string. + * + * @return string The normalized path. + * + * @since 2.2 Added method. + */ + public static function normalize($path) + { + Assert::string($path, 'The path must be a string. Got: %s'); + + return str_replace('\\', '/', $path); + } + + /** + * Returns the directory part of the path. + * + * This method is similar to PHP's dirname(), but handles various cases + * where dirname() returns a weird result: + * + * - dirname() does not accept backslashes on UNIX + * - dirname("C:/webmozart") returns "C:", not "C:/" + * - dirname("C:/") returns ".", not "C:/" + * - dirname("C:") returns ".", not "C:/" + * - dirname("webmozart") returns ".", not "" + * - dirname() does not canonicalize the result + * + * This method fixes these shortcomings and behaves like dirname() + * otherwise. + * + * The result is a canonical path. + * + * @param string $path A path string. + * + * @return string The canonical directory part. Returns the root directory + * if the root directory is passed. Returns an empty string + * if a relative path is passed that contains no slashes. + * Returns an empty string if an empty string is passed. + * + * @since 1.0 Added method. + * @since 2.0 Method now fails if $path is not a string. + */ + public static function getDirectory($path) + { + if ('' === $path) { + return ''; + } + + $path = static::canonicalize($path); + + // Maintain scheme + if (false !== ($pos = strpos($path, '://'))) { + $scheme = substr($path, 0, $pos + 3); + $path = substr($path, $pos + 3); + } else { + $scheme = ''; + } + + if (false !== ($pos = strrpos($path, '/'))) { + // Directory equals root directory "/" + if (0 === $pos) { + return $scheme.'/'; + } + + // Directory equals Windows root "C:/" + if (2 === $pos && ctype_alpha($path[0]) && ':' === $path[1]) { + return $scheme.substr($path, 0, 3); + } + + return $scheme.substr($path, 0, $pos); + } + + return ''; + } + + /** + * Returns canonical path of the user's home directory. + * + * Supported operating systems: + * + * - UNIX + * - Windows8 and upper + * + * If your operation system or environment isn't supported, an exception is thrown. + * + * The result is a canonical path. + * + * @return string The canonical home directory + * + * @throws RuntimeException If your operation system or environment isn't supported + * + * @since 2.1 Added method. + */ + public static function getHomeDirectory() + { + // For UNIX support + if (getenv('HOME')) { + return static::canonicalize(getenv('HOME')); + } + + // For >= Windows8 support + if (getenv('HOMEDRIVE') && getenv('HOMEPATH')) { + return static::canonicalize(getenv('HOMEDRIVE').getenv('HOMEPATH')); + } + + throw new RuntimeException("Your environment or operation system isn't supported"); + } + + /** + * Returns the root directory of a path. + * + * The result is a canonical path. + * + * @param string $path A path string. + * + * @return string The canonical root directory. Returns an empty string if + * the given path is relative or empty. + * + * @since 1.0 Added method. + * @since 2.0 Method now fails if $path is not a string. + */ + public static function getRoot($path) + { + if ('' === $path) { + return ''; + } + + Assert::string($path, 'The path must be a string. Got: %s'); + + // Maintain scheme + if (false !== ($pos = strpos($path, '://'))) { + $scheme = substr($path, 0, $pos + 3); + $path = substr($path, $pos + 3); + } else { + $scheme = ''; + } + + // UNIX root "/" or "\" (Windows style) + if ('/' === $path[0] || '\\' === $path[0]) { + return $scheme.'/'; + } + + $length = strlen($path); + + // Windows root + if ($length > 1 && ctype_alpha($path[0]) && ':' === $path[1]) { + // Special case: "C:" + if (2 === $length) { + return $scheme.$path.'/'; + } + + // Normal case: "C:/ or "C:\" + if ('/' === $path[2] || '\\' === $path[2]) { + return $scheme.$path[0].$path[1].'/'; + } + } + + return ''; + } + + /** + * Returns the file name from a file path. + * + * @param string $path The path string. + * + * @return string The file name. + * + * @since 1.1 Added method. + * @since 2.0 Method now fails if $path is not a string. + */ + public static function getFilename($path) + { + if ('' === $path) { + return ''; + } + + Assert::string($path, 'The path must be a string. Got: %s'); + + return basename($path); + } + + /** + * Returns the file name without the extension from a file path. + * + * @param string $path The path string. + * @param string|null $extension If specified, only that extension is cut + * off (may contain leading dot). + * + * @return string The file name without extension. + * + * @since 1.1 Added method. + * @since 2.0 Method now fails if $path or $extension have invalid types. + */ + public static function getFilenameWithoutExtension($path, $extension = null) + { + if ('' === $path) { + return ''; + } + + Assert::string($path, 'The path must be a string. Got: %s'); + Assert::nullOrString($extension, 'The extension must be a string or null. Got: %s'); + + if (null !== $extension) { + // remove extension and trailing dot + return rtrim(basename($path, $extension), '.'); + } + + return pathinfo($path, PATHINFO_FILENAME); + } + + /** + * Returns the extension from a file path. + * + * @param string $path The path string. + * @param bool $forceLowerCase Forces the extension to be lower-case + * (requires mbstring extension for correct + * multi-byte character handling in extension). + * + * @return string The extension of the file path (without leading dot). + * + * @since 1.1 Added method. + * @since 2.0 Method now fails if $path is not a string. + */ + public static function getExtension($path, $forceLowerCase = false) + { + if ('' === $path) { + return ''; + } + + Assert::string($path, 'The path must be a string. Got: %s'); + + $extension = pathinfo($path, PATHINFO_EXTENSION); + + if ($forceLowerCase) { + $extension = self::toLower($extension); + } + + return $extension; + } + + /** + * Returns whether the path has an extension. + * + * @param string $path The path string. + * @param string|array|null $extensions If null or not provided, checks if + * an extension exists, otherwise + * checks for the specified extension + * or array of extensions (with or + * without leading dot). + * @param bool $ignoreCase Whether to ignore case-sensitivity + * (requires mbstring extension for + * correct multi-byte character + * handling in the extension). + * + * @return bool Returns `true` if the path has an (or the specified) + * extension and `false` otherwise. + * + * @since 1.1 Added method. + * @since 2.0 Method now fails if $path or $extensions have invalid types. + */ + public static function hasExtension($path, $extensions = null, $ignoreCase = false) + { + if ('' === $path) { + return false; + } + + $extensions = is_object($extensions) ? array($extensions) : (array) $extensions; + + Assert::allString($extensions, 'The extensions must be strings. Got: %s'); + + $actualExtension = self::getExtension($path, $ignoreCase); + + // Only check if path has any extension + if (empty($extensions)) { + return '' !== $actualExtension; + } + + foreach ($extensions as $key => $extension) { + if ($ignoreCase) { + $extension = self::toLower($extension); + } + + // remove leading '.' in extensions array + $extensions[$key] = ltrim($extension, '.'); + } + + return in_array($actualExtension, $extensions); + } + + /** + * Changes the extension of a path string. + * + * @param string $path The path string with filename.ext to change. + * @param string $extension New extension (with or without leading dot). + * + * @return string The path string with new file extension. + * + * @since 1.1 Added method. + * @since 2.0 Method now fails if $path or $extension is not a string. + */ + public static function changeExtension($path, $extension) + { + if ('' === $path) { + return ''; + } + + Assert::string($extension, 'The extension must be a string. Got: %s'); + + $actualExtension = self::getExtension($path); + $extension = ltrim($extension, '.'); + + // No extension for paths + if ('/' === substr($path, -1)) { + return $path; + } + + // No actual extension in path + if (empty($actualExtension)) { + return $path.('.' === substr($path, -1) ? '' : '.').$extension; + } + + return substr($path, 0, -strlen($actualExtension)).$extension; + } + + /** + * Returns whether a path is absolute. + * + * @param string $path A path string. + * + * @return bool Returns true if the path is absolute, false if it is + * relative or empty. + * + * @since 1.0 Added method. + * @since 2.0 Method now fails if $path is not a string. + */ + public static function isAbsolute($path) + { + if ('' === $path) { + return false; + } + + Assert::string($path, 'The path must be a string. Got: %s'); + + // Strip scheme + if (false !== ($pos = strpos($path, '://'))) { + $path = substr($path, $pos + 3); + } + + // UNIX root "/" or "\" (Windows style) + if ('/' === $path[0] || '\\' === $path[0]) { + return true; + } + + // Windows root + if (strlen($path) > 1 && ctype_alpha($path[0]) && ':' === $path[1]) { + // Special case: "C:" + if (2 === strlen($path)) { + return true; + } + + // Normal case: "C:/ or "C:\" + if ('/' === $path[2] || '\\' === $path[2]) { + return true; + } + } + + return false; + } + + /** + * Returns whether a path is relative. + * + * @param string $path A path string. + * + * @return bool Returns true if the path is relative or empty, false if + * it is absolute. + * + * @since 1.0 Added method. + * @since 2.0 Method now fails if $path is not a string. + */ + public static function isRelative($path) + { + return !static::isAbsolute($path); + } + + /** + * Turns a relative path into an absolute path. + * + * Usually, the relative path is appended to the given base path. Dot + * segments ("." and "..") are removed/collapsed and all slashes turned + * into forward slashes. + * + * ```php + * echo Path::makeAbsolute("../style.css", "/webmozart/puli/css"); + * // => /webmozart/puli/style.css + * ``` + * + * If an absolute path is passed, that path is returned unless its root + * directory is different than the one of the base path. In that case, an + * exception is thrown. + * + * ```php + * Path::makeAbsolute("/style.css", "/webmozart/puli/css"); + * // => /style.css + * + * Path::makeAbsolute("C:/style.css", "C:/webmozart/puli/css"); + * // => C:/style.css + * + * Path::makeAbsolute("C:/style.css", "/webmozart/puli/css"); + * // InvalidArgumentException + * ``` + * + * If the base path is not an absolute path, an exception is thrown. + * + * The result is a canonical path. + * + * @param string $path A path to make absolute. + * @param string $basePath An absolute base path. + * + * @return string An absolute path in canonical form. + * + * @throws InvalidArgumentException If the base path is not absolute or if + * the given path is an absolute path with + * a different root than the base path. + * + * @since 1.0 Added method. + * @since 2.0 Method now fails if $path or $basePath is not a string. + * @since 2.2.2 Method does not fail anymore of $path and $basePath are + * absolute, but on different partitions. + */ + public static function makeAbsolute($path, $basePath) + { + Assert::stringNotEmpty($basePath, 'The base path must be a non-empty string. Got: %s'); + + if (!static::isAbsolute($basePath)) { + throw new InvalidArgumentException(sprintf( + 'The base path "%s" is not an absolute path.', + $basePath + )); + } + + if (static::isAbsolute($path)) { + return static::canonicalize($path); + } + + if (false !== ($pos = strpos($basePath, '://'))) { + $scheme = substr($basePath, 0, $pos + 3); + $basePath = substr($basePath, $pos + 3); + } else { + $scheme = ''; + } + + return $scheme.self::canonicalize(rtrim($basePath, '/\\').'/'.$path); + } + + /** + * Turns a path into a relative path. + * + * The relative path is created relative to the given base path: + * + * ```php + * echo Path::makeRelative("/webmozart/style.css", "/webmozart/puli"); + * // => ../style.css + * ``` + * + * If a relative path is passed and the base path is absolute, the relative + * path is returned unchanged: + * + * ```php + * Path::makeRelative("style.css", "/webmozart/puli/css"); + * // => style.css + * ``` + * + * If both paths are relative, the relative path is created with the + * assumption that both paths are relative to the same directory: + * + * ```php + * Path::makeRelative("style.css", "webmozart/puli/css"); + * // => ../../../style.css + * ``` + * + * If both paths are absolute, their root directory must be the same, + * otherwise an exception is thrown: + * + * ```php + * Path::makeRelative("C:/webmozart/style.css", "/webmozart/puli"); + * // InvalidArgumentException + * ``` + * + * If the passed path is absolute, but the base path is not, an exception + * is thrown as well: + * + * ```php + * Path::makeRelative("/webmozart/style.css", "webmozart/puli"); + * // InvalidArgumentException + * ``` + * + * If the base path is not an absolute path, an exception is thrown. + * + * The result is a canonical path. + * + * @param string $path A path to make relative. + * @param string $basePath A base path. + * + * @return string A relative path in canonical form. + * + * @throws InvalidArgumentException If the base path is not absolute or if + * the given path has a different root + * than the base path. + * + * @since 1.0 Added method. + * @since 2.0 Method now fails if $path or $basePath is not a string. + */ + public static function makeRelative($path, $basePath) + { + Assert::string($basePath, 'The base path must be a string. Got: %s'); + + $path = static::canonicalize($path); + $basePath = static::canonicalize($basePath); + + list($root, $relativePath) = self::split($path); + list($baseRoot, $relativeBasePath) = self::split($basePath); + + // If the base path is given as absolute path and the path is already + // relative, consider it to be relative to the given absolute path + // already + if ('' === $root && '' !== $baseRoot) { + // If base path is already in its root + if ('' === $relativeBasePath) { + $relativePath = ltrim($relativePath, './\\'); + } + + return $relativePath; + } + + // If the passed path is absolute, but the base path is not, we + // cannot generate a relative path + if ('' !== $root && '' === $baseRoot) { + throw new InvalidArgumentException(sprintf( + 'The absolute path "%s" cannot be made relative to the '. + 'relative path "%s". You should provide an absolute base '. + 'path instead.', + $path, + $basePath + )); + } + + // Fail if the roots of the two paths are different + if ($baseRoot && $root !== $baseRoot) { + throw new InvalidArgumentException(sprintf( + 'The path "%s" cannot be made relative to "%s", because they '. + 'have different roots ("%s" and "%s").', + $path, + $basePath, + $root, + $baseRoot + )); + } + + if ('' === $relativeBasePath) { + return $relativePath; + } + + // Build a "../../" prefix with as many "../" parts as necessary + $parts = explode('/', $relativePath); + $baseParts = explode('/', $relativeBasePath); + $dotDotPrefix = ''; + + // Once we found a non-matching part in the prefix, we need to add + // "../" parts for all remaining parts + $match = true; + + foreach ($baseParts as $i => $basePart) { + if ($match && isset($parts[$i]) && $basePart === $parts[$i]) { + unset($parts[$i]); + + continue; + } + + $match = false; + $dotDotPrefix .= '../'; + } + + return rtrim($dotDotPrefix.implode('/', $parts), '/'); + } + + /** + * Returns whether the given path is on the local filesystem. + * + * @param string $path A path string. + * + * @return bool Returns true if the path is local, false for a URL. + * + * @since 1.0 Added method. + * @since 2.0 Method now fails if $path is not a string. + */ + public static function isLocal($path) + { + Assert::string($path, 'The path must be a string. Got: %s'); + + return '' !== $path && false === strpos($path, '://'); + } + + /** + * Returns the longest common base path of a set of paths. + * + * Dot segments ("." and "..") are removed/collapsed and all slashes turned + * into forward slashes. + * + * ```php + * $basePath = Path::getLongestCommonBasePath(array( + * '/webmozart/css/style.css', + * '/webmozart/css/..' + * )); + * // => /webmozart + * ``` + * + * The root is returned if no common base path can be found: + * + * ```php + * $basePath = Path::getLongestCommonBasePath(array( + * '/webmozart/css/style.css', + * '/puli/css/..' + * )); + * // => / + * ``` + * + * If the paths are located on different Windows partitions, `null` is + * returned. + * + * ```php + * $basePath = Path::getLongestCommonBasePath(array( + * 'C:/webmozart/css/style.css', + * 'D:/webmozart/css/..' + * )); + * // => null + * ``` + * + * @param array $paths A list of paths. + * + * @return string|null The longest common base path in canonical form or + * `null` if the paths are on different Windows + * partitions. + * + * @since 1.0 Added method. + * @since 2.0 Method now fails if $paths are not strings. + */ + public static function getLongestCommonBasePath(array $paths) + { + Assert::allString($paths, 'The paths must be strings. Got: %s'); + + list($bpRoot, $basePath) = self::split(self::canonicalize(reset($paths))); + + for (next($paths); null !== key($paths) && '' !== $basePath; next($paths)) { + list($root, $path) = self::split(self::canonicalize(current($paths))); + + // If we deal with different roots (e.g. C:/ vs. D:/), it's time + // to quit + if ($root !== $bpRoot) { + return null; + } + + // Make the base path shorter until it fits into path + while (true) { + if ('.' === $basePath) { + // No more base paths + $basePath = ''; + + // Next path + continue 2; + } + + // Prevent false positives for common prefixes + // see isBasePath() + if (0 === strpos($path.'/', $basePath.'/')) { + // Next path + continue 2; + } + + $basePath = dirname($basePath); + } + } + + return $bpRoot.$basePath; + } + + /** + * Joins two or more path strings. + * + * The result is a canonical path. + * + * @param string[]|string $paths Path parts as parameters or array. + * + * @return string The joint path. + * + * @since 2.0 Added method. + */ + public static function join($paths) + { + if (!is_array($paths)) { + $paths = func_get_args(); + } + + Assert::allString($paths, 'The paths must be strings. Got: %s'); + + $finalPath = null; + $wasScheme = false; + + foreach ($paths as $path) { + $path = (string) $path; + + if ('' === $path) { + continue; + } + + if (null === $finalPath) { + // For first part we keep slashes, like '/top', 'C:\' or 'phar://' + $finalPath = $path; + $wasScheme = (strpos($path, '://') !== false); + continue; + } + + // Only add slash if previous part didn't end with '/' or '\' + if (!in_array(substr($finalPath, -1), array('/', '\\'))) { + $finalPath .= '/'; + } + + // If first part included a scheme like 'phar://' we allow current part to start with '/', otherwise trim + $finalPath .= $wasScheme ? $path : ltrim($path, '/'); + $wasScheme = false; + } + + if (null === $finalPath) { + return ''; + } + + return self::canonicalize($finalPath); + } + + /** + * Returns whether a path is a base path of another path. + * + * Dot segments ("." and "..") are removed/collapsed and all slashes turned + * into forward slashes. + * + * ```php + * Path::isBasePath('/webmozart', '/webmozart/css'); + * // => true + * + * Path::isBasePath('/webmozart', '/webmozart'); + * // => true + * + * Path::isBasePath('/webmozart', '/webmozart/..'); + * // => false + * + * Path::isBasePath('/webmozart', '/puli'); + * // => false + * ``` + * + * @param string $basePath The base path to test. + * @param string $ofPath The other path. + * + * @return bool Whether the base path is a base path of the other path. + * + * @since 1.0 Added method. + * @since 2.0 Method now fails if $basePath or $ofPath is not a string. + */ + public static function isBasePath($basePath, $ofPath) + { + Assert::string($basePath, 'The base path must be a string. Got: %s'); + + $basePath = self::canonicalize($basePath); + $ofPath = self::canonicalize($ofPath); + + // Append slashes to prevent false positives when two paths have + // a common prefix, for example /base/foo and /base/foobar. + // Don't append a slash for the root "/", because then that root + // won't be discovered as common prefix ("//" is not a prefix of + // "/foobar/"). + return 0 === strpos($ofPath.'/', rtrim($basePath, '/').'/'); + } + + /** + * Splits a part into its root directory and the remainder. + * + * If the path has no root directory, an empty root directory will be + * returned. + * + * If the root directory is a Windows style partition, the resulting root + * will always contain a trailing slash. + * + * list ($root, $path) = Path::split("C:/webmozart") + * // => array("C:/", "webmozart") + * + * list ($root, $path) = Path::split("C:") + * // => array("C:/", "") + * + * @param string $path The canonical path to split. + * + * @return string[] An array with the root directory and the remaining + * relative path. + */ + private static function split($path) + { + if ('' === $path) { + return array('', ''); + } + + // Remember scheme as part of the root, if any + if (false !== ($pos = strpos($path, '://'))) { + $root = substr($path, 0, $pos + 3); + $path = substr($path, $pos + 3); + } else { + $root = ''; + } + + $length = strlen($path); + + // Remove and remember root directory + if ('/' === $path[0]) { + $root .= '/'; + $path = $length > 1 ? substr($path, 1) : ''; + } elseif ($length > 1 && ctype_alpha($path[0]) && ':' === $path[1]) { + if (2 === $length) { + // Windows special case: "C:" + $root .= $path.'/'; + $path = ''; + } elseif ('/' === $path[2]) { + // Windows normal case: "C:/".. + $root .= substr($path, 0, 3); + $path = $length > 3 ? substr($path, 3) : ''; + } + } + + return array($root, $path); + } + + /** + * Converts string to lower-case (multi-byte safe if mbstring is installed). + * + * @param string $str The string + * + * @return string Lower case string + */ + private static function toLower($str) + { + if (function_exists('mb_strtolower')) { + return mb_strtolower($str, mb_detect_encoding($str)); + } + + return strtolower($str); + } + + private function __construct() + { + } +} diff --git a/vendor/webmozart/path-util/src/Url.php b/vendor/webmozart/path-util/src/Url.php new file mode 100644 index 0000000000..834e7213d0 --- /dev/null +++ b/vendor/webmozart/path-util/src/Url.php @@ -0,0 +1,111 @@ +<?php + +/* + * This file is part of the webmozart/path-util package. + * + * (c) Bernhard Schussek <bschussek@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Webmozart\PathUtil; + +use InvalidArgumentException; +use Webmozart\Assert\Assert; + +/** + * Contains utility methods for handling URL strings. + * + * The methods in this class are able to deal with URLs. + * + * @since 2.3 + * + * @author Bernhard Schussek <bschussek@gmail.com> + * @author Claudio Zizza <claudio@budgegeria.de> + */ +final class Url +{ + /** + * Turns a URL into a relative path. + * + * The result is a canonical path. This class is using functionality of Path class. + * + * @see Path + * + * @param string $url A URL to make relative. + * @param string $baseUrl A base URL. + * + * @return string + * + * @throws InvalidArgumentException If the URL and base URL does + * not match. + */ + public static function makeRelative($url, $baseUrl) + { + Assert::string($url, 'The URL must be a string. Got: %s'); + Assert::string($baseUrl, 'The base URL must be a string. Got: %s'); + Assert::contains($baseUrl, '://', '%s is not an absolute Url.'); + + list($baseHost, $basePath) = self::split($baseUrl); + + if (false === strpos($url, '://')) { + if (0 === strpos($url, '/')) { + $host = $baseHost; + } else { + $host = ''; + } + $path = $url; + } else { + list($host, $path) = self::split($url); + } + + if ('' !== $host && $host !== $baseHost) { + throw new InvalidArgumentException(sprintf( + 'The URL "%s" cannot be made relative to "%s" since their host names are different.', + $host, + $baseHost + )); + } + + return Path::makeRelative($path, $basePath); + } + + /** + * Splits a URL into its host and the path. + * + * ```php + * list ($root, $path) = Path::split("http://example.com/webmozart") + * // => array("http://example.com", "/webmozart") + * + * list ($root, $path) = Path::split("http://example.com") + * // => array("http://example.com", "") + * ``` + * + * @param string $url The URL to split. + * + * @return string[] An array with the host and the path of the URL. + * + * @throws InvalidArgumentException If $url is not a URL. + */ + private static function split($url) + { + $pos = strpos($url, '://'); + $scheme = substr($url, 0, $pos + 3); + $url = substr($url, $pos + 3); + + if (false !== ($pos = strpos($url, '/'))) { + $host = substr($url, 0, $pos); + $url = substr($url, $pos); + } else { + // No path, only host + $host = $url; + $url = '/'; + } + + // At this point, we have $scheme, $host and $path + $root = $scheme.$host; + + return array($root, $url); + } +} diff --git a/vendor/webmozart/path-util/tests/PathTest.php b/vendor/webmozart/path-util/tests/PathTest.php new file mode 100644 index 0000000000..cc88b97a39 --- /dev/null +++ b/vendor/webmozart/path-util/tests/PathTest.php @@ -0,0 +1,1340 @@ +<?php + +/* + * This file is part of the webmozart/path-util package. + * + * (c) Bernhard Schussek <bschussek@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Webmozart\PathUtil\Tests; + +use Webmozart\PathUtil\Path; + +/** + * @since 1.0 + * + * @author Bernhard Schussek <bschussek@gmail.com> + * @author Thomas Schulz <mail@king2500.net> + */ +class PathTest extends \PHPUnit_Framework_TestCase +{ + protected $storedEnv = array(); + + public function setUp() + { + $this->storedEnv['HOME'] = getenv('HOME'); + $this->storedEnv['HOMEDRIVE'] = getenv('HOMEDRIVE'); + $this->storedEnv['HOMEPATH'] = getenv('HOMEPATH'); + + putenv('HOME=/home/webmozart'); + putenv('HOMEDRIVE='); + putenv('HOMEPATH='); + } + + public function tearDown() + { + putenv('HOME='.$this->storedEnv['HOME']); + putenv('HOMEDRIVE='.$this->storedEnv['HOMEDRIVE']); + putenv('HOMEPATH='.$this->storedEnv['HOMEPATH']); + } + + public function provideCanonicalizationTests() + { + return array( + // relative paths (forward slash) + array('css/./style.css', 'css/style.css'), + array('css/../style.css', 'style.css'), + array('css/./../style.css', 'style.css'), + array('css/.././style.css', 'style.css'), + array('css/../../style.css', '../style.css'), + array('./css/style.css', 'css/style.css'), + array('../css/style.css', '../css/style.css'), + array('./../css/style.css', '../css/style.css'), + array('.././css/style.css', '../css/style.css'), + array('../../css/style.css', '../../css/style.css'), + array('', ''), + array('.', ''), + array('..', '..'), + array('./..', '..'), + array('../.', '..'), + array('../..', '../..'), + + // relative paths (backslash) + array('css\\.\\style.css', 'css/style.css'), + array('css\\..\\style.css', 'style.css'), + array('css\\.\\..\\style.css', 'style.css'), + array('css\\..\\.\\style.css', 'style.css'), + array('css\\..\\..\\style.css', '../style.css'), + array('.\\css\\style.css', 'css/style.css'), + array('..\\css\\style.css', '../css/style.css'), + array('.\\..\\css\\style.css', '../css/style.css'), + array('..\\.\\css\\style.css', '../css/style.css'), + array('..\\..\\css\\style.css', '../../css/style.css'), + + // absolute paths (forward slash, UNIX) + array('/css/style.css', '/css/style.css'), + array('/css/./style.css', '/css/style.css'), + array('/css/../style.css', '/style.css'), + array('/css/./../style.css', '/style.css'), + array('/css/.././style.css', '/style.css'), + array('/./css/style.css', '/css/style.css'), + array('/../css/style.css', '/css/style.css'), + array('/./../css/style.css', '/css/style.css'), + array('/.././css/style.css', '/css/style.css'), + array('/../../css/style.css', '/css/style.css'), + + // absolute paths (backslash, UNIX) + array('\\css\\style.css', '/css/style.css'), + array('\\css\\.\\style.css', '/css/style.css'), + array('\\css\\..\\style.css', '/style.css'), + array('\\css\\.\\..\\style.css', '/style.css'), + array('\\css\\..\\.\\style.css', '/style.css'), + array('\\.\\css\\style.css', '/css/style.css'), + array('\\..\\css\\style.css', '/css/style.css'), + array('\\.\\..\\css\\style.css', '/css/style.css'), + array('\\..\\.\\css\\style.css', '/css/style.css'), + array('\\..\\..\\css\\style.css', '/css/style.css'), + + // absolute paths (forward slash, Windows) + array('C:/css/style.css', 'C:/css/style.css'), + array('C:/css/./style.css', 'C:/css/style.css'), + array('C:/css/../style.css', 'C:/style.css'), + array('C:/css/./../style.css', 'C:/style.css'), + array('C:/css/.././style.css', 'C:/style.css'), + array('C:/./css/style.css', 'C:/css/style.css'), + array('C:/../css/style.css', 'C:/css/style.css'), + array('C:/./../css/style.css', 'C:/css/style.css'), + array('C:/.././css/style.css', 'C:/css/style.css'), + array('C:/../../css/style.css', 'C:/css/style.css'), + + // absolute paths (backslash, Windows) + array('C:\\css\\style.css', 'C:/css/style.css'), + array('C:\\css\\.\\style.css', 'C:/css/style.css'), + array('C:\\css\\..\\style.css', 'C:/style.css'), + array('C:\\css\\.\\..\\style.css', 'C:/style.css'), + array('C:\\css\\..\\.\\style.css', 'C:/style.css'), + array('C:\\.\\css\\style.css', 'C:/css/style.css'), + array('C:\\..\\css\\style.css', 'C:/css/style.css'), + array('C:\\.\\..\\css\\style.css', 'C:/css/style.css'), + array('C:\\..\\.\\css\\style.css', 'C:/css/style.css'), + array('C:\\..\\..\\css\\style.css', 'C:/css/style.css'), + + // Windows special case + array('C:', 'C:/'), + + // Don't change malformed path + array('C:css/style.css', 'C:css/style.css'), + + // absolute paths (stream, UNIX) + array('phar:///css/style.css', 'phar:///css/style.css'), + array('phar:///css/./style.css', 'phar:///css/style.css'), + array('phar:///css/../style.css', 'phar:///style.css'), + array('phar:///css/./../style.css', 'phar:///style.css'), + array('phar:///css/.././style.css', 'phar:///style.css'), + array('phar:///./css/style.css', 'phar:///css/style.css'), + array('phar:///../css/style.css', 'phar:///css/style.css'), + array('phar:///./../css/style.css', 'phar:///css/style.css'), + array('phar:///.././css/style.css', 'phar:///css/style.css'), + array('phar:///../../css/style.css', 'phar:///css/style.css'), + + // absolute paths (stream, Windows) + array('phar://C:/css/style.css', 'phar://C:/css/style.css'), + array('phar://C:/css/./style.css', 'phar://C:/css/style.css'), + array('phar://C:/css/../style.css', 'phar://C:/style.css'), + array('phar://C:/css/./../style.css', 'phar://C:/style.css'), + array('phar://C:/css/.././style.css', 'phar://C:/style.css'), + array('phar://C:/./css/style.css', 'phar://C:/css/style.css'), + array('phar://C:/../css/style.css', 'phar://C:/css/style.css'), + array('phar://C:/./../css/style.css', 'phar://C:/css/style.css'), + array('phar://C:/.././css/style.css', 'phar://C:/css/style.css'), + array('phar://C:/../../css/style.css', 'phar://C:/css/style.css'), + + // paths with "~" UNIX + array('~/css/style.css', '/home/webmozart/css/style.css'), + array('~/css/./style.css', '/home/webmozart/css/style.css'), + array('~/css/../style.css', '/home/webmozart/style.css'), + array('~/css/./../style.css', '/home/webmozart/style.css'), + array('~/css/.././style.css', '/home/webmozart/style.css'), + array('~/./css/style.css', '/home/webmozart/css/style.css'), + array('~/../css/style.css', '/home/css/style.css'), + array('~/./../css/style.css', '/home/css/style.css'), + array('~/.././css/style.css', '/home/css/style.css'), + array('~/../../css/style.css', '/css/style.css'), + ); + } + + /** + * @dataProvider provideCanonicalizationTests + */ + public function testCanonicalize($path, $canonicalized) + { + $this->assertSame($canonicalized, Path::canonicalize($path)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The path must be a string. Got: array + */ + public function testCanonicalizeFailsIfInvalidPath() + { + Path::canonicalize(array()); + } + + public function provideGetDirectoryTests() + { + return array( + array('/webmozart/puli/style.css', '/webmozart/puli'), + array('/webmozart/puli', '/webmozart'), + array('/webmozart', '/'), + array('/', '/'), + array('', ''), + + array('\\webmozart\\puli\\style.css', '/webmozart/puli'), + array('\\webmozart\\puli', '/webmozart'), + array('\\webmozart', '/'), + array('\\', '/'), + + array('C:/webmozart/puli/style.css', 'C:/webmozart/puli'), + array('C:/webmozart/puli', 'C:/webmozart'), + array('C:/webmozart', 'C:/'), + array('C:/', 'C:/'), + array('C:', 'C:/'), + + array('C:\\webmozart\\puli\\style.css', 'C:/webmozart/puli'), + array('C:\\webmozart\\puli', 'C:/webmozart'), + array('C:\\webmozart', 'C:/'), + array('C:\\', 'C:/'), + + array('phar:///webmozart/puli/style.css', 'phar:///webmozart/puli'), + array('phar:///webmozart/puli', 'phar:///webmozart'), + array('phar:///webmozart', 'phar:///'), + array('phar:///', 'phar:///'), + + array('phar://C:/webmozart/puli/style.css', 'phar://C:/webmozart/puli'), + array('phar://C:/webmozart/puli', 'phar://C:/webmozart'), + array('phar://C:/webmozart', 'phar://C:/'), + array('phar://C:/', 'phar://C:/'), + + array('webmozart/puli/style.css', 'webmozart/puli'), + array('webmozart/puli', 'webmozart'), + array('webmozart', ''), + + array('webmozart\\puli\\style.css', 'webmozart/puli'), + array('webmozart\\puli', 'webmozart'), + array('webmozart', ''), + + array('/webmozart/./puli/style.css', '/webmozart/puli'), + array('/webmozart/../puli/style.css', '/puli'), + array('/webmozart/./../puli/style.css', '/puli'), + array('/webmozart/.././puli/style.css', '/puli'), + array('/webmozart/../../puli/style.css', '/puli'), + array('/.', '/'), + array('/..', '/'), + + array('C:webmozart', ''), + ); + } + + /** + * @dataProvider provideGetDirectoryTests + */ + public function testGetDirectory($path, $directory) + { + $this->assertSame($directory, Path::getDirectory($path)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The path must be a string. Got: array + */ + public function testGetDirectoryFailsIfInvalidPath() + { + Path::getDirectory(array()); + } + + public function provideGetFilenameTests() + { + return array( + array('/webmozart/puli/style.css', 'style.css'), + array('/webmozart/puli/STYLE.CSS', 'STYLE.CSS'), + array('/webmozart/puli/style.css/', 'style.css'), + array('/webmozart/puli/', 'puli'), + array('/webmozart/puli', 'puli'), + array('/', ''), + array('', ''), + ); + } + + /** + * @dataProvider provideGetFilenameTests + */ + public function testGetFilename($path, $filename) + { + $this->assertSame($filename, Path::getFilename($path)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The path must be a string. Got: array + */ + public function testGetFilenameFailsIfInvalidPath() + { + Path::getFilename(array()); + } + + public function provideGetFilenameWithoutExtensionTests() + { + return array( + array('/webmozart/puli/style.css.twig', null, 'style.css'), + array('/webmozart/puli/style.css.', null, 'style.css'), + array('/webmozart/puli/style.css', null, 'style'), + array('/webmozart/puli/.style.css', null, '.style'), + array('/webmozart/puli/', null, 'puli'), + array('/webmozart/puli', null, 'puli'), + array('/', null, ''), + array('', null, ''), + + array('/webmozart/puli/style.css', 'css', 'style'), + array('/webmozart/puli/style.css', '.css', 'style'), + array('/webmozart/puli/style.css', 'twig', 'style.css'), + array('/webmozart/puli/style.css', '.twig', 'style.css'), + array('/webmozart/puli/style.css', '', 'style.css'), + array('/webmozart/puli/style.css.', '', 'style.css'), + array('/webmozart/puli/style.css.', '.', 'style.css'), + array('/webmozart/puli/style.css.', '.css', 'style.css'), + array('/webmozart/puli/.style.css', 'css', '.style'), + array('/webmozart/puli/.style.css', '.css', '.style'), + ); + } + + /** + * @dataProvider provideGetFilenameWithoutExtensionTests + */ + public function testGetFilenameWithoutExtension($path, $extension, $filename) + { + $this->assertSame($filename, Path::getFilenameWithoutExtension($path, $extension)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The path must be a string. Got: array + */ + public function testGetFilenameWithoutExtensionFailsIfInvalidPath() + { + Path::getFilenameWithoutExtension(array(), '.css'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The extension must be a string or null. Got: array + */ + public function testGetFilenameWithoutExtensionFailsIfInvalidExtension() + { + Path::getFilenameWithoutExtension('/style.css', array()); + } + + public function provideGetExtensionTests() + { + $tests = array( + array('/webmozart/puli/style.css.twig', false, 'twig'), + array('/webmozart/puli/style.css', false, 'css'), + array('/webmozart/puli/style.css.', false, ''), + array('/webmozart/puli/', false, ''), + array('/webmozart/puli', false, ''), + array('/', false, ''), + array('', false, ''), + + array('/webmozart/puli/style.CSS', false, 'CSS'), + array('/webmozart/puli/style.CSS', true, 'css'), + array('/webmozart/puli/style.ÄÖÜ', false, 'ÄÖÜ'), + ); + + if (extension_loaded('mbstring')) { + // This can only be tested, when mbstring is installed + $tests[] = array('/webmozart/puli/style.ÄÖÜ', true, 'äöü'); + } + + return $tests; + } + + /** + * @dataProvider provideGetExtensionTests + */ + public function testGetExtension($path, $forceLowerCase, $extension) + { + $this->assertSame($extension, Path::getExtension($path, $forceLowerCase)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The path must be a string. Got: array + */ + public function testGetExtensionFailsIfInvalidPath() + { + Path::getExtension(array()); + } + + public function provideHasExtensionTests() + { + $tests = array( + array(true, '/webmozart/puli/style.css.twig', null, false), + array(true, '/webmozart/puli/style.css', null, false), + array(false, '/webmozart/puli/style.css.', null, false), + array(false, '/webmozart/puli/', null, false), + array(false, '/webmozart/puli', null, false), + array(false, '/', null, false), + array(false, '', null, false), + + array(true, '/webmozart/puli/style.css.twig', 'twig', false), + array(false, '/webmozart/puli/style.css.twig', 'css', false), + array(true, '/webmozart/puli/style.css', 'css', false), + array(true, '/webmozart/puli/style.css', '.css', false), + array(true, '/webmozart/puli/style.css.', '', false), + array(false, '/webmozart/puli/', 'ext', false), + array(false, '/webmozart/puli', 'ext', false), + array(false, '/', 'ext', false), + array(false, '', 'ext', false), + + array(false, '/webmozart/puli/style.css', 'CSS', false), + array(true, '/webmozart/puli/style.css', 'CSS', true), + array(false, '/webmozart/puli/style.CSS', 'css', false), + array(true, '/webmozart/puli/style.CSS', 'css', true), + array(true, '/webmozart/puli/style.ÄÖÜ', 'ÄÖÜ', false), + + array(true, '/webmozart/puli/style.css', array('ext', 'css'), false), + array(true, '/webmozart/puli/style.css', array('.ext', '.css'), false), + array(true, '/webmozart/puli/style.css.', array('ext', ''), false), + array(false, '/webmozart/puli/style.css', array('foo', 'bar', ''), false), + array(false, '/webmozart/puli/style.css', array('.foo', '.bar', ''), false), + ); + + if (extension_loaded('mbstring')) { + // This can only be tested, when mbstring is installed + $tests[] = array(true, '/webmozart/puli/style.ÄÖÜ', 'äöü', true); + $tests[] = array(true, '/webmozart/puli/style.ÄÖÜ', array('äöü'), true); + } + + return $tests; + } + + /** + * @dataProvider provideHasExtensionTests + */ + public function testHasExtension($hasExtension, $path, $extension, $ignoreCase) + { + $this->assertSame($hasExtension, Path::hasExtension($path, $extension, $ignoreCase)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The path must be a string. Got: array + */ + public function testHasExtensionFailsIfInvalidPath() + { + Path::hasExtension(array()); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The extensions must be strings. Got: stdClass + */ + public function testHasExtensionFailsIfInvalidExtension() + { + Path::hasExtension('/style.css', (object) array()); + } + + public function provideChangeExtensionTests() + { + return array( + array('/webmozart/puli/style.css.twig', 'html', '/webmozart/puli/style.css.html'), + array('/webmozart/puli/style.css', 'sass', '/webmozart/puli/style.sass'), + array('/webmozart/puli/style.css', '.sass', '/webmozart/puli/style.sass'), + array('/webmozart/puli/style.css', '', '/webmozart/puli/style.'), + array('/webmozart/puli/style.css.', 'twig', '/webmozart/puli/style.css.twig'), + array('/webmozart/puli/style.css.', '', '/webmozart/puli/style.css.'), + array('/webmozart/puli/style.css', 'äöü', '/webmozart/puli/style.äöü'), + array('/webmozart/puli/style.äöü', 'css', '/webmozart/puli/style.css'), + array('/webmozart/puli/', 'css', '/webmozart/puli/'), + array('/webmozart/puli', 'css', '/webmozart/puli.css'), + array('/', 'css', '/'), + array('', 'css', ''), + ); + } + + /** + * @dataProvider provideChangeExtensionTests + */ + public function testChangeExtension($path, $extension, $pathExpected) + { + static $call = 0; + $this->assertSame($pathExpected, Path::changeExtension($path, $extension)); + ++$call; + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The path must be a string. Got: array + */ + public function testChangeExtensionFailsIfInvalidPath() + { + Path::changeExtension(array(), '.sass'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The extension must be a string. Got: array + */ + public function testChangeExtensionFailsIfInvalidExtension() + { + Path::changeExtension('/style.css', array()); + } + + public function provideIsAbsolutePathTests() + { + return array( + array('/css/style.css', true), + array('/', true), + array('css/style.css', false), + array('', false), + + array('\\css\\style.css', true), + array('\\', true), + array('css\\style.css', false), + + array('C:/css/style.css', true), + array('D:/', true), + + array('E:\\css\\style.css', true), + array('F:\\', true), + + array('phar:///css/style.css', true), + array('phar:///', true), + + // Windows special case + array('C:', true), + + // Not considered absolute + array('C:css/style.css', false), + ); + } + + /** + * @dataProvider provideIsAbsolutePathTests + */ + public function testIsAbsolute($path, $isAbsolute) + { + $this->assertSame($isAbsolute, Path::isAbsolute($path)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The path must be a string. Got: array + */ + public function testIsAbsoluteFailsIfInvalidPath() + { + Path::isAbsolute(array()); + } + + /** + * @dataProvider provideIsAbsolutePathTests + */ + public function testIsRelative($path, $isAbsolute) + { + $this->assertSame(!$isAbsolute, Path::isRelative($path)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The path must be a string. Got: array + */ + public function testIsRelativeFailsIfInvalidPath() + { + Path::isRelative(array()); + } + + public function provideGetRootTests() + { + return array( + array('/css/style.css', '/'), + array('/', '/'), + array('css/style.css', ''), + array('', ''), + + array('\\css\\style.css', '/'), + array('\\', '/'), + array('css\\style.css', ''), + + array('C:/css/style.css', 'C:/'), + array('C:/', 'C:/'), + array('C:', 'C:/'), + + array('D:\\css\\style.css', 'D:/'), + array('D:\\', 'D:/'), + + array('phar:///css/style.css', 'phar:///'), + array('phar:///', 'phar:///'), + + array('phar://C:/css/style.css', 'phar://C:/'), + array('phar://C:/', 'phar://C:/'), + array('phar://C:', 'phar://C:/'), + ); + } + + /** + * @dataProvider provideGetRootTests + */ + public function testGetRoot($path, $root) + { + $this->assertSame($root, Path::getRoot($path)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The path must be a string. Got: array + */ + public function testGetRootFailsIfInvalidPath() + { + Path::getRoot(array()); + } + + public function providePathTests() + { + return array( + // relative to absolute path + array('css/style.css', '/webmozart/puli', '/webmozart/puli/css/style.css'), + array('../css/style.css', '/webmozart/puli', '/webmozart/css/style.css'), + array('../../css/style.css', '/webmozart/puli', '/css/style.css'), + + // relative to root + array('css/style.css', '/', '/css/style.css'), + array('css/style.css', 'C:', 'C:/css/style.css'), + array('css/style.css', 'C:/', 'C:/css/style.css'), + + // same sub directories in different base directories + array('../../puli/css/style.css', '/webmozart/css', '/puli/css/style.css'), + + array('', '/webmozart/puli', '/webmozart/puli'), + array('..', '/webmozart/puli', '/webmozart'), + ); + } + + public function provideMakeAbsoluteTests() + { + return array_merge($this->providePathTests(), array( + // collapse dots + array('css/./style.css', '/webmozart/puli', '/webmozart/puli/css/style.css'), + array('css/../style.css', '/webmozart/puli', '/webmozart/puli/style.css'), + array('css/./../style.css', '/webmozart/puli', '/webmozart/puli/style.css'), + array('css/.././style.css', '/webmozart/puli', '/webmozart/puli/style.css'), + array('./css/style.css', '/webmozart/puli', '/webmozart/puli/css/style.css'), + + array('css\\.\\style.css', '\\webmozart\\puli', '/webmozart/puli/css/style.css'), + array('css\\..\\style.css', '\\webmozart\\puli', '/webmozart/puli/style.css'), + array('css\\.\\..\\style.css', '\\webmozart\\puli', '/webmozart/puli/style.css'), + array('css\\..\\.\\style.css', '\\webmozart\\puli', '/webmozart/puli/style.css'), + array('.\\css\\style.css', '\\webmozart\\puli', '/webmozart/puli/css/style.css'), + + // collapse dots on root + array('./css/style.css', '/', '/css/style.css'), + array('../css/style.css', '/', '/css/style.css'), + array('../css/./style.css', '/', '/css/style.css'), + array('../css/../style.css', '/', '/style.css'), + array('../css/./../style.css', '/', '/style.css'), + array('../css/.././style.css', '/', '/style.css'), + + array('.\\css\\style.css', '\\', '/css/style.css'), + array('..\\css\\style.css', '\\', '/css/style.css'), + array('..\\css\\.\\style.css', '\\', '/css/style.css'), + array('..\\css\\..\\style.css', '\\', '/style.css'), + array('..\\css\\.\\..\\style.css', '\\', '/style.css'), + array('..\\css\\..\\.\\style.css', '\\', '/style.css'), + + array('./css/style.css', 'C:/', 'C:/css/style.css'), + array('../css/style.css', 'C:/', 'C:/css/style.css'), + array('../css/./style.css', 'C:/', 'C:/css/style.css'), + array('../css/../style.css', 'C:/', 'C:/style.css'), + array('../css/./../style.css', 'C:/', 'C:/style.css'), + array('../css/.././style.css', 'C:/', 'C:/style.css'), + + array('.\\css\\style.css', 'C:\\', 'C:/css/style.css'), + array('..\\css\\style.css', 'C:\\', 'C:/css/style.css'), + array('..\\css\\.\\style.css', 'C:\\', 'C:/css/style.css'), + array('..\\css\\..\\style.css', 'C:\\', 'C:/style.css'), + array('..\\css\\.\\..\\style.css', 'C:\\', 'C:/style.css'), + array('..\\css\\..\\.\\style.css', 'C:\\', 'C:/style.css'), + + array('./css/style.css', 'phar:///', 'phar:///css/style.css'), + array('../css/style.css', 'phar:///', 'phar:///css/style.css'), + array('../css/./style.css', 'phar:///', 'phar:///css/style.css'), + array('../css/../style.css', 'phar:///', 'phar:///style.css'), + array('../css/./../style.css', 'phar:///', 'phar:///style.css'), + array('../css/.././style.css', 'phar:///', 'phar:///style.css'), + + array('./css/style.css', 'phar://C:/', 'phar://C:/css/style.css'), + array('../css/style.css', 'phar://C:/', 'phar://C:/css/style.css'), + array('../css/./style.css', 'phar://C:/', 'phar://C:/css/style.css'), + array('../css/../style.css', 'phar://C:/', 'phar://C:/style.css'), + array('../css/./../style.css', 'phar://C:/', 'phar://C:/style.css'), + array('../css/.././style.css', 'phar://C:/', 'phar://C:/style.css'), + + // absolute paths + array('/css/style.css', '/webmozart/puli', '/css/style.css'), + array('\\css\\style.css', '/webmozart/puli', '/css/style.css'), + array('C:/css/style.css', 'C:/webmozart/puli', 'C:/css/style.css'), + array('D:\\css\\style.css', 'D:/webmozart/puli', 'D:/css/style.css'), + )); + } + + /** + * @dataProvider provideMakeAbsoluteTests + */ + public function testMakeAbsolute($relativePath, $basePath, $absolutePath) + { + $this->assertSame($absolutePath, Path::makeAbsolute($relativePath, $basePath)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The path must be a string. Got: array + */ + public function testMakeAbsoluteFailsIfInvalidPath() + { + Path::makeAbsolute(array(), '/webmozart/puli'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The base path must be a non-empty string. Got: array + */ + public function testMakeAbsoluteFailsIfInvalidBasePath() + { + Path::makeAbsolute('css/style.css', array()); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The base path "webmozart/puli" is not an absolute path. + */ + public function testMakeAbsoluteFailsIfBasePathNotAbsolute() + { + Path::makeAbsolute('css/style.css', 'webmozart/puli'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The base path must be a non-empty string. Got: "" + */ + public function testMakeAbsoluteFailsIfBasePathEmpty() + { + Path::makeAbsolute('css/style.css', ''); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The base path must be a non-empty string. Got: NULL + */ + public function testMakeAbsoluteFailsIfBasePathNull() + { + Path::makeAbsolute('css/style.css', null); + } + + public function provideAbsolutePathsWithDifferentRoots() + { + return array( + array('C:/css/style.css', '/webmozart/puli'), + array('C:/css/style.css', '\\webmozart\\puli'), + array('C:\\css\\style.css', '/webmozart/puli'), + array('C:\\css\\style.css', '\\webmozart\\puli'), + + array('/css/style.css', 'C:/webmozart/puli'), + array('/css/style.css', 'C:\\webmozart\\puli'), + array('\\css\\style.css', 'C:/webmozart/puli'), + array('\\css\\style.css', 'C:\\webmozart\\puli'), + + array('D:/css/style.css', 'C:/webmozart/puli'), + array('D:/css/style.css', 'C:\\webmozart\\puli'), + array('D:\\css\\style.css', 'C:/webmozart/puli'), + array('D:\\css\\style.css', 'C:\\webmozart\\puli'), + + array('phar:///css/style.css', '/webmozart/puli'), + array('/css/style.css', 'phar:///webmozart/puli'), + + array('phar://C:/css/style.css', 'C:/webmozart/puli'), + array('phar://C:/css/style.css', 'C:\\webmozart\\puli'), + array('phar://C:\\css\\style.css', 'C:/webmozart/puli'), + array('phar://C:\\css\\style.css', 'C:\\webmozart\\puli'), + ); + } + + /** + * @dataProvider provideAbsolutePathsWithDifferentRoots + */ + public function testMakeAbsoluteDoesNotFailIfDifferentRoot($basePath, $absolutePath) + { + // If a path in partition D: is passed, but $basePath is in partition + // C:, the path should be returned unchanged + $this->assertSame(Path::canonicalize($absolutePath), Path::makeAbsolute($absolutePath, $basePath)); + } + + public function provideMakeRelativeTests() + { + $paths = array_map(function (array $arguments) { + return array($arguments[2], $arguments[1], $arguments[0]); + }, $this->providePathTests()); + + return array_merge($paths, array( + array('/webmozart/puli/./css/style.css', '/webmozart/puli', 'css/style.css'), + array('/webmozart/puli/../css/style.css', '/webmozart/puli', '../css/style.css'), + array('/webmozart/puli/.././css/style.css', '/webmozart/puli', '../css/style.css'), + array('/webmozart/puli/./../css/style.css', '/webmozart/puli', '../css/style.css'), + array('/webmozart/puli/../../css/style.css', '/webmozart/puli', '../../css/style.css'), + array('/webmozart/puli/css/style.css', '/webmozart/./puli', 'css/style.css'), + array('/webmozart/puli/css/style.css', '/webmozart/../puli', '../webmozart/puli/css/style.css'), + array('/webmozart/puli/css/style.css', '/webmozart/./../puli', '../webmozart/puli/css/style.css'), + array('/webmozart/puli/css/style.css', '/webmozart/.././puli', '../webmozart/puli/css/style.css'), + array('/webmozart/puli/css/style.css', '/webmozart/../../puli', '../webmozart/puli/css/style.css'), + + // first argument shorter than second + array('/css', '/webmozart/puli', '../../css'), + + // second argument shorter than first + array('/webmozart/puli', '/css', '../webmozart/puli'), + + array('\\webmozart\\puli\\css\\style.css', '\\webmozart\\puli', 'css/style.css'), + array('\\webmozart\\css\\style.css', '\\webmozart\\puli', '../css/style.css'), + array('\\css\\style.css', '\\webmozart\\puli', '../../css/style.css'), + + array('C:/webmozart/puli/css/style.css', 'C:/webmozart/puli', 'css/style.css'), + array('C:/webmozart/css/style.css', 'C:/webmozart/puli', '../css/style.css'), + array('C:/css/style.css', 'C:/webmozart/puli', '../../css/style.css'), + + array('C:\\webmozart\\puli\\css\\style.css', 'C:\\webmozart\\puli', 'css/style.css'), + array('C:\\webmozart\\css\\style.css', 'C:\\webmozart\\puli', '../css/style.css'), + array('C:\\css\\style.css', 'C:\\webmozart\\puli', '../../css/style.css'), + + array('phar:///webmozart/puli/css/style.css', 'phar:///webmozart/puli', 'css/style.css'), + array('phar:///webmozart/css/style.css', 'phar:///webmozart/puli', '../css/style.css'), + array('phar:///css/style.css', 'phar:///webmozart/puli', '../../css/style.css'), + + array('phar://C:/webmozart/puli/css/style.css', 'phar://C:/webmozart/puli', 'css/style.css'), + array('phar://C:/webmozart/css/style.css', 'phar://C:/webmozart/puli', '../css/style.css'), + array('phar://C:/css/style.css', 'phar://C:/webmozart/puli', '../../css/style.css'), + + // already relative + already in root basepath + array('../style.css', '/', 'style.css'), + array('./style.css', '/', 'style.css'), + array('../../style.css', '/', 'style.css'), + array('..\\style.css', 'C:\\', 'style.css'), + array('.\\style.css', 'C:\\', 'style.css'), + array('..\\..\\style.css', 'C:\\', 'style.css'), + array('../style.css', 'C:/', 'style.css'), + array('./style.css', 'C:/', 'style.css'), + array('../../style.css', 'C:/', 'style.css'), + array('..\\style.css', '\\', 'style.css'), + array('.\\style.css', '\\', 'style.css'), + array('..\\..\\style.css', '\\', 'style.css'), + array('../style.css', 'phar:///', 'style.css'), + array('./style.css', 'phar:///', 'style.css'), + array('../../style.css', 'phar:///', 'style.css'), + array('..\\style.css', 'phar://C:\\', 'style.css'), + array('.\\style.css', 'phar://C:\\', 'style.css'), + array('..\\..\\style.css', 'phar://C:\\', 'style.css'), + + array('css/../style.css', '/', 'style.css'), + array('css/./style.css', '/', 'css/style.css'), + array('css\\..\\style.css', 'C:\\', 'style.css'), + array('css\\.\\style.css', 'C:\\', 'css/style.css'), + array('css/../style.css', 'C:/', 'style.css'), + array('css/./style.css', 'C:/', 'css/style.css'), + array('css\\..\\style.css', '\\', 'style.css'), + array('css\\.\\style.css', '\\', 'css/style.css'), + array('css/../style.css', 'phar:///', 'style.css'), + array('css/./style.css', 'phar:///', 'css/style.css'), + array('css\\..\\style.css', 'phar://C:\\', 'style.css'), + array('css\\.\\style.css', 'phar://C:\\', 'css/style.css'), + + // already relative + array('css/style.css', '/webmozart/puli', 'css/style.css'), + array('css\\style.css', '\\webmozart\\puli', 'css/style.css'), + + // both relative + array('css/style.css', 'webmozart/puli', '../../css/style.css'), + array('css\\style.css', 'webmozart\\puli', '../../css/style.css'), + + // relative to empty + array('css/style.css', '', 'css/style.css'), + array('css\\style.css', '', 'css/style.css'), + + // different slashes in path and base path + array('/webmozart/puli/css/style.css', '\\webmozart\\puli', 'css/style.css'), + array('\\webmozart\\puli\\css\\style.css', '/webmozart/puli', 'css/style.css'), + )); + } + + /** + * @dataProvider provideMakeRelativeTests + */ + public function testMakeRelative($absolutePath, $basePath, $relativePath) + { + $this->assertSame($relativePath, Path::makeRelative($absolutePath, $basePath)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The path must be a string. Got: array + */ + public function testMakeRelativeFailsIfInvalidPath() + { + Path::makeRelative(array(), '/webmozart/puli'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The base path must be a string. Got: array + */ + public function testMakeRelativeFailsIfInvalidBasePath() + { + Path::makeRelative('/webmozart/puli/css/style.css', array()); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The absolute path "/webmozart/puli/css/style.css" cannot be made relative to the relative path "webmozart/puli". You should provide an absolute base path instead. + */ + public function testMakeRelativeFailsIfAbsolutePathAndBasePathNotAbsolute() + { + Path::makeRelative('/webmozart/puli/css/style.css', 'webmozart/puli'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The absolute path "/webmozart/puli/css/style.css" cannot be made relative to the relative path "". You should provide an absolute base path instead. + */ + public function testMakeRelativeFailsIfAbsolutePathAndBasePathEmpty() + { + Path::makeRelative('/webmozart/puli/css/style.css', ''); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The base path must be a string. Got: NULL + */ + public function testMakeRelativeFailsIfBasePathNull() + { + Path::makeRelative('/webmozart/puli/css/style.css', null); + } + + /** + * @dataProvider provideAbsolutePathsWithDifferentRoots + * @expectedException \InvalidArgumentException + */ + public function testMakeRelativeFailsIfDifferentRoot($absolutePath, $basePath) + { + Path::makeRelative($absolutePath, $basePath); + } + + public function provideIsLocalTests() + { + return array( + array('/bg.png', true), + array('bg.png', true), + array('http://example.com/bg.png', false), + array('http://example.com', false), + array('', false), + ); + } + + /** + * @dataProvider provideIsLocalTests + */ + public function testIsLocal($path, $isLocal) + { + $this->assertSame($isLocal, Path::isLocal($path)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The path must be a string. Got: array + */ + public function testIsLocalFailsIfInvalidPath() + { + Path::isLocal(array()); + } + + public function provideGetLongestCommonBasePathTests() + { + return array( + // same paths + array(array('/base/path', '/base/path'), '/base/path'), + array(array('C:/base/path', 'C:/base/path'), 'C:/base/path'), + array(array('C:\\base\\path', 'C:\\base\\path'), 'C:/base/path'), + array(array('C:/base/path', 'C:\\base\\path'), 'C:/base/path'), + array(array('phar:///base/path', 'phar:///base/path'), 'phar:///base/path'), + array(array('phar://C:/base/path', 'phar://C:/base/path'), 'phar://C:/base/path'), + + // trailing slash + array(array('/base/path/', '/base/path'), '/base/path'), + array(array('C:/base/path/', 'C:/base/path'), 'C:/base/path'), + array(array('C:\\base\\path\\', 'C:\\base\\path'), 'C:/base/path'), + array(array('C:/base/path/', 'C:\\base\\path'), 'C:/base/path'), + array(array('phar:///base/path/', 'phar:///base/path'), 'phar:///base/path'), + array(array('phar://C:/base/path/', 'phar://C:/base/path'), 'phar://C:/base/path'), + + array(array('/base/path', '/base/path/'), '/base/path'), + array(array('C:/base/path', 'C:/base/path/'), 'C:/base/path'), + array(array('C:\\base\\path', 'C:\\base\\path\\'), 'C:/base/path'), + array(array('C:/base/path', 'C:\\base\\path\\'), 'C:/base/path'), + array(array('phar:///base/path', 'phar:///base/path/'), 'phar:///base/path'), + array(array('phar://C:/base/path', 'phar://C:/base/path/'), 'phar://C:/base/path'), + + // first in second + array(array('/base/path/sub', '/base/path'), '/base/path'), + array(array('C:/base/path/sub', 'C:/base/path'), 'C:/base/path'), + array(array('C:\\base\\path\\sub', 'C:\\base\\path'), 'C:/base/path'), + array(array('C:/base/path/sub', 'C:\\base\\path'), 'C:/base/path'), + array(array('phar:///base/path/sub', 'phar:///base/path'), 'phar:///base/path'), + array(array('phar://C:/base/path/sub', 'phar://C:/base/path'), 'phar://C:/base/path'), + + // second in first + array(array('/base/path', '/base/path/sub'), '/base/path'), + array(array('C:/base/path', 'C:/base/path/sub'), 'C:/base/path'), + array(array('C:\\base\\path', 'C:\\base\\path\\sub'), 'C:/base/path'), + array(array('C:/base/path', 'C:\\base\\path\\sub'), 'C:/base/path'), + array(array('phar:///base/path', 'phar:///base/path/sub'), 'phar:///base/path'), + array(array('phar://C:/base/path', 'phar://C:/base/path/sub'), 'phar://C:/base/path'), + + // first is prefix + array(array('/base/path/di', '/base/path/dir'), '/base/path'), + array(array('C:/base/path/di', 'C:/base/path/dir'), 'C:/base/path'), + array(array('C:\\base\\path\\di', 'C:\\base\\path\\dir'), 'C:/base/path'), + array(array('C:/base/path/di', 'C:\\base\\path\\dir'), 'C:/base/path'), + array(array('phar:///base/path/di', 'phar:///base/path/dir'), 'phar:///base/path'), + array(array('phar://C:/base/path/di', 'phar://C:/base/path/dir'), 'phar://C:/base/path'), + + // second is prefix + array(array('/base/path/dir', '/base/path/di'), '/base/path'), + array(array('C:/base/path/dir', 'C:/base/path/di'), 'C:/base/path'), + array(array('C:\\base\\path\\dir', 'C:\\base\\path\\di'), 'C:/base/path'), + array(array('C:/base/path/dir', 'C:\\base\\path\\di'), 'C:/base/path'), + array(array('phar:///base/path/dir', 'phar:///base/path/di'), 'phar:///base/path'), + array(array('phar://C:/base/path/dir', 'phar://C:/base/path/di'), 'phar://C:/base/path'), + + // root is common base path + array(array('/first', '/second'), '/'), + array(array('C:/first', 'C:/second'), 'C:/'), + array(array('C:\\first', 'C:\\second'), 'C:/'), + array(array('C:/first', 'C:\\second'), 'C:/'), + array(array('phar:///first', 'phar:///second'), 'phar:///'), + array(array('phar://C:/first', 'phar://C:/second'), 'phar://C:/'), + + // windows vs unix + array(array('/base/path', 'C:/base/path'), null), + array(array('C:/base/path', '/base/path'), null), + array(array('/base/path', 'C:\\base\\path'), null), + array(array('phar:///base/path', 'phar://C:/base/path'), null), + + // different partitions + array(array('C:/base/path', 'D:/base/path'), null), + array(array('C:/base/path', 'D:\\base\\path'), null), + array(array('C:\\base\\path', 'D:\\base\\path'), null), + array(array('phar://C:/base/path', 'phar://D:/base/path'), null), + + // three paths + array(array('/base/path/foo', '/base/path', '/base/path/bar'), '/base/path'), + array(array('C:/base/path/foo', 'C:/base/path', 'C:/base/path/bar'), 'C:/base/path'), + array(array('C:\\base\\path\\foo', 'C:\\base\\path', 'C:\\base\\path\\bar'), 'C:/base/path'), + array(array('C:/base/path//foo', 'C:/base/path', 'C:\\base\\path\\bar'), 'C:/base/path'), + array(array('phar:///base/path/foo', 'phar:///base/path', 'phar:///base/path/bar'), 'phar:///base/path'), + array(array('phar://C:/base/path/foo', 'phar://C:/base/path', 'phar://C:/base/path/bar'), 'phar://C:/base/path'), + + // three paths with root + array(array('/base/path/foo', '/', '/base/path/bar'), '/'), + array(array('C:/base/path/foo', 'C:/', 'C:/base/path/bar'), 'C:/'), + array(array('C:\\base\\path\\foo', 'C:\\', 'C:\\base\\path\\bar'), 'C:/'), + array(array('C:/base/path//foo', 'C:/', 'C:\\base\\path\\bar'), 'C:/'), + array(array('phar:///base/path/foo', 'phar:///', 'phar:///base/path/bar'), 'phar:///'), + array(array('phar://C:/base/path/foo', 'phar://C:/', 'phar://C:/base/path/bar'), 'phar://C:/'), + + // three paths, different roots + array(array('/base/path/foo', 'C:/base/path', '/base/path/bar'), null), + array(array('/base/path/foo', 'C:\\base\\path', '/base/path/bar'), null), + array(array('C:/base/path/foo', 'D:/base/path', 'C:/base/path/bar'), null), + array(array('C:\\base\\path\\foo', 'D:\\base\\path', 'C:\\base\\path\\bar'), null), + array(array('C:/base/path//foo', 'D:/base/path', 'C:\\base\\path\\bar'), null), + array(array('phar:///base/path/foo', 'phar://C:/base/path', 'phar:///base/path/bar'), null), + array(array('phar://C:/base/path/foo', 'phar://D:/base/path', 'phar://C:/base/path/bar'), null), + + // only one path + array(array('/base/path'), '/base/path'), + array(array('C:/base/path'), 'C:/base/path'), + array(array('C:\\base\\path'), 'C:/base/path'), + array(array('phar:///base/path'), 'phar:///base/path'), + array(array('phar://C:/base/path'), 'phar://C:/base/path'), + ); + } + + /** + * @dataProvider provideGetLongestCommonBasePathTests + */ + public function testGetLongestCommonBasePath(array $paths, $basePath) + { + $this->assertSame($basePath, Path::getLongestCommonBasePath($paths)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The paths must be strings. Got: array + */ + public function testGetLongestCommonBasePathFailsIfInvalidPath() + { + Path::getLongestCommonBasePath(array(array())); + } + + public function provideIsBasePathTests() + { + return array( + // same paths + array('/base/path', '/base/path', true), + array('C:/base/path', 'C:/base/path', true), + array('C:\\base\\path', 'C:\\base\\path', true), + array('C:/base/path', 'C:\\base\\path', true), + array('phar:///base/path', 'phar:///base/path', true), + array('phar://C:/base/path', 'phar://C:/base/path', true), + + // trailing slash + array('/base/path/', '/base/path', true), + array('C:/base/path/', 'C:/base/path', true), + array('C:\\base\\path\\', 'C:\\base\\path', true), + array('C:/base/path/', 'C:\\base\\path', true), + array('phar:///base/path/', 'phar:///base/path', true), + array('phar://C:/base/path/', 'phar://C:/base/path', true), + + array('/base/path', '/base/path/', true), + array('C:/base/path', 'C:/base/path/', true), + array('C:\\base\\path', 'C:\\base\\path\\', true), + array('C:/base/path', 'C:\\base\\path\\', true), + array('phar:///base/path', 'phar:///base/path/', true), + array('phar://C:/base/path', 'phar://C:/base/path/', true), + + // first in second + array('/base/path/sub', '/base/path', false), + array('C:/base/path/sub', 'C:/base/path', false), + array('C:\\base\\path\\sub', 'C:\\base\\path', false), + array('C:/base/path/sub', 'C:\\base\\path', false), + array('phar:///base/path/sub', 'phar:///base/path', false), + array('phar://C:/base/path/sub', 'phar://C:/base/path', false), + + // second in first + array('/base/path', '/base/path/sub', true), + array('C:/base/path', 'C:/base/path/sub', true), + array('C:\\base\\path', 'C:\\base\\path\\sub', true), + array('C:/base/path', 'C:\\base\\path\\sub', true), + array('phar:///base/path', 'phar:///base/path/sub', true), + array('phar://C:/base/path', 'phar://C:/base/path/sub', true), + + // first is prefix + array('/base/path/di', '/base/path/dir', false), + array('C:/base/path/di', 'C:/base/path/dir', false), + array('C:\\base\\path\\di', 'C:\\base\\path\\dir', false), + array('C:/base/path/di', 'C:\\base\\path\\dir', false), + array('phar:///base/path/di', 'phar:///base/path/dir', false), + array('phar://C:/base/path/di', 'phar://C:/base/path/dir', false), + + // second is prefix + array('/base/path/dir', '/base/path/di', false), + array('C:/base/path/dir', 'C:/base/path/di', false), + array('C:\\base\\path\\dir', 'C:\\base\\path\\di', false), + array('C:/base/path/dir', 'C:\\base\\path\\di', false), + array('phar:///base/path/dir', 'phar:///base/path/di', false), + array('phar://C:/base/path/dir', 'phar://C:/base/path/di', false), + + // root + array('/', '/second', true), + array('C:/', 'C:/second', true), + array('C:', 'C:/second', true), + array('C:\\', 'C:\\second', true), + array('C:/', 'C:\\second', true), + array('phar:///', 'phar:///second', true), + array('phar://C:/', 'phar://C:/second', true), + + // windows vs unix + array('/base/path', 'C:/base/path', false), + array('C:/base/path', '/base/path', false), + array('/base/path', 'C:\\base\\path', false), + array('/base/path', 'phar:///base/path', false), + array('phar:///base/path', 'phar://C:/base/path', false), + + // different partitions + array('C:/base/path', 'D:/base/path', false), + array('C:/base/path', 'D:\\base\\path', false), + array('C:\\base\\path', 'D:\\base\\path', false), + array('C:/base/path', 'phar://C:/base/path', false), + array('phar://C:/base/path', 'phar://D:/base/path', false), + ); + } + + /** + * @dataProvider provideIsBasePathTests + */ + public function testIsBasePath($path, $ofPath, $result) + { + $this->assertSame($result, Path::isBasePath($path, $ofPath)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The base path must be a string. Got: array + */ + public function testIsBasePathFailsIfInvalidBasePath() + { + Path::isBasePath(array(), '/base/path'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The path must be a string. Got: array + */ + public function testIsBasePathFailsIfInvalidPath() + { + Path::isBasePath('/base/path', array()); + } + + public function provideJoinTests() + { + return array( + array('', '', ''), + array('/path/to/test', '', '/path/to/test'), + array('/path/to//test', '', '/path/to/test'), + array('', '/path/to/test', '/path/to/test'), + array('', '/path/to//test', '/path/to/test'), + + array('/path/to/test', 'subdir', '/path/to/test/subdir'), + array('/path/to/test/', 'subdir', '/path/to/test/subdir'), + array('/path/to/test', '/subdir', '/path/to/test/subdir'), + array('/path/to/test/', '/subdir', '/path/to/test/subdir'), + array('/path/to/test', './subdir', '/path/to/test/subdir'), + array('/path/to/test/', './subdir', '/path/to/test/subdir'), + array('/path/to/test/', '../parentdir', '/path/to/parentdir'), + array('/path/to/test', '../parentdir', '/path/to/parentdir'), + array('path/to/test/', '/subdir', 'path/to/test/subdir'), + array('path/to/test', '/subdir', 'path/to/test/subdir'), + array('../path/to/test', '/subdir', '../path/to/test/subdir'), + array('path', '../../subdir', '../subdir'), + array('/path', '../../subdir', '/subdir'), + array('../path', '../../subdir', '../../subdir'), + + array(array('/path/to/test', 'subdir'), '', '/path/to/test/subdir'), + array(array('/path/to/test', '/subdir'), '', '/path/to/test/subdir'), + array(array('/path/to/test/', 'subdir'), '', '/path/to/test/subdir'), + array(array('/path/to/test/', '/subdir'), '', '/path/to/test/subdir'), + + array(array('/path'), '', '/path'), + array(array('/path', 'to', '/test'), '', '/path/to/test'), + array(array('/path', '', '/test'), '', '/path/test'), + array(array('path', 'to', 'test'), '', 'path/to/test'), + array(array(), '', ''), + + array('base/path', 'to/test', 'base/path/to/test'), + + array('C:\\path\\to\\test', 'subdir', 'C:/path/to/test/subdir'), + array('C:\\path\\to\\test\\', 'subdir', 'C:/path/to/test/subdir'), + array('C:\\path\\to\\test', '/subdir', 'C:/path/to/test/subdir'), + array('C:\\path\\to\\test\\', '/subdir', 'C:/path/to/test/subdir'), + + array('/', 'subdir', '/subdir'), + array('/', '/subdir', '/subdir'), + array('C:/', 'subdir', 'C:/subdir'), + array('C:/', '/subdir', 'C:/subdir'), + array('C:\\', 'subdir', 'C:/subdir'), + array('C:\\', '/subdir', 'C:/subdir'), + array('C:', 'subdir', 'C:/subdir'), + array('C:', '/subdir', 'C:/subdir'), + + array('phar://', '/path/to/test', 'phar:///path/to/test'), + array('phar:///', '/path/to/test', 'phar:///path/to/test'), + array('phar:///path/to/test', 'subdir', 'phar:///path/to/test/subdir'), + array('phar:///path/to/test', 'subdir/', 'phar:///path/to/test/subdir'), + array('phar:///path/to/test', '/subdir', 'phar:///path/to/test/subdir'), + array('phar:///path/to/test/', 'subdir', 'phar:///path/to/test/subdir'), + array('phar:///path/to/test/', '/subdir', 'phar:///path/to/test/subdir'), + + array('phar://', 'C:/path/to/test', 'phar://C:/path/to/test'), + array('phar://', 'C:\\path\\to\\test', 'phar://C:/path/to/test'), + array('phar://C:/path/to/test', 'subdir', 'phar://C:/path/to/test/subdir'), + array('phar://C:/path/to/test', 'subdir/', 'phar://C:/path/to/test/subdir'), + array('phar://C:/path/to/test', '/subdir', 'phar://C:/path/to/test/subdir'), + array('phar://C:/path/to/test/', 'subdir', 'phar://C:/path/to/test/subdir'), + array('phar://C:/path/to/test/', '/subdir', 'phar://C:/path/to/test/subdir'), + array('phar://C:', 'path/to/test', 'phar://C:/path/to/test'), + array('phar://C:', '/path/to/test', 'phar://C:/path/to/test'), + array('phar://C:/', 'path/to/test', 'phar://C:/path/to/test'), + array('phar://C:/', '/path/to/test', 'phar://C:/path/to/test'), + ); + } + + /** + * @dataProvider provideJoinTests + */ + public function testJoin($path1, $path2, $result) + { + $this->assertSame($result, Path::join($path1, $path2)); + } + + public function testJoinVarArgs() + { + $this->assertSame('/path', Path::join('/path')); + $this->assertSame('/path/to', Path::join('/path', 'to')); + $this->assertSame('/path/to/test', Path::join('/path', 'to', '/test')); + $this->assertSame('/path/to/test/subdir', Path::join('/path', 'to', '/test', 'subdir/')); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The paths must be strings. Got: array + */ + public function testJoinFailsIfInvalidPath() + { + Path::join('/path', array()); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Your environment or operation system isn't supported + */ + public function testGetHomeDirectoryFailsIfNotSupportedOperationSystem() + { + putenv('HOME='); + + Path::getHomeDirectory(); + } + + public function testGetHomeDirectoryForUnix() + { + $this->assertEquals('/home/webmozart', Path::getHomeDirectory()); + } + + public function testGetHomeDirectoryForWindows() + { + putenv('HOME='); + putenv('HOMEDRIVE=C:'); + putenv('HOMEPATH=/users/webmozart'); + + $this->assertEquals('C:/users/webmozart', Path::getHomeDirectory()); + } + + public function testNormalize() + { + $this->assertSame('C:/Foo/Bar/test', Path::normalize('C:\\Foo\\Bar/test')); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testNormalizeFailsIfNoString() + { + Path::normalize(true); + } +} diff --git a/vendor/webmozart/path-util/tests/UrlTest.php b/vendor/webmozart/path-util/tests/UrlTest.php new file mode 100644 index 0000000000..ae7816abe8 --- /dev/null +++ b/vendor/webmozart/path-util/tests/UrlTest.php @@ -0,0 +1,179 @@ +<?php + +/* + * This file is part of the webmozart/path-util package. + * + * (c) Bernhard Schussek <bschussek@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Webmozart\PathUtil\Tests; + +use Webmozart\PathUtil\Url; + +/** + * @since 2.3 + * + * @author Bernhard Schussek <bschussek@gmail.com> + * @author Claudio Zizza <claudio@budgegeria.de> + */ +class UrlTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider provideMakeRelativeTests + * @covers Webmozart\PathUtil\Url + */ + public function testMakeRelative($absolutePath, $basePath, $relativePath) + { + $host = 'http://example.com'; + + $relative = Url::makeRelative($host.$absolutePath, $host.$basePath); + $this->assertSame($relativePath, $relative); + $relative = Url::makeRelative($absolutePath, $host.$basePath); + $this->assertSame($relativePath, $relative); + } + + /** + * @dataProvider provideMakeRelativeIsAlreadyRelativeTests + * @covers Webmozart\PathUtil\Url + */ + public function testMakeRelativeIsAlreadyRelative($absolutePath, $basePath, $relativePath) + { + $host = 'http://example.com'; + + $relative = Url::makeRelative($absolutePath, $host.$basePath); + $this->assertSame($relativePath, $relative); + } + + /** + * @dataProvider provideMakeRelativeTests + * @covers Webmozart\PathUtil\Url + */ + public function testMakeRelativeWithFullUrl($absolutePath, $basePath, $relativePath) + { + $host = 'ftp://user:password@example.com:8080'; + + $relative = Url::makeRelative($host.$absolutePath, $host.$basePath); + $this->assertSame($relativePath, $relative); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The URL must be a string. Got: array + * @covers Webmozart\PathUtil\Url + */ + public function testMakeRelativeFailsIfInvalidUrl() + { + Url::makeRelative(array(), 'http://example.com/webmozart/puli'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The base URL must be a string. Got: array + * @covers Webmozart\PathUtil\Url + */ + public function testMakeRelativeFailsIfInvalidBaseUrl() + { + Url::makeRelative('http://example.com/webmozart/puli/css/style.css', array()); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage "webmozart/puli" is not an absolute Url. + * @covers Webmozart\PathUtil\Url + */ + public function testMakeRelativeFailsIfBaseUrlNoUrl() + { + Url::makeRelative('http://example.com/webmozart/puli/css/style.css', 'webmozart/puli'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage "" is not an absolute Url. + * @covers Webmozart\PathUtil\Url + */ + public function testMakeRelativeFailsIfBaseUrlEmpty() + { + Url::makeRelative('http://example.com/webmozart/puli/css/style.css', ''); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The base URL must be a string. Got: NULL + * @covers Webmozart\PathUtil\Url + */ + public function testMakeRelativeFailsIfBaseUrlNull() + { + Url::makeRelative('http://example.com/webmozart/puli/css/style.css', null); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The URL "http://example.com" cannot be made relative to "http://example2.com" since + * their host names are different. + * @covers Webmozart\PathUtil\Url + */ + public function testMakeRelativeFailsIfDifferentDomains() + { + Url::makeRelative('http://example.com/webmozart/puli/css/style.css', 'http://example2.com/webmozart/puli'); + } + + public function provideMakeRelativeTests() + { + return array( + + array('/webmozart/puli/css/style.css', '/webmozart/puli', 'css/style.css'), + array('/webmozart/puli/css/style.css?key=value&key2=value', '/webmozart/puli', 'css/style.css?key=value&key2=value'), + array('/webmozart/puli/css/style.css?key[]=value&key[]=value', '/webmozart/puli', 'css/style.css?key[]=value&key[]=value'), + array('/webmozart/css/style.css', '/webmozart/puli', '../css/style.css'), + array('/css/style.css', '/webmozart/puli', '../../css/style.css'), + array('/', '/', ''), + + // relative to root + array('/css/style.css', '/', 'css/style.css'), + + // same sub directories in different base directories + array('/puli/css/style.css', '/webmozart/css', '../../puli/css/style.css'), + + array('/webmozart/puli/./css/style.css', '/webmozart/puli', 'css/style.css'), + array('/webmozart/puli/../css/style.css', '/webmozart/puli', '../css/style.css'), + array('/webmozart/puli/.././css/style.css', '/webmozart/puli', '../css/style.css'), + array('/webmozart/puli/./../css/style.css', '/webmozart/puli', '../css/style.css'), + array('/webmozart/puli/../../css/style.css', '/webmozart/puli', '../../css/style.css'), + array('/webmozart/puli/css/style.css', '/webmozart/./puli', 'css/style.css'), + array('/webmozart/puli/css/style.css', '/webmozart/../puli', '../webmozart/puli/css/style.css'), + array('/webmozart/puli/css/style.css', '/webmozart/./../puli', '../webmozart/puli/css/style.css'), + array('/webmozart/puli/css/style.css', '/webmozart/.././puli', '../webmozart/puli/css/style.css'), + array('/webmozart/puli/css/style.css', '/webmozart/../../puli', '../webmozart/puli/css/style.css'), + + // first argument shorter than second + array('/css', '/webmozart/puli', '../../css'), + + // second argument shorter than first + array('/webmozart/puli', '/css', '../webmozart/puli'), + + array('', '', ''), + ); + } + + public function provideMakeRelativeIsAlreadyRelativeTests() + { + return array( + array('css/style.css', '/webmozart/puli', 'css/style.css'), + array('css/style.css', '', 'css/style.css'), + array('css/../style.css', '', 'style.css'), + array('css/./style.css', '', 'css/style.css'), + array('../style.css', '/', 'style.css'), + array('./style.css', '/', 'style.css'), + array('../../style.css', '/', 'style.css'), + array('../../style.css', '', 'style.css'), + array('./style.css', '', 'style.css'), + array('../style.css', '', 'style.css'), + array('./../style.css', '', 'style.css'), + array('css/./../style.css', '', 'style.css'), + array('css//style.css', '', 'css/style.css'), + ); + } +}