From bda5458e6c190a861417fb0239f86057c1d0c22f Mon Sep 17 00:00:00 2001 From: Marco Cesarato Date: Tue, 19 Jan 2021 23:05:37 +0100 Subject: [PATCH] feat: add breaking changes and issues references --- TODO.md | 2 -- src/Changelog.php | 63 +++++++++++++++++++++++++++++++------------ src/Commit/Body.php | 2 +- src/Commit/Footer.php | 30 +++++++++++++++++++++ src/Commit/Parser.php | 38 +++++++++++++++++++++----- 5 files changed, 109 insertions(+), 26 deletions(-) diff --git a/TODO.md b/TODO.md index ee1b49e..cdae731 100644 --- a/TODO.md +++ b/TODO.md @@ -1,7 +1,5 @@ # TODO -- Add `⚠ BREAKING CHANGES` list on top of the version release changes -- Add `Refs` and `Closes` on changes line - Automated bump version (if not specified version bump) - Major if find `BREAKING CHANGES` - Minor if find `feat` diff --git a/src/Changelog.php b/src/Changelog.php index 8f9bdbe..b5d315d 100644 --- a/src/Changelog.php +++ b/src/Changelog.php @@ -125,9 +125,6 @@ public function generate(InputInterface $input, SymfonyStyle $output): int $newVersion = '1.0.0'; } - // Remote url - $url = Git::getRemoteUrl(); - $options = []; // Git retrieve options per version if ($history) { @@ -215,24 +212,30 @@ public function generate(InputInterface $input, SymfonyStyle $output): int } } + // Changes groups sorting + $changes = ['breaking_changes' => []]; + foreach ($this->config->getTypes() as $key => $type) { + $changes[$key] = []; + } + // Group all changes to lists by type - $changes = []; $types = $this->config->getTypes(); foreach ($commits as $commit) { if (in_array($commit->getType(), $types)) { $itemKey = strtolower(preg_replace('/[^a-zA-Z0-9_-]+/', '', $commit->getDescription())); + $breakingChanges = $commit->getBreakingChanges(); $type = (string)$commit->getType(); $scope = $commit->getScope()->toPrettyString(); $hash = $commit->getHash(); - $changes[$type][$scope][$itemKey][$hash] = [ - 'description' => ucfirst($commit->getDescription()), - 'short' => $commit->getShortHash(), - 'url' => $url, - 'sha' => $hash, - ]; + if (!empty($breakingChanges)) { + $type = 'breaking_changes'; + } + $changes[$type][$scope][$itemKey][$hash] = $commit; } } + // Remote url + $url = Git::getRemoteUrl(); // Initialize changelogs $changelogNew .= "## [{$params['to']}]($url/compare/{$params['from']}...v{$params['to']}) ({$params['date']})\n\n"; // Add all changes list to new changelog @@ -272,42 +275,68 @@ public function generate(InputInterface $input, SymfonyStyle $output): int /** * Generate markdown from changes. + * + * @param Commit\Parser[][][][] $changes */ protected function getMarkdownChanges(array $changes): string { $changelog = ''; + // Remote url + $url = Git::getRemoteUrl(); // Add all changes list to new changelog foreach ($changes as $type => $list) { if (empty($list)) { continue; } + if ($type === 'breaking_changes') { + $label = '⚠ BREAKING CHANGES'; + } else { + $label = $this->config->getTypeLabel($type); + } + $changelog .= "\n### {$label}\n\n"; ksort($list); - $changelog .= PHP_EOL . '### ' . $this->config->getTypeLabel($type) . "\n\n"; foreach ($list as $scope => $items) { asort($items); if (is_string($scope) && !empty($scope)) { // scope section - $changelog .= PHP_EOL . "##### {$scope}" . "\n\n"; + $changelog .= "\n##### {$scope}\n\n"; } foreach ($items as $itemsList) { + $important = ''; $description = ''; $sha = ''; + $references = ''; $shaGroup = []; + $refsGroup = []; foreach ($itemsList as $item) { - $description = $item['description']; - if (!empty($item['sha'])) { - $shaGroup[] = "[{$item['short']}]({$item['url']}/commit/{$item['sha']})"; + $description = ucfirst($item->getDescription()); + $refs = $item->getReferences(); + + if ($item->isImportant()) { + $important = '**'; } + + if (!empty($refs)) { + foreach ($refs as $ref) { + $refsGroup[] = '[#' . $ref . "]({$url}/issue/{$ref})"; + } + } + if (!empty($item->getHash())) { + $shaGroup[] = '[' . $item->getShortHash() . "]({$url}/commit/" . $item->getHash() . ')'; + } + } + if (!empty($refsGroup)) { + $references = implode(', ', $refsGroup); } if (!empty($shaGroup)) { $sha = '(' . implode(', ', $shaGroup) . ')'; } - $changelog .= "* {$description} {$sha}\n"; + $changelog .= "* {$important}{$description}{$important} {$references} {$sha}\n"; } } } // Add version separator - $changelog .= PHP_EOL . '---' . "\n\n"; + $changelog .= "\n---\n\n"; return $changelog; } diff --git a/src/Commit/Body.php b/src/Commit/Body.php index 7a62d4d..30b33b7 100644 --- a/src/Commit/Body.php +++ b/src/Commit/Body.php @@ -18,6 +18,6 @@ public function __construct(string $content) public function __toString(): string { - return $this->content; + return ucfirst($this->content); } } diff --git a/src/Commit/Footer.php b/src/Commit/Footer.php index db65d7f..ede063c 100644 --- a/src/Commit/Footer.php +++ b/src/Commit/Footer.php @@ -26,6 +26,36 @@ public function __construct(string $token, string $value) $this->value = $value; } + public function getToken(): string + { + return strtolower($this->token); + } + + public function getValue(): string + { + return ucfirst($this->value); + } + + /** + * Get issues references. + */ + public function getReferences(): array + { + $refs = []; + $value = $this->getValue(); + if ($value[0] === '#') { + $values = explode(' ', $value); + foreach ($values as $val) { + $ref = ltrim($val, '#'); + if (is_numeric($ref)) { + $refs[] = $ref; + } + } + } + + return array_unique($refs); + } + public function __toString(): string { return $this->token . ': ' . $this->value; diff --git a/src/Commit/Parser.php b/src/Commit/Parser.php index 89057f0..82aab6c 100644 --- a/src/Commit/Parser.php +++ b/src/Commit/Parser.php @@ -8,7 +8,7 @@ class Parser implements Stringable { protected const PATTERN_HEADER = "/^(?'type'[a-z]+)(\((?'scope'.+)\))?(?'important'[!]?)[:][[:blank:]](?'description'.+)/iums"; - protected const PATTERN_FOOTER = "/(?'token'^([a-z0-9_-]+|BREAKING[[:blank:]]CHANGES?))(?'value'([:][[:blank:]]|[[:blank:]]\#(?=\w)).*?)$/iums"; + protected const PATTERN_FOOTER = "/(?'token'^([a-z0-9_-]+|BREAKING[[:blank:]]CHANGES?))(?'value'([:][[:blank:]]|[:]?[[:blank:]][#](?=\w)).*?)$/iums"; /** * Raw content. @@ -94,10 +94,10 @@ public function __construct(string $commit) protected function parseHeader(string $header) { preg_match(self::PATTERN_HEADER, $header, $matches); - $this->type = new Type($matches['type']); - $this->scope = new Scope($matches['scope']); + $this->type = new Type((string)$matches['type']); + $this->scope = new Scope((string)$matches['scope']); $this->important = !empty($matches['important']) ? true : false; - $this->description = new Description($matches['description']); + $this->description = new Description((string)$matches['description']); } /** @@ -110,8 +110,9 @@ protected function parseMessage(string $message) foreach ($matches as $match) { $footer = $match[0]; $body = str_replace($footer, '', $body); - $value = ltrim($match['value'], ':'); - $this->footers[] = new Footer($match['token'], $value); + $value = ltrim((string)$match['value'], ':'); + $value = Format::clean($value); + $this->footers[] = new Footer((string)$match['token'], $value); } } $body = Format::clean($body); @@ -171,6 +172,31 @@ public function getFooters(): array return $this->footers; } + public function getBreakingChanges(): array + { + $messages = []; + foreach ($this->footers as $footer) { + if ($footer->getToken() === 'breaking changes') { + $messages[] = $footer->getValue(); + } + } + + return $messages; + } + + /** + * Get issues references. + */ + public function getReferences(): array + { + $refs = []; + foreach ($this->footers as $footer) { + $refs = array_merge($footer->getReferences()); + } + + return array_unique($refs); + } + public function getHeader() { $header = $this->type;