Completed
Push — master ( ffa9df...a6be25 )
by Jonathan
10s
created

GenerateChangelogCommand::getFileWriteStrategy()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 3
nop 1
dl 0
loc 14
ccs 8
cts 8
cp 1
crap 3
rs 9.4285
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
        $changelogOutput = $this->getChangelogOutput($input, $output);
118
119 13
        $this->changelogGenerator->generate(
120 13
            $this->getChangelogConfig($input),
121 10
            $changelogOutput
122
        );
123
124 10
        if ($this->getFileWriteStrategy($input) !== self::WRITE_STRATEGY_PREPEND) {
125 8
            return;
126
        }
127
128 2
        $file = $this->getChangelogFilePath($input->getOption('file'));
129
130 2
        if (! ($changelogOutput instanceof BufferedOutput)) {
131 1
            return;
132
        }
133
134 1
        file_put_contents($file, $changelogOutput->fetch() . file_get_contents($file));
135 1
    }
136
137 13
    private function getChangelogConfig(InputInterface $input) : ChangelogConfig
138
    {
139 13
        $user       = $input->getOption('user');
140 13
        $repository = $input->getOption('repository');
141 13
        $milestone  = $input->getOption('milestone');
142 13
        $labels     = $input->getOption('label');
143
144 13
        $changelogConfig = $this->loadConfigFile($input);
145
146 10
        if ($changelogConfig !== null) {
147 3
            return $changelogConfig;
148
        }
149
150 7
        return new ChangelogConfig(
151 7
            $user,
152 7
            $repository,
153 7
            $milestone,
154 7
            $labels
155
        );
156
    }
157
158
    /**
159
     * @return false|resource
160
     */
161 1
    protected function fopen(string $file, string $mode)
162
    {
163 1
        return fopen($file, $mode);
164
    }
165
166
    /**
167
     * @throws InvalidArgumentException
168
     */
169 2
    protected function createOutput(string $file, string $fileWriteStrategy) : OutputInterface
170
    {
171 2
        if ($fileWriteStrategy === self::WRITE_STRATEGY_PREPEND) {
172 1
            return new BufferedOutput();
173
        }
174
175 2
        $handle = $this->fopen($file, $this->getFileHandleMode($fileWriteStrategy));
176
177 2
        if ($handle === false) {
178 1
            throw new InvalidArgumentException(sprintf('Could not open handle for %s', $file));
179
        }
180
181 1
        return new StreamOutput($handle);
182
    }
183
184 2
    private function getFileHandleMode(string $fileWriteStrategy) : string
185
    {
186 2
        if ($fileWriteStrategy === self::WRITE_STRATEGY_APPEND) {
187 2
            return 'a+';
188
        }
189
190 1
        return 'w+';
191
    }
192
193 13
    private function getChangelogOutput(InputInterface $input, OutputInterface $output) : OutputInterface
194
    {
195 13
        $file              = $input->getOption('file');
196 13
        $fileWriteStrategy = $this->getFileWriteStrategy($input);
197
198 13
        $changelogOutput = $output;
199
200 13
        if ($file !== false) {
201 5
            $changelogOutput = $this->createOutput($this->getChangelogFilePath($file), $fileWriteStrategy);
202
        }
203
204 13
        return $changelogOutput;
205
    }
206
207 13
    private function getFileWriteStrategy(InputInterface $input) : string
208
    {
209 13
        $append  = (bool) $input->getOption('append');
210 13
        $prepend = (bool) $input->getOption('prepend');
211
212 13
        if ($append) {
213 1
            return self::WRITE_STRATEGY_APPEND;
214
        }
215
216 12
        if ($prepend) {
217 2
            return self::WRITE_STRATEGY_PREPEND;
218
        }
219
220 10
        return self::WRITE_STRATEGY_REPLACE;
221
    }
222
223 5
    private function getChangelogFilePath(?string $file) : string
224
    {
225 5
        return $file === null ? sprintf('%s/CHANGELOG.md', getcwd()) : $file;
226
    }
227
228
    /**
229
     * @throws InvalidArgumentException
230
     */
231 13
    private function loadConfigFile(InputInterface $input) : ?ChangelogConfig
232
    {
233 13
        $config = $input->getOption('config');
234
235 13
        if ($config === null) {
236 7
            $config = 'changelog-generator-config.php';
237
238 7
            if (! file_exists($config)) {
239 7
                return null;
240
            }
241
        }
242
243 6
        if (! file_exists($config)) {
244 1
            throw new InvalidArgumentException(sprintf('Configuration file "%s" does not exist.', $config));
245
        }
246
247 5
        $changelogConfigs = include $config;
248
249 5
        if (! is_array($changelogConfigs) || count($changelogConfigs) === 0) {
250 1
            throw new InvalidArgumentException(sprintf('Configuration file "%s" did not return anything.', $config));
251
        }
252
253 4
        $changelogConfig = $this->findChangelogConfig($input, $changelogConfigs);
254
255 3
        $this->overrideChangelogConfig($input, $changelogConfig);
256
257 3
        return $changelogConfig;
258
    }
259
260
    /**
261
     * @param ChangelogConfig[] $changelogConfigs
262
     */
263 4
    private function findChangelogConfig(InputInterface $input, array $changelogConfigs) : ChangelogConfig
264
    {
265 4
        $project = $input->getOption('project');
266
267 4
        $changelogConfig = current($changelogConfigs);
268
269 4
        if ($project !== null) {
270 4
            if (! isset($changelogConfigs[$project])) {
271 1
                throw new InvalidArgumentException(sprintf('Could not find project named "%s" configured', $project));
272
            }
273
274 3
            $changelogConfig = $changelogConfigs[$project];
275
        }
276
277 3
        return $changelogConfig;
278
    }
279
280 3
    private function overrideChangelogConfig(InputInterface $input, ChangelogConfig $changelogConfig) : void
281
    {
282 3
        $user       = $input->getOption('user');
283 3
        $repository = $input->getOption('repository');
284 3
        $milestone  = $input->getOption('milestone');
285 3
        $labels     = $input->getOption('label');
286
287 3
        if ($user !== null) {
288 3
            $changelogConfig->setUser($user);
289
        }
290
291 3
        if ($repository !== null) {
292 3
            $changelogConfig->setRepository($repository);
293
        }
294
295 3
        if ($milestone !== null) {
296 3
            $changelogConfig->setMilestone($milestone);
297
        }
298
299 3
        if ($labels === []) {
300 1
            return;
301
        }
302
303 2
        $changelogConfig->setLabels($labels);
304 2
    }
305
}
306