Skip to content

Commit

Permalink
ref: use symfony command
Browse files Browse the repository at this point in the history
  • Loading branch information
kauffinger committed Feb 18, 2025
1 parent 74e8a77 commit 6853b81
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 57 deletions.
7 changes: 6 additions & 1 deletion bin/codemap
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,10 @@ declare(strict_types=1);
require __DIR__ . '/../vendor/autoload.php';

use Kauffinger\Codemap\Console\CodemapCommand;
use Symfony\Component\Console\Application;

exit((new CodemapCommand())->__invoke($argv));
$application = new Application('php-codemap', '1.0.0');
$command = new CodemapCommand();
$application->add($command);
$application->setDefaultCommand($command->getName(), true);
$application->run();
5 changes: 3 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
],
"require": {
"php": "^8.3.0",
"nikic/php-parser": "^5.4"
"nikic/php-parser": "^5.4",
"symfony/console": "^7.0"
},
"require-dev": {
"laravel/pint": "^1.18.1",
Expand Down Expand Up @@ -57,4 +58,4 @@
"@test:unit"
]
}
}
}
121 changes: 79 additions & 42 deletions src/Console/CodemapCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,66 @@
use Kauffinger\Codemap\Enum\PhpVersion;
use Kauffinger\Codemap\Formatter\TextCodemapFormatter;
use Kauffinger\Codemap\Generator\CodemapGenerator;

