This commit is contained in:
2020-10-06 14:27:47 +07:00
commit 586be80cf6
16613 changed files with 3274099 additions and 0 deletions

3
vendor/symfony/browser-kit/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
vendor/
composer.lock
phpunit.xml

35
vendor/symfony/browser-kit/CHANGELOG.md vendored Normal file
View File

@@ -0,0 +1,35 @@
CHANGELOG
=========
3.4.0
-----
* [BC BREAK] Client will skip redirects during history navigation
(back and forward calls) according to W3C Browsers recommendation
3.3.0
-----
* [BC BREAK] The request method is dropped from POST to GET when the response
status code is 301.
3.2.0
-----
* Client HTTP user agent has been changed to 'Symfony BrowserKit'
2.3.0
-----
* [BC BREAK] `Client::followRedirect()` won't redirect responses with
a non-3xx Status Code and `Location` header anymore, as per
http://tools.ietf.org/html/rfc2616#section-14.30
* added `Client::getInternalRequest()` and `Client::getInternalResponse()` to
have access to the BrowserKit internal request and response objects
2.1.0
-----
* [BC BREAK] The CookieJar internals have changed to allow cookies with the
same name on different sub-domains/sub-paths

619
vendor/symfony/browser-kit/Client.php vendored Normal file
View File

@@ -0,0 +1,619 @@
<?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\BrowserKit;
use Symfony\Component\DomCrawler\Crawler;
use Symfony\Component\DomCrawler\Form;
use Symfony\Component\DomCrawler\Link;
use Symfony\Component\Process\PhpProcess;
/**
* Client simulates a browser.
*
* To make the actual request, you need to implement the doRequest() method.
*
* If you want to be able to run requests in their own process (insulated flag),
* you need to also implement the getScript() method.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class Client
{
protected $history;
protected $cookieJar;
protected $server = array();
protected $internalRequest;
protected $request;
protected $internalResponse;
protected $response;
protected $crawler;
protected $insulated = false;
protected $redirect;
protected $followRedirects = true;
private $maxRedirects = -1;
private $redirectCount = 0;
private $redirects = array();
private $isMainRequest = true;
/**
* @param array $server The server parameters (equivalent of $_SERVER)
* @param History $history A History instance to store the browser history
* @param CookieJar $cookieJar A CookieJar instance to store the cookies
*/
public function __construct(array $server = array(), History $history = null, CookieJar $cookieJar = null)
{
$this->setServerParameters($server);
$this->history = $history ?: new History();
$this->cookieJar = $cookieJar ?: new CookieJar();
}
/**
* Sets whether to automatically follow redirects or not.
*
* @param bool $followRedirect Whether to follow redirects
*/
public function followRedirects($followRedirect = true)
{
$this->followRedirects = (bool) $followRedirect;
}
/**
* Returns whether client automatically follows redirects or not.
*
* @return bool
*/
public function isFollowingRedirects()
{
return $this->followRedirects;
}
/**
* Sets the maximum number of redirects that crawler can follow.
*
* @param int $maxRedirects
*/
public function setMaxRedirects($maxRedirects)
{
$this->maxRedirects = $maxRedirects < 0 ? -1 : $maxRedirects;
$this->followRedirects = -1 != $this->maxRedirects;
}
/**
* Returns the maximum number of redirects that crawler can follow.
*
* @return int
*/
public function getMaxRedirects()
{
return $this->maxRedirects;
}
/**
* Sets the insulated flag.
*
* @param bool $insulated Whether to insulate the requests or not
*
* @throws \RuntimeException When Symfony Process Component is not installed
*/
public function insulate($insulated = true)
{
if ($insulated && !class_exists('Symfony\\Component\\Process\\Process')) {
throw new \RuntimeException('Unable to isolate requests as the Symfony Process Component is not installed.');
}
$this->insulated = (bool) $insulated;
}
/**
* Sets server parameters.
*
* @param array $server An array of server parameters
*/
public function setServerParameters(array $server)
{
$this->server = array_merge(array(
'HTTP_USER_AGENT' => 'Symfony BrowserKit',
), $server);
}
/**
* Sets single server parameter.
*
* @param string $key A key of the parameter
* @param string $value A value of the parameter
*/
public function setServerParameter($key, $value)
{
$this->server[$key] = $value;
}
/**
* Gets single server parameter for specified key.
*
* @param string $key A key of the parameter to get
* @param string $default A default value when key is undefined
*
* @return string A value of the parameter
*/
public function getServerParameter($key, $default = '')
{
return isset($this->server[$key]) ? $this->server[$key] : $default;
}
/**
* Returns the History instance.
*
* @return History A History instance
*/
public function getHistory()
{
return $this->history;
}
/**
* Returns the CookieJar instance.
*
* @return CookieJar A CookieJar instance
*/
public function getCookieJar()
{
return $this->cookieJar;
}
/**
* Returns the current Crawler instance.
*
* @return Crawler|null A Crawler instance
*/
public function getCrawler()
{
return $this->crawler;
}
/**
* Returns the current BrowserKit Response instance.
*
* @return Response|null A BrowserKit Response instance
*/
public function getInternalResponse()
{
return $this->internalResponse;
}
/**
* Returns the current origin response instance.
*
* The origin response is the response instance that is returned
* by the code that handles requests.
*
* @return object|null A response instance
*
* @see doRequest()
*/
public function getResponse()
{
return $this->response;
}
/**
* Returns the current BrowserKit Request instance.
*
* @return Request|null A BrowserKit Request instance
*/
public function getInternalRequest()
{
return $this->internalRequest;
}
/**
* Returns the current origin Request instance.
*
* The origin request is the request instance that is sent
* to the code that handles requests.
*
* @return object|null A Request instance
*
* @see doRequest()
*/
public function getRequest()
{
return $this->request;
}
/**
* Clicks on a given link.
*
* @return Crawler
*/
public function click(Link $link)
{
if ($link instanceof Form) {
return $this->submit($link);
}
return $this->request($link->getMethod(), $link->getUri());
}
/**
* Submits a form.
*
* @param Form $form A Form instance
* @param array $values An array of form field values
*
* @return Crawler
*/
public function submit(Form $form, array $values = array())
{
$form->setValues($values);
return $this->request($form->getMethod(), $form->getUri(), $form->getPhpValues(), $form->getPhpFiles());
}
/**
* Calls a URI.
*
* @param string $method The request method
* @param string $uri The URI to fetch
* @param array $parameters The Request parameters
* @param array $files The files
* @param array $server The server parameters (HTTP headers are referenced with a HTTP_ prefix as PHP does)
* @param string $content The raw body data
* @param bool $changeHistory Whether to update the history or not (only used internally for back(), forward(), and reload())
*
* @return Crawler
*/
public function request($method, $uri, array $parameters = array(), array $files = array(), array $server = array(), $content = null, $changeHistory = true)
{
if ($this->isMainRequest) {
$this->redirectCount = 0;
} else {
++$this->redirectCount;
}
$uri = $this->getAbsoluteUri($uri);
$server = array_merge($this->server, $server);
if (isset($server['HTTPS'])) {
$uri = preg_replace('{^'.parse_url($uri, PHP_URL_SCHEME).'}', $server['HTTPS'] ? 'https' : 'http', $uri);
}
if (!$this->history->isEmpty()) {
$server['HTTP_REFERER'] = $this->history->current()->getUri();
}
if (empty($server['HTTP_HOST'])) {
$server['HTTP_HOST'] = $this->extractHost($uri);
}
$server['HTTPS'] = 'https' == parse_url($uri, PHP_URL_SCHEME);
$this->internalRequest = new Request($uri, $method, $parameters, $files, $this->cookieJar->allValues($uri), $server, $content);
$this->request = $this->filterRequest($this->internalRequest);
if (true === $changeHistory) {
$this->history->add($this->internalRequest);
}
if ($this->insulated) {
$this->response = $this->doRequestInProcess($this->request);
} else {
$this->response = $this->doRequest($this->request);
}
$this->internalResponse = $this->filterResponse($this->response);
$this->cookieJar->updateFromResponse($this->internalResponse, $uri);
$status = $this->internalResponse->getStatus();
if ($status >= 300 && $status < 400) {
$this->redirect = $this->internalResponse->getHeader('Location');
} else {
$this->redirect = null;
}
if ($this->followRedirects && $this->redirect) {
$this->redirects[serialize($this->history->current())] = true;
return $this->crawler = $this->followRedirect();
}
return $this->crawler = $this->createCrawlerFromContent($this->internalRequest->getUri(), $this->internalResponse->getContent(), $this->internalResponse->getHeader('Content-Type'));
}
/**
* Makes a request in another process.
*
* @param object $request An origin request instance
*
* @return object An origin response instance
*
* @throws \RuntimeException When processing returns exit code
*/
protected function doRequestInProcess($request)
{
$deprecationsFile = tempnam(sys_get_temp_dir(), 'deprec');
putenv('SYMFONY_DEPRECATIONS_SERIALIZE='.$deprecationsFile);
$_ENV['SYMFONY_DEPRECATIONS_SERIALIZE'] = $deprecationsFile;
$process = new PhpProcess($this->getScript($request), null, null);
$process->run();
if (file_exists($deprecationsFile)) {
$deprecations = file_get_contents($deprecationsFile);
unlink($deprecationsFile);
foreach ($deprecations ? unserialize($deprecations) : array() as $deprecation) {
if ($deprecation[0]) {
@trigger_error($deprecation[1], E_USER_DEPRECATED);
} else {
@trigger_error($deprecation[1], E_USER_DEPRECATED);
}
}
}
if (!$process->isSuccessful() || !preg_match('/^O\:\d+\:/', $process->getOutput())) {
throw new \RuntimeException(sprintf('OUTPUT: %s ERROR OUTPUT: %s', $process->getOutput(), $process->getErrorOutput()));
}
return unserialize($process->getOutput());
}
/**
* Makes a request.
*
* @param object $request An origin request instance
*
* @return object An origin response instance
*/
abstract protected function doRequest($request);
/**
* Returns the script to execute when the request must be insulated.
*
* @param object $request An origin request instance
*
* @throws \LogicException When this abstract class is not implemented
*/
protected function getScript($request)
{
throw new \LogicException('To insulate requests, you need to override the getScript() method.');
}
/**
* Filters the BrowserKit request to the origin one.
*
* @param Request $request The BrowserKit Request to filter
*
* @return object An origin request instance
*/
protected function filterRequest(Request $request)
{
return $request;
}
/**
* Filters the origin response to the BrowserKit one.
*
* @param object $response The origin response to filter
*
* @return Response An BrowserKit Response instance
*/
protected function filterResponse($response)
{
return $response;
}
/**
* Creates a crawler.
*
* This method returns null if the DomCrawler component is not available.
*
* @param string $uri A URI
* @param string $content Content for the crawler to use
* @param string $type Content type
*
* @return Crawler|null
*/
protected function createCrawlerFromContent($uri, $content, $type)
{
if (!class_exists('Symfony\Component\DomCrawler\Crawler')) {
return;
}
$crawler = new Crawler(null, $uri);
$crawler->addContent($content, $type);
return $crawler;
}
/**
* Goes back in the browser history.
*
* @return Crawler
*/
public function back()
{
do {
$request = $this->history->back();
} while (array_key_exists(serialize($request), $this->redirects));
return $this->requestFromRequest($request, false);
}
/**
* Goes forward in the browser history.
*
* @return Crawler
*/
public function forward()
{
do {
$request = $this->history->forward();
} while (array_key_exists(serialize($request), $this->redirects));
return $this->requestFromRequest($request, false);
}
/**
* Reloads the current browser.
*
* @return Crawler
*/
public function reload()
{
return $this->requestFromRequest($this->history->current(), false);
}
/**
* Follow redirects?
*
* @return Crawler
*
* @throws \LogicException If request was not a redirect
*/
public function followRedirect()
{
if (empty($this->redirect)) {
throw new \LogicException('The request was not redirected.');
}
if (-1 !== $this->maxRedirects) {
if ($this->redirectCount > $this->maxRedirects) {
$this->redirectCount = 0;
throw new \LogicException(sprintf('The maximum number (%d) of redirections was reached.', $this->maxRedirects));
}
}
$request = $this->internalRequest;
if (\in_array($this->internalResponse->getStatus(), array(301, 302, 303))) {
$method = 'GET';
$files = array();
$content = null;
} else {
$method = $request->getMethod();
$files = $request->getFiles();
$content = $request->getContent();
}
if ('GET' === strtoupper($method)) {
// Don't forward parameters for GET request as it should reach the redirection URI
$parameters = array();
} else {
$parameters = $request->getParameters();
}
$server = $request->getServer();
$server = $this->updateServerFromUri($server, $this->redirect);
$this->isMainRequest = false;
$response = $this->request($method, $this->redirect, $parameters, $files, $server, $content);
$this->isMainRequest = true;
return $response;
}
/**
* Restarts the client.
*
* It flushes history and all cookies.
*/
public function restart()
{
$this->cookieJar->clear();
$this->history->clear();
}
/**
* Takes a URI and converts it to absolute if it is not already absolute.
*
* @param string $uri A URI
*
* @return string An absolute URI
*/
protected function getAbsoluteUri($uri)
{
// already absolute?
if (0 === strpos($uri, 'http://') || 0 === strpos($uri, 'https://')) {
return $uri;
}
if (!$this->history->isEmpty()) {
$currentUri = $this->history->current()->getUri();
} else {
$currentUri = sprintf('http%s://%s/',
isset($this->server['HTTPS']) ? 's' : '',
isset($this->server['HTTP_HOST']) ? $this->server['HTTP_HOST'] : 'localhost'
);
}
// protocol relative URL
if (0 === strpos($uri, '//')) {
return parse_url($currentUri, PHP_URL_SCHEME).':'.$uri;
}
// anchor or query string parameters?
if (!$uri || '#' == $uri[0] || '?' == $uri[0]) {
return preg_replace('/[#?].*?$/', '', $currentUri).$uri;
}
if ('/' !== $uri[0]) {
$path = parse_url($currentUri, PHP_URL_PATH);
if ('/' !== substr($path, -1)) {
$path = substr($path, 0, strrpos($path, '/') + 1);
}
$uri = $path.$uri;
}
return preg_replace('#^(.*?//[^/]+)\/.*$#', '$1', $currentUri).$uri;
}
/**
* Makes a request from a Request object directly.
*
* @param Request $request A Request instance
* @param bool $changeHistory Whether to update the history or not (only used internally for back(), forward(), and reload())
*
* @return Crawler
*/
protected function requestFromRequest(Request $request, $changeHistory = true)
{
return $this->request($request->getMethod(), $request->getUri(), $request->getParameters(), $request->getFiles(), $request->getServer(), $request->getContent(), $changeHistory);
}
private function updateServerFromUri($server, $uri)
{
$server['HTTP_HOST'] = $this->extractHost($uri);
$scheme = parse_url($uri, PHP_URL_SCHEME);
$server['HTTPS'] = null === $scheme ? $server['HTTPS'] : 'https' == $scheme;
unset($server['HTTP_IF_NONE_MATCH'], $server['HTTP_IF_MODIFIED_SINCE']);
return $server;
}
private function extractHost($uri)
{
$host = parse_url($uri, PHP_URL_HOST);
if ($port = parse_url($uri, PHP_URL_PORT)) {
return $host.':'.$port;
}
return $host;
}
}

301
vendor/symfony/browser-kit/Cookie.php vendored Normal file
View File

@@ -0,0 +1,301 @@
<?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\BrowserKit;
/**
* Cookie represents an HTTP cookie.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Cookie
{
/**
* Handles dates as defined by RFC 2616 section 3.3.1, and also some other
* non-standard, but common formats.
*/
private static $dateFormats = array(
'D, d M Y H:i:s T',
'D, d-M-y H:i:s T',
'D, d-M-Y H:i:s T',
'D, d-m-y H:i:s T',
'D, d-m-Y H:i:s T',
'D M j G:i:s Y',
'D M d H:i:s Y T',
);
protected $name;
protected $value;
protected $expires;
protected $path;
protected $domain;
protected $secure;
protected $httponly;
protected $rawValue;
/**
* Sets a cookie.
*
* @param string $name The cookie name
* @param string $value The value of the cookie
* @param string|null $expires The time the cookie expires
* @param string|null $path The path on the server in which the cookie will be available on
* @param string $domain The domain that the cookie is available
* @param bool $secure Indicates that the cookie should only be transmitted over a secure HTTPS connection from the client
* @param bool $httponly The cookie httponly flag
* @param bool $encodedValue Whether the value is encoded or not
*/
public function __construct($name, $value, $expires = null, $path = null, $domain = '', $secure = false, $httponly = true, $encodedValue = false)
{
if ($encodedValue) {
$this->value = urldecode($value);
$this->rawValue = $value;
} else {
$this->value = $value;
$this->rawValue = rawurlencode($value);
}
$this->name = $name;
$this->path = empty($path) ? '/' : $path;
$this->domain = $domain;
$this->secure = (bool) $secure;
$this->httponly = (bool) $httponly;
if (null !== $expires) {
$timestampAsDateTime = \DateTime::createFromFormat('U', $expires);
if (false === $timestampAsDateTime) {
throw new \UnexpectedValueException(sprintf('The cookie expiration time "%s" is not valid.', $expires));
}
$this->expires = $timestampAsDateTime->format('U');
}
}
/**
* Returns the HTTP representation of the Cookie.
*/
public function __toString()
{
$cookie = sprintf('%s=%s', $this->name, $this->rawValue);
if (null !== $this->expires) {
$dateTime = \DateTime::createFromFormat('U', $this->expires, new \DateTimeZone('GMT'));
$cookie .= '; expires='.str_replace('+0000', '', $dateTime->format(self::$dateFormats[0]));
}
if ('' !== $this->domain) {
$cookie .= '; domain='.$this->domain;
}
if ($this->path) {
$cookie .= '; path='.$this->path;
}
if ($this->secure) {
$cookie .= '; secure';
}
if ($this->httponly) {
$cookie .= '; httponly';
}
return $cookie;
}
/**
* Creates a Cookie instance from a Set-Cookie header value.
*
* @param string $cookie A Set-Cookie header value
* @param string|null $url The base URL
*
* @return static
*
* @throws \InvalidArgumentException
*/
public static function fromString($cookie, $url = null)
{
$parts = explode(';', $cookie);
if (false === strpos($parts[0], '=')) {
throw new \InvalidArgumentException(sprintf('The cookie string "%s" is not valid.', $parts[0]));
}
list($name, $value) = explode('=', array_shift($parts), 2);
$values = array(
'name' => trim($name),
'value' => trim($value),
'expires' => null,
'path' => '/',
'domain' => '',
'secure' => false,
'httponly' => false,
'passedRawValue' => true,
);
if (null !== $url) {
if ((false === $urlParts = parse_url($url)) || !isset($urlParts['host'])) {
throw new \InvalidArgumentException(sprintf('The URL "%s" is not valid.', $url));
}
$values['domain'] = $urlParts['host'];
$values['path'] = isset($urlParts['path']) ? substr($urlParts['path'], 0, strrpos($urlParts['path'], '/')) : '';
}
foreach ($parts as $part) {
$part = trim($part);
if ('secure' === strtolower($part)) {
// Ignore the secure flag if the original URI is not given or is not HTTPS
if (!$url || !isset($urlParts['scheme']) || 'https' != $urlParts['scheme']) {
continue;
}
$values['secure'] = true;
continue;
}
if ('httponly' === strtolower($part)) {
$values['httponly'] = true;
continue;
}
if (2 === \count($elements = explode('=', $part, 2))) {
if ('expires' === strtolower($elements[0])) {
$elements[1] = self::parseDate($elements[1]);
}
$values[strtolower($elements[0])] = $elements[1];
}
}
return new static(
$values['name'],
$values['value'],
$values['expires'],
$values['path'],
$values['domain'],
$values['secure'],
$values['httponly'],
$values['passedRawValue']
);
}
private static function parseDate($dateValue)
{
// trim single quotes around date if present
if (($length = \strlen($dateValue)) > 1 && "'" === $dateValue[0] && "'" === $dateValue[$length - 1]) {
$dateValue = substr($dateValue, 1, -1);
}
foreach (self::$dateFormats as $dateFormat) {
if (false !== $date = \DateTime::createFromFormat($dateFormat, $dateValue, new \DateTimeZone('GMT'))) {
return $date->format('U');
}
}
// attempt a fallback for unusual formatting
if (false !== $date = date_create($dateValue, new \DateTimeZone('GMT'))) {
return $date->format('U');
}
}
/**
* Gets the name of the cookie.
*
* @return string The cookie name
*/
public function getName()
{
return $this->name;
}
/**
* Gets the value of the cookie.
*
* @return string The cookie value
*/
public function getValue()
{
return $this->value;
}
/**
* Gets the raw value of the cookie.
*
* @return string The cookie value
*/
public function getRawValue()
{
return $this->rawValue;
}
/**
* Gets the expires time of the cookie.
*
* @return string|null The cookie expires time
*/
public function getExpiresTime()
{
return $this->expires;
}
/**
* Gets the path of the cookie.
*
* @return string The cookie path
*/
public function getPath()
{
return $this->path;
}
/**
* Gets the domain of the cookie.
*
* @return string The cookie domain
*/
public function getDomain()
{
return $this->domain;
}
/**
* Returns the secure flag of the cookie.
*
* @return bool The cookie secure flag
*/
public function isSecure()
{
return $this->secure;
}
/**
* Returns the httponly flag of the cookie.
*
* @return bool The cookie httponly flag
*/
public function isHttpOnly()
{
return $this->httponly;
}
/**
* Returns true if the cookie has expired.
*
* @return bool true if the cookie has expired, false otherwise
*/
public function isExpired()
{
return null !== $this->expires && 0 != $this->expires && $this->expires < time();
}
}

