diff --git a/scripts/add-headers.php b/scripts/add-headers.php new file mode 100644 index 0000000..fe101e7 --- /dev/null +++ b/scripts/add-headers.php @@ -0,0 +1,137 @@ + line, + * and inserts a file-level docblock in the required format. + * + * Run: php scripts/add-headers.php + * Run again to verify idempotency (second run = no diff). + */ + +declare(strict_types=1); + +$repoRoot = __DIR__ . '/..'; +$copyrightLine = '@copyright 2026 Joe Huss '; +$headerTemplate = <<<'PHPBLOCK' + +/** + * . + * + * @copyright 2026 Joe Huss + * @license MIT + */ +PHPBLOCK; + +// One-line descriptions per file (best-effort; empty string is acceptable per spec). +$descriptions = [ + 'src/HelloMetadataProvider.php' => 'Reference metadata-provider plugin for Phlix', + 'tests/HelloMetadataProviderTest.php' => 'Smoke tests for HelloMetadataProvider', + 'tests/bootstrap.php' => 'Test bootstrap for the plugin PHPUnit suite', +]; + +$changed = 0; +$skipped = 0; + +foreach (getPHPFiles($repoRoot) as $file) { + $relativePath = substr($file, strlen($repoRoot) + 1); + + // Skip files that already carry the copyright line. + $content = file_get_contents($file); + if (str_contains($content, $copyrightLine)) { + echo "SKIP {$relativePath} (already has copyright)\n"; + $skipped++; + continue; + } + + // Build the specific docblock for this file. + $description = $descriptions[$relativePath] ?? inferDescription($file, $relativePath); + $docblock = str_replace('.', $description . '.', $headerTemplate); + + // Insert after Absolute file paths. + */ +function getPHPFiles(string $repoRoot): array +{ + $dirs = ['src', 'tests']; + $excludes = ['vendor', '.git', 'generated', 'node_modules', '.phpunit.cache']; + + $files = []; + foreach ($dirs as $dir) { + $path = $repoRoot . '/' . $dir; + if (!is_dir($path)) { + continue; + } + collect($path, $files, $excludes); + } + + sort($files); + return $files; +} + +function collect(string $dir, array &$files, array $excludes): void +{ + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS), + RecursiveIteratorIterator::SELF_FIRST + ); + + foreach ($iterator as $node) { + if ($node->isDir()) { + $basename = $node->getBasename(); + if (in_array($basename, $excludes, true)) { + $iterator->setFlags(RecursiveIteratorIterator::CATCH_GET_CHILD); + } + if (in_array($basename, $excludes, true)) { + continue; + } + } + + if ($node->isFile() && $node->getExtension() === 'php') { + $files[] = $node->getPathname(); + } + } +} + +/** + * Best-effort one-line description derived from namespace / class name. + */ +function inferDescription(string $file, string $relativePath): string +{ + $content = file_get_contents($file); + + // Try to extract namespace or class name. + if (preg_match('/^namespace\s+([^;]+);/m', $content, $nsMatch)) { + $parts = explode('\\', $nsMatch[1]); + $last = end($parts); + if ($last !== '') { + return 'Phlix plugin component: ' . $last; + } + } + + if (preg_match('/(?:final\s+)?class\s+(\S+)/', $content, $classMatch)) { + return 'Phlix plugin component: ' . $classMatch[1]; + } + + return 'Phlix plugin source file.'; +} diff --git a/src/HelloMetadataProvider.php b/src/HelloMetadataProvider.php index ca429ce..bfb39f3 100644 --- a/src/HelloMetadataProvider.php +++ b/src/HelloMetadataProvider.php @@ -1,5 +1,12 @@ + * @license MIT + */ + declare(strict_types=1); namespace Phlix\PluginExample; diff --git a/tests/HelloMetadataProviderTest.php b/tests/HelloMetadataProviderTest.php index c63365c..d2318c0 100644 --- a/tests/HelloMetadataProviderTest.php +++ b/tests/HelloMetadataProviderTest.php @@ -1,5 +1,12 @@ + * @license MIT + */ + declare(strict_types=1); namespace Phlix\PluginExample\Tests; diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 5597af2..5124ba8 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,5 +1,12 @@ + * @license MIT + */ + declare(strict_types=1); /**