Skip to content

Commit d8acb68

Browse files
authored
Merge pull request #9 from smeghead/feature/add-single-and-scopes-modes
Feature/add single and scopes modes
2 parents d9c7ef8 + 72afe52 commit d8acb68

10 files changed

+283
-33
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# CHANGELOG
22

3+
### Features
4+
5+
* Added subcommand support with `single` and `scopes` modes
6+
* The `single` mode analyzes a single PHP file
7+
* The `scopes` mode supports analyzing multiple files and directories
8+
* Enhanced command line interface with help and version options
9+
310
## v0.0.3 (2025-03-20)
411

512
### Features

src/Analyze/VariableAnalyzer.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ private function analyzeFunction(Func $function): Scope
3434
$analyzedVars = [];
3535

3636
foreach ($variableNames as $variableName) {
37-
$vars = array_filter($variables, fn($variable) => $variable->name === $variableName);
37+
// array_values でインデックスを振り直す
38+
$vars = array_values(array_filter($variables, fn($variable) => $variable->name === $variableName));
3839
$variableHardUsage = $this->calcVariableHardUsage($vars);
3940
$analyzedVars[] = new AnalyzedVariable($variableName, $variableHardUsage);
4041
}

src/Command.php

Lines changed: 3 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,44 +4,15 @@
44

55
namespace Smeghead\PhpVariableHardUsage;
66

7-
use Smeghead\PhpVariableHardUsage\Analyze\VariableAnalyzer;
8-
use Smeghead\PhpVariableHardUsage\Parse\VariableParser;
9-
107
final class Command
118
{
129
/**
1310
* @param list<string> $argv
1411
*/
1512
public function run(array $argv): void
1613
{
17-
if (count($argv) < 2) {
18-
$this->printHelp();
19-
return;
20-
}
21-
22-
$filePath = $argv[1];
23-
if (!file_exists($filePath)) {
24-
echo "File not found: $filePath\n";
25-
return;
26-
}
27-
28-
$parser = new VariableParser();
29-
$content =file_get_contents($filePath);
30-
if ($content === false) {
31-
echo "Failed to read file: $filePath\n";
32-
return;
33-
}
34-
$parseResult = $parser->parse($content);
35-
$analyzer = new VariableAnalyzer($filePath, $parseResult->functions);
36-
$result = $analyzer->analyze();
37-
echo $result->format();
38-
}
39-
40-
private function printHelp(): void
41-
{
42-
echo "Usage: php bin/php-variable-hard-usage [source_file]\n";
43-
echo "Options:\n";
44-
echo " --help Display help information\n";
45-
echo " --version Show the version of the tool\n";
14+
$factory = new CommandFactory();
15+
$command = $factory->createCommand($argv);
16+
$command->execute();
4617
}
4718
}

src/Command/AbstractCommand.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Smeghead\PhpVariableHardUsage\Command;
6+
7+
abstract class AbstractCommand implements CommandInterface
8+
{
9+
private const VERSION = '0.0.3';
10+
11+
protected function printVersion(): void
12+
{
13+
echo "PHP Variable Hard Usage Analyzer, version " . self::VERSION . "\n";
14+
}
15+
16+
protected function printHelp(): void
17+
{
18+
echo "Usage: php bin/php-variable-hard-usage [command] [options]\n";
19+
echo "Commands:\n";
20+
echo " single <file> Analyze a single file\n";
21+
echo " scopes <path1> [<path2> ...] Analyze PHP files in directories or specific files\n";
22+
echo "Options:\n";
23+
echo " --help Display help information\n";
24+
echo " --version Show the version of the tool\n";
25+
}
26+
}

src/Command/CommandInterface.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Smeghead\PhpVariableHardUsage\Command;
6+
7+
interface CommandInterface
8+
{
9+
public function execute(): void;
10+
}

src/Command/HelpCommand.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Smeghead\PhpVariableHardUsage\Command;
6+
7+
final class HelpCommand extends AbstractCommand
8+
{
9+
public function execute(): void
10+
{
11+
$this->printHelp();
12+
}
13+
}

