Passed
Pull Request — master (#165)
by Akihito
02:05 queued 17s
created

DumpDocs::fileOutput()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
c 0
b 0
f 0
dl 0
loc 10
rs 10
cc 2
nc 2
nop 4
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Koriym\AppStateDiagram;
6
7
use stdClass;
8
9
use function assert;
10
use function basename;
11
use function dirname;
12
use function explode;
13
use function file_put_contents;
14
use function filter_var;
15
use function implode;
16
use function is_dir;
17
use function is_string;
18
use function mkdir;
19
use function property_exists;
20
use function sprintf;
21
use function str_replace;
22
use function strpos;
23
use function substr;
24
use function usort;
25
26
use const FILTER_VALIDATE_URL;
27
use const PHP_EOL;
28
29
/** @psalm-suppress MissingConstructor */
30
final class DumpDocs
31
{
32
    public const MODE_HTML = 'html';
33
    public const MODE_MARKDOWN = 'markdown';
34
35
    /** @var array<string, AbstractDescriptor> */
36
    private $descriptors = [];
37
38
    /** @var "html"|"md" */
0 ignored issues
show
Documentation Bug introduced by
The doc comment "html"|"md" at position 0 could not be parsed: Unknown type name '"html"' at position 0 in "html"|"md".
Loading history...
39
    private $ext;
40
41
    public function __invoke(Profile $profile, string $alpsFile, string $format = self::MODE_HTML): void
42
    {
43
        $descriptors = $this->descriptors = $profile->descriptors;
44
        $this->ext = $format === self::MODE_MARKDOWN ? 'md' : self::MODE_HTML;
45
        $docsDir = $this->mkDir(dirname($alpsFile), 'docs');
46
        $asdFile = sprintf('../%s', basename(str_replace(['xml', 'json'], 'svg', $alpsFile)));
47
        foreach ($descriptors as $descriptor) {
48
            $markDown = $this->getSemanticDoc($descriptor, $asdFile, $profile->title);
49
            $basePath = sprintf('%s/%s.%s', $docsDir, $descriptor->type, $descriptor->id);
50
            $title = "{$descriptor->id} ({$descriptor->type})";
51
            $this->fileOutput($title, $markDown, $basePath, $format);
52
        }
53
54
        foreach ($profile->tags as $tag => $descriptorIds) {
55
            $markDown = $this->getTagDoc($tag, $descriptorIds, $profile->title, $asdFile);
56
            $basePath = sprintf('%s/tag.%s', $docsDir, $tag);
57
            $this->fileOutput($tag, $markDown, $basePath, $format);
58
        }
59
60
        $this->dumpImage($profile->title, $docsDir, $format, $alpsFile, '');
61
        $this->dumpImage($profile->title, $docsDir, $format, $alpsFile, 'title.');
62
    }
63
64
    private function dumpImage(string $title, string $docsDir, string $format, string $alpsFile, string $type): void
65
    {
66
        $imgSrc = str_replace(['json', 'xml'], "{$type}svg", basename($alpsFile));
67
        $format === self::MODE_HTML ?
68
            $this->dumpImageHtml($title, $docsDir, $imgSrc, $type):
69
            $this->dumpImageMd($title, $docsDir, $imgSrc, $type);
70
    }
71
72
    private function dumpImageMd(string $title, string $docsDir, string $imgSrc, string $type): void
0 ignored issues
show
Unused Code introduced by
The parameter $title is not used and could be removed. ( Ignorable by Annotation )

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

72
    private function dumpImageMd(/** @scrutinizer ignore-unused */ string $title, string $docsDir, string $imgSrc, string $type): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
73
    {
74
        $isIdMode = $type === '';
75
        $link = $isIdMode ? 'id | [title](asd.title.md)' : '[id](asd.md) | title';
76
        $html = <<<EOT
77
{$link}
78
<img src="../{$imgSrc}" alt="application state diagram">
79
EOT;
80
        file_put_contents($docsDir . "/asd.{$type}md", $html);
81
    }
82
83
    private function dumpImageHtml(string $title, string $docsDir, string $imgSrc, string $type): void
84
    {
85
        $isIdMode = $type === '';
86
        $link = $isIdMode ? 'id | <a href="asd.title.html">title</a>' : '<a href="asd.html">id</a> | title';
87
        $html = <<<EOT
88
<html lang="en">
89
<head>
90
    <title>{$title}</title>
91
    <meta charset="UTF-8">
92
</head>
93
<body>
94
    <div style="font-size: medium;" >{$link}</div>
95
    <iframe src="../{$imgSrc}" style="border:0; width:100%; height:95%" allow="fullscreen"></iframe>
96
</body>
97
</html>
98
99
EOT;
100
        file_put_contents($docsDir . "/asd.{$type}html", $html);
101
    }
102
103
    private function convertHtml(string $title, string $markdown): string
104
    {
105
        return (new MdToHtml())($title, $markdown) . PHP_EOL;
106
    }
107
108
    private function fileOutput(string $title, string $markDown, string $basePath, string $format): void
109
    {
110
        $file = sprintf('%s.%s', $basePath, $this->ext);
111
        if ($format === self::MODE_MARKDOWN) {
112
            file_put_contents($file, $markDown);
113
114
            return;
115
        }
116
117
        file_put_contents($file, $this->convertHtml($title, $markDown));
118
    }
119
120
    private function mkDir(string $baseDir, string $dirName): string
121
    {
122
        $dir = sprintf('%s/%s', $baseDir, $dirName);
123
        if (! is_dir($dir)) {
124
            mkdir($dir, 0777, true); // @codeCoverageIgnore
125
        }
126
127
        return $dir;
128
    }
129
130
    private function getSemanticDoc(AbstractDescriptor $descriptor, string $asd, string $title): string
131
    {
132
        $descriptorSemantic = $this->getDescriptorInDescriptor($descriptor);
133
        $rt = $this->getRt($descriptor);
134
        $description = '';
135
        $description .= $this->getDescriptorProp('type', $descriptor);
136
        $description .= $this->getDescriptorProp('title', $descriptor);
137
        $description .= $this->getDescriptorProp('href', $descriptor);
138
        $description .= $this->getDescriptorKeyValue('doc', (string) ($descriptor->doc->value ?? ''));
139
        $description .= $this->getDescriptorProp('def', $descriptor);
140
        $description .= $this->getDescriptorProp('rel', $descriptor);
141
        $description .= $this->getTag($descriptor->tags);
0 ignored issues
show
Bug introduced by
$descriptor->tags of type Koriym\AppStateDiagram\list is incompatible with the type array expected by parameter $tags of Koriym\AppStateDiagram\DumpDocs::getTag(). ( Ignorable by Annotation )

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

141
        $description .= $this->getTag(/** @scrutinizer ignore-type */ $descriptor->tags);
Loading history...
142
        $linkRelations = $this->getLinkRelations($descriptor->linkRelations);
143
        $titleHeader = $title ? sprintf('%s: Semantic Descriptor', $title) : 'Semantic Descriptor';
144
145
        return <<<EOT
146
{$titleHeader}
147
# {$descriptor->id}
148
{$description}{$rt}{$linkRelations}{$descriptorSemantic}
149
---
150
151
[home](../index.{$this->ext}) | [asd]($asd)
152
EOT;
153
    }
154
155
    private function getDescriptorProp(string $key, AbstractDescriptor $descriptor): string
156
    {
157
        if (! property_exists($descriptor, $key) || ! $descriptor->{$key}) {
158
            return '';
159
        }
160
161
        $value = (string) $descriptor->{$key};
162
        if ($this->isUrl($value)) {
163
            return " * {$key}: [{$value}]({$value})" . PHP_EOL;
164
        }
165
166
        if ($this->isFragment($value)) {
167
            [, $id] = explode('#', $value);
168
169
            return " * {$key}: [{$id}](semantic.{$id}.{$this->ext})" . PHP_EOL;
170
        }
171
172
        return " * {$key}: {$value}" . PHP_EOL;
173
    }
174
175
    private function isUrl(string $text): bool
176
    {
177
        return filter_var($text, FILTER_VALIDATE_URL) !== false;
178
    }
179
180
    private function isFragment(string $text): bool
181
    {
182
        return $text[0] === '#';
183
    }
184
185
    private function getDescriptorKeyValue(string $key, string $value): string
186
    {
187
        if (! $value) {
188
            return '';
189
        }
190
191
        return " * {$key}: {$value}" . PHP_EOL;
192
    }
193
194
    private function getRt(AbstractDescriptor $descriptor): string
195
    {
196
        if ($descriptor instanceof SemanticDescriptor) {
197
            return '';
198
        }
199
200
        assert($descriptor instanceof TransDescriptor);
201
202
        return sprintf(' * rt: [%s](semantic.%s.%s)', $descriptor->rt, $descriptor->rt, $this->ext) . PHP_EOL;
203
    }
204
205
    private function getDescriptorInDescriptor(AbstractDescriptor $descriptor): string
206
    {
207
        if ($descriptor->descriptor === []) {
0 ignored issues
show
introduced by
The condition $descriptor->descriptor === array() is always false.
Loading history...
208
            return '';
209
        }
210
211
        $descriptors = $this->getInlineDescriptors($descriptor->descriptor);
0 ignored issues
show
Bug introduced by
$descriptor->descriptor of type Koriym\AppStateDiagram\list is incompatible with the type array expected by parameter $inlineDescriptors of Koriym\AppStateDiagram\D...:getInlineDescriptors(). ( Ignorable by Annotation )

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

211
        $descriptors = $this->getInlineDescriptors(/** @scrutinizer ignore-type */ $descriptor->descriptor);
Loading history...
212
213
        $table = sprintf(' * descriptor%s%s| id | type | title |%s|---|---|---|%s', PHP_EOL, PHP_EOL, PHP_EOL, PHP_EOL);
214
        foreach ($descriptors as $descriptor) {
215
            $table .= sprintf('| %s | %s | %s |', $descriptor->htmlLink($this->ext), $descriptor->type, $descriptor->title) . PHP_EOL;
216
        }
217
218
        return $table;
219
    }
220
221
    /**
222
     * @param non-empty-list<stdClass> $inlineDescriptors
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-list<stdClass> at position 0 could not be parsed: Unknown type name 'non-empty-list' at position 0 in non-empty-list<stdClass>.
Loading history...
223
     *
224
     * @return non-empty-list<AbstractDescriptor>
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-list<AbstractDescriptor> at position 0 could not be parsed: Unknown type name 'non-empty-list' at position 0 in non-empty-list<AbstractDescriptor>.
Loading history...
225
     */
226
    private function getInlineDescriptors(array $inlineDescriptors): array
227
    {
228
        $descriptors = [];
229
        foreach ($inlineDescriptors as $descriptor) {
230
            if (isset($descriptor->id)) {
231
                assert(is_string($descriptor->id));
232
                $descriptors[] = $this->descriptors[$descriptor->id];
233
                continue;
234
            }
235
236
            assert(is_string($descriptor->href));
237
            $id = substr($descriptor->href, (int) strpos($descriptor->href, '#') + 1);
238
            assert(isset($this->descriptors[$id]));
239
240
            $original = clone $this->descriptors[$id];
241
            if (isset($descriptor->title)) {
242
                $original->title = (string) $descriptor->title;
243
            }
244
245
            $descriptors[] = $original;
246
        }
247
248
        usort($descriptors, static function (AbstractDescriptor $a, AbstractDescriptor $b): int {
249
            $order = ['semantic' => 0, 'safe' => 1, 'unsafe' => 2, 'idempotent' => 3];
250
251
            return $order[$a->type] <=> $order[$b->type];
252
        });
253
254
        assert($descriptors !== []);
255
256
        return $descriptors;
257
    }
258
259
    /**
260
     * @param list<string> $tags
0 ignored issues
show
Bug introduced by
The type Koriym\AppStateDiagram\list was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
261
     */
262
    private function getTag(array $tags): string
263
    {
264
        if ($tags === []) {
265
            return '';
266
        }
267
268
        return " * tag: {$this->getTagString($tags)}";
269
    }
270
271
    /**
272
     * @param list<string> $tags
273
     */
274
    private function getTagString(array $tags): string
275
    {
276
        $string = [];
277
        foreach ($tags as $tag) {
278
            $string[] = "[{$tag}](tag.{$tag}.{$this->ext})";
279
        }
280
281
        return implode(', ', $string) . PHP_EOL;
282
    }
283
284
    /**
285
     * @param list<string> $descriptorIds
286
     */
287
    private function getTagDoc(string $tag, array $descriptorIds, string $title, string $asd): string
288
    {
289
        $list = '';
290
        foreach ($descriptorIds as $descriptorId) {
291
            $descriptor = $this->descriptors[$descriptorId];
292
            $list .= " * {$descriptor->htmlLink($this->ext)}" . PHP_EOL;
293
        }
294
295
        $titleHeader = $title ? sprintf('%s: Tag', $title) : 'Tag';
296
297
        return <<<EOT
298
{$titleHeader}
299
# {$tag}
300
301
{$list}
302
---
303
304
[home](../index.{$this->ext}) | [asd]({$asd}) | {$tag} 
305
EOT;
306
    }
307
308
    private function getLinkRelations(LinkRelations $linkRelations): string
309
    {
310
        if ((string) $linkRelations === '') {
311
            return '';
312
        }
313
314
        return ' * links' . PHP_EOL . $linkRelations . PHP_EOL;
315
    }
316
}
317