Passed
Push — master ( b7e39a...e3bdde )
by Jonathan
02:28
created

GenerateChangelogCommand   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 288
Duplicated Lines 0 %

Test Coverage

Coverage 99.34%

Importance

Changes 0
Metric Value
dl 0
loc 288
ccs 151
cts 152
cp 0.9934
rs 8.8
c 0
b 0
f 0
wmc 36

13 Methods

Rating   Name   Duplication   Size   Complexity  
B loadConfigFile() 0 27 6
A createOutput() 0 13 3
B execute() 0 30 5
B overrideChangelogConfig() 0 24 5
A getChangelogConfig() 0 18 2
A getChangelogOutput() 0 12 2
A getFileWriteStrategy() 0 14 3
A getFileHandleMode() 0 7 2
A __construct() 0 5 1
A fopen() 0 3 1
A getChangelogFilePath() 0 3 2
A findChangelogConfig() 0 15 3
A configure() 0 69 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ChangelogGenerator\Command;
6
7
use ChangelogGenerator\ChangelogConfig;
8
use ChangelogGenerator\ChangelogGenerator;
9
use InvalidArgumentException;
10
use Symfony\Component\Console\Command\Command;
11
use Symfony\Component\Console\Input\InputInterface;
12
use Symfony\Component\Console\Input\InputOption;
13
use Symfony\Component\Console\Output\BufferedOutput;
14
use Symfony\Component\Console\Output\OutputInterface;
15
use Symfony\Component\Console\Output\StreamOutput;
16
use function count;
17
use function current;
18
use function file_exists;
19
use function file_get_contents;
20
use function file_put_contents;
21
use function fopen;
22
use function getcwd;
23
use function is_array;
24
use function sprintf;
25
use function touch;
26
27
class GenerateChangelogCommand extends Command
28
{
29
    public const WRITE_STRATEGY_REPLACE = 'replace';
30
    public const WRITE_STRATEGY_APPEND  = 'append';
31
    public const WRITE_STRATEGY_PREPEND = 'prepend';
32
33
    /** @var ChangelogGenerator */
34
    private $changelogGenerator;
35
36 16
    public function __construct(ChangelogGenerator $changelogGenerator)
37
    {
38 16
        $this->changelogGenerator = $changelogGenerator;
39
40 16
        parent::__construct();
41 16
    }
42
43 16
    protected function configure() : void
44
    {
45
        $this
46 16
            ->setName('generate')
47 16
            ->setDescription('Generate a changelog markdown document from a GitHub milestone.')
48 16
            ->setHelp(<<<EOT
49 16
The <info>%command.name%</info> command generates a changelog markdown document from a GitHub milestone:
50
51
    <info>%command.full_name% --user=doctrine --repository=migrations --milestone=2.0</info>
52
53
You can filter the changelog by label names using the --label option:
54
55
    <info>%command.full_name% --user=doctrine --repository=migrations --milestone=2.0 --label=Enhancement --label=Bug</info>
56
EOT
57
            )
58 16
            ->addOption(
59 16
                'user',
60 16
                null,
61 16
                InputOption::VALUE_REQUIRED,
62 16
                'User that owns the repository.'
63
            )
64 16
            ->addOption(
65 16
                'repository',
66 16
                null,
67 16
                InputOption::VALUE_REQUIRED,
68 16
                'The repository owned by the user.'
69
            )
70 16
            ->addOption(
71 16
                'milestone',
72 16
                null,
73 16
                InputOption::VALUE_REQUIRED,
74 16
                'The milestone to build the changelog for.'
75
            )
76 16
            ->addOption(
77 16
                'file',
78 16
                null,
79 16
                InputOption::VALUE_OPTIONAL,
80 16
                'Write the changelog to a file.',
81 16
                false
82
            )
83 16
            ->addOption(
84 16
                'append',
85 16
                null,
86 16
                InputOption::VALUE_NONE,
87 16
                'Append the changelog to the file.'
88
            )
89 16
            ->addOption(
90 16
                'prepend',
91 16
                null,
92 16
                InputOption::VALUE_NONE,
93 16
                'Prepend the changelog to the file.'
94
            )
95 16
            ->addOption(
96 16
                'label',
97 16
                null,
98 16
                InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
99 16
                'The labels to generate a changelog for.'
100
            )
101 16
            ->addOption(
102 16
                'config',
103 16
                'c',
104 16
                InputOption::VALUE_REQUIRED,
105 16
                'The path to a configuration file.'
106
            )
107 16
            ->addOption(
108 16
                'project',
109 16
                'p',
110 16
                InputOption::VALUE_REQUIRED,
111 16
                'The project from the configuration to generate a changelog for.'
112
            )
113
        ;
114 16
    }
115
116 14
    protected function execute(InputInterface $input, OutputInterface $output) : void
117
    {
118 14
        $changelogConfig = $this->getChangelogConfig($input);
119
120 11
        if (! $changelogConfig->isValid()) {
121 1
            throw new InvalidArgumentException('You must pass a config file with the --config option or manually specify the --user --repository and --milestone options.');
122
        }
123
124 10
        $changelogOutput = $this->getChangelogOutput($input, $output);
125
126 10
        $this->changelogGenerator->generate(
127 10
            $changelogConfig,
128 10
            $changelogOutput
129
        );
130
131 10
        if ($this->getFileWriteStrategy($input) !== self::WRITE_STRATEGY_PREPEND) {
132 8
            return;
133
        }
134
135 2
        $file = $this->getChangelogFilePath($input->getOption('file'));
136
137 2
        if (! ($changelogOutput instanceof BufferedOutput)) {
138 1
            return;
139
        }
140
141 1
        if (! file_exists($file)) {
142
            touch($file);
143
        }
144
145 1
        file_put_contents($file, $changelogOutput->fetch() . file_get_contents($file));
146 1
    }
147
148 14
    private function getChangelogConfig(InputInterface $input) : ChangelogConfig
149
    {
150 14
        $user       = (string) $input->getOption('user');
151 14
        $repository = (string) $input->getOption('repository');
152 14
        $milestone  = (string) $input->getOption('milestone');
153 14
        $labels     = $input->getOption('label');
154
155 14
        $changelogConfig = $this->loadConfigFile($input);
156
157 11
        if ($changelogConfig !== null) {
158 3
            return $changelogConfig;
159
        }
160
161 8
        return new ChangelogConfig(
162 8
            $user,
163 8
            $repository,
164 8
            $milestone,
165 8
            $labels
166
        );
167
    }
168
169
    /**
170
     * @return false|resource
171
     */
172 1
    protected function fopen(string $file, string $mode)
173
    {
174 1
        return fopen($file, $mode);
175
    }
176
177
    /**
178
     * @throws InvalidArgumentException
179
     */
180 2
    protected function createOutput(string $file, string $fileWriteStrategy) : OutputInterface
181
    {
182 2
        if ($fileWriteStrategy === self::WRITE_STRATEGY_PREPEND) {
183 1
            return new BufferedOutput();
184
        }
185
186 2
        $handle = $this->fopen($file, $this->getFileHandleMode($fileWriteStrategy));
187
188 2
        if ($handle === false) {
189 1
            throw new InvalidArgumentException(sprintf('Could not open handle for %s', $file));
190
        }
191
192 1
        return new StreamOutput($handle);
193
    }
194
195 2
    private function getFileHandleMode(string $fileWriteStrategy) : string
196
    {
197 2
        if ($fileWriteStrategy === self::WRITE_STRATEGY_APPEND) {
198 2
            return 'a+';
199
        }
200
201 1
        return 'w+';
202
    }
203
204 10
    private function getChangelogOutput(InputInterface $input, OutputInterface $output) : OutputInterface
205
    {
206 10
        $file              = $input->getOption('file');
207 10
        $fileWriteStrategy = $this->getFileWriteStrategy($input);
208
209 10
        $changelogOutput = $output;
210
211 10
        if ($file !== false) {
212 5
            $changelogOutput = $this->createOutput($this->getChangelogFilePath($file), $fileWriteStrategy);
213
        }
214
215 10
        return $changelogOutput;
216
    }
217
218 10
    private function getFileWriteStrategy(InputInterface $input) : string
219
    {
220 10
        $append  = (bool) $input->getOption('append');
221 10
        $prepend = (bool) $input->getOption('prepend');
222
223 10
        if ($append) {
224 1
            return self::WRITE_STRATEGY_APPEND;
225
        }
226
227 9
        if ($prepend) {
228 2
            return self::WRITE_STRATEGY_PREPEND;
229
        }
230
231 7
        return self::WRITE_STRATEGY_REPLACE;
232
    }
233
234 5
    private function getChangelogFilePath(?string $file) : string
235
    {
236 5
        return $file === null ? sprintf('%s/CHANGELOG.md', getcwd()) : $file;
237
    }
238
239
    /**
240
     * @throws InvalidArgumentException
241
     */
242 14
    private function loadConfigFile(InputInterface $input) : ?ChangelogConfig
243
    {
244 14
        $config = $input->getOption('config');
245
246 14
        if ($config === null) {
247 8
            $config = 'changelog-generator-config.php';
248
249 8
            if (! file_exists($config)) {
250 8
                return null;
251
            }
252
        }
253
254 6
        if (! file_exists($config)) {
255 1
            throw new InvalidArgumentException(sprintf('Configuration file "%s" does not exist.', $config));
256
        }
257
258 5
        $changelogConfigs = include $config;
259
260 5
        if (! is_array($changelogConfigs) || count($changelogConfigs) === 0) {
261 1
            throw new InvalidArgumentException(sprintf('Configuration file "%s" did not return anything.', $config));
262
        }
263
264 4
        $changelogConfig = $this->findChangelogConfig($input, $changelogConfigs);
265
266 3
        $this->overrideChangelogConfig($input, $changelogConfig);
267
268 3
        return $changelogConfig;
269
    }
270
271
    /**
272
     * @param ChangelogConfig[] $changelogConfigs
273
     */
274 4
    private function findChangelogConfig(InputInterface $input, array $changelogConfigs) : ChangelogConfig
275
    {
276 4
        $project = $input->getOption('project');
277
278 4
        $changelogConfig = current($changelogConfigs);
279
280 4
        if ($project !== null) {
281 4
            if (! isset($changelogConfigs[$project])) {
282 1
                throw new InvalidArgumentException(sprintf('Could not find project named "%s" configured', $project));
283
            }
284
285 3
            $changelogConfig = $changelogConfigs[$project];
286
        }
287
288 3
        return $changelogConfig;
289
    }
290
291 3
    private function overrideChangelogConfig(InputInterface $input, ChangelogConfig $changelogConfig) : void
292
    {
293 3
        $user       = $input->getOption('user');
294 3
        $repository = $input->getOption('repository');
295 3
        $milestone  = $input->getOption('milestone');
296 3
        $labels     = $input->getOption('label');
297
298 3
        if ($user !== null) {
299 3
            $changelogConfig->setUser($user);
300
        }
301
302 3
        if ($repository !== null) {
303 3
            $changelogConfig->setRepository($repository);
304
        }
305
306 3
        if ($milestone !== null) {
307 3
            $changelogConfig->setMilestone($milestone);
308
        }
309
310 3
        if ($labels === []) {
311 1
            return;
312
        }
313
314 2
        $changelogConfig->setLabels($labels);
315 2
    }
316
}
317