2021-05-12 15:47:19 +00:00
< ? php
/*
* This file is part of the Symfony package .
*
* ( c ) Fabien Potencier < fabien @ symfony . com >
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
2021-05-19 00:12:47 +00:00
namespace RectorPrefix20210519\Symfony\Component\Yaml\Command ;
2021-05-12 15:47:19 +00:00
2021-05-19 00:12:47 +00:00
use RectorPrefix20210519\Symfony\Component\Console\Command\Command ;
use RectorPrefix20210519\Symfony\Component\Console\Exception\InvalidArgumentException ;
use RectorPrefix20210519\Symfony\Component\Console\Exception\RuntimeException ;
use RectorPrefix20210519\Symfony\Component\Console\Input\InputArgument ;
use RectorPrefix20210519\Symfony\Component\Console\Input\InputInterface ;
use RectorPrefix20210519\Symfony\Component\Console\Input\InputOption ;
use RectorPrefix20210519\Symfony\Component\Console\Output\OutputInterface ;
use RectorPrefix20210519\Symfony\Component\Console\Style\SymfonyStyle ;
use RectorPrefix20210519\Symfony\Component\Yaml\Exception\ParseException ;
use RectorPrefix20210519\Symfony\Component\Yaml\Parser ;
use RectorPrefix20210519\Symfony\Component\Yaml\Yaml ;
2021-05-12 15:47:19 +00:00
/**
* Validates YAML files syntax and outputs encountered errors .
*
* @ author Grégoire Pineau < lyrixx @ lyrixx . info >
* @ author Robin Chalas < robin . chalas @ gmail . com >
*/
2021-05-19 00:12:47 +00:00
class LintCommand extends \RectorPrefix20210519\Symfony\Component\Console\Command\Command
2021-05-12 15:47:19 +00:00
{
protected static $defaultName = 'lint:yaml' ;
private $parser ;
private $format ;
private $displayCorrectFiles ;
private $directoryIteratorProvider ;
private $isReadableProvider ;
public function __construct ( string $name = null , callable $directoryIteratorProvider = null , callable $isReadableProvider = null )
{
parent :: __construct ( $name );
$this -> directoryIteratorProvider = $directoryIteratorProvider ;
$this -> isReadableProvider = $isReadableProvider ;
}
/**
* { @ inheritdoc }
*/
protected function configure ()
{
2021-05-19 00:12:47 +00:00
$this -> setDescription ( 'Lint a file and outputs encountered errors' ) -> addArgument ( 'filename' , \RectorPrefix20210519\Symfony\Component\Console\Input\InputArgument :: IS_ARRAY , 'A file, a directory or "-" for reading from STDIN' ) -> addOption ( 'format' , null , \RectorPrefix20210519\Symfony\Component\Console\Input\InputOption :: VALUE_REQUIRED , 'The output format' , 'txt' ) -> addOption ( 'parse-tags' , null , \RectorPrefix20210519\Symfony\Component\Console\Input\InputOption :: VALUE_NONE , 'Parse custom tags' ) -> setHelp ( <<< EOF
2021-05-12 15:47:19 +00:00
The < info >% command . name %</ info > command lints a YAML file and outputs to STDOUT
the first encountered syntax error .
You can validates YAML contents passed from STDIN :
< info > cat filename | php % command . full_name % -</ info >
You can also validate the syntax of a file :
< info > php % command . full_name % filename </ info >
Or of a whole directory :
< info > php % command . full_name % dirname </ info >
< info > php % command . full_name % dirname -- format = json </ info >
EOF
);
}
2021-05-19 00:12:47 +00:00
protected function execute ( \RectorPrefix20210519\Symfony\Component\Console\Input\InputInterface $input , \RectorPrefix20210519\Symfony\Component\Console\Output\OutputInterface $output )
2021-05-12 15:47:19 +00:00
{
2021-05-19 00:12:47 +00:00
$io = new \RectorPrefix20210519\Symfony\Component\Console\Style\SymfonyStyle ( $input , $output );
2021-05-12 15:47:19 +00:00
$filenames = ( array ) $input -> getArgument ( 'filename' );
$this -> format = $input -> getOption ( 'format' );
$this -> displayCorrectFiles = $output -> isVerbose ();
2021-05-19 00:12:47 +00:00
$flags = $input -> getOption ( 'parse-tags' ) ? \RectorPrefix20210519\Symfony\Component\Yaml\Yaml :: PARSE_CUSTOM_TAGS : 0 ;
2021-05-12 15:47:19 +00:00
if ([ '-' ] === $filenames ) {
return $this -> display ( $io , [ $this -> validate ( \file_get_contents ( 'php://stdin' ), $flags )]);
}
if ( ! $filenames ) {
2021-05-19 00:12:47 +00:00
throw new \RectorPrefix20210519\Symfony\Component\Console\Exception\RuntimeException ( 'Please provide a filename or pipe file content to STDIN.' );
2021-05-12 15:47:19 +00:00
}
$filesInfo = [];
foreach ( $filenames as $filename ) {
if ( ! $this -> isReadable ( $filename )) {
2021-05-19 00:12:47 +00:00
throw new \RectorPrefix20210519\Symfony\Component\Console\Exception\RuntimeException ( \sprintf ( 'File or directory "%s" is not readable.' , $filename ));
2021-05-12 15:47:19 +00:00
}
foreach ( $this -> getFiles ( $filename ) as $file ) {
$filesInfo [] = $this -> validate ( \file_get_contents ( $file ), $flags , $file );
}
}
return $this -> display ( $io , $filesInfo );
}
private function validate ( string $content , int $flags , string $file = null )
{
$prevErrorHandler = \set_error_handler ( function ( $level , $message , $file , $line ) use ( & $prevErrorHandler ) {
if ( \E_USER_DEPRECATED === $level ) {
2021-05-19 00:12:47 +00:00
throw new \RectorPrefix20210519\Symfony\Component\Yaml\Exception\ParseException ( $message , $this -> getParser () -> getRealCurrentLineNb () + 1 );
2021-05-12 15:47:19 +00:00
}
return $prevErrorHandler ? $prevErrorHandler ( $level , $message , $file , $line ) : \false ;
});
try {
2021-05-19 00:12:47 +00:00
$this -> getParser () -> parse ( $content , \RectorPrefix20210519\Symfony\Component\Yaml\Yaml :: PARSE_CONSTANT | $flags );
} catch ( \RectorPrefix20210519\Symfony\Component\Yaml\Exception\ParseException $e ) {
2021-05-12 15:47:19 +00:00
return [ 'file' => $file , 'line' => $e -> getParsedLine (), 'valid' => \false , 'message' => $e -> getMessage ()];
} finally {
\restore_error_handler ();
}
return [ 'file' => $file , 'valid' => \true ];
}
2021-05-19 00:12:47 +00:00
private function display ( \RectorPrefix20210519\Symfony\Component\Console\Style\SymfonyStyle $io , array $files ) : int
2021-05-12 15:47:19 +00:00
{
switch ( $this -> format ) {
case 'txt' :
return $this -> displayTxt ( $io , $files );
case 'json' :
return $this -> displayJson ( $io , $files );
default :
2021-05-19 00:12:47 +00:00
throw new \RectorPrefix20210519\Symfony\Component\Console\Exception\InvalidArgumentException ( \sprintf ( 'The format "%s" is not supported.' , $this -> format ));
2021-05-12 15:47:19 +00:00
}
}
2021-05-19 00:12:47 +00:00
private function displayTxt ( \RectorPrefix20210519\Symfony\Component\Console\Style\SymfonyStyle $io , array $filesInfo ) : int
2021-05-12 15:47:19 +00:00
{
$countFiles = \count ( $filesInfo );
$erroredFiles = 0 ;
$suggestTagOption = \false ;
foreach ( $filesInfo as $info ) {
if ( $info [ 'valid' ] && $this -> displayCorrectFiles ) {
$io -> comment ( '<info>OK</info>' . ( $info [ 'file' ] ? \sprintf ( ' in %s' , $info [ 'file' ]) : '' ));
} elseif ( ! $info [ 'valid' ]) {
++ $erroredFiles ;
$io -> text ( '<error> ERROR </error>' . ( $info [ 'file' ] ? \sprintf ( ' in %s' , $info [ 'file' ]) : '' ));
$io -> text ( \sprintf ( '<error> >> %s</error>' , $info [ 'message' ]));
if ( \false !== \strpos ( $info [ 'message' ], 'PARSE_CUSTOM_TAGS' )) {
$suggestTagOption = \true ;
}
}
}
if ( 0 === $erroredFiles ) {
$io -> success ( \sprintf ( 'All %d YAML files contain valid syntax.' , $countFiles ));
} else {
$io -> warning ( \sprintf ( '%d YAML files have valid syntax and %d contain errors.%s' , $countFiles - $erroredFiles , $erroredFiles , $suggestTagOption ? ' Use the --parse-tags option if you want parse custom tags.' : '' ));
}
return \min ( $erroredFiles , 1 );
}
2021-05-19 00:12:47 +00:00
private function displayJson ( \RectorPrefix20210519\Symfony\Component\Console\Style\SymfonyStyle $io , array $filesInfo ) : int
2021-05-12 15:47:19 +00:00
{
$errors = 0 ;
\array_walk ( $filesInfo , function ( & $v ) use ( & $errors ) {
$v [ 'file' ] = ( string ) $v [ 'file' ];
if ( ! $v [ 'valid' ]) {
++ $errors ;
}
if ( isset ( $v [ 'message' ]) && \false !== \strpos ( $v [ 'message' ], 'PARSE_CUSTOM_TAGS' )) {
$v [ 'message' ] .= ' Use the --parse-tags option if you want parse custom tags.' ;
}
});
$io -> writeln ( \json_encode ( $filesInfo , \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES ));
return \min ( $errors , 1 );
}
private function getFiles ( string $fileOrDirectory ) : iterable
{
if ( \is_file ( $fileOrDirectory )) {
( yield new \SplFileInfo ( $fileOrDirectory ));
return ;
}
foreach ( $this -> getDirectoryIterator ( $fileOrDirectory ) as $file ) {
if ( ! \in_array ( $file -> getExtension (), [ 'yml' , 'yaml' ])) {
continue ;
}
( yield $file );
}
}
2021-05-19 00:12:47 +00:00
private function getParser () : \RectorPrefix20210519\Symfony\Component\Yaml\Parser
2021-05-12 15:47:19 +00:00
{
if ( ! $this -> parser ) {
2021-05-19 00:12:47 +00:00
$this -> parser = new \RectorPrefix20210519\Symfony\Component\Yaml\Parser ();
2021-05-12 15:47:19 +00:00
}
return $this -> parser ;
}
private function getDirectoryIterator ( string $directory ) : iterable
{
$default = function ( $directory ) {
return new \RecursiveIteratorIterator ( new \RecursiveDirectoryIterator ( $directory , \FilesystemIterator :: SKIP_DOTS | \FilesystemIterator :: FOLLOW_SYMLINKS ), \RecursiveIteratorIterator :: LEAVES_ONLY );
};
if ( null !== $this -> directoryIteratorProvider ) {
return ( $this -> directoryIteratorProvider )( $directory , $default );
}
return $default ( $directory );
}
private function isReadable ( string $fileOrDirectory ) : bool
{
$default = function ( $fileOrDirectory ) {
return \is_readable ( $fileOrDirectory );
};
if ( null !== $this -> isReadableProvider ) {
return ( $this -> isReadableProvider )( $fileOrDirectory , $default );
}
return $default ( $fileOrDirectory );
}
}