first commit
This commit is contained in:
692
vendor/symfony/browser-kit/AbstractBrowser.php
vendored
Normal file
692
vendor/symfony/browser-kit/AbstractBrowser.php
vendored
Normal file
@ -0,0 +1,692 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@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\BrowserKit\Exception\BadMethodCallException;
|
||||
use Symfony\Component\BrowserKit\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\BrowserKit\Exception\LogicException;
|
||||
use Symfony\Component\BrowserKit\Exception\RuntimeException;
|
||||
use Symfony\Component\DomCrawler\Crawler;
|
||||
use Symfony\Component\DomCrawler\Form;
|
||||
use Symfony\Component\DomCrawler\Link;
|
||||
use Symfony\Component\Process\PhpProcess;
|
||||
|
||||
/**
|
||||
* 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 AbstractBrowser
|
||||
{
|
||||
protected $history;
|
||||
protected $cookieJar;
|
||||
protected $server = [];
|
||||
protected $internalRequest;
|
||||
protected $request;
|
||||
protected $internalResponse;
|
||||
protected $response;
|
||||
protected $crawler;
|
||||
protected bool $useHtml5Parser = true;
|
||||
protected $insulated = false;
|
||||
protected $redirect;
|
||||
protected $followRedirects = true;
|
||||
protected $followMetaRefresh = false;
|
||||
|
||||
private int $maxRedirects = -1;
|
||||
private int $redirectCount = 0;
|
||||
private array $redirects = [];
|
||||
private bool $isMainRequest = true;
|
||||
|
||||
/**
|
||||
* @param array $server The server parameters (equivalent of $_SERVER)
|
||||
*/
|
||||
public function __construct(array $server = [], ?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.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function followRedirects(bool $followRedirects = true)
|
||||
{
|
||||
$this->followRedirects = $followRedirects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to automatically follow meta refresh redirects or not.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function followMetaRefresh(bool $followMetaRefresh = true)
|
||||
{
|
||||
$this->followMetaRefresh = $followMetaRefresh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether client automatically follows redirects or not.
|
||||
*/
|
||||
public function isFollowingRedirects(): bool
|
||||
{
|
||||
return $this->followRedirects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum number of redirects that crawler can follow.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setMaxRedirects(int $maxRedirects)
|
||||
{
|
||||
$this->maxRedirects = $maxRedirects < 0 ? -1 : $maxRedirects;
|
||||
$this->followRedirects = -1 !== $this->maxRedirects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum number of redirects that crawler can follow.
|
||||
*/
|
||||
public function getMaxRedirects(): int
|
||||
{
|
||||
return $this->maxRedirects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the insulated flag.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws LogicException When Symfony Process Component is not installed
|
||||
*/
|
||||
public function insulate(bool $insulated = true)
|
||||
{
|
||||
if ($insulated && !class_exists(\Symfony\Component\Process\Process::class)) {
|
||||
throw new LogicException('Unable to isolate requests as the Symfony Process Component is not installed. Try running "composer require symfony/process".');
|
||||
}
|
||||
|
||||
$this->insulated = $insulated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets server parameters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setServerParameters(array $server)
|
||||
{
|
||||
$this->server = array_merge([
|
||||
'HTTP_USER_AGENT' => 'Symfony BrowserKit',
|
||||
], $server);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets single server parameter.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setServerParameter(string $key, string $value)
|
||||
{
|
||||
$this->server[$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets single server parameter for specified key.
|
||||
*/
|
||||
public function getServerParameter(string $key, mixed $default = ''): mixed
|
||||
{
|
||||
return $this->server[$key] ?? $default;
|
||||
}
|
||||
|
||||
public function xmlHttpRequest(string $method, string $uri, array $parameters = [], array $files = [], array $server = [], ?string $content = null, bool $changeHistory = true): Crawler
|
||||
{
|
||||
$this->setServerParameter('HTTP_X_REQUESTED_WITH', 'XMLHttpRequest');
|
||||
|
||||
try {
|
||||
return $this->request($method, $uri, $parameters, $files, $server, $content, $changeHistory);
|
||||
} finally {
|
||||
unset($this->server['HTTP_X_REQUESTED_WITH']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the request parameters into a JSON string and uses it as request content.
|
||||
*/
|
||||
public function jsonRequest(string $method, string $uri, array $parameters = [], array $server = [], bool $changeHistory = true): Crawler
|
||||
{
|
||||
$content = json_encode($parameters, \JSON_PRESERVE_ZERO_FRACTION);
|
||||
|
||||
$this->setServerParameter('CONTENT_TYPE', 'application/json');
|
||||
$this->setServerParameter('HTTP_ACCEPT', 'application/json');
|
||||
|
||||
try {
|
||||
return $this->request($method, $uri, [], [], $server, $content, $changeHistory);
|
||||
} finally {
|
||||
unset($this->server['CONTENT_TYPE']);
|
||||
unset($this->server['HTTP_ACCEPT']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the History instance.
|
||||
*/
|
||||
public function getHistory(): History
|
||||
{
|
||||
return $this->history;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the CookieJar instance.
|
||||
*/
|
||||
public function getCookieJar(): CookieJar
|
||||
{
|
||||
return $this->cookieJar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current Crawler instance.
|
||||
*/
|
||||
public function getCrawler(): Crawler
|
||||
{
|
||||
return $this->crawler ?? throw new BadMethodCallException(\sprintf('The "request()" method must be called before "%s()".', __METHOD__));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether parsing should be done using "masterminds/html5".
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function useHtml5Parser(bool $useHtml5Parser): static
|
||||
{
|
||||
$this->useHtml5Parser = $useHtml5Parser;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current BrowserKit Response instance.
|
||||
*/
|
||||
public function getInternalResponse(): Response
|
||||
{
|
||||
return $this->internalResponse ?? throw new BadMethodCallException(\sprintf('The "request()" method must be called before "%s()".', __METHOD__));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current origin response instance.
|
||||
*
|
||||
* The origin response is the response instance that is returned
|
||||
* by the code that handles requests.
|
||||
*
|
||||
* @see doRequest()
|
||||
*/
|
||||
public function getResponse(): object
|
||||
{
|
||||
return $this->response ?? throw new BadMethodCallException(\sprintf('The "request()" method must be called before "%s()".', __METHOD__));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current BrowserKit Request instance.
|
||||
*/
|
||||
public function getInternalRequest(): Request
|
||||
{
|
||||
return $this->internalRequest ?? throw new BadMethodCallException(\sprintf('The "request()" method must be called before "%s()".', __METHOD__));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current origin Request instance.
|
||||
*
|
||||
* The origin request is the request instance that is sent
|
||||
* to the code that handles requests.
|
||||
*
|
||||
* @see doRequest()
|
||||
*/
|
||||
public function getRequest(): object
|
||||
{
|
||||
return $this->request ?? throw new BadMethodCallException(\sprintf('The "request()" method must be called before "%s()".', __METHOD__));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks on a given link.
|
||||
*
|
||||
* @param array $serverParameters An array of server parameters
|
||||
*/
|
||||
public function click(Link $link/* , array $serverParameters = [] */): Crawler
|
||||
{
|
||||
$serverParameters = 1 < \func_num_args() ? func_get_arg(1) : [];
|
||||
|
||||
if ($link instanceof Form) {
|
||||
return $this->submit($link, [], $serverParameters);
|
||||
}
|
||||
|
||||
return $this->request($link->getMethod(), $link->getUri(), [], [], $serverParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks the first link (or clickable image) that contains the given text.
|
||||
*
|
||||
* @param string $linkText The text of the link or the alt attribute of the clickable image
|
||||
* @param array $serverParameters An array of server parameters
|
||||
*/
|
||||
public function clickLink(string $linkText/* , array $serverParameters = [] */): Crawler
|
||||
{
|
||||
$serverParameters = 1 < \func_num_args() ? func_get_arg(1) : [];
|
||||
|
||||
$crawler = $this->crawler ?? throw new BadMethodCallException(\sprintf('The "request()" method must be called before "%s()".', __METHOD__));
|
||||
|
||||
return $this->click($crawler->selectLink($linkText)->link(), $serverParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits a form.
|
||||
*
|
||||
* @param array $values An array of form field values
|
||||
* @param array $serverParameters An array of server parameters
|
||||
*/
|
||||
public function submit(Form $form, array $values = [], array $serverParameters = []): Crawler
|
||||
{
|
||||
$form->setValues($values);
|
||||
|
||||
return $this->request($form->getMethod(), $form->getUri(), $form->getPhpValues(), $form->getPhpFiles(), $serverParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the first form that contains a button with the given content and
|
||||
* uses it to submit the given form field values.
|
||||
*
|
||||
* @param string $button The text content, id, value or name of the form <button> or <input type="submit">
|
||||
* @param array $fieldValues Use this syntax: ['my_form[name]' => '...', 'my_form[email]' => '...']
|
||||
* @param string $method The HTTP method used to submit the form
|
||||
* @param array $serverParameters These values override the ones stored in $_SERVER (HTTP headers must include an HTTP_ prefix as PHP does)
|
||||
*/
|
||||
public function submitForm(string $button, array $fieldValues = [], string $method = 'POST', array $serverParameters = []): Crawler
|
||||
{
|
||||
$crawler = $this->crawler ?? throw new BadMethodCallException(\sprintf('The "request()" method must be called before "%s()".', __METHOD__));
|
||||
$buttonNode = $crawler->selectButton($button);
|
||||
|
||||
if (0 === $buttonNode->count()) {
|
||||
throw new InvalidArgumentException(\sprintf('There is no button with "%s" as its content, id, value or name.', $button));
|
||||
}
|
||||
|
||||
$form = $buttonNode->form($fieldValues, $method);
|
||||
|
||||
return $this->submit($form, [], $serverParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 an 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())
|
||||
*/
|
||||
public function request(string $method, string $uri, array $parameters = [], array $files = [], array $server = [], ?string $content = null, bool $changeHistory = true): Crawler
|
||||
{
|
||||
if ($this->isMainRequest) {
|
||||
$this->redirectCount = 0;
|
||||
} else {
|
||||
++$this->redirectCount;
|
||||
}
|
||||
|
||||
$originalUri = $uri;
|
||||
|
||||
$uri = $this->getAbsoluteUri($uri);
|
||||
|
||||
$server = array_merge($this->server, $server);
|
||||
|
||||
if (!empty($server['HTTP_HOST']) && !parse_url($originalUri, \PHP_URL_HOST)) {
|
||||
$uri = preg_replace('{^(https?\://)'.preg_quote($this->extractHost($uri)).'}', '${1}'.$server['HTTP_HOST'], $uri);
|
||||
}
|
||||
|
||||
if (isset($server['HTTPS']) && !parse_url($originalUri, \PHP_URL_SCHEME)) {
|
||||
$uri = preg_replace('{^'.parse_url($uri, \PHP_URL_SCHEME).'}', $server['HTTPS'] ? 'https' : 'http', $uri);
|
||||
}
|
||||
|
||||
if (!isset($server['HTTP_REFERER']) && !$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->getStatusCode();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
$this->crawler = $this->createCrawlerFromContent($this->internalRequest->getUri(), $this->internalResponse->getContent(), $this->internalResponse->getHeader('Content-Type') ?? '');
|
||||
|
||||
// Check for meta refresh redirect
|
||||
if ($this->followMetaRefresh && null !== $redirect = $this->getMetaRefreshUrl()) {
|
||||
$this->redirect = $redirect;
|
||||
$this->redirects[serialize($this->history->current())] = true;
|
||||
$this->crawler = $this->followRedirect();
|
||||
}
|
||||
|
||||
return $this->crawler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a request in another process.
|
||||
*
|
||||
* @return object
|
||||
*
|
||||
* @throws \RuntimeException When processing returns exit code
|
||||
*/
|
||||
protected function doRequestInProcess(object $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) : [] as $deprecation) {
|
||||
if ($deprecation[0]) {
|
||||
// unsilenced on purpose
|
||||
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.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
abstract protected function doRequest(object $request);
|
||||
|
||||
/**
|
||||
* Returns the script to execute when the request must be insulated.
|
||||
*
|
||||
* @param object $request An origin request instance
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws LogicException When this abstract class is not implemented
|
||||
*/
|
||||
protected function getScript(object $request)
|
||||
{
|
||||
throw new LogicException('To insulate requests, you need to override the getScript() method.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the BrowserKit request to the origin one.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
protected function filterRequest(Request $request)
|
||||
{
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the origin response to the BrowserKit one.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
protected function filterResponse(object $response)
|
||||
{
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a crawler.
|
||||
*
|
||||
* This method returns null if the DomCrawler component is not available.
|
||||
*/
|
||||
protected function createCrawlerFromContent(string $uri, string $content, string $type): ?Crawler
|
||||
{
|
||||
if (!class_exists(Crawler::class)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$crawler = new Crawler(null, $uri, null, $this->useHtml5Parser);
|
||||
$crawler->addContent($content, $type);
|
||||
|
||||
return $crawler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Goes back in the browser history.
|
||||
*/
|
||||
public function back(): Crawler
|
||||
{
|
||||
do {
|
||||
$request = $this->history->back();
|
||||
} while (\array_key_exists(serialize($request), $this->redirects));
|
||||
|
||||
return $this->requestFromRequest($request, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Goes forward in the browser history.
|
||||
*/
|
||||
public function forward(): Crawler
|
||||
{
|
||||
do {
|
||||
$request = $this->history->forward();
|
||||
} while (\array_key_exists(serialize($request), $this->redirects));
|
||||
|
||||
return $this->requestFromRequest($request, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the current browser.
|
||||
*/
|
||||
public function reload(): Crawler
|
||||
{
|
||||
return $this->requestFromRequest($this->history->current(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Follow redirects?
|
||||
*
|
||||
* @throws LogicException If request was not a redirect
|
||||
*/
|
||||
public function followRedirect(): Crawler
|
||||
{
|
||||
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->getStatusCode(), [301, 302, 303])) {
|
||||
$method = 'GET';
|
||||
$files = [];
|
||||
$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 = [];
|
||||
} 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://dev.w3.org/html5/spec-preview/the-meta-element.html#attr-meta-http-equiv-refresh
|
||||
*/
|
||||
private function getMetaRefreshUrl(): ?string
|
||||
{
|
||||
$metaRefresh = $this->getCrawler()->filter('head meta[http-equiv="refresh"]');
|
||||
foreach ($metaRefresh->extract(['content']) as $content) {
|
||||
if (preg_match('/^\s*0\s*;\s*URL\s*=\s*(?|\'([^\']++)|"([^"]++)|([^\'"].*))/i', $content, $m)) {
|
||||
return str_replace("\t\r\n", '', rtrim($m[1]));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restarts the client.
|
||||
*
|
||||
* It flushes history and all cookies.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function restart()
|
||||
{
|
||||
$this->cookieJar->clear();
|
||||
$this->history->clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a URI and converts it to absolute if it is not already absolute.
|
||||
*/
|
||||
protected function getAbsoluteUri(string $uri): string
|
||||
{
|
||||
// already absolute?
|
||||
if (str_starts_with($uri, 'http://') || str_starts_with($uri, 'https://')) {
|
||||
return $uri;
|
||||
}
|
||||
|
||||
if (!$this->history->isEmpty()) {
|
||||
$currentUri = $this->history->current()->getUri();
|
||||
} else {
|
||||
$currentUri = \sprintf('http%s://%s/',
|
||||
isset($this->server['HTTPS']) ? 's' : '',
|
||||
$this->server['HTTP_HOST'] ?? 'localhost'
|
||||
);
|
||||
}
|
||||
|
||||
// protocol relative URL
|
||||
if ('' !== trim($uri, '/') && str_starts_with($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 (!str_ends_with($path, '/')) {
|
||||
$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 bool $changeHistory Whether to update the history or not (only used internally for back(), forward(), and reload())
|
||||
*/
|
||||
protected function requestFromRequest(Request $request, bool $changeHistory = true): Crawler
|
||||
{
|
||||
return $this->request($request->getMethod(), $request->getUri(), $request->getParameters(), $request->getFiles(), $request->getServer(), $request->getContent(), $changeHistory);
|
||||
}
|
||||
|
||||
private function updateServerFromUri(array $server, string $uri): array
|
||||
{
|
||||
$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(string $uri): ?string
|
||||
{
|
||||
$host = parse_url($uri, \PHP_URL_HOST);
|
||||
|
||||
if ($port = parse_url($uri, \PHP_URL_PORT)) {
|
||||
return $host.':'.$port;
|
||||
}
|
||||
|
||||
return $host;
|
||||
}
|
||||
}
|
||||
78
vendor/symfony/browser-kit/CHANGELOG.md
vendored
Normal file
78
vendor/symfony/browser-kit/CHANGELOG.md
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
6.4
|
||||
---
|
||||
|
||||
* Add argument `$serverParameters` to `AbstractBrowser::click()` and `AbstractBrowser::clickLink()`
|
||||
|
||||
6.3
|
||||
---
|
||||
|
||||
* Add `AbstractBrowser::useHtml5Parser()`
|
||||
|
||||
6.1
|
||||
---
|
||||
|
||||
* Add `toArray` method to `Response`
|
||||
|
||||
5.3
|
||||
---
|
||||
|
||||
* Added `jsonRequest` method to `AbstractBrowser`
|
||||
* Allowed sending a body with GET requests when a content-type is defined
|
||||
|
||||
5.2.0
|
||||
-----
|
||||
|
||||
* [BC BREAK] Request parameters are now casted to string in `Request::__construct()`.
|
||||
|
||||
4.3.0
|
||||
-----
|
||||
|
||||
* Added PHPUnit constraints: `BrowserCookieValueSame` and `BrowserHasCookie`
|
||||
* Added `HttpBrowser`, an implementation of a browser with the HttpClient component
|
||||
* Renamed `Client` to `AbstractBrowser`
|
||||
* Marked `Response` final.
|
||||
* Deprecated `Response::buildHeader()`
|
||||
* Deprecated `Response::getStatus()`, use `Response::getStatusCode()` instead
|
||||
|
||||
4.2.0
|
||||
-----
|
||||
|
||||
* The method `Client::submit()` will have a new `$serverParameters` argument
|
||||
in version 5.0, not defining it is deprecated
|
||||
* Added ability to read the "samesite" attribute of cookies using `Cookie::getSameSite()`
|
||||
|
||||
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
|
||||
300
vendor/symfony/browser-kit/Cookie.php
vendored
Normal file
300
vendor/symfony/browser-kit/Cookie.php
vendored
Normal file
@ -0,0 +1,300 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@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\BrowserKit\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\BrowserKit\Exception\UnexpectedValueException;
|
||||
|
||||
/**
|
||||
* 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 const DATE_FORMATS = [
|
||||
'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;
|
||||
private ?string $samesite;
|
||||
|
||||
/**
|
||||
* Sets a cookie.
|
||||
*
|
||||
* @param string $name The cookie name
|
||||
* @param string|null $value The value of the cookie
|
||||
* @param string|int|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
|
||||
* @param string|null $samesite The cookie samesite attribute
|
||||
*/
|
||||
public function __construct(string $name, ?string $value, string|int|null $expires = null, ?string $path = null, string $domain = '', bool $secure = false, bool $httponly = true, bool $encodedValue = false, ?string $samesite = null)
|
||||
{
|
||||
if ($encodedValue) {
|
||||
$this->rawValue = $value ?? '';
|
||||
$this->value = urldecode($this->rawValue);
|
||||
} else {
|
||||
$this->value = $value ?? '';
|
||||
$this->rawValue = rawurlencode($this->value);
|
||||
}
|
||||
$this->name = $name;
|
||||
$this->path = empty($path) ? '/' : $path;
|
||||
$this->domain = $domain;
|
||||
$this->secure = $secure;
|
||||
$this->httponly = $httponly;
|
||||
$this->samesite = $samesite;
|
||||
|
||||
if (null !== $expires) {
|
||||
$timestampAsDateTime = \DateTimeImmutable::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(): string
|
||||
{
|
||||
$cookie = \sprintf('%s=%s', $this->name, $this->rawValue);
|
||||
|
||||
if (null !== $this->expires) {
|
||||
$dateTime = \DateTimeImmutable::createFromFormat('U', $this->expires, new \DateTimeZone('GMT'));
|
||||
$cookie .= '; expires='.str_replace('+0000', '', $dateTime->format(self::DATE_FORMATS[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';
|
||||
}
|
||||
|
||||
if (null !== $this->samesite) {
|
||||
$cookie .= '; samesite='.$this->samesite;
|
||||
}
|
||||
|
||||
return $cookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Cookie instance from a Set-Cookie header value.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function fromString(string $cookie, ?string $url = null): static
|
||||
{
|
||||
$parts = explode(';', $cookie);
|
||||
|
||||
if (!str_contains($parts[0], '=')) {
|
||||
throw new InvalidArgumentException(\sprintf('The cookie string "%s" is not valid.', $parts[0]));
|
||||
}
|
||||
|
||||
[$name, $value] = explode('=', array_shift($parts), 2);
|
||||
|
||||
$values = [
|
||||
'name' => trim($name),
|
||||
'value' => trim($value),
|
||||
'expires' => null,
|
||||
'path' => '/',
|
||||
'domain' => '',
|
||||
'secure' => false,
|
||||
'httponly' => false,
|
||||
'passedRawValue' => true,
|
||||
'samesite' => null,
|
||||
];
|
||||
|
||||
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 (null === $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'],
|
||||
$values['samesite']
|
||||
);
|
||||
}
|
||||
|
||||
private static function parseDate(string $dateValue): ?string
|
||||
{
|
||||
// trim single quotes around date if present
|
||||
if (($length = \strlen($dateValue)) > 1 && "'" === $dateValue[0] && "'" === $dateValue[$length - 1]) {
|
||||
$dateValue = substr($dateValue, 1, -1);
|
||||
}
|
||||
|
||||
foreach (self::DATE_FORMATS as $dateFormat) {
|
||||
if (false !== $date = \DateTimeImmutable::createFromFormat($dateFormat, $dateValue, new \DateTimeZone('GMT'))) {
|
||||
return $date->format('U');
|
||||
}
|
||||
}
|
||||
|
||||
// attempt a fallback for unusual formatting
|
||||
if (false !== $date = date_create_immutable($dateValue, new \DateTimeZone('GMT'))) {
|
||||
return $date->format('U');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the cookie.
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the cookie.
|
||||
*/
|
||||
public function getValue(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the raw value of the cookie.
|
||||
*/
|
||||
public function getRawValue(): string
|
||||
{
|
||||
return $this->rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the expires time of the cookie.
|
||||
*/
|
||||
public function getExpiresTime(): ?string
|
||||
{
|
||||
return $this->expires;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path of the cookie.
|
||||
*/
|
||||
public function getPath(): string
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the domain of the cookie.
|
||||
*/
|
||||
public function getDomain(): string
|
||||
{
|
||||
return $this->domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the secure flag of the cookie.
|
||||
*/
|
||||
public function isSecure(): bool
|
||||
{
|
||||
return $this->secure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the httponly flag of the cookie.
|
||||
*/
|
||||
public function isHttpOnly(): bool
|
||||
{
|
||||
return $this->httponly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the cookie has expired.
|
||||
*/
|
||||
public function isExpired(): bool
|
||||
{
|
||||
return null !== $this->expires && 0 != $this->expires && $this->expires <= time();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the samesite attribute of the cookie.
|
||||
*/
|
||||
public function getSameSite(): ?string
|
||||
{
|
||||
return $this->samesite;
|
||||
}
|
||||
}
|
||||
231
vendor/symfony/browser-kit/CookieJar.php
vendored
Normal file
231
vendor/symfony/browser-kit/CookieJar.php
vendored
Normal file
@ -0,0 +1,231 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\BrowserKit;
|
||||
|
||||
use Symfony\Component\BrowserKit\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* CookieJar.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class CookieJar
|
||||
{
|
||||
protected $cookieJar = [];
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
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).
|
||||
*/
|
||||
public function get(string $name, string $path = '/', ?string $domain = null): ?Cookie
|
||||
{
|
||||
$this->flushExpiredCookies();
|
||||
|
||||
foreach ($this->cookieJar as $cookieDomain => $pathCookies) {
|
||||
if ($cookieDomain && $domain) {
|
||||
$cookieDomain = '.'.ltrim($cookieDomain, '.');
|
||||
if (!str_ends_with('.'.$domain, $cookieDomain)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($pathCookies as $cookiePath => $namedCookies) {
|
||||
if (!str_starts_with($path, $cookiePath)) {
|
||||
continue;
|
||||
}
|
||||
if (isset($namedCookies[$name])) {
|
||||
return $namedCookies[$name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function expire(string $name, ?string $path = '/', ?string $domain = null)
|
||||
{
|
||||
$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 = [$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.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
$this->cookieJar = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the cookie jar from a response Set-Cookie headers.
|
||||
*
|
||||
* @param string[] $setCookies Set-Cookie headers from an HTTP response
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function updateFromSetCookie(array $setCookies, ?string $uri = null)
|
||||
{
|
||||
$cookies = [];
|
||||
|
||||
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) {
|
||||
// invalid cookies are just ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the cookie jar from a Response object.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function updateFromResponse(Response $response, ?string $uri = null)
|
||||
{
|
||||
$this->updateFromSetCookie($response->getHeader('Set-Cookie', false), $uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns not yet expired cookies.
|
||||
*
|
||||
* @return Cookie[]
|
||||
*/
|
||||
public function all(): array
|
||||
{
|
||||
$this->flushExpiredCookies();
|
||||
|
||||
$flattenedCookies = [];
|
||||
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.
|
||||
*/
|
||||
public function allValues(string $uri, bool $returnsRawValue = false): array
|
||||
{
|
||||
$this->flushExpiredCookies();
|
||||
|
||||
$parts = array_replace(['path' => '/'], parse_url($uri));
|
||||
$cookies = [];
|
||||
foreach ($this->cookieJar as $domain => $pathCookies) {
|
||||
if ($domain) {
|
||||
$domain = '.'.ltrim($domain, '.');
|
||||
if (!str_ends_with('.'.$parts['host'], $domain)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($pathCookies as $path => $namedCookies) {
|
||||
if (!str_starts_with($parts['path'], $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.
|
||||
*/
|
||||
public function allRawValues(string $uri): array
|
||||
{
|
||||
return $this->allValues($uri, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all expired cookies.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
vendor/symfony/browser-kit/Exception/BadMethodCallException.php
vendored
Normal file
16
vendor/symfony/browser-kit/Exception/BadMethodCallException.php
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\BrowserKit\Exception;
|
||||
|
||||
class BadMethodCallException extends \BadMethodCallException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
21
vendor/symfony/browser-kit/Exception/ExceptionInterface.php
vendored
Normal file
21
vendor/symfony/browser-kit/Exception/ExceptionInterface.php
vendored
Normal 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\BrowserKit\Exception;
|
||||
|
||||
/**
|
||||
* Base ExceptionInterface for the BrowserKit component.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
interface ExceptionInterface extends \Throwable
|
||||
{
|
||||
}
|
||||
16
vendor/symfony/browser-kit/Exception/InvalidArgumentException.php
vendored
Normal file
16
vendor/symfony/browser-kit/Exception/InvalidArgumentException.php
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\BrowserKit\Exception;
|
||||
|
||||
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
16
vendor/symfony/browser-kit/Exception/JsonException.php
vendored
Normal file
16
vendor/symfony/browser-kit/Exception/JsonException.php
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\BrowserKit\Exception;
|
||||
|
||||
class JsonException extends \JsonException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
16
vendor/symfony/browser-kit/Exception/LogicException.php
vendored
Normal file
16
vendor/symfony/browser-kit/Exception/LogicException.php
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\BrowserKit\Exception;
|
||||
|
||||
class LogicException extends \LogicException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
16
vendor/symfony/browser-kit/Exception/RuntimeException.php
vendored
Normal file
16
vendor/symfony/browser-kit/Exception/RuntimeException.php
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\BrowserKit\Exception;
|
||||
|
||||
class RuntimeException extends \RuntimeException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
16
vendor/symfony/browser-kit/Exception/UnexpectedValueException.php
vendored
Normal file
16
vendor/symfony/browser-kit/Exception/UnexpectedValueException.php
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\BrowserKit\Exception;
|
||||
|
||||
class UnexpectedValueException extends \UnexpectedValueException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
98
vendor/symfony/browser-kit/History.php
vendored
Normal file
98
vendor/symfony/browser-kit/History.php
vendored
Normal file
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\BrowserKit;
|
||||
|
||||
use Symfony\Component\BrowserKit\Exception\LogicException;
|
||||
|
||||
/**
|
||||
* History.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class History
|
||||
{
|
||||
protected $stack = [];
|
||||
protected $position = -1;
|
||||
|
||||
/**
|
||||
* Clears the history.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
$this->stack = [];
|
||||
$this->position = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a Request to the history.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return 0 === \count($this->stack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Goes back in the history.
|
||||
*
|
||||
* @throws LogicException if the stack is already on the first page
|
||||
*/
|
||||
public function back(): Request
|
||||
{
|
||||
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.
|
||||
*
|
||||
* @throws LogicException if the stack is already on the last page
|
||||
*/
|
||||
public function forward(): Request
|
||||
{
|
||||
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.
|
||||
*
|
||||
* @throws LogicException if the stack is empty
|
||||
*/
|
||||
public function current(): Request
|
||||
{
|
||||
if (-1 === $this->position) {
|
||||
throw new LogicException('The page history is empty.');
|
||||
}
|
||||
|
||||
return clone $this->stack[$this->position];
|
||||
}
|
||||
}
|
||||
159
vendor/symfony/browser-kit/HttpBrowser.php
vendored
Normal file
159
vendor/symfony/browser-kit/HttpBrowser.php
vendored
Normal file
@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@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\BrowserKit\Exception\LogicException;
|
||||
use Symfony\Component\HttpClient\HttpClient;
|
||||
use Symfony\Component\Mime\Part\AbstractPart;
|
||||
use Symfony\Component\Mime\Part\DataPart;
|
||||
use Symfony\Component\Mime\Part\Multipart\FormDataPart;
|
||||
use Symfony\Component\Mime\Part\TextPart;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
/**
|
||||
* An implementation of a browser using the HttpClient component
|
||||
* to make real HTTP requests.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class HttpBrowser extends AbstractBrowser
|
||||
{
|
||||
private HttpClientInterface $client;
|
||||
|
||||
public function __construct(?HttpClientInterface $client = null, ?History $history = null, ?CookieJar $cookieJar = null)
|
||||
{
|
||||
if (!$client && !class_exists(HttpClient::class)) {
|
||||
throw new LogicException(\sprintf('You cannot use "%s" as the HttpClient component is not installed. Try running "composer require symfony/http-client".', __CLASS__));
|
||||
}
|
||||
|
||||
$this->client = $client ?? HttpClient::create();
|
||||
|
||||
parent::__construct([], $history, $cookieJar);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
*/
|
||||
protected function doRequest(object $request): Response
|
||||
{
|
||||
$headers = $this->getHeaders($request);
|
||||
[$body, $extraHeaders] = $this->getBodyAndExtraHeaders($request, $headers);
|
||||
|
||||
$response = $this->client->request($request->getMethod(), $request->getUri(), [
|
||||
'headers' => array_merge($headers, $extraHeaders),
|
||||
'body' => $body,
|
||||
'max_redirects' => 0,
|
||||
]);
|
||||
|
||||
return new Response($response->getContent(false), $response->getStatusCode(), $response->getHeaders(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array [$body, $headers]
|
||||
*/
|
||||
private function getBodyAndExtraHeaders(Request $request, array $headers): array
|
||||
{
|
||||
if (\in_array($request->getMethod(), ['GET', 'HEAD']) && !isset($headers['content-type'])) {
|
||||
return ['', []];
|
||||
}
|
||||
|
||||
if (!class_exists(AbstractPart::class)) {
|
||||
throw new LogicException('You cannot pass non-empty bodies as the Mime component is not installed. Try running "composer require symfony/mime".');
|
||||
}
|
||||
|
||||
if (null !== $content = $request->getContent()) {
|
||||
if (isset($headers['content-type'])) {
|
||||
return [$content, []];
|
||||
}
|
||||
|
||||
$part = new TextPart($content, 'utf-8', 'plain', '8bit');
|
||||
|
||||
return [$part->bodyToString(), $part->getPreparedHeaders()->toArray()];
|
||||
}
|
||||
|
||||
$fields = $request->getParameters();
|
||||
|
||||
if ($uploadedFiles = $this->getUploadedFiles($request->getFiles())) {
|
||||
$part = new FormDataPart(array_replace_recursive($fields, $uploadedFiles));
|
||||
|
||||
return [$part->bodyToIterable(), $part->getPreparedHeaders()->toArray()];
|
||||
}
|
||||
|
||||
if (!$fields) {
|
||||
return ['', []];
|
||||
}
|
||||
|
||||
array_walk_recursive($fields, $caster = static function (&$v) use (&$caster) {
|
||||
if (\is_object($v)) {
|
||||
if ($vars = get_object_vars($v)) {
|
||||
array_walk_recursive($vars, $caster);
|
||||
$v = $vars;
|
||||
} elseif (method_exists($v, '__toString')) {
|
||||
$v = (string) $v;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return [http_build_query($fields, '', '&'), ['Content-Type' => 'application/x-www-form-urlencoded']];
|
||||
}
|
||||
|
||||
protected function getHeaders(Request $request): array
|
||||
{
|
||||
$headers = [];
|
||||
foreach ($request->getServer() as $key => $value) {
|
||||
$key = strtolower(str_replace('_', '-', $key));
|
||||
$contentHeaders = ['content-length' => true, 'content-md5' => true, 'content-type' => true];
|
||||
if (str_starts_with($key, 'http-')) {
|
||||
$headers[substr($key, 5)] = $value;
|
||||
} elseif (isset($contentHeaders[$key])) {
|
||||
// CONTENT_* are not prefixed with HTTP_
|
||||
$headers[$key] = $value;
|
||||
}
|
||||
}
|
||||
$cookies = [];
|
||||
foreach ($this->getCookieJar()->allRawValues($request->getUri()) as $name => $value) {
|
||||
$cookies[] = $name.'='.$value;
|
||||
}
|
||||
if ($cookies) {
|
||||
$headers['cookie'] = implode('; ', $cookies);
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively go through the list. If the file has a tmp_name, convert it to a DataPart.
|
||||
* Keep the original hierarchy.
|
||||
*/
|
||||
private function getUploadedFiles(array $files): array
|
||||
{
|
||||
$uploadedFiles = [];
|
||||
foreach ($files as $name => $file) {
|
||||
if (!\is_array($file)) {
|
||||
return $uploadedFiles;
|
||||
}
|
||||
if (!isset($file['tmp_name'])) {
|
||||
$uploadedFiles[$name] = $this->getUploadedFiles($file);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('' === $file['tmp_name']) {
|
||||
$uploadedFiles[$name] = new DataPart('', '');
|
||||
continue;
|
||||
}
|
||||
|
||||
$uploadedFiles[$name] = DataPart::fromPath($file['tmp_name'], $file['name']);
|
||||
}
|
||||
|
||||
return $uploadedFiles;
|
||||
}
|
||||
}
|
||||
19
vendor/symfony/browser-kit/LICENSE
vendored
Normal file
19
vendor/symfony/browser-kit/LICENSE
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2004-present 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.
|
||||
17
vendor/symfony/browser-kit/README.md
vendored
Normal file
17
vendor/symfony/browser-kit/README.md
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
BrowserKit Component
|
||||
====================
|
||||
|
||||
The BrowserKit component simulates the behavior of a web browser, allowing you
|
||||
to make requests, click on links and submit forms programmatically.
|
||||
|
||||
The component comes with a concrete implementation that uses the HttpClient
|
||||
component to make real HTTP requests.
|
||||
|
||||
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)
|
||||
107
vendor/symfony/browser-kit/Request.php
vendored
Normal file
107
vendor/symfony/browser-kit/Request.php
vendored
Normal 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\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(string $uri, string $method, array $parameters = [], array $files = [], array $cookies = [], array $server = [], ?string $content = null)
|
||||
{
|
||||
$this->uri = $uri;
|
||||
$this->method = $method;
|
||||
|
||||
array_walk_recursive($parameters, static function (&$value) {
|
||||
$value = (string) $value;
|
||||
});
|
||||
|
||||
$this->parameters = $parameters;
|
||||
$this->files = $files;
|
||||
$this->cookies = $cookies;
|
||||
$this->server = $server;
|
||||
$this->content = $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request URI.
|
||||
*/
|
||||
public function getUri(): string
|
||||
{
|
||||
return $this->uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request HTTP method.
|
||||
*/
|
||||
public function getMethod(): string
|
||||
{
|
||||
return $this->method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request parameters.
|
||||
*/
|
||||
public function getParameters(): array
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request server files.
|
||||
*/
|
||||
public function getFiles(): array
|
||||
{
|
||||
return $this->files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request cookies.
|
||||
*/
|
||||
public function getCookies(): array
|
||||
{
|
||||
return $this->cookies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request server parameters.
|
||||
*/
|
||||
public function getServer(): array
|
||||
{
|
||||
return $this->server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request raw body data.
|
||||
*/
|
||||
public function getContent(): ?string
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
}
|
||||
112
vendor/symfony/browser-kit/Response.php
vendored
Normal file
112
vendor/symfony/browser-kit/Response.php
vendored
Normal file
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@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\BrowserKit\Exception\JsonException;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
final class Response
|
||||
{
|
||||
private string $content;
|
||||
private int $status;
|
||||
private array $headers;
|
||||
private array $jsonData;
|
||||
|
||||
/**
|
||||
* 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 (302 "Found" by default)
|
||||
* @param array $headers An array of headers
|
||||
*/
|
||||
public function __construct(string $content = '', int $status = 200, array $headers = [])
|
||||
{
|
||||
$this->content = $content;
|
||||
$this->status = $status;
|
||||
$this->headers = $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the response object to string containing all headers and the response content.
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
$headers = '';
|
||||
foreach ($this->headers as $name => $value) {
|
||||
if (\is_string($value)) {
|
||||
$headers .= \sprintf("%s: %s\n", $name, $value);
|
||||
} else {
|
||||
foreach ($value as $headerValue) {
|
||||
$headers .= \sprintf("%s: %s\n", $name, $headerValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $headers."\n".$this->content;
|
||||
}
|
||||
|
||||
public function getContent(): string
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
public function getStatusCode(): int
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
public function getHeaders(): array
|
||||
{
|
||||
return $this->headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|array|null The first header value if $first is true, an array of values otherwise
|
||||
*/
|
||||
public function getHeader(string $header, bool $first = true): string|array|null
|
||||
{
|
||||
$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 : [$value];
|
||||
}
|
||||
}
|
||||
|
||||
return $first ? null : [];
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
if (isset($this->jsonData)) {
|
||||
return $this->jsonData;
|
||||
}
|
||||
|
||||
try {
|
||||
$content = json_decode($this->content, true, flags: \JSON_BIGINT_AS_STRING | \JSON_THROW_ON_ERROR);
|
||||
} catch (\JsonException $e) {
|
||||
throw new JsonException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
if (!\is_array($content)) {
|
||||
throw new JsonException(\sprintf('JSON content was expected to decode to an array, "%s" returned.', get_debug_type($content)));
|
||||
}
|
||||
|
||||
return $this->jsonData = $content;
|
||||
}
|
||||
}
|
||||
68
vendor/symfony/browser-kit/Test/Constraint/BrowserCookieValueSame.php
vendored
Normal file
68
vendor/symfony/browser-kit/Test/Constraint/BrowserCookieValueSame.php
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\BrowserKit\Test\Constraint;
|
||||
|
||||
use PHPUnit\Framework\Constraint\Constraint;
|
||||
use Symfony\Component\BrowserKit\AbstractBrowser;
|
||||
|
||||
final class BrowserCookieValueSame extends Constraint
|
||||
{
|
||||
private string $name;
|
||||
private string $value;
|
||||
private bool $raw;
|
||||
private string $path;
|
||||
private ?string $domain;
|
||||
|
||||
public function __construct(string $name, string $value, bool $raw = false, string $path = '/', ?string $domain = null)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->path = $path;
|
||||
$this->domain = $domain;
|
||||
$this->value = $value;
|
||||
$this->raw = $raw;
|
||||
}
|
||||
|
||||
public function toString(): string
|
||||
{
|
||||
$str = \sprintf('has cookie "%s"', $this->name);
|
||||
if ('/' !== $this->path) {
|
||||
$str .= \sprintf(' with path "%s"', $this->path);
|
||||
}
|
||||
if ($this->domain) {
|
||||
$str .= \sprintf(' for domain "%s"', $this->domain);
|
||||
}
|
||||
$str .= \sprintf(' with %svalue "%s"', $this->raw ? 'raw ' : '', $this->value);
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractBrowser $browser
|
||||
*/
|
||||
protected function matches($browser): bool
|
||||
{
|
||||
$cookie = $browser->getCookieJar()->get($this->name, $this->path, $this->domain);
|
||||
if (!$cookie) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->value === ($this->raw ? $cookie->getRawValue() : $cookie->getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractBrowser $browser
|
||||
*/
|
||||
protected function failureDescription($browser): string
|
||||
{
|
||||
return 'the Browser '.$this->toString();
|
||||
}
|
||||
}
|
||||
58
vendor/symfony/browser-kit/Test/Constraint/BrowserHasCookie.php
vendored
Normal file
58
vendor/symfony/browser-kit/Test/Constraint/BrowserHasCookie.php
vendored
Normal 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\BrowserKit\Test\Constraint;
|
||||
|
||||
use PHPUnit\Framework\Constraint\Constraint;
|
||||
use Symfony\Component\BrowserKit\AbstractBrowser;
|
||||
|
||||
final class BrowserHasCookie extends Constraint
|
||||
{
|
||||
private string $name;
|
||||
private string $path;
|
||||
private ?string $domain;
|
||||
|
||||
public function __construct(string $name, string $path = '/', ?string $domain = null)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->path = $path;
|
||||
$this->domain = $domain;
|
||||
}
|
||||
|
||||
public function toString(): string
|
||||
{
|
||||
$str = \sprintf('has cookie "%s"', $this->name);
|
||||
if ('/' !== $this->path) {
|
||||
$str .= \sprintf(' with path "%s"', $this->path);
|
||||
}
|
||||
if ($this->domain) {
|
||||
$str .= \sprintf(' for domain "%s"', $this->domain);
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractBrowser $browser
|
||||
*/
|
||||
protected function matches($browser): bool
|
||||
{
|
||||
return null !== $browser->getCookieJar()->get($this->name, $this->path, $this->domain);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractBrowser $browser
|
||||
*/
|
||||
protected function failureDescription($browser): string
|
||||
{
|
||||
return 'the Browser '.$this->toString();
|
||||
}
|
||||
}
|
||||
35
vendor/symfony/browser-kit/composer.json
vendored
Normal file
35
vendor/symfony/browser-kit/composer.json
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "symfony/browser-kit",
|
||||
"type": "library",
|
||||
"description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically",
|
||||
"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": ">=8.1",
|
||||
"symfony/dom-crawler": "^5.4|^6.0|^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/css-selector": "^5.4|^6.0|^7.0",
|
||||
"symfony/http-client": "^5.4|^6.0|^7.0",
|
||||
"symfony/mime": "^5.4|^6.0|^7.0",
|
||||
"symfony/process": "^5.4|^6.0|^7.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Symfony\\Component\\BrowserKit\\": "" },
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"minimum-stability": "dev"
|
||||
}
|
||||
1345
vendor/symfony/console/Application.php
vendored
Normal file
1345
vendor/symfony/console/Application.php
vendored
Normal file
File diff suppressed because it is too large
Load Diff
39
vendor/symfony/console/Attribute/AsCommand.php
vendored
Normal file
39
vendor/symfony/console/Attribute/AsCommand.php
vendored
Normal 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\Attribute;
|
||||
|
||||
/**
|
||||
* Service tag to autoconfigure commands.
|
||||
*/
|
||||
#[\Attribute(\Attribute::TARGET_CLASS)]
|
||||
class AsCommand
|
||||
{
|
||||
public function __construct(
|
||||
public string $name,
|
||||
public ?string $description = null,
|
||||
array $aliases = [],
|
||||
bool $hidden = false,
|
||||
) {
|
||||
if (!$hidden && !$aliases) {
|
||||
return;
|
||||
}
|
||||
|
||||
$name = explode('|', $name);
|
||||
$name = array_merge($name, $aliases);
|
||||
|
||||
if ($hidden && '' !== $name[0]) {
|
||||
array_unshift($name, '');
|
||||
}
|
||||
|
||||
$this->name = implode('|', $name);
|
||||
}
|
||||
}
|
||||
261
vendor/symfony/console/CHANGELOG.md
vendored
Normal file
261
vendor/symfony/console/CHANGELOG.md
vendored
Normal file
@ -0,0 +1,261 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
6.4
|
||||
---
|
||||
|
||||
* Add `SignalMap` to map signal value to its name
|
||||
* Multi-line text in vertical tables is aligned properly
|
||||
* The application can also catch errors with `Application::setCatchErrors(true)`
|
||||
* Add `RunCommandMessage` and `RunCommandMessageHandler`
|
||||
* Dispatch `ConsoleTerminateEvent` after an exit on signal handling and add `ConsoleTerminateEvent::getInterruptingSignal()`
|
||||
|
||||
6.3
|
||||
---
|
||||
|
||||
* Add support for choosing exit code while handling signal, or to not exit at all
|
||||
* Add `ProgressBar::setPlaceholderFormatter` to set a placeholder attached to a instance, instead of being global.
|
||||
* Add `ReStructuredTextDescriptor`
|
||||
|
||||
6.2
|
||||
---
|
||||
|
||||
* Improve truecolor terminal detection in some cases
|
||||
* Add support for 256 color terminals (conversion from Ansi24 to Ansi8 if terminal is capable of it)
|
||||
* Deprecate calling `*Command::setApplication()`, `*FormatterStyle::setForeground/setBackground()`, `Helper::setHelpSet()`, `Input*::setDefault()`, `Question::setAutocompleterCallback/setValidator()`without any arguments
|
||||
* Change the signature of `OutputFormatterStyleInterface::setForeground/setBackground()` to `setForeground/setBackground(?string)`
|
||||
* Change the signature of `HelperInterface::setHelperSet()` to `setHelperSet(?HelperSet)`
|
||||
|
||||
6.1
|
||||
---
|
||||
|
||||
* Add support to display table vertically when calling setVertical()
|
||||
* Add method `__toString()` to `InputInterface`
|
||||
* Added `OutputWrapper` to prevent truncated URL in `SymfonyStyle::createBlock`.
|
||||
* Deprecate `Command::$defaultName` and `Command::$defaultDescription`, use the `AsCommand` attribute instead
|
||||
* Add suggested values for arguments and options in input definition, for input completion
|
||||
* Add `$resumeAt` parameter to `ProgressBar#start()`, so that one can easily 'resume' progress on longer tasks, and still get accurate `getEstimate()` and `getRemaining()` results.
|
||||
|
||||
6.0
|
||||
---
|
||||
|
||||
* `Command::setHidden()` has a default value (`true`) for `$hidden` parameter and is final
|
||||
* Remove `Helper::strlen()`, use `Helper::width()` instead
|
||||
* Remove `Helper::strlenWithoutDecoration()`, use `Helper::removeDecoration()` instead
|
||||
* `AddConsoleCommandPass` can not be configured anymore
|
||||
* Remove `HelperSet::setCommand()` and `getCommand()` without replacement
|
||||
|
||||
5.4
|
||||
---
|
||||
|
||||
* Add `TesterTrait::assertCommandIsSuccessful()` to test command
|
||||
* Deprecate `HelperSet::setCommand()` and `getCommand()` without replacement
|
||||
|
||||
5.3
|
||||
---
|
||||
|
||||
* Add `GithubActionReporter` to render annotations in a Github Action
|
||||
* Add `InputOption::VALUE_NEGATABLE` flag to handle `--foo`/`--no-foo` options
|
||||
* Add the `Command::$defaultDescription` static property and the `description` attribute
|
||||
on the `console.command` tag to allow the `list` command to instantiate commands lazily
|
||||
* Add option `--short` to the `list` command
|
||||
* Add support for bright colors
|
||||
* Add `#[AsCommand]` attribute for declaring commands on PHP 8
|
||||
* Add `Helper::width()` and `Helper::length()`
|
||||
* The `--ansi` and `--no-ansi` options now default to `null`.
|
||||
|
||||
5.2.0
|
||||
-----
|
||||
|
||||
* Added `SingleCommandApplication::setAutoExit()` to allow testing via `CommandTester`
|
||||
* added support for multiline responses to questions through `Question::setMultiline()`
|
||||
and `Question::isMultiline()`
|
||||
* Added `SignalRegistry` class to stack signals handlers
|
||||
* Added support for signals:
|
||||
* Added `Application::getSignalRegistry()` and `Application::setSignalsToDispatchEvent()` methods
|
||||
* Added `SignalableCommandInterface` interface
|
||||
* Added `TableCellStyle` class to customize table cell
|
||||
* Removed `php ` prefix invocation from help messages.
|
||||
|
||||
5.1.0
|
||||
-----
|
||||
|
||||
* `Command::setHidden()` is final since Symfony 5.1
|
||||
* Add `SingleCommandApplication`
|
||||
* Add `Cursor` class
|
||||
|
||||
5.0.0
|
||||
-----
|
||||
|
||||
* removed support for finding hidden commands using an abbreviation, use the full name instead
|
||||
* removed `TableStyle::setCrossingChar()` method in favor of `TableStyle::setDefaultCrossingChar()`
|
||||
* removed `TableStyle::setHorizontalBorderChar()` method in favor of `TableStyle::setDefaultCrossingChars()`
|
||||
* removed `TableStyle::getHorizontalBorderChar()` method in favor of `TableStyle::getBorderChars()`
|
||||
* removed `TableStyle::setVerticalBorderChar()` method in favor of `TableStyle::setVerticalBorderChars()`
|
||||
* removed `TableStyle::getVerticalBorderChar()` method in favor of `TableStyle::getBorderChars()`
|
||||
* removed support for returning `null` from `Command::execute()`, return `0` instead
|
||||
* `ProcessHelper::run()` accepts only `array|Symfony\Component\Process\Process` for its `command` argument
|
||||
* `Application::setDispatcher` accepts only `Symfony\Contracts\EventDispatcher\EventDispatcherInterface`
|
||||
for its `dispatcher` argument
|
||||
* renamed `Application::renderException()` and `Application::doRenderException()`
|
||||
to `renderThrowable()` and `doRenderThrowable()` respectively.
|
||||
|
||||
4.4.0
|
||||
-----
|
||||
|
||||
* deprecated finding hidden commands using an abbreviation, use the full name instead
|
||||
* added `Question::setTrimmable` default to true to allow the answer to be trimmed
|
||||
* added method `minSecondsBetweenRedraws()` and `maxSecondsBetweenRedraws()` on `ProgressBar`
|
||||
* `Application` implements `ResetInterface`
|
||||
* marked all dispatched event classes as `@final`
|
||||
* added support for displaying table horizontally
|
||||
* deprecated returning `null` from `Command::execute()`, return `0` instead
|
||||
* Deprecated the `Application::renderException()` and `Application::doRenderException()` methods,
|
||||
use `renderThrowable()` and `doRenderThrowable()` instead.
|
||||
* added support for the `NO_COLOR` env var (https://no-color.org/)
|
||||
|
||||
4.3.0
|
||||
-----
|
||||
|
||||
* added support for hyperlinks
|
||||
* added `ProgressBar::iterate()` method that simplify updating the progress bar when iterating
|
||||
* added `Question::setAutocompleterCallback()` to provide a callback function
|
||||
that dynamically generates suggestions as the user types
|
||||
|
||||
4.2.0
|
||||
-----
|
||||
|
||||
* allowed passing commands as `[$process, 'ENV_VAR' => 'value']` to
|
||||
`ProcessHelper::run()` to pass environment variables
|
||||
* deprecated passing a command as a string to `ProcessHelper::run()`,
|
||||
pass it the command as an array of its arguments instead
|
||||
* made the `ProcessHelper` class final
|
||||
* added `WrappableOutputFormatterInterface::formatAndWrap()` (implemented in `OutputFormatter`)
|
||||
* added `capture_stderr_separately` option to `CommandTester::execute()`
|
||||
|
||||
4.1.0
|
||||
-----
|
||||
|
||||
* added option to run suggested command if command is not found and only 1 alternative is available
|
||||
* added option to modify console output and print multiple modifiable sections
|
||||
* added support for iterable messages in output `write` and `writeln` methods
|
||||
|
||||
4.0.0
|
||||
-----
|
||||
|
||||
* `OutputFormatter` throws an exception when unknown options are used
|
||||
* removed `QuestionHelper::setInputStream()/getInputStream()`
|
||||
* removed `Application::getTerminalWidth()/getTerminalHeight()` and
|
||||
`Application::setTerminalDimensions()/getTerminalDimensions()`
|
||||
* removed `ConsoleExceptionEvent`
|
||||
* removed `ConsoleEvents::EXCEPTION`
|
||||
|
||||
3.4.0
|
||||
-----
|
||||
|
||||
* added `SHELL_VERBOSITY` env var to control verbosity
|
||||
* added `CommandLoaderInterface`, `FactoryCommandLoader` and PSR-11
|
||||
`ContainerCommandLoader` for commands lazy-loading
|
||||
* added a case-insensitive command name matching fallback
|
||||
* added static `Command::$defaultName/getDefaultName()`, allowing for
|
||||
commands to be registered at compile time in the application command loader.
|
||||
Setting the `$defaultName` property avoids the need for filling the `command`
|
||||
attribute on the `console.command` tag when using `AddConsoleCommandPass`.
|
||||
|
||||
3.3.0
|
||||
-----
|
||||
|
||||
* added `ExceptionListener`
|
||||
* added `AddConsoleCommandPass` (originally in FrameworkBundle)
|
||||
* [BC BREAK] `Input::getOption()` no longer returns the default value for options
|
||||
with value optional explicitly passed empty
|
||||
* added console.error event to catch exceptions thrown by other listeners
|
||||
* deprecated console.exception event in favor of console.error
|
||||
* added ability to handle `CommandNotFoundException` through the
|
||||
`console.error` event
|
||||
* deprecated default validation in `SymfonyQuestionHelper::ask`
|
||||
|
||||
3.2.0
|
||||
------
|
||||
|
||||
* added `setInputs()` method to CommandTester for ease testing of commands expecting inputs
|
||||
* added `setStream()` and `getStream()` methods to Input (implement StreamableInputInterface)
|
||||
* added StreamableInputInterface
|
||||
* added LockableTrait
|
||||
|
||||
3.1.0
|
||||
-----
|
||||
|
||||
* added truncate method to FormatterHelper
|
||||
* added setColumnWidth(s) method to Table
|
||||
|
||||
2.8.3
|
||||
-----
|
||||
|
||||
* remove readline support from the question helper as it caused issues
|
||||
|
||||
2.8.0
|
||||
-----
|
||||
|
||||
* use readline for user input in the question helper when available to allow
|
||||
the use of arrow keys
|
||||
|
||||
2.6.0
|
||||
-----
|
||||
|
||||
* added a Process helper
|
||||
* added a DebugFormatter helper
|
||||
|
||||
2.5.0
|
||||
-----
|
||||
|
||||
* deprecated the dialog helper (use the question helper instead)
|
||||
* deprecated TableHelper in favor of Table
|
||||
* deprecated ProgressHelper in favor of ProgressBar
|
||||
* added ConsoleLogger
|
||||
* added a question helper
|
||||
* added a way to set the process name of a command
|
||||
* added a way to set a default command instead of `ListCommand`
|
||||
|
||||
2.4.0
|
||||
-----
|
||||
|
||||
* added a way to force terminal dimensions
|
||||
* added a convenient method to detect verbosity level
|
||||
* [BC BREAK] made descriptors use output instead of returning a string
|
||||
|
||||
2.3.0
|
||||
-----
|
||||
|
||||
* added multiselect support to the select dialog helper
|
||||
* added Table Helper for tabular data rendering
|
||||
* added support for events in `Application`
|
||||
* added a way to normalize EOLs in `ApplicationTester::getDisplay()` and `CommandTester::getDisplay()`
|
||||
* added a way to set the progress bar progress via the `setCurrent` method
|
||||
* added support for multiple InputOption shortcuts, written as `'-a|-b|-c'`
|
||||
* added two additional verbosity levels, VERBOSITY_VERY_VERBOSE and VERBOSITY_DEBUG
|
||||
|
||||
2.2.0
|
||||
-----
|
||||
|
||||
* added support for colorization on Windows via ConEmu
|
||||
* add a method to Dialog Helper to ask for a question and hide the response
|
||||
* added support for interactive selections in console (DialogHelper::select())
|
||||
* added support for autocompletion as you type in Dialog Helper
|
||||
|
||||
2.1.0
|
||||
-----
|
||||
|
||||
* added ConsoleOutputInterface
|
||||
* added the possibility to disable a command (Command::isEnabled())
|
||||
* added suggestions when a command does not exist
|
||||
* added a --raw option to the list command
|
||||
* added support for STDERR in the console output class (errors are now sent
|
||||
to STDERR)
|
||||
* made the defaults (helper set, commands, input definition) in Application
|
||||
more easily customizable
|
||||
* added support for the shell even if readline is not available
|
||||
* added support for process isolation in Symfony shell via
|
||||
`--process-isolation` switch
|
||||
* added support for `--`, which disables options parsing after that point
|
||||
(tokens will be parsed as arguments)
|
||||
99
vendor/symfony/console/CI/GithubActionReporter.php
vendored
Normal file
99
vendor/symfony/console/CI/GithubActionReporter.php
vendored
Normal file
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\CI;
|
||||
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Utility class for Github actions.
|
||||
*
|
||||
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
|
||||
*/
|
||||
class GithubActionReporter
|
||||
{
|
||||
private OutputInterface $output;
|
||||
|
||||
/**
|
||||
* @see https://github.com/actions/toolkit/blob/5e5e1b7aacba68a53836a34db4a288c3c1c1585b/packages/core/src/command.ts#L80-L85
|
||||
*/
|
||||
private const ESCAPED_DATA = [
|
||||
'%' => '%25',
|
||||
"\r" => '%0D',
|
||||
"\n" => '%0A',
|
||||
];
|
||||
|
||||
/**
|
||||
* @see https://github.com/actions/toolkit/blob/5e5e1b7aacba68a53836a34db4a288c3c1c1585b/packages/core/src/command.ts#L87-L94
|
||||
*/
|
||||
private const ESCAPED_PROPERTIES = [
|
||||
'%' => '%25',
|
||||
"\r" => '%0D',
|
||||
"\n" => '%0A',
|
||||
':' => '%3A',
|
||||
',' => '%2C',
|
||||
];
|
||||
|
||||
public function __construct(OutputInterface $output)
|
||||
{
|
||||
$this->output = $output;
|
||||
}
|
||||
|
||||
public static function isGithubActionEnvironment(): bool
|
||||
{
|
||||
return false !== getenv('GITHUB_ACTIONS');
|
||||
}
|
||||
|
||||
/**
|
||||
* Output an error using the Github annotations format.
|
||||
*
|
||||
* @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-an-error-message
|
||||
*/
|
||||
public function error(string $message, ?string $file = null, ?int $line = null, ?int $col = null): void
|
||||
{
|
||||
$this->log('error', $message, $file, $line, $col);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output a warning using the Github annotations format.
|
||||
*
|
||||
* @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message
|
||||
*/
|
||||
public function warning(string $message, ?string $file = null, ?int $line = null, ?int $col = null): void
|
||||
{
|
||||
$this->log('warning', $message, $file, $line, $col);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output a debug log using the Github annotations format.
|
||||
*
|
||||
* @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-a-debug-message
|
||||
*/
|
||||
public function debug(string $message, ?string $file = null, ?int $line = null, ?int $col = null): void
|
||||
{
|
||||
$this->log('debug', $message, $file, $line, $col);
|
||||
}
|
||||
|
||||
private function log(string $type, string $message, ?string $file = null, ?int $line = null, ?int $col = null): void
|
||||
{
|
||||
// Some values must be encoded.
|
||||
$message = strtr($message, self::ESCAPED_DATA);
|
||||
|
||||
if (!$file) {
|
||||
// No file provided, output the message solely:
|
||||
$this->output->writeln(\sprintf('::%s::%s', $type, $message));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->output->writeln(\sprintf('::%s file=%s,line=%s,col=%s::%s', $type, strtr($file, self::ESCAPED_PROPERTIES), strtr($line ?? 1, self::ESCAPED_PROPERTIES), strtr($col ?? 0, self::ESCAPED_PROPERTIES), $message));
|
||||
}
|
||||
}
|
||||
133
vendor/symfony/console/Color.php
vendored
Normal file
133
vendor/symfony/console/Color.php
vendored
Normal file
@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console;
|
||||
|
||||
use Symfony\Component\Console\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
final class Color
|
||||
{
|
||||
private const COLORS = [
|
||||
'black' => 0,
|
||||
'red' => 1,
|
||||
'green' => 2,
|
||||
'yellow' => 3,
|
||||
'blue' => 4,
|
||||
'magenta' => 5,
|
||||
'cyan' => 6,
|
||||
'white' => 7,
|
||||
'default' => 9,
|
||||
];
|
||||
|
||||
private const BRIGHT_COLORS = [
|
||||
'gray' => 0,
|
||||
'bright-red' => 1,
|
||||
'bright-green' => 2,
|
||||
'bright-yellow' => 3,
|
||||
'bright-blue' => 4,
|
||||
'bright-magenta' => 5,
|
||||
'bright-cyan' => 6,
|
||||
'bright-white' => 7,
|
||||
];
|
||||
|
||||
private const AVAILABLE_OPTIONS = [
|
||||
'bold' => ['set' => 1, 'unset' => 22],
|
||||
'underscore' => ['set' => 4, 'unset' => 24],
|
||||
'blink' => ['set' => 5, 'unset' => 25],
|
||||
'reverse' => ['set' => 7, 'unset' => 27],
|
||||
'conceal' => ['set' => 8, 'unset' => 28],
|
||||
];
|
||||
|
||||
private string $foreground;
|
||||
private string $background;
|
||||
private array $options = [];
|
||||
|
||||
public function __construct(string $foreground = '', string $background = '', array $options = [])
|
||||
{
|
||||
$this->foreground = $this->parseColor($foreground);
|
||||
$this->background = $this->parseColor($background, true);
|
||||
|
||||
foreach ($options as $option) {
|
||||
if (!isset(self::AVAILABLE_OPTIONS[$option])) {
|
||||
throw new InvalidArgumentException(\sprintf('Invalid option specified: "%s". Expected one of (%s).', $option, implode(', ', array_keys(self::AVAILABLE_OPTIONS))));
|
||||
}
|
||||
|
||||
$this->options[$option] = self::AVAILABLE_OPTIONS[$option];
|
||||
}
|
||||
}
|
||||
|
||||
public function apply(string $text): string
|
||||
{
|
||||
return $this->set().$text.$this->unset();
|
||||
}
|
||||
|
||||
public function set(): string
|
||||
{
|
||||
$setCodes = [];
|
||||
if ('' !== $this->foreground) {
|
||||
$setCodes[] = $this->foreground;
|
||||
}
|
||||
if ('' !== $this->background) {
|
||||
$setCodes[] = $this->background;
|
||||
}
|
||||
foreach ($this->options as $option) {
|
||||
$setCodes[] = $option['set'];
|
||||
}
|
||||
if (0 === \count($setCodes)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return \sprintf("\033[%sm", implode(';', $setCodes));
|
||||
}
|
||||
|
||||
public function unset(): string
|
||||
{
|
||||
$unsetCodes = [];
|
||||
if ('' !== $this->foreground) {
|
||||
$unsetCodes[] = 39;
|
||||
}
|
||||
if ('' !== $this->background) {
|
||||
$unsetCodes[] = 49;
|
||||
}
|
||||
foreach ($this->options as $option) {
|
||||
$unsetCodes[] = $option['unset'];
|
||||
}
|
||||
if (0 === \count($unsetCodes)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return \sprintf("\033[%sm", implode(';', $unsetCodes));
|
||||
}
|
||||
|
||||
private function parseColor(string $color, bool $background = false): string
|
||||
{
|
||||
if ('' === $color) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ('#' === $color[0]) {
|
||||
return ($background ? '4' : '3').Terminal::getColorMode()->convertFromHexToAnsiColorCode($color);
|
||||
}
|
||||
|
||||
if (isset(self::COLORS[$color])) {
|
||||
return ($background ? '4' : '3').self::COLORS[$color];
|
||||
}
|
||||
|
||||
if (isset(self::BRIGHT_COLORS[$color])) {
|
||||
return ($background ? '10' : '9').self::BRIGHT_COLORS[$color];
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException(\sprintf('Invalid "%s" color; expected one of (%s).', $color, implode(', ', array_merge(array_keys(self::COLORS), array_keys(self::BRIGHT_COLORS)))));
|
||||
}
|
||||
}
|
||||
725
vendor/symfony/console/Command/Command.php
vendored
Normal file
725
vendor/symfony/console/Command/Command.php
vendored
Normal file
@ -0,0 +1,725 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@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\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Completion\CompletionInput;
|
||||
use Symfony\Component\Console\Completion\CompletionSuggestions;
|
||||
use Symfony\Component\Console\Completion\Suggestion;
|
||||
use Symfony\Component\Console\Exception\ExceptionInterface;
|
||||
use Symfony\Component\Console\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Console\Exception\LogicException;
|
||||
use Symfony\Component\Console\Helper\HelperInterface;
|
||||
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
|
||||
{
|
||||
// see https://tldp.org/LDP/abs/html/exitcodes.html
|
||||
public const SUCCESS = 0;
|
||||
public const FAILURE = 1;
|
||||
public const INVALID = 2;
|
||||
|
||||
/**
|
||||
* @var string|null The default command name
|
||||
*
|
||||
* @deprecated since Symfony 6.1, use the AsCommand attribute instead
|
||||
*/
|
||||
protected static $defaultName;
|
||||
|
||||
/**
|
||||
* @var string|null The default command description
|
||||
*
|
||||
* @deprecated since Symfony 6.1, use the AsCommand attribute instead
|
||||
*/
|
||||
protected static $defaultDescription;
|
||||
|
||||
private ?Application $application = null;
|
||||
private ?string $name = null;
|
||||
private ?string $processTitle = null;
|
||||
private array $aliases = [];
|
||||
private InputDefinition $definition;
|
||||
private bool $hidden = false;
|
||||
private string $help = '';
|
||||
private string $description = '';
|
||||
private ?InputDefinition $fullDefinition = null;
|
||||
private bool $ignoreValidationErrors = false;
|
||||
private ?\Closure $code = null;
|
||||
private array $synopsis = [];
|
||||
private array $usages = [];
|
||||
private ?HelperSet $helperSet = null;
|
||||
|
||||
public static function getDefaultName(): ?string
|
||||
{
|
||||
$class = static::class;
|
||||
|
||||
if ($attribute = (new \ReflectionClass($class))->getAttributes(AsCommand::class)) {
|
||||
return $attribute[0]->newInstance()->name;
|
||||
}
|
||||
|
||||
$r = new \ReflectionProperty($class, 'defaultName');
|
||||
|
||||
if ($class !== $r->class || null === static::$defaultName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
trigger_deprecation('symfony/console', '6.1', 'Relying on the static property "$defaultName" for setting a command name is deprecated. Add the "%s" attribute to the "%s" class instead.', AsCommand::class, static::class);
|
||||
|
||||
return static::$defaultName;
|
||||
}
|
||||
|
||||
public static function getDefaultDescription(): ?string
|
||||
{
|
||||
$class = static::class;
|
||||
|
||||
if ($attribute = (new \ReflectionClass($class))->getAttributes(AsCommand::class)) {
|
||||
return $attribute[0]->newInstance()->description;
|
||||
}
|
||||
|
||||
$r = new \ReflectionProperty($class, 'defaultDescription');
|
||||
|
||||
if ($class !== $r->class || null === static::$defaultDescription) {
|
||||
return null;
|
||||
}
|
||||
|
||||
trigger_deprecation('symfony/console', '6.1', 'Relying on the static property "$defaultDescription" for setting a command description is deprecated. Add the "%s" attribute to the "%s" class instead.', AsCommand::class, static::class);
|
||||
|
||||
return static::$defaultDescription;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $name The name of the command; passing null means it must be set in configure()
|
||||
*
|
||||
* @throws LogicException When the command name is empty
|
||||
*/
|
||||
public function __construct(?string $name = null)
|
||||
{
|
||||
$this->definition = new InputDefinition();
|
||||
|
||||
if (null === $name && null !== $name = static::getDefaultName()) {
|
||||
$aliases = explode('|', $name);
|
||||
|
||||
if ('' === $name = array_shift($aliases)) {
|
||||
$this->setHidden(true);
|
||||
$name = array_shift($aliases);
|
||||
}
|
||||
|
||||
$this->setAliases($aliases);
|
||||
}
|
||||
|
||||
if (null !== $name) {
|
||||
$this->setName($name);
|
||||
}
|
||||
|
||||
if ('' === $this->description) {
|
||||
$this->setDescription(static::getDefaultDescription() ?? '');
|
||||
}
|
||||
|
||||
$this->configure();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ignores validation errors.
|
||||
*
|
||||
* This is mainly useful for the help command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ignoreValidationErrors()
|
||||
{
|
||||
$this->ignoreValidationErrors = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function setApplication(?Application $application = null)
|
||||
{
|
||||
if (1 > \func_num_args()) {
|
||||
trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
|
||||
}
|
||||
$this->application = $application;
|
||||
if ($application) {
|
||||
$this->setHelperSet($application->getHelperSet());
|
||||
} else {
|
||||
$this->helperSet = null;
|
||||
}
|
||||
|
||||
$this->fullDefinition = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function setHelperSet(HelperSet $helperSet)
|
||||
{
|
||||
$this->helperSet = $helperSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the helper set.
|
||||
*/
|
||||
public function getHelperSet(): ?HelperSet
|
||||
{
|
||||
return $this->helperSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the application instance for this command.
|
||||
*/
|
||||
public function getApplication(): ?Application
|
||||
{
|
||||
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 cannot
|
||||
* run properly under the current conditions.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEnabled()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the current command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the current command.
|
||||
*
|
||||
* This method is not abstract because you can use this class
|
||||
* as a concrete class. In this case, instead of defining the
|
||||
* execute() method, you set the code to execute by passing
|
||||
* a Closure to the setCode() method.
|
||||
*
|
||||
* @return int 0 if everything went fine, or an exit code
|
||||
*
|
||||
* @throws LogicException When this abstract method is not implemented
|
||||
*
|
||||
* @see setCode()
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
throw new LogicException('You must override the execute() method in the concrete command class.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Interacts with the user.
|
||||
*
|
||||
* This method is executed before the InputDefinition is validated.
|
||||
* This means that this is the only place where the command can
|
||||
* interactively ask for values of missing required arguments.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function interact(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the command after the input has been bound and before the input
|
||||
* is validated.
|
||||
*
|
||||
* This is mainly useful when a lot of commands extends one main command
|
||||
* where some things need to be initialized based on the input arguments and options.
|
||||
*
|
||||
* @see InputInterface::bind()
|
||||
* @see InputInterface::validate()
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function initialize(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the command.
|
||||
*
|
||||
* The code to execute is either defined directly with the
|
||||
* setCode() method or by overriding the execute() method
|
||||
* in a sub-class.
|
||||
*
|
||||
* @return int The command exit code
|
||||
*
|
||||
* @throws ExceptionInterface When input binding fails. Bypass this by calling {@link ignoreValidationErrors()}.
|
||||
*
|
||||
* @see setCode()
|
||||
* @see execute()
|
||||
*/
|
||||
public function run(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
// add the application arguments and options
|
||||
$this->mergeApplicationDefinition();
|
||||
|
||||
// bind the input against the command specific arguments/options
|
||||
try {
|
||||
$input->bind($this->getDefinition());
|
||||
} catch (ExceptionInterface $e) {
|
||||
if (!$this->ignoreValidationErrors) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
$this->initialize($input, $output);
|
||||
|
||||
if (null !== $this->processTitle) {
|
||||
if (\function_exists('cli_set_process_title')) {
|
||||
if (!@cli_set_process_title($this->processTitle)) {
|
||||
if ('Darwin' === \PHP_OS) {
|
||||
$output->writeln('<comment>Running "cli_set_process_title" as an unprivileged user is not supported on MacOS.</comment>', OutputInterface::VERBOSITY_VERY_VERBOSE);
|
||||
} else {
|
||||
cli_set_process_title($this->processTitle);
|
||||
}
|
||||
}
|
||||
} elseif (\function_exists('setproctitle')) {
|
||||
setproctitle($this->processTitle);
|
||||
} elseif (OutputInterface::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) {
|
||||
$output->writeln('<comment>Install the proctitle PECL to be able to change the process title.</comment>');
|
||||
}
|
||||
}
|
||||
|
||||
if ($input->isInteractive()) {
|
||||
$this->interact($input, $output);
|
||||
}
|
||||
|
||||
// The command name argument is often omitted when a command is executed directly with its run() method.
|
||||
// It would fail the validation if we didn't make sure the command argument is present,
|
||||
// since it's required by the application.
|
||||
if ($input->hasArgument('command') && null === $input->getArgument('command')) {
|
||||
$input->setArgument('command', $this->getName());
|
||||
}
|
||||
|
||||
$input->validate();
|
||||
|
||||
if ($this->code) {
|
||||
$statusCode = ($this->code)($input, $output);
|
||||
} else {
|
||||
$statusCode = $this->execute($input, $output);
|
||||
|
||||
if (!\is_int($statusCode)) {
|
||||
throw new \TypeError(\sprintf('Return value of "%s::execute()" must be of the type int, "%s" returned.', static::class, get_debug_type($statusCode)));
|
||||
}
|
||||
}
|
||||
|
||||
return is_numeric($statusCode) ? (int) $statusCode : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds suggestions to $suggestions for the current completion input (e.g. option or argument).
|
||||
*/
|
||||
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
|
||||
{
|
||||
$definition = $this->getDefinition();
|
||||
if (CompletionInput::TYPE_OPTION_VALUE === $input->getCompletionType() && $definition->hasOption($input->getCompletionName())) {
|
||||
$definition->getOption($input->getCompletionName())->complete($input, $suggestions);
|
||||
} elseif (CompletionInput::TYPE_ARGUMENT_VALUE === $input->getCompletionType() && $definition->hasArgument($input->getCompletionName())) {
|
||||
$definition->getArgument($input->getCompletionName())->complete($input, $suggestions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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): static
|
||||
{
|
||||
if ($code instanceof \Closure) {
|
||||
$r = new \ReflectionFunction($code);
|
||||
if (null === $r->getClosureThis()) {
|
||||
set_error_handler(static function () {});
|
||||
try {
|
||||
if ($c = \Closure::bind($code, $this)) {
|
||||
$code = $c;
|
||||
}
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$code = $code(...);
|
||||
}
|
||||
|
||||
$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
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function mergeApplicationDefinition(bool $mergeArgs = true): void
|
||||
{
|
||||
if (null === $this->application) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->fullDefinition = new InputDefinition();
|
||||
$this->fullDefinition->setOptions($this->definition->getOptions());
|
||||
$this->fullDefinition->addOptions($this->application->getDefinition()->getOptions());
|
||||
|
||||
if ($mergeArgs) {
|
||||
$this->fullDefinition->setArguments($this->application->getDefinition()->getArguments());
|
||||
$this->fullDefinition->addArguments($this->definition->getArguments());
|
||||
} else {
|
||||
$this->fullDefinition->setArguments($this->definition->getArguments());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an array of argument and option instances.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setDefinition(array|InputDefinition $definition): static
|
||||
{
|
||||
if ($definition instanceof InputDefinition) {
|
||||
$this->definition = $definition;
|
||||
} else {
|
||||
$this->definition->setDefinition($definition);
|
||||
}
|
||||
|
||||
$this->fullDefinition = null;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the InputDefinition attached to this Command.
|
||||
*/
|
||||
public function getDefinition(): InputDefinition
|
||||
{
|
||||
return $this->fullDefinition ?? $this->getNativeDefinition();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public function getNativeDefinition(): InputDefinition
|
||||
{
|
||||
return $this->definition ?? throw new LogicException(\sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', static::class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an argument.
|
||||
*
|
||||
* @param $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL
|
||||
* @param $default The default value (for InputArgument::OPTIONAL mode only)
|
||||
* @param array|\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion> $suggestedValues The values used for input completion
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws InvalidArgumentException When argument mode is not valid
|
||||
*/
|
||||
public function addArgument(string $name, ?int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = null */): static
|
||||
{
|
||||
$suggestedValues = 5 <= \func_num_args() ? func_get_arg(4) : [];
|
||||
if (!\is_array($suggestedValues) && !$suggestedValues instanceof \Closure) {
|
||||
throw new \TypeError(\sprintf('Argument 5 passed to "%s()" must be array or \Closure, "%s" given.', __METHOD__, get_debug_type($suggestedValues)));
|
||||
}
|
||||
$this->definition->addArgument(new InputArgument($name, $mode, $description, $default, $suggestedValues));
|
||||
$this->fullDefinition?->addArgument(new InputArgument($name, $mode, $description, $default, $suggestedValues));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an option.
|
||||
*
|
||||
* @param $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
|
||||
* @param $mode The option mode: One of the InputOption::VALUE_* constants
|
||||
* @param $default The default value (must be null for InputOption::VALUE_NONE)
|
||||
* @param array|\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion> $suggestedValues The values used for input completion
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws InvalidArgumentException If option mode is invalid or incompatible
|
||||
*/
|
||||
public function addOption(string $name, string|array|null $shortcut = null, ?int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = [] */): static
|
||||
{
|
||||
$suggestedValues = 6 <= \func_num_args() ? func_get_arg(5) : [];
|
||||
if (!\is_array($suggestedValues) && !$suggestedValues instanceof \Closure) {
|
||||
throw new \TypeError(\sprintf('Argument 5 passed to "%s()" must be array or \Closure, "%s" given.', __METHOD__, get_debug_type($suggestedValues)));
|
||||
}
|
||||
$this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default, $suggestedValues));
|
||||
$this->fullDefinition?->addOption(new InputOption($name, $shortcut, $mode, $description, $default, $suggestedValues));
|
||||
|
||||
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');
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws InvalidArgumentException When the name is invalid
|
||||
*/
|
||||
public function setName(string $name): static
|
||||
{
|
||||
$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.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setProcessTitle(string $title): static
|
||||
{
|
||||
$this->processTitle = $title;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the command name.
|
||||
*/
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $hidden Whether or not the command should be hidden from the list of commands
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setHidden(bool $hidden = true): static
|
||||
{
|
||||
$this->hidden = $hidden;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool whether the command should be publicly shown or not
|
||||
*/
|
||||
public function isHidden(): bool
|
||||
{
|
||||
return $this->hidden;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the description for the command.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setDescription(string $description): static
|
||||
{
|
||||
$this->description = $description;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the description for the command.
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the help for the command.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setHelp(string $help): static
|
||||
{
|
||||
$this->help = $help;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the help for the command.
|
||||
*/
|
||||
public function getHelp(): string
|
||||
{
|
||||
return $this->help;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the processed help for the command replacing the %command.name% and
|
||||
* %command.full_name% patterns with the real values dynamically.
|
||||
*/
|
||||
public function getProcessedHelp(): string
|
||||
{
|
||||
$name = $this->name;
|
||||
$isSingleCommand = $this->application?->isSingleCommand();
|
||||
|
||||
$placeholders = [
|
||||
'%command.name%',
|
||||
'%command.full_name%',
|
||||
];
|
||||
$replacements = [
|
||||
$name,
|
||||
$isSingleCommand ? $_SERVER['PHP_SELF'] : $_SERVER['PHP_SELF'].' '.$name,
|
||||
];
|
||||
|
||||
return str_replace($placeholders, $replacements, $this->getHelp() ?: $this->getDescription());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the aliases for the command.
|
||||
*
|
||||
* @param string[] $aliases An array of aliases for the command
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws InvalidArgumentException When an alias is invalid
|
||||
*/
|
||||
public function setAliases(iterable $aliases): static
|
||||
{
|
||||
$list = [];
|
||||
|
||||
foreach ($aliases as $alias) {
|
||||
$this->validateName($alias);
|
||||
$list[] = $alias;
|
||||
}
|
||||
|
||||
$this->aliases = \is_array($aliases) ? $aliases : $list;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the aliases for the command.
|
||||
*/
|
||||
public function getAliases(): array
|
||||
{
|
||||
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
|
||||
*/
|
||||
public function getSynopsis(bool $short = false): string
|
||||
{
|
||||
$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, it'll be prefixed with the command name.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addUsage(string $usage): static
|
||||
{
|
||||
if (!str_starts_with($usage, $this->name)) {
|
||||
$usage = \sprintf('%s %s', $this->name, $usage);
|
||||
}
|
||||
|
||||
$this->usages[] = $usage;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns alternative usages of the command.
|
||||
*/
|
||||
public function getUsages(): array
|
||||
{
|
||||
return $this->usages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a helper instance by name.
|
||||
*
|
||||
* @return HelperInterface
|
||||
*
|
||||
* @throws LogicException if no HelperSet is defined
|
||||
* @throws InvalidArgumentException if the helper is not defined
|
||||
*/
|
||||
public function getHelper(string $name): mixed
|
||||
{
|
||||
if (null === $this->helperSet) {
|
||||
throw new LogicException(\sprintf('Cannot retrieve helper "%s" because there is no HelperSet defined. Did you forget to add your command to the application or to set the application on the command using the setApplication() method? You can also set the HelperSet directly using the setHelperSet() method.', $name));
|
||||
}
|
||||
|
||||
return $this->helperSet->get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a command name.
|
||||
*
|
||||
* It must be non-empty and parts can optionally be separated by ":".
|
||||
*
|
||||
* @throws InvalidArgumentException When the name is invalid
|
||||
*/
|
||||
private function validateName(string $name): void
|
||||
{
|
||||
if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) {
|
||||
throw new InvalidArgumentException(\sprintf('Command name "%s" is invalid.', $name));
|
||||
}
|
||||
}
|
||||
}
|
||||
223
vendor/symfony/console/Command/CompleteCommand.php
vendored
Normal file
223
vendor/symfony/console/Command/CompleteCommand.php
vendored
Normal file
@ -0,0 +1,223 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@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\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Completion\CompletionInput;
|
||||
use Symfony\Component\Console\Completion\CompletionSuggestions;
|
||||
use Symfony\Component\Console\Completion\Output\BashCompletionOutput;
|
||||
use Symfony\Component\Console\Completion\Output\CompletionOutputInterface;
|
||||
use Symfony\Component\Console\Completion\Output\FishCompletionOutput;
|
||||
use Symfony\Component\Console\Completion\Output\ZshCompletionOutput;
|
||||
use Symfony\Component\Console\Exception\CommandNotFoundException;
|
||||
use Symfony\Component\Console\Exception\ExceptionInterface;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Responsible for providing the values to the shell completion.
|
||||
*
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*/
|
||||
#[AsCommand(name: '|_complete', description: 'Internal command to provide shell completion suggestions')]
|
||||
final class CompleteCommand extends Command
|
||||
{
|
||||
public const COMPLETION_API_VERSION = '1';
|
||||
|
||||
/**
|
||||
* @deprecated since Symfony 6.1
|
||||
*/
|
||||
protected static $defaultName = '|_complete';
|
||||
|
||||
/**
|
||||
* @deprecated since Symfony 6.1
|
||||
*/
|
||||
protected static $defaultDescription = 'Internal command to provide shell completion suggestions';
|
||||
|
||||
private array $completionOutputs;
|
||||
|
||||
private bool $isDebug = false;
|
||||
|
||||
/**
|
||||
* @param array<string, class-string<CompletionOutputInterface>> $completionOutputs A list of additional completion outputs, with shell name as key and FQCN as value
|
||||
*/
|
||||
public function __construct(array $completionOutputs = [])
|
||||
{
|
||||
// must be set before the parent constructor, as the property value is used in configure()
|
||||
$this->completionOutputs = $completionOutputs + [
|
||||
'bash' => BashCompletionOutput::class,
|
||||
'fish' => FishCompletionOutput::class,
|
||||
'zsh' => ZshCompletionOutput::class,
|
||||
];
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->addOption('shell', 's', InputOption::VALUE_REQUIRED, 'The shell type ("'.implode('", "', array_keys($this->completionOutputs)).'")')
|
||||
->addOption('input', 'i', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'An array of input tokens (e.g. COMP_WORDS or argv)')
|
||||
->addOption('current', 'c', InputOption::VALUE_REQUIRED, 'The index of the "input" array that the cursor is in (e.g. COMP_CWORD)')
|
||||
->addOption('api-version', 'a', InputOption::VALUE_REQUIRED, 'The API version of the completion script')
|
||||
->addOption('symfony', 'S', InputOption::VALUE_REQUIRED, 'deprecated')
|
||||
;
|
||||
}
|
||||
|
||||
protected function initialize(InputInterface $input, OutputInterface $output): void
|
||||
{
|
||||
$this->isDebug = filter_var(getenv('SYMFONY_COMPLETION_DEBUG'), \FILTER_VALIDATE_BOOL);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
try {
|
||||
// "symfony" must be kept for compat with the shell scripts generated by Symfony Console 5.4 - 6.1
|
||||
$version = $input->getOption('symfony') ? '1' : $input->getOption('api-version');
|
||||
if ($version && version_compare($version, self::COMPLETION_API_VERSION, '<')) {
|
||||
$message = \sprintf('Completion script version is not supported ("%s" given, ">=%s" required).', $version, self::COMPLETION_API_VERSION);
|
||||
$this->log($message);
|
||||
|
||||
$output->writeln($message.' Install the Symfony completion script again by using the "completion" command.');
|
||||
|
||||
return 126;
|
||||
}
|
||||
|
||||
$shell = $input->getOption('shell');
|
||||
if (!$shell) {
|
||||
throw new \RuntimeException('The "--shell" option must be set.');
|
||||
}
|
||||
|
||||
if (!$completionOutput = $this->completionOutputs[$shell] ?? false) {
|
||||
throw new \RuntimeException(\sprintf('Shell completion is not supported for your shell: "%s" (supported: "%s").', $shell, implode('", "', array_keys($this->completionOutputs))));
|
||||
}
|
||||
|
||||
$completionInput = $this->createCompletionInput($input);
|
||||
$suggestions = new CompletionSuggestions();
|
||||
|
||||
$this->log([
|
||||
'',
|
||||
'<comment>'.date('Y-m-d H:i:s').'</>',
|
||||
'<info>Input:</> <comment>("|" indicates the cursor position)</>',
|
||||
' '.(string) $completionInput,
|
||||
'<info>Command:</>',
|
||||
' '.(string) implode(' ', $_SERVER['argv']),
|
||||
'<info>Messages:</>',
|
||||
]);
|
||||
|
||||
$command = $this->findCommand($completionInput, $output);
|
||||
if (null === $command) {
|
||||
$this->log(' No command found, completing using the Application class.');
|
||||
|
||||
$this->getApplication()->complete($completionInput, $suggestions);
|
||||
} elseif (
|
||||
$completionInput->mustSuggestArgumentValuesFor('command')
|
||||
&& $command->getName() !== $completionInput->getCompletionValue()
|
||||
&& !\in_array($completionInput->getCompletionValue(), $command->getAliases(), true)
|
||||
) {
|
||||
$this->log(' No command found, completing using the Application class.');
|
||||
|
||||
// expand shortcut names ("cache:cl<TAB>") into their full name ("cache:clear")
|
||||
$suggestions->suggestValues(array_filter(array_merge([$command->getName()], $command->getAliases())));
|
||||
} else {
|
||||
$command->mergeApplicationDefinition();
|
||||
$completionInput->bind($command->getDefinition());
|
||||
|
||||
if (CompletionInput::TYPE_OPTION_NAME === $completionInput->getCompletionType()) {
|
||||
$this->log(' Completing option names for the <comment>'.($command instanceof LazyCommand ? $command->getCommand() : $command)::class.'</> command.');
|
||||
|
||||
$suggestions->suggestOptions($command->getDefinition()->getOptions());
|
||||
} else {
|
||||
$this->log([
|
||||
' Completing using the <comment>'.($command instanceof LazyCommand ? $command->getCommand() : $command)::class.'</> class.',
|
||||
' Completing <comment>'.$completionInput->getCompletionType().'</> for <comment>'.$completionInput->getCompletionName().'</>',
|
||||
]);
|
||||
if (null !== $compval = $completionInput->getCompletionValue()) {
|
||||
$this->log(' Current value: <comment>'.$compval.'</>');
|
||||
}
|
||||
|
||||
$command->complete($completionInput, $suggestions);
|
||||
}
|
||||
}
|
||||
|
||||
/** @var CompletionOutputInterface $completionOutput */
|
||||
$completionOutput = new $completionOutput();
|
||||
|
||||
$this->log('<info>Suggestions:</>');
|
||||
if ($options = $suggestions->getOptionSuggestions()) {
|
||||
$this->log(' --'.implode(' --', array_map(fn ($o) => $o->getName(), $options)));
|
||||
} elseif ($values = $suggestions->getValueSuggestions()) {
|
||||
$this->log(' '.implode(' ', $values));
|
||||
} else {
|
||||
$this->log(' <comment>No suggestions were provided</>');
|
||||
}
|
||||
|
||||
$completionOutput->write($suggestions, $output);
|
||||
} catch (\Throwable $e) {
|
||||
$this->log([
|
||||
'<error>Error!</error>',
|
||||
(string) $e,
|
||||
]);
|
||||
|
||||
if ($output->isDebug()) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function createCompletionInput(InputInterface $input): CompletionInput
|
||||
{
|
||||
$currentIndex = $input->getOption('current');
|
||||
if (!$currentIndex || !ctype_digit($currentIndex)) {
|
||||
throw new \RuntimeException('The "--current" option must be set and it must be an integer.');
|
||||
}
|
||||
|
||||
$completionInput = CompletionInput::fromTokens($input->getOption('input'), (int) $currentIndex);
|
||||
|
||||
try {
|
||||
$completionInput->bind($this->getApplication()->getDefinition());
|
||||
} catch (ExceptionInterface) {
|
||||
}
|
||||
|
||||
return $completionInput;
|
||||
}
|
||||
|
||||
private function findCommand(CompletionInput $completionInput, OutputInterface $output): ?Command
|
||||
{
|
||||
try {
|
||||
$inputName = $completionInput->getFirstArgument();
|
||||
if (null === $inputName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->getApplication()->find($inputName);
|
||||
} catch (CommandNotFoundException) {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function log($messages): void
|
||||
{
|
||||
if (!$this->isDebug) {
|
||||
return;
|
||||
}
|
||||
|
||||
$commandName = basename($_SERVER['argv'][0]);
|
||||
file_put_contents(sys_get_temp_dir().'/sf_'.$commandName.'.log', implode(\PHP_EOL, (array) $messages).\PHP_EOL, \FILE_APPEND);
|
||||
}
|
||||
}
|
||||
161
vendor/symfony/console/Command/DumpCompletionCommand.php
vendored
Normal file
161
vendor/symfony/console/Command/DumpCompletionCommand.php
vendored
Normal 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\Command;
|
||||
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
/**
|
||||
* Dumps the completion script for the current shell.
|
||||
*
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*/
|
||||
#[AsCommand(name: 'completion', description: 'Dump the shell completion script')]
|
||||
final class DumpCompletionCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @deprecated since Symfony 6.1
|
||||
*/
|
||||
protected static $defaultName = 'completion';
|
||||
|
||||
/**
|
||||
* @deprecated since Symfony 6.1
|
||||
*/
|
||||
protected static $defaultDescription = 'Dump the shell completion script';
|
||||
|
||||
private array $supportedShells;
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$fullCommand = $_SERVER['PHP_SELF'];
|
||||
$commandName = basename($fullCommand);
|
||||
$fullCommand = @realpath($fullCommand) ?: $fullCommand;
|
||||
|
||||
$shell = $this->guessShell();
|
||||
[$rcFile, $completionFile] = match ($shell) {
|
||||
'fish' => ['~/.config/fish/config.fish', "/etc/fish/completions/$commandName.fish"],
|
||||
'zsh' => ['~/.zshrc', '$fpath[1]/_'.$commandName],
|
||||
default => ['~/.bashrc', "/etc/bash_completion.d/$commandName"],
|
||||
};
|
||||
|
||||
$supportedShells = implode(', ', $this->getSupportedShells());
|
||||
|
||||
$this
|
||||
->setHelp(<<<EOH
|
||||
The <info>%command.name%</> command dumps the shell completion script required
|
||||
to use shell autocompletion (currently, {$supportedShells} completion are supported).
|
||||
|
||||
<comment>Static installation
|
||||
-------------------</>
|
||||
|
||||
Dump the script to a global completion file and restart your shell:
|
||||
|
||||
<info>%command.full_name% {$shell} | sudo tee {$completionFile}</>
|
||||
|
||||
Or dump the script to a local file and source it:
|
||||
|
||||
<info>%command.full_name% {$shell} > completion.sh</>
|
||||
|
||||
<comment># source the file whenever you use the project</>
|
||||
<info>source completion.sh</>
|
||||
|
||||
<comment># or add this line at the end of your "{$rcFile}" file:</>
|
||||
<info>source /path/to/completion.sh</>
|
||||
|
||||
<comment>Dynamic installation
|
||||
--------------------</>
|
||||
|
||||
Add this to the end of your shell configuration file (e.g. <info>"{$rcFile}"</>):
|
||||
|
||||
<info>eval "$({$fullCommand} completion {$shell})"</>
|
||||
EOH
|
||||
)
|
||||
->addArgument('shell', InputArgument::OPTIONAL, 'The shell type (e.g. "bash"), the value of the "$SHELL" env var will be used if this is not given', null, $this->getSupportedShells(...))
|
||||
->addOption('debug', null, InputOption::VALUE_NONE, 'Tail the completion debug log')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$commandName = basename($_SERVER['argv'][0]);
|
||||
|
||||
if ($input->getOption('debug')) {
|
||||
$this->tailDebugLog($commandName, $output);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
$shell = $input->getArgument('shell') ?? self::guessShell();
|
||||
$completionFile = __DIR__.'/../Resources/completion.'.$shell;
|
||||
if (!file_exists($completionFile)) {
|
||||
$supportedShells = $this->getSupportedShells();
|
||||
|
||||
if ($output instanceof ConsoleOutputInterface) {
|
||||
$output = $output->getErrorOutput();
|
||||
}
|
||||
if ($shell) {
|
||||
$output->writeln(\sprintf('<error>Detected shell "%s", which is not supported by Symfony shell completion (supported shells: "%s").</>', $shell, implode('", "', $supportedShells)));
|
||||
} else {
|
||||
$output->writeln(\sprintf('<error>Shell not detected, Symfony shell completion only supports "%s").</>', implode('", "', $supportedShells)));
|
||||
}
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
$output->write(str_replace(['{{ COMMAND_NAME }}', '{{ VERSION }}'], [$commandName, CompleteCommand::COMPLETION_API_VERSION], file_get_contents($completionFile)));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static function guessShell(): string
|
||||
{
|
||||
return basename($_SERVER['SHELL'] ?? '');
|
||||
}
|
||||
|
||||
private function tailDebugLog(string $commandName, OutputInterface $output): void
|
||||
{
|
||||
$debugFile = sys_get_temp_dir().'/sf_'.$commandName.'.log';
|
||||
if (!file_exists($debugFile)) {
|
||||
touch($debugFile);
|
||||
}
|
||||
$process = new Process(['tail', '-f', $debugFile], null, null, null, 0);
|
||||
$process->run(function (string $type, string $line) use ($output): void {
|
||||
$output->write($line);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function getSupportedShells(): array
|
||||
{
|
||||
if (isset($this->supportedShells)) {
|
||||
return $this->supportedShells;
|
||||
}
|
||||
|
||||
$shells = [];
|
||||
|
||||
foreach (new \DirectoryIterator(__DIR__.'/../Resources/') as $file) {
|
||||
if (str_starts_with($file->getBasename(), 'completion.') && $file->isFile()) {
|
||||
$shells[] = $file->getExtension();
|
||||
}
|
||||
}
|
||||
sort($shells);
|
||||
|
||||
return $this->supportedShells = $shells;
|
||||
}
|
||||
}
|
||||
82
vendor/symfony/console/Command/HelpCommand.php
vendored
Normal file
82
vendor/symfony/console/Command/HelpCommand.php
vendored
Normal file
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Command;
|
||||
|
||||
use Symfony\Component\Console\Descriptor\ApplicationDescription;
|
||||
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 $command;
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this->ignoreValidationErrors();
|
||||
|
||||
$this
|
||||
->setName('help')
|
||||
->setDefinition([
|
||||
new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help', fn () => array_keys((new ApplicationDescription($this->getApplication()))->getCommands())),
|
||||
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt', fn () => (new DescriptorHelper())->getFormats()),
|
||||
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'),
|
||||
])
|
||||
->setDescription('Display help for a command')
|
||||
->setHelp(<<<'EOF'
|
||||
The <info>%command.name%</info> command displays help for a given command:
|
||||
|
||||
<info>%command.full_name% list</info>
|
||||
|
||||
You can also output the help in other formats by using the <comment>--format</comment> option:
|
||||
|
||||
<info>%command.full_name% --format=xml list</info>
|
||||
|
||||
To display the list of available commands, please use the <info>list</info> command.
|
||||
EOF
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function setCommand(Command $command)
|
||||
{
|
||||
$this->command = $command;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$this->command ??= $this->getApplication()->find($input->getArgument('command_name'));
|
||||
|
||||
$helper = new DescriptorHelper();
|
||||
$helper->describe($output, $this->command, [
|
||||
'format' => $input->getOption('format'),
|
||||
'raw_text' => $input->getOption('raw'),
|
||||
]);
|
||||
|
||||
unset($this->command);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
207
vendor/symfony/console/Command/LazyCommand.php
vendored
Normal file
207
vendor/symfony/console/Command/LazyCommand.php
vendored
Normal file
@ -0,0 +1,207 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Command;
|
||||
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Completion\CompletionInput;
|
||||
use Symfony\Component\Console\Completion\CompletionSuggestions;
|
||||
use Symfony\Component\Console\Completion\Suggestion;
|
||||
use Symfony\Component\Console\Helper\HelperInterface;
|
||||
use Symfony\Component\Console\Helper\HelperSet;
|
||||
use Symfony\Component\Console\Input\InputDefinition;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
final class LazyCommand extends Command
|
||||
{
|
||||
private \Closure|Command $command;
|
||||
private ?bool $isEnabled;
|
||||
|
||||
public function __construct(string $name, array $aliases, string $description, bool $isHidden, \Closure $commandFactory, ?bool $isEnabled = true)
|
||||
{
|
||||
$this->setName($name)
|
||||
->setAliases($aliases)
|
||||
->setHidden($isHidden)
|
||||
->setDescription($description);
|
||||
|
||||
$this->command = $commandFactory;
|
||||
$this->isEnabled = $isEnabled;
|
||||
}
|
||||
|
||||
public function ignoreValidationErrors(): void
|
||||
{
|
||||
$this->getCommand()->ignoreValidationErrors();
|
||||
}
|
||||
|
||||
public function setApplication(?Application $application = null): void
|
||||
{
|
||||
if (1 > \func_num_args()) {
|
||||
trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
|
||||
}
|
||||
if ($this->command instanceof parent) {
|
||||
$this->command->setApplication($application);
|
||||
}
|
||||
|
||||
parent::setApplication($application);
|
||||
}
|
||||
|
||||
public function setHelperSet(HelperSet $helperSet): void
|
||||
{
|
||||
if ($this->command instanceof parent) {
|
||||
$this->command->setHelperSet($helperSet);
|
||||
}
|
||||
|
||||
parent::setHelperSet($helperSet);
|
||||
}
|
||||
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
return $this->isEnabled ?? $this->getCommand()->isEnabled();
|
||||
}
|
||||
|
||||
public function run(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
return $this->getCommand()->run($input, $output);
|
||||
}
|
||||
|
||||
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
|
||||
{
|
||||
$this->getCommand()->complete($input, $suggestions);
|
||||
}
|
||||
|
||||
public function setCode(callable $code): static
|
||||
{
|
||||
$this->getCommand()->setCode($code);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function mergeApplicationDefinition(bool $mergeArgs = true): void
|
||||
{
|
||||
$this->getCommand()->mergeApplicationDefinition($mergeArgs);
|
||||
}
|
||||
|
||||
public function setDefinition(array|InputDefinition $definition): static
|
||||
{
|
||||
$this->getCommand()->setDefinition($definition);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDefinition(): InputDefinition
|
||||
{
|
||||
return $this->getCommand()->getDefinition();
|
||||
}
|
||||
|
||||
public function getNativeDefinition(): InputDefinition
|
||||
{
|
||||
return $this->getCommand()->getNativeDefinition();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion> $suggestedValues The values used for input completion
|
||||
*/
|
||||
public function addArgument(string $name, ?int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = [] */): static
|
||||
{
|
||||
$suggestedValues = 5 <= \func_num_args() ? func_get_arg(4) : [];
|
||||
$this->getCommand()->addArgument($name, $mode, $description, $default, $suggestedValues);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion> $suggestedValues The values used for input completion
|
||||
*/
|
||||
public function addOption(string $name, string|array|null $shortcut = null, ?int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = [] */): static
|
||||
{
|
||||
$suggestedValues = 6 <= \func_num_args() ? func_get_arg(5) : [];
|
||||
$this->getCommand()->addOption($name, $shortcut, $mode, $description, $default, $suggestedValues);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setProcessTitle(string $title): static
|
||||
{
|
||||
$this->getCommand()->setProcessTitle($title);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setHelp(string $help): static
|
||||
{
|
||||
$this->getCommand()->setHelp($help);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getHelp(): string
|
||||
{
|
||||
return $this->getCommand()->getHelp();
|
||||
}
|
||||
|
||||
public function getProcessedHelp(): string
|
||||
{
|
||||
return $this->getCommand()->getProcessedHelp();
|
||||
}
|
||||
|
||||
public function getSynopsis(bool $short = false): string
|
||||
{
|
||||
return $this->getCommand()->getSynopsis($short);
|
||||
}
|
||||
|
||||
public function addUsage(string $usage): static
|
||||
{
|
||||
$this->getCommand()->addUsage($usage);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUsages(): array
|
||||
{
|
||||
return $this->getCommand()->getUsages();
|
||||
}
|
||||
|
||||
public function getHelper(string $name): HelperInterface
|
||||
{
|
||||
return $this->getCommand()->getHelper($name);
|
||||
}
|
||||
|
||||
public function getCommand(): parent
|
||||
{
|
||||
if (!$this->command instanceof \Closure) {
|
||||
return $this->command;
|
||||
}
|
||||
|
||||
$command = $this->command = ($this->command)();
|
||||
$command->setApplication($this->getApplication());
|
||||
|
||||
if (null !== $this->getHelperSet()) {
|
||||
$command->setHelperSet($this->getHelperSet());
|
||||
}
|
||||
|
||||
$command->setName($this->getName())
|
||||
->setAliases($this->getAliases())
|
||||
->setHidden($this->isHidden())
|
||||
->setDescription($this->getDescription());
|
||||
|
||||
// Will throw if the command is not correctly initialized.
|
||||
$command->getDefinition();
|
||||
|
||||
return $command;
|
||||
}
|
||||
}
|
||||
75
vendor/symfony/console/Command/ListCommand.php
vendored
Normal file
75
vendor/symfony/console/Command/ListCommand.php
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@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\Descriptor\ApplicationDescription;
|
||||
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;
|
||||
|
||||
/**
|
||||
* ListCommand displays the list of all available commands for the application.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class ListCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('list')
|
||||
->setDefinition([
|
||||
new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name', null, fn () => array_keys((new ApplicationDescription($this->getApplication()))->getNamespaces())),
|
||||
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', fn () => (new DescriptorHelper())->getFormats()),
|
||||
new InputOption('short', null, InputOption::VALUE_NONE, 'To skip describing commands\' arguments'),
|
||||
])
|
||||
->setDescription('List commands')
|
||||
->setHelp(<<<'EOF'
|
||||
The <info>%command.name%</info> command lists all commands:
|
||||
|
||||
<info>%command.full_name%</info>
|
||||
|
||||
You can also display the commands for a specific namespace:
|
||||
|
||||
<info>%command.full_name% test</info>
|
||||
|
||||
You can also output the information in other formats by using the <comment>--format</comment> option:
|
||||
|
||||
<info>%command.full_name% --format=xml</info>
|
||||
|
||||
It's also possible to get raw list of commands (useful for embedding command runner):
|
||||
|
||||
<info>%command.full_name% --raw</info>
|
||||
EOF
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$helper = new DescriptorHelper();
|
||||
$helper->describe($output, $this->getApplication(), [
|
||||
'format' => $input->getOption('format'),
|
||||
'raw_text' => $input->getOption('raw'),
|
||||
'namespace' => $input->getArgument('namespace'),
|
||||
'short' => $input->getOption('short'),
|
||||
]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
68
vendor/symfony/console/Command/LockableTrait.php
vendored
Normal file
68
vendor/symfony/console/Command/LockableTrait.php
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Command;
|
||||
|
||||
use Symfony\Component\Console\Exception\LogicException;
|
||||
use Symfony\Component\Lock\LockFactory;
|
||||
use Symfony\Component\Lock\LockInterface;
|
||||
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
|
||||
{
|
||||
private ?LockInterface $lock = null;
|
||||
|
||||
/**
|
||||
* Locks a command.
|
||||
*/
|
||||
private function lock(?string $name = null, bool $blocking = false): bool
|
||||
{
|
||||
if (!class_exists(SemaphoreStore::class)) {
|
||||
throw new LogicException('To enable the locking feature you must install the symfony/lock component. Try running "composer require symfony/lock".');
|
||||
}
|
||||
|
||||
if (null !== $this->lock) {
|
||||
throw new LogicException('A lock is already in place.');
|
||||
}
|
||||
|
||||
if (SemaphoreStore::isSupported()) {
|
||||
$store = new SemaphoreStore();
|
||||
} else {
|
||||
$store = new FlockStore();
|
||||
}
|
||||
|
||||
$this->lock = (new LockFactory($store))->createLock($name ?: $this->getName());
|
||||
if (!$this->lock->acquire($blocking)) {
|
||||
$this->lock = null;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the command lock if there is one.
|
||||
*/
|
||||
private function release(): void
|
||||
{
|
||||
if ($this->lock) {
|
||||
$this->lock->release();
|
||||
$this->lock = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
34
vendor/symfony/console/Command/SignalableCommandInterface.php
vendored
Normal file
34
vendor/symfony/console/Command/SignalableCommandInterface.php
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@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;
|
||||
|
||||
/**
|
||||
* Interface for command reacting to signal.
|
||||
*
|
||||
* @author Grégoire Pineau <lyrixx@lyrix.info>
|
||||
*/
|
||||
interface SignalableCommandInterface
|
||||
{
|
||||
/**
|
||||
* Returns the list of signals to subscribe.
|
||||
*/
|
||||
public function getSubscribedSignals(): array;
|
||||
|
||||
/**
|
||||
* The method will be called when the application is signaled.
|
||||
*
|
||||
* @param int|false $previousExitCode
|
||||
*
|
||||
* @return int|false The exit code to return or false to continue the normal execution
|
||||
*/
|
||||
public function handleSignal(int $signal/* , int|false $previousExitCode = 0 */);
|
||||
}
|
||||
356
vendor/symfony/console/Command/TraceableCommand.php
vendored
Normal file
356
vendor/symfony/console/Command/TraceableCommand.php
vendored
Normal file
@ -0,0 +1,356 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@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\Completion\CompletionInput;
|
||||
use Symfony\Component\Console\Completion\CompletionSuggestions;
|
||||
use Symfony\Component\Console\Helper\HelperInterface;
|
||||
use Symfony\Component\Console\Helper\HelperSet;
|
||||
use Symfony\Component\Console\Input\InputDefinition;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Stopwatch\Stopwatch;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @author Jules Pietri <jules@heahprod.com>
|
||||
*/
|
||||
final class TraceableCommand extends Command implements SignalableCommandInterface
|
||||
{
|
||||
public readonly Command $command;
|
||||
public int $exitCode;
|
||||
public ?int $interruptedBySignal = null;
|
||||
public bool $ignoreValidation;
|
||||
public bool $isInteractive = false;
|
||||
public string $duration = 'n/a';
|
||||
public string $maxMemoryUsage = 'n/a';
|
||||
public InputInterface $input;
|
||||
public OutputInterface $output;
|
||||
/** @var array<string, mixed> */
|
||||
public array $arguments;
|
||||
/** @var array<string, mixed> */
|
||||
public array $options;
|
||||
/** @var array<string, mixed> */
|
||||
public array $interactiveInputs = [];
|
||||
public array $handledSignals = [];
|
||||
|
||||
public function __construct(
|
||||
Command $command,
|
||||
private readonly Stopwatch $stopwatch,
|
||||
) {
|
||||
if ($command instanceof LazyCommand) {
|
||||
$command = $command->getCommand();
|
||||
}
|
||||
|
||||
$this->command = $command;
|
||||
|
||||
// prevent call to self::getDefaultDescription()
|
||||
$this->setDescription($command->getDescription());
|
||||
|
||||
parent::__construct($command->getName());
|
||||
|
||||
// init below enables calling {@see parent::run()}
|
||||
[$code, $processTitle, $ignoreValidationErrors] = \Closure::bind(function () {
|
||||
return [$this->code, $this->processTitle, $this->ignoreValidationErrors];
|
||||
}, $command, Command::class)();
|
||||
|
||||
if (\is_callable($code)) {
|
||||
$this->setCode($code);
|
||||
}
|
||||
|
||||
if ($processTitle) {
|
||||
parent::setProcessTitle($processTitle);
|
||||
}
|
||||
|
||||
if ($ignoreValidationErrors) {
|
||||
parent::ignoreValidationErrors();
|
||||
}
|
||||
|
||||
$this->ignoreValidation = $ignoreValidationErrors;
|
||||
}
|
||||
|
||||
public function __call(string $name, array $arguments): mixed
|
||||
{
|
||||
return $this->command->{$name}(...$arguments);
|
||||
}
|
||||
|
||||
public function getSubscribedSignals(): array
|
||||
{
|
||||
return $this->command instanceof SignalableCommandInterface ? $this->command->getSubscribedSignals() : [];
|
||||
}
|
||||
|
||||
public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false
|
||||
{
|
||||
if (!$this->command instanceof SignalableCommandInterface) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$event = $this->stopwatch->start($this->getName().'.handle_signal');
|
||||
|
||||
$exit = $this->command->handleSignal($signal, $previousExitCode);
|
||||
|
||||
$event->stop();
|
||||
|
||||
if (!isset($this->handledSignals[$signal])) {
|
||||
$this->handledSignals[$signal] = [
|
||||
'handled' => 0,
|
||||
'duration' => 0,
|
||||
'memory' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
++$this->handledSignals[$signal]['handled'];
|
||||
$this->handledSignals[$signal]['duration'] += $event->getDuration();
|
||||
$this->handledSignals[$signal]['memory'] = max(
|
||||
$this->handledSignals[$signal]['memory'],
|
||||
$event->getMemory() >> 20
|
||||
);
|
||||
|
||||
return $exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Calling parent method is required to be used in {@see parent::run()}.
|
||||
*/
|
||||
public function ignoreValidationErrors(): void
|
||||
{
|
||||
$this->ignoreValidation = true;
|
||||
$this->command->ignoreValidationErrors();
|
||||
|
||||
parent::ignoreValidationErrors();
|
||||
}
|
||||
|
||||
public function setApplication(?Application $application = null): void
|
||||
{
|
||||
$this->command->setApplication($application);
|
||||
}
|
||||
|
||||
public function getApplication(): ?Application
|
||||
{
|
||||
return $this->command->getApplication();
|
||||
}
|
||||
|
||||
public function setHelperSet(HelperSet $helperSet): void
|
||||
{
|
||||
$this->command->setHelperSet($helperSet);
|
||||
}
|
||||
|
||||
public function getHelperSet(): ?HelperSet
|
||||
{
|
||||
return $this->command->getHelperSet();
|
||||
}
|
||||
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
return $this->command->isEnabled();
|
||||
}
|
||||
|
||||
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
|
||||
{
|
||||
$this->command->complete($input, $suggestions);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Calling parent method is required to be used in {@see parent::run()}.
|
||||
*/
|
||||
public function setCode(callable $code): static
|
||||
{
|
||||
$this->command->setCode($code);
|
||||
|
||||
return parent::setCode(function (InputInterface $input, OutputInterface $output) use ($code): int {
|
||||
$event = $this->stopwatch->start($this->getName().'.code');
|
||||
|
||||
$this->exitCode = $code($input, $output);
|
||||
|
||||
$event->stop();
|
||||
|
||||
return $this->exitCode;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function mergeApplicationDefinition(bool $mergeArgs = true): void
|
||||
{
|
||||
$this->command->mergeApplicationDefinition($mergeArgs);
|
||||
}
|
||||
|
||||
public function setDefinition(array|InputDefinition $definition): static
|
||||
{
|
||||
$this->command->setDefinition($definition);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDefinition(): InputDefinition
|
||||
{
|
||||
return $this->command->getDefinition();
|
||||
}
|
||||
|
||||
public function getNativeDefinition(): InputDefinition
|
||||
{
|
||||
return $this->command->getNativeDefinition();
|
||||
}
|
||||
|
||||
public function addArgument(string $name, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static
|
||||
{
|
||||
$this->command->addArgument($name, $mode, $description, $default, $suggestedValues);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addOption(string $name, string|array|null $shortcut = null, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static
|
||||
{
|
||||
$this->command->addOption($name, $shortcut, $mode, $description, $default, $suggestedValues);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Calling parent method is required to be used in {@see parent::run()}.
|
||||
*/
|
||||
public function setProcessTitle(string $title): static
|
||||
{
|
||||
$this->command->setProcessTitle($title);
|
||||
|
||||
return parent::setProcessTitle($title);
|
||||
}
|
||||
|
||||
public function setHelp(string $help): static
|
||||
{
|
||||
$this->command->setHelp($help);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getHelp(): string
|
||||
{
|
||||
return $this->command->getHelp();
|
||||
}
|
||||
|
||||
public function getProcessedHelp(): string
|
||||
{
|
||||
return $this->command->getProcessedHelp();
|
||||
}
|
||||
|
||||
public function getSynopsis(bool $short = false): string
|
||||
{
|
||||
return $this->command->getSynopsis($short);
|
||||
}
|
||||
|
||||
public function addUsage(string $usage): static
|
||||
{
|
||||
$this->command->addUsage($usage);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUsages(): array
|
||||
{
|
||||
return $this->command->getUsages();
|
||||
}
|
||||
|
||||
public function getHelper(string $name): HelperInterface
|
||||
{
|
||||
return $this->command->getHelper($name);
|
||||
}
|
||||
|
||||
public function run(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$this->input = $input;
|
||||
$this->output = $output;
|
||||
$this->arguments = $input->getArguments();
|
||||
$this->options = $input->getOptions();
|
||||
$event = $this->stopwatch->start($this->getName(), 'command');
|
||||
|
||||
try {
|
||||
$this->exitCode = $this->command->run($input, $output);
|
||||
} finally {
|
||||
$event->stop();
|
||||
|
||||
if ($output instanceof ConsoleOutputInterface && $output->isDebug()) {
|
||||
$output->getErrorOutput()->writeln((string) $event);
|
||||
}
|
||||
|
||||
$this->duration = $event->getDuration().' ms';
|
||||
$this->maxMemoryUsage = ($event->getMemory() >> 20).' MiB';
|
||||
|
||||
if ($this->isInteractive) {
|
||||
$this->extractInteractiveInputs($input->getArguments(), $input->getOptions());
|
||||
}
|
||||
}
|
||||
|
||||
return $this->exitCode;
|
||||
}
|
||||
|
||||
protected function initialize(InputInterface $input, OutputInterface $output): void
|
||||
{
|
||||
$event = $this->stopwatch->start($this->getName().'.init', 'command');
|
||||
|
||||
$this->command->initialize($input, $output);
|
||||
|
||||
$event->stop();
|
||||
}
|
||||
|
||||
protected function interact(InputInterface $input, OutputInterface $output): void
|
||||
{
|
||||
if (!$this->isInteractive = Command::class !== (new \ReflectionMethod($this->command, 'interact'))->getDeclaringClass()->getName()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event = $this->stopwatch->start($this->getName().'.interact', 'command');
|
||||
|
||||
$this->command->interact($input, $output);
|
||||
|
||||
$event->stop();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$event = $this->stopwatch->start($this->getName().'.execute', 'command');
|
||||
|
||||
$exitCode = $this->command->execute($input, $output);
|
||||
|
||||
$event->stop();
|
||||
|
||||
return $exitCode;
|
||||
}
|
||||
|
||||
private function extractInteractiveInputs(array $arguments, array $options): void
|
||||
{
|
||||
foreach ($arguments as $argName => $argValue) {
|
||||
if (\array_key_exists($argName, $this->arguments) && $this->arguments[$argName] === $argValue) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->interactiveInputs[$argName] = $argValue;
|
||||
}
|
||||
|
||||
foreach ($options as $optName => $optValue) {
|
||||
if (\array_key_exists($optName, $this->options) && $this->options[$optName] === $optValue) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->interactiveInputs['--'.$optName] = $optValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
38
vendor/symfony/console/CommandLoader/CommandLoaderInterface.php
vendored
Normal file
38
vendor/symfony/console/CommandLoader/CommandLoaderInterface.php
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\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.
|
||||
*
|
||||
* @throws CommandNotFoundException
|
||||
*/
|
||||
public function get(string $name): Command;
|
||||
|
||||
/**
|
||||
* Checks if a command exists.
|
||||
*/
|
||||
public function has(string $name): bool;
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getNames(): array;
|
||||
}
|
||||
55
vendor/symfony/console/CommandLoader/ContainerCommandLoader.php
vendored
Normal file
55
vendor/symfony/console/CommandLoader/ContainerCommandLoader.php
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\CommandLoader;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
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 ContainerInterface $container;
|
||||
private array $commandMap;
|
||||
|
||||
/**
|
||||
* @param array $commandMap An array with command names as keys and service ids as values
|
||||
*/
|
||||
public function __construct(ContainerInterface $container, array $commandMap)
|
||||
{
|
||||
$this->container = $container;
|
||||
$this->commandMap = $commandMap;
|
||||
}
|
||||
|
||||
public function get(string $name): Command
|
||||
{
|
||||
if (!$this->has($name)) {
|
||||
throw new CommandNotFoundException(\sprintf('Command "%s" does not exist.', $name));
|
||||
}
|
||||
|
||||
return $this->container->get($this->commandMap[$name]);
|
||||
}
|
||||
|
||||
public function has(string $name): bool
|
||||
{
|
||||
return isset($this->commandMap[$name]) && $this->container->has($this->commandMap[$name]);
|
||||
}
|
||||
|
||||
public function getNames(): array
|
||||
{
|
||||
return array_keys($this->commandMap);
|
||||
}
|
||||
}
|
||||
54
vendor/symfony/console/CommandLoader/FactoryCommandLoader.php
vendored
Normal file
54
vendor/symfony/console/CommandLoader/FactoryCommandLoader.php
vendored
Normal 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\Console\CommandLoader;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
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 array $factories;
|
||||
|
||||
/**
|
||||
* @param callable[] $factories Indexed by command names
|
||||
*/
|
||||
public function __construct(array $factories)
|
||||
{
|
||||
$this->factories = $factories;
|
||||
}
|
||||
|
||||
public function has(string $name): bool
|
||||
{
|
||||
return isset($this->factories[$name]);
|
||||
}
|
||||
|
||||
public function get(string $name): Command
|
||||
{
|
||||
if (!isset($this->factories[$name])) {
|
||||
throw new CommandNotFoundException(\sprintf('Command "%s" does not exist.', $name));
|
||||
}
|
||||
|
||||
$factory = $this->factories[$name];
|
||||
|
||||
return $factory();
|
||||
}
|
||||
|
||||
public function getNames(): array
|
||||
{
|
||||
return array_keys($this->factories);
|
||||
}
|
||||
}
|
||||
248
vendor/symfony/console/Completion/CompletionInput.php
vendored
Normal file
248
vendor/symfony/console/Completion/CompletionInput.php
vendored
Normal file
@ -0,0 +1,248 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@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\Completion;
|
||||
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
use Symfony\Component\Console\Input\InputDefinition;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
/**
|
||||
* An input specialized for shell completion.
|
||||
*
|
||||
* This input allows unfinished option names or values and exposes what kind of
|
||||
* completion is expected.
|
||||
*
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*/
|
||||
final class CompletionInput extends ArgvInput
|
||||
{
|
||||
public const TYPE_ARGUMENT_VALUE = 'argument_value';
|
||||
public const TYPE_OPTION_VALUE = 'option_value';
|
||||
public const TYPE_OPTION_NAME = 'option_name';
|
||||
public const TYPE_NONE = 'none';
|
||||
|
||||
private array $tokens;
|
||||
private int $currentIndex;
|
||||
private string $completionType;
|
||||
private ?string $completionName = null;
|
||||
private string $completionValue = '';
|
||||
|
||||
/**
|
||||
* Converts a terminal string into tokens.
|
||||
*
|
||||
* This is required for shell completions without COMP_WORDS support.
|
||||
*/
|
||||
public static function fromString(string $inputStr, int $currentIndex): self
|
||||
{
|
||||
preg_match_all('/(?<=^|\s)([\'"]?)(.+?)(?<!\\\\)\1(?=$|\s)/', $inputStr, $tokens);
|
||||
|
||||
return self::fromTokens($tokens[0], $currentIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an input based on an COMP_WORDS token list.
|
||||
*
|
||||
* @param string[] $tokens the set of split tokens (e.g. COMP_WORDS or argv)
|
||||
* @param int $currentIndex the index of the cursor (e.g. COMP_CWORD)
|
||||
*/
|
||||
public static function fromTokens(array $tokens, int $currentIndex): self
|
||||
{
|
||||
$input = new self($tokens);
|
||||
$input->tokens = $tokens;
|
||||
$input->currentIndex = $currentIndex;
|
||||
|
||||
return $input;
|
||||
}
|
||||
|
||||
public function bind(InputDefinition $definition): void
|
||||
{
|
||||
parent::bind($definition);
|
||||
|
||||
$relevantToken = $this->getRelevantToken();
|
||||
if ('-' === $relevantToken[0]) {
|
||||
// the current token is an input option: complete either option name or option value
|
||||
[$optionToken, $optionValue] = explode('=', $relevantToken, 2) + ['', ''];
|
||||
|
||||
$option = $this->getOptionFromToken($optionToken);
|
||||
if (null === $option && !$this->isCursorFree()) {
|
||||
$this->completionType = self::TYPE_OPTION_NAME;
|
||||
$this->completionValue = $relevantToken;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($option?->acceptValue()) {
|
||||
$this->completionType = self::TYPE_OPTION_VALUE;
|
||||
$this->completionName = $option->getName();
|
||||
$this->completionValue = $optionValue ?: (!str_starts_with($optionToken, '--') ? substr($optionToken, 2) : '');
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$previousToken = $this->tokens[$this->currentIndex - 1];
|
||||
if ('-' === $previousToken[0] && '' !== trim($previousToken, '-')) {
|
||||
// check if previous option accepted a value
|
||||
$previousOption = $this->getOptionFromToken($previousToken);
|
||||
if ($previousOption?->acceptValue()) {
|
||||
$this->completionType = self::TYPE_OPTION_VALUE;
|
||||
$this->completionName = $previousOption->getName();
|
||||
$this->completionValue = $relevantToken;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// complete argument value
|
||||
$this->completionType = self::TYPE_ARGUMENT_VALUE;
|
||||
|
||||
foreach ($this->definition->getArguments() as $argumentName => $argument) {
|
||||
if (!isset($this->arguments[$argumentName])) {
|
||||
break;
|
||||
}
|
||||
|
||||
$argumentValue = $this->arguments[$argumentName];
|
||||
$this->completionName = $argumentName;
|
||||
if (\is_array($argumentValue)) {
|
||||
$this->completionValue = $argumentValue ? $argumentValue[array_key_last($argumentValue)] : null;
|
||||
} else {
|
||||
$this->completionValue = $argumentValue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->currentIndex >= \count($this->tokens)) {
|
||||
if (!isset($this->arguments[$argumentName]) || $this->definition->getArgument($argumentName)->isArray()) {
|
||||
$this->completionName = $argumentName;
|
||||
$this->completionValue = '';
|
||||
} else {
|
||||
// we've reached the end
|
||||
$this->completionType = self::TYPE_NONE;
|
||||
$this->completionName = null;
|
||||
$this->completionValue = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of completion required.
|
||||
*
|
||||
* TYPE_ARGUMENT_VALUE when completing the value of an input argument
|
||||
* TYPE_OPTION_VALUE when completing the value of an input option
|
||||
* TYPE_OPTION_NAME when completing the name of an input option
|
||||
* TYPE_NONE when nothing should be completed
|
||||
*
|
||||
* TYPE_OPTION_NAME and TYPE_NONE are already implemented by the Console component.
|
||||
*
|
||||
* @return self::TYPE_*
|
||||
*/
|
||||
public function getCompletionType(): string
|
||||
{
|
||||
return $this->completionType;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the input option or argument when completing a value.
|
||||
*
|
||||
* @return string|null returns null when completing an option name
|
||||
*/
|
||||
public function getCompletionName(): ?string
|
||||
{
|
||||
return $this->completionName;
|
||||
}
|
||||
|
||||
/**
|
||||
* The value already typed by the user (or empty string).
|
||||
*/
|
||||
public function getCompletionValue(): string
|
||||
{
|
||||
return $this->completionValue;
|
||||
}
|
||||
|
||||
public function mustSuggestOptionValuesFor(string $optionName): bool
|
||||
{
|
||||
return self::TYPE_OPTION_VALUE === $this->getCompletionType() && $optionName === $this->getCompletionName();
|
||||
}
|
||||
|
||||
public function mustSuggestArgumentValuesFor(string $argumentName): bool
|
||||
{
|
||||
return self::TYPE_ARGUMENT_VALUE === $this->getCompletionType() && $argumentName === $this->getCompletionName();
|
||||
}
|
||||
|
||||
protected function parseToken(string $token, bool $parseOptions): bool
|
||||
{
|
||||
try {
|
||||
return parent::parseToken($token, $parseOptions);
|
||||
} catch (RuntimeException) {
|
||||
// suppress errors, completed input is almost never valid
|
||||
}
|
||||
|
||||
return $parseOptions;
|
||||
}
|
||||
|
||||
private function getOptionFromToken(string $optionToken): ?InputOption
|
||||
{
|
||||
$optionName = ltrim($optionToken, '-');
|
||||
if (!$optionName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ('-' === ($optionToken[1] ?? ' ')) {
|
||||
// long option name
|
||||
return $this->definition->hasOption($optionName) ? $this->definition->getOption($optionName) : null;
|
||||
}
|
||||
|
||||
// short option name
|
||||
return $this->definition->hasShortcut($optionName[0]) ? $this->definition->getOptionForShortcut($optionName[0]) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The token of the cursor, or the last token if the cursor is at the end of the input.
|
||||
*/
|
||||
private function getRelevantToken(): string
|
||||
{
|
||||
return $this->tokens[$this->isCursorFree() ? $this->currentIndex - 1 : $this->currentIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the cursor is "free" (i.e. at the end of the input preceded by a space).
|
||||
*/
|
||||
private function isCursorFree(): bool
|
||||
{
|
||||
$nrOfTokens = \count($this->tokens);
|
||||
if ($this->currentIndex > $nrOfTokens) {
|
||||
throw new \LogicException('Current index is invalid, it must be the number of input tokens or one more.');
|
||||
}
|
||||
|
||||
return $this->currentIndex >= $nrOfTokens;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
$str = '';
|
||||
foreach ($this->tokens as $i => $token) {
|
||||
$str .= $token;
|
||||
|
||||
if ($this->currentIndex === $i) {
|
||||
$str .= '|';
|
||||
}
|
||||
|
||||
$str .= ' ';
|
||||
}
|
||||
|
||||
if ($this->currentIndex > $i) {
|
||||
$str .= '|';
|
||||
}
|
||||
|
||||
return rtrim($str);
|
||||
}
|
||||
}
|
||||
97
vendor/symfony/console/Completion/CompletionSuggestions.php
vendored
Normal file
97
vendor/symfony/console/Completion/CompletionSuggestions.php
vendored
Normal file
@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Completion;
|
||||
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
/**
|
||||
* Stores all completion suggestions for the current input.
|
||||
*
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*/
|
||||
final class CompletionSuggestions
|
||||
{
|
||||
private array $valueSuggestions = [];
|
||||
private array $optionSuggestions = [];
|
||||
|
||||
/**
|
||||
* Add a suggested value for an input option or argument.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function suggestValue(string|Suggestion $value): static
|
||||
{
|
||||
$this->valueSuggestions[] = !$value instanceof Suggestion ? new Suggestion($value) : $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add multiple suggested values at once for an input option or argument.
|
||||
*
|
||||
* @param list<string|Suggestion> $values
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function suggestValues(array $values): static
|
||||
{
|
||||
foreach ($values as $value) {
|
||||
$this->suggestValue($value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a suggestion for an input option name.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function suggestOption(InputOption $option): static
|
||||
{
|
||||
$this->optionSuggestions[] = $option;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add multiple suggestions for input option names at once.
|
||||
*
|
||||
* @param InputOption[] $options
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function suggestOptions(array $options): static
|
||||
{
|
||||
foreach ($options as $option) {
|
||||
$this->suggestOption($option);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return InputOption[]
|
||||
*/
|
||||
public function getOptionSuggestions(): array
|
||||
{
|
||||
return $this->optionSuggestions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Suggestion[]
|
||||
*/
|
||||
public function getValueSuggestions(): array
|
||||
{
|
||||
return $this->valueSuggestions;
|
||||
}
|
||||
}
|
||||
33
vendor/symfony/console/Completion/Output/BashCompletionOutput.php
vendored
Normal file
33
vendor/symfony/console/Completion/Output/BashCompletionOutput.php
vendored
Normal 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\Completion\Output;
|
||||
|
||||
use Symfony\Component\Console\Completion\CompletionSuggestions;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*/
|
||||
class BashCompletionOutput implements CompletionOutputInterface
|
||||
{
|
||||
public function write(CompletionSuggestions $suggestions, OutputInterface $output): void
|
||||
{
|
||||
$values = $suggestions->getValueSuggestions();
|
||||
foreach ($suggestions->getOptionSuggestions() as $option) {
|
||||
$values[] = '--'.$option->getName();
|
||||
if ($option->isNegatable()) {
|
||||
$values[] = '--no-'.$option->getName();
|
||||
}
|
||||
}
|
||||
$output->writeln(implode("\n", $values));
|
||||
}
|
||||
}
|
||||
25
vendor/symfony/console/Completion/Output/CompletionOutputInterface.php
vendored
Normal file
25
vendor/symfony/console/Completion/Output/CompletionOutputInterface.php
vendored
Normal 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\Completion\Output;
|
||||
|
||||
use Symfony\Component\Console\Completion\CompletionSuggestions;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Transforms the {@see CompletionSuggestions} object into output readable by the shell completion.
|
||||
*
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*/
|
||||
interface CompletionOutputInterface
|
||||
{
|
||||
public function write(CompletionSuggestions $suggestions, OutputInterface $output): void;
|
||||
}
|
||||
33
vendor/symfony/console/Completion/Output/FishCompletionOutput.php
vendored
Normal file
33
vendor/symfony/console/Completion/Output/FishCompletionOutput.php
vendored
Normal 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\Completion\Output;
|
||||
|
||||
use Symfony\Component\Console\Completion\CompletionSuggestions;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* @author Guillaume Aveline <guillaume.aveline@pm.me>
|
||||
*/
|
||||
class FishCompletionOutput implements CompletionOutputInterface
|
||||
{
|
||||
public function write(CompletionSuggestions $suggestions, OutputInterface $output): void
|
||||
{
|
||||
$values = $suggestions->getValueSuggestions();
|
||||
foreach ($suggestions->getOptionSuggestions() as $option) {
|
||||
$values[] = '--'.$option->getName();
|
||||
if ($option->isNegatable()) {
|
||||
$values[] = '--no-'.$option->getName();
|
||||
}
|
||||
}
|
||||
$output->write(implode("\n", $values));
|
||||
}
|
||||
}
|
||||
36
vendor/symfony/console/Completion/Output/ZshCompletionOutput.php
vendored
Normal file
36
vendor/symfony/console/Completion/Output/ZshCompletionOutput.php
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Completion\Output;
|
||||
|
||||
use Symfony\Component\Console\Completion\CompletionSuggestions;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* @author Jitendra A <adhocore@gmail.com>
|
||||
*/
|
||||
class ZshCompletionOutput implements CompletionOutputInterface
|
||||
{
|
||||
public function write(CompletionSuggestions $suggestions, OutputInterface $output): void
|
||||
{
|
||||
$values = [];
|
||||
foreach ($suggestions->getValueSuggestions() as $value) {
|
||||
$values[] = $value->getValue().($value->getDescription() ? "\t".$value->getDescription() : '');
|
||||
}
|
||||
foreach ($suggestions->getOptionSuggestions() as $option) {
|
||||
$values[] = '--'.$option->getName().($option->getDescription() ? "\t".$option->getDescription() : '');
|
||||
if ($option->isNegatable()) {
|
||||
$values[] = '--no-'.$option->getName().($option->getDescription() ? "\t".$option->getDescription() : '');
|
||||
}
|
||||
}
|
||||
$output->write(implode("\n", $values)."\n");
|
||||
}
|
||||
}
|
||||
41
vendor/symfony/console/Completion/Suggestion.php
vendored
Normal file
41
vendor/symfony/console/Completion/Suggestion.php
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Completion;
|
||||
|
||||
/**
|
||||
* Represents a single suggested value.
|
||||
*
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*/
|
||||
class Suggestion implements \Stringable
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $value,
|
||||
private readonly string $description = '',
|
||||
) {
|
||||
}
|
||||
|
||||
public function getValue(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->getValue();
|
||||
}
|
||||
}
|
||||
72
vendor/symfony/console/ConsoleEvents.php
vendored
Normal file
72
vendor/symfony/console/ConsoleEvents.php
vendored
Normal 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;
|
||||
|
||||
use Symfony\Component\Console\Event\ConsoleCommandEvent;
|
||||
use Symfony\Component\Console\Event\ConsoleErrorEvent;
|
||||
use Symfony\Component\Console\Event\ConsoleSignalEvent;
|
||||
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
|
||||
|
||||
/**
|
||||
* Contains all events dispatched by an Application.
|
||||
*
|
||||
* @author Francesco Levorato <git@flevour.net>
|
||||
*/
|
||||
final class ConsoleEvents
|
||||
{
|
||||
/**
|
||||
* The COMMAND event allows you to attach listeners before any command is
|
||||
* executed by the console. It also allows you to modify the command, input and output
|
||||
* before they are handed to the command.
|
||||
*
|
||||
* @Event("Symfony\Component\Console\Event\ConsoleCommandEvent")
|
||||
*/
|
||||
public const COMMAND = 'console.command';
|
||||
|
||||
/**
|
||||
* The SIGNAL event allows you to perform some actions
|
||||
* after the command execution was interrupted.
|
||||
*
|
||||
* @Event("Symfony\Component\Console\Event\ConsoleSignalEvent")
|
||||
*/
|
||||
public const SIGNAL = 'console.signal';
|
||||
|
||||
/**
|
||||
* The TERMINATE event allows you to attach listeners after a command is
|
||||
* executed by the console.
|
||||
*
|
||||
* @Event("Symfony\Component\Console\Event\ConsoleTerminateEvent")
|
||||
*/
|
||||
public const TERMINATE = 'console.terminate';
|
||||
|
||||
/**
|
||||
* The ERROR event occurs when an uncaught exception or error appears.
|
||||
*
|
||||
* This event allows you to deal with the exception/error or
|
||||
* to modify the thrown exception.
|
||||
*
|
||||
* @Event("Symfony\Component\Console\Event\ConsoleErrorEvent")
|
||||
*/
|
||||
public const ERROR = 'console.error';
|
||||
|
||||
/**
|
||||
* Event aliases.
|
||||
*
|
||||
* These aliases can be consumed by RegisterListenersPass.
|
||||
*/
|
||||
public const ALIASES = [
|
||||
ConsoleCommandEvent::class => self::COMMAND,
|
||||
ConsoleErrorEvent::class => self::ERROR,
|
||||
ConsoleSignalEvent::class => self::SIGNAL,
|
||||
ConsoleTerminateEvent::class => self::TERMINATE,
|
||||
];
|
||||
}
|
||||
204
vendor/symfony/console/Cursor.php
vendored
Normal file
204
vendor/symfony/console/Cursor.php
vendored
Normal file
@ -0,0 +1,204 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console;
|
||||
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* @author Pierre du Plessis <pdples@gmail.com>
|
||||
*/
|
||||
final class Cursor
|
||||
{
|
||||
private OutputInterface $output;
|
||||
/** @var resource */
|
||||
private $input;
|
||||
|
||||
/**
|
||||
* @param resource|null $input
|
||||
*/
|
||||
public function __construct(OutputInterface $output, $input = null)
|
||||
{
|
||||
$this->output = $output;
|
||||
$this->input = $input ?? (\defined('STDIN') ? \STDIN : fopen('php://input', 'r+'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function moveUp(int $lines = 1): static
|
||||
{
|
||||
$this->output->write(\sprintf("\x1b[%dA", $lines));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function moveDown(int $lines = 1): static
|
||||
{
|
||||
$this->output->write(\sprintf("\x1b[%dB", $lines));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function moveRight(int $columns = 1): static
|
||||
{
|
||||
$this->output->write(\sprintf("\x1b[%dC", $columns));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function moveLeft(int $columns = 1): static
|
||||
{
|
||||
$this->output->write(\sprintf("\x1b[%dD", $columns));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function moveToColumn(int $column): static
|
||||
{
|
||||
$this->output->write(\sprintf("\x1b[%dG", $column));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function moveToPosition(int $column, int $row): static
|
||||
{
|
||||
$this->output->write(\sprintf("\x1b[%d;%dH", $row + 1, $column));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function savePosition(): static
|
||||
{
|
||||
$this->output->write("\x1b7");
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function restorePosition(): static
|
||||
{
|
||||
$this->output->write("\x1b8");
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function hide(): static
|
||||
{
|
||||
$this->output->write("\x1b[?25l");
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function show(): static
|
||||
{
|
||||
$this->output->write("\x1b[?25h\x1b[?0c");
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all the output from the current line.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function clearLine(): static
|
||||
{
|
||||
$this->output->write("\x1b[2K");
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all the output from the current line after the current position.
|
||||
*/
|
||||
public function clearLineAfter(): self
|
||||
{
|
||||
$this->output->write("\x1b[K");
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all the output from the cursors' current position to the end of the screen.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function clearOutput(): static
|
||||
{
|
||||
$this->output->write("\x1b[0J");
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the entire screen.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function clearScreen(): static
|
||||
{
|
||||
$this->output->write("\x1b[2J");
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current cursor position as x,y coordinates.
|
||||
*/
|
||||
public function getCurrentPosition(): array
|
||||
{
|
||||
static $isTtySupported;
|
||||
|
||||
if (!$isTtySupported ??= '/' === \DIRECTORY_SEPARATOR && stream_isatty(\STDOUT)) {
|
||||
return [1, 1];
|
||||
}
|
||||
|
||||
$sttyMode = shell_exec('stty -g');
|
||||
shell_exec('stty -icanon -echo');
|
||||
|
||||
@fwrite($this->input, "\033[6n");
|
||||
|
||||
$code = trim(fread($this->input, 1024));
|
||||
|
||||
shell_exec(\sprintf('stty %s', $sttyMode));
|
||||
|
||||
sscanf($code, "\033[%d;%dR", $row, $col);
|
||||
|
||||
return [$col, $row];
|
||||
}
|
||||
}
|
||||
234
vendor/symfony/console/DataCollector/CommandDataCollector.php
vendored
Normal file
234
vendor/symfony/console/DataCollector/CommandDataCollector.php
vendored
Normal file
@ -0,0 +1,234 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@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\DataCollector;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Debug\CliRequest;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\SignalRegistry\SignalMap;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
|
||||
use Symfony\Component\VarDumper\Cloner\Data;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @author Jules Pietri <jules@heahprod.com>
|
||||
*/
|
||||
final class CommandDataCollector extends DataCollector
|
||||
{
|
||||
public function collect(Request $request, Response $response, ?\Throwable $exception = null): void
|
||||
{
|
||||
if (!$request instanceof CliRequest) {
|
||||
return;
|
||||
}
|
||||
|
||||
$command = $request->command;
|
||||
$application = $command->getApplication();
|
||||
|
||||
$this->data = [
|
||||
'command' => $this->cloneVar($command->command),
|
||||
'exit_code' => $command->exitCode,
|
||||
'interrupted_by_signal' => $command->interruptedBySignal,
|
||||
'duration' => $command->duration,
|
||||
'max_memory_usage' => $command->maxMemoryUsage,
|
||||
'verbosity_level' => match ($command->output->getVerbosity()) {
|
||||
OutputInterface::VERBOSITY_QUIET => 'quiet',
|
||||
OutputInterface::VERBOSITY_NORMAL => 'normal',
|
||||
OutputInterface::VERBOSITY_VERBOSE => 'verbose',
|
||||
OutputInterface::VERBOSITY_VERY_VERBOSE => 'very verbose',
|
||||
OutputInterface::VERBOSITY_DEBUG => 'debug',
|
||||
},
|
||||
'interactive' => $command->isInteractive,
|
||||
'validate_input' => !$command->ignoreValidation,
|
||||
'enabled' => $command->isEnabled(),
|
||||
'visible' => !$command->isHidden(),
|
||||
'input' => $this->cloneVar($command->input),
|
||||
'output' => $this->cloneVar($command->output),
|
||||
'interactive_inputs' => array_map($this->cloneVar(...), $command->interactiveInputs),
|
||||
'signalable' => $command->getSubscribedSignals(),
|
||||
'handled_signals' => $command->handledSignals,
|
||||
'helper_set' => array_map($this->cloneVar(...), iterator_to_array($command->getHelperSet())),
|
||||
];
|
||||
|
||||
$baseDefinition = $application->getDefinition();
|
||||
|
||||
foreach ($command->arguments as $argName => $argValue) {
|
||||
if ($baseDefinition->hasArgument($argName)) {
|
||||
$this->data['application_inputs'][$argName] = $this->cloneVar($argValue);
|
||||
} else {
|
||||
$this->data['arguments'][$argName] = $this->cloneVar($argValue);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($command->options as $optName => $optValue) {
|
||||
if ($baseDefinition->hasOption($optName)) {
|
||||
$this->data['application_inputs']['--'.$optName] = $this->cloneVar($optValue);
|
||||
} else {
|
||||
$this->data['options'][$optName] = $this->cloneVar($optValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return 'command';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{
|
||||
* class?: class-string,
|
||||
* executor?: string,
|
||||
* file: string,
|
||||
* line: int,
|
||||
* }
|
||||
*/
|
||||
public function getCommand(): array
|
||||
{
|
||||
$class = $this->data['command']->getType();
|
||||
$r = new \ReflectionMethod($class, 'execute');
|
||||
|
||||
if (Command::class !== $r->getDeclaringClass()) {
|
||||
return [
|
||||
'executor' => $class.'::'.$r->name,
|
||||
'file' => $r->getFileName(),
|
||||
'line' => $r->getStartLine(),
|
||||
];
|
||||
}
|
||||
|
||||
$r = new \ReflectionClass($class);
|
||||
|
||||
return [
|
||||
'class' => $class,
|
||||
'file' => $r->getFileName(),
|
||||
'line' => $r->getStartLine(),
|
||||
];
|
||||
}
|
||||
|
||||
public function getInterruptedBySignal(): ?string
|
||||
{
|
||||
if (isset($this->data['interrupted_by_signal'])) {
|
||||
return \sprintf('%s (%d)', SignalMap::getSignalName($this->data['interrupted_by_signal']), $this->data['interrupted_by_signal']);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getDuration(): string
|
||||
{
|
||||
return $this->data['duration'];
|
||||
}
|
||||
|
||||
public function getMaxMemoryUsage(): string
|
||||
{
|
||||
return $this->data['max_memory_usage'];
|
||||
}
|
||||
|
||||
public function getVerbosityLevel(): string
|
||||
{
|
||||
return $this->data['verbosity_level'];
|
||||
}
|
||||
|
||||
public function getInteractive(): bool
|
||||
{
|
||||
return $this->data['interactive'];
|
||||
}
|
||||
|
||||
public function getValidateInput(): bool
|
||||
{
|
||||
return $this->data['validate_input'];
|
||||
}
|
||||
|
||||
public function getEnabled(): bool
|
||||
{
|
||||
return $this->data['enabled'];
|
||||
}
|
||||
|
||||
public function getVisible(): bool
|
||||
{
|
||||
return $this->data['visible'];
|
||||
}
|
||||
|
||||
public function getInput(): Data
|
||||
{
|
||||
return $this->data['input'];
|
||||
}
|
||||
|
||||
public function getOutput(): Data
|
||||
{
|
||||
return $this->data['output'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Data[]
|
||||
*/
|
||||
public function getArguments(): array
|
||||
{
|
||||
return $this->data['arguments'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Data[]
|
||||
*/
|
||||
public function getOptions(): array
|
||||
{
|
||||
return $this->data['options'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Data[]
|
||||
*/
|
||||
public function getApplicationInputs(): array
|
||||
{
|
||||
return $this->data['application_inputs'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Data[]
|
||||
*/
|
||||
public function getInteractiveInputs(): array
|
||||
{
|
||||
return $this->data['interactive_inputs'] ?? [];
|
||||
}
|
||||
|
||||
public function getSignalable(): array
|
||||
{
|
||||
return array_map(
|
||||
static fn (int $signal): string => \sprintf('%s (%d)', SignalMap::getSignalName($signal), $signal),
|
||||
$this->data['signalable']
|
||||
);
|
||||
}
|
||||
|
||||
public function getHandledSignals(): array
|
||||
{
|
||||
$keys = array_map(
|
||||
static fn (int $signal): string => \sprintf('%s (%d)', SignalMap::getSignalName($signal), $signal),
|
||||
array_keys($this->data['handled_signals'])
|
||||
);
|
||||
|
||||
return array_combine($keys, array_values($this->data['handled_signals']));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Data[]
|
||||
*/
|
||||
public function getHelperSet(): array
|
||||
{
|
||||
return $this->data['helper_set'] ?? [];
|
||||
}
|
||||
|
||||
public function reset(): void
|
||||
{
|
||||
$this->data = [];
|
||||
}
|
||||
}
|
||||
70
vendor/symfony/console/Debug/CliRequest.php
vendored
Normal file
70
vendor/symfony/console/Debug/CliRequest.php
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Debug;
|
||||
|
||||
use Symfony\Component\Console\Command\TraceableCommand;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class CliRequest extends Request
|
||||
{
|
||||
public function __construct(
|
||||
public readonly TraceableCommand $command,
|
||||
) {
|
||||
parent::__construct(
|
||||
attributes: ['_controller' => \get_class($command->command), '_virtual_type' => 'command'],
|
||||
server: $_SERVER,
|
||||
);
|
||||
}
|
||||
|
||||
// Methods below allow to populate a profile, thus enable search and filtering
|
||||
public function getUri(): string
|
||||
{
|
||||
if ($this->server->has('SYMFONY_CLI_BINARY_NAME')) {
|
||||
$binary = $this->server->get('SYMFONY_CLI_BINARY_NAME').' console';
|
||||
} else {
|
||||
$binary = $this->server->get('argv')[0];
|
||||
}
|
||||
|
||||
return $binary.' '.$this->command->input;
|
||||
}
|
||||
|
||||
public function getMethod(): string
|
||||
{
|
||||
return $this->command->isInteractive ? 'INTERACTIVE' : 'BATCH';
|
||||
}
|
||||
|
||||
public function getResponse(): Response
|
||||
{
|
||||
return new class($this->command->exitCode) extends Response {
|
||||
public function __construct(private readonly int $exitCode)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function getStatusCode(): int
|
||||
{
|
||||
return $this->exitCode;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public function getClientIp(): string
|
||||
{
|
||||
$application = $this->command->getApplication();
|
||||
|
||||
return $application->getName().' '.$application->getVersion();
|
||||
}
|
||||
}
|
||||
134
vendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php
vendored
Normal file
134
vendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php
vendored
Normal file
@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@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\Command\LazyCommand;
|
||||
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
|
||||
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
|
||||
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\Reference;
|
||||
use Symfony\Component\DependencyInjection\TypedReference;
|
||||
|
||||
/**
|
||||
* Registers console commands.
|
||||
*
|
||||
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
||||
*/
|
||||
class AddConsoleCommandPass implements CompilerPassInterface
|
||||
{
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
$commandServices = $container->findTaggedServiceIds('console.command', true);
|
||||
$lazyCommandMap = [];
|
||||
$lazyCommandRefs = [];
|
||||
$serviceIds = [];
|
||||
|
||||
foreach ($commandServices as $id => $tags) {
|
||||
$definition = $container->getDefinition($id);
|
||||
$definition->addTag('container.no_preload');
|
||||
$class = $container->getParameterBag()->resolveValue($definition->getClass());
|
||||
|
||||
if (isset($tags[0]['command'])) {
|
||||
$aliases = $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, 'console.command', Command::class));
|
||||
}
|
||||
$aliases = str_replace('%', '%%', $class::getDefaultName() ?? '');
|
||||
}
|
||||
|
||||
$aliases = explode('|', $aliases ?? '');
|
||||
$commandName = array_shift($aliases);
|
||||
|
||||
if ($isHidden = '' === $commandName) {
|
||||
$commandName = array_shift($aliases);
|
||||
}
|
||||
|
||||
if (null === $commandName) {
|
||||
if (!$definition->isPublic() || $definition->isPrivate() || $definition->hasTag('container.private')) {
|
||||
$commandId = 'console.command.public_alias.'.$id;
|
||||
$container->setAlias($commandId, $id)->setPublic(true);
|
||||
$id = $commandId;
|
||||
}
|
||||
$serviceIds[] = $id;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$description = $tags[0]['description'] ?? null;
|
||||
|
||||
unset($tags[0]);
|
||||
$lazyCommandMap[$commandName] = $id;
|
||||
$lazyCommandRefs[$id] = new TypedReference($id, $class);
|
||||
|
||||
foreach ($aliases as $alias) {
|
||||
$lazyCommandMap[$alias] = $id;
|
||||
}
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
if (isset($tag['command'])) {
|
||||
$aliases[] = $tag['command'];
|
||||
$lazyCommandMap[$tag['command']] = $id;
|
||||
}
|
||||
|
||||
$description ??= $tag['description'] ?? null;
|
||||
}
|
||||
|
||||
$definition->addMethodCall('setName', [$commandName]);
|
||||
|
||||
if ($aliases) {
|
||||
$definition->addMethodCall('setAliases', [$aliases]);
|
||||
}
|
||||
|
||||
if ($isHidden) {
|
||||
$definition->addMethodCall('setHidden', [true]);
|
||||
}
|
||||
|
||||
if (!$description) {
|
||||
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, 'console.command', Command::class));
|
||||
}
|
||||
$description = str_replace('%', '%%', $class::getDefaultDescription() ?? '');
|
||||
}
|
||||
|
||||
if ($description) {
|
||||
$definition->addMethodCall('setDescription', [$description]);
|
||||
|
||||
$container->register('.'.$id.'.lazy', LazyCommand::class)
|
||||
->setArguments([$commandName, $aliases, $description, $isHidden, new ServiceClosureArgument($lazyCommandRefs[$id])]);
|
||||
|
||||
$lazyCommandRefs[$id] = new Reference('.'.$id.'.lazy');
|
||||
}
|
||||
}
|
||||
|
||||
$container
|
||||
->register('console.command_loader', ContainerCommandLoader::class)
|
||||
->setPublic(true)
|
||||
->addTag('container.no_preload')
|
||||
->setArguments([ServiceLocatorTagPass::register($container, $lazyCommandRefs), $lazyCommandMap]);
|
||||
|
||||
$container->setParameter('console.command.ids', $serviceIds);
|
||||
}
|
||||
}
|
||||
139
vendor/symfony/console/Descriptor/ApplicationDescription.php
vendored
Normal file
139
vendor/symfony/console/Descriptor/ApplicationDescription.php
vendored
Normal file
@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Descriptor;
|
||||
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Exception\CommandNotFoundException;
|
||||
|
||||
/**
|
||||
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ApplicationDescription
|
||||
{
|
||||
public const GLOBAL_NAMESPACE = '_global';
|
||||
|
||||
private Application $application;
|
||||
private ?string $namespace;
|
||||
private bool $showHidden;
|
||||
private array $namespaces;
|
||||
|
||||
/**
|
||||
* @var array<string, Command>
|
||||
*/
|
||||
private array $commands;
|
||||
|
||||
/**
|
||||
* @var array<string, Command>
|
||||
*/
|
||||
private array $aliases = [];
|
||||
|
||||
public function __construct(Application $application, ?string $namespace = null, bool $showHidden = false)
|
||||
{
|
||||
$this->application = $application;
|
||||
$this->namespace = $namespace;
|
||||
$this->showHidden = $showHidden;
|
||||
}
|
||||
|
||||
public function getNamespaces(): array
|
||||
{
|
||||
if (!isset($this->namespaces)) {
|
||||
$this->inspectApplication();
|
||||
}
|
||||
|
||||
return $this->namespaces;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Command[]
|
||||
*/
|
||||
public function getCommands(): array
|
||||
{
|
||||
if (!isset($this->commands)) {
|
||||
$this->inspectApplication();
|
||||
}
|
||||
|
||||
return $this->commands;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws CommandNotFoundException
|
||||
*/
|
||||
public function getCommand(string $name): Command
|
||||
{
|
||||
if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) {
|
||||
throw new CommandNotFoundException(\sprintf('Command "%s" does not exist.', $name));
|
||||
}
|
||||
|
||||
return $this->commands[$name] ?? $this->aliases[$name];
|
||||
}
|
||||
|
||||
private function inspectApplication(): void
|
||||
{
|
||||
$this->commands = [];
|
||||
$this->namespaces = [];
|
||||
|
||||
$all = $this->application->all($this->namespace ? $this->application->findNamespace($this->namespace) : null);
|
||||
foreach ($this->sortCommands($all) as $namespace => $commands) {
|
||||
$names = [];
|
||||
|
||||
/** @var Command $command */
|
||||
foreach ($commands as $name => $command) {
|
||||
if (!$command->getName() || (!$this->showHidden && $command->isHidden())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($command->getName() === $name) {
|
||||
$this->commands[$name] = $command;
|
||||
} else {
|
||||
$this->aliases[$name] = $command;
|
||||
}
|
||||
|
||||
$names[] = $name;
|
||||
}
|
||||
|
||||
$this->namespaces[$namespace] = ['id' => $namespace, 'commands' => $names];
|
||||
}
|
||||
}
|
||||
|
||||
private function sortCommands(array $commands): array
|
||||
{
|
||||
$namespacedCommands = [];
|
||||
$globalCommands = [];
|
||||
$sortedCommands = [];
|
||||
foreach ($commands as $name => $command) {
|
||||
$key = $this->application->extractNamespace($name, 1);
|
||||
if (\in_array($key, ['', self::GLOBAL_NAMESPACE], true)) {
|
||||
$globalCommands[$name] = $command;
|
||||
} else {
|
||||
$namespacedCommands[$key][$name] = $command;
|
||||
}
|
||||
}
|
||||
|
||||
if ($globalCommands) {
|
||||
ksort($globalCommands);
|
||||
$sortedCommands[self::GLOBAL_NAMESPACE] = $globalCommands;
|
||||
}
|
||||
|
||||
if ($namespacedCommands) {
|
||||
ksort($namespacedCommands, \SORT_STRING);
|
||||
foreach ($namespacedCommands as $key => $commandsSet) {
|
||||
ksort($commandsSet);
|
||||
$sortedCommands[$key] = $commandsSet;
|
||||
}
|
||||
}
|
||||
|
||||
return $sortedCommands;
|
||||
}
|
||||
}
|
||||
74
vendor/symfony/console/Descriptor/Descriptor.php
vendored
Normal file
74
vendor/symfony/console/Descriptor/Descriptor.php
vendored
Normal file
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@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
|
||||
{
|
||||
protected OutputInterface $output;
|
||||
|
||||
public function describe(OutputInterface $output, object $object, array $options = []): void
|
||||
{
|
||||
$this->output = $output;
|
||||
|
||||
match (true) {
|
||||
$object instanceof InputArgument => $this->describeInputArgument($object, $options),
|
||||
$object instanceof InputOption => $this->describeInputOption($object, $options),
|
||||
$object instanceof InputDefinition => $this->describeInputDefinition($object, $options),
|
||||
$object instanceof Command => $this->describeCommand($object, $options),
|
||||
$object instanceof Application => $this->describeApplication($object, $options),
|
||||
default => throw new InvalidArgumentException(\sprintf('Object of type "%s" is not describable.', get_debug_type($object))),
|
||||
};
|
||||
}
|
||||
|
||||
protected function write(string $content, bool $decorated = false): void
|
||||
{
|
||||
$this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW);
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes an InputArgument instance.
|
||||
*/
|
||||
abstract protected function describeInputArgument(InputArgument $argument, array $options = []): void;
|
||||
|
||||
/**
|
||||
* Describes an InputOption instance.
|
||||
*/
|
||||
abstract protected function describeInputOption(InputOption $option, array $options = []): void;
|
||||
|
||||
/**
|
||||
* Describes an InputDefinition instance.
|
||||
*/
|
||||
abstract protected function describeInputDefinition(InputDefinition $definition, array $options = []): void;
|
||||
|
||||
/**
|
||||
* Describes a Command instance.
|
||||
*/
|
||||
abstract protected function describeCommand(Command $command, array $options = []): void;
|
||||
|
||||
/**
|
||||
* Describes an Application instance.
|
||||
*/
|
||||
abstract protected function describeApplication(Application $application, array $options = []): void;
|
||||
}
|
||||
27
vendor/symfony/console/Descriptor/DescriptorInterface.php
vendored
Normal file
27
vendor/symfony/console/Descriptor/DescriptorInterface.php
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Descriptor;
|
||||
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Descriptor interface.
|
||||
*
|
||||
* @author Jean-François Simon <contact@jfsimon.fr>
|
||||
*/
|
||||
interface DescriptorInterface
|
||||
{
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function describe(OutputInterface $output, object $object, array $options = []);
|
||||
}
|
||||
166
vendor/symfony/console/Descriptor/JsonDescriptor.php
vendored
Normal file
166
vendor/symfony/console/Descriptor/JsonDescriptor.php
vendored
Normal file
@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\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
|
||||
{
|
||||
protected function describeInputArgument(InputArgument $argument, array $options = []): void
|
||||
{
|
||||
$this->writeData($this->getInputArgumentData($argument), $options);
|
||||
}
|
||||
|
||||
protected function describeInputOption(InputOption $option, array $options = []): void
|
||||
{
|
||||
$this->writeData($this->getInputOptionData($option), $options);
|
||||
if ($option->isNegatable()) {
|
||||
$this->writeData($this->getInputOptionData($option, true), $options);
|
||||
}
|
||||
}
|
||||
|
||||
protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
|
||||
{
|
||||
$this->writeData($this->getInputDefinitionData($definition), $options);
|
||||
}
|
||||
|
||||
protected function describeCommand(Command $command, array $options = []): void
|
||||
{
|
||||
$this->writeData($this->getCommandData($command, $options['short'] ?? false), $options);
|
||||
}
|
||||
|
||||
protected function describeApplication(Application $application, array $options = []): void
|
||||
{
|
||||
$describedNamespace = $options['namespace'] ?? null;
|
||||
$description = new ApplicationDescription($application, $describedNamespace, true);
|
||||
$commands = [];
|
||||
|
||||
foreach ($description->getCommands() as $command) {
|
||||
$commands[] = $this->getCommandData($command, $options['short'] ?? false);
|
||||
}
|
||||
|
||||
$data = [];
|
||||
if ('UNKNOWN' !== $application->getName()) {
|
||||
$data['application']['name'] = $application->getName();
|
||||
if ('UNKNOWN' !== $application->getVersion()) {
|
||||
$data['application']['version'] = $application->getVersion();
|
||||
}
|
||||
}
|
||||
|
||||
$data['commands'] = $commands;
|
||||
|
||||
if ($describedNamespace) {
|
||||
$data['namespace'] = $describedNamespace;
|
||||
} else {
|
||||
$data['namespaces'] = array_values($description->getNamespaces());
|
||||
}
|
||||
|
||||
$this->writeData($data, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes data as json.
|
||||
*/
|
||||
private function writeData(array $data, array $options): void
|
||||
{
|
||||
$flags = $options['json_encoding'] ?? 0;
|
||||
|
||||
$this->write(json_encode($data, $flags));
|
||||
}
|
||||
|
||||
private function getInputArgumentData(InputArgument $argument): array
|
||||
{
|
||||
return [
|
||||
'name' => $argument->getName(),
|
||||
'is_required' => $argument->isRequired(),
|
||||
'is_array' => $argument->isArray(),
|
||||
'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $argument->getDescription()),
|
||||
'default' => \INF === $argument->getDefault() ? 'INF' : $argument->getDefault(),
|
||||
];
|
||||
}
|
||||
|
||||
private function getInputOptionData(InputOption $option, bool $negated = false): array
|
||||
{
|
||||
return $negated ? [
|
||||
'name' => '--no-'.$option->getName(),
|
||||
'shortcut' => '',
|
||||
'accept_value' => false,
|
||||
'is_value_required' => false,
|
||||
'is_multiple' => false,
|
||||
'description' => 'Negate the "--'.$option->getName().'" option',
|
||||
'default' => null === $option->getDefault() ? null : !$option->getDefault(),
|
||||
] : [
|
||||
'name' => '--'.$option->getName(),
|
||||
'shortcut' => $option->getShortcut() ? '-'.str_replace('|', '|-', $option->getShortcut()) : '',
|
||||
'accept_value' => $option->acceptValue(),
|
||||
'is_value_required' => $option->isValueRequired(),
|
||||
'is_multiple' => $option->isArray(),
|
||||
'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $option->getDescription()),
|
||||
'default' => \INF === $option->getDefault() ? 'INF' : $option->getDefault(),
|
||||
];
|
||||
}
|
||||
|
||||
private function getInputDefinitionData(InputDefinition $definition): array
|
||||
{
|
||||
$inputArguments = [];
|
||||
foreach ($definition->getArguments() as $name => $argument) {
|
||||
$inputArguments[$name] = $this->getInputArgumentData($argument);
|
||||
}
|
||||
|
||||
$inputOptions = [];
|
||||
foreach ($definition->getOptions() as $name => $option) {
|
||||
$inputOptions[$name] = $this->getInputOptionData($option);
|
||||
if ($option->isNegatable()) {
|
||||
$inputOptions['no-'.$name] = $this->getInputOptionData($option, true);
|
||||
}
|
||||
}
|
||||
|
||||
return ['arguments' => $inputArguments, 'options' => $inputOptions];
|
||||
}
|
||||
|
||||
private function getCommandData(Command $command, bool $short = false): array
|
||||
{
|
||||
$data = [
|
||||
'name' => $command->getName(),
|
||||
'description' => $command->getDescription(),
|
||||
];
|
||||
|
||||
if ($short) {
|
||||
$data += [
|
||||
'usage' => $command->getAliases(),
|
||||
];
|
||||
} else {
|
||||
$command->mergeApplicationDefinition(false);
|
||||
|
||||
$data += [
|
||||
'usage' => array_merge([$command->getSynopsis()], $command->getUsages(), $command->getAliases()),
|
||||
'help' => $command->getProcessedHelp(),
|
||||
'definition' => $this->getInputDefinitionData($command->getDefinition()),
|
||||
];
|
||||
}
|
||||
|
||||
$data['hidden'] = $command->isHidden();
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
173
vendor/symfony/console/Descriptor/MarkdownDescriptor.php
vendored
Normal file
173
vendor/symfony/console/Descriptor/MarkdownDescriptor.php
vendored
Normal file
@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@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
|
||||
{
|
||||
public function describe(OutputInterface $output, object $object, array $options = []): void
|
||||
{
|
||||
$decorated = $output->isDecorated();
|
||||
$output->setDecorated(false);
|
||||
|
||||
parent::describe($output, $object, $options);
|
||||
|
||||
$output->setDecorated($decorated);
|
||||
}
|
||||
|
||||
protected function write(string $content, bool $decorated = true): void
|
||||
{
|
||||
parent::write($content, $decorated);
|
||||
}
|
||||
|
||||
protected function describeInputArgument(InputArgument $argument, array $options = []): void
|
||||
{
|
||||
$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)).'`'
|
||||
);
|
||||
}
|
||||
|
||||
protected function describeInputOption(InputOption $option, array $options = []): void
|
||||
{
|
||||
$name = '--'.$option->getName();
|
||||
if ($option->isNegatable()) {
|
||||
$name .= '|--no-'.$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"
|
||||
.'* Is negatable: '.($option->isNegatable() ? 'yes' : 'no')."\n"
|
||||
.'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`'
|
||||
);
|
||||
}
|
||||
|
||||
protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
|
||||
{
|
||||
if ($showArguments = \count($definition->getArguments()) > 0) {
|
||||
$this->write('### Arguments');
|
||||
foreach ($definition->getArguments() as $argument) {
|
||||
$this->write("\n\n");
|
||||
$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->describeInputOption($option);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function describeCommand(Command $command, array $options = []): void
|
||||
{
|
||||
if ($options['short'] ?? false) {
|
||||
$this->write(
|
||||
'`'.$command->getName()."`\n"
|
||||
.str_repeat('-', Helper::width($command->getName()) + 2)."\n\n"
|
||||
.($command->getDescription() ? $command->getDescription()."\n\n" : '')
|
||||
.'### Usage'."\n\n"
|
||||
.array_reduce($command->getAliases(), fn ($carry, $usage) => $carry.'* `'.$usage.'`'."\n")
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$command->mergeApplicationDefinition(false);
|
||||
|
||||
$this->write(
|
||||
'`'.$command->getName()."`\n"
|
||||
.str_repeat('-', Helper::width($command->getName()) + 2)."\n\n"
|
||||
.($command->getDescription() ? $command->getDescription()."\n\n" : '')
|
||||
.'### Usage'."\n\n"
|
||||
.array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), fn ($carry, $usage) => $carry.'* `'.$usage.'`'."\n")
|
||||
);
|
||||
|
||||
if ($help = $command->getProcessedHelp()) {
|
||||
$this->write("\n");
|
||||
$this->write($help);
|
||||
}
|
||||
|
||||
$definition = $command->getDefinition();
|
||||
if ($definition->getOptions() || $definition->getArguments()) {
|
||||
$this->write("\n\n");
|
||||
$this->describeInputDefinition($definition);
|
||||
}
|
||||
}
|
||||
|
||||
protected function describeApplication(Application $application, array $options = []): void
|
||||
{
|
||||
$describedNamespace = $options['namespace'] ?? null;
|
||||
$description = new ApplicationDescription($application, $describedNamespace);
|
||||
$title = $this->getApplicationTitle($application);
|
||||
|
||||
$this->write($title."\n".str_repeat('=', Helper::width($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(fn ($commandName) => \sprintf('* [`%s`](#%s)', $commandName, str_replace(':', '', $description->getCommand($commandName)->getName())), $namespace['commands'])));
|
||||
}
|
||||
|
||||
foreach ($description->getCommands() as $command) {
|
||||
$this->write("\n\n");
|
||||
$this->describeCommand($command, $options);
|
||||
}
|
||||
}
|
||||
|
||||
private function getApplicationTitle(Application $application): string
|
||||
{
|
||||
if ('UNKNOWN' !== $application->getName()) {
|
||||
if ('UNKNOWN' !== $application->getVersion()) {
|
||||
return \sprintf('%s %s', $application->getName(), $application->getVersion());
|
||||
}
|
||||
|
||||
return $application->getName();
|
||||
}
|
||||
|
||||
return 'Console Tool';
|
||||
}
|
||||
}
|
||||
272
vendor/symfony/console/Descriptor/ReStructuredTextDescriptor.php
vendored
Normal file
272
vendor/symfony/console/Descriptor/ReStructuredTextDescriptor.php
vendored
Normal file
@ -0,0 +1,272 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@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;
|
||||
use Symfony\Component\String\UnicodeString;
|
||||
|
||||
class ReStructuredTextDescriptor extends Descriptor
|
||||
{
|
||||
// <h1>
|
||||
private string $partChar = '=';
|
||||
// <h2>
|
||||
private string $chapterChar = '-';
|
||||
// <h3>
|
||||
private string $sectionChar = '~';
|
||||
// <h4>
|
||||
private string $subsectionChar = '.';
|
||||
// <h5>
|
||||
private string $subsubsectionChar = '^';
|
||||
// <h6>
|
||||
private string $paragraphsChar = '"';
|
||||
|
||||
private array $visibleNamespaces = [];
|
||||
|
||||
public function describe(OutputInterface $output, object $object, array $options = []): void
|
||||
{
|
||||
$decorated = $output->isDecorated();
|
||||
$output->setDecorated(false);
|
||||
|
||||
parent::describe($output, $object, $options);
|
||||
|
||||
$output->setDecorated($decorated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override parent method to set $decorated = true.
|
||||
*/
|
||||
protected function write(string $content, bool $decorated = true): void
|
||||
{
|
||||
parent::write($content, $decorated);
|
||||
}
|
||||
|
||||
protected function describeInputArgument(InputArgument $argument, array $options = []): void
|
||||
{
|
||||
$this->write(
|
||||
$argument->getName() ?: '<none>'."\n".str_repeat($this->paragraphsChar, Helper::width($argument->getName()))."\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)).'``'
|
||||
);
|
||||
}
|
||||
|
||||
protected function describeInputOption(InputOption $option, array $options = []): void
|
||||
{
|
||||
$name = '\-\-'.$option->getName();
|
||||
if ($option->isNegatable()) {
|
||||
$name .= '|\-\-no-'.$option->getName();
|
||||
}
|
||||
if ($option->getShortcut()) {
|
||||
$name .= '|-'.str_replace('|', '|-', $option->getShortcut());
|
||||
}
|
||||
|
||||
$optionDescription = $option->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n\n", $option->getDescription())."\n\n" : '';
|
||||
$optionDescription = (new UnicodeString($optionDescription))->ascii();
|
||||
$this->write(
|
||||
$name."\n".str_repeat($this->paragraphsChar, Helper::width($name))."\n\n"
|
||||
.$optionDescription
|
||||
.'- **Accept value**: '.($option->acceptValue() ? 'yes' : 'no')."\n"
|
||||
.'- **Is value required**: '.($option->isValueRequired() ? 'yes' : 'no')."\n"
|
||||
.'- **Is multiple**: '.($option->isArray() ? 'yes' : 'no')."\n"
|
||||
.'- **Is negatable**: '.($option->isNegatable() ? 'yes' : 'no')."\n"
|
||||
.'- **Default**: ``'.str_replace("\n", '', var_export($option->getDefault(), true)).'``'."\n"
|
||||
);
|
||||
}
|
||||
|
||||
protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
|
||||
{
|
||||
if ($showArguments = ((bool) $definition->getArguments())) {
|
||||
$this->write("Arguments\n".str_repeat($this->subsubsectionChar, 9))."\n\n";
|
||||
foreach ($definition->getArguments() as $argument) {
|
||||
$this->write("\n\n");
|
||||
$this->describeInputArgument($argument);
|
||||
}
|
||||
}
|
||||
|
||||
if ($nonDefaultOptions = $this->getNonDefaultOptions($definition)) {
|
||||
if ($showArguments) {
|
||||
$this->write("\n\n");
|
||||
}
|
||||
|
||||
$this->write("Options\n".str_repeat($this->subsubsectionChar, 7)."\n\n");
|
||||
foreach ($nonDefaultOptions as $option) {
|
||||
$this->describeInputOption($option);
|
||||
$this->write("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function describeCommand(Command $command, array $options = []): void
|
||||
{
|
||||
if ($options['short'] ?? false) {
|
||||
$this->write(
|
||||
'``'.$command->getName()."``\n"
|
||||
.str_repeat($this->subsectionChar, Helper::width($command->getName()))."\n\n"
|
||||
.($command->getDescription() ? $command->getDescription()."\n\n" : '')
|
||||
."Usage\n".str_repeat($this->paragraphsChar, 5)."\n\n"
|
||||
.array_reduce($command->getAliases(), static fn ($carry, $usage) => $carry.'- ``'.$usage.'``'."\n")
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$command->mergeApplicationDefinition(false);
|
||||
|
||||
foreach ($command->getAliases() as $alias) {
|
||||
$this->write('.. _'.$alias.":\n\n");
|
||||
}
|
||||
$this->write(
|
||||
$command->getName()."\n"
|
||||
.str_repeat($this->subsectionChar, Helper::width($command->getName()))."\n\n"
|
||||
.($command->getDescription() ? $command->getDescription()."\n\n" : '')
|
||||
."Usage\n".str_repeat($this->subsubsectionChar, 5)."\n\n"
|
||||
.array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), static fn ($carry, $usage) => $carry.'- ``'.$usage.'``'."\n")
|
||||
);
|
||||
|
||||
if ($help = $command->getProcessedHelp()) {
|
||||
$this->write("\n");
|
||||
$this->write($help);
|
||||
}
|
||||
|
||||
$definition = $command->getDefinition();
|
||||
if ($definition->getOptions() || $definition->getArguments()) {
|
||||
$this->write("\n\n");
|
||||
$this->describeInputDefinition($definition);
|
||||
}
|
||||
}
|
||||
|
||||
protected function describeApplication(Application $application, array $options = []): void
|
||||
{
|
||||
$description = new ApplicationDescription($application, $options['namespace'] ?? null);
|
||||
$title = $this->getApplicationTitle($application);
|
||||
|
||||
$this->write($title."\n".str_repeat($this->partChar, Helper::width($title)));
|
||||
$this->createTableOfContents($description, $application);
|
||||
$this->describeCommands($application, $options);
|
||||
}
|
||||
|
||||
private function getApplicationTitle(Application $application): string
|
||||
{
|
||||
if ('UNKNOWN' === $application->getName()) {
|
||||
return 'Console Tool';
|
||||
}
|
||||
if ('UNKNOWN' !== $application->getVersion()) {
|
||||
return \sprintf('%s %s', $application->getName(), $application->getVersion());
|
||||
}
|
||||
|
||||
return $application->getName();
|
||||
}
|
||||
|
||||
private function describeCommands($application, array $options): void
|
||||
{
|
||||
$title = 'Commands';
|
||||
$this->write("\n\n$title\n".str_repeat($this->chapterChar, Helper::width($title))."\n\n");
|
||||
foreach ($this->visibleNamespaces as $namespace) {
|
||||
if ('_global' === $namespace) {
|
||||
$commands = $application->all('');
|
||||
$this->write('Global'."\n".str_repeat($this->sectionChar, Helper::width('Global'))."\n\n");
|
||||
} else {
|
||||
$commands = $application->all($namespace);
|
||||
$this->write($namespace."\n".str_repeat($this->sectionChar, Helper::width($namespace))."\n\n");
|
||||
}
|
||||
|
||||
foreach ($this->removeAliasesAndHiddenCommands($commands) as $command) {
|
||||
$this->describeCommand($command, $options);
|
||||
$this->write("\n\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function createTableOfContents(ApplicationDescription $description, Application $application): void
|
||||
{
|
||||
$this->setVisibleNamespaces($description);
|
||||
$chapterTitle = 'Table of Contents';
|
||||
$this->write("\n\n$chapterTitle\n".str_repeat($this->chapterChar, Helper::width($chapterTitle))."\n\n");
|
||||
foreach ($this->visibleNamespaces as $namespace) {
|
||||
if ('_global' === $namespace) {
|
||||
$commands = $application->all('');
|
||||
} else {
|
||||
$commands = $application->all($namespace);
|
||||
$this->write("\n\n");
|
||||
$this->write($namespace."\n".str_repeat($this->sectionChar, Helper::width($namespace))."\n\n");
|
||||
}
|
||||
$commands = $this->removeAliasesAndHiddenCommands($commands);
|
||||
|
||||
$this->write("\n\n");
|
||||
$this->write(implode("\n", array_map(static fn ($commandName) => \sprintf('- `%s`_', $commandName), array_keys($commands))));
|
||||
}
|
||||
}
|
||||
|
||||
private function getNonDefaultOptions(InputDefinition $definition): array
|
||||
{
|
||||
$globalOptions = [
|
||||
'help',
|
||||
'quiet',
|
||||
'verbose',
|
||||
'version',
|
||||
'ansi',
|
||||
'no-interaction',
|
||||
];
|
||||
$nonDefaultOptions = [];
|
||||
foreach ($definition->getOptions() as $option) {
|
||||
// Skip global options.
|
||||
if (!\in_array($option->getName(), $globalOptions)) {
|
||||
$nonDefaultOptions[] = $option;
|
||||
}
|
||||
}
|
||||
|
||||
return $nonDefaultOptions;
|
||||
}
|
||||
|
||||
private function setVisibleNamespaces(ApplicationDescription $description): void
|
||||
{
|
||||
$commands = $description->getCommands();
|
||||
foreach ($description->getNamespaces() as $namespace) {
|
||||
try {
|
||||
$namespaceCommands = $namespace['commands'];
|
||||
foreach ($namespaceCommands as $key => $commandName) {
|
||||
if (!\array_key_exists($commandName, $commands)) {
|
||||
// If the array key does not exist, then this is an alias.
|
||||
unset($namespaceCommands[$key]);
|
||||
} elseif ($commands[$commandName]->isHidden()) {
|
||||
unset($namespaceCommands[$key]);
|
||||
}
|
||||
}
|
||||
if (!$namespaceCommands) {
|
||||
// If the namespace contained only aliases or hidden commands, skip the namespace.
|
||||
continue;
|
||||
}
|
||||
} catch (\Exception) {
|
||||
}
|
||||
$this->visibleNamespaces[] = $namespace['id'];
|
||||
}
|
||||
}
|
||||
|
||||
private function removeAliasesAndHiddenCommands(array $commands): array
|
||||
{
|
||||
foreach ($commands as $key => $command) {
|
||||
if ($command->isHidden() || \in_array($key, $command->getAliases(), true)) {
|
||||
unset($commands[$key]);
|
||||
}
|
||||
}
|
||||
unset($commands['completion']);
|
||||
|
||||
return $commands;
|
||||
}
|
||||
}
|
||||
317
vendor/symfony/console/Descriptor/TextDescriptor.php
vendored
Normal file
317
vendor/symfony/console/Descriptor/TextDescriptor.php
vendored
Normal file
@ -0,0 +1,317 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@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
|
||||
{
|
||||
protected function describeInputArgument(InputArgument $argument, array $options = []): void
|
||||
{
|
||||
if (null !== $argument->getDefault() && (!\is_array($argument->getDefault()) || \count($argument->getDefault()))) {
|
||||
$default = \sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($argument->getDefault()));
|
||||
} else {
|
||||
$default = '';
|
||||
}
|
||||
|
||||
$totalWidth = $options['total_width'] ?? Helper::width($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);
|
||||
}
|
||||
|
||||
protected function describeInputOption(InputOption $option, array $options = []): void
|
||||
{
|
||||
if ($option->acceptValue() && null !== $option->getDefault() && (!\is_array($option->getDefault()) || \count($option->getDefault()))) {
|
||||
$default = \sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($option->getDefault()));
|
||||
} else {
|
||||
$default = '';
|
||||
}
|
||||
|
||||
$value = '';
|
||||
if ($option->acceptValue()) {
|
||||
$value = '='.strtoupper($option->getName());
|
||||
|
||||
if ($option->isValueOptional()) {
|
||||
$value = '['.$value.']';
|
||||
}
|
||||
}
|
||||
|
||||
$totalWidth = $options['total_width'] ?? $this->calculateTotalWidthForOptions([$option]);
|
||||
$synopsis = \sprintf('%s%s',
|
||||
$option->getShortcut() ? \sprintf('-%s, ', $option->getShortcut()) : ' ',
|
||||
\sprintf($option->isNegatable() ? '--%1$s|--no-%1$s' : '--%1$s%2$s', $option->getName(), $value)
|
||||
);
|
||||
|
||||
$spacingWidth = $totalWidth - Helper::width($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);
|
||||
}
|
||||
|
||||
protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
|
||||
{
|
||||
$totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions());
|
||||
foreach ($definition->getArguments() as $argument) {
|
||||
$totalWidth = max($totalWidth, Helper::width($argument->getName()));
|
||||
}
|
||||
|
||||
if ($definition->getArguments()) {
|
||||
$this->writeText('<comment>Arguments:</comment>', $options);
|
||||
$this->writeText("\n");
|
||||
foreach ($definition->getArguments() as $argument) {
|
||||
$this->describeInputArgument($argument, array_merge($options, ['total_width' => $totalWidth]));
|
||||
$this->writeText("\n");
|
||||
}
|
||||
}
|
||||
|
||||
if ($definition->getArguments() && $definition->getOptions()) {
|
||||
$this->writeText("\n");
|
||||
}
|
||||
|
||||
if ($definition->getOptions()) {
|
||||
$laterOptions = [];
|
||||
|
||||
$this->writeText('<comment>Options:</comment>', $options);
|
||||
foreach ($definition->getOptions() as $option) {
|
||||
if (\strlen($option->getShortcut() ?? '') > 1) {
|
||||
$laterOptions[] = $option;
|
||||
continue;
|
||||
}
|
||||
$this->writeText("\n");
|
||||
$this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
|
||||
}
|
||||
foreach ($laterOptions as $option) {
|
||||
$this->writeText("\n");
|
||||
$this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function describeCommand(Command $command, array $options = []): void
|
||||
{
|
||||
$command->mergeApplicationDefinition(false);
|
||||
|
||||
if ($description = $command->getDescription()) {
|
||||
$this->writeText('<comment>Description:</comment>', $options);
|
||||
$this->writeText("\n");
|
||||
$this->writeText(' '.$description);
|
||||
$this->writeText("\n\n");
|
||||
}
|
||||
|
||||
$this->writeText('<comment>Usage:</comment>', $options);
|
||||
foreach (array_merge([$command->getSynopsis(true)], $command->getAliases(), $command->getUsages()) as $usage) {
|
||||
$this->writeText("\n");
|
||||
$this->writeText(' '.OutputFormatter::escape($usage), $options);
|
||||
}
|
||||
$this->writeText("\n");
|
||||
|
||||
$definition = $command->getDefinition();
|
||||
if ($definition->getOptions() || $definition->getArguments()) {
|
||||
$this->writeText("\n");
|
||||
$this->describeInputDefinition($definition, $options);
|
||||
$this->writeText("\n");
|
||||
}
|
||||
|
||||
$help = $command->getProcessedHelp();
|
||||
if ($help && $help !== $description) {
|
||||
$this->writeText("\n");
|
||||
$this->writeText('<comment>Help:</comment>', $options);
|
||||
$this->writeText("\n");
|
||||
$this->writeText(' '.str_replace("\n", "\n ", $help), $options);
|
||||
$this->writeText("\n");
|
||||
}
|
||||
}
|
||||
|
||||
protected function describeApplication(Application $application, array $options = []): void
|
||||
{
|
||||
$describedNamespace = $options['namespace'] ?? null;
|
||||
$description = new ApplicationDescription($application, $describedNamespace);
|
||||
|
||||
if (isset($options['raw_text']) && $options['raw_text']) {
|
||||
$width = $this->getColumnWidth($description->getCommands());
|
||||
|
||||
foreach ($description->getCommands() as $command) {
|
||||
$this->writeText(\sprintf("%-{$width}s %s", $command->getName(), $command->getDescription()), $options);
|
||||
$this->writeText("\n");
|
||||
}
|
||||
} else {
|
||||
if ('' != $help = $application->getHelp()) {
|
||||
$this->writeText("$help\n\n", $options);
|
||||
}
|
||||
|
||||
$this->writeText("<comment>Usage:</comment>\n", $options);
|
||||
$this->writeText(" command [options] [arguments]\n\n", $options);
|
||||
|
||||
$this->describeInputDefinition(new InputDefinition($application->getDefinition()->getOptions()), $options);
|
||||
|
||||
$this->writeText("\n");
|
||||
$this->writeText("\n");
|
||||
|
||||
$commands = $description->getCommands();
|
||||
$namespaces = $description->getNamespaces();
|
||||
if ($describedNamespace && $namespaces) {
|
||||
// make sure all alias commands are included when describing a specific namespace
|
||||
$describedNamespaceInfo = reset($namespaces);
|
||||
foreach ($describedNamespaceInfo['commands'] as $name) {
|
||||
$commands[$name] = $description->getCommand($name);
|
||||
}
|
||||
}
|
||||
|
||||
// calculate max. width based on available commands per namespace
|
||||
$width = $this->getColumnWidth(array_merge(...array_values(array_map(fn ($namespace) => array_intersect($namespace['commands'], array_keys($commands)), array_values($namespaces)))));
|
||||
|
||||
if ($describedNamespace) {
|
||||
$this->writeText(\sprintf('<comment>Available commands for the "%s" namespace:</comment>', $describedNamespace), $options);
|
||||
} else {
|
||||
$this->writeText('<comment>Available commands:</comment>', $options);
|
||||
}
|
||||
|
||||
foreach ($namespaces as $namespace) {
|
||||
$namespace['commands'] = array_filter($namespace['commands'], fn ($name) => 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::width($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");
|
||||
}
|
||||
}
|
||||
|
||||
private function writeText(string $content, array $options = []): void
|
||||
{
|
||||
$this->write(
|
||||
isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content,
|
||||
isset($options['raw_output']) ? !$options['raw_output'] : true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats command aliases to show them in the command description.
|
||||
*/
|
||||
private function getCommandAliasesText(Command $command): string
|
||||
{
|
||||
$text = '';
|
||||
$aliases = $command->getAliases();
|
||||
|
||||
if ($aliases) {
|
||||
$text = '['.implode('|', $aliases).'] ';
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats input option/argument default value.
|
||||
*/
|
||||
private function formatDefaultValue(mixed $default): string
|
||||
{
|
||||
if (\INF === $default) {
|
||||
return 'INF';
|
||||
}
|
||||
|
||||
if (\is_string($default)) {
|
||||
$default = OutputFormatter::escape($default);
|
||||
} elseif (\is_array($default)) {
|
||||
foreach ($default as $key => $value) {
|
||||
if (\is_string($value)) {
|
||||
$default[$key] = OutputFormatter::escape($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return str_replace('\\\\', '\\', json_encode($default, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<Command|string> $commands
|
||||
*/
|
||||
private function getColumnWidth(array $commands): int
|
||||
{
|
||||
$widths = [];
|
||||
|
||||
foreach ($commands as $command) {
|
||||
if ($command instanceof Command) {
|
||||
$widths[] = Helper::width($command->getName());
|
||||
foreach ($command->getAliases() as $alias) {
|
||||
$widths[] = Helper::width($alias);
|
||||
}
|
||||
} else {
|
||||
$widths[] = Helper::width($command);
|
||||
}
|
||||
}
|
||||
|
||||
return $widths ? max($widths) + 2 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputOption[] $options
|
||||
*/
|
||||
private function calculateTotalWidthForOptions(array $options): int
|
||||
{
|
||||
$totalWidth = 0;
|
||||
foreach ($options as $option) {
|
||||
// "-" + shortcut + ", --" + name
|
||||
$nameLength = 1 + max(Helper::width($option->getShortcut()), 1) + 4 + Helper::width($option->getName());
|
||||
if ($option->isNegatable()) {
|
||||
$nameLength += 6 + Helper::width($option->getName()); // |--no- + name
|
||||
} elseif ($option->acceptValue()) {
|
||||
$valueLength = 1 + Helper::width($option->getName()); // = + value
|
||||
$valueLength += $option->isValueOptional() ? 2 : 0; // [ + ]
|
||||
|
||||
$nameLength += $valueLength;
|
||||
}
|
||||
$totalWidth = max($totalWidth, $nameLength);
|
||||
}
|
||||
|
||||
return $totalWidth;
|
||||
}
|
||||
}
|
||||
232
vendor/symfony/console/Descriptor/XmlDescriptor.php
vendored
Normal file
232
vendor/symfony/console/Descriptor/XmlDescriptor.php
vendored
Normal file
@ -0,0 +1,232 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Descriptor;
|
||||
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputDefinition;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
/**
|
||||
* XML descriptor.
|
||||
*
|
||||
* @author Jean-François Simon <contact@jfsimon.fr>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class XmlDescriptor extends Descriptor
|
||||
{
|
||||
public function getInputDefinitionDocument(InputDefinition $definition): \DOMDocument
|
||||
{
|
||||
$dom = new \DOMDocument('1.0', 'UTF-8');
|
||||
$dom->appendChild($definitionXML = $dom->createElement('definition'));
|
||||
|
||||
$definitionXML->appendChild($argumentsXML = $dom->createElement('arguments'));
|
||||
foreach ($definition->getArguments() as $argument) {
|
||||
$this->appendDocument($argumentsXML, $this->getInputArgumentDocument($argument));
|
||||
}
|
||||
|
||||
$definitionXML->appendChild($optionsXML = $dom->createElement('options'));
|
||||
foreach ($definition->getOptions() as $option) {
|
||||
$this->appendDocument($optionsXML, $this->getInputOptionDocument($option));
|
||||
}
|
||||
|
||||
return $dom;
|
||||
}
|
||||
|
||||
public function getCommandDocument(Command $command, bool $short = false): \DOMDocument
|
||||
{
|
||||
$dom = new \DOMDocument('1.0', 'UTF-8');
|
||||
$dom->appendChild($commandXML = $dom->createElement('command'));
|
||||
|
||||
$commandXML->setAttribute('id', $command->getName());
|
||||
$commandXML->setAttribute('name', $command->getName());
|
||||
$commandXML->setAttribute('hidden', $command->isHidden() ? 1 : 0);
|
||||
|
||||
$commandXML->appendChild($usagesXML = $dom->createElement('usages'));
|
||||
|
||||
$commandXML->appendChild($descriptionXML = $dom->createElement('description'));
|
||||
$descriptionXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getDescription())));
|
||||
|
||||
if ($short) {
|
||||
foreach ($command->getAliases() as $usage) {
|
||||
$usagesXML->appendChild($dom->createElement('usage', $usage));
|
||||
}
|
||||
} else {
|
||||
$command->mergeApplicationDefinition(false);
|
||||
|
||||
foreach (array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()) as $usage) {
|
||||
$usagesXML->appendChild($dom->createElement('usage', $usage));
|
||||
}
|
||||
|
||||
$commandXML->appendChild($helpXML = $dom->createElement('help'));
|
||||
$helpXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getProcessedHelp())));
|
||||
|
||||
$definitionXML = $this->getInputDefinitionDocument($command->getDefinition());
|
||||
$this->appendDocument($commandXML, $definitionXML->getElementsByTagName('definition')->item(0));
|
||||
}
|
||||
|
||||
return $dom;
|
||||
}
|
||||
|
||||
public function getApplicationDocument(Application $application, ?string $namespace = null, bool $short = false): \DOMDocument
|
||||
{
|
||||
$dom = new \DOMDocument('1.0', 'UTF-8');
|
||||
$dom->appendChild($rootXml = $dom->createElement('symfony'));
|
||||
|
||||
if ('UNKNOWN' !== $application->getName()) {
|
||||
$rootXml->setAttribute('name', $application->getName());
|
||||
if ('UNKNOWN' !== $application->getVersion()) {
|
||||
$rootXml->setAttribute('version', $application->getVersion());
|
||||
}
|
||||
}
|
||||
|
||||
$rootXml->appendChild($commandsXML = $dom->createElement('commands'));
|
||||
|
||||
$description = new ApplicationDescription($application, $namespace, true);
|
||||
|
||||
if ($namespace) {
|
||||
$commandsXML->setAttribute('namespace', $namespace);
|
||||
}
|
||||
|
||||
foreach ($description->getCommands() as $command) {
|
||||
$this->appendDocument($commandsXML, $this->getCommandDocument($command, $short));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
protected function describeInputArgument(InputArgument $argument, array $options = []): void
|
||||
{
|
||||
$this->writeDocument($this->getInputArgumentDocument($argument));
|
||||
}
|
||||
|
||||
protected function describeInputOption(InputOption $option, array $options = []): void
|
||||
{
|
||||
$this->writeDocument($this->getInputOptionDocument($option));
|
||||
}
|
||||
|
||||
protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
|
||||
{
|
||||
$this->writeDocument($this->getInputDefinitionDocument($definition));
|
||||
}
|
||||
|
||||
protected function describeCommand(Command $command, array $options = []): void
|
||||
{
|
||||
$this->writeDocument($this->getCommandDocument($command, $options['short'] ?? false));
|
||||
}
|
||||
|
||||
protected function describeApplication(Application $application, array $options = []): void
|
||||
{
|
||||
$this->writeDocument($this->getApplicationDocument($application, $options['namespace'] ?? null, $options['short'] ?? false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends document children to parent node.
|
||||
*/
|
||||
private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent): void
|
||||
{
|
||||
foreach ($importedParent->childNodes as $childNode) {
|
||||
$parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes DOM document.
|
||||
*/
|
||||
private function writeDocument(\DOMDocument $dom): void
|
||||
{
|
||||
$dom->formatOutput = true;
|
||||
$this->write($dom->saveXML());
|
||||
}
|
||||
|
||||
private function getInputArgumentDocument(InputArgument $argument): \DOMDocument
|
||||
{
|
||||
$dom = new \DOMDocument('1.0', 'UTF-8');
|
||||
|
||||
$dom->appendChild($objectXML = $dom->createElement('argument'));
|
||||
$objectXML->setAttribute('name', $argument->getName());
|
||||
$objectXML->setAttribute('is_required', $argument->isRequired() ? 1 : 0);
|
||||
$objectXML->setAttribute('is_array', $argument->isArray() ? 1 : 0);
|
||||
$objectXML->appendChild($descriptionXML = $dom->createElement('description'));
|
||||
$descriptionXML->appendChild($dom->createTextNode($argument->getDescription()));
|
||||
|
||||
$objectXML->appendChild($defaultsXML = $dom->createElement('defaults'));
|
||||
$defaults = \is_array($argument->getDefault()) ? $argument->getDefault() : (\is_bool($argument->getDefault()) ? [var_export($argument->getDefault(), true)] : ($argument->getDefault() ? [$argument->getDefault()] : []));
|
||||
foreach ($defaults as $default) {
|
||||
$defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
|
||||
$defaultXML->appendChild($dom->createTextNode($default));
|
||||
}
|
||||
|
||||
return $dom;
|
||||
}
|
||||
|
||||
private function getInputOptionDocument(InputOption $option): \DOMDocument
|
||||
{
|
||||
$dom = new \DOMDocument('1.0', 'UTF-8');
|
||||
|
||||
$dom->appendChild($objectXML = $dom->createElement('option'));
|
||||
$objectXML->setAttribute('name', '--'.$option->getName());
|
||||
$pos = strpos($option->getShortcut() ?? '', '|');
|
||||
if (false !== $pos) {
|
||||
$objectXML->setAttribute('shortcut', '-'.substr($option->getShortcut(), 0, $pos));
|
||||
$objectXML->setAttribute('shortcuts', '-'.str_replace('|', '|-', $option->getShortcut()));
|
||||
} else {
|
||||
$objectXML->setAttribute('shortcut', $option->getShortcut() ? '-'.$option->getShortcut() : '');
|
||||
}
|
||||
$objectXML->setAttribute('accept_value', $option->acceptValue() ? 1 : 0);
|
||||
$objectXML->setAttribute('is_value_required', $option->isValueRequired() ? 1 : 0);
|
||||
$objectXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0);
|
||||
$objectXML->appendChild($descriptionXML = $dom->createElement('description'));
|
||||
$descriptionXML->appendChild($dom->createTextNode($option->getDescription()));
|
||||
|
||||
if ($option->acceptValue()) {
|
||||
$defaults = \is_array($option->getDefault()) ? $option->getDefault() : (\is_bool($option->getDefault()) ? [var_export($option->getDefault(), true)] : ($option->getDefault() ? [$option->getDefault()] : []));
|
||||
$objectXML->appendChild($defaultsXML = $dom->createElement('defaults'));
|
||||
|
||||
if (!empty($defaults)) {
|
||||
foreach ($defaults as $default) {
|
||||
$defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
|
||||
$defaultXML->appendChild($dom->createTextNode($default));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($option->isNegatable()) {
|
||||
$dom->appendChild($objectXML = $dom->createElement('option'));
|
||||
$objectXML->setAttribute('name', '--no-'.$option->getName());
|
||||
$objectXML->setAttribute('shortcut', '');
|
||||
$objectXML->setAttribute('accept_value', 0);
|
||||
$objectXML->setAttribute('is_value_required', 0);
|
||||
$objectXML->setAttribute('is_multiple', 0);
|
||||
$objectXML->appendChild($descriptionXML = $dom->createElement('description'));
|
||||
$descriptionXML->appendChild($dom->createTextNode('Negate the "--'.$option->getName().'" option'));
|
||||
}
|
||||
|
||||
return $dom;
|
||||
}
|
||||
}
|
||||
54
vendor/symfony/console/Event/ConsoleCommandEvent.php
vendored
Normal file
54
vendor/symfony/console/Event/ConsoleCommandEvent.php
vendored
Normal 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\Console\Event;
|
||||
|
||||
/**
|
||||
* Allows to do things before the command is executed, like skipping the command or executing code before the command is
|
||||
* going to be executed.
|
||||
*
|
||||
* Changing the input arguments will have no effect.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
final class ConsoleCommandEvent extends ConsoleEvent
|
||||
{
|
||||
/**
|
||||
* The return code for skipped commands, this will also be passed into the terminate event.
|
||||
*/
|
||||
public const RETURN_CODE_DISABLED = 113;
|
||||
|
||||
/**
|
||||
* Indicates if the command should be run or skipped.
|
||||
*/
|
||||
private bool $commandShouldRun = true;
|
||||
|
||||
/**
|
||||
* Disables the command, so it won't be run.
|
||||
*/
|
||||
public function disableCommand(): bool
|
||||
{
|
||||
return $this->commandShouldRun = false;
|
||||
}
|
||||
|
||||
public function enableCommand(): bool
|
||||
{
|
||||
return $this->commandShouldRun = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the command is runnable, false otherwise.
|
||||
*/
|
||||
public function commandShouldRun(): bool
|
||||
{
|
||||
return $this->commandShouldRun;
|
||||
}
|
||||
}
|
||||
57
vendor/symfony/console/Event/ConsoleErrorEvent.php
vendored
Normal file
57
vendor/symfony/console/Event/ConsoleErrorEvent.php
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Event;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Allows to handle throwables thrown while running a command.
|
||||
*
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*/
|
||||
final class ConsoleErrorEvent extends ConsoleEvent
|
||||
{
|
||||
private \Throwable $error;
|
||||
private int $exitCode;
|
||||
|
||||
public function __construct(InputInterface $input, OutputInterface $output, \Throwable $error, ?Command $command = null)
|
||||
{
|
||||
parent::__construct($command, $input, $output);
|
||||
|
||||
$this->error = $error;
|
||||
}
|
||||
|
||||
public function getError(): \Throwable
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
public function setError(\Throwable $error): void
|
||||
{
|
||||
$this->error = $error;
|
||||
}
|
||||
|
||||
public function setExitCode(int $exitCode): void
|
||||
{
|
||||
$this->exitCode = $exitCode;
|
||||
|
||||
$r = new \ReflectionProperty($this->error, 'code');
|
||||
$r->setValue($this->error, $this->exitCode);
|
||||
}
|
||||
|
||||
public function getExitCode(): int
|
||||
{
|
||||
return $this->exitCode ?? (\is_int($this->error->getCode()) && 0 !== $this->error->getCode() ? $this->error->getCode() : 1);
|
||||
}
|
||||
}
|
||||
61
vendor/symfony/console/Event/ConsoleEvent.php
vendored
Normal file
61
vendor/symfony/console/Event/ConsoleEvent.php
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Event;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Allows to inspect input and output of a command.
|
||||
*
|
||||
* @author Francesco Levorato <git@flevour.net>
|
||||
*/
|
||||
class ConsoleEvent extends Event
|
||||
{
|
||||
protected $command;
|
||||
|
||||
private InputInterface $input;
|
||||
private OutputInterface $output;
|
||||
|
||||
public function __construct(?Command $command, InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->command = $command;
|
||||
$this->input = $input;
|
||||
$this->output = $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the command that is executed.
|
||||
*/
|
||||
public function getCommand(): ?Command
|
||||
{
|
||||
return $this->command;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the input instance.
|
||||
*/
|
||||
public function getInput(): InputInterface
|
||||
{
|
||||
return $this->input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the output instance.
|
||||
*/
|
||||
public function getOutput(): OutputInterface
|
||||
{
|
||||
return $this->output;
|
||||
}
|
||||
}
|
||||
56
vendor/symfony/console/Event/ConsoleSignalEvent.php
vendored
Normal file
56
vendor/symfony/console/Event/ConsoleSignalEvent.php
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Event;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* @author marie <marie@users.noreply.github.com>
|
||||
*/
|
||||
final class ConsoleSignalEvent extends ConsoleEvent
|
||||
{
|
||||
private int $handlingSignal;
|
||||
private int|false $exitCode;
|
||||
|
||||
public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $handlingSignal, int|false $exitCode = 0)
|
||||
{
|
||||
parent::__construct($command, $input, $output);
|
||||
$this->handlingSignal = $handlingSignal;
|
||||
$this->exitCode = $exitCode;
|
||||
}
|
||||
|
||||
public function getHandlingSignal(): int
|
||||
{
|
||||
return $this->handlingSignal;
|
||||
}
|
||||
|
||||
public function setExitCode(int $exitCode): void
|
||||
{
|
||||
if ($exitCode < 0 || $exitCode > 255) {
|
||||
throw new \InvalidArgumentException('Exit code must be between 0 and 255.');
|
||||
}
|
||||
|
||||
$this->exitCode = $exitCode;
|
||||
}
|
||||
|
||||
public function abortExit(): void
|
||||
{
|
||||
$this->exitCode = false;
|
||||
}
|
||||
|
||||
public function getExitCode(): int|false
|
||||
{
|
||||
return $this->exitCode;
|
||||
}
|
||||
}
|
||||
50
vendor/symfony/console/Event/ConsoleTerminateEvent.php
vendored
Normal file
50
vendor/symfony/console/Event/ConsoleTerminateEvent.php
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
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>
|
||||
* @author Jules Pietri <jules@heahprod.com>
|
||||
*/
|
||||
final class ConsoleTerminateEvent extends ConsoleEvent
|
||||
{
|
||||
public function __construct(
|
||||
Command $command,
|
||||
InputInterface $input,
|
||||
OutputInterface $output,
|
||||
private int $exitCode,
|
||||
private readonly ?int $interruptingSignal = null,
|
||||
) {
|
||||
parent::__construct($command, $input, $output);
|
||||
}
|
||||
|
||||
public function setExitCode(int $exitCode): void
|
||||
{
|
||||
$this->exitCode = $exitCode;
|
||||
}
|
||||
|
||||
public function getExitCode(): int
|
||||
{
|
||||
return $this->exitCode;
|
||||
}
|
||||
|
||||
public function getInterruptingSignal(): ?int
|
||||
{
|
||||
return $this->interruptingSignal;
|
||||
}
|
||||
}
|
||||
101
vendor/symfony/console/EventListener/ErrorListener.php
vendored
Normal file
101
vendor/symfony/console/EventListener/ErrorListener.php
vendored
Normal file
@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\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 ?LoggerInterface $logger;
|
||||
|
||||
public function __construct(?LoggerInterface $logger = null)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function onConsoleError(ConsoleErrorEvent $event)
|
||||
{
|
||||
if (null === $this->logger) {
|
||||
return;
|
||||
}
|
||||
|
||||
$error = $event->getError();
|
||||
|
||||
if (!$inputString = $this->getInputString($event)) {
|
||||
$this->logger->critical('An error occurred while using the console. Message: "{message}"', ['exception' => $error, 'message' => $error->getMessage()]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->logger->critical('Error thrown while running command "{command}". Message: "{message}"', ['exception' => $error, 'command' => $inputString, 'message' => $error->getMessage()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function onConsoleTerminate(ConsoleTerminateEvent $event)
|
||||
{
|
||||
if (null === $this->logger) {
|
||||
return;
|
||||
}
|
||||
|
||||
$exitCode = $event->getExitCode();
|
||||
|
||||
if (0 === $exitCode) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$inputString = $this->getInputString($event)) {
|
||||
$this->logger->debug('The console exited with code "{code}"', ['code' => $exitCode]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->logger->debug('Command "{command}" exited with code "{code}"', ['command' => $inputString, 'code' => $exitCode]);
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
ConsoleEvents::ERROR => ['onConsoleError', -128],
|
||||
ConsoleEvents::TERMINATE => ['onConsoleTerminate', -128],
|
||||
];
|
||||
}
|
||||
|
||||
private static function getInputString(ConsoleEvent $event): ?string
|
||||
{
|
||||
$commandName = $event->getCommand()?->getName();
|
||||
$input = $event->getInput();
|
||||
|
||||
if ($input instanceof \Stringable) {
|
||||
if ($commandName) {
|
||||
return str_replace(["'$commandName'", "\"$commandName\""], $commandName, (string) $input);
|
||||
}
|
||||
|
||||
return (string) $input;
|
||||
}
|
||||
|
||||
return $commandName;
|
||||
}
|
||||
}
|
||||
43
vendor/symfony/console/Exception/CommandNotFoundException.php
vendored
Normal file
43
vendor/symfony/console/Exception/CommandNotFoundException.php
vendored
Normal 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 array $alternatives;
|
||||
|
||||
/**
|
||||
* @param string $message Exception message to throw
|
||||
* @param string[] $alternatives List of similar defined names
|
||||
* @param int $code Exception code
|
||||
* @param \Throwable|null $previous Previous exception used for the exception chaining
|
||||
*/
|
||||
public function __construct(string $message, array $alternatives = [], int $code = 0, ?\Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
|
||||
$this->alternatives = $alternatives;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAlternatives(): array
|
||||
{
|
||||
return $this->alternatives;
|
||||
}
|
||||
}
|
||||
21
vendor/symfony/console/Exception/ExceptionInterface.php
vendored
Normal file
21
vendor/symfony/console/Exception/ExceptionInterface.php
vendored
Normal 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 extends \Throwable
|
||||
{
|
||||
}
|
||||
19
vendor/symfony/console/Exception/InvalidArgumentException.php
vendored
Normal file
19
vendor/symfony/console/Exception/InvalidArgumentException.php
vendored
Normal 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
|
||||
{
|
||||
}
|
||||
21
vendor/symfony/console/Exception/InvalidOptionException.php
vendored
Normal file
21
vendor/symfony/console/Exception/InvalidOptionException.php
vendored
Normal 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 or value typed in the console.
|
||||
*
|
||||
* @author Jérôme Tamarelle <jerome@tamarelle.net>
|
||||
*/
|
||||
class InvalidOptionException extends \InvalidArgumentException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
19
vendor/symfony/console/Exception/LogicException.php
vendored
Normal file
19
vendor/symfony/console/Exception/LogicException.php
vendored
Normal 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
|
||||
{
|
||||
}
|
||||
21
vendor/symfony/console/Exception/MissingInputException.php
vendored
Normal file
21
vendor/symfony/console/Exception/MissingInputException.php
vendored
Normal 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 failure to read input from stdin.
|
||||
*
|
||||
* @author Gabriel Ostrolucký <gabriel.ostrolucky@gmail.com>
|
||||
*/
|
||||
class MissingInputException extends RuntimeException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
21
vendor/symfony/console/Exception/NamespaceNotFoundException.php
vendored
Normal file
21
vendor/symfony/console/Exception/NamespaceNotFoundException.php
vendored
Normal 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 namespace typed in the console.
|
||||
*
|
||||
* @author Pierre du Plessis <pdples@gmail.com>
|
||||
*/
|
||||
class NamespaceNotFoundException extends CommandNotFoundException
|
||||
{
|
||||
}
|
||||
29
vendor/symfony/console/Exception/RunCommandFailedException.php
vendored
Normal file
29
vendor/symfony/console/Exception/RunCommandFailedException.php
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Exception;
|
||||
|
||||
use Symfony\Component\Console\Messenger\RunCommandContext;
|
||||
|
||||
/**
|
||||
* @author Kevin Bond <kevinbond@gmail.com>
|
||||
*/
|
||||
final class RunCommandFailedException extends RuntimeException
|
||||
{
|
||||
public function __construct(\Throwable|string $exception, public readonly RunCommandContext $context)
|
||||
{
|
||||
parent::__construct(
|
||||
$exception instanceof \Throwable ? $exception->getMessage() : $exception,
|
||||
$exception instanceof \Throwable ? $exception->getCode() : 0,
|
||||
$exception instanceof \Throwable ? $exception : null,
|
||||
);
|
||||
}
|
||||
}
|
||||
19
vendor/symfony/console/Exception/RuntimeException.php
vendored
Normal file
19
vendor/symfony/console/Exception/RuntimeException.php
vendored
Normal 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
|
||||
{
|
||||
}
|
||||
51
vendor/symfony/console/Formatter/NullOutputFormatter.php
vendored
Normal file
51
vendor/symfony/console/Formatter/NullOutputFormatter.php
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Formatter;
|
||||
|
||||
/**
|
||||
* @author Tien Xuan Vo <tien.xuan.vo@gmail.com>
|
||||
*/
|
||||
final class NullOutputFormatter implements OutputFormatterInterface
|
||||
{
|
||||
private NullOutputFormatterStyle $style;
|
||||
|
||||
public function format(?string $message): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getStyle(string $name): OutputFormatterStyleInterface
|
||||
{
|
||||
// to comply with the interface we must return a OutputFormatterStyleInterface
|
||||
return $this->style ??= new NullOutputFormatterStyle();
|
||||
}
|
||||
|
||||
public function hasStyle(string $name): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isDecorated(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function setDecorated(bool $decorated): void
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public function setStyle(string $name, OutputFormatterStyleInterface $style): void
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
54
vendor/symfony/console/Formatter/NullOutputFormatterStyle.php
vendored
Normal file
54
vendor/symfony/console/Formatter/NullOutputFormatterStyle.php
vendored
Normal 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\Console\Formatter;
|
||||
|
||||
/**
|
||||
* @author Tien Xuan Vo <tien.xuan.vo@gmail.com>
|
||||
*/
|
||||
final class NullOutputFormatterStyle implements OutputFormatterStyleInterface
|
||||
{
|
||||
public function apply(string $text): string
|
||||
{
|
||||
return $text;
|
||||
}
|
||||
|
||||
public function setBackground(?string $color = null): void
|
||||
{
|
||||
if (1 > \func_num_args()) {
|
||||
trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
|
||||
}
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public function setForeground(?string $color = null): void
|
||||
{
|
||||
if (1 > \func_num_args()) {
|
||||
trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
|
||||
}
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public function setOption(string $option): void
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public function setOptions(array $options): void
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public function unsetOption(string $option): void
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
290
vendor/symfony/console/Formatter/OutputFormatter.php
vendored
Normal file
290
vendor/symfony/console/Formatter/OutputFormatter.php
vendored
Normal file
@ -0,0 +1,290 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Formatter;
|
||||
|
||||
use Symfony\Component\Console\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Console\Helper\Helper;
|
||||
|
||||
use function Symfony\Component\String\b;
|
||||
|
||||
/**
|
||||
* Formatter class for console output.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
* @author Roland Franssen <franssen.roland@gmail.com>
|
||||
*/
|
||||
class OutputFormatter implements WrappableOutputFormatterInterface
|
||||
{
|
||||
private bool $decorated;
|
||||
private array $styles = [];
|
||||
private OutputFormatterStyleStack $styleStack;
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
$this->styleStack = clone $this->styleStack;
|
||||
foreach ($this->styles as $key => $value) {
|
||||
$this->styles[$key] = clone $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes "<" and ">" special chars in given text.
|
||||
*/
|
||||
public static function escape(string $text): string
|
||||
{
|
||||
$text = preg_replace('/([^\\\\]|^)([<>])/', '$1\\\\$2', $text);
|
||||
|
||||
return self::escapeTrailingBackslash($text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes trailing "\" in given text.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function escapeTrailingBackslash(string $text): string
|
||||
{
|
||||
if (str_ends_with($text, '\\')) {
|
||||
$len = \strlen($text);
|
||||
$text = rtrim($text, '\\');
|
||||
$text = str_replace("\0", '', $text);
|
||||
$text .= str_repeat("\0", $len - \strlen($text));
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes console output formatter.
|
||||
*
|
||||
* @param OutputFormatterStyleInterface[] $styles Array of "name => FormatterStyle" instances
|
||||
*/
|
||||
public function __construct(bool $decorated = false, array $styles = [])
|
||||
{
|
||||
$this->decorated = $decorated;
|
||||
|
||||
$this->setStyle('error', new OutputFormatterStyle('white', 'red'));
|
||||
$this->setStyle('info', new OutputFormatterStyle('green'));
|
||||
$this->setStyle('comment', new OutputFormatterStyle('yellow'));
|
||||
$this->setStyle('question', new OutputFormatterStyle('black', 'cyan'));
|
||||
|
||||
foreach ($styles as $name => $style) {
|
||||
$this->setStyle($name, $style);
|
||||
}
|
||||
|
||||
$this->styleStack = new OutputFormatterStyleStack();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function setDecorated(bool $decorated)
|
||||
{
|
||||
$this->decorated = $decorated;
|
||||
}
|
||||
|
||||
public function isDecorated(): bool
|
||||
{
|
||||
return $this->decorated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function setStyle(string $name, OutputFormatterStyleInterface $style)
|
||||
{
|
||||
$this->styles[strtolower($name)] = $style;
|
||||
}
|
||||
|
||||
public function hasStyle(string $name): bool
|
||||
{
|
||||
return isset($this->styles[strtolower($name)]);
|
||||
}
|
||||
|
||||
public function getStyle(string $name): OutputFormatterStyleInterface
|
||||
{
|
||||
if (!$this->hasStyle($name)) {
|
||||
throw new InvalidArgumentException(\sprintf('Undefined style: "%s".', $name));
|
||||
}
|
||||
|
||||
return $this->styles[strtolower($name)];
|
||||
}
|
||||
|
||||
public function format(?string $message): ?string
|
||||
{
|
||||
return $this->formatAndWrap($message, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function formatAndWrap(?string $message, int $width)
|
||||
{
|
||||
if (null === $message) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$offset = 0;
|
||||
$output = '';
|
||||
$openTagRegex = '[a-z](?:[^\\\\<>]*+ | \\\\.)*';
|
||||
$closeTagRegex = '[a-z][^<>]*+';
|
||||
$currentLineLength = 0;
|
||||
preg_match_all("#<(($openTagRegex) | /($closeTagRegex)?)>#ix", $message, $matches, \PREG_OFFSET_CAPTURE);
|
||||
foreach ($matches[0] as $i => $match) {
|
||||
$pos = $match[1];
|
||||
$text = $match[0];
|
||||
|
||||
if (0 != $pos && '\\' == $message[$pos - 1]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// convert byte position to character position.
|
||||
$pos = Helper::length(substr($message, 0, $pos));
|
||||
// add the text up to the next tag
|
||||
$output .= $this->applyCurrentStyle(Helper::substr($message, $offset, $pos - $offset), $output, $width, $currentLineLength);
|
||||
$offset = $pos + Helper::length($text);
|
||||
|
||||
// opening tag?
|
||||
if ($open = '/' !== $text[1]) {
|
||||
$tag = $matches[1][$i][0];
|
||||
} else {
|
||||
$tag = $matches[3][$i][0] ?? '';
|
||||
}
|
||||
|
||||
if (!$open && !$tag) {
|
||||
// </>
|
||||
$this->styleStack->pop();
|
||||
} elseif (null === $style = $this->createStyleFromString($tag)) {
|
||||
$output .= $this->applyCurrentStyle($text, $output, $width, $currentLineLength);
|
||||
} elseif ($open) {
|
||||
$this->styleStack->push($style);
|
||||
} else {
|
||||
$this->styleStack->pop($style);
|
||||
}
|
||||
}
|
||||
|
||||
$output .= $this->applyCurrentStyle(Helper::substr($message, $offset), $output, $width, $currentLineLength);
|
||||
|
||||
return strtr($output, ["\0" => '\\', '\\<' => '<', '\\>' => '>']);
|
||||
}
|
||||
|
||||
public function getStyleStack(): OutputFormatterStyleStack
|
||||
{
|
||||
return $this->styleStack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to create new style instance from string.
|
||||
*/
|
||||
private function createStyleFromString(string $string): ?OutputFormatterStyleInterface
|
||||
{
|
||||
if (isset($this->styles[$string])) {
|
||||
return $this->styles[$string];
|
||||
}
|
||||
|
||||
if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', $string, $matches, \PREG_SET_ORDER)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$style = new OutputFormatterStyle();
|
||||
foreach ($matches as $match) {
|
||||
array_shift($match);
|
||||
$match[0] = strtolower($match[0]);
|
||||
|
||||
if ('fg' == $match[0]) {
|
||||
$style->setForeground(strtolower($match[1]));
|
||||
} elseif ('bg' == $match[0]) {
|
||||
$style->setBackground(strtolower($match[1]));
|
||||
} elseif ('href' === $match[0]) {
|
||||
$url = preg_replace('{\\\\([<>])}', '$1', $match[1]);
|
||||
$style->setHref($url);
|
||||
} elseif ('options' === $match[0]) {
|
||||
preg_match_all('([^,;]+)', strtolower($match[1]), $options);
|
||||
$options = array_shift($options);
|
||||
foreach ($options as $option) {
|
||||
$style->setOption($option);
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return $style;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies current style from stack to text, if must be applied.
|
||||
*/
|
||||
private function applyCurrentStyle(string $text, string $current, int $width, int &$currentLineLength): string
|
||||
{
|
||||
if ('' === $text) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!$width) {
|
||||
return $this->isDecorated() ? $this->styleStack->getCurrent()->apply($text) : $text;
|
||||
}
|
||||
|
||||
if (!$currentLineLength && '' !== $current) {
|
||||
$text = ltrim($text);
|
||||
}
|
||||
|
||||
if ($currentLineLength) {
|
||||
$lines = explode("\n", $text, 2);
|
||||
$prefix = Helper::substr($lines[0], 0, $i = $width - $currentLineLength)."\n";
|
||||
$text = Helper::substr($lines[0], $i);
|
||||
|
||||
if (isset($lines[1])) {
|
||||
// $prefix may contain the full first line in which the \n is already a part of $prefix.
|
||||
if ('' !== $text) {
|
||||
$text .= "\n";
|
||||
}
|
||||
|
||||
$text .= $lines[1];
|
||||
}
|
||||
} else {
|
||||
$prefix = '';
|
||||
}
|
||||
|
||||
preg_match('~(\\n)$~', $text, $matches);
|
||||
$text = $prefix.$this->addLineBreaks($text, $width);
|
||||
$text = rtrim($text, "\n").($matches[1] ?? '');
|
||||
|
||||
if (!$currentLineLength && '' !== $current && !str_ends_with($current, "\n")) {
|
||||
$text = "\n".$text;
|
||||
}
|
||||
|
||||
$lines = explode("\n", $text);
|
||||
|
||||
foreach ($lines as $i => $line) {
|
||||
$currentLineLength = 0 === $i ? $currentLineLength + Helper::length($line) : Helper::length($line);
|
||||
if ($width <= $currentLineLength) {
|
||||
$currentLineLength = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->isDecorated()) {
|
||||
foreach ($lines as $i => $line) {
|
||||
$lines[$i] = $this->styleStack->getCurrent()->apply($line);
|
||||
}
|
||||
}
|
||||
|
||||
return implode("\n", $lines);
|
||||
}
|
||||
|
||||
private function addLineBreaks(string $text, int $width): string
|
||||
{
|
||||
$encoding = mb_detect_encoding($text, null, true) ?: 'UTF-8';
|
||||
|
||||
return b($text)->toUnicodeString($encoding)->wordwrap($width, "\n", true)->toByteString($encoding);
|
||||
}
|
||||
}
|
||||
56
vendor/symfony/console/Formatter/OutputFormatterInterface.php
vendored
Normal file
56
vendor/symfony/console/Formatter/OutputFormatterInterface.php
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Formatter;
|
||||
|
||||
/**
|
||||
* Formatter interface for console output.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*/
|
||||
interface OutputFormatterInterface
|
||||
{
|
||||
/**
|
||||
* Sets the decorated flag.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setDecorated(bool $decorated);
|
||||
|
||||
/**
|
||||
* Whether the output will decorate messages.
|
||||
*/
|
||||
public function isDecorated(): bool;
|
||||
|
||||
/**
|
||||
* Sets a new style.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setStyle(string $name, OutputFormatterStyleInterface $style);
|
||||
|
||||
/**
|
||||
* Checks if output formatter has style with specified name.
|
||||
*/
|
||||
public function hasStyle(string $name): bool;
|
||||
|
||||
/**
|
||||
* Gets style options from style with specified name.
|
||||
*
|
||||
* @throws \InvalidArgumentException When style isn't defined
|
||||
*/
|
||||
public function getStyle(string $name): OutputFormatterStyleInterface;
|
||||
|
||||
/**
|
||||
* Formats a message according to the given styles.
|
||||
*/
|
||||
public function format(?string $message): ?string;
|
||||
}
|
||||
110
vendor/symfony/console/Formatter/OutputFormatterStyle.php
vendored
Normal file
110
vendor/symfony/console/Formatter/OutputFormatterStyle.php
vendored
Normal file
@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Formatter;
|
||||
|
||||
use Symfony\Component\Console\Color;
|
||||
|
||||
/**
|
||||
* Formatter style class for defining styles.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*/
|
||||
class OutputFormatterStyle implements OutputFormatterStyleInterface
|
||||
{
|
||||
private Color $color;
|
||||
private string $foreground;
|
||||
private string $background;
|
||||
private array $options;
|
||||
private ?string $href = null;
|
||||
private bool $handlesHrefGracefully;
|
||||
|
||||
/**
|
||||
* Initializes output formatter style.
|
||||
*
|
||||
* @param string|null $foreground The style foreground color name
|
||||
* @param string|null $background The style background color name
|
||||
*/
|
||||
public function __construct(?string $foreground = null, ?string $background = null, array $options = [])
|
||||
{
|
||||
$this->color = new Color($this->foreground = $foreground ?: '', $this->background = $background ?: '', $this->options = $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function setForeground(?string $color = null)
|
||||
{
|
||||
if (1 > \func_num_args()) {
|
||||
trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
|
||||
}
|
||||
$this->color = new Color($this->foreground = $color ?: '', $this->background, $this->options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function setBackground(?string $color = null)
|
||||
{
|
||||
if (1 > \func_num_args()) {
|
||||
trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
|
||||
}
|
||||
$this->color = new Color($this->foreground, $this->background = $color ?: '', $this->options);
|
||||
}
|
||||
|
||||
public function setHref(string $url): void
|
||||
{
|
||||
$this->href = $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function setOption(string $option)
|
||||
{
|
||||
$this->options[] = $option;
|
||||
$this->color = new Color($this->foreground, $this->background, $this->options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function unsetOption(string $option)
|
||||
{
|
||||
$pos = array_search($option, $this->options);
|
||||
if (false !== $pos) {
|
||||
unset($this->options[$pos]);
|
||||
}
|
||||
|
||||
$this->color = new Color($this->foreground, $this->background, $this->options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function setOptions(array $options)
|
||||
{
|
||||
$this->color = new Color($this->foreground, $this->background, $this->options = $options);
|
||||
}
|
||||
|
||||
public function apply(string $text): string
|
||||
{
|
||||
$this->handlesHrefGracefully ??= 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR')
|
||||
&& (!getenv('KONSOLE_VERSION') || (int) getenv('KONSOLE_VERSION') > 201100)
|
||||
&& !isset($_SERVER['IDEA_INITIAL_DIRECTORY']);
|
||||
|
||||
if (null !== $this->href && $this->handlesHrefGracefully) {
|
||||
$text = "\033]8;;$this->href\033\\$text\033]8;;\033\\";
|
||||
}
|
||||
|
||||
return $this->color->apply($text);
|
||||
}
|
||||
}
|
||||
60
vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php
vendored
Normal file
60
vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php
vendored
Normal 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\Formatter;
|
||||
|
||||
/**
|
||||
* Formatter style interface for defining styles.
|
||||
*
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*/
|
||||
interface OutputFormatterStyleInterface
|
||||
{
|
||||
/**
|
||||
* Sets style foreground color.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setForeground(?string $color);
|
||||
|
||||
/**
|
||||
* Sets style background color.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setBackground(?string $color);
|
||||
|
||||
/**
|
||||
* Sets some specific style option.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setOption(string $option);
|
||||
|
||||
/**
|
||||
* Unsets some specific style option.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function unsetOption(string $option);
|
||||
|
||||
/**
|
||||
* Sets multiple style options at once.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setOptions(array $options);
|
||||
|
||||
/**
|
||||
* Applies the style to a given text.
|
||||
*/
|
||||
public function apply(string $text): string;
|
||||
}
|
||||
107
vendor/symfony/console/Formatter/OutputFormatterStyleStack.php
vendored
Normal file
107
vendor/symfony/console/Formatter/OutputFormatterStyleStack.php
vendored
Normal 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\Formatter;
|
||||
|
||||
use Symfony\Component\Console\Exception\InvalidArgumentException;
|
||||
use Symfony\Contracts\Service\ResetInterface;
|
||||
|
||||
/**
|
||||
* @author Jean-François Simon <contact@jfsimon.fr>
|
||||
*/
|
||||
class OutputFormatterStyleStack implements ResetInterface
|
||||
{
|
||||
/**
|
||||
* @var OutputFormatterStyleInterface[]
|
||||
*/
|
||||
private array $styles = [];
|
||||
|
||||
private OutputFormatterStyleInterface $emptyStyle;
|
||||
|
||||
public function __construct(?OutputFormatterStyleInterface $emptyStyle = null)
|
||||
{
|
||||
$this->emptyStyle = $emptyStyle ?? new OutputFormatterStyle();
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets stack (ie. empty internal arrays).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
$this->styles = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes a style in the stack.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function push(OutputFormatterStyleInterface $style)
|
||||
{
|
||||
$this->styles[] = $style;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pops a style from the stack.
|
||||
*
|
||||
* @throws InvalidArgumentException When style tags incorrectly nested
|
||||
*/
|
||||
public function pop(?OutputFormatterStyleInterface $style = null): OutputFormatterStyleInterface
|
||||
{
|
||||
if (!$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.
|
||||
*/
|
||||
public function getCurrent(): OutputFormatterStyleInterface
|
||||
{
|
||||
if (!$this->styles) {
|
||||
return $this->emptyStyle;
|
||||
}
|
||||
|
||||
return $this->styles[\count($this->styles) - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle): static
|
||||
{
|
||||
$this->emptyStyle = $emptyStyle;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getEmptyStyle(): OutputFormatterStyleInterface
|
||||
{
|
||||
return $this->emptyStyle;
|
||||
}
|
||||
}
|
||||
27
vendor/symfony/console/Formatter/WrappableOutputFormatterInterface.php
vendored
Normal file
27
vendor/symfony/console/Formatter/WrappableOutputFormatterInterface.php
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Formatter;
|
||||
|
||||
/**
|
||||
* Formatter interface for console output that supports word wrapping.
|
||||
*
|
||||
* @author Roland Franssen <franssen.roland@gmail.com>
|
||||
*/
|
||||
interface WrappableOutputFormatterInterface extends OutputFormatterInterface
|
||||
{
|
||||
/**
|
||||
* Formats a message according to the given styles, wrapping at `$width` (0 means no wrapping).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function formatAndWrap(?string $message, int $width);
|
||||
}
|
||||
98
vendor/symfony/console/Helper/DebugFormatterHelper.php
vendored
Normal file
98
vendor/symfony/console/Helper/DebugFormatterHelper.php
vendored
Normal file
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\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 const COLORS = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'default'];
|
||||
private array $started = [];
|
||||
private int $count = -1;
|
||||
|
||||
/**
|
||||
* Starts a debug formatting session.
|
||||
*/
|
||||
public function start(string $id, string $message, string $prefix = 'RUN'): string
|
||||
{
|
||||
$this->started[$id] = ['border' => ++$this->count % \count(self::COLORS)];
|
||||
|
||||
return \sprintf("%s<bg=blue;fg=white> %s </> <fg=blue>%s</>\n", $this->getBorder($id), $prefix, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds progress to a formatting session.
|
||||
*/
|
||||
public function progress(string $id, string $buffer, bool $error = false, string $prefix = 'OUT', string $errorPrefix = 'ERR'): string
|
||||
{
|
||||
$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.
|
||||
*/
|
||||
public function stop(string $id, string $message, bool $successful, string $prefix = 'RES'): string
|
||||
{
|
||||
$trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : '';
|
||||
|
||||
if ($successful) {
|
||||
return \sprintf("%s%s<bg=green;fg=white> %s </> <fg=green>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
|
||||
}
|
||||
|
||||
$message = \sprintf("%s%s<bg=red;fg=white> %s </> <fg=red>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
|
||||
|
||||
unset($this->started[$id]['out'], $this->started[$id]['err']);
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
private function getBorder(string $id): string
|
||||
{
|
||||
return \sprintf('<bg=%s> </>', self::COLORS[$this->started[$id]['border']]);
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return 'debug_formatter';
|
||||
}
|
||||
}
|
||||
93
vendor/symfony/console/Helper/DescriptorHelper.php
vendored
Normal file
93
vendor/symfony/console/Helper/DescriptorHelper.php
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\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\ReStructuredTextDescriptor;
|
||||
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 array $descriptors = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->register('txt', new TextDescriptor())
|
||||
->register('xml', new XmlDescriptor())
|
||||
->register('json', new JsonDescriptor())
|
||||
->register('md', new MarkdownDescriptor())
|
||||
->register('rst', new ReStructuredTextDescriptor())
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes an object if supported.
|
||||
*
|
||||
* Available options are:
|
||||
* * format: string, the output format name
|
||||
* * raw_text: boolean, sets output type as raw
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws InvalidArgumentException when the given format is not supported
|
||||
*/
|
||||
public function describe(OutputInterface $output, ?object $object, array $options = [])
|
||||
{
|
||||
$options = array_merge([
|
||||
'raw_text' => false,
|
||||
'format' => 'txt',
|
||||
], $options);
|
||||
|
||||
if (!isset($this->descriptors[$options['format']])) {
|
||||
throw new InvalidArgumentException(\sprintf('Unsupported format "%s".', $options['format']));
|
||||
}
|
||||
|
||||
$descriptor = $this->descriptors[$options['format']];
|
||||
$descriptor->describe($output, $object, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a descriptor.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function register(string $format, DescriptorInterface $descriptor): static
|
||||
{
|
||||
$this->descriptors[$format] = $descriptor;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return 'descriptor';
|
||||
}
|
||||
|
||||
public function getFormats(): array
|
||||
{
|
||||
return array_keys($this->descriptors);
|
||||
}
|
||||
}
|
||||
57
vendor/symfony/console/Helper/Dumper.php
vendored
Normal file
57
vendor/symfony/console/Helper/Dumper.php
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Helper;
|
||||
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\VarDumper\Cloner\ClonerInterface;
|
||||
use Symfony\Component\VarDumper\Cloner\VarCloner;
|
||||
use Symfony\Component\VarDumper\Dumper\CliDumper;
|
||||
|
||||
/**
|
||||
* @author Roland Franssen <franssen.roland@gmail.com>
|
||||
*/
|
||||
final class Dumper
|
||||
{
|
||||
private OutputInterface $output;
|
||||
private ?CliDumper $dumper;
|
||||
private ?ClonerInterface $cloner;
|
||||
private \Closure $handler;
|
||||
|
||||
public function __construct(OutputInterface $output, ?CliDumper $dumper = null, ?ClonerInterface $cloner = null)
|
||||
{
|
||||
$this->output = $output;
|
||||
$this->dumper = $dumper;
|
||||
$this->cloner = $cloner;
|
||||
|
||||
if (class_exists(CliDumper::class)) {
|
||||
$this->handler = function ($var): string {
|
||||
$dumper = $this->dumper ??= new CliDumper(null, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR);
|
||||
$dumper->setColors($this->output->isDecorated());
|
||||
|
||||
return rtrim($dumper->dump(($this->cloner ??= new VarCloner())->cloneVar($var)->withRefHandles(false), true));
|
||||
};
|
||||
} else {
|
||||
$this->handler = fn ($var): string => match (true) {
|
||||
null === $var => 'null',
|
||||
true === $var => 'true',
|
||||
false === $var => 'false',
|
||||
\is_string($var) => '"'.$var.'"',
|
||||
default => rtrim(print_r($var, true)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public function __invoke(mixed $var): string
|
||||
{
|
||||
return ($this->handler)($var);
|
||||
}
|
||||
}
|
||||
81
vendor/symfony/console/Helper/FormatterHelper.php
vendored
Normal file
81
vendor/symfony/console/Helper/FormatterHelper.php
vendored
Normal 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\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.
|
||||
*/
|
||||
public function formatSection(string $section, string $message, string $style = 'info'): string
|
||||
{
|
||||
return \sprintf('<%s>[%s]</%s> %s', $style, $section, $style, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a message as a block of text.
|
||||
*/
|
||||
public function formatBlock(string|array $messages, string $style, bool $large = false): string
|
||||
{
|
||||
if (!\is_array($messages)) {
|
||||
$messages = [$messages];
|
||||
}
|
||||
|
||||
$len = 0;
|
||||
$lines = [];
|
||||
foreach ($messages as $message) {
|
||||
$message = OutputFormatter::escape($message);
|
||||
$lines[] = \sprintf($large ? ' %s ' : ' %s ', $message);
|
||||
$len = max(self::width($message) + ($large ? 4 : 2), $len);
|
||||
}
|
||||
|
||||
$messages = $large ? [str_repeat(' ', $len)] : [];
|
||||
for ($i = 0; isset($lines[$i]); ++$i) {
|
||||
$messages[] = $lines[$i].str_repeat(' ', $len - self::width($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.
|
||||
*/
|
||||
public function truncate(string $message, int $length, string $suffix = '...'): string
|
||||
{
|
||||
$computedLength = $length - self::width($suffix);
|
||||
|
||||
if ($computedLength > self::width($message)) {
|
||||
return $message;
|
||||
}
|
||||
|
||||
return self::substr($message, 0, $length).$suffix;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return 'formatter';
|
||||
}
|
||||
}
|
||||
180
vendor/symfony/console/Helper/Helper.php
vendored
Normal file
180
vendor/symfony/console/Helper/Helper.php
vendored
Normal file
@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Helper;
|
||||
|
||||
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
|
||||
use Symfony\Component\String\UnicodeString;
|
||||
|
||||
/**
|
||||
* Helper is the base class for all helper classes.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
abstract class Helper implements HelperInterface
|
||||
{
|
||||
protected $helperSet;
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function setHelperSet(?HelperSet $helperSet = null)
|
||||
{
|
||||
if (1 > \func_num_args()) {
|
||||
trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
|
||||
}
|
||||
$this->helperSet = $helperSet;
|
||||
}
|
||||
|
||||
public function getHelperSet(): ?HelperSet
|
||||
{
|
||||
return $this->helperSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the width of a string, using mb_strwidth if it is available.
|
||||
* The width is how many characters positions the string will use.
|
||||
*/
|
||||
public static function width(?string $string): int
|
||||
{
|
||||
$string ??= '';
|
||||
|
||||
if (preg_match('//u', $string)) {
|
||||
$string = preg_replace('/[\p{Cc}\x7F]++/u', '', $string, -1, $count);
|
||||
|
||||
return (new UnicodeString($string))->width(false) + $count;
|
||||
}
|
||||
|
||||
if (false === $encoding = mb_detect_encoding($string, null, true)) {
|
||||
return \strlen($string);
|
||||
}
|
||||
|
||||
return mb_strwidth($string, $encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the length of a string, using mb_strlen if it is available.
|
||||
* The length is related to how many bytes the string will use.
|
||||
*/
|
||||
public static function length(?string $string): int
|
||||
{
|
||||
$string ??= '';
|
||||
|
||||
if (preg_match('//u', $string)) {
|
||||
return (new UnicodeString($string))->length();
|
||||
}
|
||||
|
||||
if (false === $encoding = mb_detect_encoding($string, null, true)) {
|
||||
return \strlen($string);
|
||||
}
|
||||
|
||||
return mb_strlen($string, $encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the subset of a string, using mb_substr if it is available.
|
||||
*/
|
||||
public static function substr(?string $string, int $from, ?int $length = null): string
|
||||
{
|
||||
$string ??= '';
|
||||
|
||||
if (preg_match('//u', $string)) {
|
||||
return (new UnicodeString($string))->slice($from, $length);
|
||||
}
|
||||
|
||||
if (false === $encoding = mb_detect_encoding($string, null, true)) {
|
||||
return substr($string, $from, $length);
|
||||
}
|
||||
|
||||
return mb_substr($string, $from, $length, $encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function formatTime(int|float $secs, int $precision = 1)
|
||||
{
|
||||
$secs = (int) floor($secs);
|
||||
|
||||
if (0 === $secs) {
|
||||
return '< 1 sec';
|
||||
}
|
||||
|
||||
static $timeFormats = [
|
||||
[1, '1 sec', 'secs'],
|
||||
[60, '1 min', 'mins'],
|
||||
[3600, '1 hr', 'hrs'],
|
||||
[86400, '1 day', 'days'],
|
||||
];
|
||||
|
||||
$times = [];
|
||||
foreach ($timeFormats as $index => $format) {
|
||||
$seconds = isset($timeFormats[$index + 1]) ? $secs % $timeFormats[$index + 1][0] : $secs;
|
||||
|
||||
if (isset($times[$index - $precision])) {
|
||||
unset($times[$index - $precision]);
|
||||
}
|
||||
|
||||
if (0 === $seconds) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$unitCount = ($seconds / $format[0]);
|
||||
$times[$index] = 1 === $unitCount ? $format[1] : $unitCount.' '.$format[2];
|
||||
|
||||
if ($secs === $seconds) {
|
||||
break;
|
||||
}
|
||||
|
||||
$secs -= $seconds;
|
||||
}
|
||||
|
||||
return implode(', ', array_reverse($times));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function formatMemory(int $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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function removeDecoration(OutputFormatterInterface $formatter, ?string $string)
|
||||
{
|
||||
$isDecorated = $formatter->isDecorated();
|
||||
$formatter->setDecorated(false);
|
||||
// remove <...> formatting
|
||||
$string = $formatter->format($string ?? '');
|
||||
// remove already formatted characters
|
||||
$string = preg_replace("/\033\[[^m]*m/", '', $string ?? '');
|
||||
// remove terminal hyperlinks
|
||||
$string = preg_replace('/\\033]8;[^;]*;[^\\033]*\\033\\\\/', '', $string ?? '');
|
||||
$formatter->setDecorated($isDecorated);
|
||||
|
||||
return $string;
|
||||
}
|
||||
}
|
||||
39
vendor/symfony/console/Helper/HelperInterface.php
vendored
Normal file
39
vendor/symfony/console/Helper/HelperInterface.php
vendored
Normal 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.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setHelperSet(?HelperSet $helperSet);
|
||||
|
||||
/**
|
||||
* Gets the helper set associated with this helper.
|
||||
*/
|
||||
public function getHelperSet(): ?HelperSet;
|
||||
|
||||
/**
|
||||
* Returns the canonical name of this helper.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName();
|
||||
}
|
||||
77
vendor/symfony/console/Helper/HelperSet.php
vendored
Normal file
77
vendor/symfony/console/Helper/HelperSet.php
vendored
Normal 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\Console\Helper;
|
||||
|
||||
use Symfony\Component\Console\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* HelperSet represents a set of helpers to be used with a command.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @implements \IteratorAggregate<string, HelperInterface>
|
||||
*/
|
||||
class HelperSet implements \IteratorAggregate
|
||||
{
|
||||
/** @var array<string, HelperInterface> */
|
||||
private array $helpers = [];
|
||||
|
||||
/**
|
||||
* @param HelperInterface[] $helpers
|
||||
*/
|
||||
public function __construct(array $helpers = [])
|
||||
{
|
||||
foreach ($helpers as $alias => $helper) {
|
||||
$this->set($helper, \is_int($alias) ? null : $alias);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function set(HelperInterface $helper, ?string $alias = null)
|
||||
{
|
||||
$this->helpers[$helper->getName()] = $helper;
|
||||
if (null !== $alias) {
|
||||
$this->helpers[$alias] = $helper;
|
||||
}
|
||||
|
||||
$helper->setHelperSet($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the helper if defined.
|
||||
*/
|
||||
public function has(string $name): bool
|
||||
{
|
||||
return isset($this->helpers[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a helper value.
|
||||
*
|
||||
* @throws InvalidArgumentException if the helper is not defined
|
||||
*/
|
||||
public function get(string $name): HelperInterface
|
||||
{
|
||||
if (!$this->has($name)) {
|
||||
throw new InvalidArgumentException(\sprintf('The helper "%s" is not defined.', $name));
|
||||
}
|
||||
|
||||
return $this->helpers[$name];
|
||||
}
|
||||
|
||||
public function getIterator(): \Traversable
|
||||
{
|
||||
return new \ArrayIterator($this->helpers);
|
||||
}
|
||||
}
|
||||
33
vendor/symfony/console/Helper/InputAwareHelper.php
vendored
Normal file
33
vendor/symfony/console/Helper/InputAwareHelper.php
vendored
Normal 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;
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function setInput(InputInterface $input)
|
||||
{
|
||||
$this->input = $input;
|
||||
}
|
||||
}
|
||||
76
vendor/symfony/console/Helper/OutputWrapper.php
vendored
Normal file
76
vendor/symfony/console/Helper/OutputWrapper.php
vendored
Normal 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;
|
||||
|
||||
/**
|
||||
* Simple output wrapper for "tagged outputs" instead of wordwrap(). This solution is based on a StackOverflow
|
||||
* answer: https://stackoverflow.com/a/20434776/1476819 from user557597 (alias SLN).
|
||||
*
|
||||
* (?:
|
||||
* # -- Words/Characters
|
||||
* ( # (1 start)
|
||||
* (?> # Atomic Group - Match words with valid breaks
|
||||
* .{1,16} # 1-N characters
|
||||
* # Followed by one of 4 prioritized, non-linebreak whitespace
|
||||
* (?: # break types:
|
||||
* (?<= [^\S\r\n] ) # 1. - Behind a non-linebreak whitespace
|
||||
* [^\S\r\n]? # ( optionally accept an extra non-linebreak whitespace )
|
||||
* | (?= \r? \n ) # 2. - Ahead a linebreak
|
||||
* | $ # 3. - EOS
|
||||
* | [^\S\r\n] # 4. - Accept an extra non-linebreak whitespace
|
||||
* )
|
||||
* ) # End atomic group
|
||||
* |
|
||||
* .{1,16} # No valid word breaks, just break on the N'th character
|
||||
* ) # (1 end)
|
||||
* (?: \r? \n )? # Optional linebreak after Words/Characters
|
||||
* |
|
||||
* # -- Or, Linebreak
|
||||
* (?: \r? \n | $ ) # Stand alone linebreak or at EOS
|
||||
* )
|
||||
*
|
||||
* @author Krisztián Ferenczi <ferenczi.krisztian@gmail.com>
|
||||
*
|
||||
* @see https://stackoverflow.com/a/20434776/1476819
|
||||
*/
|
||||
final class OutputWrapper
|
||||
{
|
||||
private const TAG_OPEN_REGEX_SEGMENT = '[a-z](?:[^\\\\<>]*+ | \\\\.)*';
|
||||
private const TAG_CLOSE_REGEX_SEGMENT = '[a-z][^<>]*+';
|
||||
private const URL_PATTERN = 'https?://\S+';
|
||||
|
||||
public function __construct(
|
||||
private bool $allowCutUrls = false,
|
||||
) {
|
||||
}
|
||||
|
||||
public function wrap(string $text, int $width, string $break = "\n"): string
|
||||
{
|
||||
if (!$width) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
$tagPattern = \sprintf('<(?:(?:%s)|/(?:%s)?)>', self::TAG_OPEN_REGEX_SEGMENT, self::TAG_CLOSE_REGEX_SEGMENT);
|
||||
$limitPattern = "{1,$width}";
|
||||
$patternBlocks = [$tagPattern];
|
||||
if (!$this->allowCutUrls) {
|
||||
$patternBlocks[] = self::URL_PATTERN;
|
||||
}
|
||||
$patternBlocks[] = '.';
|
||||
$blocks = implode('|', $patternBlocks);
|
||||
$rowPattern = "(?:$blocks)$limitPattern";
|
||||
$pattern = \sprintf('#(?:((?>(%1$s)((?<=[^\S\r\n])[^\S\r\n]?|(?=\r?\n)|$|[^\S\r\n]))|(%1$s))(?:\r?\n)?|(?:\r?\n|$))#imux', $rowPattern);
|
||||
$output = rtrim(preg_replace($pattern, '\\1'.$break, $text), $break);
|
||||
|
||||
return str_replace(' '.$break, $break, $output);
|
||||
}
|
||||
}
|
||||
137
vendor/symfony/console/Helper/ProcessHelper.php
vendored
Normal file
137
vendor/symfony/console/Helper/ProcessHelper.php
vendored
Normal 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\Helper;
|
||||
|
||||
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Process\Exception\ProcessFailedException;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
/**
|
||||
* The ProcessHelper class provides helpers to run external processes.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class ProcessHelper extends Helper
|
||||
{
|
||||
/**
|
||||
* Runs an external process.
|
||||
*
|
||||
* @param array|Process $cmd An instance of Process or an array of the command and arguments
|
||||
* @param callable|null $callback A PHP callback to run whenever there is some
|
||||
* output available on STDOUT or STDERR
|
||||
*/
|
||||
public function run(OutputInterface $output, array|Process $cmd, ?string $error = null, ?callable $callback = null, int $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE): Process
|
||||
{
|
||||
if (!class_exists(Process::class)) {
|
||||
throw new \LogicException('The ProcessHelper cannot be run as the Process component is not installed. Try running "compose require symfony/process".');
|
||||
}
|
||||
|
||||
if ($output instanceof ConsoleOutputInterface) {
|
||||
$output = $output->getErrorOutput();
|
||||
}
|
||||
|
||||
$formatter = $this->getHelperSet()->get('debug_formatter');
|
||||
|
||||
if ($cmd instanceof Process) {
|
||||
$cmd = [$cmd];
|
||||
}
|
||||
|
||||
if (\is_string($cmd[0] ?? null)) {
|
||||
$process = new Process($cmd);
|
||||
$cmd = [];
|
||||
} elseif (($cmd[0] ?? null) instanceof Process) {
|
||||
$process = $cmd[0];
|
||||
unset($cmd[0]);
|
||||
} else {
|
||||
throw new \InvalidArgumentException(\sprintf('Invalid command provided to "%s()": the command should be an array whose first element is either the path to the binary to run or a "Process" object.', __METHOD__));
|
||||
}
|
||||
|
||||
if ($verbosity <= $output->getVerbosity()) {
|
||||
$output->write($formatter->start(spl_object_hash($process), $this->escapeString($process->getCommandLine())));
|
||||
}
|
||||
|
||||
if ($output->isDebug()) {
|
||||
$callback = $this->wrapCallback($output, $process, $callback);
|
||||
}
|
||||
|
||||
$process->run($callback, $cmd);
|
||||
|
||||
if ($verbosity <= $output->getVerbosity()) {
|
||||
$message = $process->isSuccessful() ? 'Command ran successfully' : \sprintf('%s Command did not run successfully', $process->getExitCode());
|
||||
$output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful()));
|
||||
}
|
||||
|
||||
if (!$process->isSuccessful() && null !== $error) {
|
||||
$output->writeln(\sprintf('<error>%s</error>', $this->escapeString($error)));
|
||||
}
|
||||
|
||||
return $process;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the process.
|
||||
*
|
||||
* This is identical to run() except that an exception is thrown if the process
|
||||
* exits with a non-zero exit code.
|
||||
*
|
||||
* @param array|Process $cmd An instance of Process or a command to run
|
||||
* @param callable|null $callback A PHP callback to run whenever there is some
|
||||
* output available on STDOUT or STDERR
|
||||
*
|
||||
* @throws ProcessFailedException
|
||||
*
|
||||
* @see run()
|
||||
*/
|
||||
public function mustRun(OutputInterface $output, array|Process $cmd, ?string $error = null, ?callable $callback = null): Process
|
||||
{
|
||||
$process = $this->run($output, $cmd, $error, $callback);
|
||||
|
||||
if (!$process->isSuccessful()) {
|
||||
throw new ProcessFailedException($process);
|
||||
}
|
||||
|
||||
return $process;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a Process callback to add debugging output.
|
||||
*/
|
||||
public function wrapCallback(OutputInterface $output, Process $process, ?callable $callback = null): callable
|
||||
{
|
||||
if ($output instanceof ConsoleOutputInterface) {
|
||||
$output = $output->getErrorOutput();
|
||||
}
|
||||
|
||||
$formatter = $this->getHelperSet()->get('debug_formatter');
|
||||
|
||||
return function ($type, $buffer) use ($output, $process, $callback, $formatter) {
|
||||
$output->write($formatter->progress(spl_object_hash($process), $this->escapeString($buffer), Process::ERR === $type));
|
||||
|
||||
if (null !== $callback) {
|
||||
$callback($type, $buffer);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private function escapeString(string $str): string
|
||||
{
|
||||
return str_replace('<', '\\<', $str);
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return 'process';
|
||||
}
|
||||
}
|
||||
627
vendor/symfony/console/Helper/ProgressBar.php
vendored
Normal file
627
vendor/symfony/console/Helper/ProgressBar.php
vendored
Normal file
@ -0,0 +1,627 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@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\Cursor;
|
||||
use Symfony\Component\Console\Exception\LogicException;
|
||||
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||
use Symfony\Component\Console\Output\ConsoleSectionOutput;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Terminal;
|
||||
|
||||
/**
|
||||
* The ProgressBar provides helpers to display progress output.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Chris Jones <leeked@gmail.com>
|
||||
*/
|
||||
final class ProgressBar
|
||||
{
|
||||
public const FORMAT_VERBOSE = 'verbose';
|
||||
public const FORMAT_VERY_VERBOSE = 'very_verbose';
|
||||
public const FORMAT_DEBUG = 'debug';
|
||||
public const FORMAT_NORMAL = 'normal';
|
||||
|
||||
private const FORMAT_VERBOSE_NOMAX = 'verbose_nomax';
|
||||
private const FORMAT_VERY_VERBOSE_NOMAX = 'very_verbose_nomax';
|
||||
private const FORMAT_DEBUG_NOMAX = 'debug_nomax';
|
||||
private const FORMAT_NORMAL_NOMAX = 'normal_nomax';
|
||||
|
||||
private int $barWidth = 28;
|
||||
private string $barChar;
|
||||
private string $emptyBarChar = '-';
|
||||
private string $progressChar = '>';
|
||||
private ?string $format = null;
|
||||
private ?string $internalFormat = null;
|
||||
private ?int $redrawFreq = 1;
|
||||
private int $writeCount = 0;
|
||||
private float $lastWriteTime = 0;
|
||||
private float $minSecondsBetweenRedraws = 0;
|
||||
private float $maxSecondsBetweenRedraws = 1;
|
||||
private OutputInterface $output;
|
||||
private int $step = 0;
|
||||
private int $startingStep = 0;
|
||||
private ?int $max = null;
|
||||
private int $startTime;
|
||||
private int $stepWidth;
|
||||
private float $percent = 0.0;
|
||||
private array $messages = [];
|
||||
private bool $overwrite = true;
|
||||
private Terminal $terminal;
|
||||
private ?string $previousMessage = null;
|
||||
private Cursor $cursor;
|
||||
private array $placeholders = [];
|
||||
|
||||
private static array $formatters;
|
||||
private static array $formats;
|
||||
|
||||
/**
|
||||
* @param int $max Maximum steps (0 if unknown)
|
||||
*/
|
||||
public function __construct(OutputInterface $output, int $max = 0, float $minSecondsBetweenRedraws = 1 / 25)
|
||||
{
|
||||
if ($output instanceof ConsoleOutputInterface) {
|
||||
$output = $output->getErrorOutput();
|
||||
}
|
||||
|
||||
$this->output = $output;
|
||||
$this->setMaxSteps($max);
|
||||
$this->terminal = new Terminal();
|
||||
|
||||
if (0 < $minSecondsBetweenRedraws) {
|
||||
$this->redrawFreq = null;
|
||||
$this->minSecondsBetweenRedraws = $minSecondsBetweenRedraws;
|
||||
}
|
||||
|
||||
if (!$this->output->isDecorated()) {
|
||||
// disable overwrite when output does not support ANSI codes.
|
||||
$this->overwrite = false;
|
||||
|
||||
// set a reasonable redraw frequency so output isn't flooded
|
||||
$this->redrawFreq = null;
|
||||
}
|
||||
|
||||
$this->startTime = time();
|
||||
$this->cursor = new Cursor($output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a placeholder formatter for a given name, globally for all instances of ProgressBar.
|
||||
*
|
||||
* This method also allow you to override an existing placeholder.
|
||||
*
|
||||
* @param string $name The placeholder name (including the delimiter char like %)
|
||||
* @param callable(ProgressBar):string $callable A PHP callable
|
||||
*/
|
||||
public static function setPlaceholderFormatterDefinition(string $name, callable $callable): void
|
||||
{
|
||||
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 %)
|
||||
*/
|
||||
public static function getPlaceholderFormatterDefinition(string $name): ?callable
|
||||
{
|
||||
self::$formatters ??= self::initPlaceholderFormatters();
|
||||
|
||||
return self::$formatters[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a placeholder formatter for a given name, for this instance only.
|
||||
*
|
||||
* @param callable(ProgressBar):string $callable A PHP callable
|
||||
*/
|
||||
public function setPlaceholderFormatter(string $name, callable $callable): void
|
||||
{
|
||||
$this->placeholders[$name] = $callable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the placeholder formatter for a given name.
|
||||
*
|
||||
* @param string $name The placeholder name (including the delimiter char like %)
|
||||
*/
|
||||
public function getPlaceholderFormatter(string $name): ?callable
|
||||
{
|
||||
return $this->placeholders[$name] ?? $this::getPlaceholderFormatterDefinition($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a format for a given name.
|
||||
*
|
||||
* This method also allow you to override an existing format.
|
||||
*
|
||||
* @param string $name The format name
|
||||
* @param string $format A format string
|
||||
*/
|
||||
public static function setFormatDefinition(string $name, string $format): void
|
||||
{
|
||||
self::$formats ??= self::initFormats();
|
||||
|
||||
self::$formats[$name] = $format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the format for a given name.
|
||||
*
|
||||
* @param string $name The format name
|
||||
*/
|
||||
public static function getFormatDefinition(string $name): ?string
|
||||
{
|
||||
self::$formats ??= self::initFormats();
|
||||
|
||||
return self::$formats[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates a text with a named placeholder.
|
||||
*
|
||||
* The text is displayed when the progress bar is rendered but only
|
||||
* when the corresponding placeholder is part of the custom format line
|
||||
* (by wrapping the name with %).
|
||||
*
|
||||
* @param string $message The text to associate with the placeholder
|
||||
* @param string $name The name of the placeholder
|
||||
*/
|
||||
public function setMessage(string $message, string $name = 'message'): void
|
||||
{
|
||||
$this->messages[$name] = $message;
|
||||
}
|
||||
|
||||
public function getMessage(string $name = 'message'): ?string
|
||||
{
|
||||
return $this->messages[$name] ?? null;
|
||||
}
|
||||
|
||||
public function getStartTime(): int
|
||||
{
|
||||
return $this->startTime;
|
||||
}
|
||||
|
||||
public function getMaxSteps(): int
|
||||
{
|
||||
return $this->max;
|
||||
}
|
||||
|
||||
public function getProgress(): int
|
||||
{
|
||||
return $this->step;
|
||||
}
|
||||
|
||||
private function getStepWidth(): int
|
||||
{
|
||||
return $this->stepWidth;
|
||||
}
|
||||
|
||||
public function getProgressPercent(): float
|
||||
{
|
||||
return $this->percent;
|
||||
}
|
||||
|
||||
public function getBarOffset(): float
|
||||
{
|
||||
return floor($this->max ? $this->percent * $this->barWidth : (null === $this->redrawFreq ? (int) (min(5, $this->barWidth / 15) * $this->writeCount) : $this->step) % $this->barWidth);
|
||||
}
|
||||
|
||||
public function getEstimated(): float
|
||||
{
|
||||
if (0 === $this->step || $this->step === $this->startingStep) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return round((time() - $this->startTime) / ($this->step - $this->startingStep) * $this->max);
|
||||
}
|
||||
|
||||
public function getRemaining(): float
|
||||
{
|
||||
if (0 === $this->step || $this->step === $this->startingStep) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return round((time() - $this->startTime) / ($this->step - $this->startingStep) * ($this->max - $this->step));
|
||||
}
|
||||
|
||||
public function setBarWidth(int $size): void
|
||||
{
|
||||
$this->barWidth = max(1, $size);
|
||||
}
|
||||
|
||||
public function getBarWidth(): int
|
||||
{
|
||||
return $this->barWidth;
|
||||
}
|
||||
|
||||
public function setBarCharacter(string $char): void
|
||||
{
|
||||
$this->barChar = $char;
|
||||
}
|
||||
|
||||
public function getBarCharacter(): string
|
||||
{
|
||||
return $this->barChar ?? ($this->max ? '=' : $this->emptyBarChar);
|
||||
}
|
||||
|
||||
public function setEmptyBarCharacter(string $char): void
|
||||
{
|
||||
$this->emptyBarChar = $char;
|
||||
}
|
||||
|
||||
public function getEmptyBarCharacter(): string
|
||||
{
|
||||
return $this->emptyBarChar;
|
||||
}
|
||||
|
||||
public function setProgressCharacter(string $char): void
|
||||
{
|
||||
$this->progressChar = $char;
|
||||
}
|
||||
|
||||
public function getProgressCharacter(): string
|
||||
{
|
||||
return $this->progressChar;
|
||||
}
|
||||
|
||||
public function setFormat(string $format): void
|
||||
{
|
||||
$this->format = null;
|
||||
$this->internalFormat = $format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the redraw frequency.
|
||||
*
|
||||
* @param int|null $freq The frequency in steps
|
||||
*/
|
||||
public function setRedrawFrequency(?int $freq): void
|
||||
{
|
||||
$this->redrawFreq = null !== $freq ? max(1, $freq) : null;
|
||||
}
|
||||
|
||||
public function minSecondsBetweenRedraws(float $seconds): void
|
||||
{
|
||||
$this->minSecondsBetweenRedraws = $seconds;
|
||||
}
|
||||
|
||||
public function maxSecondsBetweenRedraws(float $seconds): void
|
||||
{
|
||||
$this->maxSecondsBetweenRedraws = $seconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator that will automatically update the progress bar when iterated.
|
||||
*
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
*
|
||||
* @param iterable<TKey, TValue> $iterable
|
||||
* @param int|null $max Number of steps to complete the bar (0 if indeterminate), if null it will be inferred from $iterable
|
||||
*
|
||||
* @return iterable<TKey, TValue>
|
||||
*/
|
||||
public function iterate(iterable $iterable, ?int $max = null): iterable
|
||||
{
|
||||
$this->start($max ?? (is_countable($iterable) ? \count($iterable) : 0));
|
||||
|
||||
foreach ($iterable as $key => $value) {
|
||||
yield $key => $value;
|
||||
|
||||
$this->advance();
|
||||
}
|
||||
|
||||
$this->finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the progress output.
|
||||
*
|
||||
* @param int|null $max Number of steps to complete the bar (0 if indeterminate), null to leave unchanged
|
||||
* @param int $startAt The starting point of the bar (useful e.g. when resuming a previously started bar)
|
||||
*/
|
||||
public function start(?int $max = null, int $startAt = 0): void
|
||||
{
|
||||
$this->startTime = time();
|
||||
$this->step = $startAt;
|
||||
$this->startingStep = $startAt;
|
||||
|
||||
$startAt > 0 ? $this->setProgress($startAt) : $this->percent = 0.0;
|
||||
|
||||
if (null !== $max) {
|
||||
$this->setMaxSteps($max);
|
||||
}
|
||||
|
||||
$this->display();
|
||||
}
|
||||
|
||||
/**
|
||||
* Advances the progress output X steps.
|
||||
*
|
||||
* @param int $step Number of steps to advance
|
||||
*/
|
||||
public function advance(int $step = 1): void
|
||||
{
|
||||
$this->setProgress($this->step + $step);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to overwrite the progressbar, false for new line.
|
||||
*/
|
||||
public function setOverwrite(bool $overwrite): void
|
||||
{
|
||||
$this->overwrite = $overwrite;
|
||||
}
|
||||
|
||||
public function setProgress(int $step): void
|
||||
{
|
||||
if ($this->max && $step > $this->max) {
|
||||
$this->max = $step;
|
||||
} elseif ($step < 0) {
|
||||
$step = 0;
|
||||
}
|
||||
|
||||
$redrawFreq = $this->redrawFreq ?? (($this->max ?: 10) / 10);
|
||||
$prevPeriod = (int) ($this->step / $redrawFreq);
|
||||
$currPeriod = (int) ($step / $redrawFreq);
|
||||
$this->step = $step;
|
||||
$this->percent = $this->max ? (float) $this->step / $this->max : 0;
|
||||
$timeInterval = microtime(true) - $this->lastWriteTime;
|
||||
|
||||
// Draw regardless of other limits
|
||||
if ($this->max === $step) {
|
||||
$this->display();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Throttling
|
||||
if ($timeInterval < $this->minSecondsBetweenRedraws) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw each step period, but not too late
|
||||
if ($prevPeriod !== $currPeriod || $timeInterval >= $this->maxSecondsBetweenRedraws) {
|
||||
$this->display();
|
||||
}
|
||||
}
|
||||
|
||||
public function setMaxSteps(int $max): void
|
||||
{
|
||||
$this->format = null;
|
||||
$this->max = max(0, $max);
|
||||
$this->stepWidth = $this->max ? Helper::width((string) $this->max) : 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finishes the progress output.
|
||||
*/
|
||||
public function finish(): void
|
||||
{
|
||||
if (!$this->max) {
|
||||
$this->max = $this->step;
|
||||
}
|
||||
|
||||
if ($this->step === $this->max && !$this->overwrite) {
|
||||
// prevent double 100% output
|
||||
return;
|
||||
}
|
||||
|
||||
$this->setProgress($this->max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs the current progress string.
|
||||
*/
|
||||
public function display(): void
|
||||
{
|
||||
if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (null === $this->format) {
|
||||
$this->setRealFormat($this->internalFormat ?: $this->determineBestFormat());
|
||||
}
|
||||
|
||||
$this->overwrite($this->buildLine());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the progress bar from the current line.
|
||||
*
|
||||
* This is useful if you wish to write some output
|
||||
* while a progress bar is running.
|
||||
* Call display() to show the progress bar again.
|
||||
*/
|
||||
public function clear(): void
|
||||
{
|
||||
if (!$this->overwrite) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (null === $this->format) {
|
||||
$this->setRealFormat($this->internalFormat ?: $this->determineBestFormat());
|
||||
}
|
||||
|
||||
$this->overwrite('');
|
||||
}
|
||||
|
||||
private function setRealFormat(string $format): void
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrites a previous message to the output.
|
||||
*/
|
||||
private function overwrite(string $message): void
|
||||
{
|
||||
if ($this->previousMessage === $message) {
|
||||
return;
|
||||
}
|
||||
|
||||
$originalMessage = $message;
|
||||
|
||||
if ($this->overwrite) {
|
||||
if (null !== $this->previousMessage) {
|
||||
if ($this->output instanceof ConsoleSectionOutput) {
|
||||
$messageLines = explode("\n", $this->previousMessage);
|
||||
$lineCount = \count($messageLines);
|
||||
|
||||
$lastLineWithoutDecoration = Helper::removeDecoration($this->output->getFormatter(), end($messageLines) ?? '');
|
||||
|
||||
// When the last previous line is empty (without formatting) it is already cleared by the section output, so we don't need to clear it again
|
||||
if ('' === $lastLineWithoutDecoration) {
|
||||
--$lineCount;
|
||||
}
|
||||
|
||||
foreach ($messageLines as $messageLine) {
|
||||
$messageLineLength = Helper::width(Helper::removeDecoration($this->output->getFormatter(), $messageLine));
|
||||
if ($messageLineLength > $this->terminal->getWidth()) {
|
||||
$lineCount += floor($messageLineLength / $this->terminal->getWidth());
|
||||
}
|
||||
}
|
||||
|
||||
$this->output->clear($lineCount);
|
||||
} else {
|
||||
$lineCount = substr_count($this->previousMessage, "\n");
|
||||
for ($i = 0; $i < $lineCount; ++$i) {
|
||||
$this->cursor->moveToColumn(1);
|
||||
$this->cursor->clearLine();
|
||||
$this->cursor->moveUp();
|
||||
}
|
||||
|
||||
$this->cursor->moveToColumn(1);
|
||||
$this->cursor->clearLine();
|
||||
}
|
||||
}
|
||||
} elseif ($this->step > 0) {
|
||||
$message = \PHP_EOL.$message;
|
||||
}
|
||||
|
||||
$this->previousMessage = $originalMessage;
|
||||
$this->lastWriteTime = microtime(true);
|
||||
|
||||
$this->output->write($message);
|
||||
++$this->writeCount;
|
||||
}
|
||||
|
||||
private function determineBestFormat(): string
|
||||
{
|
||||
return match ($this->output->getVerbosity()) {
|
||||
// OutputInterface::VERBOSITY_QUIET: display is disabled anyway
|
||||
OutputInterface::VERBOSITY_VERBOSE => $this->max ? self::FORMAT_VERBOSE : self::FORMAT_VERBOSE_NOMAX,
|
||||
OutputInterface::VERBOSITY_VERY_VERBOSE => $this->max ? self::FORMAT_VERY_VERBOSE : self::FORMAT_VERY_VERBOSE_NOMAX,
|
||||
OutputInterface::VERBOSITY_DEBUG => $this->max ? self::FORMAT_DEBUG : self::FORMAT_DEBUG_NOMAX,
|
||||
default => $this->max ? self::FORMAT_NORMAL : self::FORMAT_NORMAL_NOMAX,
|
||||
};
|
||||
}
|
||||
|
||||
private static function initPlaceholderFormatters(): array
|
||||
{
|
||||
return [
|
||||
'bar' => function (self $bar, OutputInterface $output) {
|
||||
$completeBars = $bar->getBarOffset();
|
||||
$display = str_repeat($bar->getBarCharacter(), $completeBars);
|
||||
if ($completeBars < $bar->getBarWidth()) {
|
||||
$emptyBars = $bar->getBarWidth() - $completeBars - Helper::length(Helper::removeDecoration($output->getFormatter(), $bar->getProgressCharacter()));
|
||||
$display .= $bar->getProgressCharacter().str_repeat($bar->getEmptyBarCharacter(), $emptyBars);
|
||||
}
|
||||
|
||||
return $display;
|
||||
},
|
||||
'elapsed' => fn (self $bar) => Helper::formatTime(time() - $bar->getStartTime(), 2),
|
||||
'remaining' => function (self $bar) {
|
||||
if (!$bar->getMaxSteps()) {
|
||||
throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.');
|
||||
}
|
||||
|
||||
return Helper::formatTime($bar->getRemaining(), 2);
|
||||
},
|
||||
'estimated' => function (self $bar) {
|
||||
if (!$bar->getMaxSteps()) {
|
||||
throw new LogicException('Unable to display the estimated time if the maximum number of steps is not set.');
|
||||
}
|
||||
|
||||
return Helper::formatTime($bar->getEstimated(), 2);
|
||||
},
|
||||
'memory' => fn (self $bar) => Helper::formatMemory(memory_get_usage(true)),
|
||||
'current' => fn (self $bar) => str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', \STR_PAD_LEFT),
|
||||
'max' => fn (self $bar) => $bar->getMaxSteps(),
|
||||
'percent' => fn (self $bar) => floor($bar->getProgressPercent() * 100),
|
||||
];
|
||||
}
|
||||
|
||||
private static function initFormats(): array
|
||||
{
|
||||
return [
|
||||
self::FORMAT_NORMAL => ' %current%/%max% [%bar%] %percent:3s%%',
|
||||
self::FORMAT_NORMAL_NOMAX => ' %current% [%bar%]',
|
||||
|
||||
self::FORMAT_VERBOSE => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%',
|
||||
self::FORMAT_VERBOSE_NOMAX => ' %current% [%bar%] %elapsed:6s%',
|
||||
|
||||
self::FORMAT_VERY_VERBOSE => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%',
|
||||
self::FORMAT_VERY_VERBOSE_NOMAX => ' %current% [%bar%] %elapsed:6s%',
|
||||
|
||||
self::FORMAT_DEBUG => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%',
|
||||
self::FORMAT_DEBUG_NOMAX => ' %current% [%bar%] %elapsed:6s% %memory:6s%',
|
||||
];
|
||||
}
|
||||
|
||||
private function buildLine(): string
|
||||
{
|
||||
\assert(null !== $this->format);
|
||||
|
||||
$regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i";
|
||||
$callback = function ($matches) {
|
||||
if ($formatter = $this->getPlaceholderFormatter($matches[1])) {
|
||||
$text = $formatter($this, $this->output);
|
||||
} elseif (isset($this->messages[$matches[1]])) {
|
||||
$text = $this->messages[$matches[1]];
|
||||
} else {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
if (isset($matches[2])) {
|
||||
$text = \sprintf('%'.$matches[2], $text);
|
||||
}
|
||||
|
||||
return $text;
|
||||
};
|
||||
$line = preg_replace_callback($regex, $callback, $this->format);
|
||||
|
||||
// gets string length for each sub line with multiline format
|
||||
$linesLength = array_map(fn ($subLine) => Helper::width(Helper::removeDecoration($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);
|
||||
}
|
||||
}
|
||||
235
vendor/symfony/console/Helper/ProgressIndicator.php
vendored
Normal file
235
vendor/symfony/console/Helper/ProgressIndicator.php
vendored
Normal file
@ -0,0 +1,235 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@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 const FORMATS = [
|
||||
'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%)',
|
||||
];
|
||||
|
||||
private OutputInterface $output;
|
||||
private int $startTime;
|
||||
private ?string $format = null;
|
||||
private ?string $message = null;
|
||||
private array $indicatorValues;
|
||||
private int $indicatorCurrent;
|
||||
private int $indicatorChangeInterval;
|
||||
private float $indicatorUpdateTime;
|
||||
private bool $started = false;
|
||||
|
||||
/**
|
||||
* @var array<string, callable>
|
||||
*/
|
||||
private static array $formatters;
|
||||
|
||||
/**
|
||||
* @param int $indicatorChangeInterval Change interval in milliseconds
|
||||
* @param array|null $indicatorValues Animated indicator characters
|
||||
*/
|
||||
public function __construct(OutputInterface $output, ?string $format = null, int $indicatorChangeInterval = 100, ?array $indicatorValues = null)
|
||||
{
|
||||
$this->output = $output;
|
||||
|
||||
$format ??= $this->determineBestFormat();
|
||||
$indicatorValues ??= ['-', '\\', '|', '/'];
|
||||
$indicatorValues = array_values($indicatorValues);
|
||||
|
||||
if (2 > \count($indicatorValues)) {
|
||||
throw new InvalidArgumentException('Must have at least 2 indicator value characters.');
|
||||
}
|
||||
|
||||
$this->format = self::getFormatDefinition($format);
|
||||
$this->indicatorChangeInterval = $indicatorChangeInterval;
|
||||
$this->indicatorValues = $indicatorValues;
|
||||
$this->startTime = time();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current indicator message.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setMessage(?string $message)
|
||||
{
|
||||
$this->message = $message;
|
||||
|
||||
$this->display();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the indicator output.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function start(string $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.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function finish(string $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.
|
||||
*/
|
||||
public static function getFormatDefinition(string $name): ?string
|
||||
{
|
||||
return self::FORMATS[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a placeholder formatter for a given name.
|
||||
*
|
||||
* This method also allow you to override an existing placeholder.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function setPlaceholderFormatterDefinition(string $name, callable $callable)
|
||||
{
|
||||
self::$formatters ??= self::initPlaceholderFormatters();
|
||||
|
||||
self::$formatters[$name] = $callable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the placeholder formatter for a given name (including the delimiter char like %).
|
||||
*/
|
||||
public static function getPlaceholderFormatterDefinition(string $name): ?callable
|
||||
{
|
||||
self::$formatters ??= self::initPlaceholderFormatters();
|
||||
|
||||
return self::$formatters[$name] ?? null;
|
||||
}
|
||||
|
||||
private function display(): void
|
||||
{
|
||||
if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) {
|
||||
if ($formatter = self::getPlaceholderFormatterDefinition($matches[1])) {
|
||||
return $formatter($this);
|
||||
}
|
||||
|
||||
return $matches[0];
|
||||
}, $this->format ?? ''));
|
||||
}
|
||||
|
||||
private function determineBestFormat(): string
|
||||
{
|
||||
return match ($this->output->getVerbosity()) {
|
||||
// OutputInterface::VERBOSITY_QUIET: display is disabled anyway
|
||||
OutputInterface::VERBOSITY_VERBOSE => $this->output->isDecorated() ? 'verbose' : 'verbose_no_ansi',
|
||||
OutputInterface::VERBOSITY_VERY_VERBOSE,
|
||||
OutputInterface::VERBOSITY_DEBUG => $this->output->isDecorated() ? 'very_verbose' : 'very_verbose_no_ansi',
|
||||
default => $this->output->isDecorated() ? 'normal' : 'normal_no_ansi',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrites a previous message to the output.
|
||||
*/
|
||||
private function overwrite(string $message): void
|
||||
{
|
||||
if ($this->output->isDecorated()) {
|
||||
$this->output->write("\x0D\x1B[2K");
|
||||
$this->output->write($message);
|
||||
} else {
|
||||
$this->output->writeln($message);
|
||||
}
|
||||
}
|
||||
|
||||
private function getCurrentTimeInMilliseconds(): float
|
||||
{
|
||||
return round(microtime(true) * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, \Closure>
|
||||
*/
|
||||
private static function initPlaceholderFormatters(): array
|
||||
{
|
||||
return [
|
||||
'indicator' => fn (self $indicator) => $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)],
|
||||
'message' => fn (self $indicator) => $indicator->message,
|
||||
'elapsed' => fn (self $indicator) => Helper::formatTime(time() - $indicator->startTime, 2),
|
||||
'memory' => fn () => Helper::formatMemory(memory_get_usage(true)),
|
||||
];
|
||||
}
|
||||
}
|
||||
621
vendor/symfony/console/Helper/QuestionHelper.php
vendored
Normal file
621
vendor/symfony/console/Helper/QuestionHelper.php
vendored
Normal file
@ -0,0 +1,621 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@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\Cursor;
|
||||
use Symfony\Component\Console\Exception\MissingInputException;
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatter;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\StreamableInputInterface;
|
||||
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||
use Symfony\Component\Console\Output\ConsoleSectionOutput;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ChoiceQuestion;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
use Symfony\Component\Console\Terminal;
|
||||
|
||||
use function Symfony\Component\String\s;
|
||||
|
||||
/**
|
||||
* The QuestionHelper class provides helpers to interact with the user.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class QuestionHelper extends Helper
|
||||
{
|
||||
/**
|
||||
* @var resource|null
|
||||
*/
|
||||
private $inputStream;
|
||||
|
||||
private static bool $stty = true;
|
||||
private static bool $stdinIsInteractive;
|
||||
|
||||
/**
|
||||
* Asks a question to the user.
|
||||
*
|
||||
* @return mixed The user answer
|
||||
*
|
||||
* @throws RuntimeException If there is no data to read in the input stream
|
||||
*/
|
||||
public function ask(InputInterface $input, OutputInterface $output, Question $question): mixed
|
||||
{
|
||||
if ($output instanceof ConsoleOutputInterface) {
|
||||
$output = $output->getErrorOutput();
|
||||
}
|
||||
|
||||
if (!$input->isInteractive()) {
|
||||
return $this->getDefaultAnswer($question);
|
||||
}
|
||||
|
||||
if ($input instanceof StreamableInputInterface && $stream = $input->getStream()) {
|
||||
$this->inputStream = $stream;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!$question->getValidator()) {
|
||||
return $this->doAsk($output, $question);
|
||||
}
|
||||
|
||||
$interviewer = fn () => $this->doAsk($output, $question);
|
||||
|
||||
return $this->validateAttempts($interviewer, $output, $question);
|
||||
} catch (MissingInputException $exception) {
|
||||
$input->setInteractive(false);
|
||||
|
||||
if (null === $fallbackOutput = $this->getDefaultAnswer($question)) {
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
return $fallbackOutput;
|
||||
}
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return 'question';
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents usage of stty.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function disableStty()
|
||||
{
|
||||
self::$stty = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks the question to the user.
|
||||
*
|
||||
* @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
|
||||
*/
|
||||
private function doAsk(OutputInterface $output, Question $question): mixed
|
||||
{
|
||||
$this->writePrompt($output, $question);
|
||||
|
||||
$inputStream = $this->inputStream ?: \STDIN;
|
||||
$autocomplete = $question->getAutocompleterCallback();
|
||||
|
||||
if (null === $autocomplete || !self::$stty || !Terminal::hasSttyAvailable()) {
|
||||
$ret = false;
|
||||
if ($question->isHidden()) {
|
||||
try {
|
||||
$hiddenResponse = $this->getHiddenResponse($output, $inputStream, $question->isTrimmable());
|
||||
$ret = $question->isTrimmable() ? trim($hiddenResponse) : $hiddenResponse;
|
||||
} catch (RuntimeException $e) {
|
||||
if (!$question->isHiddenFallback()) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (false === $ret) {
|
||||
$isBlocked = stream_get_meta_data($inputStream)['blocked'] ?? true;
|
||||
|
||||
if (!$isBlocked) {
|
||||
stream_set_blocking($inputStream, true);
|
||||
}
|
||||
|
||||
$ret = $this->readInput($inputStream, $question);
|
||||
|
||||
if (!$isBlocked) {
|
||||
stream_set_blocking($inputStream, false);
|
||||
}
|
||||
|
||||
if (false === $ret) {
|
||||
throw new MissingInputException('Aborted.');
|
||||
}
|
||||
if ($question->isTrimmable()) {
|
||||
$ret = trim($ret);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$autocomplete = $this->autocomplete($output, $question, $inputStream, $autocomplete);
|
||||
$ret = $question->isTrimmable() ? trim($autocomplete) : $autocomplete;
|
||||
}
|
||||
|
||||
if ($output instanceof ConsoleSectionOutput) {
|
||||
$output->addContent(''); // add EOL to the question
|
||||
$output->addContent($ret);
|
||||
}
|
||||
|
||||
$ret = \strlen($ret) > 0 ? $ret : $question->getDefault();
|
||||
|
||||
if ($normalizer = $question->getNormalizer()) {
|
||||
return $normalizer($ret);
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
private function getDefaultAnswer(Question $question): mixed
|
||||
{
|
||||
$default = $question->getDefault();
|
||||
|
||||
if (null === $default) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
if ($validator = $question->getValidator()) {
|
||||
return \call_user_func($validator, $default);
|
||||
} elseif ($question instanceof ChoiceQuestion) {
|
||||
$choices = $question->getChoices();
|
||||
|
||||
if (!$question->isMultiselect()) {
|
||||
return $choices[$default] ?? $default;
|
||||
}
|
||||
|
||||
$default = explode(',', $default);
|
||||
foreach ($default as $k => $v) {
|
||||
$v = $question->isTrimmable() ? trim($v) : $v;
|
||||
$default[$k] = $choices[$v] ?? $v;
|
||||
}
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs the question prompt.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function writePrompt(OutputInterface $output, Question $question)
|
||||
{
|
||||
$message = $question->getQuestion();
|
||||
|
||||
if ($question instanceof ChoiceQuestion) {
|
||||
$output->writeln(array_merge([
|
||||
$question->getQuestion(),
|
||||
], $this->formatChoiceQuestionChoices($question, 'info')));
|
||||
|
||||
$message = $question->getPrompt();
|
||||
}
|
||||
|
||||
$output->write($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
protected function formatChoiceQuestionChoices(ChoiceQuestion $question, string $tag): array
|
||||
{
|
||||
$messages = [];
|
||||
|
||||
$maxWidth = max(array_map([__CLASS__, 'width'], array_keys($choices = $question->getChoices())));
|
||||
|
||||
foreach ($choices as $key => $value) {
|
||||
$padding = str_repeat(' ', $maxWidth - self::width($key));
|
||||
|
||||
$messages[] = \sprintf(" [<$tag>%s$padding</$tag>] %s", $key, $value);
|
||||
}
|
||||
|
||||
return $messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs an error message.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function writeError(OutputInterface $output, \Exception $error)
|
||||
{
|
||||
if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) {
|
||||
$message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error');
|
||||
} else {
|
||||
$message = '<error>'.$error->getMessage().'</error>';
|
||||
}
|
||||
|
||||
$output->writeln($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Autocompletes a question.
|
||||
*
|
||||
* @param resource $inputStream
|
||||
*/
|
||||
private function autocomplete(OutputInterface $output, Question $question, $inputStream, callable $autocomplete): string
|
||||
{
|
||||
$cursor = new Cursor($output, $inputStream);
|
||||
|
||||
$fullChoice = '';
|
||||
$ret = '';
|
||||
|
||||
$i = 0;
|
||||
$ofs = -1;
|
||||
$matches = $autocomplete($ret);
|
||||
$numMatches = \count($matches);
|
||||
$inputHelper = new TerminalInputHelper($inputStream);
|
||||
|
||||
// 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)) {
|
||||
$inputHelper->waitForInput();
|
||||
$c = fread($inputStream, 1);
|
||||
|
||||
// as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false.
|
||||
if (false === $c || ('' === $ret && '' === $c && null === $question->getDefault())) {
|
||||
// Restore the terminal so it behaves normally again
|
||||
$inputHelper->finish();
|
||||
throw new MissingInputException('Aborted.');
|
||||
} elseif ("\177" === $c) { // Backspace Character
|
||||
if (0 === $numMatches && 0 !== $i) {
|
||||
--$i;
|
||||
$cursor->moveLeft(s($fullChoice)->slice(-1)->width(false));
|
||||
|
||||
$fullChoice = self::substr($fullChoice, 0, $i);
|
||||
}
|
||||
|
||||
if (0 === $i) {
|
||||
$ofs = -1;
|
||||
$matches = $autocomplete($ret);
|
||||
$numMatches = \count($matches);
|
||||
} else {
|
||||
$numMatches = 0;
|
||||
}
|
||||
|
||||
// Pop the last character off the end of our string
|
||||
$ret = self::substr($ret, 0, $i);
|
||||
} elseif ("\033" === $c) {
|
||||
// Did we read an escape sequence?
|
||||
$c .= fread($inputStream, 2);
|
||||
|
||||
// A = Up Arrow. B = Down Arrow
|
||||
if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) {
|
||||
if ('A' === $c[2] && -1 === $ofs) {
|
||||
$ofs = 0;
|
||||
}
|
||||
|
||||
if (0 === $numMatches) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$ofs += ('A' === $c[2]) ? -1 : 1;
|
||||
$ofs = ($numMatches + $ofs) % $numMatches;
|
||||
}
|
||||
} elseif ('' === $c || \ord($c) < 32) {
|
||||
if ("\t" === $c || "\n" === $c) {
|
||||
if ($numMatches > 0 && -1 !== $ofs) {
|
||||
$ret = (string) $matches[$ofs];
|
||||
// Echo out remaining chars for current match
|
||||
$remainingCharacters = substr($ret, \strlen($this->mostRecentlyEnteredValue($fullChoice)));
|
||||
$output->write($remainingCharacters);
|
||||
$fullChoice .= $remainingCharacters;
|
||||
$i = (false === $encoding = mb_detect_encoding($fullChoice, null, true)) ? \strlen($fullChoice) : mb_strlen($fullChoice, $encoding);
|
||||
|
||||
$matches = array_filter(
|
||||
$autocomplete($ret),
|
||||
fn ($match) => '' === $ret || str_starts_with($match, $ret)
|
||||
);
|
||||
$numMatches = \count($matches);
|
||||
$ofs = -1;
|
||||
}
|
||||
|
||||
if ("\n" === $c) {
|
||||
$output->write($c);
|
||||
break;
|
||||
}
|
||||
|
||||
$numMatches = 0;
|
||||
}
|
||||
|
||||
continue;
|
||||
} else {
|
||||
if ("\x80" <= $c) {
|
||||
$c .= fread($inputStream, ["\xC0" => 1, "\xD0" => 1, "\xE0" => 2, "\xF0" => 3][$c & "\xF0"]);
|
||||
}
|
||||
|
||||
$output->write($c);
|
||||
$ret .= $c;
|
||||
$fullChoice .= $c;
|
||||
++$i;
|
||||
|
||||
$tempRet = $ret;
|
||||
|
||||
if ($question instanceof ChoiceQuestion && $question->isMultiselect()) {
|
||||
$tempRet = $this->mostRecentlyEnteredValue($fullChoice);
|
||||
}
|
||||
|
||||
$numMatches = 0;
|
||||
$ofs = 0;
|
||||
|
||||
foreach ($autocomplete($ret) as $value) {
|
||||
// If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle)
|
||||
if (str_starts_with($value, $tempRet)) {
|
||||
$matches[$numMatches++] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$cursor->clearLineAfter();
|
||||
|
||||
if ($numMatches > 0 && -1 !== $ofs) {
|
||||
$cursor->savePosition();
|
||||
// Write highlighted text, complete the partially entered response
|
||||
$charactersEntered = \strlen($this->mostRecentlyEnteredValue($fullChoice));
|
||||
$output->write('<hl>'.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $charactersEntered)).'</hl>');
|
||||
$cursor->restorePosition();
|
||||
}
|
||||
}
|
||||
|
||||
// Restore the terminal so it behaves normally again
|
||||
$inputHelper->finish();
|
||||
|
||||
return $fullChoice;
|
||||
}
|
||||
|
||||
private function mostRecentlyEnteredValue(string $entered): string
|
||||
{
|
||||
// Determine the most recent value that the user entered
|
||||
if (!str_contains($entered, ',')) {
|
||||
return $entered;
|
||||
}
|
||||
|
||||
$choices = explode(',', $entered);
|
||||
if ('' !== $lastChoice = trim($choices[\count($choices) - 1])) {
|
||||
return $lastChoice;
|
||||
}
|
||||
|
||||
return $entered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a hidden response from user.
|
||||
*
|
||||
* @param resource $inputStream The handler resource
|
||||
* @param bool $trimmable Is the answer trimmable
|
||||
*
|
||||
* @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
|
||||
*/
|
||||
private function getHiddenResponse(OutputInterface $output, $inputStream, bool $trimmable = true): string
|
||||
{
|
||||
if ('\\' === \DIRECTORY_SEPARATOR) {
|
||||
$exe = __DIR__.'/../Resources/bin/hiddeninput.exe';
|
||||
|
||||
// handle code running from a phar
|
||||
if (str_starts_with(__FILE__, 'phar:')) {
|
||||
$tmpExe = sys_get_temp_dir().'/hiddeninput.exe';
|
||||
copy($exe, $tmpExe);
|
||||
$exe = $tmpExe;
|
||||
}
|
||||
|
||||
$sExec = shell_exec('"'.$exe.'"');
|
||||
$value = $trimmable ? rtrim($sExec) : $sExec;
|
||||
$output->writeln('');
|
||||
|
||||
if (isset($tmpExe)) {
|
||||
unlink($tmpExe);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
$inputHelper = null;
|
||||
|
||||
if (self::$stty && Terminal::hasSttyAvailable()) {
|
||||
$inputHelper = new TerminalInputHelper($inputStream);
|
||||
shell_exec('stty -echo');
|
||||
} elseif ($this->isInteractiveInput($inputStream)) {
|
||||
throw new RuntimeException('Unable to hide the response.');
|
||||
}
|
||||
|
||||
$value = $this->doReadInput($inputStream, helper: $inputHelper);
|
||||
|
||||
if (4095 === \strlen($value)) {
|
||||
$errOutput = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output;
|
||||
$errOutput->warning('The value was possibly truncated by your shell or terminal emulator');
|
||||
}
|
||||
|
||||
// Restore the terminal so it behaves normally again
|
||||
$inputHelper?->finish();
|
||||
|
||||
if ($trimmable) {
|
||||
$value = trim($value);
|
||||
}
|
||||
$output->writeln('');
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates an attempt.
|
||||
*
|
||||
* @param callable $interviewer A callable that will ask for a question and return the result
|
||||
*
|
||||
* @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): mixed
|
||||
{
|
||||
$error = null;
|
||||
$attempts = $question->getMaxAttempts();
|
||||
|
||||
while (null === $attempts || $attempts--) {
|
||||
if (null !== $error) {
|
||||
$this->writeError($output, $error);
|
||||
}
|
||||
|
||||
try {
|
||||
return $question->getValidator()($interviewer());
|
||||
} catch (RuntimeException $e) {
|
||||
throw $e;
|
||||
} catch (\Exception $error) {
|
||||
}
|
||||
}
|
||||
|
||||
throw $error;
|
||||
}
|
||||
|
||||
private function isInteractiveInput($inputStream): bool
|
||||
{
|
||||
if ('php://stdin' !== (stream_get_meta_data($inputStream)['uri'] ?? null)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset(self::$stdinIsInteractive)) {
|
||||
return self::$stdinIsInteractive;
|
||||
}
|
||||
|
||||
return self::$stdinIsInteractive = @stream_isatty(fopen('php://stdin', 'r'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads one or more lines of input and returns what is read.
|
||||
*
|
||||
* @param resource $inputStream The handler resource
|
||||
* @param Question $question The question being asked
|
||||
*/
|
||||
private function readInput($inputStream, Question $question): string|false
|
||||
{
|
||||
if (!$question->isMultiline()) {
|
||||
$cp = $this->setIOCodepage();
|
||||
$ret = $this->doReadInput($inputStream);
|
||||
|
||||
return $this->resetIOCodepage($cp, $ret);
|
||||
}
|
||||
|
||||
$multiLineStreamReader = $this->cloneInputStream($inputStream);
|
||||
if (null === $multiLineStreamReader) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$cp = $this->setIOCodepage();
|
||||
$ret = $this->doReadInput($multiLineStreamReader, "\x4");
|
||||
|
||||
if (stream_get_meta_data($inputStream)['seekable']) {
|
||||
fseek($inputStream, ftell($multiLineStreamReader));
|
||||
}
|
||||
|
||||
return $this->resetIOCodepage($cp, $ret);
|
||||
}
|
||||
|
||||
private function setIOCodepage(): int
|
||||
{
|
||||
if (\function_exists('sapi_windows_cp_set')) {
|
||||
$cp = sapi_windows_cp_get();
|
||||
sapi_windows_cp_set(sapi_windows_cp_get('oem'));
|
||||
|
||||
return $cp;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets console I/O to the specified code page and converts the user input.
|
||||
*/
|
||||
private function resetIOCodepage(int $cp, string|false $input): string|false
|
||||
{
|
||||
if (0 !== $cp) {
|
||||
sapi_windows_cp_set($cp);
|
||||
|
||||
if (false !== $input && '' !== $input) {
|
||||
$input = sapi_windows_cp_conv(sapi_windows_cp_get('oem'), $cp, $input);
|
||||
}
|
||||
}
|
||||
|
||||
return $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones an input stream in order to act on one instance of the same
|
||||
* stream without affecting the other instance.
|
||||
*
|
||||
* @param resource $inputStream The handler resource
|
||||
*
|
||||
* @return resource|null The cloned resource, null in case it could not be cloned
|
||||
*/
|
||||
private function cloneInputStream($inputStream)
|
||||
{
|
||||
$streamMetaData = stream_get_meta_data($inputStream);
|
||||
$seekable = $streamMetaData['seekable'] ?? false;
|
||||
$mode = $streamMetaData['mode'] ?? 'rb';
|
||||
$uri = $streamMetaData['uri'] ?? null;
|
||||
|
||||
if (null === $uri) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$cloneStream = fopen($uri, $mode);
|
||||
|
||||
// For seekable and writable streams, add all the same data to the
|
||||
// cloned stream and then seek to the same offset.
|
||||
if (true === $seekable && !\in_array($mode, ['r', 'rb', 'rt'])) {
|
||||
$offset = ftell($inputStream);
|
||||
rewind($inputStream);
|
||||
stream_copy_to_stream($inputStream, $cloneStream);
|
||||
fseek($inputStream, $offset);
|
||||
fseek($cloneStream, $offset);
|
||||
}
|
||||
|
||||
return $cloneStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $inputStream
|
||||
*/
|
||||
private function doReadInput($inputStream, ?string $exitChar = null, ?TerminalInputHelper $helper = null): string
|
||||
{
|
||||
$ret = '';
|
||||
$helper ??= new TerminalInputHelper($inputStream, false);
|
||||
|
||||
while (!feof($inputStream)) {
|
||||
$helper->waitForInput();
|
||||
$char = fread($inputStream, 1);
|
||||
|
||||
// as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false.
|
||||
if (false === $char || ('' === $ret && '' === $char)) {
|
||||
throw new MissingInputException('Aborted.');
|
||||
}
|
||||
|
||||
if (\PHP_EOL === "{$ret}{$char}" || $exitChar === $char) {
|
||||
break;
|
||||
}
|
||||
|
||||
$ret .= $char;
|
||||
|
||||
if (null === $exitChar && "\n" === $char) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
109
vendor/symfony/console/Helper/SymfonyQuestionHelper.php
vendored
Normal file
109
vendor/symfony/console/Helper/SymfonyQuestionHelper.php
vendored
Normal 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\Helper;
|
||||
|
||||
use Symfony\Component\Console\Formatter\OutputFormatter;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ChoiceQuestion;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
/**
|
||||
* Symfony Style Guide compliant question helper.
|
||||
*
|
||||
* @author Kevin Bond <kevinbond@gmail.com>
|
||||
*/
|
||||
class SymfonyQuestionHelper extends QuestionHelper
|
||||
{
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
protected function writePrompt(OutputInterface $output, Question $question)
|
||||
{
|
||||
$text = OutputFormatter::escapeTrailingBackslash($question->getQuestion());
|
||||
$default = $question->getDefault();
|
||||
|
||||
if ($question->isMultiline()) {
|
||||
$text .= \sprintf(' (press %s to continue)', $this->getEofShortcut($output));
|
||||
}
|
||||
|
||||
switch (true) {
|
||||
case null === $default:
|
||||
$text = \sprintf(' <info>%s</info>:', $text);
|
||||
|
||||
break;
|
||||
|
||||
case $question instanceof ConfirmationQuestion:
|
||||
$text = \sprintf(' <info>%s (yes/no)</info> [<comment>%s</comment>]:', $text, $default ? 'yes' : 'no');
|
||||
|
||||
break;
|
||||
|
||||
case $question instanceof ChoiceQuestion && $question->isMultiselect():
|
||||
$choices = $question->getChoices();
|
||||
$default = explode(',', $default);
|
||||
|
||||
foreach ($default as $key => $value) {
|
||||
$default[$key] = $choices[trim($value)];
|
||||
}
|
||||
|
||||
$text = \sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape(implode(', ', $default)));
|
||||
|
||||
break;
|
||||
|
||||
case $question instanceof ChoiceQuestion:
|
||||
$choices = $question->getChoices();
|
||||
$text = \sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape($choices[$default] ?? $default));
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
$text = \sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape($default));
|
||||
}
|
||||
|
||||
$output->writeln($text);
|
||||
|
||||
$prompt = ' > ';
|
||||
|
||||
if ($question instanceof ChoiceQuestion) {
|
||||
$output->writeln($this->formatChoiceQuestionChoices($question, 'comment'));
|
||||
|
||||
$prompt = $question->getPrompt();
|
||||
}
|
||||
|
||||
$output->write($prompt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
protected function writeError(OutputInterface $output, \Exception $error)
|
||||
{
|
||||
if ($output instanceof SymfonyStyle) {
|
||||
$output->newLine();
|
||||
$output->error($error->getMessage());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
parent::writeError($output, $error);
|
||||
}
|
||||
|
||||
private function getEofShortcut(OutputInterface $output): string
|
||||
{
|
||||
if ('\\' === \DIRECTORY_SEPARATOR && !$output->isDecorated()) {
|
||||
return '<comment>Ctrl+Z</comment> then <comment>Enter</comment>';
|
||||
}
|
||||
|
||||
return '<comment>Ctrl+D</comment>';
|
||||
}
|
||||
}
|
||||
967
vendor/symfony/console/Helper/Table.php
vendored
Normal file
967
vendor/symfony/console/Helper/Table.php
vendored
Normal file
@ -0,0 +1,967 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Helper;
|
||||
|
||||
use Symfony\Component\Console\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatter;
|
||||
use Symfony\Component\Console\Formatter\WrappableOutputFormatterInterface;
|
||||
use Symfony\Component\Console\Output\ConsoleSectionOutput;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Provides helpers to display a table.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Саша Стаменковић <umpirsky@gmail.com>
|
||||
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
|
||||
* @author Max Grigorian <maxakawizard@gmail.com>
|
||||
* @author Dany Maillard <danymaillard93b@gmail.com>
|
||||
*/
|
||||
class Table
|
||||
{
|
||||
private const SEPARATOR_TOP = 0;
|
||||
private const SEPARATOR_TOP_BOTTOM = 1;
|
||||
private const SEPARATOR_MID = 2;
|
||||
private const SEPARATOR_BOTTOM = 3;
|
||||
private const BORDER_OUTSIDE = 0;
|
||||
private const BORDER_INSIDE = 1;
|
||||
private const DISPLAY_ORIENTATION_DEFAULT = 'default';
|
||||
private const DISPLAY_ORIENTATION_HORIZONTAL = 'horizontal';
|
||||
private const DISPLAY_ORIENTATION_VERTICAL = 'vertical';
|
||||
|
||||
private ?string $headerTitle = null;
|
||||
private ?string $footerTitle = null;
|
||||
private array $headers = [];
|
||||
private array $rows = [];
|
||||
private array $effectiveColumnWidths = [];
|
||||
private int $numberOfColumns;
|
||||
private OutputInterface $output;
|
||||
private TableStyle $style;
|
||||
private array $columnStyles = [];
|
||||
private array $columnWidths = [];
|
||||
private array $columnMaxWidths = [];
|
||||
private bool $rendered = false;
|
||||
private string $displayOrientation = self::DISPLAY_ORIENTATION_DEFAULT;
|
||||
|
||||
private static array $styles;
|
||||
|
||||
public function __construct(OutputInterface $output)
|
||||
{
|
||||
$this->output = $output;
|
||||
|
||||
self::$styles ??= self::initStyles();
|
||||
|
||||
$this->setStyle('default');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a style definition.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function setStyleDefinition(string $name, TableStyle $style)
|
||||
{
|
||||
self::$styles ??= self::initStyles();
|
||||
|
||||
self::$styles[$name] = $style;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a style definition by name.
|
||||
*/
|
||||
public static function getStyleDefinition(string $name): TableStyle
|
||||
{
|
||||
self::$styles ??= self::initStyles();
|
||||
|
||||
return self::$styles[$name] ?? throw new InvalidArgumentException(\sprintf('Style "%s" is not defined.', $name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets table style.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setStyle(TableStyle|string $name): static
|
||||
{
|
||||
$this->style = $this->resolveStyle($name);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current table style.
|
||||
*/
|
||||
public function getStyle(): TableStyle
|
||||
{
|
||||
return $this->style;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets table column style.
|
||||
*
|
||||
* @param TableStyle|string $name The style name or a TableStyle instance
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setColumnStyle(int $columnIndex, TableStyle|string $name): static
|
||||
{
|
||||
$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.
|
||||
*/
|
||||
public function getColumnStyle(int $columnIndex): TableStyle
|
||||
{
|
||||
return $this->columnStyles[$columnIndex] ?? $this->getStyle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum width of a column.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setColumnWidth(int $columnIndex, int $width): static
|
||||
{
|
||||
$this->columnWidths[$columnIndex] = $width;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum width of all columns.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setColumnWidths(array $widths): static
|
||||
{
|
||||
$this->columnWidths = [];
|
||||
foreach ($widths as $index => $width) {
|
||||
$this->setColumnWidth($index, $width);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum width of a column.
|
||||
*
|
||||
* Any cell within this column which contents exceeds the specified width will be wrapped into multiple lines, while
|
||||
* formatted strings are preserved.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setColumnMaxWidth(int $columnIndex, int $width): static
|
||||
{
|
||||
if (!$this->output->getFormatter() instanceof WrappableOutputFormatterInterface) {
|
||||
throw new \LogicException(\sprintf('Setting a maximum column width is only supported when using a "%s" formatter, got "%s".', WrappableOutputFormatterInterface::class, get_debug_type($this->output->getFormatter())));
|
||||
}
|
||||
|
||||
$this->columnMaxWidths[$columnIndex] = $width;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setHeaders(array $headers): static
|
||||
{
|
||||
$headers = array_values($headers);
|
||||
if ($headers && !\is_array($headers[0])) {
|
||||
$headers = [$headers];
|
||||
}
|
||||
|
||||
$this->headers = $headers;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setRows(array $rows)
|
||||
{
|
||||
$this->rows = [];
|
||||
|
||||
return $this->addRows($rows);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function addRows(array $rows): static
|
||||
{
|
||||
foreach ($rows as $row) {
|
||||
$this->addRow($row);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function addRow(TableSeparator|array $row): static
|
||||
{
|
||||
if ($row instanceof TableSeparator) {
|
||||
$this->rows[] = $row;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->rows[] = array_values($row);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a row to the table, and re-renders the table.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function appendRow(TableSeparator|array $row): static
|
||||
{
|
||||
if (!$this->output instanceof ConsoleSectionOutput) {
|
||||
throw new RuntimeException(\sprintf('Output should be an instance of "%s" when calling "%s".', ConsoleSectionOutput::class, __METHOD__));
|
||||
}
|
||||
|
||||
if ($this->rendered) {
|
||||
$this->output->clear($this->calculateRowCount());
|
||||
}
|
||||
|
||||
$this->addRow($row);
|
||||
$this->render();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setRow(int|string $column, array $row): static
|
||||
{
|
||||
$this->rows[$column] = $row;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setHeaderTitle(?string $title): static
|
||||
{
|
||||
$this->headerTitle = $title;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setFooterTitle(?string $title): static
|
||||
{
|
||||
$this->footerTitle = $title;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setHorizontal(bool $horizontal = true): static
|
||||
{
|
||||
$this->displayOrientation = $horizontal ? self::DISPLAY_ORIENTATION_HORIZONTAL : self::DISPLAY_ORIENTATION_DEFAULT;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setVertical(bool $vertical = true): static
|
||||
{
|
||||
$this->displayOrientation = $vertical ? self::DISPLAY_ORIENTATION_VERTICAL : self::DISPLAY_ORIENTATION_DEFAULT;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders table to output.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* +---------------+-----------------------+------------------+
|
||||
* | ISBN | Title | Author |
|
||||
* +---------------+-----------------------+------------------+
|
||||
* | 99921-58-10-7 | Divine Comedy | Dante Alighieri |
|
||||
* | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
|
||||
* | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
|
||||
* +---------------+-----------------------+------------------+
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$divider = new TableSeparator();
|
||||
$isCellWithColspan = static fn ($cell) => $cell instanceof TableCell && $cell->getColspan() >= 2;
|
||||
|
||||
$horizontal = self::DISPLAY_ORIENTATION_HORIZONTAL === $this->displayOrientation;
|
||||
$vertical = self::DISPLAY_ORIENTATION_VERTICAL === $this->displayOrientation;
|
||||
|
||||
$rows = [];
|
||||
if ($horizontal) {
|
||||
foreach ($this->headers[0] ?? [] as $i => $header) {
|
||||
$rows[$i] = [$header];
|
||||
foreach ($this->rows as $row) {
|
||||
if ($row instanceof TableSeparator) {
|
||||
continue;
|
||||
}
|
||||
if (isset($row[$i])) {
|
||||
$rows[$i][] = $row[$i];
|
||||
} elseif ($isCellWithColspan($rows[$i][0])) {
|
||||
// Noop, there is a "title"
|
||||
} else {
|
||||
$rows[$i][] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif ($vertical) {
|
||||
$formatter = $this->output->getFormatter();
|
||||
$maxHeaderLength = array_reduce($this->headers[0] ?? [], static fn ($max, $header) => max($max, Helper::width(Helper::removeDecoration($formatter, $header))), 0);
|
||||
|
||||
foreach ($this->rows as $row) {
|
||||
if ($row instanceof TableSeparator) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($rows) {
|
||||
$rows[] = [$divider];
|
||||
}
|
||||
|
||||
$containsColspan = false;
|
||||
foreach ($row as $cell) {
|
||||
if ($containsColspan = $isCellWithColspan($cell)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$headers = $this->headers[0] ?? [];
|
||||
$maxRows = max(\count($headers), \count($row));
|
||||
for ($i = 0; $i < $maxRows; ++$i) {
|
||||
$cell = (string) ($row[$i] ?? '');
|
||||
|
||||
$eol = str_contains($cell, "\r\n") ? "\r\n" : "\n";
|
||||
$parts = explode($eol, $cell);
|
||||
foreach ($parts as $idx => $part) {
|
||||
if ($headers && !$containsColspan) {
|
||||
if (0 === $idx) {
|
||||
$rows[] = [\sprintf(
|
||||
'<comment>%s%s</>: %s',
|
||||
str_repeat(' ', $maxHeaderLength - Helper::width(Helper::removeDecoration($formatter, $headers[$i] ?? ''))),
|
||||
$headers[$i] ?? '',
|
||||
$part
|
||||
)];
|
||||
} else {
|
||||
$rows[] = [\sprintf(
|
||||
'%s %s',
|
||||
str_pad('', $maxHeaderLength, ' ', \STR_PAD_LEFT),
|
||||
$part
|
||||
)];
|
||||
}
|
||||
} elseif ('' !== $cell) {
|
||||
$rows[] = [$part];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$rows = array_merge($this->headers, [$divider], $this->rows);
|
||||
}
|
||||
|
||||
$this->calculateNumberOfColumns($rows);
|
||||
|
||||
$rowGroups = $this->buildTableRows($rows);
|
||||
$this->calculateColumnsWidth($rowGroups);
|
||||
|
||||
$isHeader = !$horizontal;
|
||||
$isFirstRow = $horizontal;
|
||||
$hasTitle = (bool) $this->headerTitle;
|
||||
|
||||
foreach ($rowGroups as $rowGroup) {
|
||||
$isHeaderSeparatorRendered = false;
|
||||
|
||||
foreach ($rowGroup as $row) {
|
||||
if ($divider === $row) {
|
||||
$isHeader = false;
|
||||
$isFirstRow = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($row instanceof TableSeparator) {
|
||||
$this->renderRowSeparator();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$row) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($isHeader && !$isHeaderSeparatorRendered) {
|
||||
$this->renderRowSeparator(
|
||||
self::SEPARATOR_TOP,
|
||||
$hasTitle ? $this->headerTitle : null,
|
||||
$hasTitle ? $this->style->getHeaderTitleFormat() : null
|
||||
);
|
||||
$hasTitle = false;
|
||||
$isHeaderSeparatorRendered = true;
|
||||
}
|
||||
|
||||
if ($isFirstRow) {
|
||||
$this->renderRowSeparator(
|
||||
$horizontal ? self::SEPARATOR_TOP : self::SEPARATOR_TOP_BOTTOM,
|
||||
$hasTitle ? $this->headerTitle : null,
|
||||
$hasTitle ? $this->style->getHeaderTitleFormat() : null
|
||||
);
|
||||
$isFirstRow = false;
|
||||
$hasTitle = false;
|
||||
}
|
||||
|
||||
if ($vertical) {
|
||||
$isHeader = false;
|
||||
$isFirstRow = false;
|
||||
}
|
||||
|
||||
if ($horizontal) {
|
||||
$this->renderRow($row, $this->style->getCellRowFormat(), $this->style->getCellHeaderFormat());
|
||||
} else {
|
||||
$this->renderRow($row, $isHeader ? $this->style->getCellHeaderFormat() : $this->style->getCellRowFormat());
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->renderRowSeparator(self::SEPARATOR_BOTTOM, $this->footerTitle, $this->style->getFooterTitleFormat());
|
||||
|
||||
$this->cleanup();
|
||||
$this->rendered = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders horizontal header separator.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* +-----+-----------+-------+
|
||||
*/
|
||||
private function renderRowSeparator(int $type = self::SEPARATOR_MID, ?string $title = null, ?string $titleFormat = null): void
|
||||
{
|
||||
if (!$count = $this->numberOfColumns) {
|
||||
return;
|
||||
}
|
||||
|
||||
$borders = $this->style->getBorderChars();
|
||||
if (!$borders[0] && !$borders[2] && !$this->style->getCrossingChar()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$crossings = $this->style->getCrossingChars();
|
||||
if (self::SEPARATOR_MID === $type) {
|
||||
[$horizontal, $leftChar, $midChar, $rightChar] = [$borders[2], $crossings[8], $crossings[0], $crossings[4]];
|
||||
} elseif (self::SEPARATOR_TOP === $type) {
|
||||
[$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[1], $crossings[2], $crossings[3]];
|
||||
} elseif (self::SEPARATOR_TOP_BOTTOM === $type) {
|
||||
[$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[9], $crossings[10], $crossings[11]];
|
||||
} else {
|
||||
[$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[7], $crossings[6], $crossings[5]];
|
||||
}
|
||||
|
||||
$markup = $leftChar;
|
||||
for ($column = 0; $column < $count; ++$column) {
|
||||
$markup .= str_repeat($horizontal, $this->effectiveColumnWidths[$column]);
|
||||
$markup .= $column === $count - 1 ? $rightChar : $midChar;
|
||||
}
|
||||
|
||||
if (null !== $title) {
|
||||
$titleLength = Helper::width(Helper::removeDecoration($formatter = $this->output->getFormatter(), $formattedTitle = \sprintf($titleFormat, $title)));
|
||||
$markupLength = Helper::width($markup);
|
||||
if ($titleLength > $limit = $markupLength - 4) {
|
||||
$titleLength = $limit;
|
||||
$formatLength = Helper::width(Helper::removeDecoration($formatter, \sprintf($titleFormat, '')));
|
||||
$formattedTitle = \sprintf($titleFormat, Helper::substr($title, 0, $limit - $formatLength - 3).'...');
|
||||
}
|
||||
|
||||
$titleStart = intdiv($markupLength - $titleLength, 2);
|
||||
if (false === mb_detect_encoding($markup, null, true)) {
|
||||
$markup = substr_replace($markup, $formattedTitle, $titleStart, $titleLength);
|
||||
} else {
|
||||
$markup = mb_substr($markup, 0, $titleStart).$formattedTitle.mb_substr($markup, $titleStart + $titleLength);
|
||||
}
|
||||
}
|
||||
|
||||
$this->output->writeln(\sprintf($this->style->getBorderFormat(), $markup));
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders vertical column separator.
|
||||
*/
|
||||
private function renderColumnSeparator(int $type = self::BORDER_OUTSIDE): string
|
||||
{
|
||||
$borders = $this->style->getBorderChars();
|
||||
|
||||
return \sprintf($this->style->getBorderFormat(), self::BORDER_OUTSIDE === $type ? $borders[1] : $borders[3]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders table row.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
|
||||
*/
|
||||
private function renderRow(array $row, string $cellFormat, ?string $firstCellFormat = null): void
|
||||
{
|
||||
$rowContent = $this->renderColumnSeparator(self::BORDER_OUTSIDE);
|
||||
$columns = $this->getRowColumns($row);
|
||||
$last = \count($columns) - 1;
|
||||
foreach ($columns as $i => $column) {
|
||||
if ($firstCellFormat && 0 === $i) {
|
||||
$rowContent .= $this->renderCell($row, $column, $firstCellFormat);
|
||||
} else {
|
||||
$rowContent .= $this->renderCell($row, $column, $cellFormat);
|
||||
}
|
||||
$rowContent .= $this->renderColumnSeparator($last === $i ? self::BORDER_OUTSIDE : self::BORDER_INSIDE);
|
||||
}
|
||||
$this->output->writeln($rowContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders table cell with padding.
|
||||
*/
|
||||
private function renderCell(array $row, int $column, string $cellFormat): string
|
||||
{
|
||||
$cell = $row[$column] ?? '';
|
||||
$width = $this->effectiveColumnWidths[$column];
|
||||
if ($cell instanceof TableCell && $cell->getColspan() > 1) {
|
||||
// add the width of the following columns(numbers of colspan).
|
||||
foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) {
|
||||
$width += $this->getColumnSeparatorWidth() + $this->effectiveColumnWidths[$nextColumn];
|
||||
}
|
||||
}
|
||||
|
||||
// str_pad won't work properly with multi-byte strings, we need to fix the padding
|
||||
$width += \strlen($cell) - Helper::width($cell) - substr_count($cell, "\0");
|
||||
$style = $this->getColumnStyle($column);
|
||||
|
||||
if ($cell instanceof TableSeparator) {
|
||||
return \sprintf($style->getBorderFormat(), str_repeat($style->getBorderChars()[2], $width));
|
||||
}
|
||||
|
||||
$width += Helper::length($cell) - Helper::length(Helper::removeDecoration($this->output->getFormatter(), $cell));
|
||||
$content = \sprintf($style->getCellRowContentFormat(), $cell);
|
||||
|
||||
$padType = $style->getPadType();
|
||||
if ($cell instanceof TableCell && $cell->getStyle() instanceof TableCellStyle) {
|
||||
$isNotStyledByTag = !preg_match('/^<(\w+|(\w+=[\w,]+;?)*)>.+<\/(\w+|(\w+=\w+;?)*)?>$/', $cell);
|
||||
if ($isNotStyledByTag) {
|
||||
$cellFormat = $cell->getStyle()->getCellFormat();
|
||||
if (!\is_string($cellFormat)) {
|
||||
$tag = http_build_query($cell->getStyle()->getTagOptions(), '', ';');
|
||||
$cellFormat = '<'.$tag.'>%s</>';
|
||||
}
|
||||
|
||||
if (str_contains($content, '</>')) {
|
||||
$content = str_replace('</>', '', $content);
|
||||
$width -= 3;
|
||||
}
|
||||
if (str_contains($content, '<fg=default;bg=default>')) {
|
||||
$content = str_replace('<fg=default;bg=default>', '', $content);
|
||||
$width -= \strlen('<fg=default;bg=default>');
|
||||
}
|
||||
}
|
||||
|
||||
$padType = $cell->getStyle()->getPadByAlign();
|
||||
}
|
||||
|
||||
return \sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $padType));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate number of columns for this table.
|
||||
*/
|
||||
private function calculateNumberOfColumns(array $rows): void
|
||||
{
|
||||
$columns = [0];
|
||||
foreach ($rows as $row) {
|
||||
if ($row instanceof TableSeparator) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$columns[] = $this->getNumberOfColumns($row);
|
||||
}
|
||||
|
||||
$this->numberOfColumns = max($columns);
|
||||
}
|
||||
|
||||
private function buildTableRows(array $rows): TableRows
|
||||
{
|
||||
/** @var WrappableOutputFormatterInterface $formatter */
|
||||
$formatter = $this->output->getFormatter();
|
||||
$unmergedRows = [];
|
||||
for ($rowKey = 0; $rowKey < \count($rows); ++$rowKey) {
|
||||
$rows = $this->fillNextRows($rows, $rowKey);
|
||||
|
||||
// Remove any new line breaks and replace it with a new line
|
||||
foreach ($rows[$rowKey] as $column => $cell) {
|
||||
$colspan = $cell instanceof TableCell ? $cell->getColspan() : 1;
|
||||
|
||||
$minWrappedWidth = 0;
|
||||
$widthApplied = [];
|
||||
$lengthColumnBorder = $this->getColumnSeparatorWidth() + Helper::width($this->style->getCellRowContentFormat()) - 2;
|
||||
for ($i = $column; $i < ($column + $colspan); ++$i) {
|
||||
if (isset($this->columnMaxWidths[$i])) {
|
||||
$minWrappedWidth += $this->columnMaxWidths[$i];
|
||||
$widthApplied[] = ['type' => 'max', 'column' => $i];
|
||||
} elseif (($this->columnWidths[$i] ?? 0) > 0 && $colspan > 1) {
|
||||
$minWrappedWidth += $this->columnWidths[$i];
|
||||
$widthApplied[] = ['type' => 'min', 'column' => $i];
|
||||
}
|
||||
}
|
||||
if (1 === \count($widthApplied)) {
|
||||
if ($colspan > 1) {
|
||||
$minWrappedWidth *= $colspan; // previous logic
|
||||
}
|
||||
} elseif (\count($widthApplied) > 1) {
|
||||
$minWrappedWidth += (\count($widthApplied) - 1) * $lengthColumnBorder;
|
||||
}
|
||||
|
||||
$cellWidth = Helper::width(Helper::removeDecoration($formatter, $cell));
|
||||
if ($minWrappedWidth && $cellWidth > $minWrappedWidth) {
|
||||
$cell = $formatter->formatAndWrap($cell, $minWrappedWidth);
|
||||
}
|
||||
// update minimal columnWidths for spanned columns
|
||||
if ($colspan > 1 && $minWrappedWidth > 0) {
|
||||
$columnsMinWidthProcessed = [];
|
||||
$cellWidth = min($cellWidth, $minWrappedWidth);
|
||||
foreach ($widthApplied as $item) {
|
||||
if ('max' === $item['type'] && $cellWidth >= $this->columnMaxWidths[$item['column']]) {
|
||||
$minWidthColumn = $this->columnMaxWidths[$item['column']];
|
||||
$this->columnWidths[$item['column']] = $minWidthColumn;
|
||||
$columnsMinWidthProcessed[$item['column']] = true;
|
||||
$cellWidth -= $minWidthColumn + $lengthColumnBorder;
|
||||
}
|
||||
}
|
||||
for ($i = $column; $i < ($column + $colspan); ++$i) {
|
||||
if (isset($columnsMinWidthProcessed[$i])) {
|
||||
continue;
|
||||
}
|
||||
$this->columnWidths[$i] = $cellWidth + $lengthColumnBorder;
|
||||
}
|
||||
}
|
||||
if (!str_contains($cell ?? '', "\n")) {
|
||||
continue;
|
||||
}
|
||||
$eol = str_contains($cell ?? '', "\r\n") ? "\r\n" : "\n";
|
||||
$escaped = implode($eol, array_map(OutputFormatter::escapeTrailingBackslash(...), explode($eol, $cell)));
|
||||
$cell = $cell instanceof TableCell ? new TableCell($escaped, ['colspan' => $cell->getColspan()]) : $escaped;
|
||||
$lines = explode($eol, str_replace($eol, '<fg=default;bg=default></>'.$eol, $cell));
|
||||
foreach ($lines as $lineKey => $line) {
|
||||
if ($colspan > 1) {
|
||||
$line = new TableCell($line, ['colspan' => $colspan]);
|
||||
}
|
||||
if (0 === $lineKey) {
|
||||
$rows[$rowKey][$column] = $line;
|
||||
} else {
|
||||
if (!\array_key_exists($rowKey, $unmergedRows) || !\array_key_exists($lineKey, $unmergedRows[$rowKey])) {
|
||||
$unmergedRows[$rowKey][$lineKey] = $this->copyRow($rows, $rowKey);
|
||||
}
|
||||
$unmergedRows[$rowKey][$lineKey][$column] = $line;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new TableRows(function () use ($rows, $unmergedRows): \Traversable {
|
||||
foreach ($rows as $rowKey => $row) {
|
||||
$rowGroup = [$row instanceof TableSeparator ? $row : $this->fillCells($row)];
|
||||
|
||||
if (isset($unmergedRows[$rowKey])) {
|
||||
foreach ($unmergedRows[$rowKey] as $row) {
|
||||
$rowGroup[] = $row instanceof TableSeparator ? $row : $this->fillCells($row);
|
||||
}
|
||||
}
|
||||
yield $rowGroup;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function calculateRowCount(): int
|
||||
{
|
||||
$numberOfRows = \count(iterator_to_array($this->buildTableRows(array_merge($this->headers, [new TableSeparator()], $this->rows))));
|
||||
|
||||
if ($this->headers) {
|
||||
++$numberOfRows; // Add row for header separator
|
||||
}
|
||||
|
||||
if ($this->rows) {
|
||||
++$numberOfRows; // Add row for footer separator
|
||||
}
|
||||
|
||||
return $numberOfRows;
|
||||
}
|
||||
|
||||
/**
|
||||
* fill rows that contains rowspan > 1.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
private function fillNextRows(array $rows, int $line): array
|
||||
{
|
||||
$unmergedRows = [];
|
||||
foreach ($rows[$line] as $column => $cell) {
|
||||
if (null !== $cell && !$cell instanceof TableCell && !\is_scalar($cell) && !$cell instanceof \Stringable) {
|
||||
throw new InvalidArgumentException(\sprintf('A cell must be a TableCell, a scalar or an object implementing "__toString()", "%s" given.', get_debug_type($cell)));
|
||||
}
|
||||
if ($cell instanceof TableCell && $cell->getRowspan() > 1) {
|
||||
$nbLines = $cell->getRowspan() - 1;
|
||||
$lines = [$cell];
|
||||
if (str_contains($cell, "\n")) {
|
||||
$eol = str_contains($cell, "\r\n") ? "\r\n" : "\n";
|
||||
$lines = explode($eol, str_replace($eol, '<fg=default;bg=default>'.$eol.'</>', $cell));
|
||||
$nbLines = \count($lines) > $nbLines ? substr_count($cell, $eol) : $nbLines;
|
||||
|
||||
$rows[$line][$column] = new TableCell($lines[0], ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]);
|
||||
unset($lines[0]);
|
||||
}
|
||||
|
||||
// create a two dimensional array (rowspan x colspan)
|
||||
$unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, []), $unmergedRows);
|
||||
foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
|
||||
$value = $lines[$unmergedRowKey - $line] ?? '';
|
||||
$unmergedRows[$unmergedRowKey][$column] = new TableCell($value, ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]);
|
||||
if ($nbLines === $unmergedRowKey - $line) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
|
||||
// we need to know if $unmergedRow will be merged or inserted into $rows
|
||||
if (isset($rows[$unmergedRowKey]) && \is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) {
|
||||
foreach ($unmergedRow as $cellKey => $cell) {
|
||||
// insert cell into row at cellKey position
|
||||
array_splice($rows[$unmergedRowKey], $cellKey, 0, [$cell]);
|
||||
}
|
||||
} else {
|
||||
$row = $this->copyRow($rows, $unmergedRowKey - 1);
|
||||
foreach ($unmergedRow as $column => $cell) {
|
||||
if (!empty($cell)) {
|
||||
$row[$column] = $unmergedRow[$column];
|
||||
}
|
||||
}
|
||||
array_splice($rows, $unmergedRowKey, 0, [$row]);
|
||||
}
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* fill cells for a row that contains colspan > 1.
|
||||
*/
|
||||
private function fillCells(iterable $row): iterable
|
||||
{
|
||||
$newRow = [];
|
||||
|
||||
foreach ($row as $column => $cell) {
|
||||
$newRow[] = $cell;
|
||||
if ($cell instanceof TableCell && $cell->getColspan() > 1) {
|
||||
foreach (range($column + 1, $column + $cell->getColspan() - 1) as $position) {
|
||||
// insert empty value at column position
|
||||
$newRow[] = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $newRow ?: $row;
|
||||
}
|
||||
|
||||
private function copyRow(array $rows, int $line): array
|
||||
{
|
||||
$row = $rows[$line];
|
||||
foreach ($row as $cellKey => $cellValue) {
|
||||
$row[$cellKey] = '';
|
||||
if ($cellValue instanceof TableCell) {
|
||||
$row[$cellKey] = new TableCell('', ['colspan' => $cellValue->getColspan()]);
|
||||
}
|
||||
}
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets number of columns by row.
|
||||
*/
|
||||
private function getNumberOfColumns(array $row): int
|
||||
{
|
||||
$columns = \count($row);
|
||||
foreach ($row as $column) {
|
||||
$columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0;
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets list of columns for the given row.
|
||||
*/
|
||||
private function getRowColumns(array $row): array
|
||||
{
|
||||
$columns = range(0, $this->numberOfColumns - 1);
|
||||
foreach ($row as $cellKey => $cell) {
|
||||
if ($cell instanceof TableCell && $cell->getColspan() > 1) {
|
||||
// exclude grouped columns.
|
||||
$columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1));
|
||||
}
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates columns widths.
|
||||
*/
|
||||
private function calculateColumnsWidth(iterable $groups): void
|
||||
{
|
||||
for ($column = 0; $column < $this->numberOfColumns; ++$column) {
|
||||
$lengths = [];
|
||||
foreach ($groups as $group) {
|
||||
foreach ($group as $row) {
|
||||
if ($row instanceof TableSeparator) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($row as $i => $cell) {
|
||||
if ($cell instanceof TableCell) {
|
||||
$textContent = Helper::removeDecoration($this->output->getFormatter(), $cell);
|
||||
$textLength = Helper::width($textContent);
|
||||
if ($textLength > 0) {
|
||||
$contentColumns = mb_str_split($textContent, ceil($textLength / $cell->getColspan()));
|
||||
foreach ($contentColumns as $position => $content) {
|
||||
$row[$i + $position] = $content;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$lengths[] = $this->getCellWidth($row, $column);
|
||||
}
|
||||
}
|
||||
|
||||
$this->effectiveColumnWidths[$column] = max($lengths) + Helper::width($this->style->getCellRowContentFormat()) - 2;
|
||||
}
|
||||
}
|
||||
|
||||
private function getColumnSeparatorWidth(): int
|
||||
{
|
||||
return Helper::width(\sprintf($this->style->getBorderFormat(), $this->style->getBorderChars()[3]));
|
||||
}
|
||||
|
||||
private function getCellWidth(array $row, int $column): int
|
||||
{
|
||||
$cellWidth = 0;
|
||||
|
||||
if (isset($row[$column])) {
|
||||
$cell = $row[$column];
|
||||
$cellWidth = Helper::width(Helper::removeDecoration($this->output->getFormatter(), $cell));
|
||||
}
|
||||
|
||||
$columnWidth = $this->columnWidths[$column] ?? 0;
|
||||
$cellWidth = max($cellWidth, $columnWidth);
|
||||
|
||||
return isset($this->columnMaxWidths[$column]) ? min($this->columnMaxWidths[$column], $cellWidth) : $cellWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after rendering to cleanup cache data.
|
||||
*/
|
||||
private function cleanup(): void
|
||||
{
|
||||
$this->effectiveColumnWidths = [];
|
||||
unset($this->numberOfColumns);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, TableStyle>
|
||||
*/
|
||||
private static function initStyles(): array
|
||||
{
|
||||
$borderless = new TableStyle();
|
||||
$borderless
|
||||
->setHorizontalBorderChars('=')
|
||||
->setVerticalBorderChars(' ')
|
||||
->setDefaultCrossingChar(' ')
|
||||
;
|
||||
|
||||
$compact = new TableStyle();
|
||||
$compact
|
||||
->setHorizontalBorderChars('')
|
||||
->setVerticalBorderChars('')
|
||||
->setDefaultCrossingChar('')
|
||||
->setCellRowContentFormat('%s ')
|
||||
;
|
||||
|
||||
$styleGuide = new TableStyle();
|
||||
$styleGuide
|
||||
->setHorizontalBorderChars('-')
|
||||
->setVerticalBorderChars(' ')
|
||||
->setDefaultCrossingChar(' ')
|
||||
->setCellHeaderFormat('%s')
|
||||
;
|
||||
|
||||
$box = (new TableStyle())
|
||||
->setHorizontalBorderChars('─')
|
||||
->setVerticalBorderChars('│')
|
||||
->setCrossingChars('┼', '┌', '┬', '┐', '┤', '┘', '┴', '└', '├')
|
||||
;
|
||||
|
||||
$boxDouble = (new TableStyle())
|
||||
->setHorizontalBorderChars('═', '─')
|
||||
->setVerticalBorderChars('║', '│')
|
||||
->setCrossingChars('┼', '╔', '╤', '╗', '╢', '╝', '╧', '╚', '╟', '╠', '╪', '╣')
|
||||
;
|
||||
|
||||
return [
|
||||
'default' => new TableStyle(),
|
||||
'borderless' => $borderless,
|
||||
'compact' => $compact,
|
||||
'symfony-style-guide' => $styleGuide,
|
||||
'box' => $box,
|
||||
'box-double' => $boxDouble,
|
||||
];
|
||||
}
|
||||
|
||||
private function resolveStyle(TableStyle|string $name): TableStyle
|
||||
{
|
||||
if ($name instanceof TableStyle) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
return self::$styles[$name] ?? throw new InvalidArgumentException(\sprintf('Style "%s" is not defined.', $name));
|
||||
}
|
||||
}
|
||||
72
vendor/symfony/console/Helper/TableCell.php
vendored
Normal file
72
vendor/symfony/console/Helper/TableCell.php
vendored
Normal 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\Helper;
|
||||
|
||||
use Symfony\Component\Console\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
|
||||
*/
|
||||
class TableCell
|
||||
{
|
||||
private string $value;
|
||||
private array $options = [
|
||||
'rowspan' => 1,
|
||||
'colspan' => 1,
|
||||
'style' => null,
|
||||
];
|
||||
|
||||
public function __construct(string $value = '', array $options = [])
|
||||
{
|
||||
$this->value = $value;
|
||||
|
||||
// check option names
|
||||
if ($diff = array_diff(array_keys($options), array_keys($this->options))) {
|
||||
throw new InvalidArgumentException(\sprintf('The TableCell does not support the following options: \'%s\'.', implode('\', \'', $diff)));
|
||||
}
|
||||
|
||||
if (isset($options['style']) && !$options['style'] instanceof TableCellStyle) {
|
||||
throw new InvalidArgumentException('The style option must be an instance of "TableCellStyle".');
|
||||
}
|
||||
|
||||
$this->options = array_merge($this->options, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cell value.
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets number of colspan.
|
||||
*/
|
||||
public function getColspan(): int
|
||||
{
|
||||
return (int) $this->options['colspan'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets number of rowspan.
|
||||
*/
|
||||
public function getRowspan(): int
|
||||
{
|
||||
return (int) $this->options['rowspan'];
|
||||
}
|
||||
|
||||
public function getStyle(): ?TableCellStyle
|
||||
{
|
||||
return $this->options['style'];
|
||||
}
|
||||
}
|
||||
84
vendor/symfony/console/Helper/TableCellStyle.php
vendored
Normal file
84
vendor/symfony/console/Helper/TableCellStyle.php
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Helper;
|
||||
|
||||
use Symfony\Component\Console\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @author Yewhen Khoptynskyi <khoptynskyi@gmail.com>
|
||||
*/
|
||||
class TableCellStyle
|
||||
{
|
||||
public const DEFAULT_ALIGN = 'left';
|
||||
|
||||
private const TAG_OPTIONS = [
|
||||
'fg',
|
||||
'bg',
|
||||
'options',
|
||||
];
|
||||
|
||||
private const ALIGN_MAP = [
|
||||
'left' => \STR_PAD_RIGHT,
|
||||
'center' => \STR_PAD_BOTH,
|
||||
'right' => \STR_PAD_LEFT,
|
||||
];
|
||||
|
||||
private array $options = [
|
||||
'fg' => 'default',
|
||||
'bg' => 'default',
|
||||
'options' => null,
|
||||
'align' => self::DEFAULT_ALIGN,
|
||||
'cellFormat' => null,
|
||||
];
|
||||
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
if ($diff = array_diff(array_keys($options), array_keys($this->options))) {
|
||||
throw new InvalidArgumentException(\sprintf('The TableCellStyle does not support the following options: \'%s\'.', implode('\', \'', $diff)));
|
||||
}
|
||||
|
||||
if (isset($options['align']) && !\array_key_exists($options['align'], self::ALIGN_MAP)) {
|
||||
throw new InvalidArgumentException(\sprintf('Wrong align value. Value must be following: \'%s\'.', implode('\', \'', array_keys(self::ALIGN_MAP))));
|
||||
}
|
||||
|
||||
$this->options = array_merge($this->options, $options);
|
||||
}
|
||||
|
||||
public function getOptions(): array
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets options we need for tag for example fg, bg.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getTagOptions(): array
|
||||
{
|
||||
return array_filter(
|
||||
$this->getOptions(),
|
||||
fn ($key) => \in_array($key, self::TAG_OPTIONS) && isset($this->options[$key]),
|
||||
\ARRAY_FILTER_USE_KEY
|
||||
);
|
||||
}
|
||||
|
||||
public function getPadByAlign(): int
|
||||
{
|
||||
return self::ALIGN_MAP[$this->getOptions()['align']];
|
||||
}
|
||||
|
||||
public function getCellFormat(): ?string
|
||||
{
|
||||
return $this->getOptions()['cellFormat'];
|
||||
}
|
||||
}
|
||||
30
vendor/symfony/console/Helper/TableRows.php
vendored
Normal file
30
vendor/symfony/console/Helper/TableRows.php
vendored
Normal 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\Helper;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class TableRows implements \IteratorAggregate
|
||||
{
|
||||
private \Closure $generator;
|
||||
|
||||
public function __construct(\Closure $generator)
|
||||
{
|
||||
$this->generator = $generator;
|
||||
}
|
||||
|
||||
public function getIterator(): \Traversable
|
||||
{
|
||||
return ($this->generator)();
|
||||
}
|
||||
}
|
||||
25
vendor/symfony/console/Helper/TableSeparator.php
vendored
Normal file
25
vendor/symfony/console/Helper/TableSeparator.php
vendored
Normal 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 = [])
|
||||
{
|
||||
parent::__construct('', $options);
|
||||
}
|
||||
}
|
||||
362
vendor/symfony/console/Helper/TableStyle.php
vendored
Normal file
362
vendor/symfony/console/Helper/TableStyle.php
vendored
Normal file
@ -0,0 +1,362 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Helper;
|
||||
|
||||
use Symfony\Component\Console\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Console\Exception\LogicException;
|
||||
|
||||
/**
|
||||
* Defines the styles for a Table.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Саша Стаменковић <umpirsky@gmail.com>
|
||||
* @author Dany Maillard <danymaillard93b@gmail.com>
|
||||
*/
|
||||
class TableStyle
|
||||
{
|
||||
private string $paddingChar = ' ';
|
||||
private string $horizontalOutsideBorderChar = '-';
|
||||
private string $horizontalInsideBorderChar = '-';
|
||||
private string $verticalOutsideBorderChar = '|';
|
||||
private string $verticalInsideBorderChar = '|';
|
||||
private string $crossingChar = '+';
|
||||
private string $crossingTopRightChar = '+';
|
||||
private string $crossingTopMidChar = '+';
|
||||
private string $crossingTopLeftChar = '+';
|
||||
private string $crossingMidRightChar = '+';
|
||||
private string $crossingBottomRightChar = '+';
|
||||
private string $crossingBottomMidChar = '+';
|
||||
private string $crossingBottomLeftChar = '+';
|
||||
private string $crossingMidLeftChar = '+';
|
||||
private string $crossingTopLeftBottomChar = '+';
|
||||
private string $crossingTopMidBottomChar = '+';
|
||||
private string $crossingTopRightBottomChar = '+';
|
||||
private string $headerTitleFormat = '<fg=black;bg=white;options=bold> %s </>';
|
||||
private string $footerTitleFormat = '<fg=black;bg=white;options=bold> %s </>';
|
||||
private string $cellHeaderFormat = '<info>%s</info>';
|
||||
private string $cellRowFormat = '%s';
|
||||
private string $cellRowContentFormat = ' %s ';
|
||||
private string $borderFormat = '%s';
|
||||
private int $padType = \STR_PAD_RIGHT;
|
||||
|
||||
/**
|
||||
* Sets padding character, used for cell padding.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setPaddingChar(string $paddingChar): static
|
||||
{
|
||||
if (!$paddingChar) {
|
||||
throw new LogicException('The padding char must not be empty.');
|
||||
}
|
||||
|
||||
$this->paddingChar = $paddingChar;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets padding character, used for cell padding.
|
||||
*/
|
||||
public function getPaddingChar(): string
|
||||
{
|
||||
return $this->paddingChar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets horizontal border characters.
|
||||
*
|
||||
* <code>
|
||||
* ╔═══════════════╤══════════════════════════╤══════════════════╗
|
||||
* ║ ISBN │ Title │ Author ║
|
||||
* ╠═══════1═══════╪══════════════════════════╪══════════════════╣
|
||||
* ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║
|
||||
* ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║
|
||||
* ╟───────2───────┼──────────────────────────┼──────────────────╢
|
||||
* ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║
|
||||
* ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║
|
||||
* ╚═══════════════╧══════════════════════════╧══════════════════╝
|
||||
* </code>
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setHorizontalBorderChars(string $outside, ?string $inside = null): static
|
||||
{
|
||||
$this->horizontalOutsideBorderChar = $outside;
|
||||
$this->horizontalInsideBorderChar = $inside ?? $outside;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets vertical border characters.
|
||||
*
|
||||
* <code>
|
||||
* ╔═══════════════╤══════════════════════════╤══════════════════╗
|
||||
* 1 ISBN 2 Title │ Author ║
|
||||
* ╠═══════════════╪══════════════════════════╪══════════════════╣
|
||||
* ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║
|
||||
* ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║
|
||||
* ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║
|
||||
* ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║
|
||||
* ╚═══════════════╧══════════════════════════╧══════════════════╝
|
||||
* </code>
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setVerticalBorderChars(string $outside, ?string $inside = null): static
|
||||
{
|
||||
$this->verticalOutsideBorderChar = $outside;
|
||||
$this->verticalInsideBorderChar = $inside ?? $outside;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets border characters.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getBorderChars(): array
|
||||
{
|
||||
return [
|
||||
$this->horizontalOutsideBorderChar,
|
||||
$this->verticalOutsideBorderChar,
|
||||
$this->horizontalInsideBorderChar,
|
||||
$this->verticalInsideBorderChar,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets crossing characters.
|
||||
*
|
||||
* Example:
|
||||
* <code>
|
||||
* 1═══════════════2══════════════════════════2══════════════════3
|
||||
* ║ ISBN │ Title │ Author ║
|
||||
* 8'══════════════0'═════════════════════════0'═════════════════4'
|
||||
* ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║
|
||||
* ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║
|
||||
* 8───────────────0──────────────────────────0──────────────────4
|
||||
* ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║
|
||||
* ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║
|
||||
* 7═══════════════6══════════════════════════6══════════════════5
|
||||
* </code>
|
||||
*
|
||||
* @param string $cross Crossing char (see #0 of example)
|
||||
* @param string $topLeft Top left char (see #1 of example)
|
||||
* @param string $topMid Top mid char (see #2 of example)
|
||||
* @param string $topRight Top right char (see #3 of example)
|
||||
* @param string $midRight Mid right char (see #4 of example)
|
||||
* @param string $bottomRight Bottom right char (see #5 of example)
|
||||
* @param string $bottomMid Bottom mid char (see #6 of example)
|
||||
* @param string $bottomLeft Bottom left char (see #7 of example)
|
||||
* @param string $midLeft Mid left char (see #8 of example)
|
||||
* @param string|null $topLeftBottom Top left bottom char (see #8' of example), equals to $midLeft if null
|
||||
* @param string|null $topMidBottom Top mid bottom char (see #0' of example), equals to $cross if null
|
||||
* @param string|null $topRightBottom Top right bottom char (see #4' of example), equals to $midRight if null
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setCrossingChars(string $cross, string $topLeft, string $topMid, string $topRight, string $midRight, string $bottomRight, string $bottomMid, string $bottomLeft, string $midLeft, ?string $topLeftBottom = null, ?string $topMidBottom = null, ?string $topRightBottom = null): static
|
||||
{
|
||||
$this->crossingChar = $cross;
|
||||
$this->crossingTopLeftChar = $topLeft;
|
||||
$this->crossingTopMidChar = $topMid;
|
||||
$this->crossingTopRightChar = $topRight;
|
||||
$this->crossingMidRightChar = $midRight;
|
||||
$this->crossingBottomRightChar = $bottomRight;
|
||||
$this->crossingBottomMidChar = $bottomMid;
|
||||
$this->crossingBottomLeftChar = $bottomLeft;
|
||||
$this->crossingMidLeftChar = $midLeft;
|
||||
$this->crossingTopLeftBottomChar = $topLeftBottom ?? $midLeft;
|
||||
$this->crossingTopMidBottomChar = $topMidBottom ?? $cross;
|
||||
$this->crossingTopRightBottomChar = $topRightBottom ?? $midRight;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets default crossing character used for each cross.
|
||||
*
|
||||
* @see {@link setCrossingChars()} for setting each crossing individually.
|
||||
*/
|
||||
public function setDefaultCrossingChar(string $char): self
|
||||
{
|
||||
return $this->setCrossingChars($char, $char, $char, $char, $char, $char, $char, $char, $char);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets crossing character.
|
||||
*/
|
||||
public function getCrossingChar(): string
|
||||
{
|
||||
return $this->crossingChar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets crossing characters.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getCrossingChars(): array
|
||||
{
|
||||
return [
|
||||
$this->crossingChar,
|
||||
$this->crossingTopLeftChar,
|
||||
$this->crossingTopMidChar,
|
||||
$this->crossingTopRightChar,
|
||||
$this->crossingMidRightChar,
|
||||
$this->crossingBottomRightChar,
|
||||
$this->crossingBottomMidChar,
|
||||
$this->crossingBottomLeftChar,
|
||||
$this->crossingMidLeftChar,
|
||||
$this->crossingTopLeftBottomChar,
|
||||
$this->crossingTopMidBottomChar,
|
||||
$this->crossingTopRightBottomChar,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets header cell format.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setCellHeaderFormat(string $cellHeaderFormat): static
|
||||
{
|
||||
$this->cellHeaderFormat = $cellHeaderFormat;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets header cell format.
|
||||
*/
|
||||
public function getCellHeaderFormat(): string
|
||||
{
|
||||
return $this->cellHeaderFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets row cell format.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setCellRowFormat(string $cellRowFormat): static
|
||||
{
|
||||
$this->cellRowFormat = $cellRowFormat;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets row cell format.
|
||||
*/
|
||||
public function getCellRowFormat(): string
|
||||
{
|
||||
return $this->cellRowFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets row cell content format.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setCellRowContentFormat(string $cellRowContentFormat): static
|
||||
{
|
||||
$this->cellRowContentFormat = $cellRowContentFormat;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets row cell content format.
|
||||
*/
|
||||
public function getCellRowContentFormat(): string
|
||||
{
|
||||
return $this->cellRowContentFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets table border format.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setBorderFormat(string $borderFormat): static
|
||||
{
|
||||
$this->borderFormat = $borderFormat;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets table border format.
|
||||
*/
|
||||
public function getBorderFormat(): string
|
||||
{
|
||||
return $this->borderFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets cell padding type.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setPadType(int $padType): static
|
||||
{
|
||||
if (!\in_array($padType, [\STR_PAD_LEFT, \STR_PAD_RIGHT, \STR_PAD_BOTH], true)) {
|
||||
throw new InvalidArgumentException('Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH).');
|
||||
}
|
||||
|
||||
$this->padType = $padType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets cell padding type.
|
||||
*/
|
||||
public function getPadType(): int
|
||||
{
|
||||
return $this->padType;
|
||||
}
|
||||
|
||||
public function getHeaderTitleFormat(): string
|
||||
{
|
||||
return $this->headerTitleFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setHeaderTitleFormat(string $format): static
|
||||
{
|
||||
$this->headerTitleFormat = $format;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFooterTitleFormat(): string
|
||||
{
|
||||
return $this->footerTitleFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setFooterTitleFormat(string $format): static
|
||||
{
|
||||
$this->footerTitleFormat = $format;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user