ValidatePublicationsCommand::countRecursive()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 3
nop 2
dl 0
loc 13
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Hyde\Publications\Commands;
6
7
use Hyde\Hyde;
8
use Hyde\Publications\Actions\PublicationPageValidator;
9
use Hyde\Publications\Models\PublicationType;
10
use Hyde\Publications\Publications;
11
use Illuminate\Support\Collection;
12
use InvalidArgumentException;
13
use LaravelZero\Framework\Commands\Command;
14
15
use function array_key_last;
16
use function array_map;
17
use function array_values;
18
use function basename;
19
use function collect;
20
use function count;
21
use function explode;
22
use function filled;
23
use function glob;
24
use function in_array;
25
use function json_encode;
26
use function memory_get_peak_usage;
27
use function microtime;
28
use function round;
29
use function sprintf;
30
use function str_repeat;
31
use function str_replace;
32
use function str_starts_with;
33
use function strlen;
34
use function substr_count;
35
36
/**
37
 * Hyde command to validate one or all publications.
38
 *
39
 * @see \Hyde\Publications\Testing\Feature\ValidatePublicationsCommandTest
40
 *
41
 * @internal This command is not part of the public API and may change without notice.
42
 */
43
class ValidatePublicationsCommand extends ValidatingCommand
44
{
45
    /** @var string */
46
    protected $signature = 'validate:publications
47
		{publicationType? : The name of the publication type to validate.}
48
		{--json : Display results as JSON.}';
49
50
    /** @var string */
51
    protected $description = 'Validate all or the specified publication type(s)';
52
53
    protected float $timeStart;
54
55
    protected array $results = [];
56
57
    protected int $countedErrors = 0;
58
    protected int $countedWarnings = 0;
59
60
    protected string $passedIcon = "<fg=green>\u{2713}</>";
61
    protected string $failedIcon = "<fg=red>\u{2A2F}</>";
62
    protected string $warningIcon = "<fg=yellow>\u{0021}</>";
63
64
    public function safeHandle(): int
65
    {
66
        $this->timeStart = microtime(true);
67
68
        if (! $this->option('json')) {
69
            $this->title('Validating publications!');
70
        }
71
72
        $publicationTypesToValidate = $this->getPublicationTypesToValidate();
73
74
        foreach ($publicationTypesToValidate as $publicationType) {
75
            $this->validatePublicationType($publicationType);
76
        }
77
78
        $this->countedErrors = substr_count(json_encode($this->results), '":"Error: ');
79
        $this->countedWarnings = substr_count(json_encode($this->results), '":"Warning: ');
80
81
        if ($this->option('json')) {
82
            $this->outputJson();
83
        } else {
84
            $this->displayResults();
85
86
            $this->outputSummary();
87
        }
88
89
        if ($this->countedErrors > 0) {
90
            return Command::FAILURE;
91
        }
92
93
        return Command::SUCCESS;
94
    }
95
96
    protected function getPublicationTypesToValidate(): Collection
97
    {
98
        $publicationTypes = Publications::getPublicationTypes();
99
        $name = $this->argument('publicationType');
100
101
        if (filled($name)) {
102
            if (! $publicationTypes->has($name)) {
103
                throw new InvalidArgumentException("Publication type [$name] does not exist");
104
            }
105
106
            return collect([$name => PublicationType::get($name)]);
0 ignored issues
show
Bug introduced by
array($name => Hyde\Publ...cationType::get($name)) of type Hyde\Publications\Models\PublicationType[] is incompatible with the type Illuminate\Contracts\Support\Arrayable expected by parameter $value of collect(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

106
            return collect(/** @scrutinizer ignore-type */ [$name => PublicationType::get($name)]);
Loading history...
107
        }
108
109
        if ($publicationTypes->isEmpty()) {
110
            throw new InvalidArgumentException('No publication types to validate!');
111
        }
112
113
        return $publicationTypes;
114
    }
115
116
    protected function validatePublicationType(PublicationType $publicationType): void
117
    {
118
        $this->results[$publicationType->getIdentifier()] = [];
119
120
        foreach (glob(Hyde::path("{$publicationType->getDirectory()}/*.md")) as $publicationFile) {
0 ignored issues
show
Bug introduced by
The method path() does not exist on Hyde\Hyde. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

120
        foreach (glob(Hyde::/** @scrutinizer ignore-call */ path("{$publicationType->getDirectory()}/*.md")) as $publicationFile) {
Loading history...
121
            $identifier = basename($publicationFile, '.md');
122
            $this->results[$publicationType->getIdentifier()][$identifier] = PublicationPageValidator::call($publicationType, $identifier)->getResults();
123
        }
124
    }
125
126
    protected function displayResults(): void
127
    {
128
        foreach ($this->results as $publicationTypeName => $publications) {
129
            $this->infoComment("Validating publication type [$publicationTypeName]");
130
            foreach ($publications ?? [] as $publicationName => $errors) {
131
                $this->displayPublicationResults($publicationName, $errors);
132
            }
133
134
            if ($publicationTypeName !== array_key_last($this->results)) {
135
                $this->output->newLine();
136
            }
137
        }
138
    }
139
140
    protected function displayPublicationResults(string $publicationName, array $results): void
141
    {
142
        $this->line(sprintf('  %s <fg=cyan>%s.md</>', $this->getPublicationResultsIcon(
143
            $this->getMessageTypesInResult($results)), $publicationName
144
        ));
145
146
        foreach ($results as $message) {
147
            $this->displayPublicationFieldResults($message);
148
        }
149
    }
150
151
    protected function displayPublicationFieldResults(string $message): void
152
    {
153
        $isWarning = str_starts_with($message, 'Warning: ');
154
        $isError = str_starts_with($message, 'Error: ');
155
156
        $message = str_replace(['Warning: ', 'Error: '], '', $message);
157
158
        if ($isWarning || $isError) {
159
            if ($isWarning) {
160
                $this->line(sprintf('    %s <comment>%s</comment>', $this->warningIcon, $message));
161
            } else {
162
                $this->line(sprintf('    %s <fg=red>%s</>', $this->failedIcon, $message));
163
            }
164
        } elseif ($this->output->isVerbose()) {
165
            $this->line(sprintf('    %s <fg=green>%s</>', $this->passedIcon, $message));
166
        }
167
    }
168
169
    protected function getPublicationResultsIcon(array $types): string
170
    {
171
        if (in_array('Error', $types)) {
172
            return $this->failedIcon;
173
        }
174
175
        if (in_array('Warning', $types)) {
176
            return $this->warningIcon;
177
        }
178
179
        return $this->passedIcon;
180
    }
181
182
    protected function getMessageTypesInResult(array $results): array
183
    {
184
        return array_map(function (string $result): string {
185
            return explode(':', $result)[0];
186
        }, array_values($results));
187
    }
188
189
    protected function outputSummary(): void
190
    {
191
        $size = strlen('Summary:');
192
        $spaces = str_repeat(' ', $size);
193
194
        $this->output->newLine();
195
        $this->output->writeln("<bg=blue;fg=white>{$spaces}Summary:$spaces</>");
196
        $this->output->newLine();
197
198
        $countPublicationTypes = count($this->results);
199
        $countPublications = self::countRecursive($this->results, 1);
200
        $countFields = self::countRecursive($this->results, 2);
201
202
        $this->output->writeln(sprintf('<fg=green>Validated %d publication types, %d publications, %d fields</><fg=gray> in %sms using %sMB peak memory</>',
203
            $countPublicationTypes, $countPublications, $countFields,
204
            round((microtime(true) - $this->timeStart) * 1000),
205
            round(memory_get_peak_usage() / 1024 / 1024)
206
        ));
207
208
        $this->output->writeln('<fg='.($this->countedWarnings ? 'yellow' : 'green').">Found $this->countedWarnings Warnings</>");
209
        $this->output->writeln('<fg='.($this->countedErrors ? 'red' : 'green').">Found $this->countedErrors Errors</>");
210
    }
211
212
    protected function outputJson(): void
213
    {
214
        $this->output->writeln(json_encode($this->results, JSON_PRETTY_PRINT));
215
    }
216
217
    protected static function countRecursive(array $array, int $limit): int
218
    {
219
        $count = 0;
220
221
        foreach ($array as $child) {
222
            if ($limit > 0) {
223
                $count += self::countRecursive($child, $limit - 1);
224
            } else {
225
                $count += 1;
226
            }
227
        }
228
229
        return $count;
230
    }
231
}
232