239
vendor/symfony/browser-kit/CookieJar.php vendored Normal file
View File

@@ -0,0 +1,239 @@
<?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\BrowserKit;
/**
* CookieJar.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class CookieJar
{
protected $cookieJar = array();
public function set(Cookie $cookie)
{
$this->cookieJar[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie;
}
/**
* Gets a cookie by name.
*
* You should never use an empty domain, but if you do so,
* this method returns the first cookie for the given name/path
* (this behavior ensures a BC behavior with previous versions of
* Symfony).
*
* @param string $name The cookie name
* @param string $path The cookie path
* @param string $domain The cookie domain
*
* @return Cookie|null A Cookie instance or null if the cookie does not exist
*/
public function get($name, $path = '/', $domain = null)
{
$this->flushExpiredCookies();
foreach ($this->cookieJar as $cookieDomain => $pathCookies) {
if ($cookieDomain && $domain) {
$cookieDomain = '.'.ltrim($cookieDomain, '.');
if ($cookieDomain !== substr('.'.$domain, -\strlen($cookieDomain))) {
continue;
}
}
foreach ($pathCookies as $cookiePath => $namedCookies) {
if (0 !== strpos($path, $cookiePath)) {
continue;
}
if (isset($namedCookies[$name])) {
return $namedCookies[$name];
}
}
}
}
/**
* Removes a cookie by name.
*
* You should never use an empty domain, but if you do so,
* all cookies for the given name/path expire (this behavior
* ensures a BC behavior with previous versions of Symfony).
*
* @param string $name The cookie name
* @param string $path The cookie path
* @param string $domain The cookie domain
*/
public function expire($name, $path = '/', $domain = null)
{
if (null === $path) {
$path = '/';
}
if (empty($domain)) {
// an empty domain means any domain
// this should never happen but it allows for a better BC
$domains = array_keys($this->cookieJar);
} else {
$domains = array($domain);
}
foreach ($domains as $domain) {
unset($this->cookieJar[$domain][$path][$name]);
if (empty($this->cookieJar[$domain][$path])) {
unset($this->cookieJar[$domain][$path]);
if (empty($this->cookieJar[$domain])) {
unset($this->cookieJar[$domain]);
}
}
}
}
/**
* Removes all the cookies from the jar.
*/
public function clear()
{
$this->cookieJar = array();
}
/**
* Updates the cookie jar from a response Set-Cookie headers.
*
* @param array $setCookies Set-Cookie headers from an HTTP response
* @param string $uri The base URL
*/
public function updateFromSetCookie(array $setCookies, $uri = null)
{
$cookies = array();
foreach ($setCookies as $cookie) {
foreach (explode(',', $cookie) as $i => $part) {
if (0 === $i || preg_match('/^(?P<token>\s*[0-9A-Za-z!#\$%\&\'\*\+\-\.^_`\|~]+)=/', $part)) {
$cookies[] = ltrim($part);
} else {
$cookies[\count($cookies) - 1] .= ','.$part;
}
}
}
foreach ($cookies as $cookie) {
try {
$this->set(Cookie::fromString($cookie, $uri));
} catch (\InvalidArgumentException $e) {
// invalid cookies are just ignored
}
}
}
/**
* Updates the cookie jar from a Response object.
*
* @param Response $response A Response object
* @param string $uri The base URL
*/
public function updateFromResponse(Response $response, $uri = null)
{
$this->updateFromSetCookie($response->getHeader('Set-Cookie', false), $uri);
}
/**
* Returns not yet expired cookies.
*
* @return Cookie[] An array of cookies
*/
public function all()
{
$this->flushExpiredCookies();
$flattenedCookies = array();
foreach ($this->cookieJar as $path) {
foreach ($path as $cookies) {
foreach ($cookies as $cookie) {
$flattenedCookies[] = $cookie;
}
}
}
return $flattenedCookies;
}
/**
* Returns not yet expired cookie values for the given URI.
*
* @param string $uri A URI
* @param bool $returnsRawValue Returns raw value or urldecoded value
*
* @return array An array of cookie values
*/
public function allValues($uri, $returnsRawValue = false)
{
$this->flushExpiredCookies();
$parts = array_replace(array('path' => '/'), parse_url($uri));
$cookies = array();
foreach ($this->cookieJar as $domain => $pathCookies) {
if ($domain) {
$domain = '.'.ltrim($domain, '.');
if ($domain != substr('.'.$parts['host'], -\strlen($domain))) {
continue;
}
}
foreach ($pathCookies as $path => $namedCookies) {
if ($path != substr($parts['path'], 0, \strlen($path))) {
continue;
}
foreach ($namedCookies as $cookie) {
if ($cookie->isSecure() && 'https' != $parts['scheme']) {
continue;
}
$cookies[$cookie->getName()] = $returnsRawValue ? $cookie->getRawValue() : $cookie->getValue();
}
}
}
return $cookies;
}
/**
* Returns not yet expired raw cookie values for the given URI.
*
* @param string $uri A URI
*
* @return array An array of cookie values
*/
public function allRawValues($uri)
{
return $this->allValues($uri, true);
}
/**
* Removes all expired cookies.
*/
public function flushExpiredCookies()
{
foreach ($this->cookieJar as $domain => $pathCookies) {
foreach ($pathCookies as $path => $namedCookies) {
foreach ($namedCookies as $name => $cookie) {
if ($cookie->isExpired()) {
unset($this->cookieJar[$domain][$path][$name]);
}
}
}
}
}
}

100
vendor/symfony/browser-kit/History.php vendored Normal file
View File

@@ -0,0 +1,100 @@
<?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\BrowserKit;
/**
* History.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class History
{
protected $stack = array();
protected $position = -1;
/**
* Clears the history.
*/
public function clear()
{
$this->stack = array();
$this->position = -1;
}
/**
* Adds a Request to the history.
*/
public function add(Request $request)
{
$this->stack = \array_slice($this->stack, 0, $this->position + 1);
$this->stack[] = clone $request;
$this->position = \count($this->stack) - 1;
}
/**
* Returns true if the history is empty.
*
* @return bool true if the history is empty, false otherwise
*/
public function isEmpty()
{
return 0 == \count($this->stack);
}
/**
* Goes back in the history.
*
* @return Request A Request instance
*
* @throws \LogicException if the stack is already on the first page
*/
public function back()
{
if ($this->position < 1) {
throw new \LogicException('You are already on the first page.');
}
return clone $this->stack[--$this->position];
}
/**
* Goes forward in the history.
*
* @return Request A Request instance
*
* @throws \LogicException if the stack is already on the last page
*/
public function forward()
{
if ($this->position > \count($this->stack) - 2) {
throw new \LogicException('You are already on the last page.');
}
return clone $this->stack[++$this->position];
}
/**
* Returns the current element in the history.
*
* @return Request A Request instance
*
* @throws \LogicException if the stack is empty
*/
public function current()
{
if (-1 == $this->position) {
throw new \LogicException('The page history is empty.');
}
return clone $this->stack[$this->position];
}
}

19
vendor/symfony/browser-kit/LICENSE vendored Normal file
View File

@@ -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.

14
vendor/symfony/browser-kit/README.md vendored Normal file
View File

@@ -0,0 +1,14 @@
BrowserKit Component
====================
The BrowserKit component simulates the behavior of a web browser, allowing you
to make requests, click on links and submit forms programmatically.
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/browser_kit/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)

116
vendor/symfony/browser-kit/Request.php vendored Normal file
View File

@@ -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\BrowserKit;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class Request
{
protected $uri;
protected $method;
protected $parameters;
protected $files;
protected $cookies;
protected $server;
protected $content;
/**
* @param string $uri The request URI
* @param string $method The HTTP method request
* @param array $parameters The request parameters
* @param array $files An array of uploaded files
* @param array $cookies An array of cookies
* @param array $server An array of server parameters
* @param string $content The raw body data
*/
public function __construct($uri, $method, array $parameters = array(), array $files = array(), array $cookies = array(), array $server = array(), $content = null)
{
$this->uri = $uri;
$this->method = $method;
$this->parameters = $parameters;
$this->files = $files;
$this->cookies = $cookies;
$this->server = $server;
$this->content = $content;
}
/**
* Gets the request URI.
*
* @return string The request URI
*/
public function getUri()
{
return $this->uri;
}
/**
* Gets the request HTTP method.
*
* @return string The request HTTP method
*/
public function getMethod()
{
return $this->method;
}
/**
* Gets the request parameters.
*
* @return array The request parameters
*/
public function getParameters()
{
return $this->parameters;
}
/**
* Gets the request server files.
*
* @return array The request files
*/
public function getFiles()
{
return $this->files;
}
/**
* Gets the request cookies.
*
* @return array The request cookies
*/
public function getCookies()
{
return $this->cookies;
}
/**
* Gets the request server parameters.
*
* @return array The request server parameters
*/
public function getServer()
{
return $this->server;
}
/**
* Gets the request raw body data.
*
* @return string The request raw body data
*/
public function getContent()
{
return $this->content;
}
}

125
vendor/symfony/browser-kit/Response.php vendored Normal file
View File

@@ -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\BrowserKit;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class Response
{
protected $content;
protected $status;
protected $headers;
/**
* The headers array is a set of key/value pairs. If a header is present multiple times
* then the value is an array of all the values.
*
* @param string $content The content of the response
* @param int $status The response status code
* @param array $headers An array of headers
*/
public function __construct($content = '', $status = 200, array $headers = array())
{
$this->content = $content;
$this->status = $status;
$this->headers = $headers;
}
/**
* Converts the response object to string containing all headers and the response content.
*
* @return string The response with headers and content
*/
public function __toString()
{
$headers = '';
foreach ($this->headers as $name => $value) {
if (\is_string($value)) {
$headers .= $this->buildHeader($name, $value);
} else {
foreach ($value as $headerValue) {
$headers .= $this->buildHeader($name, $headerValue);
}
}
}
return $headers."\n".$this->content;
}
/**
* Returns the build header line.
*
* @param string $name The header name
* @param string $value The header value
*
* @return string The built header line
*/
protected function buildHeader($name, $value)
{
return sprintf("%s: %s\n", $name, $value);
}
/**
* Gets the response content.
*
* @return string The response content
*/
public function getContent()
{
return $this->content;
}
/**
* Gets the response status code.
*
* @return int The response status code
*/
public function getStatus()
{
return $this->status;
}
/**
* Gets the response headers.
*
* @return array The response headers
*/
public function getHeaders()
{
return $this->headers;
}
/**
* Gets a response header.
*
* @param string $header The header name
* @param bool $first Whether to return the first value or all header values
*
* @return string|array The first header value if $first is true, an array of values otherwise
*/
public function getHeader($header, $first = true)
{
$normalizedHeader = str_replace('-', '_', strtolower($header));
foreach ($this->headers as $key => $value) {
if (str_replace('-', '_', strtolower($key)) === $normalizedHeader) {
if ($first) {
return \is_array($value) ? (\count($value) ? $value[0] : '') : $value;
}
return \is_array($value) ? $value : array($value);
}
}
return $first ? null : array();
}
}

View File

