Skip to content

Commit af323b9

Browse files
authored
Rework CLI input and scope handling (#33)
* refactor: reworked command flags, scopes, and diff detection * review: removed legacy --diff flag acceptance
1 parent a80c4c8 commit af323b9

46 files changed

Lines changed: 2448 additions & 308 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,26 @@ Register it in your config:
6363
<sniff class="Acme\DocbookSniffs\MySniff" />
6464
```
6565

66+
## CLI Scope
67+
68+
By default, DocbookCS checks the current Git diff from its upstream branch point
69+
through the working tree. Alternatively, a unified diff can be piped or file and
70+
directory paths passed. The inspection scope is limited to the given diff or the
71+
full contents of the given file paths.
72+
73+
XML references are expanded by default, but reported violations remain limited
74+
to the given scope. With `--wide`, every file inferred from paths or a diff is
75+
checked as a whole, and referenced `SYSTEM` XML files are recursively included.
76+
77+
| Input | `--wide` | Full File(s) | References |
78+
|------------|---------:|-------------:|-----------:|
79+
| none | no | no | no |
80+
| none | yes | yes | yes |
81+
| path | no | yes | no |
82+
| path | yes | yes | yes |
83+
| piped diff | no | no | no |
84+
| piped diff | yes | yes | yes |
85+
6686
## License
6787

6888
Apache 2.0

bin/docbook-cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,4 @@ use DocbookCS\Application;
2525
exit(2);
2626
})();
2727

28-
new Application($argv ?? [])->run();
28+
exit(Application::withArguments($argv ?? [])->run());

phpstan.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ parameters:
77

88
paths:
99
- bin/
10+
- bin/docbook-cs # extension-less file
1011
- src/
1112
- tests/
1213

phpunit.xml.dist

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@
1818
<testsuite name="unit">
1919
<directory>tests/Unit</directory>
2020
</testsuite>
21-
<testsuite name="integration">
22-
<directory>tests/Integration</directory>
23-
</testsuite>
2421
</testsuites>
2522

2623
<source restrictNotices="true" restrictWarnings="true">

src/Application.php

Lines changed: 60 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@
77
use DocbookCS\Config\ConfigData;
88
use DocbookCS\Config\ConfigParser;
99
use DocbookCS\Config\ConfigParserException;
10-
use DocbookCS\Diff\DiffParser;
1110
use DocbookCS\Progress\ConsoleProgress;
1211
use DocbookCS\Progress\NullProgress;
1312
use DocbookCS\Progress\ProgressInterface;
1413
use DocbookCS\Report\Reporter\CheckstyleReporter;
1514
use DocbookCS\Report\Reporter\ConsoleReporter;
1615
use DocbookCS\Report\Reporter\JsonReporter;
1716
use DocbookCS\Report\Reporter\ReporterInterface;
17+
use DocbookCS\Runner\RunPlanner;
1818
use DocbookCS\Runner\SniffRunner;
1919

2020
final class Application
@@ -23,38 +23,65 @@ final class Application
2323

2424
private const string DEFAULT_CONFIG = 'docbookcs.xml';
2525

26-
/** @var list<string> */
27-
private array $argv;
28-
2926
/** @var resource */
3027
private $stdout;
3128

3229
/** @var resource */
3330
private $stderr;
3431

35-
/** @var resource */
36-
private $stdin;
32+
/**
33+
* @param list<string> $argv
34+
* @throws \RuntimeException if redirected stdin cannot be read.
35+
*/
36+
public static function withArguments(array $argv): self
37+
{
38+
$stdin = null;
39+
$stat = fstat(STDIN);
40+
41+
if ($stat === false) {
42+
return new self($argv, unifiedDiff: $stdin);
43+
}
44+
45+
$type = $stat['mode'] & 0170000;
46+
47+
if ($type === 0010000 || $type === 0100000) {
48+
$stdin = stream_get_contents(STDIN);
49+
50+
if ($stdin === false) {
51+
throw new \RuntimeException('Could not read diff from stdin.');
52+
}
53+
}
54+
55+
return new self($argv, unifiedDiff: $stdin);
56+
}
3757

3858
/**
3959
* @param list<string> $argv
4060
* @param ?resource $stdout
4161
* @param ?resource $stderr
42-
* @param ?resource $stdin
4362
*/
44-
public function __construct(array $argv, mixed $stdout = null, mixed $stderr = null, mixed $stdin = null)
45-
{
46-
$this->argv = $argv;
63+
public function __construct(
64+
private readonly array $argv,
65+
mixed $stdout = null,
66+
mixed $stderr = null,
67+
private readonly ?string $unifiedDiff = null,
68+
) {
4769
$this->stdout = $stdout ?? STDOUT;
4870
$this->stderr = $stderr ?? STDERR;
49-
$this->stdin = $stdin ?? STDIN;
5071
}
5172

5273
/**
5374
* @return int Exit code (0 = success, 1 = violations found, 2 = runtime error).
5475
*/
5576
public function run(): int
5677
{
57-
$options = $this->parseArgv();
78+
try {
79+
$options = $this->parseArgv();
80+
} catch (\InvalidArgumentException $e) {
81+
$this->writeError('Error: ' . $e->getMessage() . PHP_EOL);
82+
83+
return 2;
84+
}
5885

5986
if ($options['help']) {
6087
$this->printHelp();
@@ -76,31 +103,19 @@ public function run(): int
76103
return 2;
77104
}
78105

79-
$overridePaths = $options['paths'] !== [] ? $options['paths'] : null;
80-
81-
// If override paths are relative, resolve them against cwd.
82-
if ($overridePaths !== null) {
83-
$overridePaths = $this->resolveOverridePaths($overridePaths);
84-
}
85-
86-
$diff = null;
87-
88-
if ($options['diff'] !== null) {
89-
try {
90-
$diffContent = $this->readDiff($options['diff']);
91-
$diff = (new DiffParser())->parse($diffContent);
92-
} catch (\Throwable $e) {
93-
$this->writeError('Error reading diff: ' . $e->getMessage() . PHP_EOL);
106+
try {
107+
$runPlan = new RunPlanner($config, $options['wide'])->plan($options['paths'], $this->unifiedDiff);
108+
} catch (\Throwable $e) {
109+
$this->writeError('Error resolving input: ' . $e->getMessage() . PHP_EOL);
94110

95-
return 2;
96-
}
111+
return 2;
97112
}
98113

99114
$progress = $this->createProgress($options);
100115

101116
try {
102117
$runner = new SniffRunner($progress);
103-
$report = $runner->run($config, $overridePaths, $diff);
118+
$report = $runner->run($runPlan);
104119
} catch (\Throwable $e) {
105120
$this->writeError('Runtime error: ' . $e->getMessage() . PHP_EOL);
106121

@@ -118,27 +133,6 @@ public function run(): int
118133
return (int) $report->hasViolations();
119134
}
120135

121-
/**
122-
* @param list<string> $paths
123-
* @return list<string>
124-
*/
125-
private function resolveOverridePaths(array $paths): array
126-
{
127-
$cwd = getcwd() ?: '.';
128-
$resolved = [];
129-
130-
foreach ($paths as $path) {
131-
if (str_starts_with($path, '/') || preg_match('#^[a-zA-Z]:[/\\\\]#', $path)) {
132-
$resolved[] = $path;
133-
continue;
134-
}
135-
136-
$resolved[] = $cwd . '/' . $path;
137-
}
138-
139-
return $resolved;
140-
}
141-
142136
/**
143137
* @return array{
144138
* help: bool,
@@ -148,9 +142,10 @@ private function resolveOverridePaths(array $paths): array
148142
* colors: bool,
149143
* quiet: bool,
150144
* paths: list<string>,
151-
* diff: string|null,
145+
* wide: bool,
152146
* perf: bool,
153147
* }
148+
* @throws \InvalidArgumentException for unsupported options.
154149
*/
155150
private function parseArgv(): array
156151
{
@@ -162,7 +157,7 @@ private function parseArgv(): array
162157
'colors' => $this->detectColorSupport(),
163158
'quiet' => false,
164159
'paths' => [],
165-
'diff' => null,
160+
'wide' => false,
166161
'perf' => false,
167162
];
168163

@@ -227,57 +222,31 @@ private function parseArgv(): array
227222
continue;
228223
}
229224

230-
// --diff = read from stdin
231-
// --diff=FILE = read from file
232-
// --diff=- = read from stdin (explicit)
233-
if ($arg === '--diff') {
234-
$result['diff'] = '';
235-
$i++;
236-
continue;
237-
}
238-
239-
if (str_starts_with($arg, '--diff=')) {
240-
$result['diff'] = substr($arg, 7);
225+
if ($arg === '--perf') {
226+
$result['perf'] = true;
241227
$i++;
242228
continue;
243229
}
244230

245-
if ($arg === '--perf') {
246-
$result['perf'] = true;
231+
if ($arg === '--wide') {
232+
$result['wide'] = true;
247233
$i++;
248234
continue;
249235
}
250236

251237
// Anything else is a path to scan.
252238
if (!str_starts_with($arg, '-')) {
253239
$result['paths'][] = $arg;
240+
$i++;
241+
continue;
254242
}
255243

256-
$i++;
244+
throw new \InvalidArgumentException(sprintf('Unknown option: %s', $arg));
257245
}
258246

259247
return $result;
260248
}
261249

262-
/** @throws \RuntimeException if the source cannot be read. */
263-
private function readDiff(string $source): string
264-
{
265-
if ($source === '' || $source === '-') {
266-
$content = stream_get_contents($this->stdin);
267-
if ($content === false) {
268-
throw new \RuntimeException('Could not read diff from stdin.'); // @codeCoverageIgnore
269-
}
270-
return $content;
271-
}
272-
273-
$content = @file_get_contents($source);
274-
if ($content === false) {
275-
throw new \RuntimeException(sprintf('Could not read diff file: %s', $source));
276-
}
277-
278-
return $content;
279-
}
280-
281250
/** @param array{report: string, quiet: bool, colors: bool} $options */
282251
private function createProgress(array $options): ProgressInterface
283252
{
@@ -382,22 +351,20 @@ private function printHelp(): void
382351
--report=<format> Output format: console (default), checkstyle, json.
383352
--colors Force ANSI color output.
384353
--no-colors Disable ANSI color output.
385-
--diff[=<file>] Restrict analysis to files changed in a unified diff.
386-
Omit the value or pass "-" to read the diff from stdin.
387-
Violations are only reported when the violating element
388-
is on or contains a changed line (parent-context aware).
354+
--wide Check whole selected files and recursively include
355+
referenced XML files.
389356
390357
Arguments:
391358
<file-or-directory> One or more files or directories to scan.
392-
If omitted, the paths from the config file are used.
359+
Paths cannot be combined with diff input.
393360
394361
Examples:
395362
docbook-cs
396363
docbook-cs --config=myconfig.xml reference/
397364
docbook-cs --report=checkstyle --no-colors > report.xml
398365
docbook-cs reference/strings/functions/strlen.xml
399-
git diff HEAD | docbook-cs --diff --report=checkstyle
400-
docbook-cs --diff=changes.patch --report=json
366+
git diff HEAD | docbook-cs
367+
git diff HEAD | docbook-cs --wide --report=checkstyle
401368

402369
HELP;
403370

0 commit comments

Comments
 (0)