Passed
Pull Request — master (#42)
by
unknown
04:53 queued 43s
created

GenerateChangelogCommand::getIncludeDate()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 20
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 5.0342

Importance

Changes 0
Metric Value
cc 5
eloc 8
nc 4
nop 1
dl 0
loc 20
ccs 8
cts 9
cp 0.8889
crap 5.0342
rs 9.6111
c 0
b 0
f 0
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 in_array;
24
use function is_array;
25
use function is_string;
26
use function sprintf;
27
use function touch;
28
29
class GenerateChangelogCommand extends Command
30
{
31
    public const WRITE_STRATEGY_REPLACE = 'replace';
32
    public const WRITE_STRATEGY_APPEND  = 'append';
33
    public const WRITE_STRATEGY_PREPEND = 'prepend';
34
35
    /** @var ChangelogGenerator */
36
    private $changelogGenerator;
37
38 22
    public function __construct(ChangelogGenerator $changelogGenerator)
39
    {
40 22
        $this->changelogGenerator = $changelogGenerator;
41
42 22
        parent::__construct();
43 22
    }
44
45 22
    protected function configure() : void
46
    {
47
        $this
48 22
            ->setName('generate')
49 22
            ->setDescription('Generate a changelog markdown document from a GitHub milestone.')
50 22
            ->setHelp(<<<EOT
51 22
The <info>%command.name%</info> command generates a changelog markdown document from a GitHub milestone:
52
53
    <info>%command.full_name% --user=doctrine --repository=migrations --milestone=2.0</info>
54
55
You can filter the changelog by label names using the --label option:
56
57
    <info>%command.full_name% --user=doctrine --repository=migrations --milestone=2.0 --label=Enhancement --label=Bug</info>
58
EOT
59
            )
60 22
            ->addOption(
61 22
                'user',
62 22
                null,
63 22
                InputOption::VALUE_REQUIRED,
64 22
                'User that owns the repository.'
65
            )
66 22
            ->addOption(
67 22
                'repository',
68 22
                null,
69 22
                InputOption::VALUE_REQUIRED,
70 22
                'The repository owned by the user.'
71
            )
72 22
            ->addOption(
73 22
                'milestone',
74 22
                null,
75 22
                InputOption::VALUE_REQUIRED,
76 22
                'The milestone to build the changelog for.'
77
            )
78 22
            ->addOption(
79 22
                'file',
80 22
                null,
81 22
                InputOption::VALUE_OPTIONAL,
82 22
                'Write the changelog to a file.',
83 22
                false
84
            )
85 22
            ->addOption(
86 22
                'append',
87 22
                null,
88 22
                InputOption::VALUE_NONE,
89 22
                'Append the changelog to the file.'
90
            )
91 22
            ->addOption(
92 22
                'prepend',
93 22
                null,
94 22
                InputOption::VALUE_NONE,
95 22
                'Prepend the changelog to the file.'
96
            )
97 22
            ->addOption(
98 22
                'label',
99 22
                null,
100 22
                InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
101 22
                'The labels to generate a changelog for.'
102
            )
103 22
            ->addOption(
104 22
                'config',
105 22
                'c',
106 22
                InputOption::VALUE_REQUIRED,
107 22
                'The path to a configuration file.'
108
            )
109 22
            ->addOption(
110 22
                'project',
111 22
                'p',
112 22
                InputOption::VALUE_REQUIRED,
113 22
                'The project from the configuration to generate a changelog for.'
114
            )
115 22
            ->addOption(
116 22
                'include-open',
117 22
                'a',
118 22
                InputOption::VALUE_OPTIONAL,
119 22
                'Whether to also include open issues.',
120 22
                ''
121
            )
122 22
            ->addOption(
123 22
                'include-date',
124 22
                null,
125 22
                InputOption::VALUE_OPTIONAL,
126 22
                'Whether to also include date to the milestone.',
127 22
                ''
128
            )
129
        ;
130 22
    }
131
132 20
    protected function execute(InputInterface $input, OutputInterface $output) : void
133
    {
134 20
        $changelogConfig = $this->getChangelogConfig($input);
135
136 17
        if (! $changelogConfig->isValid()) {
137 1
            throw new InvalidArgumentException('You must pass a config file with the --config option or manually specify the --user --repository and --milestone options.');
138
        }
139
140 16
        $changelogOutput = $this->getChangelogOutput($input, $output);
141
142 16
        $this->changelogGenerator->generate(
143 16
            $changelogConfig,
144 16
            $changelogOutput
145
        );
146
147 16
        if ($this->getFileWriteStrategy($input) !== self::WRITE_STRATEGY_PREPEND) {
148 13
            return;
149
        }
150
151 3
        $file = $this->getChangelogFilePath($input->getOption('file'));
152
153 3
        if (! ($changelogOutput instanceof BufferedOutput)) {
154 1
            return;
155
        }
156
157 2
        if (! file_exists($file)) {
158 1
            touch($file);
159
        }
160
161 2
        file_put_contents($file, $changelogOutput->fetch() . file_get_contents($file));
162 2
    }
163
164 20
    private function getChangelogConfig(InputInterface $input) : ChangelogConfig
165
    {
166 20
        $changelogConfig = $this->loadConfigFile($input);
167
168 17
        if ($changelogConfig !== null) {
169 6
            return $changelogConfig;
170
        }
171
172 11
        $user        = (string) $input->getOption('user');
173 11
        $repository  = (string) $input->getOption('repository');
174 11
        $milestone   = (string) $input->getOption('milestone');
175 11
        $labels      = $input->getOption('label');
176 11
        $includeOpen = $this->getIncludeOpen($input);
177
178 11
        $changelogConfig = new ChangelogConfig(
179 11
            $user,
180 11
            $repository,
181 11
            $milestone,
182 11
            $labels,
183 11
            $includeOpen
184
        );
185
186 11
        if ($input->hasOption('include-date')) {
187 11
            $changelogConfig->setIncludeDate($this->getIncludeDate($input));
188
        }
189
190 11
        return $changelogConfig;
191
    }
192
193
    /**
194
     * @return false|resource
195
     */
196 1
    protected function fopen(string $file, string $mode)
197
    {
198 1
        return fopen($file, $mode);
199
    }
200
201
    /**
202
     * @throws InvalidArgumentException
203
     */
204 2
    protected function createOutput(string $file, string $fileWriteStrategy) : OutputInterface
205
    {
206 2
        if ($fileWriteStrategy === self::WRITE_STRATEGY_PREPEND) {
207 1
            return new BufferedOutput();
208
        }
209
210 2
        $handle = $this->fopen($file, $this->getFileHandleMode($fileWriteStrategy));
211
212 2
        if ($handle === false) {
213 1
            throw new InvalidArgumentException(sprintf('Could not open handle for %s', $file));
214
        }
215
216 1
        return new StreamOutput($handle);
217
    }
218
219 2
    private function getFileHandleMode(string $fileWriteStrategy) : string
220
    {
221 2
        if ($fileWriteStrategy === self::WRITE_STRATEGY_APPEND) {
222 2
            return 'a+';
223
        }
224
225 1
        return 'w+';
226
    }
227
228 16
    private function getChangelogOutput(InputInterface $input, OutputInterface $output) : OutputInterface
229
    {
230 16
        $file              = $input->getOption('file');
231 16
        $fileWriteStrategy = $this->getFileWriteStrategy($input);
232
233 16
        $changelogOutput = $output;
234
235 16
        if ($file !== false) {
236 6
            $changelogOutput = $this->createOutput($this->getChangelogFilePath($file), $fileWriteStrategy);
237
        }
238
239 16
        return $changelogOutput;
240
    }
241
242 16
    private function getFileWriteStrategy(InputInterface $input) : string
243
    {
244 16
        $append  = (bool) $input->getOption('append');
245 16
        $prepend = (bool) $input->getOption('prepend');
246
247 16
        if ($append) {
248 1
            return self::WRITE_STRATEGY_APPEND;
249
        }
250
251 15
        if ($prepend) {
252 3
            return self::WRITE_STRATEGY_PREPEND;
253
        }
254
255 12
        return self::WRITE_STRATEGY_REPLACE;
256
    }
257
258 6
    private function getChangelogFilePath(?string $file) : string
259
    {
260 6
        return $file === null ? sprintf('%s/CHANGELOG.md', getcwd()) : $file;
261
    }
262
263
    /**
264
     * @throws InvalidArgumentException
265
     */
266 20
    private function loadConfigFile(InputInterface $input) : ?ChangelogConfig
267
    {
268 20
        $config = $input->getOption('config');
269
270 20
        if ($config === null) {
271 11
            $config = 'changelog-generator-config.php';
272
273 11
            if (! file_exists($config)) {
274 11
                return null;
275
            }
276
        }
277
278 9
        if (! file_exists($config)) {
279 1
            throw new InvalidArgumentException(sprintf('Configuration file "%s" does not exist.', $config));
280
        }
281
282 8
        $changelogConfigs = include $config;
283
284 8
        if (! is_array($changelogConfigs) || count($changelogConfigs) === 0) {
285 1
            throw new InvalidArgumentException(sprintf('Configuration file "%s" did not return anything.', $config));
286
        }
287
288 7
        $changelogConfig = $this->findChangelogConfig($input, $changelogConfigs);
289
290 6
        $this->overrideChangelogConfig($input, $changelogConfig);
291
292 6
        return $changelogConfig;
293
    }
294
295
    /**
296
     * @param ChangelogConfig[] $changelogConfigs
297
     */
298 7
    private function findChangelogConfig(InputInterface $input, array $changelogConfigs) : ChangelogConfig
299
    {
300 7
        $project = $input->getOption('project');
301
302 7
        $changelogConfig = current($changelogConfigs);
303
304 7
        if ($project !== null) {
305 7
            if (! isset($changelogConfigs[$project])) {
306 1
                throw new InvalidArgumentException(sprintf('Could not find project named "%s" configured', $project));
307
            }
308
309 6
            $changelogConfig = $changelogConfigs[$project];
310
        }
311
312 6
        return $changelogConfig;
313
    }
314
315 6
    private function overrideChangelogConfig(InputInterface $input, ChangelogConfig $changelogConfig) : void
316
    {
317 6
        $user       = $input->getOption('user');
318 6
        $repository = $input->getOption('repository');
319 6
        $milestone  = $input->getOption('milestone');
320 6
        $labels     = $input->getOption('label');
321
322 6
        if ($user !== null) {
323 6
            $changelogConfig->setUser($user);
324
        }
325
326 6
        if ($repository !== null) {
327 6
            $changelogConfig->setRepository($repository);
328
        }
329
330 6
        if ($milestone !== null) {
331 6
            $changelogConfig->setMilestone($milestone);
332
        }
333
334 6
        if ($labels !== []) {
335 5
            $changelogConfig->setLabels($labels);
336
        }
337
338 6
        if ($input->hasOption('include-open')) {
339 6
            $changelogConfig->setIncludeOpen($this->getIncludeOpen($input));
340
        }
341
342 6
        if (! $input->hasOption('include-date')) {
343
            return;
344
        }
345
346 6
        $changelogConfig->setIncludeDate($this->getIncludeDate($input));
347 6
    }
348
349 17
    private function getIncludeOpen(InputInterface $input) : bool
350
    {
351 17
        $includeOpen = $input->getOption('include-open');
352
353
        // --include-open option not provided, default to false
354 17
        if ($includeOpen === '') {
355 14
            return false;
356
        }
357
358
        // --include-open option provided, but no value was given, default to true
359 3
        if ($includeOpen === null) {
360 1
            return true;
361
        }
362
363
        // --include-open option provided and value was provided
364 2
        if (is_string($includeOpen) && in_array($includeOpen, ['1', 'true'], true)) {
365 1
            return true;
366
        }
367
368 1
        return false;
369
    }
370
371 17
    private function getIncludeDate(InputInterface $input) : bool
372
    {
373 17
        $includeOpen = $input->getOption('include-date');
374
375
        // --include-date option not provided, default to false
376 17
        if ($includeOpen === '') {
377 15
            return false;
378
        }
379
380
        // --include-date option provided, but no value was given, default to true
381 2
        if ($includeOpen === null) {
382 1
            return true;
383
        }
384
385
        // --include-date option provided and value was provided
386 1
        if (is_string($includeOpen) && in_array($includeOpen, ['1', 'true'], true)) {
387
            return true;
388
        }
389
390 1
        return false;
391
    }
392
}
393