@@ -0,0 +1,718 @@
<?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\BrowserKit\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\BrowserKit\Client;
use Symfony\Component\BrowserKit\CookieJar;
use Symfony\Component\BrowserKit\History;
use Symfony\Component\BrowserKit\Response;
class SpecialResponse extends Response
{
}
class TestClient extends Client
{
protected $nextResponse = null;
protected $nextScript = null;
public function setNextResponse(Response $response)
{
$this->nextResponse = $response;
}
public function setNextScript($script)
{
$this->nextScript = $script;
}
protected function doRequest($request)
{
if (null === $this->nextResponse) {
return new Response();
}
$response = $this->nextResponse;
$this->nextResponse = null;
return $response;
}
protected function filterResponse($response)
{
if ($response instanceof SpecialResponse) {
return new Response($response->getContent(), $response->getStatus(), $response->getHeaders());
}
return $response;
}
protected function getScript($request)
{
$r = new \ReflectionClass('Symfony\Component\BrowserKit\Response');
$path = $r->getFileName();
return <<<EOF
<?php
require_once('$path');
echo serialize($this->nextScript);
EOF;
}
}
class ClientTest extends TestCase
{
public function testGetHistory()
{
$client = new TestClient(array(), $history = new History());
$this->assertSame($history, $client->getHistory(), '->getHistory() returns the History');
}
public function testGetCookieJar()
{
$client = new TestClient(array(), null, $cookieJar = new CookieJar());
$this->assertSame($cookieJar, $client->getCookieJar(), '->getCookieJar() returns the CookieJar');
}
public function testGetRequest()
{
$client = new TestClient();
$client->request('GET', 'http://example.com/');
$this->assertEquals('http://example.com/', $client->getRequest()->getUri(), '->getCrawler() returns the Request of the last request');
}
public function testGetRequestWithIpAsHttpHost()
{
$client = new TestClient();
$client->request('GET', 'https://example.com/foo', array(), array(), array('HTTP_HOST' => '127.0.0.1'));
$this->assertEquals('https://example.com/foo', $client->getRequest()->getUri());
$headers = $client->getRequest()->getServer();
$this->assertEquals('127.0.0.1', $headers['HTTP_HOST']);
}
public function testGetResponse()
{
$client = new TestClient();
$client->setNextResponse(new Response('foo'));
$client->request('GET', 'http://example.com/');
$this->assertEquals('foo', $client->getResponse()->getContent(), '->getCrawler() returns the Response of the last request');
$this->assertInstanceOf('Symfony\Component\BrowserKit\Response', $client->getResponse(), '->getCrawler() returns the Response of the last request');
}
public function testGetInternalResponse()
{
$client = new TestClient();
$client->setNextResponse(new SpecialResponse('foo'));
$client->request('GET', 'http://example.com/');
$this->assertInstanceOf('Symfony\Component\BrowserKit\Response', $client->getInternalResponse());
$this->assertNotInstanceOf('Symfony\Component\BrowserKit\Tests\SpecialResponse', $client->getInternalResponse());
$this->assertInstanceOf('Symfony\Component\BrowserKit\Tests\SpecialResponse', $client->getResponse());
}
public function testGetContent()
{
$json = '{"jsonrpc":"2.0","method":"echo","id":7,"params":["Hello World"]}';
$client = new TestClient();
$client->request('POST', 'http://example.com/jsonrpc', array(), array(), array(), $json);
$this->assertEquals($json, $client->getRequest()->getContent());
}
public function testGetCrawler()
{
$client = new TestClient();
$client->setNextResponse(new Response('foo'));
$crawler = $client->request('GET', 'http://example.com/');
$this->assertSame($crawler, $client->getCrawler(), '->getCrawler() returns the Crawler of the last request');
}
public function testRequestHttpHeaders()
{
$client = new TestClient();
$client->request('GET', '/');
$headers = $client->getRequest()->getServer();
$this->assertEquals('localhost', $headers['HTTP_HOST'], '->request() sets the HTTP_HOST header');
$client = new TestClient();
$client->request('GET', 'http://www.example.com');
$headers = $client->getRequest()->getServer();
$this->assertEquals('www.example.com', $headers['HTTP_HOST'], '->request() sets the HTTP_HOST header');
$client->request('GET', 'https://www.example.com');
$headers = $client->getRequest()->getServer();
$this->assertTrue($headers['HTTPS'], '->request() sets the HTTPS header');
$client = new TestClient();
$client->request('GET', 'http://www.example.com:8080');
$headers = $client->getRequest()->getServer();
$this->assertEquals('www.example.com:8080', $headers['HTTP_HOST'], '->request() sets the HTTP_HOST header with port');
}
public function testRequestURIConversion()
{
$client = new TestClient();
$client->request('GET', '/foo');
$this->assertEquals('http://localhost/foo', $client->getRequest()->getUri(), '->request() converts the URI to an absolute one');
$client = new TestClient();
$client->request('GET', 'http://www.example.com');
$this->assertEquals('http://www.example.com', $client->getRequest()->getUri(), '->request() does not change absolute URIs');
$client = new TestClient();
$client->request('GET', 'http://www.example.com/');
$client->request('GET', '/foo');
$this->assertEquals('http://www.example.com/foo', $client->getRequest()->getUri(), '->request() uses the previous request for relative URLs');
$client = new TestClient();
$client->request('GET', 'http://www.example.com/foo');
$client->request('GET', '#');
$this->assertEquals('http://www.example.com/foo#', $client->getRequest()->getUri(), '->request() uses the previous request for #');
$client->request('GET', '#');
$this->assertEquals('http://www.example.com/foo#', $client->getRequest()->getUri(), '->request() uses the previous request for #');
$client->request('GET', '#foo');
$this->assertEquals('http://www.example.com/foo#foo', $client->getRequest()->getUri(), '->request() uses the previous request for #');
$client = new TestClient();
$client->request('GET', 'http://www.example.com/foo/');
$client->request('GET', 'bar');
$this->assertEquals('http://www.example.com/foo/bar', $client->getRequest()->getUri(), '->request() uses the previous request for relative URLs');
$client = new TestClient();
$client->request('GET', 'http://www.example.com/foo/foobar');
$client->request('GET', 'bar');
$this->assertEquals('http://www.example.com/foo/bar', $client->getRequest()->getUri(), '->request() uses the previous request for relative URLs');
$client = new TestClient();
$client->request('GET', 'http://www.example.com/foo/');
$client->request('GET', 'http');
$this->assertEquals('http://www.example.com/foo/http', $client->getRequest()->getUri(), '->request() uses the previous request for relative URLs');
$client = new TestClient();
$client->request('GET', 'http://www.example.com/foo');
$client->request('GET', 'http/bar');
$this->assertEquals('http://www.example.com/http/bar', $client->getRequest()->getUri(), '->request() uses the previous request for relative URLs');
$client = new TestClient();
$client->request('GET', 'http://www.example.com/');
$client->request('GET', 'http');
$this->assertEquals('http://www.example.com/http', $client->getRequest()->getUri(), '->request() uses the previous request for relative URLs');
$client = new TestClient();
$client->request('GET', 'http://www.example.com/foo');
$client->request('GET', '?');
$this->assertEquals('http://www.example.com/foo?', $client->getRequest()->getUri(), '->request() uses the previous request for ?');
$client->request('GET', '?');
$this->assertEquals('http://www.example.com/foo?', $client->getRequest()->getUri(), '->request() uses the previous request for ?');
$client->request('GET', '?foo=bar');
$this->assertEquals('http://www.example.com/foo?foo=bar', $client->getRequest()->getUri(), '->request() uses the previous request for ?');
}
public function testRequestReferer()
{
$client = new TestClient();
$client->request('GET', 'http://www.example.com/foo/foobar');
$client->request('GET', 'bar');
$server = $client->getRequest()->getServer();
$this->assertEquals('http://www.example.com/foo/foobar', $server['HTTP_REFERER'], '->request() sets the referer');
}
public function testRequestHistory()
{
$client = new TestClient();
$client->request('GET', 'http://www.example.com/foo/foobar');
$client->request('GET', 'bar');
$this->assertEquals('http://www.example.com/foo/bar', $client->getHistory()->current()->getUri(), '->request() updates the History');
$this->assertEquals('http://www.example.com/foo/foobar', $client->getHistory()->back()->getUri(), '->request() updates the History');
}
public function testRequestCookies()
{
$client = new TestClient();
$client->setNextResponse(new Response('<html><a href="/foo">foo</a></html>', 200, array('Set-Cookie' => 'foo=bar')));
$client->request('GET', 'http://www.example.com/foo/foobar');
$this->assertEquals(array('foo' => 'bar'), $client->getCookieJar()->allValues('http://www.example.com/foo/foobar'), '->request() updates the CookieJar');
$client->request('GET', 'bar');
$this->assertEquals(array('foo' => 'bar'), $client->getCookieJar()->allValues('http://www.example.com/foo/foobar'), '->request() updates the CookieJar');
}
public function testRequestSecureCookies()
{
$client = new TestClient();
$client->setNextResponse(new Response('<html><a href="/foo">foo</a></html>', 200, array('Set-Cookie' => 'foo=bar; path=/; secure')));
$client->request('GET', 'https://www.example.com/foo/foobar');
$this->assertTrue($client->getCookieJar()->get('foo', '/', 'www.example.com')->isSecure());
}
public function testClick()
{
$client = new TestClient();
$client->setNextResponse(new Response('<html><a href="/foo">foo</a></html>'));
$crawler = $client->request('GET', 'http://www.example.com/foo/foobar');
$client->click($crawler->filter('a')->link());
$this->assertEquals('http://www.example.com/foo', $client->getRequest()->getUri(), '->click() clicks on links');
}
public function testClickForm()
{
$client = new TestClient();
$client->setNextResponse(new Response('<html><form action="/foo"><input type="submit" /></form></html>'));
$crawler = $client->request('GET', 'http://www.example.com/foo/foobar');
$client->click($crawler->filter('input')->form());
$this->assertEquals('http://www.example.com/foo', $client->getRequest()->getUri(), '->click() Form submit forms');
}
public function testSubmit()
{
$client = new TestClient();
$client->setNextResponse(new Response('<html><form action="/foo"><input type="submit" /></form></html>'));
$crawler = $client->request('GET', 'http://www.example.com/foo/foobar');
$client->submit($crawler->filter('input')->form());
$this->assertEquals('http://www.example.com/foo', $client->getRequest()->getUri(), '->submit() submit forms');
}
public function testSubmitPreserveAuth()
{
$client = new TestClient(array('PHP_AUTH_USER' => 'foo', 'PHP_AUTH_PW' => 'bar'));
$client->setNextResponse(new Response('<html><form action="/foo"><input type="submit" /></form></html>'));
$crawler = $client->request('GET', 'http://www.example.com/foo/foobar');
$server = $client->getRequest()->getServer();
$this->assertArrayHasKey('PHP_AUTH_USER', $server);
$this->assertEquals('foo', $server['PHP_AUTH_USER']);
$this->assertArrayHasKey('PHP_AUTH_PW', $server);
$this->assertEquals('bar', $server['PHP_AUTH_PW']);
$client->submit($crawler->filter('input')->form());
$this->assertEquals('http://www.example.com/foo', $client->getRequest()->getUri(), '->submit() submit forms');
$server = $client->getRequest()->getServer();
$this->assertArrayHasKey('PHP_AUTH_USER', $server);
$this->assertEquals('foo', $server['PHP_AUTH_USER']);
$this->assertArrayHasKey('PHP_AUTH_PW', $server);
$this->assertEquals('bar', $server['PHP_AUTH_PW']);
}
public function testFollowRedirect()
{
$client = new TestClient();
$client->followRedirects(false);
$client->request('GET', 'http://www.example.com/foo/foobar');
try {
$client->followRedirect();
$this->fail('->followRedirect() throws a \LogicException if the request was not redirected');
} catch (\Exception $e) {
$this->assertInstanceOf('LogicException', $e, '->followRedirect() throws a \LogicException if the request was not redirected');
}
$client->setNextResponse(new Response('', 302, array('Location' => 'http://www.example.com/redirected')));
$client->request('GET', 'http://www.example.com/foo/foobar');
$client->followRedirect();
$this->assertEquals('http://www.example.com/redirected', $client->getRequest()->getUri(), '->followRedirect() follows a redirect if any');
$client = new TestClient();
$client->setNextResponse(new Response('', 302, array('Location' => 'http://www.example.com/redirected')));
$client->request('GET', 'http://www.example.com/foo/foobar');
$this->assertEquals('http://www.example.com/redirected', $client->getRequest()->getUri(), '->followRedirect() automatically follows redirects if followRedirects is true');
$client = new TestClient();
$client->setNextResponse(new Response('', 201, array('Location' => 'http://www.example.com/redirected')));
$client->request('GET', 'http://www.example.com/foo/foobar');
$this->assertEquals('http://www.example.com/foo/foobar', $client->getRequest()->getUri(), '->followRedirect() does not follow redirect if HTTP Code is not 30x');
$client = new TestClient();
$client->setNextResponse(new Response('', 201, array('Location' => 'http://www.example.com/redirected')));
$client->followRedirects(false);
$client->request('GET', 'http://www.example.com/foo/foobar');
try {
$client->followRedirect();
$this->fail('->followRedirect() throws a \LogicException if the request did not respond with 30x HTTP Code');
} catch (\Exception $e) {
$this->assertInstanceOf('LogicException', $e, '->followRedirect() throws a \LogicException if the request did not respond with 30x HTTP Code');
}
}
public function testFollowRelativeRedirect()
{
$client = new TestClient();
$client->setNextResponse(new Response('', 302, array('Location' => '/redirected')));
$client->request('GET', 'http://www.example.com/foo/foobar');
$this->assertEquals('http://www.example.com/redirected', $client->getRequest()->getUri(), '->followRedirect() follows a redirect if any');
$client = new TestClient();
$client->setNextResponse(new Response('', 302, array('Location' => '/redirected:1234')));
$client->request('GET', 'http://www.example.com/foo/foobar');
$this->assertEquals('http://www.example.com/redirected:1234', $client->getRequest()->getUri(), '->followRedirect() follows relative urls');
}
public function testFollowRedirectWithMaxRedirects()
{
$client = new TestClient();
$client->setMaxRedirects(1);
$client->setNextResponse(new Response('', 302, array('Location' => 'http://www.example.com/redirected')));
$client->request('GET', 'http://www.example.com/foo/foobar');
$this->assertEquals('http://www.example.com/redirected', $client->getRequest()->getUri(), '->followRedirect() follows a redirect if any');
$client->setNextResponse(new Response('', 302, array('Location' => 'http://www.example.com/redirected2')));
try {
$client->followRedirect();
$this->fail('->followRedirect() throws a \LogicException if the request was redirected and limit of redirections was reached');
} catch (\Exception $e) {
$this->assertInstanceOf('LogicException', $e, '->followRedirect() throws a \LogicException if the request was redirected and limit of redirections was reached');
}
$client->setNextResponse(new Response('', 302, array('Location' => 'http://www.example.com/redirected')));
$client->request('GET', 'http://www.example.com/foo/foobar');
$this->assertEquals('http://www.example.com/redirected', $client->getRequest()->getUri(), '->followRedirect() follows a redirect if any');
$client->setNextResponse(new Response('', 302, array('Location' => '/redirected')));
$client->request('GET', 'http://www.example.com/foo/foobar');
$this->assertEquals('http://www.example.com/redirected', $client->getRequest()->getUri(), '->followRedirect() follows relative URLs');
$client = new TestClient();
$client->setNextResponse(new Response('', 302, array('Location' => '//www.example.org/')));
$client->request('GET', 'https://www.example.com/');
$this->assertEquals('https://www.example.org/', $client->getRequest()->getUri(), '->followRedirect() follows protocol-relative URLs');
$client = new TestClient();
$client->setNextResponse(new Response('', 302, array('Location' => 'http://www.example.com/redirected')));
$client->request('POST', 'http://www.example.com/foo/foobar', array('name' => 'bar'));
$this->assertEquals('GET', $client->getRequest()->getMethod(), '->followRedirect() uses a GET for 302');
$this->assertEquals(array(), $client->getRequest()->getParameters(), '->followRedirect() does not submit parameters when changing the method');
}
public function testFollowRedirectWithCookies()
{
$client = new TestClient();
$client->followRedirects(false);
$client->setNextResponse(new Response('', 302, array(
'Location' => 'http://www.example.com/redirected',
'Set-Cookie' => 'foo=bar',
)));
$client->request('GET', 'http://www.example.com/');
$this->assertEquals(array(), $client->getRequest()->getCookies());
$client->followRedirect();
$this->assertEquals(array('foo' => 'bar'), $client->getRequest()->getCookies());
}
public function testFollowRedirectWithHeaders()
{
$headers = array(
'HTTP_HOST' => 'www.example.com',
'HTTP_USER_AGENT' => 'Symfony BrowserKit',
'CONTENT_TYPE' => 'application/vnd.custom+xml',
'HTTPS' => false,
);
$client = new TestClient();
$client->followRedirects(false);
$client->setNextResponse(new Response('', 302, array(
'Location' => 'http://www.example.com/redirected',
)));
$client->request('GET', 'http://www.example.com/', array(), array(), array(
'CONTENT_TYPE' => 'application/vnd.custom+xml',
));
$this->assertEquals($headers, $client->getRequest()->getServer());
$client->followRedirect();
$headers['HTTP_REFERER'] = 'http://www.example.com/';
$this->assertEquals($headers, $client->getRequest()->getServer());
}
public function testFollowRedirectWithPort()
{
$headers = array(
'HTTP_HOST' => 'www.example.com:8080',
'HTTP_USER_AGENT' => 'Symfony BrowserKit',
'HTTPS' => false,
'HTTP_REFERER' => 'http://www.example.com:8080/',
);
$client = new TestClient();
$client->setNextResponse(new Response('', 302, array(
'Location' => 'http://www.example.com:8080/redirected',
)));
$client->request('GET', 'http://www.example.com:8080/');
$this->assertEquals($headers, $client->getRequest()->getServer());
}
public function testIsFollowingRedirects()
{
$client = new TestClient();
$this->assertTrue($client->isFollowingRedirects(), '->getFollowRedirects() returns default value');
$client->followRedirects(false);
$this->assertFalse($client->isFollowingRedirects(), '->getFollowRedirects() returns assigned value');
}
public function testGetMaxRedirects()
{
$client = new TestClient();
$this->assertEquals(-1, $client->getMaxRedirects(), '->getMaxRedirects() returns default value');
$client->setMaxRedirects(3);
$this->assertEquals(3, $client->getMaxRedirects(), '->getMaxRedirects() returns assigned value');
}
public function testFollowRedirectWithPostMethod()
{
$parameters = array('foo' => 'bar');
$files = array('myfile.foo' => 'baz');
$server = array('X_TEST_FOO' => 'bazbar');
$content = 'foobarbaz';
$client = new TestClient();
$client->setNextResponse(new Response('', 307, array('Location' => 'http://www.example.com/redirected')));
$client->request('POST', 'http://www.example.com/foo/foobar', $parameters, $files, $server, $content);
$this->assertEquals('http://www.example.com/redirected', $client->getRequest()->getUri(), '->followRedirect() follows a redirect with POST method');
$this->assertArrayHasKey('foo', $client->getRequest()->getParameters(), '->followRedirect() keeps parameters with POST method');
$this->assertArrayHasKey('myfile.foo', $client->getRequest()->getFiles(), '->followRedirect() keeps files with POST method');
$this->assertArrayHasKey('X_TEST_FOO', $client->getRequest()->getServer(), '->followRedirect() keeps $_SERVER with POST method');
$this->assertEquals($content, $client->getRequest()->getContent(), '->followRedirect() keeps content with POST method');
$this->assertEquals('POST', $client->getRequest()->getMethod(), '->followRedirect() keeps request method');
}
public function testFollowRedirectDropPostMethod()
{
$parameters = array('foo' => 'bar');
$files = array('myfile.foo' => 'baz');
$server = array('X_TEST_FOO' => 'bazbar');
$content = 'foobarbaz';
$client = new TestClient();
foreach (array(301, 302, 303) as $code) {
$client->setNextResponse(new Response('', $code, array('Location' => 'http://www.example.com/redirected')));
$client->request('POST', 'http://www.example.com/foo/foobar', $parameters, $files, $server, $content);
$this->assertEquals('http://www.example.com/redirected', $client->getRequest()->getUri(), '->followRedirect() follows a redirect with POST method on response code: '.$code.'.');
$this->assertEmpty($client->getRequest()->getParameters(), '->followRedirect() drops parameters with POST method on response code: '.$code.'.');
$this->assertEmpty($client->getRequest()->getFiles(), '->followRedirect() drops files with POST method on response code: '.$code.'.');
$this->assertArrayHasKey('X_TEST_FOO', $client->getRequest()->getServer(), '->followRedirect() keeps $_SERVER with POST method on response code: '.$code.'.');
$this->assertEmpty($client->getRequest()->getContent(), '->followRedirect() drops content with POST method on response code: '.$code.'.');
$this->assertEquals('GET', $client->getRequest()->getMethod(), '->followRedirect() drops request method to GET on response code: '.$code.'.');
}
}
public function testBack()
{
$client = new TestClient();
$parameters = array('foo' => 'bar');
$files = array('myfile.foo' => 'baz');
$server = array('X_TEST_FOO' => 'bazbar');
$content = 'foobarbaz';
$client->request('GET', 'http://www.example.com/foo/foobar', $parameters, $files, $server, $content);
$client->request('GET', 'http://www.example.com/foo');
$client->back();
$this->assertEquals('http://www.example.com/foo/foobar', $client->getRequest()->getUri(), '->back() goes back in the history');
$this->assertArrayHasKey('foo', $client->getRequest()->getParameters(), '->back() keeps parameters');
$this->assertArrayHasKey('myfile.foo', $client->getRequest()->getFiles(), '->back() keeps files');
$this->assertArrayHasKey('X_TEST_FOO', $client->getRequest()->getServer(), '->back() keeps $_SERVER');
$this->assertEquals($content, $client->getRequest()->getContent(), '->back() keeps content');
}
public function testForward()
{
$client = new TestClient();
$parameters = array('foo' => 'bar');
$files = array('myfile.foo' => 'baz');
$server = array('X_TEST_FOO' => 'bazbar');
$content = 'foobarbaz';
$client->request('GET', 'http://www.example.com/foo/foobar');
$client->request('GET', 'http://www.example.com/foo', $parameters, $files, $server, $content);
$client->back();
$client->forward();
$this->assertEquals('http://www.example.com/foo', $client->getRequest()->getUri(), '->forward() goes forward in the history');
$this->assertArrayHasKey('foo', $client->getRequest()->getParameters(), '->forward() keeps parameters');
$this->assertArrayHasKey('myfile.foo', $client->getRequest()->getFiles(), '->forward() keeps files');
$this->assertArrayHasKey('X_TEST_FOO', $client->getRequest()->getServer(), '->forward() keeps $_SERVER');
$this->assertEquals($content, $client->getRequest()->getContent(), '->forward() keeps content');
}
public function testBackAndFrowardWithRedirects()
{
$client = new TestClient();
$client->request('GET', 'http://www.example.com/foo');
$client->setNextResponse(new Response('', 301, array('Location' => 'http://www.example.com/redirected')));
$client->request('GET', 'http://www.example.com/bar');
$this->assertEquals('http://www.example.com/redirected', $client->getRequest()->getUri(), 'client followed redirect');
$client->back();
$this->assertEquals('http://www.example.com/foo', $client->getRequest()->getUri(), '->back() goes back in the history skipping redirects');
$client->forward();
$this->assertEquals('http://www.example.com/redirected', $client->getRequest()->getUri(), '->forward() goes forward in the history skipping redirects');
}
public function testReload()
{
$client = new TestClient();
$parameters = array('foo' => 'bar');
$files = array('myfile.foo' => 'baz');
$server = array('X_TEST_FOO' => 'bazbar');
$content = 'foobarbaz';
$client->request('GET', 'http://www.example.com/foo/foobar', $parameters, $files, $server, $content);
$client->reload();
$this->assertEquals('http://www.example.com/foo/foobar', $client->getRequest()->getUri(), '->reload() reloads the current page');
$this->assertArrayHasKey('foo', $client->getRequest()->getParameters(), '->reload() keeps parameters');
$this->assertArrayHasKey('myfile.foo', $client->getRequest()->getFiles(), '->reload() keeps files');
$this->assertArrayHasKey('X_TEST_FOO', $client->getRequest()->getServer(), '->reload() keeps $_SERVER');
$this->assertEquals($content, $client->getRequest()->getContent(), '->reload() keeps content');
}
public function testRestart()
{
$client = new TestClient();
$client->request('GET', 'http://www.example.com/foo/foobar');
$client->restart();
$this->assertTrue($client->getHistory()->isEmpty(), '->restart() clears the history');
$this->assertEquals(array(), $client->getCookieJar()->all(), '->restart() clears the cookies');
}
public function testInsulatedRequests()
{
$client = new TestClient();
$client->insulate();
$client->setNextScript("new Symfony\Component\BrowserKit\Response('foobar')");
$client->request('GET', 'http://www.example.com/foo/foobar');
$this->assertEquals('foobar', $client->getResponse()->getContent(), '->insulate() process the request in a forked process');
$client->setNextScript("new Symfony\Component\BrowserKit\Response('foobar)");
try {
$client->request('GET', 'http://www.example.com/foo/foobar');
$this->fail('->request() throws a \RuntimeException if the script has an error');
} catch (\Exception $e) {
$this->assertInstanceOf('RuntimeException', $e, '->request() throws a \RuntimeException if the script has an error');
}
}
public function testGetServerParameter()
{
$client = new TestClient();
$this->assertEquals('', $client->getServerParameter('HTTP_HOST'));
$this->assertEquals('Symfony BrowserKit', $client->getServerParameter('HTTP_USER_AGENT'));
$this->assertEquals('testvalue', $client->getServerParameter('testkey', 'testvalue'));
}
public function testSetServerParameter()
{
$client = new TestClient();
$this->assertEquals('', $client->getServerParameter('HTTP_HOST'));
$this->assertEquals('Symfony BrowserKit', $client->getServerParameter('HTTP_USER_AGENT'));
$client->setServerParameter('HTTP_HOST', 'testhost');
$this->assertEquals('testhost', $client->getServerParameter('HTTP_HOST'));
$client->setServerParameter('HTTP_USER_AGENT', 'testua');
$this->assertEquals('testua', $client->getServerParameter('HTTP_USER_AGENT'));
}
public function testSetServerParameterInRequest()
{
$client = new TestClient();
$this->assertEquals('', $client->getServerParameter('HTTP_HOST'));
$this->assertEquals('Symfony BrowserKit', $client->getServerParameter('HTTP_USER_AGENT'));
$client->request('GET', 'https://www.example.com/https/www.example.com', array(), array(), array(
'HTTP_HOST' => 'testhost',
'HTTP_USER_AGENT' => 'testua',
'HTTPS' => false,
'NEW_SERVER_KEY' => 'new-server-key-value',
));
$this->assertEquals('', $client->getServerParameter('HTTP_HOST'));
$this->assertEquals('Symfony BrowserKit', $client->getServerParameter('HTTP_USER_AGENT'));
$this->assertEquals('http://www.example.com/https/www.example.com', $client->getRequest()->getUri());
$server = $client->getRequest()->getServer();
$this->assertArrayHasKey('HTTP_USER_AGENT', $server);
$this->assertEquals('testua', $server['HTTP_USER_AGENT']);
$this->assertArrayHasKey('HTTP_HOST', $server);
$this->assertEquals('testhost', $server['HTTP_HOST']);
$this->assertArrayHasKey('NEW_SERVER_KEY', $server);
$this->assertEquals('new-server-key-value', $server['NEW_SERVER_KEY']);
$this->assertArrayHasKey('HTTPS', $server);
$this->assertFalse($server['HTTPS']);
}
public function testInternalRequest()
{
$client = new TestClient();
$client->request('GET', 'https://www.example.com/https/www.example.com', array(), array(), array(
'HTTP_HOST' => 'testhost',
'HTTP_USER_AGENT' => 'testua',
'HTTPS' => false,
'NEW_SERVER_KEY' => 'new-server-key-value',
));
$this->assertInstanceOf('Symfony\Component\BrowserKit\Request', $client->getInternalRequest());
}
public function testInternalRequestNull()
{
$client = new TestClient();
$this->assertNull($client->getInternalRequest());
}
}

View File