final class CodemapCommand
use Override;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
* Generates a codemap of PHP code, scanning specified paths and writing the output to a file or stdout.
*/
final class CodemapCommand extends Command
{
/**
* Invokes the codemap command.
*
* @param string[] $commandArguments The command-line arguments (if any).
* @return int Exit code.
* Configures the command with arguments and options.
*/
public function __invoke(array $commandArguments): int
#[Override]
protected function configure(): void
{
// The first element is usually the script name, so remove it
array_shift($commandArguments);
$this
->setName('codemap')
->setDescription('Generate a codemap of PHP code')
->addArgument('paths', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Paths to scan', [])
->addOption('output', 'o', InputOption::VALUE_REQUIRED, 'Output file path (use "-" for stdout)', 'codemap.txt')
->addOption('php-version', null, InputOption::VALUE_REQUIRED, 'PHP version to use for parsing (e.g., "8.3")');
}

// Attempt to load codemap.php from project root:
/**
* Executes the codemap generation process.
*
* @param InputInterface $input The command input
* @param OutputInterface $output The command output
* @return int Exit code (0 for success, 1 for failure)
*/
#[Override]
protected function execute(InputInterface $input, OutputInterface $output): int
{
$codemapConfigFilePath = __DIR__.'/../../codemap.php';
$configuredScanPaths = [];
$configuredPhpVersion = null;

// Check if codemap.php exists, otherwise attempt to parse composer.json
if (! file_exists($codemapConfigFilePath)) {
// Attempt to parse composer.json
if (file_exists($codemapConfigFilePath)) {
$codemapConfiguration = require $codemapConfigFilePath;
if ($codemapConfiguration instanceof CodemapConfig) {
$configuredScanPaths = $codemapConfiguration->getScanPaths();
$configuredPhpVersion = $codemapConfiguration->getConfiguredPhpVersion();
}
} else {
// Attempt to parse composer.json for PHP version
$composerJsonPath = __DIR__.'/../../composer.json';
$composerContents = file_get_contents($composerJsonPath) ?: '';
$composerData = json_decode($composerContents, true);
$composerPhpVersionString = $composerData['require']['php'] ?? '^8.4.0';

Check failure on line 62 in src/Console/CodemapCommand.php

View workflow job for this annotation

GitHub Actions / Formats P8.3 - ubuntu-latest - prefer-lowest

Cannot access offset 'php' on mixed.

Check failure on line 62 in src/Console/CodemapCommand.php

View workflow job for this annotation

GitHub Actions / Formats P8.3 - ubuntu-latest - prefer-lowest

Cannot access offset 'require' on mixed.

Check failure on line 62 in src/Console/CodemapCommand.php

View workflow job for this annotation

GitHub Actions / Formats P8.3 - ubuntu-latest - prefer-stable

Cannot access offset 'php' on mixed.

Check failure on line 62 in src/Console/CodemapCommand.php

View workflow job for this annotation

GitHub Actions / Formats P8.3 - ubuntu-latest - prefer-stable

Cannot access offset 'require' on mixed.

// Extract the minor version from something like ^8.3.0
// Extract minor version (e.g., "8.4")
$parsedComposerVersion = '8.4';
if (preg_match('/(\d+\.\d+)/', (string) $composerPhpVersionString, $matches)) {

Check failure on line 66 in src/Console/CodemapCommand.php

View workflow job for this annotation

GitHub Actions / Formats P8.3 - ubuntu-latest - prefer-lowest

Cannot cast mixed to string.

Check failure on line 66 in src/Console/CodemapCommand.php

View workflow job for this annotation

GitHub Actions / Formats P8.3 - ubuntu-latest - prefer-stable

Cannot cast mixed to string.
$parsedComposerVersion = $matches[1]; // e.g. "8.3"
$parsedComposerVersion = $matches[1];
}

// Map parsedComposerVersion to our enum
// Map to PhpVersion enum
$mappedPhpVersion = PhpVersion::PHP_8_4;
foreach (PhpVersion::cases() as $phpVersionCase) {
if ($phpVersionCase->value === $parsedComposerVersion) {
Expand All @@ -50,47 +76,48 @@ public function __invoke(array $commandArguments): int
}
}

// Create a default codemap.php
// Generate and write default config
$generatedDefaultConfig = $this->generateDefaultConfig($mappedPhpVersion);

file_put_contents($codemapConfigFilePath, $generatedDefaultConfig);
echo "Created default codemap config at: {$codemapConfigFilePath}".PHP_EOL;
$output->writeln('<info>Created default codemap config at: '.$codemapConfigFilePath.'</info>');
$configuredScanPaths = [__DIR__.'/../../src'];
$configuredPhpVersion = $mappedPhpVersion;
}

if (file_exists($codemapConfigFilePath)) {
$codemapConfiguration = require $codemapConfigFilePath;
if ($codemapConfiguration instanceof CodemapConfig) {
$configuredScanPaths = $codemapConfiguration->getScanPaths();
$configuredPhpVersion = $codemapConfiguration->getConfiguredPhpVersion();
}
// Determine scan paths: CLI arguments or configured defaults
$paths = $input->getArgument('paths');
if (empty($paths)) {
$paths = $configuredScanPaths;
}

// If no CLI paths are provided, default to config paths or fallback to src folder
if ($commandArguments === []) {
$commandArguments = $configuredScanPaths === [] ? [__DIR__.'/../../src'] : $configuredScanPaths;
// Determine PHP version: CLI option or configured default
$phpVersionString = $input->getOption('php-version');
if ($phpVersionString) {
$phpVersion = PhpVersion::tryFrom($phpVersionString);

Check failure on line 96 in src/Console/CodemapCommand.php

View workflow job for this annotation

GitHub Actions / Formats P8.3 - ubuntu-latest - prefer-lowest

Parameter #1 $value of static method Kauffinger\Codemap\Enum\PhpVersion::tryFrom() expects int|string, mixed given.

Check failure on line 96 in src/Console/CodemapCommand.php

View workflow job for this annotation

GitHub Actions / Formats P8.3 - ubuntu-latest - prefer-stable

Parameter #1 $value of static method Kauffinger\Codemap\Enum\PhpVersion::tryFrom() expects int|string, mixed given.
if (! $phpVersion instanceof PhpVersion) {
$output->writeln('<error>Invalid PHP version: '.$phpVersionString.'</error>');

return Command::FAILURE;
}
} else {
$phpVersion = $configuredPhpVersion;
}

$targetScanPaths = $commandArguments;
$outputFilePath = __DIR__.'/../../codemap.txt';
$outputFile = $input->getOption('output');

$codemapGenerator = new CodemapGenerator;

// If a configured PHP version is specified, map it to PhpParser's version if possible.
if ($configuredPhpVersion instanceof PhpVersion) {
$codemapGenerator->setPhpParserVersion(\PhpParser\PhpVersion::fromString($configuredPhpVersion->value));
if ($phpVersion instanceof PhpVersion) {
$codemapGenerator->setPhpParserVersion(\PhpParser\PhpVersion::fromString($phpVersion->value));
}

$aggregatedCodemapResults = [];

foreach ($targetScanPaths as $scanPath) {
foreach ($paths as $scanPath) {

Check failure on line 114 in src/Console/CodemapCommand.php

View workflow job for this annotation

GitHub Actions / Formats P8.3 - ubuntu-latest - prefer-lowest

Argument of an invalid type mixed supplied for foreach, only iterables are supported.

Check failure on line 114 in src/Console/CodemapCommand.php

View workflow job for this annotation

GitHub Actions / Formats P8.3 - ubuntu-latest - prefer-stable

Argument of an invalid type mixed supplied for foreach, only iterables are supported.
if (! file_exists($scanPath)) {

Check failure on line 115 in src/Console/CodemapCommand.php

View workflow job for this annotation

GitHub Actions / Formats P8.3 - ubuntu-latest - prefer-lowest

Parameter #1 $filename of function file_exists expects string, mixed given.

Check failure on line 115 in src/Console/CodemapCommand.php

View workflow job for this annotation

GitHub Actions / Formats P8.3 - ubuntu-latest - prefer-stable

Parameter #1 $filename of function file_exists expects string, mixed given.
echo "Warning: Path '$scanPath' does not exist.".PHP_EOL;
$output->writeln('<error>Warning: Path \''.$scanPath.'\' does not exist.</error>');

continue;
}

$fileResults = $codemapGenerator->generate($scanPath);

Check failure on line 120 in src/Console/CodemapCommand.php

View workflow job for this annotation

GitHub Actions / Formats P8.3 - ubuntu-latest - prefer-lowest

Parameter #1 $pathToScan of method Kauffinger\Codemap\Generator\CodemapGenerator::generate() expects string, mixed given.

Check failure on line 120 in src/Console/CodemapCommand.php

View workflow job for this annotation

GitHub Actions / Formats P8.3 - ubuntu-latest - prefer-stable

Parameter #1 $pathToScan of method Kauffinger\Codemap\Generator\CodemapGenerator::generate() expects string, mixed given.

foreach ($fileResults as $fileName => $codemapDto) {
$aggregatedCodemapResults[$fileName] = $codemapDto;
}
Expand All @@ -99,12 +126,22 @@ public function __invoke(array $commandArguments): int
$formatter = new TextCodemapFormatter;
$formattedCodemapOutput = $formatter->format($aggregatedCodemapResults);

file_put_contents($outputFilePath, $formattedCodemapOutput);
echo "Codemap generated at: {$outputFilePath}".PHP_EOL;
if ($outputFile === '-') {
$output->write($formattedCodemapOutput);
} else {
file_put_contents($outputFile, $formattedCodemapOutput);

Check failure on line 132 in src/Console/CodemapCommand.php

View workflow job for this annotation

GitHub Actions / Formats P8.3 - ubuntu-latest - prefer-lowest

Parameter #1 $filename of function file_put_contents expects string, mixed given.

Check failure on line 132 in src/Console/CodemapCommand.php

View workflow job for this annotation

GitHub Actions / Formats P8.3 - ubuntu-latest - prefer-stable

Parameter #1 $filename of function file_put_contents expects string, mixed given.
$output->writeln('<info>Codemap generated at: '.$outputFile.'</info>');
}

return 0;
return Command::SUCCESS;
}

/**
* Generates the content for a default codemap configuration file.
*
* @param PhpVersion $mappedPhpVersion The PHP version to include in the config
* @return string The configuration file content
*/
private function generateDefaultConfig(PhpVersion $mappedPhpVersion): string
{
return <<<PHP
Expand Down
34 changes: 22 additions & 12 deletions tests/Feature/CodemapCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
declare(strict_types=1);

use Kauffinger\Codemap\Console\CodemapCommand;
use Symfony\Component\Console\Tester\CommandTester;

test('CodemapCommand runs without error and generates codemap.txt', function (): void {
// Use a temp directory for output
Expand All @@ -21,24 +22,33 @@ public function hello(): string {
}
PHP);

// Mock CLI arguments (script name + path to scan)
$args = [
'codemap', // script name (ignored in the command)
$testFile,
];
// Set the output file path within the temporary directory
$codemapFilePath = $tempDir.DIRECTORY_SEPARATOR.'codemap.txt';

// Call the command
$exitCode = (new CodemapCommand)->__invoke($args);
// Create and set up the command tester
$command = new CodemapCommand;
$commandTester = new CommandTester($command);

// Assert command success
expect($exitCode)->toBe(0);
// Execute the command with arguments and options
$commandTester->execute([
'paths' => [$testFile],
'--output' => $codemapFilePath,
]);

// Check if codemap.txt got created (in the project root by default)
$codemapFilePath = __DIR__.'/../../codemap.txt';
// Assert command executed successfully
expect($commandTester->getStatusCode())->toBe(0);

// Check if codemap.txt was created
expect(file_exists($codemapFilePath))->toBeTrue();

// Verify the content of the generated codemap
$codemapContent = file_get_contents($codemapFilePath);
expect($codemapContent)->toContain('File: TestClass.php')
->toContain('Class: TestClass')
->toContain('public function hello(): string');

// Clean up
unlink($testFile);
rmdir($tempDir);
unlink($codemapFilePath);
rmdir($tempDir);
});

0 comments on commit 6853b81

Please sign in to comment.