package App::Changelord; use 5.36.0; use Moo; use CLI::Osprey; use YAML; use List::AllUtils qw/ pairmap partition_by /; option source => ( is => 'ro', format => 's', doc => 'changelog yaml file', default => 'CHANGELOG.yml', ); has changelog => ( lazy => 1, is => 'ro', default => sub($self) { return YAML::LoadFile($self->source) } ); has change_types => ( is => 'ro', default => sub($self) { return [ { title => 'Features' , level => 'minor', keywords => [ 'feat' ] } , { title => 'Bug fixes' , level => 'patch', keywords => [ 'fix' ] }, ] } ); sub render_header($self) { my $output = "# Changelog"; my $name = $self->changelog->{project}{name}; my %links = (); if( $self->changelog->{project}{homepage} ) { $name = "[$name][homepage]"; $links{homepage} = $self->changelog->{project}{homepage}; } $output .= " for $name" if $name; if(%links) { $output .= "\n\n"; $output .= $self->render_refs(%links); } $output .= "\n\n"; } sub render_refs($self,%links) { my $output = ''; for my $ref ( sort keys %links ) { $output .= " [$ref]: $links{$ref}\n" } return $output . "\n"; } sub as_markdown($self) { my $changelog = $self->changelog; my $output = $self->render_header; my $n = 0; $output .= join "\n", map { $self->render_release($_, $n++) } $changelog->{releases}->@*; return $output; } sub render_release($self, $release, $n=0) { # it's a string? Okay then! return $release unless ref $release; my $version = $release->{version} || ( $n ? '???' : 'NEXT' ); my $date = $release->{date}; my $output = ''; $output .= "## $version"; $output .= ", $date" if $date; $output .= "\n"; if( $release->{changes} ) { my @changes = map { ref ? $_ : { desc => $_ } } $release->{changes}->@*; my @keywords = map { $_->{keywords}->@* } $self->change_types->@*; # find the generics my @generics = grep { my $type = $_->{type}; my $res = !$type; if( $type and not grep { $type eq $_} @keywords ) { $res = 1; warn "change type '$type' is not recognized\n"; } $res; } @changes; $output .= "\n" if @generics; $output .= " * $_->{desc}\n" for @generics; my %keyword_mapping = map { my $title = $_->{title}; map { $_ => $title } $_->{keywords}->@*; } $self->change_types->@*; my %groups = partition_by { no warnings qw/ uninitialized /; $keyword_mapping{$_->{type}} || '' } @changes; for my $type ( $self->change_types->@* ) { my $c = $groups{$type->{title}} or next; $output .= "\n### $type->{title}\n\n"; $output .= $self->render_change($_) for $c->@*; } } return $output . "\n"; } sub render_change($self, $change) { return " * " . $change->{desc} . "\n"; } sub run($self) { no warnings 'utf8'; print $self->as_markdown; } subcommand 'schema' => 'App::Changelord::Command::Schema'; subcommand 'validate' => 'App::Changelord::Command::Validate'; 'end of App::Changeman';