@@ -0,0 +1,252 @@
<?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\BrowserKit\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\BrowserKit\Cookie;
use Symfony\Component\BrowserKit\CookieJar;
use Symfony\Component\BrowserKit\Response;
class CookieJarTest extends TestCase
{
public function testSetGet()
{
$cookieJar = new CookieJar();
$cookieJar->set($cookie = new Cookie('foo', 'bar'));
$this->assertEquals($cookie, $cookieJar->get('foo'), '->set() sets a cookie');
$this->assertNull($cookieJar->get('foobar'), '->get() returns null if the cookie does not exist');
$cookieJar->set($cookie = new Cookie('foo', 'bar', time() - 86400));
$this->assertNull($cookieJar->get('foo'), '->get() returns null if the cookie is expired');
}
public function testExpire()
{
$cookieJar = new CookieJar();
$cookieJar->set($cookie = new Cookie('foo', 'bar'));
$cookieJar->expire('foo');
$this->assertNull($cookieJar->get('foo'), '->get() returns null if the cookie is expired');
}
public function testAll()
{
$cookieJar = new CookieJar();
$cookieJar->set($cookie1 = new Cookie('foo', 'bar'));
$cookieJar->set($cookie2 = new Cookie('bar', 'foo'));
$this->assertEquals(array($cookie1, $cookie2), $cookieJar->all(), '->all() returns all cookies in the jar');
}
public function testClear()
{
$cookieJar = new CookieJar();
$cookieJar->set($cookie1 = new Cookie('foo', 'bar'));
$cookieJar->set($cookie2 = new Cookie('bar', 'foo'));
$cookieJar->clear();
$this->assertEquals(array(), $cookieJar->all(), '->clear() expires all cookies');
}
public function testUpdateFromResponse()
{
$response = new Response('', 200, array('Set-Cookie' => 'foo=foo'));
$cookieJar = new CookieJar();
$cookieJar->updateFromResponse($response);
$this->assertEquals('foo', $cookieJar->get('foo')->getValue(), '->updateFromResponse() updates cookies from a Response objects');
}
public function testUpdateFromSetCookie()
{
$setCookies = array('foo=foo');
$cookieJar = new CookieJar();
$cookieJar->set(new Cookie('bar', 'bar'));
$cookieJar->updateFromSetCookie($setCookies);
$this->assertInstanceOf('Symfony\Component\BrowserKit\Cookie', $cookieJar->get('foo'));
$this->assertInstanceOf('Symfony\Component\BrowserKit\Cookie', $cookieJar->get('bar'));
$this->assertEquals('foo', $cookieJar->get('foo')->getValue(), '->updateFromSetCookie() updates cookies from a Set-Cookie header');
$this->assertEquals('bar', $cookieJar->get('bar')->getValue(), '->updateFromSetCookie() keeps existing cookies');
}
public function testUpdateFromEmptySetCookie()
{
$cookieJar = new CookieJar();
$cookieJar->updateFromSetCookie(array(''));
$this->assertEquals(array(), $cookieJar->all());
}
public function testUpdateFromSetCookieWithMultipleCookies()
{
$timestamp = time() + 3600;
$date = gmdate('D, d M Y H:i:s \G\M\T', $timestamp);
$setCookies = array(sprintf('foo=foo; expires=%s; domain=.symfony.com; path=/, bar=bar; domain=.blog.symfony.com, PHPSESSID=id; expires=%1$s', $date));
$cookieJar = new CookieJar();
$cookieJar->updateFromSetCookie($setCookies);
$fooCookie = $cookieJar->get('foo', '/', '.symfony.com');
$barCookie = $cookieJar->get('bar', '/', '.blog.symfony.com');
$phpCookie = $cookieJar->get('PHPSESSID');
$this->assertInstanceOf('Symfony\Component\BrowserKit\Cookie', $fooCookie);
$this->assertInstanceOf('Symfony\Component\BrowserKit\Cookie', $barCookie);
$this->assertInstanceOf('Symfony\Component\BrowserKit\Cookie', $phpCookie);
$this->assertEquals('foo', $fooCookie->getValue());
$this->assertEquals('bar', $barCookie->getValue());
$this->assertEquals('id', $phpCookie->getValue());
$this->assertEquals($timestamp, $fooCookie->getExpiresTime());
$this->assertNull($barCookie->getExpiresTime());
$this->assertEquals($timestamp, $phpCookie->getExpiresTime());
}
/**
* @dataProvider provideAllValuesValues
*/
public function testAllValues($uri, $values)
{
$cookieJar = new CookieJar();
$cookieJar->set($cookie1 = new Cookie('foo_nothing', 'foo'));
$cookieJar->set($cookie2 = new Cookie('foo_expired', 'foo', time() - 86400));
$cookieJar->set($cookie3 = new Cookie('foo_path', 'foo', null, '/foo'));
$cookieJar->set($cookie4 = new Cookie('foo_domain', 'foo', null, '/', '.example.com'));
$cookieJar->set($cookie4 = new Cookie('foo_strict_domain', 'foo', null, '/', '.www4.example.com'));
$cookieJar->set($cookie5 = new Cookie('foo_secure', 'foo', null, '/', '', true));
$this->assertEquals($values, array_keys($cookieJar->allValues($uri)), '->allValues() returns the cookie for a given URI');
}
public function provideAllValuesValues()
{
return array(
array('http://www.example.com', array('foo_nothing', 'foo_domain')),
array('http://www.example.com/', array('foo_nothing', 'foo_domain')),
array('http://foo.example.com/', array('foo_nothing', 'foo_domain')),
array('http://foo.example1.com/', array('foo_nothing')),
array('https://foo.example.com/', array('foo_nothing', 'foo_secure', 'foo_domain')),
array('http://www.example.com/foo/bar', array('foo_nothing', 'foo_path', 'foo_domain')),
array('http://www4.example.com/', array('foo_nothing', 'foo_domain', 'foo_strict_domain')),
);
}
public function testEncodedValues()
{
$cookieJar = new CookieJar();
$cookieJar->set($cookie = new Cookie('foo', 'bar%3Dbaz', null, '/', '', false, true, true));
$this->assertEquals(array('foo' => 'bar=baz'), $cookieJar->allValues('/'));
$this->assertEquals(array('foo' => 'bar%3Dbaz'), $cookieJar->allRawValues('/'));
}
public function testCookieExpireWithSameNameButDifferentPaths()
{
$cookieJar = new CookieJar();
$cookieJar->set($cookie1 = new Cookie('foo', 'bar1', null, '/foo'));
$cookieJar->set($cookie2 = new Cookie('foo', 'bar2', null, '/bar'));
$cookieJar->expire('foo', '/foo');
$this->assertNull($cookieJar->get('foo'), '->get() returns null if the cookie is expired');
$this->assertEquals(array(), array_keys($cookieJar->allValues('http://example.com/')));
$this->assertEquals(array(), $cookieJar->allValues('http://example.com/foo'));
$this->assertEquals(array('foo' => 'bar2'), $cookieJar->allValues('http://example.com/bar'));
}
public function testCookieExpireWithNullPaths()
{
$cookieJar = new CookieJar();
$cookieJar->set($cookie1 = new Cookie('foo', 'bar1', null, '/'));
$cookieJar->expire('foo', null);
$this->assertNull($cookieJar->get('foo'), '->get() returns null if the cookie is expired');
$this->assertEquals(array(), array_keys($cookieJar->allValues('http://example.com/')));
}
public function testCookieExpireWithDomain()
{
$cookieJar = new CookieJar();
$cookieJar->set($cookie1 = new Cookie('foo', 'bar1', null, '/foo', 'http://example2.com/'));
$cookieJar->expire('foo', '/foo', 'http://example2.com/');
$this->assertNull($cookieJar->get('foo'), '->get() returns null if the cookie is expired');
$this->assertEquals(array(), array_keys($cookieJar->allValues('http://example2.com/')));
}
public function testCookieWithSameNameButDifferentPaths()
{
$cookieJar = new CookieJar();
$cookieJar->set($cookie1 = new Cookie('foo', 'bar1', null, '/foo'));
$cookieJar->set($cookie2 = new Cookie('foo', 'bar2', null, '/bar'));
$this->assertEquals(array(), array_keys($cookieJar->allValues('http://example.com/')));
$this->assertEquals(array('foo' => 'bar1'), $cookieJar->allValues('http://example.com/foo'));
$this->assertEquals(array('foo' => 'bar2'), $cookieJar->allValues('http://example.com/bar'));
}
public function testCookieWithSameNameButDifferentDomains()
{
$cookieJar = new CookieJar();
$cookieJar->set($cookie1 = new Cookie('foo', 'bar1', null, '/', 'foo.example.com'));
$cookieJar->set($cookie2 = new Cookie('foo', 'bar2', null, '/', 'bar.example.com'));
$this->assertEquals(array(), array_keys($cookieJar->allValues('http://example.com/')));
$this->assertEquals(array('foo' => 'bar1'), $cookieJar->allValues('http://foo.example.com/'));
$this->assertEquals(array('foo' => 'bar2'), $cookieJar->allValues('http://bar.example.com/'));
}
public function testCookieGetWithSubdomain()
{
$cookieJar = new CookieJar();
$cookieJar->set($cookie1 = new Cookie('foo', 'bar', null, '/', '.example.com'));
$cookieJar->set($cookie2 = new Cookie('foo1', 'bar', null, '/', 'test.example.com'));
$this->assertEquals($cookie1, $cookieJar->get('foo', '/', 'foo.example.com'));
$this->assertEquals($cookie1, $cookieJar->get('foo', '/', 'example.com'));
$this->assertEquals($cookie2, $cookieJar->get('foo1', '/', 'test.example.com'));
}
public function testCookieGetWithWrongSubdomain()
{
$cookieJar = new CookieJar();
$cookieJar->set($cookie1 = new Cookie('foo1', 'bar', null, '/', 'test.example.com'));
$this->assertNull($cookieJar->get('foo1', '/', 'foo.example.com'));
}
public function testCookieGetWithSubdirectory()
{
$cookieJar = new CookieJar();
$cookieJar->set($cookie1 = new Cookie('foo', 'bar', null, '/test', '.example.com'));
$cookieJar->set($cookie2 = new Cookie('foo1', 'bar1', null, '/', '.example.com'));
$this->assertNull($cookieJar->get('foo', '/', '.example.com'));
$this->assertNull($cookieJar->get('foo', '/bar', '.example.com'));
$this->assertEquals($cookie1, $cookieJar->get('foo', '/test', 'example.com'));
$this->assertEquals($cookie2, $cookieJar->get('foo1', '/', 'example.com'));
$this->assertEquals($cookie2, $cookieJar->get('foo1', '/bar', 'example.com'));
$this->assertEquals($cookie2, $cookieJar->get('foo1', '/bar'));
}
public function testCookieWithWildcardDomain()
{
$cookieJar = new CookieJar();
$cookieJar->set(new Cookie('foo', 'bar', null, '/', '.example.com'));
$this->assertEquals(array('foo' => 'bar'), $cookieJar->allValues('http://www.example.com'));
$this->assertEmpty($cookieJar->allValues('http://wwwexample.com'));
}
}

View File

@@ -0,0 +1,205 @@
<?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\BrowserKit\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\BrowserKit\Cookie;
class CookieTest extends TestCase
{
public function testToString()
{
$cookie = new Cookie('foo', 'bar', strtotime('Fri, 20-May-2011 15:25:52 GMT'), '/', '.myfoodomain.com', true);
$this->assertEquals('foo=bar; expires=Fri, 20 May 2011 15:25:52 GMT; domain=.myfoodomain.com; path=/; secure; httponly', (string) $cookie, '->__toString() returns string representation of the cookie');
$cookie = new Cookie('foo', 'bar with white spaces', strtotime('Fri, 20-May-2011 15:25:52 GMT'), '/', '.myfoodomain.com', true);
$this->assertEquals('foo=bar%20with%20white%20spaces; expires=Fri, 20 May 2011 15:25:52 GMT; domain=.myfoodomain.com; path=/; secure; httponly', (string) $cookie, '->__toString() encodes the value of the cookie according to RFC 3986 (white space = %20)');
$cookie = new Cookie('foo', null, 1, '/admin/', '.myfoodomain.com');
$this->assertEquals('foo=; expires=Thu, 01 Jan 1970 00:00:01 GMT; domain=.myfoodomain.com; path=/admin/; httponly', (string) $cookie, '->__toString() returns string representation of a cleared cookie if value is NULL');
$cookie = new Cookie('foo', 'bar', 0, '/', '');
$this->assertEquals('foo=bar; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; httponly', (string) $cookie);
}
/**
* @dataProvider getTestsForToFromString
*/
public function testToFromString($cookie, $url = null)
{
$this->assertEquals($cookie, (string) Cookie::fromString($cookie, $url));
}
public function getTestsForToFromString()
{
return array(
array('foo=bar; path=/'),
array('foo=bar; path=/foo'),
array('foo=bar; domain=google.com; path=/'),
array('foo=bar; domain=example.com; path=/; secure', 'https://example.com/'),
array('foo=bar; path=/; httponly'),
array('foo=bar; domain=google.com; path=/foo; secure; httponly', 'https://google.com/'),
array('foo=bar=baz; path=/'),
array('foo=bar%3Dbaz; path=/'),
);
}
public function testFromStringIgnoreSecureFlag()
{
$this->assertFalse(Cookie::fromString('foo=bar; secure')->isSecure());
$this->assertFalse(Cookie::fromString('foo=bar; secure', 'http://example.com/')->isSecure());
}
/**
* @dataProvider getExpireCookieStrings
*/
public function testFromStringAcceptsSeveralExpiresDateFormats($cookie)
{
$this->assertEquals(1596185377, Cookie::fromString($cookie)->getExpiresTime());
}
public function getExpireCookieStrings()
{
return array(
array('foo=bar; expires=Fri, 31-Jul-2020 08:49:37 GMT'),
array('foo=bar; expires=Fri, 31 Jul 2020 08:49:37 GMT'),
array('foo=bar; expires=Fri, 31-07-2020 08:49:37 GMT'),
array('foo=bar; expires=Fri, 31-07-20 08:49:37 GMT'),
array('foo=bar; expires=Friday, 31-Jul-20 08:49:37 GMT'),
array('foo=bar; expires=Fri Jul 31 08:49:37 2020'),
array('foo=bar; expires=\'Fri Jul 31 08:49:37 2020\''),
array('foo=bar; expires=Friday July 31st 2020, 08:49:37 GMT'),
);
}
public function testFromStringWithCapitalization()
{
$this->assertEquals('Foo=Bar; path=/', (string) Cookie::fromString('Foo=Bar'));
$this->assertEquals('foo=bar; expires=Fri, 31 Dec 2010 23:59:59 GMT; path=/', (string) Cookie::fromString('foo=bar; Expires=Fri, 31 Dec 2010 23:59:59 GMT'));
$this->assertEquals('foo=bar; domain=www.example.org; path=/; httponly', (string) Cookie::fromString('foo=bar; DOMAIN=www.example.org; HttpOnly'));
}
public function testFromStringWithUrl()
{
$this->assertEquals('foo=bar; domain=www.example.com; path=/', (string) Cookie::fromString('foo=bar', 'http://www.example.com/'));
$this->assertEquals('foo=bar; domain=www.example.com; path=/', (string) Cookie::fromString('foo=bar', 'http://www.example.com'));
$this->assertEquals('foo=bar; domain=www.example.com; path=/', (string) Cookie::fromString('foo=bar', 'http://www.example.com?foo'));
$this->assertEquals('foo=bar; domain=www.example.com; path=/foo', (string) Cookie::fromString('foo=bar', 'http://www.example.com/foo/bar'));
$this->assertEquals('foo=bar; domain=www.example.com; path=/', (string) Cookie::fromString('foo=bar; path=/', 'http://www.example.com/foo/bar'));
$this->assertEquals('foo=bar; domain=www.myotherexample.com; path=/', (string) Cookie::fromString('foo=bar; domain=www.myotherexample.com', 'http://www.example.com/'));
}
public function testFromStringThrowsAnExceptionIfCookieIsNotValid()
{
$this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('InvalidArgumentException');
Cookie::fromString('foo');
}
public function testFromStringIgnoresInvalidExpiresDate()
{
$cookie = Cookie::fromString('foo=bar; expires=Flursday July 31st 2020, 08:49:37 GMT');
$this->assertFalse($cookie->isExpired());
}
public function testFromStringThrowsAnExceptionIfUrlIsNotValid()
{
$this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('InvalidArgumentException');
Cookie::fromString('foo=bar', 'foobar');
}
public function testGetName()
{
$cookie = new Cookie('foo', 'bar');
$this->assertEquals('foo', $cookie->getName(), '->getName() returns the cookie name');
}
public function testGetValue()
{
$cookie = new Cookie('foo', 'bar');
$this->assertEquals('bar', $cookie->getValue(), '->getValue() returns the cookie value');
$cookie = new Cookie('foo', 'bar%3Dbaz', null, '/', '', false, true, true); // raw value
$this->assertEquals('bar=baz', $cookie->getValue(), '->getValue() returns the urldecoded cookie value');
}
public function testGetRawValue()
{
$cookie = new Cookie('foo', 'bar=baz'); // decoded value
$this->assertEquals('bar%3Dbaz', $cookie->getRawValue(), '->getRawValue() returns the urlencoded cookie value');
$cookie = new Cookie('foo', 'bar%3Dbaz', null, '/', '', false, true, true); // raw value
$this->assertEquals('bar%3Dbaz', $cookie->getRawValue(), '->getRawValue() returns the non-urldecoded cookie value');
}
public function testGetPath()
{
$cookie = new Cookie('foo', 'bar', 0);
$this->assertEquals('/', $cookie->getPath(), '->getPath() returns / is no path is defined');
$cookie = new Cookie('foo', 'bar', 0, '/foo');
$this->assertEquals('/foo', $cookie->getPath(), '->getPath() returns the cookie path');
}
public function testGetDomain()
{
$cookie = new Cookie('foo', 'bar', 0, '/', 'foo.com');
$this->assertEquals('foo.com', $cookie->getDomain(), '->getDomain() returns the cookie domain');
}
public function testIsSecure()
{
$cookie = new Cookie('foo', 'bar');
$this->assertFalse($cookie->isSecure(), '->isSecure() returns false if not defined');
$cookie = new Cookie('foo', 'bar', 0, '/', 'foo.com', true);
$this->assertTrue($cookie->isSecure(), '->isSecure() returns the cookie secure flag');
}
public function testIsHttponly()
{
$cookie = new Cookie('foo', 'bar');
$this->assertTrue($cookie->isHttpOnly(), '->isHttpOnly() returns false if not defined');
$cookie = new Cookie('foo', 'bar', 0, '/', 'foo.com', false, true);
$this->assertTrue($cookie->isHttpOnly(), '->isHttpOnly() returns the cookie httponly flag');
}
public function testGetExpiresTime()
{
$cookie = new Cookie('foo', 'bar');
$this->assertNull($cookie->getExpiresTime(), '->getExpiresTime() returns the expires time');
$cookie = new Cookie('foo', 'bar', $time = time() - 86400);
$this->assertEquals($time, $cookie->getExpiresTime(), '->getExpiresTime() returns the expires time');
}
public function testIsExpired()
{
$cookie = new Cookie('foo', 'bar');
$this->assertFalse($cookie->isExpired(), '->isExpired() returns false when the cookie never expires (null as expires time)');
$cookie = new Cookie('foo', 'bar', time() - 86400);
$this->assertTrue($cookie->isExpired(), '->isExpired() returns true when the cookie is expired');
$cookie = new Cookie('foo', 'bar', 0);
$this->assertFalse($cookie->isExpired());
}
/**
* @expectedException \UnexpectedValueException
* @expectedExceptionMessage The cookie expiration time "string" is not valid.
*/
public function testConstructException()
{
$cookie = new Cookie('foo', 'bar', 'string');
}
}

View File

@@ -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\BrowserKit\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\BrowserKit\History;
use Symfony\Component\BrowserKit\Request;
class HistoryTest extends TestCase
{
public function testAdd()
{
$history = new History();
$history->add(new Request('http://www.example1.com/', 'get'));
$this->assertSame('http://www.example1.com/', $history->current()->getUri(), '->add() adds a request to the history');
$history->add(new Request('http://www.example2.com/', 'get'));
$this->assertSame('http://www.example2.com/', $history->current()->getUri(), '->add() adds a request to the history');
$history->add(new Request('http://www.example3.com/', 'get'));
$history->back();
$history->add(new Request('http://www.example4.com/', 'get'));
$this->assertSame('http://www.example4.com/', $history->current()->getUri(), '->add() adds a request to the history');
$history->back();
$this->assertSame('http://www.example2.com/', $history->current()->getUri(), '->add() adds a request to the history');
}
public function testClearIsEmpty()
{
$history = new History();
$history->add(new Request('http://www.example.com/', 'get'));
$this->assertFalse($history->isEmpty(), '->isEmpty() returns false if the history is not empty');
$history->clear();
$this->assertTrue($history->isEmpty(), '->isEmpty() true if the history is empty');
}
public function testCurrent()
{
$history = new History();
try {
$history->current();
$this->fail('->current() throws a \LogicException if the history is empty');
} catch (\Exception $e) {
$this->assertInstanceOf('LogicException', $e, '->current() throws a \LogicException if the history is empty');
}
$history->add(new Request('http://www.example.com/', 'get'));
$this->assertSame('http://www.example.com/', $history->current()->getUri(), '->current() returns the current request in the history');
}
public function testBack()
{
$history = new History();
$history->add(new Request('http://www.example.com/', 'get'));
try {
$history->back();
$this->fail('->back() throws a \LogicException if the history is already on the first page');
} catch (\Exception $e) {
$this->assertInstanceOf('LogicException', $e, '->current() throws a \LogicException if the history is already on the first page');
}
$history->add(new Request('http://www.example1.com/', 'get'));
$history->back();
$this->assertSame('http://www.example.com/', $history->current()->getUri(), '->back() returns the previous request in the history');
}
public function testForward()
{
$history = new History();
$history->add(new Request('http://www.example.com/', 'get'));
$history->add(new Request('http://www.example1.com/', 'get'));
try {
$history->forward();
$this->fail('->forward() throws a \LogicException if the history is already on the last page');
} catch (\Exception $e) {
$this->assertInstanceOf('LogicException', $e, '->forward() throws a \LogicException if the history is already on the last page');
}
$history->back();
$history->forward();
$this->assertSame('http://www.example1.com/', $history->current()->getUri(), '->forward() returns the next request in the history');
}
}

View File

