App-Changelog

 view release on metacpan or  search on metacpan

lib/App/Changelog.pm  view on Meta::CPAN

package App::Changelog;

use strict;
use warnings;

use feature 'say';

our $VERSION = '1.0.2';

sub new {
    my ( $class, %args ) = @_;
    my $self = {
        output_file  => $args{output_file} || 'CHANGELOG.md',
        compact      => $args{compact} // 1,
        filter_tag   => $args{filter_tag} || '',
        conventional => $args{conventional} // 0,
    };
    bless $self, $class;
    return $self;
}

sub generate_changelog {
    my ($self) = @_;

    say "Generating changelog from Git history...";

    my $git_log_format =
      $self->{compact} ? '--pretty=format:"%h %s"' : '--pretty=fuller';
    if ( $self->{conventional} ) {
        $git_log_format = '--pretty=format:"%h %s (%an)"';
    }

    my $git_log =
      $self->_run_git_command("git log $git_log_format --abbrev-commit");
    if ( !$git_log ) {
        die
"Error: Could not retrieve Git history. Are you in a Git repository?\n";
    }

    my @tags = $self->_get_tags();
    my $changelog_content =
      $self->_build_changelog_content( \@tags, $git_log_format );

    $self->_write_to_file($changelog_content);
    say "Changelog generated successfully in $self->{output_file}.";
}

sub _build_changelog_content {
    my ( $self, $tags, $format ) = @_;
    my $content = "# Changelog\n\n";

    for my $i ( 0 .. $#$tags ) {
        my $current_tag  = $tags->[$i];
        my $previous_tag = $i == $#$tags ? '' : $tags->[ $i + 1 ];

        my $log_command =
          $previous_tag
          ? "git log $previous_tag..$current_tag $format"
          : "git log $current_tag $format";

        my $logs = $self->_run_git_command($log_command);
        $logs = $self->_filter_conventional_commits($logs)
          if $self->{conventional};

        my $date =
          $self->_run_git_command("git log -1 --format=%ai $current_tag");
        $date =~ s/\s.*$//;

        $content .= "## [$current_tag] - $date\n\n";

        unless ( $self->{conventional} ) {
            $content .= "$logs\n" if $logs;
        }
        else {
            my %grouped_commits;
            my @log_lines = split( "\n", $logs );

            for my $log (@log_lines) {
                if ( $log =~ /^[a-f0-9]+\s([a-z]+):\s*(.*)$/ ) {
                    my $type    = $1;
                    my $message = $2;

                    push @{ $grouped_commits{$type} }, $message;
                }
            }

            for my $type ( sort keys %grouped_commits ) {
                $content .= "### " . ucfirst($type) . "\n";
                for my $message ( @{ $grouped_commits{$type} } ) {
                    $content .= "- $type: $message\n";
                }
                $content .= "\n";
            }
        }
    }

    return $content;
}

sub _filter_conventional_commits {
    my ( $self, $logs ) = @_;
    my @lines = split( /\n/, $logs );
    my @filtered;

    foreach my $line (@lines) {



( run in 2.440 seconds using v1.01-cache-2.11-cpan-364913b4093 )