Passed
Push — master ( eff209...0e7332 )
by Edward
05:02
created

BuildPropertiesCommand::getDefaultTargetRootPath()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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