Passed
Push — master ( 9f6147...2cb991 )
by Edward
02:58
created

GenerateTokenMatcherCommand::execute()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 5
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 8
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Remorhaz\UniLex\Console;
6
7
use ReflectionException;
8
use Remorhaz\UniLex\Exception;
9
use Remorhaz\UniLex\Lexer\TokenMatcherGenerator;
10
use Remorhaz\UniLex\Lexer\TokenMatcherSpec;
11
use Remorhaz\UniLex\Lexer\TokenMatcherSpecParser;
12
use RuntimeException;
13
use Symfony\Component\Console\Command\Command;
14
use Symfony\Component\Console\Exception\InvalidOptionException;
15
use Symfony\Component\Console\Input\InputArgument;
16
use Symfony\Component\Console\Input\InputInterface;
17
use Symfony\Component\Console\Input\InputOption;
18
use Symfony\Component\Console\Output\OutputInterface;
19
20
use function array_merge;
21
use function file_put_contents;
22
use function realpath;
23
use function strlen;
24
use function substr_count;
25
26
final class GenerateTokenMatcherCommand extends Command
27
{
28
29
    protected static $defaultName = 'generate-token-matcher';
30
31
    protected function configure()
32
    {
33
        $this
34
            ->setDescription('Generates token matcher.')
35
            ->setHelp('Generates token matcher based on given specification.')
36
            ->addArgument('spec', InputArgument::REQUIRED, 'Specification file')
37
            ->addArgument('target', InputArgument::REQUIRED, 'Target file')
38
            ->addOption('desc', 'd', InputOption::VALUE_REQUIRED, 'Token matcher description');
39
    }
40
41
    /**
42
     * @param InputInterface  $input
43
     * @param OutputInterface $output
44
     * @return int|void
45
     * @throws ReflectionException
46
     * @throws Exception
47
     */
48
    protected function execute(InputInterface $input, OutputInterface $output)
49
    {
50
        $output->writeln("Generating token matcher...");
51
        $spec = $this->buildSpec($input, $output);
52
        $content = $this->buildContent($output, $spec);
53
        $this->buildTarget($input, $output, $content);
54
55
        return 0;
56
    }
57
58
    /**
59
     * @param InputInterface  $input
60
     * @param OutputInterface $output
61
     * @return TokenMatcherSpec
62
     * @throws Exception
63
     * @throws ReflectionException
64
     */
65
    private function buildSpec(InputInterface $input, OutputInterface $output): TokenMatcherSpec
66
    {
67
        $specFile = $this->getSpecFile($input);
68
        $output->writeln("Specification used: {$specFile}");
69
70
        return TokenMatcherSpecParser::loadFromFile($specFile)
71
            ->getMatcherSpec()
72
            ->addFileComment(...$this->getFileComments($input));
73
    }
74
75
    /**
76
     * @param OutputInterface  $output
77
     * @param TokenMatcherSpec $spec
78
     * @return string
79
     * @throws Exception
80
     * @throws ReflectionException
81
     */
82
    private function buildContent(OutputInterface $output, TokenMatcherSpec $spec): string
83
    {
84
        $generator = new TokenMatcherGenerator($spec);
85
        $content = $generator->getOutput();
86
        $byteCount = strlen($content);
87
        $lineCount = $byteCount > 0
88
            ? substr_count($content, "\n") + 1
89
            : 0;
90
        $output->writeln("Done ({$lineCount} lines)!");
91
92
        return $content;
93
    }
94
95
    private function buildTarget(InputInterface $input, OutputInterface $output, string $content): void
96
    {
97
        $targetFile = $this->getTargetFile($input);
98
        $output->writeln("Dumping generated data to file {$targetFile}...");
99
100
        if (false === file_put_contents($targetFile, $content)) {
101
            throw new RuntimeException("Failed to write file {$targetFile}");
102
        }
103
        $byteCount = strlen($content);
104
        $output->writeln("Done ({$byteCount} bytes)!");
105
    }
106
107
    private function getTargetFile(InputInterface $input): string
108
    {
109
        return $input->getArgument('target');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $input->getArgument('target') could return the type null|string[] which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
110
    }
111
112
    private function getSpecFile(InputInterface $input): string
113
    {
114
        $specFile = $input->getArgument('spec');
115
        $specFile = realpath($specFile);
0 ignored issues
show
Bug introduced by
It seems like $specFile can also be of type string[]; however, parameter $path of realpath() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

115
        $specFile = realpath(/** @scrutinizer ignore-type */ $specFile);
Loading history...
116
        if (false === $specFile) {
117
            throw new InvalidOptionException("Option --spec must contain valid path to specification file");
118
        }
119
120
        return $specFile;
121
    }
122
123
    private function getFileComments(InputInterface $input): array
124
    {
125
        $description = $input->getOption('desc');
126
127
        return array_merge(
128
            isset($description) ? [$description, ''] : [],
129
            [
130
                "Auto-generated file, please don't edit manually.",
131
                "Generated by UniLex.",
132
            ]
133
        );
134
    }
135
}
136