Failed Conditions
Push — master ( 50388d...b7e39a )
by Jonathan
03:39 queued 01:28
created

GenerateChangelogCommand::execute()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 26
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4.0058

Importance

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