From 28001b4fa9de6da256641a5cf377a72f281bbc2a Mon Sep 17 00:00:00 2001 From: Marco Cesarato Date: Sun, 17 Jan 2021 22:36:50 +0100 Subject: [PATCH] feat: add and implementat configuration moving default values from changelog class to it --- src/Changelog.php | 77 +++++++---------- src/Configuration.php | 191 +++++++++++++++++++++++++++++++++++++++++ src/DefaultCommand.php | 29 ++++++- 3 files changed, 249 insertions(+), 48 deletions(-) create mode 100644 src/Configuration.php diff --git a/src/Changelog.php b/src/Changelog.php index f1e6c14..84e7fc6 100644 --- a/src/Changelog.php +++ b/src/Changelog.php @@ -10,41 +10,17 @@ class Changelog { /** - * Changelog filename. - * - * @var string - */ - public static $fileName = 'CHANGELOG.md'; - - /** - * Types allowed on changelog and labels (preserve the order). - * - * @var string[][] - */ - public static $types = [ - 'feat' => ['code' => 'feat', 'label' => 'Features'], - 'perf' => ['code' => 'perf', 'label' => 'Performance Features'], - 'fix' => ['code' => 'fix', 'label' => 'Fixes'], - 'refactor' => ['code' => 'refactor', 'label' => 'Refactoring'], - 'docs' => ['code' => 'docs', 'label' => 'Docs'], - 'chore' => ['code' => 'chore', 'label' => 'Chores'], - ]; - - /** - * Changelog pattern. - * - * @var string + * @var Configuration */ - public static $header = "# Changelog\nAll notable changes to this project will be documented in this file.\n\n\n"; + protected $config; /** - * Ignore message commit patterns. - * - * @var string[] + * Changelog constructor. */ - public static $ignorePatterns = [ - '/^chore\(release\):/i', - ]; + public function __construct(Configuration $config) + { + $this->config = $config; + } /** * Generate changelog. @@ -70,24 +46,35 @@ public function generate(InputInterface $input, SymfonyStyle $output) // Exclude types if ($excludeChores) { - unset(self::$types['chore']); + unset($this->config->types['chore']); } if ($excludeRefactor) { - unset(self::$types['refactor']); + unset($this->config->types['refactor']); } // Initialize changelogs - $file = $root . DIRECTORY_SEPARATOR . self::$fileName; + $file = $root . DIRECTORY_SEPARATOR . $this->config->getFileName(); $changelogCurrent = ''; $changelogNew = ''; + $mainHeaderPrefix = "\n# "; + $mainHeaderSuffix = "\n\n\n"; + $mainHeaderContent = $this->config->getHeaderTitle() . "\n\n" . $this->config->getHeaderDescription(); + $mainHeader = $mainHeaderPrefix . $mainHeaderContent . $mainHeaderSuffix; + // Get changelogs content if (file_exists($file)) { - $header = ltrim(self::$header); - $header = preg_quote($header, '/'); $changelogCurrent = file_get_contents($file); $changelogCurrent = ltrim($changelogCurrent); - $changelogCurrent = preg_replace("/^$header/i", '', $changelogCurrent); + + // Remove header + $beginHeader = preg_quote($mainHeaderPrefix, '/'); + $endHeader = preg_quote($mainHeaderSuffix, '/'); + + $pattern = '/^(' . $beginHeader . '(.*)' . $endHeader . ')/si'; + $pattern = preg_replace(['/[\n]+/', '/[\s]+/'], ['[\n]+', '[\s]+'], $pattern); + + $changelogCurrent = preg_replace($pattern, '', $changelogCurrent); } // Current Dates @@ -199,7 +186,7 @@ public function generate(InputInterface $input, SymfonyStyle $output) } // Check ignored commit $ignore = false; - foreach (self::$ignorePatterns as $pattern) { + foreach ($this->config->getIgnorePatterns() as $pattern) { if (preg_match($pattern, $head)) { $ignore = true; break; @@ -217,24 +204,24 @@ public function generate(InputInterface $input, SymfonyStyle $output) // Changes groups $changes = []; - foreach (self::$types as $key => $type) { + foreach ($this->config->getTypes() as $key => $type) { $changes[$key] = []; } // Group all changes to lists by type foreach ($commits as $commit) { - foreach (self::$types as $key => $type) { + foreach ($this->config->getTypes() as $name => $type) { $head = Utils::clean($commit['head']); - $code = preg_quote($type['code'], '/'); + $code = preg_quote($name, '/'); if (preg_match('/^' . $code . '(\(.*?\))?[:]?\\s/i', $head)) { - $parse = $this->parseCommitHead($head, $type['code']); + $parse = $this->parseCommitHead($head, $name); $scope = $parse['scope']; $description = $parse['description']; $sha = $commit['sha']; $short = substr($sha, 0, 6); // List item $itemKey = strtolower(preg_replace('/[^a-zA-Z0-9_-]+/', '', $description)); - $changes[$key][$scope][$itemKey][$sha] = [ + $changes[$name][$scope][$itemKey][$sha] = [ 'description' => $description, 'short' => $short, 'url' => $url, @@ -251,7 +238,7 @@ public function generate(InputInterface $input, SymfonyStyle $output) } // Save new changelog prepending the current one - file_put_contents($file, self::$header . "{$changelogNew}{$changelogCurrent}"); + file_put_contents($file, $mainHeader . "{$changelogNew}{$changelogCurrent}"); $output->success("Changelog generated to: {$file}"); // Create commit and add tag @@ -281,7 +268,7 @@ protected function getMarkdownChanges($changes) continue; } ksort($list); - $changelog .= PHP_EOL . '### ' . self::$types[$type]['label'] . "\n\n"; + $changelog .= PHP_EOL . '### ' . $this->config->getTypeLabel($type) . "\n\n"; foreach ($list as $scope => $items) { asort($items); if (is_string($scope) && !empty($scope)) { diff --git a/src/Configuration.php b/src/Configuration.php new file mode 100644 index 0000000..9303ad9 --- /dev/null +++ b/src/Configuration.php @@ -0,0 +1,191 @@ + ['label' => 'Features'], + 'perf' => ['label' => 'Performance Features'], + 'fix' => ['label' => 'Fixes'], + 'refactor' => ['label' => 'Refactoring'], + 'docs' => ['label' => 'Docs'], + 'chore' => ['label' => 'Chores'], + ]; + + /** + * Ignore message commit patterns. + * + * @var string[] + */ + public $ignorePatterns = [ + '/^chore\(release\):/i', + ]; + + /** + * Constructor. + */ + public function __construct(array $settings = []) + { + $this->fromArray($settings); + } + + /** + * From array. + * + * @param $array + */ + public function fromArray(array $array) + { + if (empty($array)) { + return; + } + + $defaults = [ + 'types' => $this->types, + 'excludedTypes' => [], + 'headerTitle' => $this->headerTitle, + 'headerDescription' => $this->headerDescription, + 'fileName' => $this->fileName, + 'ignorePatterns' => $this->ignorePatterns, + ]; + + $params = array_replace_recursive($defaults, $array); + + if (is_array($params['excludedTypes'])) { + foreach ($params['excludedTypes'] as $type) { + unset($params['types'][$type]); + } + } + + foreach ($params['ignorePatterns'] as $key => $pattern) { + if (!$this->isRegex($pattern)) { + $params['ignorePatterns'][$key] = '#' . preg_quote($pattern, '#') . '#i'; + } + } + + $this->setTypes($params['types']); + $this->setHeaderTitle($params['headerTitle']); + $this->setHeaderDescription($params['headerDescription']); + $this->setFileName($params['fileName']); + $this->setIgnorePatterns($params['ignorePatterns']); + } + + /** + * Check if is regex. + * + * @param $pattern + * + * @return bool + */ + protected function isRegex(string $pattern) + { + return @preg_match($pattern, null) !== false; + } + + /** + * @return string[][] + */ + public function getTypes(): array + { + return $this->types; + } + + /** + * @param $type + */ + public function getTypeLabel(string $type): string + { + return $this->types[$type]['label']; + } + + /** + * @param string[][] $types + */ + public function setTypes(array $types): Configuration + { + $this->types = $types; + + return $this; + } + + public function getFileName(): string + { + return $this->fileName; + } + + public function setFileName(string $fileName): Configuration + { + $this->fileName = $fileName; + + return $this; + } + + public function getHeaderTitle(): string + { + return $this->headerTitle; + } + + public function setHeaderTitle(string $headerTitle): Configuration + { + $this->headerTitle = $headerTitle; + + return $this; + } + + public function getHeaderDescription(): string + { + return $this->headerDescription; + } + + public function setHeaderDescription(string $headerDescription): Configuration + { + $this->headerDescription = $headerDescription; + + return $this; + } + + /** + * @return string[] + */ + public function getIgnorePatterns(): array + { + return $this->ignorePatterns; + } + + /** + * @param string[] $ignorePatterns + */ + public function setIgnorePatterns(array $ignorePatterns): Configuration + { + $this->ignorePatterns = $ignorePatterns; + + return $this; + } +} diff --git a/src/DefaultCommand.php b/src/DefaultCommand.php index 5e09efe..cd87cc1 100644 --- a/src/DefaultCommand.php +++ b/src/DefaultCommand.php @@ -11,6 +11,31 @@ class DefaultCommand extends Command { + /** + * Command name. + * + * @var string + */ + protected static $defaultName = 'changelog'; + + /** + * Changelog. + * + * @var Changelog + */ + public $changelog; + + /** + * Constructor. + */ + public function __construct(array $settings = []) + { + parent::__construct(self::$defaultName); + + $config = new Configuration($settings); + $this->changelog = new Changelog($config); + } + /** * Configure. * @@ -19,7 +44,6 @@ class DefaultCommand extends Command protected function configure() { $this - ->setName('changelog') ->setDescription('Generate changelogs and release notes from a project\'s commit messages' . 'and metadata and automate versioning with semver.org and conventionalcommits.org') ->setDefinition([ @@ -45,9 +69,8 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $changelog = new Changelog(); $outputStyle = new SymfonyStyle($input, $output); - return $changelog->generate($input, $outputStyle); + return $this->changelog->generate($input, $outputStyle); } }