@@ -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\BrowserKit\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\BrowserKit\Request;
class RequestTest extends TestCase
{
public function testGetUri()
{
$request = new Request('http://www.example.com/', 'get');
$this->assertEquals('http://www.example.com/', $request->getUri(), '->getUri() returns the URI of the request');
}
public function testGetMethod()
{
$request = new Request('http://www.example.com/', 'get');
$this->assertEquals('get', $request->getMethod(), '->getMethod() returns the method of the request');
}
public function testGetParameters()
{
$request = new Request('http://www.example.com/', 'get', array('foo' => 'bar'));
$this->assertEquals(array('foo' => 'bar'), $request->getParameters(), '->getParameters() returns the parameters of the request');
}
public function testGetFiles()
{
$request = new Request('http://www.example.com/', 'get', array(), array('foo' => 'bar'));
$this->assertEquals(array('foo' => 'bar'), $request->getFiles(), '->getFiles() returns the uploaded files of the request');
}
public function testGetCookies()
{
$request = new Request('http://www.example.com/', 'get', array(), array(), array('foo' => 'bar'));
$this->assertEquals(array('foo' => 'bar'), $request->getCookies(), '->getCookies() returns the cookies of the request');
}
public function testGetServer()
{
$request = new Request('http://www.example.com/', 'get', array(), array(), array(), array('foo' => 'bar'));
$this->assertEquals(array('foo' => 'bar'), $request->getServer(), '->getServer() returns the server parameters of the request');
}
}

View File

@@ -0,0 +1,77 @@
<?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\BrowserKit\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\BrowserKit\Response;
class ResponseTest extends TestCase
{
public function testGetUri()
{
$response = new Response('foo');
$this->assertEquals('foo', $response->getContent(), '->getContent() returns the content of the response');
}
public function testGetStatus()
{
$response = new Response('foo', 304);
$this->assertEquals('304', $response->getStatus(), '->getStatus() returns the status of the response');
}
public function testGetHeaders()
{
$response = new Response('foo', 200, array('foo' => 'bar'));
$this->assertEquals(array('foo' => 'bar'), $response->getHeaders(), '->getHeaders() returns the headers of the response');
}
public function testGetHeader()
{
$response = new Response('foo', 200, array(
'Content-Type' => 'text/html',
'Set-Cookie' => array('foo=bar', 'bar=foo'),
));
$this->assertEquals('text/html', $response->getHeader('Content-Type'), '->getHeader() returns a header of the response');
$this->assertEquals('text/html', $response->getHeader('content-type'), '->getHeader() returns a header of the response');
$this->assertEquals('text/html', $response->getHeader('content_type'), '->getHeader() returns a header of the response');
$this->assertEquals('foo=bar', $response->getHeader('Set-Cookie'), '->getHeader() returns the first header value');
$this->assertEquals(array('foo=bar', 'bar=foo'), $response->getHeader('Set-Cookie', false), '->getHeader() returns all header values if first is false');
$this->assertNull($response->getHeader('foo'), '->getHeader() returns null if the header is not defined');
$this->assertEquals(array(), $response->getHeader('foo', false), '->getHeader() returns an empty array if the header is not defined and first is set to false');
}
public function testMagicToString()
{
$response = new Response('foo', 304, array('foo' => 'bar'));
$this->assertEquals("foo: bar\n\nfoo", $response->__toString(), '->__toString() returns the headers and the content as a string');
}
public function testMagicToStringWithMultipleSetCookieHeader()
{
$headers = array(
'content-type' => 'text/html; charset=utf-8',
'set-cookie' => array('foo=bar', 'bar=foo'),
);
$expected = 'content-type: text/html; charset=utf-8'."\n";
$expected .= 'set-cookie: foo=bar'."\n";
$expected .= 'set-cookie: bar=foo'."\n\n";
$expected .= 'foo';
$response = new Response('foo', 304, $headers);
$this->assertEquals($expected, $response->__toString(), '->__toString() returns the headers and the content as a string');
}
}

View File

@@ -0,0 +1,41 @@
{
"name": "symfony/browser-kit",
"type": "library",
"description": "Symfony BrowserKit Component",
"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": "^5.5.9|>=7.0.8",
"symfony/dom-crawler": "~2.8|~3.0|~4.0"
},
"require-dev": {
"symfony/process": "~2.8|~3.0|~4.0",
"symfony/css-selector": "~2.8|~3.0|~4.0"
},
"suggest": {
"symfony/process": ""
},
"autoload": {
"psr-4": { "Symfony\\Component\\BrowserKit\\": "" },
"exclude-from-classmap": [
"/Tests/"
]
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "3.4-dev"
}
}
}

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="vendor/autoload.php"
failOnRisky="true"
failOnWarning="true"
>
<php>
<ini name="error_reporting" value="-1" />
</php>
<testsuites>
<testsuite name="Symfony BrowserKit Component Test Suite">
<directory>./Tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./</directory>
<exclude>
<directory>./Resources</directory>
<directory>./Tests</directory>
<directory>./vendor</directory>
</exclude>
</whitelist>
</filter>
</phpunit>

3
vendor/symfony/console/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
vendor/
composer.lock
phpunit.xml

1223
vendor/symfony/console/Application.php vendored Normal file

File diff suppressed because it is too large Load Diff

112
vendor/symfony/console/CHANGELOG.md vendored Normal file
View File

@@ -0,0 +1,112 @@
CHANGELOG
=========
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)

View File

@@ -0,0 +1,656 @@
<?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 = array();
private $definition;
private $hidden = false;
private $help;
private $description;
private $ignoreValidationErrors = false;
private $applicationDefinitionMerged = false;
private $applicationDefinitionMergedWithArgs = false;
private $code;
private $synopsis = array();
private $usages = array();
private $helperSet;
/**
* @return string|null The default command name or null when no default name is set
*/
public static function getDefaultName()
{
$class = \get_called_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($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 A HelperSet instance
*/
public function getHelperSet()
{
return $this->helperSet;
}
/**
* Gets the application instance for this command.
*
* @return Application 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 null|int null or 0 if everything went fine, or an error 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 just after the input has been 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.
*/
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 \Exception When binding input 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 = \call_user_func($this->code, $input, $output);
} else {
$statusCode = $this->execute($input, $output);
}
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()) {
if (\PHP_VERSION_ID < 70000) {
// Bug in PHP5: https://bugs.php.net/bug.php?id=64761
// This means that we cannot bind static closures and therefore we must
// ignore any errors here. There is no way to test if the closure is
// bindable.
$code = @\Closure::bind($code, $this);
} else {
$code = \Closure::bind($code, $this);
}
}
}
$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());
if ($mergeArgs) {
$currentArguments = $this->definition->getArguments();
$this->definition->setArguments($this->application->getDefinition()->getArguments());
$this->definition->addArguments($currentArguments);
}
$this->applicationDefinitionMerged = true;
if ($mergeArgs) {
$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()
{
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 $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)
*
* @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 $shortcut The shortcut (can be null)
* @param int $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)
*
* @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.
*
* PHP 5.5+ or the proctitle PECL library is required
*
* @param string $title The process title
*
* @return $this
*/
public function setProcessTitle($title)
{
$this->processTitle = $title;
return $this;
}
/**
* Returns the command name.
*
* @return string The command name
*/
public function getName()
{
return $this->name;
}
/**
* @param bool $hidden Whether or not the command should be hidden from the list of commands
*
* @return Command The current instance
*/
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;
$placeholders = array(
'%command.name%',
'%command.full_name%',
);
$replacements = array(
$name,
$_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 (0 !== strpos($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 ":".
*
* @param string $name
*
* @throws InvalidArgumentException When the name is invalid
*/
private function validateName($name)
{
if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) {
throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name));
}
}
}

View File

@@ -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\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(array(
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('Displays 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, array(
'format' => $input->getOption('format'),
'raw_text' => $input->getOption('raw'),
));
$this->command = null;
}
}

View File

@@ -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\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('Lists 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(), array(
'format' => $input->getOption('format'),
'raw_text' => $input->getOption('raw'),
'namespace' => $input->getArgument('namespace'),
));
}
/**
* {@inheritdoc}
*/
private function createDefinition()
{
return new InputDefinition(array(
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'),
));
}
}

View File

@@ -0,0 +1,72 @@
<?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\Console\Exception\RuntimeException;
use Symfony\Component\Lock\Factory;
use Symfony\Component\Lock\Lock;
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.
*
* @return bool
*/
private function lock($name = null, $blocking = false)
{
if (!class_exists(SemaphoreStore::class)) {
throw new RuntimeException('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($blocking)) {
$store = new SemaphoreStore();
} else {
$store = new FlockStore();
}
$this->lock = (new Factory($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;
}
}
}

View File

@@ -0,0 +1,37 @@
<?php
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();
}

View File

@@ -0,0 +1,55 @@
<?php
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 ContainerInterface $container A container from which to load command services
* @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);
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,60 @@
<?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 handled to the command.
*
* @Event("Symfony\Component\Console\Event\ConsoleCommandEvent")
*/
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")
*/
const TERMINATE = 'console.terminate';
/**
* The EXCEPTION event occurs when an uncaught exception appears
* while executing Command#run().
*
* This event allows you to deal with the exception or
* to modify the thrown exception.
*
* @Event("Symfony\Component\Console\Event\ConsoleExceptionEvent")
*
* @deprecated The console.exception event is deprecated since version 3.3 and will be removed in 4.0. Use the console.error event instead.
*/
const EXCEPTION = 'console.exception';
/**
* 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")
*/
const ERROR = 'console.error';
}

View File

@@ -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\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($commandLoaderServiceId = 'console.command_loader', $commandTag = 'console.command')
{
$this->commandLoaderServiceId = $commandLoaderServiceId;
$this->commandTag = $commandTag;
}
public function process(ContainerBuilder $container)
{
$commandServices = $container->findTaggedServiceIds($this->commandTag, true);
$lazyCommandMap = array();
$lazyCommandRefs = array();
$serviceIds = array();
$lazyServiceIds = array();
foreach ($commandServices as $id => $tags) {
$definition = $container->getDefinition($id);
$class = $container->getParameterBag()->resolveValue($definition->getClass());
$commandId = 'console.command.'.strtolower(str_replace('\\', '_', $class));
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 = $class::getDefaultName();
}
if (null === $commandName) {
if (isset($serviceIds[$commandId]) || $container->hasAlias($commandId)) {
$commandId = $commandId.'_'.$id;
}
if (!$definition->isPublic() || $definition->isPrivate()) {
$container->setAlias($commandId, $id)->setPublic(true);
$id = $commandId;
}
$serviceIds[$commandId] = $id;
continue;
}
$serviceIds[$commandId] = $id;
$lazyServiceIds[$id] = true;
unset($tags[0]);
$lazyCommandMap[$commandName] = $id;
$lazyCommandRefs[$id] = new TypedReference($id, $class);
$aliases = array();
foreach ($tags as $tag) {
if (isset($tag['command'])) {
$aliases[] = $tag['command'];
$lazyCommandMap[$tag['command']] = $id;
}
}
$definition->addMethodCall('setName', array($commandName));
if ($aliases) {
$definition->addMethodCall('setAliases', array($aliases));
}
}
$container
->register($this->commandLoaderServiceId, ContainerCommandLoader::class)
->setPublic(true)
->setArguments(array(ServiceLocatorTagPass::register($container, $lazyCommandRefs), $lazyCommandMap));
$container->setParameter('console.command.ids', $serviceIds);
$container->setParameter('console.lazy_command.ids', $lazyServiceIds);
}
}

View File

@@ -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\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
{
const GLOBAL_NAMESPACE = '_global';
private $application;
private $namespace;
private $showHidden;
/**
* @var array
*/
private $namespaces;
/**
* @var Command[]
*/
private $commands;
/**
* @var Command[]
*/
private $aliases;
/**
* @param Application $application
* @param string|null $namespace
* @param bool $showHidden
*/
public function __construct(Application $application, $namespace = null, $showHidden = false)
{
$this->application = $application;
$this->namespace = $namespace;
$this->showHidden = $showHidden;
}
/**
* @return array
*/
public function getNamespaces()
{
if (null === $this->namespaces) {
$this->inspectApplication();
}
return $this->namespaces;
}
/**
* @return Command[]
*/
public function getCommands()
{
if (null === $this->commands) {
$this->inspectApplication();
}
return $this->commands;
}
/**
* @param string $name
*
* @return Command
*
* @throws CommandNotFoundException
*/
public function getCommand($name)
{
if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) {
throw new CommandNotFoundException(sprintf('Command %s does not exist.', $name));
}
return isset($this->commands[$name]) ? $this->commands[$name] : $this->aliases[$name];
}
private function inspectApplication()
{
$this->commands = array();
$this->namespaces = array();
$all = $this->application->all($this->namespace ? $this->application->findNamespace($this->namespace) : null);
foreach ($this->sortCommands($all) as $namespace => $commands) {
$names = array();
/** @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] = array('id' => $namespace, 'commands' => $names);
}
}
/**
* @return array
*/
private function sortCommands(array $commands)
{
$namespacedCommands = array();
$globalCommands = array();
foreach ($commands as $name => $command) {
$key = $this->application->extractNamespace($name, 1);
if (!$key) {
$globalCommands['_global'][$name] = $command;
} else {
$namespacedCommands[$key][$name] = $command;
}
}
ksort($namespacedCommands);
$namespacedCommands = array_merge($globalCommands, $namespacedCommands);
foreach ($namespacedCommands as &$commandsSet) {
ksort($commandsSet);
}
// unset reference to keep scope clear
unset($commandsSet);
return $namespacedCommands;
}
}

View File

@@ -0,0 +1,107 @@
<?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 = array())
{
$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.
*
* @return string|mixed
*/
abstract protected function describeInputArgument(InputArgument $argument, array $options = array());
/**
* Describes an InputOption instance.
*
* @return string|mixed
*/
abstract protected function describeInputOption(InputOption $option, array $options = array());
/**
* Describes an InputDefinition instance.
*
* @return string|mixed
*/
abstract protected function describeInputDefinition(InputDefinition $definition, array $options = array());
/**
* Describes a Command instance.
*
* @return string|mixed
*/
abstract protected function describeCommand(Command $command, array $options = array());
/**
* Describes an Application instance.
*
* @return string|mixed
*/
abstract protected function describeApplication(Application $application, array $options = array());
}

View File

@@ -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.
*/
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 OutputInterface $output
* @param object $object
* @param array $options
*/
public function describe(OutputInterface $output, $object, array $options = array());
}

View File

@@ -0,0 +1,168 @@
<?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 = array())
{
$this->writeData($this->getInputArgumentData($argument), $options);
}
/**
* {@inheritdoc}
*/
protected function describeInputOption(InputOption $option, array $options = array())
{
$this->writeData($this->getInputOptionData($option), $options);
}
/**
* {@inheritdoc}
*/
protected function describeInputDefinition(InputDefinition $definition, array $options = array())
{
$this->writeData($this->getInputDefinitionData($definition), $options);
}
/**
* {@inheritdoc}
*/
protected function describeCommand(Command $command, array $options = array())
{
$this->writeData($this->getCommandData($command), $options);
}
/**
* {@inheritdoc}
*/
protected function describeApplication(Application $application, array $options = array())
{
$describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
$description = new ApplicationDescription($application, $describedNamespace, true);
$commands = array();
foreach ($description->getCommands() as $command) {
$commands[] = $this->getCommandData($command);
}
$data = array();
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.
*
* @return array|string
*/
private function writeData(array $data, array $options)
{
$this->write(json_encode($data, isset($options['json_encoding']) ? $options['json_encoding'] : 0));
}
/**
* @return array
*/
private function getInputArgumentData(InputArgument $argument)
{
return array(
'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(),
);
}
/**
* @return array
*/
private function getInputOptionData(InputOption $option)
{
return array(
'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(),
);
}
/**
* @return array
*/
private function getInputDefinitionData(InputDefinition $definition)
{
$inputArguments = array();
foreach ($definition->getArguments() as $name => $argument) {
$inputArguments[$name] = $this->getInputArgumentData($argument);
}
$inputOptions = array();
foreach ($definition->getOptions() as $name => $option) {
$inputOptions[$name] = $this->getInputOptionData($option);
}
return array('arguments' => $inputArguments, 'options' => $inputOptions);
}
/**
* @return array
*/
private function getCommandData(Command $command)
{
$command->getSynopsis();
$command->mergeApplicationDefinition(false);
return array(
'name' => $command->getName(),
'usage' => array_merge(array($command->getSynopsis()), $command->getUsages(), $command->getAliases()),
'description' => $command->getDescription(),
'help' => $command->getProcessedHelp(),
'definition' => $this->getInputDefinitionData($command->getNativeDefinition()),
'hidden' => $command->isHidden(),
);
}
}

View File

