Completed
Push — master ( 4905fa...027eeb )
by Jonathan
10s
created

GenerateChangelogCommand::execute()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 30
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 15
nc 5
nop 2
dl 0
loc 30
ccs 16
cts 16
cp 1
crap 5
rs 8.439
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 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 17
    public function __construct(ChangelogGenerator $changelogGenerator)
37
    {
38 17
        $this->changelogGenerator = $changelogGenerator;
39
40 17
        parent::__construct();
41 17
    }
42
43 17
    protected function configure() : void
44
    {
45
        $this
46 17
            ->setName('generate')
47 17
            ->setDescription('Generate a changelog markdown document from a GitHub milestone.')
48 17
            ->setHelp(<<<EOT
49 17
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 17
            ->addOption(
59 17
                'user',
60 17
                null,
61 17
                InputOption::VALUE_REQUIRED,
62 17
                'User that owns the repository.'
63
            )
64 17
            ->addOption(
65 17
                'repository',
66 17
                null,
67 17
                InputOption::VALUE_REQUIRED,
68 17
                'The repository owned by the user.'
69
            )
70 17
            ->addOption(
71 17
                'milestone',
72 17
                null,
73 17
                InputOption::VALUE_REQUIRED,
74 17
                'The milestone to build the changelog for.'
75
            )
76 17
            ->addOption(
77 17
                'file',
78 17
                null,
79 17
                InputOption::VALUE_OPTIONAL,
80 17
                'Write the changelog to a file.',
81 17
                false
82
            )
83 17
            ->addOption(
84 17
                'append',
85 17
                null,
86 17
                InputOption::VALUE_NONE,
87 17
                'Append the changelog to the file.'
88
            )
89 17
            ->addOption(
90 17
                'prepend',
91 17
                null,
92 17
                InputOption::VALUE_NONE,
93 17
                'Prepend the changelog to the file.'
94
            )
95 17
            ->addOption(
96 17
                'label',
97 17
                null,
98 17
                InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
99 17
                'The labels to generate a changelog for.'
100
            )
101 17
            ->addOption(
102 17
                'config',
103 17
                'c',
104 17
                InputOption::VALUE_REQUIRED,
105 17
                'The path to a configuration file.'
106
            )
107 17
            ->addOption(
108 17
                'project',
109 17
                'p',
110 17
                InputOption::VALUE_REQUIRED,
111 17
                'The project from the configuration to generate a changelog for.'
112
            )
113
        ;
114 17
    }
115
116 15
    protected function execute(InputInterface $input, OutputInterface $output) : void
117
    {
118 15
        $changelogConfig = $this->getChangelogConfig($input);
119
120 12
        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 11
        $changelogOutput = $this->getChangelogOutput($input, $output);
125
126 11
        $this->changelogGenerator->generate(
127 11
            $changelogConfig,
128 11
            $changelogOutput
129
        );
130
131 11
        if ($this->getFileWriteStrategy($input) !== self::WRITE_STRATEGY_PREPEND) {
132 8
            return;
133
        }
134
135 3
        $file = $this->getChangelogFilePath($input->getOption('file'));
136
137 3
        if (! ($changelogOutput instanceof BufferedOutput)) {
138 1
            return;
139
        }
140
141 2
        if (! file_exists($file)) {
142 1
            touch($file);
143
        }
144
145 2
        file_put_contents($file, $changelogOutput->fetch() . file_get_contents($file));
146 2
    }
147
148 15
    private function getChangelogConfig(InputInterface $input) : ChangelogConfig
149
    {
150 15
        $user       = (string) $input->getOption('user');
151 15
        $repository = (string) $input->getOption('repository');
152 15
        $milestone  = (string) $input->getOption('milestone');
153 15
        $labels     = $input->getOption('label');
154
155 15
        $changelogConfig = $this->loadConfigFile($input);
156
157 12
        if ($changelogConfig !== null) {
158 3
            return $changelogConfig;
159
        }
160
161 9
        return new ChangelogConfig(
162 9
            $user,
163 9
            $repository,
164 9
            $milestone,
165 9
            $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 11
    private function getChangelogOutput(InputInterface $input, OutputInterface $output) : OutputInterface
205
    {
206 11
        $file              = $input->getOption('file');
207 11
        $fileWriteStrategy = $this->getFileWriteStrategy($input);
208
209 11
        $changelogOutput = $output;
210
211 11
        if ($file !== false) {
212 6
            $changelogOutput = $this->createOutput($this->getChangelogFilePath($file), $fileWriteStrategy);
213
        }
214
215 11
        return $changelogOutput;
216
    }
217
218 11
    private function getFileWriteStrategy(InputInterface $input) : string
219
    {
220 11
        $append  = (bool) $input->getOption('append');
221 11
        $prepend = (bool) $input->getOption('prepend');
222
223 11
        if ($append) {
224 1
            return self::WRITE_STRATEGY_APPEND;
225
        }
226
227 10
        if ($prepend) {
228 3
            return self::WRITE_STRATEGY_PREPEND;
229
        }
230
231 7
        return self::WRITE_STRATEGY_REPLACE;
232
    }
233
234 6
    private function getChangelogFilePath(?string $file) : string
235
    {
236 6
        return $file === null ? sprintf('%s/CHANGELOG.md', getcwd()) : $file;
237
    }
238
239
    /**
240
     * @throws InvalidArgumentException
241
     */
242 15
    private function loadConfigFile(InputInterface $input) : ?ChangelogConfig
243
    {
244 15
        $config = $input->getOption('config');
245
246 15
        if ($config === null) {
247 9
            $config = 'changelog-generator-config.php';
248
249 9
            if (! file_exists($config)) {
250 9
                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