77use DocbookCS \Config \ConfigData ;
88use DocbookCS \Config \ConfigParser ;
99use DocbookCS \Config \ConfigParserException ;
10- use DocbookCS \Diff \DiffParser ;
1110use DocbookCS \Progress \ConsoleProgress ;
1211use DocbookCS \Progress \NullProgress ;
1312use DocbookCS \Progress \ProgressInterface ;
1413use DocbookCS \Report \Reporter \CheckstyleReporter ;
1514use DocbookCS \Report \Reporter \ConsoleReporter ;
1615use DocbookCS \Report \Reporter \JsonReporter ;
1716use DocbookCS \Report \Reporter \ReporterInterface ;
17+ use DocbookCS \Runner \RunPlanner ;
1818use DocbookCS \Runner \SniffRunner ;
1919
2020final 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
390357Arguments:
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
394361Examples:
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
402369HELP;
403370
0 commit comments