@@ -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 = array())
{
$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 = array())
{
$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 = array())
{
$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 = array())
{
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 = array())
{
$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(array($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 = array())
{
$describedNamespace = isset($options['namespace']) ? $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)
{
if ('UNKNOWN' !== $application->getName()) {
if ('UNKNOWN' !== $application->getVersion()) {
return sprintf('%s %s', $application->getName(), $application->getVersion());
}
return $application->getName();
}
return 'Console Tool';
}
}

View File

@@ -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 = array())
{
if (null !== $argument->getDefault() && (!\is_array($argument->getDefault()) || \count($argument->getDefault()))) {
$default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($argument->getDefault()));
} else {
$default = '';
}
$totalWidth = isset($options['total_width']) ? $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 = array())
{
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 = isset($options['total_width']) ? $options['total_width'] : $this->calculateTotalWidthForOptions(array($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 = array())
{
$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, array('total_width' => $totalWidth)));
$this->writeText("\n");
}
}
if ($definition->getArguments() && $definition->getOptions()) {
$this->writeText("\n");
}
if ($definition->getOptions()) {
$laterOptions = array();
$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, array('total_width' => $totalWidth)));
}
foreach ($laterOptions as $option) {
$this->writeText("\n");
$this->describeInputOption($option, array_merge($options, array('total_width' => $totalWidth)));
}
}
}
/**
* {@inheritdoc}
*/
protected function describeCommand(Command $command, array $options = array())
{
$command->getSynopsis(true);
$command->getSynopsis(false);
$command->mergeApplicationDefinition(false);
$this->writeText('<comment>Usage:</comment>', $options);
foreach (array_merge(array($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");
}
if ($help = $command->getProcessedHelp()) {
$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 = array())
{
$describedNamespace = isset($options['namespace']) ? $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(\call_user_func_array('array_merge', array_map(function ($namespace) use ($commands) {
return array_intersect($namespace['commands'], array_keys($commands));
}, $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($content, array $options = array())
{
$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.
*
* @return string
*/
private function getCommandAliasesText(Command $command)
{
$text = '';
$aliases = $command->getAliases();
if ($aliases) {
$text = '['.implode('|', $aliases).'] ';
}
return $text;
}
/**
* Formats input option/argument default value.
*
* @param mixed $default
*
* @return string
*/
private function formatDefaultValue($default)
{
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 (Command|string)[] $commands
*
* @return int
*/
private function getColumnWidth(array $commands)
{
$widths = array();
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
*
* @return int
*/
private function calculateTotalWidthForOptions(array $options)
{
$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;
}
}

View File

@@ -0,0 +1,251 @@
<?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
{
/**
* @return \DOMDocument
*/
public function getInputDefinitionDocument(InputDefinition $definition)
{
$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;
}
/**
* @return \DOMDocument
*/
public function getCommandDocument(Command $command)
{
$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(array($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;
}
/**
* @param Application $application
* @param string|null $namespace
*
* @return \DOMDocument
*/
public function getApplicationDocument(Application $application, $namespace = null)
{
$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 = array())
{
$this->writeDocument($this->getInputArgumentDocument($argument));
}
/**
* {@inheritdoc}
*/
protected function describeInputOption(InputOption $option, array $options = array())
{
$this->writeDocument($this->getInputOptionDocument($option));
}
/**
* {@inheritdoc}
*/
protected function describeInputDefinition(InputDefinition $definition, array $options = array())
{
$this->writeDocument($this->getInputDefinitionDocument($definition));
}
/**
* {@inheritdoc}
*/
protected function describeCommand(Command $command, array $options = array())
{
$this->writeDocument($this->getCommandDocument($command));
}
/**
* {@inheritdoc}
*/
protected function describeApplication(Application $application, array $options = array())
{
$this->writeDocument($this->getApplicationDocument($application, isset($options['namespace']) ? $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.
*
* @return \DOMDocument|string
*/
private function writeDocument(\DOMDocument $dom)
{
$dom->formatOutput = true;
$this->write($dom->saveXML());
}
/**
* @return \DOMDocument
*/
private function getInputArgumentDocument(InputArgument $argument)
{
$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()) ? array(var_export($argument->getDefault(), true)) : ($argument->getDefault() ? array($argument->getDefault()) : array()));
foreach ($defaults as $default) {
$defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
$defaultXML->appendChild($dom->createTextNode($default));
}
return $dom;
}
/**
* @return \DOMDocument
*/
private function getInputOptionDocument(InputOption $option)
{
$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()) ? array(var_export($option->getDefault(), true)) : ($option->getDefault() ? array($option->getDefault()) : array()));
$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;
}
}

View File

@@ -0,0 +1,60 @@
<?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>
*/
class ConsoleCommandEvent extends ConsoleEvent
{
/**
* The return code for skipped commands, this will also be passed into the terminate event.
*/
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;
}
}

View File

@@ -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\Event;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
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, $error, Command $command = null)
{
parent::__construct($command, $input, $output);
$this->setError($error);
}
/**
* Returns the thrown error/exception.
*
* @return \Throwable
*/
public function getError()
{
return $this->error;
}
/**
* Replaces the thrown error/exception.
*
* @param \Throwable $error
*/
public function setError($error)
{
if (!$error instanceof \Throwable && !$error instanceof \Exception) {
throw new InvalidArgumentException(sprintf('The error passed to ConsoleErrorEvent must be an instance of \Throwable or \Exception, "%s" was passed instead.', \is_object($error) ? \get_class($error) : \gettype($error)));
}
$this->error = $error;
}
/**
* Sets the exit code.
*
* @param int $exitCode The command exit code
*/
public function setExitCode($exitCode)
{
$this->exitCode = (int) $exitCode;
$r = new \ReflectionProperty($this->error, 'code');
$r->setAccessible(true);
$r->setValue($this->error, $this->exitCode);
}
/**
* Gets the exit code.
*
* @return int The command exit code
*/
public function getExitCode()
{
return null !== $this->exitCode ? $this->exitCode : (\is_int($this->error->getCode()) && 0 !== $this->error->getCode() ? $this->error->getCode() : 1);
}
}

View File

@@ -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 = null, 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;
}
}

View File

@@ -0,0 +1,71 @@
<?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;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 3.3 and will be removed in 4.0. Use the ConsoleErrorEvent instead.', ConsoleExceptionEvent::class), E_USER_DEPRECATED);
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Allows to handle exception thrown in a command.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated since version 3.3, to be removed in 4.0. Use ConsoleErrorEvent instead.
*/
class ConsoleExceptionEvent extends ConsoleEvent
{
private $exception;
private $exitCode;
public function __construct(Command $command, InputInterface $input, OutputInterface $output, \Exception $exception, $exitCode)
{
parent::__construct($command, $input, $output);
$this->setException($exception);
$this->exitCode = (int) $exitCode;
}
/**
* Returns the thrown exception.
*
* @return \Exception The thrown exception
*/
public function getException()
{
return $this->exception;
}
/**
* Replaces the thrown exception.
*
* This exception will be thrown if no response is set in the event.
*
* @param \Exception $exception The thrown exception
*/
public function setException(\Exception $exception)
{
$this->exception = $exception;
}
/**
* Gets the exit code.
*
* @return int The command exit code
*/
public function getExitCode()
{
return $this->exitCode;
}
}

View File

@@ -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 manipulate the exit code of a command after its execution.
*
* @author Francesco Levorato <git@flevour.net>
*/
class ConsoleTerminateEvent extends ConsoleEvent
{
/**
* The exit code of the command.
*
* @var int
*/
private $exitCode;
public function __construct(Command $command, InputInterface $input, OutputInterface $output, $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;
}
}

View File

@@ -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\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)) {
return $this->logger->error('An error occurred while using the console. Message: "{message}"', array('exception' => $error, 'message' => $error->getMessage()));
}
$this->logger->error('Error thrown while running command "{command}". Message: "{message}"', array('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)) {
return $this->logger->debug('The console exited with code "{code}"', array('code' => $exitCode));
}
$this->logger->debug('Command "{command}" exited with code "{code}"', array('command' => $inputString, 'code' => $exitCode));
}
public static function getSubscribedEvents()
{
return array(
ConsoleEvents::ERROR => array('onConsoleError', -128),
ConsoleEvents::TERMINATE => array('onConsoleTerminate', -128),
);
}
private static function getInputString(ConsoleEvent $event)
{
$commandName = $event->getCommand() ? $event->getCommand()->getName() : null;
$input = $event->getInput();
if (method_exists($input, '__toString')) {
if ($commandName) {
return str_replace(array("'$commandName'", "\"$commandName\""), $commandName, (string) $input);
}
return (string) $input;
}
return $commandName;
}
}

View File

@@ -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 array $alternatives List of similar defined names
* @param int $code Exception code
* @param \Exception $previous Previous exception used for the exception chaining
*/
public function __construct($message, array $alternatives = array(), $code = 0, \Exception $previous = null)
{
parent::__construct($message, $code, $previous);
$this->alternatives = $alternatives;
}
/**
* @return array A list of similar defined names
*/
public function getAlternatives()
{
return $this->alternatives;
}
}

View File

@@ -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
{
}

View File

@@ -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
{
}

View File

@@ -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
{
}

View File

@@ -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
{
}

View File

@@ -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
{
}

View File

@@ -0,0 +1,242 @@
<?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>
*/
class OutputFormatter implements OutputFormatterInterface
{
private $decorated;
private $styles = array();
private $styleStack;
/**
* Escapes "<" special char in given text.
*
* @param string $text Text to escape
*
* @return string Escaped text
*/
public static function escape($text)
{
$text = preg_replace('/([^\\\\]?)</', '$1\\<', $text);
return self::escapeTrailingBackslash($text);
}
/**
* Escapes trailing "\" in given text.
*
* @param string $text Text to escape
*
* @return string Escaped text
*
* @internal
*/
public static function escapeTrailingBackslash($text)
{
if ('\\' === substr($text, -1)) {
$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 bool $decorated Whether this formatter should actually decorate strings
* @param OutputFormatterStyleInterface[] $styles Array of "name => FormatterStyle" instances
*/
public function __construct($decorated = false, array $styles = array())
{
$this->decorated = (bool) $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)
{
$message = (string) $message;
$offset = 0;
$output = '';
$tagRegex = '[a-z][a-z0-9,_=;-]*+';
preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#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));
$offset = $pos + \strlen($text);
// opening tag?
if ($open = '/' != $text[1]) {
$tag = $matches[1][$i][0];
} else {
$tag = isset($matches[3][$i][0]) ? $matches[3][$i][0] : '';
}
if (!$open && !$tag) {
// </>
$this->styleStack->pop();
} elseif (false === $style = $this->createStyleFromString(strtolower($tag))) {
$output .= $this->applyCurrentStyle($text);
} elseif ($open) {
$this->styleStack->push($style);
} else {
$this->styleStack->pop($style);
}
}
$output .= $this->applyCurrentStyle(substr($message, $offset));
if (false !== strpos($output, "\0")) {
return strtr($output, array("\0" => '\\', '\\<' => '<'));
}
return str_replace('\\<', '<', $output);
}
/**
* @return OutputFormatterStyleStack
*/
public function getStyleStack()
{
return $this->styleStack;
}
/**
* Tries to create new style instance from string.
*
* @param string $string
*
* @return OutputFormatterStyle|false false if string is not format string
*/
private function createStyleFromString($string)
{
if (isset($this->styles[$string])) {
return $this->styles[$string];
}
if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', $string, $matches, PREG_SET_ORDER)) {
return false;
}
$style = new OutputFormatterStyle();
foreach ($matches as $match) {
array_shift($match);
if ('fg' == $match[0]) {
$style->setForeground($match[1]);
} elseif ('bg' == $match[0]) {
$style->setBackground($match[1]);
} elseif ('options' === $match[0]) {
preg_match_all('([^,;]+)', $match[1], $options);
$options = array_shift($options);
foreach ($options as $option) {
try {
$style->setOption($option);
} catch (\InvalidArgumentException $e) {
@trigger_error(sprintf('Unknown style options are deprecated since Symfony 3.2 and will be removed in 4.0. Exception "%s".', $e->getMessage()), E_USER_DEPRECATED);
return false;
}
}
} else {
return false;
}
}
return $style;
}
/**
* Applies current style from stack to text, if must be applied.
*
* @param string $text Input text
*
* @return string Styled text
*/
private function applyCurrentStyle($text)
{
return $this->isDecorated() && \strlen($text) > 0 ? $this->styleStack->getCurrent()->apply($text) : $text;
}
}

View File

@@ -0,0 +1,71 @@
<?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
* @param OutputFormatterStyleInterface $style The style instance
*/
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);
}

View File

@@ -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\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 = array(
'black' => array('set' => 30, 'unset' => 39),
'red' => array('set' => 31, 'unset' => 39),
'green' => array('set' => 32, 'unset' => 39),
'yellow' => array('set' => 33, 'unset' => 39),
'blue' => array('set' => 34, 'unset' => 39),
'magenta' => array('set' => 35, 'unset' => 39),
'cyan' => array('set' => 36, 'unset' => 39),
'white' => array('set' => 37, 'unset' => 39),
'default' => array('set' => 39, 'unset' => 39),
);
private static $availableBackgroundColors = array(
'black' => array('set' => 40, 'unset' => 49),
'red' => array('set' => 41, 'unset' => 49),
'green' => array('set' => 42, 'unset' => 49),
'yellow' => array('set' => 43, 'unset' => 49),
'blue' => array('set' => 44, 'unset' => 49),
'magenta' => array('set' => 45, 'unset' => 49),
'cyan' => array('set' => 46, 'unset' => 49),
'white' => array('set' => 47, 'unset' => 49),
'default' => array('set' => 49, 'unset' => 49),
);
private static $availableOptions = array(
'bold' => array('set' => 1, 'unset' => 22),
'underscore' => array('set' => 4, 'unset' => 24),
'blink' => array('set' => 5, 'unset' => 25),
'reverse' => array('set' => 7, 'unset' => 27),
'conceal' => array('set' => 8, 'unset' => 28),
);
private $foreground;
private $background;
private $options = array();
/**
* Initializes output formatter style.
*
* @param string|null $foreground The style foreground color name
* @param string|null $background The style background color name
* @param array $options The style options
*/
public function __construct($foreground = null, $background = null, array $options = array())
{
if (null !== $foreground) {
$this->setForeground($foreground);
}
if (null !== $background) {
$this->setBackground($background);
}
if (\count($options)) {
$this->setOptions($options);
}
}
/**
* Sets style foreground color.
*
* @param string|null $color The color name
*
* @throws InvalidArgumentException When the color name isn't defined
*/
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];
}
/**
* Sets style background color.
*
* @param string|null $color The color name
*
* @throws InvalidArgumentException When the color name isn't defined
*/
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];
}
/**
* Sets some specific style option.
*
* @param string $option The option name
*
* @throws InvalidArgumentException When the option name isn't defined
*/
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];
}
}
/**
* Unsets some specific style option.
*
* @param string $option The option name
*
* @throws InvalidArgumentException When the option name isn't defined
*/
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 = array();
foreach ($options as $option) {
$this->setOption($option);
}
}
/**
* Applies the style to a given text.
*
* @param string $text The text to style
*
* @return string
*/
public function apply($text)
{
$setCodes = array();
$unsetCodes = array();
if (null !== $this->foreground) {
$setCodes[] = $this->foreground['set'];
$unsetCodes[] = $this->foreground['unset'];
}
if (null !== $this->background) {
$setCodes[] = $this->background['set'];
$unsetCodes[] = $this->background['unset'];
}
if (\count($this->options)) {
foreach ($this->options as $option) {
$setCodes[] = $option['set'];
$unsetCodes[] = $option['unset'];
}
}
if (0 === \count($setCodes)) {
return $text;
}
return sprintf("\033[%sm%s\033[%sm", implode(';', $setCodes), $text, implode(';', $unsetCodes));
}
}

View File

@@ -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 $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);
}

View File

@@ -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\Component\Console\Formatter;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
* @author Jean-François Simon <contact@jfsimon.fr>
*/
class OutputFormatterStyleStack
{
/**
* @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 = array();
}
/**
* 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;
}
}

View File

@@ -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\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 = array('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'default');
private $started = array();
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] = array('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;
}
/**
* @param string $id The id of the formatting session
*
* @return string
*/
private function getBorder($id)
{
return sprintf('<bg=%s> </>', $this->colors[$this->started[$id]['border']]);
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'debug_formatter';
}
}

View File

@@ -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\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 = array();
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 OutputInterface $output
* @param object $object
* @param array $options
*
* @throws InvalidArgumentException when the given format is not supported
*/
public function describe(OutputInterface $output, $object, array $options = array())
{
$options = array_merge(array(
'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
* @param DescriptorInterface $descriptor
*
* @return $this
*/
public function register($format, DescriptorInterface $descriptor)
{
$this->descriptors[$format] = $descriptor;
return $this;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'descriptor';
}
}

View File

@@ -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\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 = array($messages);
}
$len = 0;
$lines = array();
foreach ($messages as $message) {
$message = OutputFormatter::escape($message);
$lines[] = sprintf($large ? ' %s ' : ' %s ', $message);
$len = max($this->strlen($message) + ($large ? 4 : 2), $len);
}
$messages = $large ? array(str_repeat(' ', $len)) : array();
for ($i = 0; isset($lines[$i]); ++$i) {
$messages[] = $lines[$i].str_repeat(' ', $len - $this->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 - $this->strlen($suffix);
if ($computedLength > $this->strlen($message)) {
return $message;
}
if (false === $encoding = mb_detect_encoding($message, null, true)) {
return substr($message, 0, $length).$suffix;
}
return mb_substr($message, 0, $length, $encoding).$suffix;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'formatter';
}
}

138
vendor/symfony/console/Helper/Helper.php vendored Normal file
View File

@@ -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\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)
{
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)
{
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 = array(
array(0, '< 1 sec'),
array(1, '1 sec'),
array(2, 'secs', 1),
array(60, '1 min'),
array(120, 'mins', 60),
array(3600, '1 hr'),
array(7200, 'hrs', 3600),
array(86400, '1 day'),
array(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;
}
}

View File

@@ -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();
}

View File

