Completed
Push — master ( a6be25...7dec61 )
by Jonathan
9s
created

GenerateChangelogCommand::execute()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 26
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 4

Importance

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