BuildPropertiesCommand::configure()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 49
Code Lines 44

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 44
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 49
rs 9.216
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Remorhaz\UCD\Tool\Console;
6
7
use LogicException;
8
use PhpParser\PrettyPrinter\Standard;
9
use Remorhaz\UCD\Tool\PropertyBuilder;
10
use RuntimeException;
11
use SplFileObject;
12
use Symfony\Component\Console\Command\Command;
13
use Symfony\Component\Console\Helper\ProgressBar;
14
use Symfony\Component\Console\Helper\ProgressIndicator;
15
use Symfony\Component\Console\Input\InputInterface;
16
use Symfony\Component\Console\Input\InputOption;
17
use Symfony\Component\Console\Output\OutputInterface;
18
use Throwable;
19
20
use function is_string;
21
use function Safe\realpath;
22
23
final class BuildPropertiesCommand extends Command
24
{
25
26
    private const OPTION_TARGET_ROOT_PATH = 'target-root-path';
27
    private const OPTION_TARGET_INDEX_ROOT_PATH = 'target-index-root-path';
28
    private const OPTION_SOURCE_ROOT_PATH = 'source-root-path';
29
    private const OPTION_SOURCE_UNICODE_DATA = 'source-unicode-data';
30
    private const OPTION_SOURCE_SCRIPTS = 'source-scripts';
31
    private const OPTION_SOURCE_PROP_LIST = 'source-prop-list';
32
    private const OPTION_SOURCE_DERIVED_CORE_PROPERTIES = 'source-derived-core-properties';
33
34
    protected static $defaultName = 'build';
35
36
    protected function configure()
37
    {
38
        $this
39
            ->setDescription('Makes Unicode properties available to regular expressions')
40
            ->addOption(
41
                self::OPTION_TARGET_ROOT_PATH,
42
                null,
43
                InputOption::VALUE_REQUIRED,
44
                'Root path for generated files (without tailing slash)',
45
                $this->getDefaultTargetRootPath()
46
            )
47
            ->addOption(
48
                self::OPTION_TARGET_INDEX_ROOT_PATH,
49
                null,
50
                InputOption::VALUE_REQUIRED,
51
                'Root path for generated index file (without tailing slash)',
52
                $this->getDefaultTargetIndexRootPath()
53
            )
54
            ->addOption(
55
                self::OPTION_SOURCE_ROOT_PATH,
56
                null,
57
                InputOption::VALUE_REQUIRED,
58
                'Location of normative Unicode files (without tailing slash)',
59
                $this->getDefaultSourceRootPath()
60
            )
61
            ->addOption(
62
                self::OPTION_SOURCE_UNICODE_DATA,
63
                null,
64
                InputOption::VALUE_REQUIRED,
65
                'Location of UnicodeData.txt (relative to source root path, with heading slash)',
66
                '/UnicodeData.txt'
67
            )->addOption(
68
                self::OPTION_SOURCE_SCRIPTS,
69
                null,
70
                InputOption::VALUE_REQUIRED,
71
                'Location of Scripts.txt (relative to source root path, with heading slash)',
72
                '/Scripts.txt'
73
            )->addOption(
74
                self::OPTION_SOURCE_PROP_LIST,
75
                null,
76
                InputOption::VALUE_REQUIRED,
77
                'Location of PropList.txt (relative to source root path, with heading slash)',
78
                '/PropList.txt'
79
            )->addOption(
80
                self::OPTION_SOURCE_DERIVED_CORE_PROPERTIES,
81
                null,
82
                InputOption::VALUE_REQUIRED,
83
                'Location of DerivedCoreProperties.txt (relative to source root path, with heading slash)',
84
                '/DerivedCoreProperties.txt'
85
            );
86
    }
87
88
    private function getDefaultTargetRootPath(): string
89
    {
90
        return $this->getRealPath(
91
            __DIR__ . '/../../../src',
92
            'Default target root path not detected'
93
        );
94
    }
95
96
    private function getDefaultTargetIndexRootPath(): string
97
    {
98
        return $this->getRealPath(
99
            __DIR__ . '/../../../config',
100
            'Default target index file root path not detected'
101
        );
102
    }
103
104
    private function getRealPath(string $path, string $errorMessage): string
105
    {
106
        try {
107
            return realpath($path);
108
        } catch (Throwable $e) {
109
            throw new LogicException($errorMessage, 0, $e);
110
        }
111
    }
112
113
    private function getDefaultSourceRootPath(): string
114
    {
115
        return $this->getRealPath(
116
            __DIR__ . '/../../data',
117
            'Default source root path not detected'
118
        );
119
    }
120
121
    protected function execute(InputInterface $input, OutputInterface $output)
122
    {
123
        $output->writeln($this->getApplication()->getName());
124
        $propertyBuilder = new PropertyBuilder(new Standard());
125
        $this->parseUnicodeData($propertyBuilder, $input, $output);
126
        $this->parseScripts($propertyBuilder, $input, $output);
127
        $this->parsePropList($propertyBuilder, $input, $output);
128
        $this->parseDerivedCoreProperties($propertyBuilder, $input, $output);
129
        $this->buildFiles($propertyBuilder, $input, $output);
130
131
        return 0;
132
    }
133
134
    private function parseUnicodeData(
135
        PropertyBuilder $propertyBuilder,
136
        InputInterface $input,
137
        OutputInterface $output
138
    ): void {
139
        $progressBar = new ProgressBar($output);
140
        $progressIndicator = new ProgressIndicator($output);
141
142
        $output->writeln(' Parsing UnicodeData.txt...');
143
        $unicodeData = new SplFileObject($this->getSourceUnicodeData($input));
144
        $progressBar->setMaxSteps($unicodeData->getSize());
145
        $onParseProgress = function (int $byteCount) use ($progressBar): void {
146
            $progressBar->advance($byteCount);
147
        };
148
        $progressBar->start();
149
        $propertyBuilder->parseUnicodeData($unicodeData, $onParseProgress);
150
        $progressBar->finish();
151
        $progressBar->clear();
152
        $output->writeln(" {$unicodeData->getSize()} bytes of UnicodeData.txt parsed");
153
        $this->fetchRangeSets($output, $propertyBuilder, $progressBar);
154
155
        $progressIndicator->start('Building UnicodeData derivatives...');
156
        $onBuildProgress = function () use ($progressIndicator) {
157
            $progressIndicator->advance();
158
        };
159
        $propertyBuilder->buildUnicodeDataDerivatives($onBuildProgress);
160
        $progressIndicator->finish('UnicodeData derivatives are built.');
161
        $this->fetchRangeSets($output, $propertyBuilder, $progressBar);
162
    }
163
164
    private function fetchRangeSets(
165
        OutputInterface $output,
166
        PropertyBuilder $propertyBuilder,
167
        ProgressBar $progressBar
168
    ): void {
169
        $output->writeln(' Creating range sets from buffer...');
170
        $bufferSize = $propertyBuilder->getRangeBufferSize();
171
        $progressBar->setMaxSteps($bufferSize);
172
        $onFetchProgress = function (int $rangeSetIndex) use ($progressBar): void {
173
            $progressBar->setProgress($rangeSetIndex);
174
        };
175
        $progressBar->start();
176
        $propertyBuilder->fetchBufferedRangeSets($onFetchProgress);
177
        $progressBar->finish();
178
        $progressBar->clear();
179
        $output->writeln(" {$bufferSize} range sets created");
180
    }
181
182
    private function getSourceRootPath(InputInterface $input): string
183
    {
184
        $optionName = self::OPTION_SOURCE_ROOT_PATH;
185
        $sourceRootPath = $input->getOption($optionName);
186
        if (is_string($sourceRootPath)) {
187
            return $sourceRootPath;
188
        }
189
190
        throw new RuntimeException("Option --{$optionName} must be a string");
191
    }
192
193
    private function getTargetRootPath(InputInterface $input): string
194
    {
195
        $optionName = self::OPTION_TARGET_ROOT_PATH;
196
        $targetRootPath = $input->getOption($optionName);
197
        if (is_string($targetRootPath)) {
198
            return $targetRootPath;
199
        }
200
201
        throw new RuntimeException("Option --{$optionName} must be a string");
202
    }
203
204
    private function getTargetIndexRootPath(InputInterface $input): string
205
    {
206
        $optionName = self::OPTION_TARGET_INDEX_ROOT_PATH;
207
        $targetRootPath = $input->getOption($optionName);
208
        if (is_string($targetRootPath)) {
209
            return $targetRootPath;
210
        }
211
212
        throw new RuntimeException("Option --{$optionName} must be a string");
213
    }
214
215
    private function getSourceUnicodeData(InputInterface $input): string
216
    {
217
        return $this->getSourceFile(self::OPTION_SOURCE_UNICODE_DATA, $input);
218
    }
219
220
    private function getSourceScripts(InputInterface $input): string
221
    {
222
        return $this->getSourceFile(self::OPTION_SOURCE_SCRIPTS, $input);
223
    }
224
225
    private function getSourcePropList(InputInterface $input): string
226
    {
227
        return $this->getSourceFile(self::OPTION_SOURCE_PROP_LIST, $input);
228
    }
229
230
    private function getSourceDerivedCoreProperties(InputInterface $input): string
231
    {
232
        return $this->getSourceFile(self::OPTION_SOURCE_DERIVED_CORE_PROPERTIES, $input);
233
    }
234
235
    private function getSourceFile(string $optionName, InputInterface $input): string
236
    {
237
        $sourceScripts = $input->getOption($optionName);
238
        if (is_string($sourceScripts)) {
239
            return $this->getSourceRootPath($input) . $sourceScripts;
240
        }
241
242
        throw new RuntimeException("Option --{$optionName} must be a string");
243
    }
244
245
    private function parseScripts(
246
        PropertyBuilder $propertyBuilder,
247
        InputInterface $input,
248
        OutputInterface $output
249
    ): void {
250
        $progressBar = new ProgressBar($output);
251
        $progressIndicator = new ProgressIndicator($output);
252
253
        $output->writeln(' Parsing Scripts.txt...');
254
255
        $scripts = new SplFileObject($this->getSourceScripts($input));
256
        $progressBar->setMaxSteps($scripts->getSize());
257
        $onParseProgress = function (int $byteCount) use ($progressBar): void {
258
            $progressBar->advance($byteCount);
259
        };
260
        $progressBar->start();
261
        $propertyBuilder->parseScripts($scripts, $onParseProgress);
262
        $progressBar->finish();
263
        $progressBar->clear();
264
        $output->writeln(" {$scripts->getSize()} bytes of Scripts.txt parsed");
265
        $this->fetchRangeSets($output, $propertyBuilder, $progressBar);
266
267
        $progressIndicator->start('Building Scripts derivatives...');
268
        $onBuildProgress = function () use ($progressIndicator) {
269
            $progressIndicator->advance();
270
        };
271
        $propertyBuilder->buildScriptsDerivatives($onBuildProgress);
272
        $progressIndicator->finish('Scripts derivatives are built.');
273
        $this->fetchRangeSets($output, $propertyBuilder, $progressBar);
274
    }
275
276
    private function parsePropList(
277
        PropertyBuilder $propertyBuilder,
278
        InputInterface $input,
279
        OutputInterface $output
280
    ): void {
281
        $progressBar = new ProgressBar($output);
282
283
        $output->writeln(' Parsing PropList.txt...');
284
        $propList = new SplFileObject($this->getSourcePropList($input));
285
        $progressBar->setMaxSteps($propList->getSize());
286
        $onParseProgress = function (int $byteCount) use ($progressBar): void {
287
            $progressBar->advance($byteCount);
288
        };
289
        $progressBar->start();
290
        $propertyBuilder->parseProperties($propList, $onParseProgress);
291
        $progressBar->finish();
292
        $progressBar->clear();
293
        $output->writeln(" {$propList->getSize()} bytes of PropList.txt parsed");
294
        $this->fetchRangeSets($output, $propertyBuilder, $progressBar);
295
    }
296
297
    private function parseDerivedCoreProperties(
298
        PropertyBuilder $propertyBuilder,
299
        InputInterface $input,
300
        OutputInterface $output
301
    ): void {
302
        $progressBar = new ProgressBar($output);
303
304
        $output->writeln(' Parsing DerivedCoreProperties.txt...');
305
        $derivedCoreProperties = new SplFileObject($this->getSourceDerivedCoreProperties($input));
306
        $progressBar->setMaxSteps($derivedCoreProperties->getSize());
307
        $onParseProgress = function (int $byteCount) use ($progressBar): void {
308
            $progressBar->advance($byteCount);
309
        };
310
        $progressBar->start();
311
        $propertyBuilder->parseProperties($derivedCoreProperties, $onParseProgress);
312
        $progressBar->finish();
313
        $progressBar->clear();
314
        $output->writeln(" {$derivedCoreProperties->getSize()} bytes of DerivedCoreProperties.txt parsed");
315
        $this->fetchRangeSets($output, $propertyBuilder, $progressBar);
316
    }
317
318
    private function buildFiles(
319
        PropertyBuilder $propertyBuilder,
320
        InputInterface $input,
321
        OutputInterface $output
322
    ): void {
323
        $progressBar = new ProgressBar($output);
324
325
        $output->writeln(' Writing target files...');
326
        $progressBar->setMaxSteps($propertyBuilder->getFileCount());
327
        $onWriteProgress = function () use ($progressBar): void {
328
            $progressBar->advance();
329
        };
330
        $progressBar->start();
331
        $propertyBuilder->writeFiles(
332
            $this->getTargetIndexRootPath($input),
333
            $this->getTargetRootPath($input),
334
            $onWriteProgress
335
        );
336
        $progressBar->finish();
337
        $progressBar->clear();
338
        $output->writeln(" {$propertyBuilder->getFileCount()} target files written");
339
    }
340
}
341