@@ -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 = array();
private $command;
/**
* @param Helper[] $helpers An array of helper
*/
public function __construct(array $helpers = array())
{
foreach ($helpers as $alias => $helper) {
$this->set($helper, \is_int($alias) ? null : $alias);
}
}
/**
* Sets a helper.
*
* @param HelperInterface $helper The helper instance
* @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 Helper[]
*/
public function getIterator()
{
return new \ArrayIterator($this->helpers);
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,141 @@
<?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>
*/
class ProcessHelper extends Helper
{
/**
* Runs an external process.
*
* @param OutputInterface $output An OutputInterface instance
* @param string|array|Process $cmd An instance of Process or an array of arguments to escape and run 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
* @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 ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
}
$formatter = $this->getHelperSet()->get('debug_formatter');
if ($cmd instanceof Process) {
$process = $cmd;
} else {
$process = new Process($cmd);
}
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);
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 OutputInterface $output An OutputInterface instance
* @param string|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.
*
* @param OutputInterface $output An OutputInterface interface
* @param Process $process The Process
* @param callable|null $callback A PHP callable
*
* @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) {
\call_user_func($callback, $type, $buffer);
}
};
}
private function escapeString($str)
{
return str_replace('<', '\\<', $str);
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'process';
}
}

View File

@@ -0,0 +1,617 @@
<?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\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 $output;
private $step = 0;
private $max;
private $startTime;
private $stepWidth;
private $percent = 0.0;
private $formatLineCount;
private $messages = array();
private $overwrite = true;
private $terminal;
private $firstRun = true;
private static $formatters;
private static $formats;
/**
* @param OutputInterface $output An OutputInterface instance
* @param int $max Maximum steps (0 if unknown)
*/
public function __construct(OutputInterface $output, $max = 0)
{
if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
}
$this->output = $output;
$this->setMaxSteps($max);
$this->terminal = new Terminal();
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->setRedrawFrequency($max / 10);
}
$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($name, callable $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 isset(self::$formatters[$name]) ? 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($name, $format)
{
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($name)
{
if (!self::$formats) {
self::$formats = self::initFormats();
}
return isset(self::$formats[$name]) ? 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($message, $name = 'message')
{
$this->messages[$name] = $message;
}
public function getMessage($name = 'message')
{
return $this->messages[$name];
}
/**
* Gets the progress bar start time.
*
* @return int The progress bar start time
*/
public function getStartTime()
{
return $this->startTime;
}
/**
* Gets the progress bar maximal steps.
*
* @return int The progress bar max steps
*/
public function getMaxSteps()
{
return $this->max;
}
/**
* Gets the current step position.
*
* @return int The progress bar step
*/
public function getProgress()
{
return $this->step;
}
/**
* Gets the progress bar step width.
*
* @return int The progress bar step width
*/
private function getStepWidth()
{
return $this->stepWidth;
}
/**
* Gets the current progress bar percent.
*
* @return float The current progress bar percent
*/
public function getProgressPercent()
{
return $this->percent;
}
/**
* Sets the progress bar width.
*
* @param int $size The progress bar size
*/
public function setBarWidth($size)
{
$this->barWidth = max(1, (int) $size);
}
/**
* Gets the progress bar width.
*
* @return int The progress bar size
*/
public function getBarWidth()
{
return $this->barWidth;
}
/**
* Sets the bar character.
*
* @param string $char A character
*/
public function setBarCharacter($char)
{
$this->barChar = $char;
}
/**
* Gets the bar character.
*
* @return string A character
*/
public function getBarCharacter()
{
if (null === $this->barChar) {
return $this->max ? '=' : $this->emptyBarChar;
}
return $this->barChar;
}
/**
* Sets the empty bar character.
*
* @param string $char A character
*/
public function setEmptyBarCharacter($char)
{
$this->emptyBarChar = $char;
}
/**
* Gets the empty bar character.
*
* @return string A character
*/
public function getEmptyBarCharacter()
{
return $this->emptyBarChar;
}
/**
* Sets the progress bar character.
*
* @param string $char A character
*/
public function setProgressCharacter($char)
{
$this->progressChar = $char;
}
/**
* Gets the progress bar character.
*
* @return string A character
*/
public function getProgressCharacter()
{
return $this->progressChar;
}
/**
* Sets the progress bar format.
*
* @param string $format The format
*/
public function setFormat($format)
{
$this->format = null;
$this->internalFormat = $format;
}
/**
* Sets the redraw frequency.
*
* @param int|float $freq The frequency in steps
*/
public function setRedrawFrequency($freq)
{
$this->redrawFreq = max((int) $freq, 1);
}
/**
* 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($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($step = 1)
{
$this->setProgress($this->step + $step);
}
/**
* Sets whether to overwrite the progressbar, false for new line.
*
* @param bool $overwrite
*/
public function setOverwrite($overwrite)
{
$this->overwrite = (bool) $overwrite;
}
/**
* Sets the current progress.
*
* @param int $step The current progress
*/
public function setProgress($step)
{
$step = (int) $step;
if ($this->max && $step > $this->max) {
$this->max = $step;
} elseif ($step < 0) {
$step = 0;
}
$prevPeriod = (int) ($this->step / $this->redrawFreq);
$currPeriod = (int) ($step / $this->redrawFreq);
$this->step = $step;
$this->percent = $this->max ? (float) $this->step / $this->max : 0;
if ($prevPeriod !== $currPeriod || $this->max === $step) {
$this->display();
}
}
/**
* Finishes the progress output.
*/
public function finish()
{
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()
{
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()
{
if (!$this->overwrite) {
return;
}
if (null === $this->format) {
$this->setRealFormat($this->internalFormat ?: $this->determineBestFormat());
}
$this->overwrite('');
}
/**
* Sets the progress bar format.
*
* @param string $format The format
*/
private function setRealFormat($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");
}
/**
* Sets the progress bar maximal steps.
*
* @param int $max The progress bar max steps
*/
private function setMaxSteps($max)
{
$this->max = max(0, (int) $max);
$this->stepWidth = $this->max ? Helper::strlen($this->max) : 4;
}
/**
* Overwrites a previous message to the output.
*
* @param string $message The message
*/
private function overwrite($message)
{
if ($this->overwrite) {
if (!$this->firstRun) {
// Move the cursor to the beginning of the line
$this->output->write("\x0D");
// Erase the line
$this->output->write("\x1B[2K");
// Erase previous lines
if ($this->formatLineCount > 0) {
$this->output->write(str_repeat("\x1B[1A\x1B[2K", $this->formatLineCount));
}
}
} elseif ($this->step > 0) {
$this->output->writeln('');
}
$this->firstRun = false;
$this->output->write($message);
}
private function determineBestFormat()
{
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()
{
return array(
'bar' => function (ProgressBar $bar, OutputInterface $output) {
$completeBars = floor($bar->getMaxSteps() > 0 ? $bar->getProgressPercent() * $bar->getBarWidth() : $bar->getProgress() % $bar->getBarWidth());
$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 (ProgressBar $bar) {
return Helper::formatTime(time() - $bar->getStartTime());
},
'remaining' => function (ProgressBar $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 (ProgressBar $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 (ProgressBar $bar) {
return Helper::formatMemory(memory_get_usage(true));
},
'current' => function (ProgressBar $bar) {
return str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', STR_PAD_LEFT);
},
'max' => function (ProgressBar $bar) {
return $bar->getMaxSteps();
},
'percent' => function (ProgressBar $bar) {
return floor($bar->getProgressPercent() * 100);
},
);
}
private static function initFormats()
{
return array(
'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%',
);
}
/**
* @return string
*/
private function buildLine()
{
$regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i";
$callback = function ($matches) {
if ($formatter = $this::getPlaceholderFormatterDefinition($matches[1])) {
$text = \call_user_func($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);
}
}

View File

@@ -0,0 +1,271 @@
<?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 OutputInterface $output
* @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, $format = null, $indicatorChangeInterval = 100, $indicatorValues = null)
{
$this->output = $output;
if (null === $format) {
$format = $this->determineBestFormat();
}
if (null === $indicatorValues) {
$indicatorValues = array('-', '\\', '|', '/');
}
$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 isset(self::$formats[$name]) ? 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 isset(self::$formatters[$name]) ? self::$formatters[$name] : null;
}
private function display()
{
if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
return;
}
$self = $this;
$this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) use ($self) {
if ($formatter = $self::getPlaceholderFormatterDefinition($matches[1])) {
return \call_user_func($formatter, $self);
}
return $matches[0];
}, $this->format));
}
private function determineBestFormat()
{
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.
*
* @param string $message The message
*/
private function overwrite($message)
{
if ($this->output->isDecorated()) {
$this->output->write("\x0D\x1B[2K");
$this->output->write($message);
} else {
$this->output->writeln($message);
}
}
private function getCurrentTimeInMilliseconds()
{
return round(microtime(true) * 1000);
}
private static function initPlaceholderFormatters()
{
return array(
'indicator' => function (ProgressIndicator $indicator) {
return $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)];
},
'message' => function (ProgressIndicator $indicator) {
return $indicator->message;
},
'elapsed' => function (ProgressIndicator $indicator) {
return Helper::formatTime(time() - $indicator->startTime);
},
'memory' => function () {
return Helper::formatMemory(memory_get_usage(true));
},
);
}
private static function initFormats()
{
return array(
'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%)',
);
}
}

View File

@@ -0,0 +1,467 @@
<?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\OutputFormatterStyle;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\StreamableInputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\Question;
/**
* 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;
/**
* 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()) {
if ($question instanceof ChoiceQuestion) {
$choices = $question->getChoices();
return $choices[$question->getDefault()];
}
return $question->getDefault();
}
if ($input instanceof StreamableInputInterface && $stream = $input->getStream()) {
$this->inputStream = $stream;
}
if (!$question->getValidator()) {
return $this->doAsk($output, $question);
}
$interviewer = function () use ($output, $question) {
return $this->doAsk($output, $question);
};
return $this->validateAttempts($interviewer, $output, $question);
}
/**
* Sets the input stream to read from when interacting with the user.
*
* This is mainly useful for testing purpose.
*
* @deprecated since version 3.2, to be removed in 4.0. Use
* StreamableInputInterface::setStream() instead.
*
* @param resource $stream The input stream
*
* @throws InvalidArgumentException In case the stream is not a resource
*/
public function setInputStream($stream)
{
@trigger_error(sprintf('The %s() method is deprecated since Symfony 3.2 and will be removed in 4.0. Use %s::setStream() instead.', __METHOD__, StreamableInputInterface::class), E_USER_DEPRECATED);
if (!\is_resource($stream)) {
throw new InvalidArgumentException('Input stream must be a valid resource.');
}
$this->inputStream = $stream;
}
/**
* Returns the helper's input stream.
*
* @deprecated since version 3.2, to be removed in 4.0. Use
* StreamableInputInterface::getStream() instead.
*
* @return resource
*/
public function getInputStream()
{
if (0 === \func_num_args() || func_get_arg(0)) {
@trigger_error(sprintf('The %s() method is deprecated since Symfony 3.2 and will be removed in 4.0. Use %s::getStream() instead.', __METHOD__, StreamableInputInterface::class), E_USER_DEPRECATED);
}
return $this->inputStream;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'question';
}
/**
* Prevents usage of stty.
*/
public static function disableStty()
{
self::$stty = false;
}
/**
* Asks the question to the user.
*
* @return bool|mixed|null|string
*
* @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->getAutocompleterValues();
if (null === $autocomplete || !$this->hasSttyAvailable()) {
$ret = false;
if ($question->isHidden()) {
try {
$ret = trim($this->getHiddenResponse($output, $inputStream));
} catch (RuntimeException $e) {
if (!$question->isHiddenFallback()) {
throw $e;
}
}
}
if (false === $ret) {
$ret = fgets($inputStream, 4096);
if (false === $ret) {
throw new RuntimeException('Aborted');
}
$ret = trim($ret);
}
} else {
$ret = trim($this->autocomplete($output, $question, $inputStream, \is_array($autocomplete) ? $autocomplete : iterator_to_array($autocomplete, false)));
}
$ret = \strlen($ret) > 0 ? $ret : $question->getDefault();
if ($normalizer = $question->getNormalizer()) {
return $normalizer($ret);
}
return $ret;
}
/**
* Outputs the question prompt.
*/
protected function writePrompt(OutputInterface $output, Question $question)
{
$message = $question->getQuestion();
if ($question instanceof ChoiceQuestion) {
$maxWidth = max(array_map(array($this, 'strlen'), array_keys($question->getChoices())));
$messages = (array) $question->getQuestion();
foreach ($question->getChoices() as $key => $value) {
$width = $maxWidth - $this->strlen($key);
$messages[] = ' [<info>'.$key.str_repeat(' ', $width).'</info>] '.$value;
}
$output->writeln($messages);
$message = $question->getPrompt();
}
$output->write($message);
}
/**
* 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 OutputInterface $output
* @param Question $question
* @param resource $inputStream
* @param array $autocomplete
*
* @return string
*/
private function autocomplete(OutputInterface $output, Question $question, $inputStream, array $autocomplete)
{
$ret = '';
$i = 0;
$ofs = -1;
$matches = $autocomplete;
$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);
// Backspace Character
if ("\177" === $c) {
if (0 === $numMatches && 0 !== $i) {
--$i;
// Move cursor backwards
$output->write("\033[1D");
}
if (0 === $i) {
$ofs = -1;
$matches = $autocomplete;
$numMatches = \count($matches);
} else {
$numMatches = 0;
}
// Pop the last character off the end of our string
$ret = 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 = $matches[$ofs];
// Echo out remaining chars for current match
$output->write(substr($ret, $i));
$i = \strlen($ret);
}
if ("\n" === $c) {
$output->write($c);
break;
}
$numMatches = 0;
}
continue;
} else {
$output->write($c);
$ret .= $c;
++$i;
$numMatches = 0;
$ofs = 0;
foreach ($autocomplete as $value) {
// If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle)
if (0 === strpos($value, $ret)) {
$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
$output->write('<hl>'.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $i)).'</hl>');
// Restore cursor position
$output->write("\0338");
}
}
// Reset stty so it behaves normally again
shell_exec(sprintf('stty %s', $sttyMode));
return $ret;
}
/**
* Gets a hidden response from user.
*
* @param OutputInterface $output An Output instance
* @param resource $inputStream The handler resource
*
* @return string The answer
*
* @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
*/
private function getHiddenResponse(OutputInterface $output, $inputStream)
{
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;
}
$value = rtrim(shell_exec($exe));
$output->writeln('');
if (isset($tmpExe)) {
unlink($tmpExe);
}
return $value;
}
if ($this->hasSttyAvailable()) {
$sttyMode = shell_exec('stty -g');
shell_exec('stty -echo');
$value = fgets($inputStream, 4096);
shell_exec(sprintf('stty %s', $sttyMode));
if (false === $value) {
throw new RuntimeException('Aborted');
}
$value = trim($value);
$output->writeln('');
return $value;
}
if (false !== $shell = $this->getShell()) {
$readCmd = 'csh' === $shell ? 'set mypassword = $<' : 'read -r mypassword';
$command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd);
$value = rtrim(shell_exec($command));
$output->writeln('');
return $value;
}
throw new RuntimeException('Unable to hide the response.');
}
/**
* Validates an attempt.
*
* @param callable $interviewer A callable that will ask for a question and return the result
* @param OutputInterface $output An Output instance
* @param Question $question A Question instance
*
* @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 \call_user_func($question->getValidator(), $interviewer());
} catch (RuntimeException $e) {
throw $e;
} catch (\Exception $error) {
}
}
throw $error;
}
/**
* Returns a valid unix shell.
*
* @return string|bool The valid shell name, false in case no valid shell is found
*/
private function getShell()
{
if (null !== self::$shell) {
return self::$shell;
}
self::$shell = false;
if (file_exists('/usr/bin/env')) {
// handle other OSs with bash/zsh/ksh/csh if available to hide the answer
$test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null";
foreach (array('bash', 'zsh', 'ksh', 'csh') as $sh) {
if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) {
self::$shell = $sh;
break;
}
}
}
return self::$shell;
}
/**
* Returns whether Stty is available or not.
*
* @return bool
*/
private function hasSttyAvailable()
{
if (null !== self::$stty) {
return self::$stty;
}
exec('stty 2>&1', $output, $exitcode);
return self::$stty = 0 === $exitcode;
}
}

View File

@@ -0,0 +1,124 @@
<?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\Formatter\OutputFormatter;
use Symfony\Component\Console\Input\InputInterface;
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}
*
* To be removed in 4.0
*/
public function ask(InputInterface $input, OutputInterface $output, Question $question)
{
$validator = $question->getValidator();
$question->setValidator(function ($value) use ($validator) {
if (null !== $validator) {
$value = $validator($value);
} else {
// make required
if (!\is_array($value) && !\is_bool($value) && 0 === \strlen($value)) {
@trigger_error('The default question validator is deprecated since Symfony 3.3 and will not be used anymore in version 4.0. Set a custom question validator if needed.', E_USER_DEPRECATED);
throw new LogicException('A value is required.');
}
}
return $value;
});
return parent::ask($input, $output, $question);
}
/**
* {@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]));
break;
default:
$text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape($default));
}
$output->writeln($text);
if ($question instanceof ChoiceQuestion) {
$width = max(array_map('strlen', array_keys($question->getChoices())));
foreach ($question->getChoices() as $key => $value) {
$output->writeln(sprintf(" [<comment>%-${width}s</comment>] %s", $key, $value));
}
}
$output->write(' > ');
}
/**
* {@inheritdoc}
*/
protected function writeError(OutputInterface $output, \Exception $error)
{
if ($output instanceof SymfonyStyle) {
$output->newLine();
$output->error($error->getMessage());
return;
}
parent::writeError($output, $error);
}
}

702
vendor/symfony/console/Helper/Table.php vendored Normal file
View File

@@ -0,0 +1,702 @@
<?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\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>
*/
class Table
{
/**
* Table headers.
*/
private $headers = array();
/**
* Table rows.
*/
private $rows = array();
/**
* Column widths cache.
*/
private $effectiveColumnWidths = array();
/**
* Number of columns cache.
*
* @var int
*/
private $numberOfColumns;
/**
* @var OutputInterface
*/
private $output;
/**
* @var TableStyle
*/
private $style;
/**
* @var array
*/
private $columnStyles = array();
/**
* User set column widths.
*
* @var array
*/
private $columnWidths = array();
private static $styles;
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
* @param TableStyle $style A TableStyle instance
*/
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)
{
if (isset($this->columnStyles[$columnIndex])) {
return $this->columnStyles[$columnIndex];
}
return $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.
*
* @param array $widths
*
* @return $this
*/
public function setColumnWidths(array $widths)
{
$this->columnWidths = array();
foreach ($widths as $index => $width) {
$this->setColumnWidth($index, $width);
}
return $this;
}
public function setHeaders(array $headers)
{
$headers = array_values($headers);
if (!empty($headers) && !\is_array($headers[0])) {
$headers = array($headers);
}
$this->headers = $headers;
return $this;
}
public function setRows(array $rows)
{
$this->rows = array();
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;
}
public function setRow($column, array $row)
{
$this->rows[$column] = $row;
return $this;
}
/**
* Renders table to output.
*
* Example:
* <code>
* +---------------+-----------------------+------------------+
* | 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 |
* +---------------+-----------------------+------------------+
* </code>
*/
public function render()
{
$this->calculateNumberOfColumns();
$rows = $this->buildTableRows($this->rows);
$headers = $this->buildTableRows($this->headers);
$this->calculateColumnsWidth(array_merge($headers, $rows));
$this->renderRowSeparator();
if (!empty($headers)) {
foreach ($headers as $header) {
$this->renderRow($header, $this->style->getCellHeaderFormat());
$this->renderRowSeparator();
}
}
foreach ($rows as $row) {
if ($row instanceof TableSeparator) {
$this->renderRowSeparator();
} else {
$this->renderRow($row, $this->style->getCellRowFormat());
}
}
if (!empty($rows)) {
$this->renderRowSeparator();
}
$this->cleanup();
}
/**
* Renders horizontal header separator.
*
* Example: <code>+-----+-----------+-------+</code>
*/
private function renderRowSeparator()
{
if (0 === $count = $this->numberOfColumns) {
return;
}
if (!$this->style->getHorizontalBorderChar() && !$this->style->getCrossingChar()) {
return;
}
$markup = $this->style->getCrossingChar();
for ($column = 0; $column < $count; ++$column) {
$markup .= str_repeat($this->style->getHorizontalBorderChar(), $this->effectiveColumnWidths[$column]).$this->style->getCrossingChar();
}
$this->output->writeln(sprintf($this->style->getBorderFormat(), $markup));
}
/**
* Renders vertical column separator.
*/
private function renderColumnSeparator()
{
return sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar());
}
/**
* Renders table row.
*
* Example: <code>| 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |</code>
*
* @param array $row
* @param string $cellFormat
*/
private function renderRow(array $row, $cellFormat)
{
if (empty($row)) {
return;
}
$rowContent = $this->renderColumnSeparator();
foreach ($this->getRowColumns($row) as $column) {
$rowContent .= $this->renderCell($row, $column, $cellFormat);
$rowContent .= $this->renderColumnSeparator();
}
$this->output->writeln($rowContent);
}
/**
* Renders table cell with padding.
*
* @param array $row
* @param int $column
* @param string $cellFormat
*/
private function renderCell(array $row, $column, $cellFormat)
{
$cell = isset($row[$column]) ? $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->getHorizontalBorderChar(), $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()
{
if (null !== $this->numberOfColumns) {
return;
}
$columns = array(0);
foreach (array_merge($this->headers, $this->rows) as $row) {
if ($row instanceof TableSeparator) {
continue;
}
$columns[] = $this->getNumberOfColumns($row);
}
$this->numberOfColumns = max($columns);
}
private function buildTableRows($rows)
{
$unmergedRows = array();
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) {
if (!strstr($cell, "\n")) {
continue;
}
$lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell));
foreach ($lines as $lineKey => $line) {
if ($cell instanceof TableCell) {
$line = new TableCell($line, array('colspan' => $cell->getColspan()));
}
if (0 === $lineKey) {
$rows[$rowKey][$column] = $line;
} else {
$unmergedRows[$rowKey][$lineKey][$column] = $line;
}
}
}
}
$tableRows = array();
foreach ($rows as $rowKey => $row) {
$tableRows[] = $this->fillCells($row);
if (isset($unmergedRows[$rowKey])) {
$tableRows = array_merge($tableRows, $unmergedRows[$rowKey]);
}
}
return $tableRows;
}
/**
* fill rows that contains rowspan > 1.
*
* @param array $rows
* @param int $line
*
* @return array
*
* @throws InvalidArgumentException
*/
private function fillNextRows(array $rows, $line)
{
$unmergedRows = array();
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 = array($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], array('colspan' => $cell->getColspan()));
unset($lines[0]);
}
// create a two dimensional array (rowspan x colspan)
$unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, array()), $unmergedRows);
foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
$value = isset($lines[$unmergedRowKey - $line]) ? $lines[$unmergedRowKey - $line] : '';
$unmergedRows[$unmergedRowKey][$column] = new TableCell($value, array('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, array($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, array($row));
}
}
return $rows;
}
/**
* fill cells for a row that contains colspan > 1.
*
* @return array
*/
private function fillCells($row)
{
$newRow = array();
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;
}
/**
* @param array $rows
* @param int $line
*
* @return array
*/
private function copyRow(array $rows, $line)
{
$row = $rows[$line];
foreach ($row as $cellKey => $cellValue) {
$row[$cellKey] = '';
if ($cellValue instanceof TableCell) {
$row[$cellKey] = new TableCell('', array('colspan' => $cellValue->getColspan()));
}
}
return $row;
}
/**
* Gets number of columns by row.
*
* @return int
*/
private function getNumberOfColumns(array $row)
{
$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.
*
* @return array
*/
private function getRowColumns(array $row)
{
$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(array $rows)
{
for ($column = 0; $column < $this->numberOfColumns; ++$column) {
$lengths = array();
foreach ($rows 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) + \strlen($this->style->getCellRowContentFormat()) - 2;
}
}
/**
* Gets column width.
*
* @return int
*/
private function getColumnSeparatorWidth()
{
return \strlen(sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar()));
}
/**
* Gets cell width.
*
* @param array $row
* @param int $column
*
* @return int
*/
private function getCellWidth(array $row, $column)
{
$cellWidth = 0;
if (isset($row[$column])) {
$cell = $row[$column];
$cellWidth = Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell);
}
$columnWidth = isset($this->columnWidths[$column]) ? $this->columnWidths[$column] : 0;
return max($cellWidth, $columnWidth);
}
/**
* Called after rendering to cleanup cache data.
*/
private function cleanup()
{
$this->effectiveColumnWidths = array();
$this->numberOfColumns = null;
}
private static function initStyles()
{
$borderless = new TableStyle();
$borderless
->setHorizontalBorderChar('=')
->setVerticalBorderChar(' ')
->setCrossingChar(' ')
;
$compact = new TableStyle();
$compact
->setHorizontalBorderChar('')
->setVerticalBorderChar(' ')
->setCrossingChar('')
->setCellRowContentFormat('%s')
;
$styleGuide = new TableStyle();
$styleGuide
->setHorizontalBorderChar('-')
->setVerticalBorderChar(' ')
->setCrossingChar(' ')
->setCellHeaderFormat('%s')
;
return array(
'default' => new TableStyle(),
'borderless' => $borderless,
'compact' => $compact,
'symfony-style-guide' => $styleGuide,
);
}
private function resolveStyle($name)
{
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));
}
}

View File

@@ -0,0 +1,76 @@
<?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 = array(
'rowspan' => 1,
'colspan' => 1,
);
/**
* @param string $value
* @param array $options
*/
public function __construct($value = '', array $options = array())
{
if (is_numeric($value) && !\is_string($value)) {
$value = (string) $value;
}
$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'];
}
}

View File

@@ -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 = array())
{
parent::__construct('', $options);
}
}

View File

