Completed
Push — master ( d7597e...5e5a6e )
by Akihito
15s queued 13s
created

DumpDocs::getLinkRelations()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
c 0
b 0
f 0
dl 0
loc 7
rs 10
cc 2
nc 2
nop 1
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($docsDir, $imgSrc, $type);
70
    }
71
72
    private function dumpImageMd(string $docsDir, string $imgSrc, string $type): void
73
    {
74
        $isIdMode = $type === '';
75
        $link = $isIdMode ? 'id | [title](asd.title.md)' : '[id](asd.md) | title';
76
        $html = <<<EOT
77
{$link}
78
79
[<img src="../{$imgSrc}" alt="application state diagram">](../{$imgSrc})
80
EOT;
81
        file_put_contents($docsDir . "/asd.{$type}md", $html);
82
    }
83
84
    private function dumpImageHtml(string $title, string $docsDir, string $imgSrc, string $type): void
85
    {
86
        $isIdMode = $type === '';
87
        $link = $isIdMode ? 'id | <a href="asd.title.html">title</a>' : '<a href="asd.html">id</a> | title';
88
        $html = <<<EOT
89
<html lang="en">
90
<head>
91
    <title>{$title}</title>
92
    <meta charset="UTF-8">
93
</head>
94
<body>
95
    <div style="font-size: medium;" >{$link}</div>
96
    <iframe src="../{$imgSrc}" style="border:0; width:100%; height:95%" allow="fullscreen"></iframe>
97
</body>
98
</html>
99
100
EOT;
101
        file_put_contents($docsDir . "/asd.{$type}html", $html);
102
    }
103
104
    private function convertHtml(string $title, string $markdown): string
105
    {
106
        return (new MdToHtml())($title, $markdown) . PHP_EOL;
107
    }
108
109
    private function fileOutput(string $title, string $markDown, string $basePath, string $format): void
110
    {
111
        $file = sprintf('%s.%s', $basePath, $this->ext);
112
        if ($format === self::MODE_MARKDOWN) {
113
            file_put_contents($file, $markDown);
114
115
            return;
116
        }
117
118
        file_put_contents($file, $this->convertHtml($title, $markDown));
119
    }
120
121
    private function mkDir(string $baseDir, string $dirName): string
122
    {
123
        $dir = sprintf('%s/%s', $baseDir, $dirName);
124
        if (! is_dir($dir)) {
125
            mkdir($dir, 0777, true); // @codeCoverageIgnore
126
        }
127
128
        return $dir;
129
    }
130
131
    private function getSemanticDoc(AbstractDescriptor $descriptor, string $asd, string $title): string
132
    {
133
        $descriptorSemantic = $this->getDescriptorInDescriptor($descriptor);
134
        $rt = $this->getRt($descriptor);
135
        $description = '';
136
        $description .= $this->getDescriptorProp('type', $descriptor);
137
        $description .= $this->getDescriptorProp('title', $descriptor);
138
        $description .= $this->getDescriptorProp('href', $descriptor);
139
        $description .= $this->getDescriptorKeyValue('doc', (string) ($descriptor->doc->value ?? ''));
140
        $description .= $this->getDescriptorProp('def', $descriptor);
141
        $description .= $this->getDescriptorProp('rel', $descriptor);
142
        $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

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

212
        $descriptors = $this->getInlineDescriptors(/** @scrutinizer ignore-type */ $descriptor->descriptor);
Loading history...
213
214
        $table = sprintf(' * descriptor%s%s| id | type | title |%s|---|---|---|%s', PHP_EOL, PHP_EOL, PHP_EOL, PHP_EOL);
215
        foreach ($descriptors as $descriptor) {
216
            $table .= sprintf('| %s | %s | %s |', $descriptor->htmlLink($this->ext), $descriptor->type, $descriptor->title) . PHP_EOL;
217
        }
218
219
        return $table;
220
    }
221
222
    /**
223
     * @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...
224
     *
225
     * @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...
226
     */
227
    private function getInlineDescriptors(array $inlineDescriptors): array
228
    {
229
        $descriptors = [];
230
        foreach ($inlineDescriptors as $descriptor) {
231
            if (isset($descriptor->id)) {
232
                assert(is_string($descriptor->id));
233
                $descriptors[] = $this->descriptors[$descriptor->id];
234
                continue;
235
            }
236
237
            assert(is_string($descriptor->href));
238
            $id = substr($descriptor->href, (int) strpos($descriptor->href, '#') + 1);
239
            assert(isset($this->descriptors[$id]));
240
241
            $original = clone $this->descriptors[$id];
242
            if (isset($descriptor->title)) {
243
                $original->title = (string) $descriptor->title;
244
            }
245
246
            $descriptors[] = $original;
247
        }
248
249
        usort($descriptors, static function (AbstractDescriptor $a, AbstractDescriptor $b): int {
250
            $order = ['semantic' => 0, 'safe' => 1, 'unsafe' => 2, 'idempotent' => 3];
251
252
            return $order[$a->type] <=> $order[$b->type];
253
        });
254
255
        assert($descriptors !== []);
256
257
        return $descriptors;
258
    }
259
260
    /**
261
     * @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...
262
     */
263
    private function getTag(array $tags): string
264
    {
265
        if ($tags === []) {
266
            return '';
267
        }
268
269
        return " * tag: {$this->getTagString($tags)}";
270
    }
271
272
    /**
273
     * @param list<string> $tags
274
     */
275
    private function getTagString(array $tags): string
276
    {
277
        $string = [];
278
        foreach ($tags as $tag) {
279
            $string[] = "[{$tag}](tag.{$tag}.{$this->ext})";
280
        }
281
282
        return implode(', ', $string) . PHP_EOL;
283
    }
284
285
    /**
286
     * @param list<string> $descriptorIds
287
     */
288
    private function getTagDoc(string $tag, array $descriptorIds, string $title, string $asd): string
289
    {
290
        $list = '';
291
        foreach ($descriptorIds as $descriptorId) {
292
            $descriptor = $this->descriptors[$descriptorId];
293
            $list .= " * {$descriptor->htmlLink($this->ext)}" . PHP_EOL;
294
        }
295
296
        $titleHeader = $title ? sprintf('%s: Tag', $title) : 'Tag';
297
298
        return <<<EOT
299
{$titleHeader}
300
# {$tag}
301
302
{$list}
303
---
304
305
[home](../index.{$this->ext}) | [asd]({$asd}) | {$tag} 
306
EOT;
307
    }
308
309
    private function getLinkRelations(LinkRelations $linkRelations): string
310
    {
311
        if ((string) $linkRelations === '') {
312
            return '';
313
        }
314
315
        return ' * links' . PHP_EOL . $linkRelations . PHP_EOL;
316
    }
317
}
318