-
Notifications
You must be signed in to change notification settings - Fork 4
Rework CLI input and scope handling #33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,14 +7,14 @@ | |
| use DocbookCS\Config\ConfigData; | ||
| use DocbookCS\Config\ConfigParser; | ||
| use DocbookCS\Config\ConfigParserException; | ||
| use DocbookCS\Diff\DiffParser; | ||
| use DocbookCS\Progress\ConsoleProgress; | ||
| use DocbookCS\Progress\NullProgress; | ||
| use DocbookCS\Progress\ProgressInterface; | ||
| use DocbookCS\Report\Reporter\CheckstyleReporter; | ||
| use DocbookCS\Report\Reporter\ConsoleReporter; | ||
| use DocbookCS\Report\Reporter\JsonReporter; | ||
| use DocbookCS\Report\Reporter\ReporterInterface; | ||
| use DocbookCS\Runner\RunPlanner; | ||
| use DocbookCS\Runner\SniffRunner; | ||
|
|
||
| final class Application | ||
|
|
@@ -32,29 +32,64 @@ final class Application | |
| /** @var resource */ | ||
| private $stderr; | ||
|
|
||
| /** @var resource */ | ||
| private $stdin; | ||
| private ?string $stdin; | ||
|
|
||
| /** | ||
| * @param list<string> $argv | ||
| * @throws \RuntimeException if redirected stdin cannot be read. | ||
| * @api | ||
| */ | ||
| public static function fromGlobals(array $argv): self | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| { | ||
| $stdin = null; | ||
| $stat = fstat(STDIN); | ||
|
|
||
| if ($stat === false) { | ||
| return new self($argv, stdin: $stdin); | ||
| } | ||
|
|
||
| $type = $stat['mode'] & 0170000; | ||
|
|
||
| if ($type === 0010000 || $type === 0100000) { | ||
| $stdin = stream_get_contents(STDIN); | ||
|
|
||
| if ($stdin === false) { | ||
| throw new \RuntimeException('Could not read diff from stdin.'); | ||
| } | ||
| } | ||
|
|
||
| return new self($argv, stdin: $stdin); | ||
| } | ||
|
|
||
| /** | ||
| * @param list<string> $argv | ||
| * @param ?resource $stdout | ||
| * @param ?resource $stderr | ||
| * @param ?resource $stdin | ||
| */ | ||
| public function __construct(array $argv, mixed $stdout = null, mixed $stderr = null, mixed $stdin = null) | ||
| { | ||
| public function __construct( | ||
| array $argv, | ||
| mixed $stdout = null, | ||
| mixed $stderr = null, | ||
| ?string $stdin = null, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Having |
||
| ) { | ||
| $this->argv = $argv; | ||
| $this->stdout = $stdout ?? STDOUT; | ||
| $this->stderr = $stderr ?? STDERR; | ||
| $this->stdin = $stdin ?? STDIN; | ||
| $this->stdin = $stdin; | ||
| } | ||
|
|
||
| /** | ||
| * @return int Exit code (0 = success, 1 = violations found, 2 = runtime error). | ||
| */ | ||
| public function run(): int | ||
| { | ||
| $options = $this->parseArgv(); | ||
| try { | ||
| $options = $this->parseArgv(); | ||
| } catch (\InvalidArgumentException $e) { | ||
| $this->writeError('Error: ' . $e->getMessage() . PHP_EOL); | ||
|
|
||
| return 2; | ||
| } | ||
|
|
||
| if ($options['help']) { | ||
| $this->printHelp(); | ||
|
|
@@ -76,31 +111,19 @@ public function run(): int | |
| return 2; | ||
| } | ||
|
|
||
| $overridePaths = $options['paths'] !== [] ? $options['paths'] : null; | ||
|
|
||
| // If override paths are relative, resolve them against cwd. | ||
| if ($overridePaths !== null) { | ||
| $overridePaths = $this->resolveOverridePaths($overridePaths); | ||
| } | ||
|
|
||
| $diff = null; | ||
|
|
||
| if ($options['diff'] !== null) { | ||
| try { | ||
| $diffContent = $this->readDiff($options['diff']); | ||
| $diff = (new DiffParser())->parse($diffContent); | ||
| } catch (\Throwable $e) { | ||
| $this->writeError('Error reading diff: ' . $e->getMessage() . PHP_EOL); | ||
| try { | ||
| $runPlan = new RunPlanner($config, $options['wide'])->plan($options['paths'], $this->stdin); | ||
| } catch (\Throwable $e) { | ||
| $this->writeError('Error resolving input: ' . $e->getMessage() . PHP_EOL); | ||
|
|
||
| return 2; | ||
| } | ||
| return 2; | ||
| } | ||
|
|
||
| $progress = $this->createProgress($options); | ||
|
|
||
| try { | ||
| $runner = new SniffRunner($progress); | ||
| $report = $runner->run($config, $overridePaths, $diff); | ||
| $report = $runner->run($runPlan); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } catch (\Throwable $e) { | ||
| $this->writeError('Runtime error: ' . $e->getMessage() . PHP_EOL); | ||
|
|
||
|
|
@@ -118,27 +141,6 @@ public function run(): int | |
| return (int) $report->hasViolations(); | ||
| } | ||
|
|
||
| /** | ||
| * @param list<string> $paths | ||
| * @return list<string> | ||
| */ | ||
| private function resolveOverridePaths(array $paths): array | ||
| { | ||
| $cwd = getcwd() ?: '.'; | ||
| $resolved = []; | ||
|
|
||
| foreach ($paths as $path) { | ||
| if (str_starts_with($path, '/') || preg_match('#^[a-zA-Z]:[/\\\\]#', $path)) { | ||
| $resolved[] = $path; | ||
| continue; | ||
| } | ||
|
|
||
| $resolved[] = $cwd . '/' . $path; | ||
| } | ||
|
|
||
| return $resolved; | ||
| } | ||
|
|
||
| /** | ||
| * @return array{ | ||
| * help: bool, | ||
|
|
@@ -148,9 +150,10 @@ private function resolveOverridePaths(array $paths): array | |
| * colors: bool, | ||
| * quiet: bool, | ||
| * paths: list<string>, | ||
| * diff: string|null, | ||
| * wide: bool, | ||
| * perf: bool, | ||
| * } | ||
| * @throws \InvalidArgumentException for unsupported options. | ||
| */ | ||
| private function parseArgv(): array | ||
| { | ||
|
|
@@ -162,7 +165,7 @@ private function parseArgv(): array | |
| 'colors' => $this->detectColorSupport(), | ||
| 'quiet' => false, | ||
| 'paths' => [], | ||
| 'diff' => null, | ||
| 'wide' => false, | ||
| 'perf' => false, | ||
| ]; | ||
|
|
||
|
|
@@ -227,57 +230,31 @@ private function parseArgv(): array | |
| continue; | ||
| } | ||
|
|
||
| // --diff = read from stdin | ||
| // --diff=FILE = read from file | ||
| // --diff=- = read from stdin (explicit) | ||
| if ($arg === '--diff') { | ||
| $result['diff'] = ''; | ||
| $i++; | ||
| continue; | ||
| } | ||
|
|
||
| if (str_starts_with($arg, '--diff=')) { | ||
| $result['diff'] = substr($arg, 7); | ||
| if ($arg === '--perf') { | ||
| $result['perf'] = true; | ||
| $i++; | ||
| continue; | ||
| } | ||
|
|
||
| if ($arg === '--perf') { | ||
| $result['perf'] = true; | ||
| if ($arg === '--wide') { | ||
| $result['wide'] = true; | ||
| $i++; | ||
| continue; | ||
| } | ||
|
|
||
| // Anything else is a path to scan. | ||
| if (!str_starts_with($arg, '-')) { | ||
| $result['paths'][] = $arg; | ||
| $i++; | ||
| continue; | ||
| } | ||
|
|
||
| $i++; | ||
| throw new \InvalidArgumentException(sprintf('Unknown option: %s', $arg)); | ||
| } | ||
|
|
||
| return $result; | ||
| } | ||
|
|
||
| /** @throws \RuntimeException if the source cannot be read. */ | ||
| private function readDiff(string $source): string | ||
| { | ||
| if ($source === '' || $source === '-') { | ||
| $content = stream_get_contents($this->stdin); | ||
| if ($content === false) { | ||
| throw new \RuntimeException('Could not read diff from stdin.'); // @codeCoverageIgnore | ||
| } | ||
| return $content; | ||
| } | ||
|
|
||
| $content = @file_get_contents($source); | ||
| if ($content === false) { | ||
| throw new \RuntimeException(sprintf('Could not read diff file: %s', $source)); | ||
| } | ||
|
|
||
| return $content; | ||
| } | ||
|
|
||
| /** @param array{report: string, quiet: bool, colors: bool} $options */ | ||
| private function createProgress(array $options): ProgressInterface | ||
| { | ||
|
|
@@ -382,22 +359,20 @@ private function printHelp(): void | |
| --report=<format> Output format: console (default), checkstyle, json. | ||
| --colors Force ANSI color output. | ||
| --no-colors Disable ANSI color output. | ||
| --diff[=<file>] Restrict analysis to files changed in a unified diff. | ||
| Omit the value or pass "-" to read the diff from stdin. | ||
| Violations are only reported when the violating element | ||
| is on or contains a changed line (parent-context aware). | ||
| --wide Check whole selected files and recursively include | ||
| referenced XML files. | ||
|
|
||
| Arguments: | ||
| <file-or-directory> One or more files or directories to scan. | ||
| If omitted, the paths from the config file are used. | ||
| Paths cannot be combined with diff input. | ||
|
|
||
| Examples: | ||
| docbook-cs | ||
| docbook-cs --config=myconfig.xml reference/ | ||
| docbook-cs --report=checkstyle --no-colors > report.xml | ||
| docbook-cs reference/strings/functions/strlen.xml | ||
| git diff HEAD | docbook-cs --diff --report=checkstyle | ||
| docbook-cs --diff=changes.patch --report=json | ||
| git diff HEAD | docbook-cs | ||
| git diff HEAD | docbook-cs --wide --report=checkstyle | ||
|
|
||
| HELP; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace DocbookCS\Diff; | ||
|
|
||
| interface DiffProviderInterface | ||
| { | ||
| public function for(string $workingDirectory): string; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned in an earlier PR, I prefer to have this in Unit as well.