@@ -0,0 +1,258 @@
<?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>
*/
class TableStyle
{
private $paddingChar = ' ';
private $horizontalBorderChar = '-';
private $verticalBorderChar = '|';
private $crossingChar = '+';
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 character.
*
* @param string $horizontalBorderChar
*
* @return $this
*/
public function setHorizontalBorderChar($horizontalBorderChar)
{
$this->horizontalBorderChar = $horizontalBorderChar;
return $this;
}
/**
* Gets horizontal border character.
*
* @return string
*/
public function getHorizontalBorderChar()
{
return $this->horizontalBorderChar;
}
/**
* Sets vertical border character.
*
* @param string $verticalBorderChar
*
* @return $this
*/
public function setVerticalBorderChar($verticalBorderChar)
{
$this->verticalBorderChar = $verticalBorderChar;
return $this;
}
/**
* Gets vertical border character.
*
* @return string
*/
public function getVerticalBorderChar()
{
return $this->verticalBorderChar;
}
/**
* Sets crossing character.
*
* @param string $crossingChar
*
* @return $this
*/
public function setCrossingChar($crossingChar)
{
$this->crossingChar = $crossingChar;
return $this;
}
/**
* Gets crossing character.
*
* @return string $crossingChar
*/
public function getCrossingChar()
{
return $this->crossingChar;
}
/**
* 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, array(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;
}
}

View File

@@ -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)
* @param InputDefinition|null $definition A InputDefinition instance
*/
public function __construct(array $argv = null, InputDefinition $definition = null)
{
if (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 && 0 === strpos($token, '--')) {
$this->parseLongOption($token);
} elseif ($parseOptions && '-' === $token[0] && '-' !== $token) {
$this->parseShortOption($token);
} else {
$this->parseArgument($token);
}
}
}
/**
* Parses a short option.
*
* @param string $token The current token
*/
private function parseShortOption($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($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($token)
{
$name = substr($token, 2);
if (false !== $pos = strpos($name, '=')) {
if (0 === \strlen($value = substr($name, $pos + 1))) {
// if no value after "=" then substr() returns "" since php7 only, false before
// see http://php.net/manual/fr/migration70.incompatible.php#119151
if (\PHP_VERSION_ID < 70000 && false === $value) {
$value = '';
}
array_unshift($this->parsed, $value);
}
$this->addLongOption(substr($name, 0, $pos), $value);
} else {
$this->addLongOption($name, null);
}
}
/**
* Parses an argument.
*
* @param string $token The current token
*
* @throws RuntimeException When too many arguments are given
*/
private function parseArgument($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() ? array($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.
*
* @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($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($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, array('', 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, array('', 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()
{
foreach ($this->tokens as $token) {
if ($token && '-' === $token[0]) {
continue;
}
return $token;
}
}
/**
* {@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 = 0 === strpos($value, '--') ? $value.'=' : $value;
if ($token === $value || '' !== $leading && 0 === strpos($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 = 0 === strpos($value, '--') ? $value.'=' : $value;
if ('' !== $leading && 0 === strpos($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);
}
}

View File

@@ -0,0 +1,206 @@
<?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(array('name' => 'foo', '--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 $key => $value) {
if ($key && '-' === $key[0]) {
continue;
}
return $value;
}
}
/**
* {@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 = array();
foreach ($this->parameters as $param => $val) {
if ($param && '-' === $param[0]) {
if (\is_array($val)) {
foreach ($val as $v) {
$params[] = $param.('' != $v ? '='.$this->escapeToken($v) : '');
}
} else {
$params[] = $param.('' != $val ? '='.$this->escapeToken($val) : '');
}
} else {
$params[] = \is_array($val) ? implode(' ', array_map(array($this, 'escapeToken'), $val)) : $this->escapeToken($val);
}
}
return implode(' ', $params);
}
/**
* {@inheritdoc}
*/
protected function parse()
{
foreach ($this->parameters as $key => $value) {
if ('--' === $key) {
return;
}
if (0 === strpos($key, '--')) {
$this->addLongOption(substr($key, 2), $value);
} elseif ('-' === $key[0]) {
$this->addShortOption(substr($key, 1), $value);
} else {
$this->addArgument($key, $value);
}
}
}
/**
* Adds a short option value.
*
* @param string $shortcut The short option key
* @param mixed $value The value for the option
*
* @throws InvalidOptionException When option given doesn't exist
*/
private function addShortOption($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.
*
* @param string $name The long option key
* @param mixed $value The value for the option
*
* @throws InvalidOptionException When option given doesn't exist
* @throws InvalidOptionException When a required value is missing
*/
private function addLongOption($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 $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;
}
}

203
vendor/symfony/console/Input/Input.php vendored Normal file
View File

@@ -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 = array();
protected $arguments = array();
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 = array();
$this->options = array();
$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($name)) {
throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
}
return isset($this->arguments[$name]) ? $this->arguments[$name] : $this->definition->getArgument($name)->getDefault();
}
/**
* {@inheritdoc}
*/
public function setArgument($name, $value)
{
if (!$this->definition->hasArgument($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($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;
}
}

View File

@@ -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
{
const REQUIRED = 1;
const OPTIONAL = 2;
const IS_ARRAY = 4;
private $name;
private $mode;
private $default;
private $description;
/**
* @param string $name The argument name
* @param int $mode The argument mode: self::REQUIRED or self::OPTIONAL
* @param string $description A description text
* @param mixed $default The default value (for self::OPTIONAL mode only)
*
* @throws InvalidArgumentException When argument mode is not valid
*/
public function __construct($name, $mode = null, $description = '', $default = null)
{
if (null === $mode) {
$mode = self::OPTIONAL;
} elseif (!\is_int($mode) || $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 mixed $default The default value
*
* @throws LogicException When incorrect default value is given
*/
public function setDefault($default = null)
{
if (self::REQUIRED === $this->mode && null !== $default) {
throw new LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.');
}
if ($this->isArray()) {
if (null === $default) {
$default = array();
} 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 mixed The default value
*/
public function getDefault()
{
return $this->default;
}
/**
* Returns the description text.
*
* @return string The description text
*/
public function getDescription()
{
return $this->description;
}
}

View File

@@ -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\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.
*
* @param InputInterface
*/
public function setInput(InputInterface $input);
}

View File

@@ -0,0 +1,402 @@
<?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(array(
* 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 = array())
{
$this->setDefinition($definition);
}
/**
* Sets the definition of the input.
*/
public function setDefinition(array $definition)
{
$arguments = array();
$options = array();
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 = array())
{
$this->arguments = array();
$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 = array())
{
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;
}
/**
* Gets the default values.
*
* @return array An array of default values
*/
public function getArgumentDefaults()
{
$values = array();
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 = array())
{
$this->options = array();
$this->shortcuts = array();
$this->addOptions($options);
}
/**
* Adds an array of InputOption objects.
*
* @param InputOption[] $options An array of InputOption objects
*/
public function addOptions($options = array())
{
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));
}
/**
* Gets an array of default values.
*
* @return array An array of all default values
*/
public function getOptionDefaults()
{
$values = array();
foreach ($this->options as $option) {
$values[$option->getName()] = $option->getDefault();
}
return $values;
}
/**
* Returns the InputOption name given a shortcut.
*
* @param string $shortcut The shortcut
*
* @return string The InputOption name
*
* @throws InvalidArgumentException When option given does not exist
*/
private function shortcutToName($shortcut)
{
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 = array();
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[] = '[--]';
}
foreach ($this->getArguments() as $argument) {
$element = '<'.$argument->getName().'>';
if (!$argument->isRequired()) {
$element = '['.$element.']';
} elseif ($argument->isArray()) {
$element = $element.' ('.$element.')';
}
if ($argument->isArray()) {
$element .= '...';
}
$elements[] = $element;
}
return implode(' ', $elements);
}
}

View File

@@ -0,0 +1,161 @@
<?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 mixed $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.
*/
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
*/
public function getArguments();
/**
* Returns the argument value for a given argument name.
*
* @param string $name The argument name
*
* @return mixed The argument value
*
* @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 string $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|int $name The InputArgument name or position
*
* @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
*/
public function getOptions();
/**
* Returns the option value for a given option name.
*
* @param string $name The option name
*
* @return mixed The option value
*
* @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 string|bool $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);
}

View File

@@ -0,0 +1,208 @@
<?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
{
const VALUE_NONE = 1;
const VALUE_REQUIRED = 2;
const VALUE_OPTIONAL = 4;
const VALUE_IS_ARRAY = 8;
private $name;
private $shortcut;
private $mode;
private $default;
private $description;
/**
* @param string $name The option name
* @param string|array $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
* @param int $mode The option mode: One of the VALUE_* constants
* @param string $description A description text
* @param mixed $default The default value (must be null for self::VALUE_NONE)
*
* @throws InvalidArgumentException If option mode is invalid or incompatible
*/
public function __construct($name, $shortcut = null, $mode = null, $description = '', $default = null)
{
if (0 === strpos($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 (!\is_int($mode) || $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 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);
}
/**
* Sets the default value.
*
* @param mixed $default The default value
*
* @throws LogicException When incorrect default value is given
*/
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 = array();
} 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 mixed The default value
*/
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()
;
}
}

View File

@@ -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();
}

View File

@@ -0,0 +1,72 @@
<?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
{
const REGEX_STRING = '([^\s]+?)(?:\s|(?<!\\\\)"|(?<!\\\\)\'|$)';
const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')';
/**
* @param string $input A string representing the parameters from the CLI
*/
public function __construct($input)
{
parent::__construct(array());
$this->setTokens($this->tokenize($input));
}
/**
* Tokenizes a string.
*
* @param string $input The input to tokenize
*
* @return array An array of tokens
*
* @throws InvalidArgumentException When unable to parse input (should never happen)
*/
private function tokenize($input)
{
$tokens = array();
$length = \strlen($input);
$cursor = 0;
while ($cursor < $length) {
if (preg_match('/\s+/A', $input, $match, null, $cursor)) {
} elseif (preg_match('/([^="\'\s]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, null, $cursor)) {
$tokens[] = $match[1].$match[2].stripcslashes(str_replace(array('"\'', '\'"', '\'\'', '""'), '', substr($match[3], 1, \strlen($match[3]) - 2)));
} elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, null, $cursor)) {
$tokens[] = stripcslashes(substr($match[0], 1, \strlen($match[0]) - 2));
} elseif (preg_match('/'.self::REGEX_STRING.'/A', $input, $match, null, $cursor)) {
$tokens[] = stripcslashes($match[1]);
} else {
// should never happen
throw new InvalidArgumentException(sprintf('Unable to parse input near "... %s ..."', substr($input, $cursor, 10)));
}
$cursor += \strlen($match[0]);
}
return $tokens;
}
}

19
vendor/symfony/console/LICENSE vendored Normal file
View File

@@ -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.

View File

@@ -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\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 http://www.php-fig.org/psr/psr-3/
*/
class ConsoleLogger extends AbstractLogger
{
const INFO = 'info';
const ERROR = 'error';
private $output;
private $verbosityLevelMap = array(
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 = array(
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(), array $formatLevelMap = array())
{
$this->output = $output;
$this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap;
$this->formatLevelMap = $formatLevelMap + $this->formatLevelMap;
}
/**
* {@inheritdoc}
*/
public function log($level, $message, array $context = array())
{
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
*
* @param string $message
* @param array $context
*
* @return string
*/
private function interpolate($message, array $context)
{
if (false === strpos($message, '{')) {
return $message;
}
$replacements = array();
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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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\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;
/**
* @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($verbosity = self::VERBOSITY_NORMAL, $decorated = null, OutputFormatterInterface $formatter = null)
{
parent::__construct($this->openOutputStream(), $verbosity, $decorated, $formatter);
$actualDecorated = $this->isDecorated();
$this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated, $this->getFormatter());
if (null === $decorated) {
$this->setDecorated($actualDecorated && $this->stderr->isDecorated());
}
}
/**
* {@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.
*
* @return bool
*/
private function isRunningOS400()
{
$checks = array(
\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');
}
return @fopen('php://stdout', 'w') ?: fopen('php://output', 'w');
}
/**
* @return resource
*/
private function openErrorStream()
{
return fopen($this->hasStderrSupport() ? 'php://stderr' : 'php://output', 'w');
}
}

View File

@@ -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\Console\Output;
/**
* ConsoleOutputInterface is the interface implemented by ConsoleOutput class.
* This adds information about stderr output stream.
*
* @author Dariusz Górecki <darek.krk@gmail.com>
*/
interface ConsoleOutputInterface extends OutputInterface
{
/**
* Gets the OutputInterface for errors.
*
* @return OutputInterface
*/
public function getErrorOutput();
public function setErrorOutput(OutputInterface $error);
}

View File

@@ -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
}
}

175
vendor/symfony/console/Output/Output.php vendored Normal file
View File

@@ -0,0 +1,175 @@
<?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($verbosity = self::VERBOSITY_NORMAL, $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)
{
$messages = (array) $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);
}

View File

@@ -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
{
const VERBOSITY_QUIET = 16;
const VERBOSITY_NORMAL = 32;
const VERBOSITY_VERBOSE = 64;
const VERBOSITY_VERY_VERBOSE = 128;
const VERBOSITY_DEBUG = 256;
const OUTPUT_NORMAL = 1;
const OUTPUT_RAW = 2;
const OUTPUT_PLAIN = 4;
/**
* Writes a message to the output.
*
* @param string|array $messages The message as an array 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|array $messages The message as an array 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();
}

View File

@@ -0,0 +1,120 @@
<?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\Exception\RuntimeException;
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, $verbosity = self::VERBOSITY_NORMAL, $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 (false === @fwrite($this->stream, $message) || ($newline && (false === @fwrite($this->stream, PHP_EOL)))) {
// should never happen
throw new RuntimeException('Unable to write output.');
}
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()
{
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;
}
}

View File

@@ -0,0 +1,189 @@
<?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($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;
}
/**
* Returns the default answer validator.
*
* @return callable
*/
private function getDefaultValidator()
{
$choices = $this->choices;
$errorMessage = $this->errorMessage;
$multiselect = $this->multiselect;
$isAssoc = $this->isAssoc($choices);
return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) {
// Collapse all spaces.
$selectedChoices = str_replace(' ', '', $selected);
if ($multiselect) {
// Check for a separated comma values
if (!preg_match('/^[^,]+(?:,[^,]+)*$/', $selectedChoices, $matches)) {
throw new InvalidArgumentException(sprintf($errorMessage, $selected));
}
$selectedChoices = explode(',', $selectedChoices);
} else {
$selectedChoices = array($selected);
}
$multiselectChoices = array();
foreach ($selectedChoices as $value) {
$results = array();
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);
};
}
}

View File

@@ -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\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($question, $default = true, $trueAnswerRegex = '/^y/i')
{
parent::__construct($question, (bool) $default);
$this->trueAnswerRegex = $trueAnswerRegex;
$this->setNormalizer($this->getDefaultNormalizer());
}
/**
* Returns the default answer normalizer.
*
* @return callable
*/
private function getDefaultNormalizer()
{
$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;
};
}
}

View File

@@ -0,0 +1,246 @@
<?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 $autocompleterValues;
private $validator;
private $default;
private $normalizer;
/**
* @param string $question The question to ask to the user
* @param mixed $default The default answer to return if the user enters nothing
*/
public function __construct($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 mixed
*/
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->autocompleterValues) {
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 null|iterable
*/
public function getAutocompleterValues()
{
return $this->autocompleterValues;
}
/**
* Sets values for the autocompleter.
*
* @param null|iterable $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);
}
if (null !== $values && !\is_array($values) && !$values instanceof \Traversable) {
throw new InvalidArgumentException('Autocompleter values can be either an array, `null` or a `Traversable` object.');
}
if ($this->hidden) {
throw new LogicException('A hidden question cannot use the autocompleter.');
}
$this->autocompleterValues = $values;
return $this;
}
/**
* Sets a validator for the question.
*
* @param null|callable $validator
*
* @return $this
*/
public function setValidator(callable $validator = null)
{
$this->validator = $validator;
return $this;
}
/**
* Gets the validator for the question.
*
* @return null|callable
*/
public function getValidator()
{
return $this->validator;
}
/**
* Sets the maximum number of attempts.
*
* Null means an unlimited number of attempts.
*
* @param null|int $attempts
*
* @return $this
*
* @throws InvalidArgumentException in case the number of attempts is invalid
*/
public function setMaxAttempts($attempts)
{
if (null !== $attempts && $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 null|int
*/
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.
*
* @param callable $normalizer
*
* @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
*/
public function getNormalizer()
{
return $this->normalizer;
}
protected function isAssoc($array)
{
return (bool) \count(array_filter(array_keys($array), 'is_string'));
}
}

20
vendor/symfony/console/README.md vendored Normal file
View File

@@ -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/index.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.

Binary file not shown.

View File

@@ -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();
}
}

View File

@@ -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\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 array $choices
* @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();
}

View File

@@ -0,0 +1,429 @@
<?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\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\Input\InputInterface;
use Symfony\Component\Console\Output\BufferedOutput;
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\Terminal;
/**
* Output decorator helpers for the Symfony Style Guide.
*
* @author Kevin Bond <kevinbond@gmail.com>
*/
class SymfonyStyle extends OutputStyle
{
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 BufferedOutput($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) : array($messages);
$this->autoPrependBlock();
$this->writeln($this->createBlock($messages, $type, $style, $prefix, $padding, $escape));
$this->newLine();
}
/**
* {@inheritdoc}
*/
public function title($message)
{
$this->autoPrependBlock();
$this->writeln(array(
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(array(
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) : array($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=white;bg=red', ' ', 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();
}
/**
* {@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];
}
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)
{
parent::writeln($messages, $type);
$this->bufferedOutput->writeln($this->reduceBuffer($messages), $type);
}
/**
* {@inheritdoc}
*/
public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL)
{
parent::write($messages, $newline, $type);
$this->bufferedOutput->write($this->reduceBuffer($messages), $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());
}
/**
* @return ProgressBar
*/
private function getProgressBar()
{
if (!$this->progressBar) {
throw new RuntimeException('The ProgressBar is not started.');
}
return $this->progressBar;
}
private function autoPrependBlock()
{
$chars = substr(str_replace(PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2);
if (!isset($chars[0])) {
return $this->newLine(); //empty history, so we should start with a new line.
}
//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()
{
$fetched = $this->bufferedOutput->fetch();
//Prepend new line if last char isn't EOL:
if ("\n" !== substr($fetched, -1)) {
$this->newLine();
}
}
private function reduceBuffer($messages)
{
// We need to know if the two last chars are PHP_EOL
// Preserve the last 4 chars inserted (PHP_EOL on windows is two chars) in the history buffer
return array_map(function ($value) {
return substr($value, -4);
}, array_merge(array($this->bufferedOutput->fetch()), (array) $messages));
}
private function createBlock($messages, $type = null, $style = null, $prefix = ' ', $padding = false, $escape = false)
{
$indentLength = 0;
$prefixLength = Helper::strlenWithoutDecoration($this->getFormatter(), $prefix);
$lines = array();
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);
}
$lines = array_merge($lines, explode(PHP_EOL, wordwrap($message, $this->lineLength - $prefixLength - $indentLength, PHP_EOL, true)));
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(' ', $this->lineLength - Helper::strlenWithoutDecoration($this->getFormatter(), $line));
if ($style) {
$line = sprintf('<%s>%s</>', $style, $line);
}
}
return $lines;
}
}

137
vendor/symfony/console/Terminal.php vendored Normal file
View File

@@ -0,0 +1,137 @@
<?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;
/**
* 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;
}
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 (null !== $dimensions = self::getConsoleMode()) {
// extract [w, h] from "wxh"
self::$width = (int) $dimensions[0];
self::$height = (int) $dimensions[1];
}
} elseif ($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()
{
if (!\function_exists('proc_open')) {
return;
}
$descriptorspec = array(
1 => array('pipe', 'w'),
2 => array('pipe', 'w'),
);
$process = proc_open('mode CON', $descriptorspec, $pipes, null, null, array('suppress_errors' => true));
if (\is_resource($process)) {
$info = stream_get_contents($pipes[1]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) {
return array((int) $matches[2], (int) $matches[1]);
}
}
}
/**
* Runs and parses stty -a if it's available, suppressing any error output.
*
* @return string|null
*/
private static function getSttyColumns()
{
if (!\function_exists('proc_open')) {
return;
}
$descriptorspec = array(
1 => array('pipe', 'w'),
2 => array('pipe', 'w'),
);
$process = proc_open('stty -a | grep columns', $descriptorspec, $pipes, null, null, array('suppress_errors' => true));
if (\is_resource($process)) {
$info = stream_get_contents($pipes[1]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
return $info;
}
}
}

View File

@@ -0,0 +1,176 @@
<?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;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\StreamOutput;
/**
* 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
{
private $application;
private $input;
private $statusCode;
/**
* @var OutputInterface
*/
private $output;
private $captureStreamsIndependently = false;
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 = array())
{
$this->input = new ArrayInput($input);
if (isset($options['interactive'])) {
$this->input->setInteractive($options['interactive']);
}
$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(
isset($options['verbosity']) ? $options['verbosity'] : ConsoleOutput::VERBOSITY_NORMAL,
isset($options['decorated']) ? $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 $this->statusCode = $this->application->run($this->input, $this->output);
}
/**
* Gets the display returned by the last execution of the application.
*
* @param bool $normalize Whether to normalize end of lines to \n or not
*
* @return string The display
*/
public function getDisplay($normalize = false)
{
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 application.
*
* @return InputInterface The current input instance
*/
public function getInput()
{
return $this->input;
}
/**
* Gets the output instance used by the last execution of the application.
*
* @return OutputInterface The current output instance
*/
public function getOutput()
{
return $this->output;
}
/**
* Gets the status code returned by the last execution of the application.
*
* @return int The status code
*/
public function getStatusCode()
{
return $this->statusCode;
}
}

View File

@@ -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\Tester;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\StreamOutput;
/**
* Eases the testing of console commands.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Robin Chalas <robin.chalas@gmail.com>
*/
class CommandTester
{
private $command;
private $input;
private $output;
private $inputs = array();
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
*
* @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 = array())
{
// 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(array('command' => $this->command->getName()), $input);
}
$this->input = new ArrayInput($input);
if ($this->inputs) {
$this->input->setStream(self::createStream($this->inputs));
}
if (isset($options['interactive'])) {
$this->input->setInteractive($options['interactive']);
}
$this->output = new StreamOutput(fopen('php://memory', 'w', false));
$this->output->setDecorated(isset($options['decorated']) ? $options['decorated'] : false);
if (isset($options['verbosity'])) {
$this->output->setVerbosity($options['verbosity']);
}
return $this->statusCode = $this->command->run($this->input, $this->output);
}
/**
* Gets the display returned by the last execution of the command.
*
* @param bool $normalize Whether to normalize end of lines to \n or not
*
* @return string The display
*/
public function getDisplay($normalize = false)
{
rewind($this->output->getStream());
$display = stream_get_contents($this->output->getStream());
if ($normalize) {
$display = str_replace(PHP_EOL, "\n", $display);
}
return $display;
}
/**
* Gets the input instance used by the last execution of the command.
*
* @return InputInterface The current input instance
*/
public function getInput()
{
return $this->input;
}
/**
* Gets the output instance used by the last execution of the command.
*
* @return OutputInterface The current output instance
*/
public function getOutput()
{
return $this->output;
}
/**
* Gets the status code returned by the last execution of the 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 CommandTester
*/
public function setInputs(array $inputs)
{
$this->inputs = $inputs;
return $this;
}
private static function createStream(array $inputs)
{
$stream = fopen('php://memory', 'r+', false);
fwrite($stream, implode(PHP_EOL, $inputs));
rewind($stream);
return $stream;
}
}

Some files were not shown because too many files have changed in this diff Show More