impoved yaml linter to be more use built-in grav for more detail

Signed-off-by: Andy Miller <rhuk@mac.com>
This commit is contained in:
Andy Miller
2026-02-05 12:16:42 -07:00
parent dfdd3786cb
commit c191b0b47e
2 changed files with 78 additions and 18 deletions

View File

@@ -15,6 +15,7 @@ use Grav\Common\Utils;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use RegexIterator;
use RocketTheme\Toolbox\Compat\Yaml\Yaml as CompatYaml;
use RocketTheme\Toolbox\File\MarkdownFile;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use Symfony\Component\Yaml\Yaml;
@@ -30,18 +31,19 @@ class YamlLinter
* @param callable|null $callback Optional callback for progress: function(string $file, bool $success, ?string $error)
* @return array
*/
public static function lint(?string $folder = null, ?callable $callback = null)
public static function lint(?string $folder = null, ?callable $callback = null, bool $strict = false)
{
if (null !== $folder) {
$folder = $folder ?: GRAV_ROOT;
return static::recurseFolder($folder, '(md|yaml)', $callback);
return static::recurseFolder($folder, '(md|yaml)', $callback, $strict);
}
return array_merge(
static::lintConfig($callback),
static::lintPages($callback),
static::lintBlueprints($callback)
static::lintConfig($callback, $strict),
static::lintPages($callback, $strict),
static::lintBlueprints($callback, $strict),
static::lintEnvironments($callback, $strict)
);
}
@@ -49,25 +51,58 @@ class YamlLinter
* @param callable|null $callback Optional callback for progress: function(string $file, bool $success, ?string $error)
* @return array
*/
public static function lintPages(?callable $callback = null)
public static function lintPages(?callable $callback = null, bool $strict = false)
{
return static::recurseFolder('page://', '(md|yaml)', $callback);
return static::recurseFolder('page://', '(md|yaml)', $callback, $strict);
}
/**
* @param callable|null $callback Optional callback for progress: function(string $file, bool $success, ?string $error)
* @param bool $strict Use the stricter Compat YAML parser (matches runtime behavior)
* @return array
*/
public static function lintConfig(?callable $callback = null)
public static function lintConfig(?callable $callback = null, bool $strict = false)
{
return static::recurseFolder('config://', '(md|yaml)', $callback);
return static::recurseFolder('config://', '(md|yaml)', $callback, $strict);
}
/**
* @param callable|null $callback Optional callback for progress: function(string $file, bool $success, ?string $error)
* @param bool $strict Use the stricter Compat YAML parser (matches runtime behavior)
* @return array
*/
public static function lintBlueprints(?callable $callback = null)
public static function lintEnvironments(?callable $callback = null, bool $strict = false)
{
$lint_errors = [];
$user_path = GRAV_ROOT . '/' . GRAV_USER_PATH;
// Scan Grav 1.6 style: user/<hostname>/config/
foreach (glob($user_path . '/*/config', GLOB_ONLYDIR) as $envConfigDir) {
$envName = basename(dirname($envConfigDir));
// Skip known non-environment directories
if (in_array($envName, ['config', 'plugins', 'themes', 'pages', 'accounts', 'data', 'assets'])) {
continue;
}
$lint_errors = array_merge($lint_errors, static::recurseFolder($envConfigDir, '(md|yaml)', $callback, $strict));
}
// Scan Grav 1.7+ style: user/env/<hostname>/config/
$envPath = $user_path . '/env';
if (is_dir($envPath)) {
foreach (glob($envPath . '/*/config', GLOB_ONLYDIR) as $envConfigDir) {
$lint_errors = array_merge($lint_errors, static::recurseFolder($envConfigDir, '(md|yaml)', $callback, $strict));
}
}
return $lint_errors;
}
/**
* @param callable|null $callback Optional callback for progress: function(string $file, bool $success, ?string $error)
* @param bool $strict Use the stricter Compat YAML parser (matches runtime behavior)
* @return array
*/
public static function lintBlueprints(?callable $callback = null, bool $strict = false)
{
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
@@ -76,7 +111,7 @@ class YamlLinter
$theme_path = 'themes://' . $current_theme . '/blueprints';
$locator->addPath('blueprints', '', [$theme_path]);
return static::recurseFolder('blueprints://', '(md|yaml)', $callback);
return static::recurseFolder('blueprints://', '(md|yaml)', $callback, $strict);
}
/**
@@ -85,7 +120,7 @@ class YamlLinter
* @param callable|null $callback Optional callback for progress: function(string $file, bool $success, ?string $error)
* @return array
*/
public static function recurseFolder($path, $extensions = '(md|yaml)', ?callable $callback = null)
public static function recurseFolder($path, $extensions = '(md|yaml)', ?callable $callback = null, bool $strict = false)
{
$lint_errors = [];
@@ -104,7 +139,12 @@ class YamlLinter
foreach ($iterator as $filepath => $file) {
$relativePath = str_replace(GRAV_ROOT, '', $filepath);
try {
Yaml::parse(static::extractYaml($filepath));
$yaml = static::extractYaml($filepath);
if ($strict) {
CompatYaml::parse($yaml);
} else {
Yaml::parse($yaml);
}
if ($callback) {
$callback($relativePath, true, null);
}

View File

@@ -39,6 +39,12 @@ class YamlLinterCommand extends GravCommand
InputOption::VALUE_OPTIONAL,
'Go through specific folder'
)
->addOption(
'strict',
's',
InputOption::VALUE_NONE,
'Use the stricter Compat YAML parser that matches runtime behavior'
)
->setDescription('Checks various files for YAML errors')
->setHelp('Checks various files for YAML errors');
}
@@ -54,6 +60,7 @@ class YamlLinterCommand extends GravCommand
$io->title('Yaml Linter');
$verbose = $io->isVerbose();
$strict = (bool) $input->getOption('strict');
$checked = 0;
$callback = $verbose ? function (string $file, bool $success, ?string $error) use ($io, &$checked) {
$checked++;
@@ -64,10 +71,14 @@ class YamlLinterCommand extends GravCommand
}
} : null;
if ($strict) {
$io->note('Using strict Compat YAML parser (matches runtime behavior)');
}
$error = 0;
if ($input->getOption('all')) {
$io->section('All');
$errors = YamlLinter::lint('', $callback);
$errors = YamlLinter::lint('', $callback, $strict);
$this->displayResult($errors, $io, 'No YAML Linting issues found', $verbose, $checked);
if (!empty($errors)) {
@@ -75,7 +86,7 @@ class YamlLinterCommand extends GravCommand
}
} elseif ($folder = $input->getOption('folder')) {
$io->section($folder);
$errors = YamlLinter::lint($folder, $callback);
$errors = YamlLinter::lint($folder, $callback, $strict);
$this->displayResult($errors, $io, 'No YAML Linting issues found', $verbose, $checked);
if (!empty($errors)) {
@@ -84,7 +95,7 @@ class YamlLinterCommand extends GravCommand
} else {
$io->section('User Configuration');
$checked = 0;
$errors = YamlLinter::lintConfig($callback);
$errors = YamlLinter::lintConfig($callback, $strict);
$this->displayResult($errors, $io, 'No YAML Linting issues with configuration', $verbose, $checked);
if (!empty($errors)) {
@@ -93,7 +104,7 @@ class YamlLinterCommand extends GravCommand
$io->section('Pages Frontmatter');
$checked = 0;
$errors = YamlLinter::lintPages($callback);
$errors = YamlLinter::lintPages($callback, $strict);
$this->displayResult($errors, $io, 'No YAML Linting issues with pages', $verbose, $checked);
if (!empty($errors)) {
@@ -102,12 +113,21 @@ class YamlLinterCommand extends GravCommand
$io->section('Page Blueprints');
$checked = 0;
$errors = YamlLinter::lintBlueprints($callback);
$errors = YamlLinter::lintBlueprints($callback, $strict);
$this->displayResult($errors, $io, 'No YAML Linting issues with blueprints', $verbose, $checked);
if (!empty($errors)) {
$error = 1;
}
$io->section('Environment Configuration');
$checked = 0;
$errors = YamlLinter::lintEnvironments($callback, $strict);
$this->displayResult($errors, $io, 'No YAML Linting issues with environment configs', $verbose, $checked);
if (!empty($errors)) {
$error = 1;
}
}
return $error;