Passed
Push — master ( 56b970...3daa8a )
by Christophe
01:56
created

CompareCommand   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 137
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 83
dl 0
loc 137
ccs 70
cts 70
cp 1
rs 10
c 0
b 0
f 0
wmc 17

4 Methods

Rating   Name   Duplication   Size   Complexity  
A configure() 0 10 1
A __construct() 0 5 1
C execute() 0 83 12
A displayMessages() 0 10 3
1
<?php
2
3
namespace Incenteev\TranslationCheckerBundle\Command;
4
5
use Incenteev\TranslationCheckerBundle\Translator\ExposingTranslator;
6
use Symfony\Component\Console\Command\Command;
7
use Symfony\Component\Console\Input\InputArgument;
8
use Symfony\Component\Console\Input\InputInterface;
9
use Symfony\Component\Console\Input\InputOption;
10
use Symfony\Component\Console\Output\OutputInterface;
11
use Symfony\Component\Translation\Catalogue\TargetOperation;
12
use Symfony\Component\Translation\MessageCatalogue;
13
use Symfony\Component\Yaml\Yaml;
14
15
class CompareCommand extends Command
16
{
17
    private $exposingTranslator;
18
19 18
    public function __construct(ExposingTranslator $exposingTranslator)
20
    {
21 18
        parent::__construct();
22
23 18
        $this->exposingTranslator = $exposingTranslator;
24 18
    }
25
26 18
    protected function configure()
27
    {
28 18
        $this->setName('incenteev:translation:compare')
29 18
            ->setDescription('Compares two translation catalogues to ensure they are in sync')
30 18
            ->addArgument('locale', InputArgument::REQUIRED, 'The locale being checked')
31 18
            ->addArgument('source', InputArgument::OPTIONAL, 'The source of the comparison', 'en')
32 18
            ->addOption('obsolete-only', null, InputOption::VALUE_NONE, 'Report only obsolete keys')
33 18
            ->addOption('domain', 'd', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'The domains being compared')
34 18
            ->addOption('whitelist-file', 'w', InputOption::VALUE_REQUIRED, 'Path to a YAML whitelist file')
35 18
            ->setHelp(<<<EOF
36 18
The <info>%command.name%</info> command compares 2 translation catalogues to
37
ensure they are in sync. If there is missing keys or obsolete keys in the target
38
catalogue, the command will exit with an error code.
39
40
When running the command in verbose mode, the translation keys will also be displayed.
41
<info>php %command.full_name% fr --verbose</info>
42
43
The <info>--domain</info> option allows to restrict the domains being checked.
44
It can be specified several times to check several domains. If the option is not passed,
45
all domains will be compared.
46
47
The <info>--obsolete-only</info> option allows to check only obsolete keys, and ignore any
48
missing keys.
49
50
The <info>--whitelist-file</info> option allows to define a whitelist of keys which are
51
ignored from the comparison (they are never reported as missing or as obsolete). This
52
file must be a Yaml file where keys are domains, and values are an array of whitelisted
53
EOF
54
            );
55 18
    }
56
57 18
    protected function execute(InputInterface $input, OutputInterface $output)
58
    {
59 18
        $sourceCatalogue = $this->exposingTranslator->getCatalogue($input->getArgument('source'));
60 18
        $comparedCatalogue = $this->exposingTranslator->getCatalogue($input->getArgument('locale'));
61
62
        // Change the locale of the catalogue as DiffOperation requires operating on a single locale
63 18
        $catalogue = new MessageCatalogue($sourceCatalogue->getLocale(), $comparedCatalogue->all());
64
65 18
        $operation = new TargetOperation($catalogue, $sourceCatalogue);
66
67 18
        $domains = $operation->getDomains();
68 18
        $restrictedDomains = $input->getOption('domain');
69 18
        if (!empty($restrictedDomains)) {
70 4
            $domains = array_intersect($domains, $restrictedDomains);
0 ignored issues
show
Bug introduced by
It seems like $restrictedDomains can also be of type boolean and string; however, parameter $array2 of array_intersect() does only seem to accept array, 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

70
            $domains = array_intersect($domains, /** @scrutinizer ignore-type */ $restrictedDomains);
Loading history...
71 4
            $output->writeln(sprintf('<comment>Checking the domains %s</comment>', implode(', ', $domains)));
72
        }
73
74 18
        $checkMissing = !$input->getOption('obsolete-only');
75
76 18
        $whitelistFile = $input->getOption('whitelist-file');
77 18
        $whitelist = array();
78
79 18
        if (null !== $whitelistFile) {
80 5
            if (!file_exists($whitelistFile)) {
0 ignored issues
show
Bug introduced by
It seems like $whitelistFile can also be of type string[]; however, parameter $filename of file_exists() 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

80
            if (!file_exists(/** @scrutinizer ignore-type */ $whitelistFile)) {
Loading history...
81 1
                $output->writeln(sprintf('<error>The whitelist file "%s" does not exist.</error>', $whitelistFile));
0 ignored issues
show
Bug introduced by
It seems like $whitelistFile can also be of type string[]; however, parameter $args of sprintf() 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

81
                $output->writeln(sprintf('<error>The whitelist file "%s" does not exist.</error>', /** @scrutinizer ignore-type */ $whitelistFile));
Loading history...
82
83 1
                return 1;
84
            }
85
86 4
            $whitelist = Yaml::parse(file_get_contents($whitelistFile));
0 ignored issues
show
Bug introduced by
It seems like $whitelistFile can also be of type string[]; however, parameter $filename of file_get_contents() 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

86
            $whitelist = Yaml::parse(file_get_contents(/** @scrutinizer ignore-type */ $whitelistFile));
Loading history...
87
88 4
            if (!is_array($whitelist)) {
89 1
                $output->writeln(sprintf('<error>The whitelist file "%s" is invalid. It must be a Yaml file containing a map.</error>', $whitelistFile));
90
91 1
                return 1;
92
            }
93
        }
94
95 16
        $valid = true;
96
97 16
        foreach ($domains as $domain) {
98 16
            $missingMessages = $checkMissing ? $operation->getNewMessages($domain) : array();
99 16
            $obsoleteMessages = $operation->getObsoleteMessages($domain);
100 16
            $written = false;
101
102 16
            if (isset($whitelist[$domain])) {
103 2
                $domainWhitelist = array_flip($whitelist[$domain]);
104 2
                $missingMessages = array_diff_key($missingMessages, $domainWhitelist);
105 2
                $obsoleteMessages = array_diff_key($obsoleteMessages, $domainWhitelist);
106
            }
107
108 16
            if (!empty($missingMessages)) {
109 7
                $valid = false;
110 7
                $written = true;
111 7
                $output->writeln(sprintf('<comment>%s</comment> messages are missing in the <info>%s</info> domain', count($missingMessages), $domain));
112
113 7
                $this->displayMessages($output, $missingMessages);
114
            }
115
116 16
            if (!empty($obsoleteMessages)) {
117 6
                $valid = false;
118 6
                $written = true;
119 6
                $output->writeln(sprintf('<comment>%s</comment> messages are obsolete in the <info>%s</info> domain', count($obsoleteMessages), $domain));
120
121 6
                $this->displayMessages($output, $obsoleteMessages);
122
            }
123
124 16
            if ($written) {
125 16
                $output->writeln('');
126
            }
127
        }
128
129 16
        if ($valid) {
130 6
            $output->writeln(sprintf(
131 6
                '<info>The <comment>%s</comment> catalogue is in sync with the <comment>%s</comment> one.</info>',
132 6
                $input->getArgument('locale'),
133 6
                $input->getArgument('source')
134
            ));
135
136 6
            return 0;
137
        }
138
139 10
        return 1;
140
    }
141
142 10
    private function displayMessages(OutputInterface $output, array $messages)
143
    {
144 10
        if ($output->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) {
145 8
            return;
146
        }
147
148 2
        foreach ($messages as $key => $translation) {
149 2
            $output->writeln('    '.$key);
150
        }
151 2
        $output->writeln('');
152 2
    }
153
}
154