login consent app sql
This commit is contained in:
600
vendor/symfony/twig-bridge/Command/DebugCommand.php
vendored
Normal file
600
vendor/symfony/twig-bridge/Command/DebugCommand.php
vendored
Normal file
@ -0,0 +1,600 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Twig\Command;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Completion\CompletionInput;
|
||||
use Symfony\Component\Console\Completion\CompletionSuggestions;
|
||||
use Symfony\Component\Console\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatter;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
|
||||
use Twig\Environment;
|
||||
use Twig\Loader\ChainLoader;
|
||||
use Twig\Loader\FilesystemLoader;
|
||||
|
||||
/**
|
||||
* Lists twig functions, filters, globals and tests present in the current project.
|
||||
*
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
*/
|
||||
class DebugCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'debug:twig';
|
||||
protected static $defaultDescription = 'Show a list of twig functions, filters, globals and tests';
|
||||
|
||||
private $twig;
|
||||
private $projectDir;
|
||||
private $bundlesMetadata;
|
||||
private $twigDefaultPath;
|
||||
private $filesystemLoaders;
|
||||
private $fileLinkFormatter;
|
||||
|
||||
public function __construct(Environment $twig, string $projectDir = null, array $bundlesMetadata = [], string $twigDefaultPath = null, FileLinkFormatter $fileLinkFormatter = null)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->twig = $twig;
|
||||
$this->projectDir = $projectDir;
|
||||
$this->bundlesMetadata = $bundlesMetadata;
|
||||
$this->twigDefaultPath = $twigDefaultPath;
|
||||
$this->fileLinkFormatter = $fileLinkFormatter;
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setDefinition([
|
||||
new InputArgument('name', InputArgument::OPTIONAL, 'The template name'),
|
||||
new InputOption('filter', null, InputOption::VALUE_REQUIRED, 'Show details for all entries matching this filter'),
|
||||
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (text or json)', 'text'),
|
||||
])
|
||||
->setDescription(self::$defaultDescription)
|
||||
->setHelp(<<<'EOF'
|
||||
The <info>%command.name%</info> command outputs a list of twig functions,
|
||||
filters, globals and tests.
|
||||
|
||||
<info>php %command.full_name%</info>
|
||||
|
||||
The command lists all functions, filters, etc.
|
||||
|
||||
<info>php %command.full_name% @Twig/Exception/error.html.twig</info>
|
||||
|
||||
The command lists all paths that match the given template name.
|
||||
|
||||
<info>php %command.full_name% --filter=date</info>
|
||||
|
||||
The command lists everything that contains the word date.
|
||||
|
||||
<info>php %command.full_name% --format=json</info>
|
||||
|
||||
The command lists everything in a machine readable json format.
|
||||
EOF
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$name = $input->getArgument('name');
|
||||
$filter = $input->getOption('filter');
|
||||
|
||||
if (null !== $name && [] === $this->getFilesystemLoaders()) {
|
||||
throw new InvalidArgumentException(sprintf('Argument "name" not supported, it requires the Twig loader "%s".', FilesystemLoader::class));
|
||||
}
|
||||
|
||||
switch ($input->getOption('format')) {
|
||||
case 'text':
|
||||
$name ? $this->displayPathsText($io, $name) : $this->displayGeneralText($io, $filter);
|
||||
break;
|
||||
case 'json':
|
||||
$name ? $this->displayPathsJson($io, $name) : $this->displayGeneralJson($io, $filter);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format')));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
|
||||
{
|
||||
if ($input->mustSuggestArgumentValuesFor('name')) {
|
||||
$suggestions->suggestValues(array_keys($this->getLoaderPaths()));
|
||||
}
|
||||
|
||||
if ($input->mustSuggestOptionValuesFor('format')) {
|
||||
$suggestions->suggestValues(['text', 'json']);
|
||||
}
|
||||
}
|
||||
|
||||
private function displayPathsText(SymfonyStyle $io, string $name)
|
||||
{
|
||||
$file = new \ArrayIterator($this->findTemplateFiles($name));
|
||||
$paths = $this->getLoaderPaths($name);
|
||||
|
||||
$io->section('Matched File');
|
||||
if ($file->valid()) {
|
||||
if ($fileLink = $this->getFileLink($file->key())) {
|
||||
$io->block($file->current(), 'OK', sprintf('fg=black;bg=green;href=%s', $fileLink), ' ', true);
|
||||
} else {
|
||||
$io->success($file->current());
|
||||
}
|
||||
$file->next();
|
||||
|
||||
if ($file->valid()) {
|
||||
$io->section('Overridden Files');
|
||||
do {
|
||||
if ($fileLink = $this->getFileLink($file->key())) {
|
||||
$io->text(sprintf('* <href=%s>%s</>', $fileLink, $file->current()));
|
||||
} else {
|
||||
$io->text(sprintf('* %s', $file->current()));
|
||||
}
|
||||
$file->next();
|
||||
} while ($file->valid());
|
||||
}
|
||||
} else {
|
||||
$alternatives = [];
|
||||
|
||||
if ($paths) {
|
||||
$shortnames = [];
|
||||
$dirs = [];
|
||||
foreach (current($paths) as $path) {
|
||||
$dirs[] = $this->isAbsolutePath($path) ? $path : $this->projectDir.'/'.$path;
|
||||
}
|
||||
foreach (Finder::create()->files()->followLinks()->in($dirs) as $file) {
|
||||
$shortnames[] = str_replace('\\', '/', $file->getRelativePathname());
|
||||
}
|
||||
|
||||
[$namespace, $shortname] = $this->parseTemplateName($name);
|
||||
$alternatives = $this->findAlternatives($shortname, $shortnames);
|
||||
if (FilesystemLoader::MAIN_NAMESPACE !== $namespace) {
|
||||
$alternatives = array_map(function ($shortname) use ($namespace) {
|
||||
return '@'.$namespace.'/'.$shortname;
|
||||
}, $alternatives);
|
||||
}
|
||||
}
|
||||
|
||||
$this->error($io, sprintf('Template name "%s" not found', $name), $alternatives);
|
||||
}
|
||||
|
||||
$io->section('Configured Paths');
|
||||
if ($paths) {
|
||||
$io->table(['Namespace', 'Paths'], $this->buildTableRows($paths));
|
||||
} else {
|
||||
$alternatives = [];
|
||||
$namespace = $this->parseTemplateName($name)[0];
|
||||
|
||||
if (FilesystemLoader::MAIN_NAMESPACE === $namespace) {
|
||||
$message = 'No template paths configured for your application';
|
||||
} else {
|
||||
$message = sprintf('No template paths configured for "@%s" namespace', $namespace);
|
||||
foreach ($this->getFilesystemLoaders() as $loader) {
|
||||
$namespaces = $loader->getNamespaces();
|
||||
foreach ($this->findAlternatives($namespace, $namespaces) as $namespace) {
|
||||
$alternatives[] = '@'.$namespace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->error($io, $message, $alternatives);
|
||||
|
||||
if (!$alternatives && $paths = $this->getLoaderPaths()) {
|
||||
$io->table(['Namespace', 'Paths'], $this->buildTableRows($paths));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function displayPathsJson(SymfonyStyle $io, string $name)
|
||||
{
|
||||
$files = $this->findTemplateFiles($name);
|
||||
$paths = $this->getLoaderPaths($name);
|
||||
|
||||
if ($files) {
|
||||
$data['matched_file'] = array_shift($files);
|
||||
if ($files) {
|
||||
$data['overridden_files'] = $files;
|
||||
}
|
||||
} else {
|
||||
$data['matched_file'] = sprintf('Template name "%s" not found', $name);
|
||||
}
|
||||
$data['loader_paths'] = $paths;
|
||||
|
||||
$io->writeln(json_encode($data));
|
||||
}
|
||||
|
||||
private function displayGeneralText(SymfonyStyle $io, string $filter = null)
|
||||
{
|
||||
$decorated = $io->isDecorated();
|
||||
$types = ['functions', 'filters', 'tests', 'globals'];
|
||||
foreach ($types as $index => $type) {
|
||||
$items = [];
|
||||
foreach ($this->twig->{'get'.ucfirst($type)}() as $name => $entity) {
|
||||
if (!$filter || str_contains($name, $filter)) {
|
||||
$items[$name] = $name.$this->getPrettyMetadata($type, $entity, $decorated);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$items) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$io->section(ucfirst($type));
|
||||
|
||||
ksort($items);
|
||||
$io->listing($items);
|
||||
}
|
||||
|
||||
if (!$filter && $paths = $this->getLoaderPaths()) {
|
||||
$io->section('Loader Paths');
|
||||
$io->table(['Namespace', 'Paths'], $this->buildTableRows($paths));
|
||||
}
|
||||
|
||||
if ($wrongBundles = $this->findWrongBundleOverrides()) {
|
||||
foreach ($this->buildWarningMessages($wrongBundles) as $message) {
|
||||
$io->warning($message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function displayGeneralJson(SymfonyStyle $io, ?string $filter)
|
||||
{
|
||||
$decorated = $io->isDecorated();
|
||||
$types = ['functions', 'filters', 'tests', 'globals'];
|
||||
$data = [];
|
||||
foreach ($types as $type) {
|
||||
foreach ($this->twig->{'get'.ucfirst($type)}() as $name => $entity) {
|
||||
if (!$filter || str_contains($name, $filter)) {
|
||||
$data[$type][$name] = $this->getMetadata($type, $entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($data['tests'])) {
|
||||
$data['tests'] = array_keys($data['tests']);
|
||||
}
|
||||
|
||||
if (!$filter && $paths = $this->getLoaderPaths($filter)) {
|
||||
$data['loader_paths'] = $paths;
|
||||
}
|
||||
|
||||
if ($wrongBundles = $this->findWrongBundleOverrides()) {
|
||||
$data['warnings'] = $this->buildWarningMessages($wrongBundles);
|
||||
}
|
||||
|
||||
$data = json_encode($data, \JSON_PRETTY_PRINT);
|
||||
$io->writeln($decorated ? OutputFormatter::escape($data) : $data);
|
||||
}
|
||||
|
||||
private function getLoaderPaths(string $name = null): array
|
||||
{
|
||||
$loaderPaths = [];
|
||||
foreach ($this->getFilesystemLoaders() as $loader) {
|
||||
$namespaces = $loader->getNamespaces();
|
||||
if (null !== $name) {
|
||||
$namespace = $this->parseTemplateName($name)[0];
|
||||
$namespaces = array_intersect([$namespace], $namespaces);
|
||||
}
|
||||
|
||||
foreach ($namespaces as $namespace) {
|
||||
$paths = array_map([$this, 'getRelativePath'], $loader->getPaths($namespace));
|
||||
|
||||
if (FilesystemLoader::MAIN_NAMESPACE === $namespace) {
|
||||
$namespace = '(None)';
|
||||
} else {
|
||||
$namespace = '@'.$namespace;
|
||||
}
|
||||
|
||||
$loaderPaths[$namespace] = array_merge($loaderPaths[$namespace] ?? [], $paths);
|
||||
}
|
||||
}
|
||||
|
||||
return $loaderPaths;
|
||||
}
|
||||
|
||||
private function getMetadata(string $type, $entity)
|
||||
{
|
||||
if ('globals' === $type) {
|
||||
return $entity;
|
||||
}
|
||||
if ('tests' === $type) {
|
||||
return null;
|
||||
}
|
||||
if ('functions' === $type || 'filters' === $type) {
|
||||
$cb = $entity->getCallable();
|
||||
if (null === $cb) {
|
||||
return null;
|
||||
}
|
||||
if (\is_array($cb)) {
|
||||
if (!method_exists($cb[0], $cb[1])) {
|
||||
return null;
|
||||
}
|
||||
$refl = new \ReflectionMethod($cb[0], $cb[1]);
|
||||
} elseif (\is_object($cb) && method_exists($cb, '__invoke')) {
|
||||
$refl = new \ReflectionMethod($cb, '__invoke');
|
||||
} elseif (\function_exists($cb)) {
|
||||
$refl = new \ReflectionFunction($cb);
|
||||
} elseif (\is_string($cb) && preg_match('{^(.+)::(.+)$}', $cb, $m) && method_exists($m[1], $m[2])) {
|
||||
$refl = new \ReflectionMethod($m[1], $m[2]);
|
||||
} else {
|
||||
throw new \UnexpectedValueException('Unsupported callback type.');
|
||||
}
|
||||
|
||||
$args = $refl->getParameters();
|
||||
|
||||
// filter out context/environment args
|
||||
if ($entity->needsEnvironment()) {
|
||||
array_shift($args);
|
||||
}
|
||||
if ($entity->needsContext()) {
|
||||
array_shift($args);
|
||||
}
|
||||
|
||||
if ('filters' === $type) {
|
||||
// remove the value the filter is applied on
|
||||
array_shift($args);
|
||||
}
|
||||
|
||||
// format args
|
||||
$args = array_map(function (\ReflectionParameter $param) {
|
||||
if ($param->isDefaultValueAvailable()) {
|
||||
return $param->getName().' = '.json_encode($param->getDefaultValue());
|
||||
}
|
||||
|
||||
return $param->getName();
|
||||
}, $args);
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getPrettyMetadata(string $type, $entity, bool $decorated): ?string
|
||||
{
|
||||
if ('tests' === $type) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
$meta = $this->getMetadata($type, $entity);
|
||||
if (null === $meta) {
|
||||
return '(unknown?)';
|
||||
}
|
||||
} catch (\UnexpectedValueException $e) {
|
||||
return sprintf(' <error>%s</error>', $decorated ? OutputFormatter::escape($e->getMessage()) : $e->getMessage());
|
||||
}
|
||||
|
||||
if ('globals' === $type) {
|
||||
if (\is_object($meta)) {
|
||||
return ' = object('.\get_class($meta).')';
|
||||
}
|
||||
|
||||
$description = substr(@json_encode($meta), 0, 50);
|
||||
|
||||
return sprintf(' = %s', $decorated ? OutputFormatter::escape($description) : $description);
|
||||
}
|
||||
|
||||
if ('functions' === $type) {
|
||||
return '('.implode(', ', $meta).')';
|
||||
}
|
||||
|
||||
if ('filters' === $type) {
|
||||
return $meta ? '('.implode(', ', $meta).')' : '';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function findWrongBundleOverrides(): array
|
||||
{
|
||||
$alternatives = [];
|
||||
$bundleNames = [];
|
||||
|
||||
if ($this->twigDefaultPath && $this->projectDir) {
|
||||
$folders = glob($this->twigDefaultPath.'/bundles/*', \GLOB_ONLYDIR);
|
||||
$relativePath = ltrim(substr($this->twigDefaultPath.'/bundles/', \strlen($this->projectDir)), \DIRECTORY_SEPARATOR);
|
||||
$bundleNames = array_reduce($folders, function ($carry, $absolutePath) use ($relativePath) {
|
||||
if (str_starts_with($absolutePath, $this->projectDir)) {
|
||||
$name = basename($absolutePath);
|
||||
$path = ltrim($relativePath.$name, \DIRECTORY_SEPARATOR);
|
||||
$carry[$name] = $path;
|
||||
}
|
||||
|
||||
return $carry;
|
||||
}, $bundleNames);
|
||||
}
|
||||
|
||||
if ($notFoundBundles = array_diff_key($bundleNames, $this->bundlesMetadata)) {
|
||||
$alternatives = [];
|
||||
foreach ($notFoundBundles as $notFoundBundle => $path) {
|
||||
$alternatives[$path] = $this->findAlternatives($notFoundBundle, array_keys($this->bundlesMetadata));
|
||||
}
|
||||
}
|
||||
|
||||
return $alternatives;
|
||||
}
|
||||
|
||||
private function buildWarningMessages(array $wrongBundles): array
|
||||
{
|
||||
$messages = [];
|
||||
foreach ($wrongBundles as $path => $alternatives) {
|
||||
$message = sprintf('Path "%s" not matching any bundle found', $path);
|
||||
if ($alternatives) {
|
||||
if (1 === \count($alternatives)) {
|
||||
$message .= sprintf(", did you mean \"%s\"?\n", $alternatives[0]);
|
||||
} else {
|
||||
$message .= ", did you mean one of these:\n";
|
||||
foreach ($alternatives as $bundle) {
|
||||
$message .= sprintf(" - %s\n", $bundle);
|
||||
}
|
||||
}
|
||||
}
|
||||
$messages[] = trim($message);
|
||||
}
|
||||
|
||||
return $messages;
|
||||
}
|
||||
|
||||
private function error(SymfonyStyle $io, string $message, array $alternatives = []): void
|
||||
{
|
||||
if ($alternatives) {
|
||||
if (1 === \count($alternatives)) {
|
||||
$message .= "\n\nDid you mean this?\n ";
|
||||
} else {
|
||||
$message .= "\n\nDid you mean one of these?\n ";
|
||||
}
|
||||
$message .= implode("\n ", $alternatives);
|
||||
}
|
||||
|
||||
$io->block($message, null, 'fg=white;bg=red', ' ', true);
|
||||
}
|
||||
|
||||
private function findTemplateFiles(string $name): array
|
||||
{
|
||||
[$namespace, $shortname] = $this->parseTemplateName($name);
|
||||
|
||||
$files = [];
|
||||
foreach ($this->getFilesystemLoaders() as $loader) {
|
||||
foreach ($loader->getPaths($namespace) as $path) {
|
||||
if (!$this->isAbsolutePath($path)) {
|
||||
$path = $this->projectDir.'/'.$path;
|
||||
}
|
||||
$filename = $path.'/'.$shortname;
|
||||
|
||||
if (is_file($filename)) {
|
||||
if (false !== $realpath = realpath($filename)) {
|
||||
$files[$realpath] = $this->getRelativePath($realpath);
|
||||
} else {
|
||||
$files[$filename] = $this->getRelativePath($filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
private function parseTemplateName(string $name, string $default = FilesystemLoader::MAIN_NAMESPACE): array
|
||||
{
|
||||
if (isset($name[0]) && '@' === $name[0]) {
|
||||
if (false === ($pos = strpos($name, '/')) || $pos === \strlen($name) - 1) {
|
||||
throw new InvalidArgumentException(sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name));
|
||||
}
|
||||
|
||||
$namespace = substr($name, 1, $pos - 1);
|
||||
$shortname = substr($name, $pos + 1);
|
||||
|
||||
return [$namespace, $shortname];
|
||||
}
|
||||
|
||||
return [$default, $name];
|
||||
}
|
||||
|
||||
private function buildTableRows(array $loaderPaths): array
|
||||
{
|
||||
$rows = [];
|
||||
$firstNamespace = true;
|
||||
$prevHasSeparator = false;
|
||||
|
||||
foreach ($loaderPaths as $namespace => $paths) {
|
||||
if (!$firstNamespace && !$prevHasSeparator && \count($paths) > 1) {
|
||||
$rows[] = ['', ''];
|
||||
}
|
||||
$firstNamespace = false;
|
||||
foreach ($paths as $path) {
|
||||
$rows[] = [$namespace, $path.\DIRECTORY_SEPARATOR];
|
||||
$namespace = '';
|
||||
}
|
||||
if (\count($paths) > 1) {
|
||||
$rows[] = ['', ''];
|
||||
$prevHasSeparator = true;
|
||||
} else {
|
||||
$prevHasSeparator = false;
|
||||
}
|
||||
}
|
||||
if ($prevHasSeparator) {
|
||||
array_pop($rows);
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
private function findAlternatives(string $name, array $collection): array
|
||||
{
|
||||
$alternatives = [];
|
||||
foreach ($collection as $item) {
|
||||
$lev = levenshtein($name, $item);
|
||||
if ($lev <= \strlen($name) / 3 || str_contains($item, $name)) {
|
||||
$alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev;
|
||||
}
|
||||
}
|
||||
|
||||
$threshold = 1e3;
|
||||
$alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; });
|
||||
ksort($alternatives, \SORT_NATURAL | \SORT_FLAG_CASE);
|
||||
|
||||
return array_keys($alternatives);
|
||||
}
|
||||
|
||||
private function getRelativePath(string $path): string
|
||||
{
|
||||
if (null !== $this->projectDir && str_starts_with($path, $this->projectDir)) {
|
||||
return ltrim(substr($path, \strlen($this->projectDir)), \DIRECTORY_SEPARATOR);
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
private function isAbsolutePath(string $file): bool
|
||||
{
|
||||
return strspn($file, '/\\', 0, 1) || (\strlen($file) > 3 && ctype_alpha($file[0]) && ':' === $file[1] && strspn($file, '/\\', 2, 1)) || null !== parse_url($file, \PHP_URL_SCHEME);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FilesystemLoader[]
|
||||
*/
|
||||
private function getFilesystemLoaders(): array
|
||||
{
|
||||
if (null !== $this->filesystemLoaders) {
|
||||
return $this->filesystemLoaders;
|
||||
}
|
||||
$this->filesystemLoaders = [];
|
||||
|
||||
$loader = $this->twig->getLoader();
|
||||
if ($loader instanceof FilesystemLoader) {
|
||||
$this->filesystemLoaders[] = $loader;
|
||||
} elseif ($loader instanceof ChainLoader) {
|
||||
foreach ($loader->getLoaders() as $l) {
|
||||
if ($l instanceof FilesystemLoader) {
|
||||
$this->filesystemLoaders[] = $l;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->filesystemLoaders;
|
||||
}
|
||||
|
||||
private function getFileLink(string $absolutePath): string
|
||||
{
|
||||
if (null === $this->fileLinkFormatter) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return (string) $this->fileLinkFormatter->format($absolutePath, 1);
|
||||
}
|
||||
}
|
296
vendor/symfony/twig-bridge/Command/LintCommand.php
vendored
Normal file
296
vendor/symfony/twig-bridge/Command/LintCommand.php
vendored
Normal file
@ -0,0 +1,296 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Twig\Command;
|
||||
|
||||
use Symfony\Component\Console\CI\GithubActionReporter;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Completion\CompletionInput;
|
||||
use Symfony\Component\Console\Completion\CompletionSuggestions;
|
||||
use Symfony\Component\Console\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Twig\Environment;
|
||||
use Twig\Error\Error;
|
||||
use Twig\Loader\ArrayLoader;
|
||||
use Twig\Loader\FilesystemLoader;
|
||||
use Twig\Source;
|
||||
|
||||
/**
|
||||
* Command that will validate your template syntax and output encountered errors.
|
||||
*
|
||||
* @author Marc Weistroff <marc.weistroff@sensiolabs.com>
|
||||
* @author Jérôme Tamarelle <jerome@tamarelle.net>
|
||||
*/
|
||||
class LintCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'lint:twig';
|
||||
protected static $defaultDescription = 'Lint a Twig template and outputs encountered errors';
|
||||
|
||||
private $twig;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $format;
|
||||
|
||||
public function __construct(Environment $twig)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->twig = $twig;
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setDescription(self::$defaultDescription)
|
||||
->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format')
|
||||
->addOption('show-deprecations', null, InputOption::VALUE_NONE, 'Show deprecations as errors')
|
||||
->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN')
|
||||
->setHelp(<<<'EOF'
|
||||
The <info>%command.name%</info> command lints a template and outputs to STDOUT
|
||||
the first encountered syntax error.
|
||||
|
||||
You can validate the syntax of contents passed from STDIN:
|
||||
|
||||
<info>cat filename | php %command.full_name% -</info>
|
||||
|
||||
Or the syntax of a file:
|
||||
|
||||
<info>php %command.full_name% filename</info>
|
||||
|
||||
Or of a whole directory:
|
||||
|
||||
<info>php %command.full_name% dirname</info>
|
||||
<info>php %command.full_name% dirname --format=json</info>
|
||||
|
||||
EOF
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$filenames = $input->getArgument('filename');
|
||||
$showDeprecations = $input->getOption('show-deprecations');
|
||||
$this->format = $input->getOption('format');
|
||||
|
||||
if (null === $this->format) {
|
||||
$this->format = GithubActionReporter::isGithubActionEnvironment() ? 'github' : 'txt';
|
||||
}
|
||||
|
||||
if (['-'] === $filenames) {
|
||||
return $this->display($input, $output, $io, [$this->validate(file_get_contents('php://stdin'), uniqid('sf_', true))]);
|
||||
}
|
||||
|
||||
if (!$filenames) {
|
||||
$loader = $this->twig->getLoader();
|
||||
if ($loader instanceof FilesystemLoader) {
|
||||
$paths = [];
|
||||
foreach ($loader->getNamespaces() as $namespace) {
|
||||
$paths[] = $loader->getPaths($namespace);
|
||||
}
|
||||
$filenames = array_merge(...$paths);
|
||||
}
|
||||
|
||||
if (!$filenames) {
|
||||
throw new RuntimeException('Please provide a filename or pipe template content to STDIN.');
|
||||
}
|
||||
}
|
||||
|
||||
if ($showDeprecations) {
|
||||
$prevErrorHandler = set_error_handler(static function ($level, $message, $file, $line) use (&$prevErrorHandler) {
|
||||
if (\E_USER_DEPRECATED === $level) {
|
||||
$templateLine = 0;
|
||||
if (preg_match('/ at line (\d+)[ .]/', $message, $matches)) {
|
||||
$templateLine = $matches[1];
|
||||
}
|
||||
|
||||
throw new Error($message, $templateLine);
|
||||
}
|
||||
|
||||
return $prevErrorHandler ? $prevErrorHandler($level, $message, $file, $line) : false;
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
$filesInfo = $this->getFilesInfo($filenames);
|
||||
} finally {
|
||||
if ($showDeprecations) {
|
||||
restore_error_handler();
|
||||
}
|
||||
}
|
||||
|
||||
return $this->display($input, $output, $io, $filesInfo);
|
||||
}
|
||||
|
||||
private function getFilesInfo(array $filenames): array
|
||||
{
|
||||
$filesInfo = [];
|
||||
foreach ($filenames as $filename) {
|
||||
foreach ($this->findFiles($filename) as $file) {
|
||||
$filesInfo[] = $this->validate(file_get_contents($file), $file);
|
||||
}
|
||||
}
|
||||
|
||||
return $filesInfo;
|
||||
}
|
||||
|
||||
protected function findFiles(string $filename)
|
||||
{
|
||||
if (is_file($filename)) {
|
||||
return [$filename];
|
||||
} elseif (is_dir($filename)) {
|
||||
return Finder::create()->files()->in($filename)->name('*.twig');
|
||||
}
|
||||
|
||||
throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename));
|
||||
}
|
||||
|
||||
private function validate(string $template, string $file): array
|
||||
{
|
||||
$realLoader = $this->twig->getLoader();
|
||||
try {
|
||||
$temporaryLoader = new ArrayLoader([$file => $template]);
|
||||
$this->twig->setLoader($temporaryLoader);
|
||||
$nodeTree = $this->twig->parse($this->twig->tokenize(new Source($template, $file)));
|
||||
$this->twig->compile($nodeTree);
|
||||
$this->twig->setLoader($realLoader);
|
||||
} catch (Error $e) {
|
||||
$this->twig->setLoader($realLoader);
|
||||
|
||||
return ['template' => $template, 'file' => $file, 'line' => $e->getTemplateLine(), 'valid' => false, 'exception' => $e];
|
||||
}
|
||||
|
||||
return ['template' => $template, 'file' => $file, 'valid' => true];
|
||||
}
|
||||
|
||||
private function display(InputInterface $input, OutputInterface $output, SymfonyStyle $io, array $files)
|
||||
{
|
||||
switch ($this->format) {
|
||||
case 'txt':
|
||||
return $this->displayTxt($output, $io, $files);
|
||||
case 'json':
|
||||
return $this->displayJson($output, $files);
|
||||
case 'github':
|
||||
return $this->displayTxt($output, $io, $files, true);
|
||||
default:
|
||||
throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format')));
|
||||
}
|
||||
}
|
||||
|
||||
private function displayTxt(OutputInterface $output, SymfonyStyle $io, array $filesInfo, bool $errorAsGithubAnnotations = false)
|
||||
{
|
||||
$errors = 0;
|
||||
$githubReporter = $errorAsGithubAnnotations ? new GithubActionReporter($output) : null;
|
||||
|
||||
foreach ($filesInfo as $info) {
|
||||
if ($info['valid'] && $output->isVerbose()) {
|
||||
$io->comment('<info>OK</info>'.($info['file'] ? sprintf(' in %s', $info['file']) : ''));
|
||||
} elseif (!$info['valid']) {
|
||||
++$errors;
|
||||
$this->renderException($io, $info['template'], $info['exception'], $info['file'], $githubReporter);
|
||||
}
|
||||
}
|
||||
|
||||
if (0 === $errors) {
|
||||
$io->success(sprintf('All %d Twig files contain valid syntax.', \count($filesInfo)));
|
||||
} else {
|
||||
$io->warning(sprintf('%d Twig files have valid syntax and %d contain errors.', \count($filesInfo) - $errors, $errors));
|
||||
}
|
||||
|
||||
return min($errors, 1);
|
||||
}
|
||||
|
||||
private function displayJson(OutputInterface $output, array $filesInfo)
|
||||
{
|
||||
$errors = 0;
|
||||
|
||||
array_walk($filesInfo, function (&$v) use (&$errors) {
|
||||
$v['file'] = (string) $v['file'];
|
||||
unset($v['template']);
|
||||
if (!$v['valid']) {
|
||||
$v['message'] = $v['exception']->getMessage();
|
||||
unset($v['exception']);
|
||||
++$errors;
|
||||
}
|
||||
});
|
||||
|
||||
$output->writeln(json_encode($filesInfo, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES));
|
||||
|
||||
return min($errors, 1);
|
||||
}
|
||||
|
||||
private function renderException(SymfonyStyle $output, string $template, Error $exception, string $file = null, GithubActionReporter $githubReporter = null)
|
||||
{
|
||||
$line = $exception->getTemplateLine();
|
||||
|
||||
if ($githubReporter) {
|
||||
$githubReporter->error($exception->getRawMessage(), $file, $line <= 0 ? null : $line);
|
||||
}
|
||||
|
||||
if ($file) {
|
||||
$output->text(sprintf('<error> ERROR </error> in %s (line %s)', $file, $line));
|
||||
} else {
|
||||
$output->text(sprintf('<error> ERROR </error> (line %s)', $line));
|
||||
}
|
||||
|
||||
// If the line is not known (this might happen for deprecations if we fail at detecting the line for instance),
|
||||
// we render the message without context, to ensure the message is displayed.
|
||||
if ($line <= 0) {
|
||||
$output->text(sprintf('<error> >> %s</error> ', $exception->getRawMessage()));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->getContext($template, $line) as $lineNumber => $code) {
|
||||
$output->text(sprintf(
|
||||
'%s %-6s %s',
|
||||
$lineNumber === $line ? '<error> >> </error>' : ' ',
|
||||
$lineNumber,
|
||||
$code
|
||||
));
|
||||
if ($lineNumber === $line) {
|
||||
$output->text(sprintf('<error> >> %s</error> ', $exception->getRawMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getContext(string $template, int $line, int $context = 3)
|
||||
{
|
||||
$lines = explode("\n", $template);
|
||||
|
||||
$position = max(0, $line - $context);
|
||||
$max = min(\count($lines), $line - 1 + $context);
|
||||
|
||||
$result = [];
|
||||
while ($position < $max) {
|
||||
$result[$position + 1] = $lines[$position];
|
||||
++$position;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
|
||||
{
|
||||
if ($input->mustSuggestOptionValuesFor('format')) {
|
||||
$suggestions->suggestValues(['txt', 'json', 'github']);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user