CommitCommand::createBody()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 3
dl 0
loc 8
ccs 5
cts 5
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
4
namespace Damianopetrungaro\PHPCommitizen;
5
6
use Damianopetrungaro\PHPCommitizen\Exception\InvalidArgumentException;
7
use Damianopetrungaro\PHPCommitizen\Section\Body;
8
use Damianopetrungaro\PHPCommitizen\Section\Description;
9
use Damianopetrungaro\PHPCommitizen\Section\Footer;
10
use Damianopetrungaro\PHPCommitizen\Section\Scope;
11
use Damianopetrungaro\PHPCommitizen\Section\Subject;
12
use Damianopetrungaro\PHPCommitizen\Section\Type;
13
use Symfony\Component\Console\Command\Command;
14
use Symfony\Component\Console\Helper\QuestionHelper;
15
use Symfony\Component\Console\Input\InputInterface;
16
use Symfony\Component\Console\Output\OutputInterface;
17
use Symfony\Component\Console\Question\ChoiceQuestion;
18
use Symfony\Component\Console\Question\Question;
19
use function file_exists;
20
use function is_array;
21
22
class CommitCommand extends Command
23
{
24
    private const COMMAND_NAME = 'commit';
25
26
    private const COMMAND_DESCRIPTION = 'Create a new commit following conventional commits specs (https://conventionalcommits.org/)';
27
28
    private const OPTION_ADD_FILE_TO_STAGE_NAME = 'all';
29
30
    private const OPTION_ADD_FILE_TO_STAGE_SHORT_NAME = 'a';
31
32
    private const OPTION_ADD_FILE_TO_STAGE_DESCRIPTION = 'All the unstaged files will be added before creating the commit';
33
34
    private const ARGUMENT_PATH_TO_CONFIGURATION = 'config';
35
36
    private const ARGUMENT_PATH_TO_CONFIGURATION_DESCRIPTION = 'Specify a php file for override default configuration';
37
38
    private const EXTRA_KEY_NAME = 'personalized';
39
40
    /**
41
     * @var array
42
     */
43
    private $configuration;
44
45
    /**
46
     * @var QuestionHelper
47
     */
48
    private $questionHelper;
49
50
    /**
51
     * @var CreateConventionalCommit
52
     */
53
    private $createConventionalCommit;
54
55 3
    public function __construct(
56
        array $configuration,
57
        CreateConventionalCommit $createConventionalCommit,
58
        QuestionHelper $questionHelper = null
59
    )
60
    {
61 3
        parent::__construct(null);
62 3
        $this->configuration = $configuration;
63 3
        $this->createConventionalCommit = $createConventionalCommit;
64 3
        $this->questionHelper = $questionHelper ?: new QuestionHelper();
65 3
    }
66
67 3
    protected function configure(): void
68
    {
69 3
        $this->setName(self::COMMAND_NAME);
70 3
        $this->setDescription(self::COMMAND_DESCRIPTION);
71 3
        $this->addArgument(
72 3
            self::ARGUMENT_PATH_TO_CONFIGURATION,
73 3
            null,
74 3
            self::ARGUMENT_PATH_TO_CONFIGURATION_DESCRIPTION
75
        );
76 3
        $this->addOption(
77 3
            self::OPTION_ADD_FILE_TO_STAGE_NAME,
78 3
            self::OPTION_ADD_FILE_TO_STAGE_SHORT_NAME,
79 3
            null,
80 3
            self::OPTION_ADD_FILE_TO_STAGE_DESCRIPTION
81
        );
82 3
    }
83
84 3
    protected function execute(InputInterface $input, OutputInterface $output)
85
    {
86 3
        $configuration = $this->loadConfiguration($input->getArgument(self::ARGUMENT_PATH_TO_CONFIGURATION));
87
88
        try {
89 1
            $type = $this->createType($input, $output, $configuration);
90 1
            $scope = $this->createScope($input, $output, $configuration);
91 1
            $description = $this->createDescription($input, $output, $configuration);
92 1
            $subject = Subject::build($type, $scope, $description, $configuration);
93 1
            $body = $this->createBody($input, $output, $configuration);
94 1
            $footer = $this->createFooter($input, $output, $configuration);
95 1
            $addAll = $input->getOption(self::OPTION_ADD_FILE_TO_STAGE_NAME);
96 1
            ($this->createConventionalCommit)($subject, $body, $footer, $addAll);
97
        } catch (InvalidArgumentException $e) {
98
            $output->writeln("<error>{$e->getMessage()}</error>");
99
        }
100 1
    }
101
102 3
    private function loadConfiguration(?string $customConfigurationPath): Configuration
103
    {
104 3
        if ($customConfigurationPath === null) {
105
            return Configuration::fromArray($this->configuration);
106
        }
107
108 3
        if (!file_exists($customConfigurationPath)) {
109 1
            throw new InvalidArgumentException("Custom configuration file does not exists: '$customConfigurationPath'");
110
        }
111
112 2
        $customConfiguration = require $customConfigurationPath;
113
114 2
        if (!is_array($customConfiguration)) {
115 1
            throw new InvalidArgumentException('Custom configuration file must return an array');
116
        }
117
118 1
        return Configuration::fromArray(array_merge($this->configuration, $customConfiguration));
119
    }
120
121 1
    private function createType(InputInterface $input, OutputInterface $output, Configuration $configuration): Type
122
    {
123 1
        $output->writeln('<comment>Commits MUST be prefixed with a type, which consists of a noun, feat, fix, etc.</comment>');
124 1
        $output->writeln("<comment>Type length must be between {$configuration->minLengthType()} and {$configuration->maxLengthType()}</comment>");
125
126 1
        $typeValues = $configuration->types();
127 1
        if ($configuration->acceptExtraType() === true) {
128
            $typeValues[] = self::EXTRA_KEY_NAME;
129
        }
130
131 1
        if ($typeValues !== [self::EXTRA_KEY_NAME]) {
132 1
            $choice = new ChoiceQuestion("<question>Select commit's type:</question> ", $typeValues);
133 1
            $typeInput = $this->questionHelper->ask($input, $output, $choice);
134
        }
135
136 1
        if (!isset($typeInput) || $typeInput === self::EXTRA_KEY_NAME) {
137
            $question = new Question("<question>Enter a custom commit's type:</question> ", '');
138
            $typeInput = $this->questionHelper->ask($input, $output, $question);
139
        }
140
141 1
        return Type::build($typeInput, $configuration);
142
    }
143
144 1
    private function createScope(InputInterface $input, OutputInterface $output, Configuration $configuration): ?Scope
145
    {
146 1
        $scopeValues = $configuration->scopes();
147 1
        if ($scopeValues === [] && $configuration->acceptExtraScope() === false) {
148
            return null;
149
        }
150
151 1
        $output->writeln('<comment>An optional scope MAY be provided after a type. A scope is a phrase describing a section of the codebase.</comment>');
152 1
        $output->writeln("<comment>Scope length MUST be between {$configuration->minLengthScope()} and {$configuration->maxLengthScope()}</comment>");
153
154 1
        if ($configuration->acceptExtraScope() === true) {
155 1
            $scopeValues[] = self::EXTRA_KEY_NAME;
156
        }
157
158 1
        if ($scopeValues !== [self::EXTRA_KEY_NAME] && $scopeValues !== []) {
159
            $choice = new ChoiceQuestion("<question>Select commit's scope:</question> ", $scopeValues);
160
            $scopeInput = $this->questionHelper->ask($input, $output, $choice);
161
        }
162
163 1
        if (!isset($scopeInput) || $scopeInput === self::EXTRA_KEY_NAME) {
164 1
            $question = new Question("<question>Enter a custom commit's scope:</question> ", '');
165 1
            $scopeInput = $this->questionHelper->ask($input, $output, $question);
166
        }
167
168 1
        if ($scopeInput === '') {
169
170
            return null;
171
        }
172
173 1
        return Scope::build($scopeInput, $configuration);
174
    }
175
176 1
    private function createDescription(InputInterface $input, OutputInterface $output, Configuration $configuration): Description
177
    {
178 1
        $output->writeln('<comment>A description MUST immediately follow the type/scope prefix. The description is a short description of the changes</comment>');
179 1
        $output->writeln("<comment>Description length MUST be between {$configuration->minLengthDescription()} and {$configuration->maxLengthDescription()}</comment>");
180
181 1
        $question = new Question("<question>Enter a custom commit's description:</question> ", '');
182 1
        $descriptionInput = $this->questionHelper->ask($input, $output, $question);
183 1
        return Description::build($descriptionInput, $configuration);
184
    }
185
186 1
    private function createBody(InputInterface $input, OutputInterface $output, Configuration $configuration): Body
187
    {
188 1
        $output->writeln('<comment>A longer commit body MAY be provided after the short description.</comment>');
189
190 1
        $question = new Question("<question>Enter commit's body:</question> ", '');
191 1
        $bodyInput = $this->questionHelper->ask($input, $output, $question);
192 1
        return Body::build($bodyInput, $configuration);
193
    }
194
195 1
    private function createFooter(InputInterface $input, OutputInterface $output, Configuration $configuration): Footer
196
    {
197 1
        $output->writeln('<comment>A footer MAY be provided one blank line after the body. The footer SHOULD contain additional meta-information about the changes(such as the issues it fixes, e.g., fixes #13, #5).</comment>');
198
199 1
        $question = new Question("<question>Enter commit's footer:</question> ", '');
200 1
        $footerInput = $this->questionHelper->ask($input, $output, $question);
201 1
        return Footer::build($footerInput, $configuration);
202
    }
203
}
204