src/Command/ScopesCommand.php

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Smeghead\PhpVariableHardUsage\Command;
6+
7+
use Smeghead\PhpVariableHardUsage\Analyze\VariableAnalyzer;
8+
use Smeghead\PhpVariableHardUsage\Parse\VariableParser;
9+
10+
final class ScopesCommand extends AbstractCommand
11+
{
12+
/** @var list<string> */
13+
private array $paths;
14+
15+
/**
16+
* @param list<string> $paths ディレクトリまたはファイルのパスリスト
17+
*/
18+
public function __construct(array $paths)
19+
{
20+
$this->paths = $paths;
21+
}
22+
23+
public function execute(): void
24+
{
25+
$phpFiles = [];
26+
27+
// 各パスを処理
28+
foreach ($this->paths as $path) {
29+
if (is_dir($path)) {
30+
// ディレクトリの場合は再帰的にPHPファイルを収集
31+
$dirFiles = $this->findPhpFiles($path);
32+
$phpFiles = array_merge($phpFiles, $dirFiles);
33+
} elseif (is_file($path) && pathinfo($path, PATHINFO_EXTENSION) === 'php') {
34+
// 単一のPHPファイルの場合
35+
$phpFiles[] = $path;
36+
} else {
37+
fwrite(STDERR, "Invalid path: {$path}\n");
38+
}
39+
}
40+
41+
if (empty($phpFiles)) {
42+
fwrite(STDERR, "No PHP files found in specified paths\n");
43+
return;
44+
}
45+
46+
// 重複を削除
47+
$phpFiles = array_unique($phpFiles);
48+
49+
$results = [];
50+
foreach ($phpFiles as $file) {
51+
try {
52+
$content = file_get_contents($file);
53+
if ($content === false) {
54+
fwrite(STDERR, "Failed to read file: {$file}\n");
55+
continue;
56+
}
57+
58+
$parser = new VariableParser();
59+
$parseResult = $parser->parse($content);
60+
$analyzer = new VariableAnalyzer($file, $parseResult->functions);
61+
$results[] = $analyzer->analyze();
62+
} catch (\Exception $e) {
63+
fwrite(STDERR, "Error analyzing {$file}: {$e->getMessage()}\n");
64+
}
65+
}
66+
67+
// 複数ファイルの結果をまとめて表示
68+
$this->printResults($results);
69+
}
70+
71+
/**
72+
* @return list<string>
73+
*/
74+
private function findPhpFiles(string $directory): array
75+
{
76+
$result = [];
77+
$files = new \RecursiveIteratorIterator(
78+
new \RecursiveDirectoryIterator($directory, \RecursiveDirectoryIterator::SKIP_DOTS)
79+
);
80+
81+
/** @var \SplFileInfo $file */
82+
foreach ($files as $file) {
83+
if ($file->isFile() && $file->getExtension() === 'php') {
84+
$result[] = $file->getPathname();
85+
}
86+
}
87+
88+
return $result;
89+
}
90+
91+
/**
92+
* @param list<\Smeghead\PhpVariableHardUsage\Analyze\AnalysisResult> $results
93+
*/
94+
private function printResults(array $results): void
95+
{
96+
// スコープベースのレポートを生成
97+
$allScopes = [];
98+
foreach ($results as $result) {
99+
foreach ($result->scopes as $scope) {
100+
$allScopes[] = [
101+
'file' => $result->filename,
102+
'namespace' => $scope->namespace,
103+
'name' => $scope->name,
104+
'variableHardUsage' => $scope->getVariableHardUsage()
105+
];
106+
}
107+
}
108+
109+
// 酷使度でソート
110+
usort($allScopes, function ($a, $b) {
111+
return $b['variableHardUsage'] <=> $a['variableHardUsage'];
112+
});
113+
114+
// 結果を表示
115+
echo json_encode(['scopes' => $allScopes], JSON_PRETTY_PRINT) . PHP_EOL;
116+
}
117+
}

src/Command/SingleCommand.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Smeghead\PhpVariableHardUsage\Command;
6+
7+
use Smeghead\PhpVariableHardUsage\Analyze\VariableAnalyzer;
8+
use Smeghead\PhpVariableHardUsage\Parse\VariableParser;
9+
10+
final class SingleCommand extends AbstractCommand
11+
{
12+
private string $filePath;
13+
14+
public function __construct(string $filePath)
15+
{
16+
$this->filePath = $filePath;
17+
}
18+
19+
public function execute(): void
20+
{
21+
if (!file_exists($this->filePath)) {
22+
fwrite(STDERR, "File not found: {$this->filePath}\n");
23+
return;
24+
}
25+
26+
$parser = new VariableParser();
27+
$content = file_get_contents($this->filePath);
28+
if ($content === false) {
29+
fwrite(STDERR, "Failed to read file: {$this->filePath}\n");
30+
return;
31+
}
32+
33+
$parseResult = $parser->parse($content);
34+
$analyzer = new VariableAnalyzer($this->filePath, $parseResult->functions);
35+
$result = $analyzer->analyze();
36+
echo $result->format();
37+
}
38+
}

src/Command/VersionCommand.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Smeghead\PhpVariableHardUsage\Command;
6+
7+
final class VersionCommand extends AbstractCommand
8+
{
9+
public function execute(): void
10+
{
11+
$this->printVersion();
12+
}
13+
}

src/CommandFactory.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Smeghead\PhpVariableHardUsage;
6+
7+
use Smeghead\PhpVariableHardUsage\Command\CommandInterface;
8+
use Smeghead\PhpVariableHardUsage\Command\HelpCommand;
9+
use Smeghead\PhpVariableHardUsage\Command\SingleCommand;
10+
use Smeghead\PhpVariableHardUsage\Command\ScopesCommand;
11+
use Smeghead\PhpVariableHardUsage\Command\VersionCommand;
12+
13+
final class CommandFactory
14+
{
15+
/**
16+
* @param list<string> $argv
17+
*/
18+
public function createCommand(array $argv): CommandInterface
19+
{
20+
if (count($argv) < 2) {
21+
return new HelpCommand();
22+
}
23+
24+
$arg = $argv[1];
25+
26+
if ($arg === '--help') {
27+
return new HelpCommand();
28+
}
29+
30+
if ($arg === '--version') {
31+
return new VersionCommand();
32+
}
33+
34+
if ($arg === 'single') {
35+
if (count($argv) < 3) {
36+
fwrite(STDERR, "Usage: php bin/php-variable-hard-usage single <file>\n");
37+
return new HelpCommand();
38+
}
39+
return new SingleCommand($argv[2]);
40+
}
41+
42+
if ($arg === 'scopes') {
43+
if (count($argv) < 3) {
44+
fwrite(STDERR, "Usage: php bin/php-variable-hard-usage scopes <path1> [<path2> ...]\n");
45+
return new HelpCommand();
46+
}
47+
// 複数のパスを渡す
48+
return new ScopesCommand(array_slice($argv, 2));
49+
}
50+
51+
// 後方互換性のため、コマンドが指定されていない場合は単一ファイルモードとして扱う
52+
return new SingleCommand($argv[1]);
53+
}
54+
}

0 commit comments

Comments
 (0)