diff --git a/CHANGELOG.yml b/CHANGELOG.yml index a42d24e..2fd4b4f 100644 --- a/CHANGELOG.yml +++ b/CHANGELOG.yml @@ -4,14 +4,16 @@ project: homepage: https://git.babyl.ca/yanick/App-Changelord releases: - version: ~ + date: ~ changes: - Initial release + - desc: Doing the thing change_types: - - feat: - level: minor - title: Features - keywords: [] - - fix: - level: patch - title: Bug fixes - keywords: [] + - keywords: + - feat + level: minor + title: Features + - keywords: + - fix + level: patch + title: Bug fixes diff --git a/lib/App/Changelord.pm b/lib/App/Changelord.pm index 3c2d1bf..2a91a2e 100644 --- a/lib/App/Changelord.pm +++ b/lib/App/Changelord.pm @@ -1,39 +1,47 @@ package App::Changelord; +# SYNOPSIS: cli-based changelog manager # version next latest use 5.36.0; use Moo; -use CLI::Osprey; +use CLI::Osprey + desc => 'changelog manager'; + use YAML; use List::AllUtils qw/ pairmap partition_by /; use App::Changelord::Role::ChangeTypes; -option source => ( - is => 'ro', - format => 's', - doc => 'changelog yaml file', - default => 'CHANGELOG.yml', -); - -has changelog => ( is => 'lazy' ); - -sub _build_changelog($self) { - return YAML::LoadFile($self->source) -} - -with 'App::Changelord::Role::ChangeTypes'; -with 'App::Changelord::Role::Render'; - sub run($self) { - no warnings 'utf8'; - print $self->as_markdown; + App::Changelord::Command::Print->new( + parent_command => $self, + )->run; } subcommand $_ => 'App::Changelord::Command::' . ucfirst $_ =~ s/-(.)/uc $1/er - for qw/ schema validate version bump init add git-gather /; + for qw/ schema validate version bump init add git-gather print /; 1; + +__END__ + +=head1 DESCRIPTION + +C offers a collection of cli commands to +interact with a YAML-based CHANGELOG file format, from which +a Markdown CHANGELOG fit for general comsumption can be generated. + +See the original blog entry in the C section for the full +motivation. + +For a list of the commands, C, then to +get information on the individual commands C. + +=head1 SEE ALSO + +L - the introducing blog entry. + + diff --git a/lib/App/Changelord/Command/Add.pm b/lib/App/Changelord/Command/Add.pm index 9074fb7..1fd3209 100644 --- a/lib/App/Changelord/Command/Add.pm +++ b/lib/App/Changelord/Command/Add.pm @@ -3,15 +3,17 @@ package App::Changelord::Command::Add; use 5.36.0; use Moo; -use CLI::Osprey desc => 'add a change to the changelog'; +use CLI::Osprey + desc => 'add a change to the NEXT release', + description_pod => <<'END'; +Add a change entry to the NEXT release. +END use PerlX::Maybe; use Path::Tiny; use App::Changelord::Command::Init; -has changelog => ( is => 'lazy' ); - -sub _build_changelog ($self) { $self->parent_command->changelog } +with 'App::Changelord::Role::Changelog'; # TODO validate the type option type => ( @@ -48,7 +50,7 @@ sub next_release($self) { } sub save_changelog($self) { - my $src = $self->parent_command->source; + my $src = $self->source; path($src)->spew( App::Changelord::Command::Init::serialize_changelog($self) ); } @@ -63,6 +65,8 @@ sub run ($self) { }; $self->save_changelog; + + say "change added to the changelog"; } 'end of App::Changelog::Command::Add'; diff --git a/lib/App/Changelord/Command/Bump.pm b/lib/App/Changelord/Command/Bump.pm index aaf25e3..ddea6ac 100644 --- a/lib/App/Changelord/Command/Bump.pm +++ b/lib/App/Changelord/Command/Bump.pm @@ -3,7 +3,11 @@ package App::Changelord::Command::Bump; use 5.36.0; use Moo; -use CLI::Osprey desc => 'bump next version'; +use CLI::Osprey desc => 'bump next version', +description_pod => <<'END'; +Set a version for the NEXT release based on the types of its changes. +Also set the release date of that release to today. +END use Path::Tiny; use JSON; @@ -11,15 +15,11 @@ use YAML qw/ Bless /; use List::AllUtils qw/ first min uniq /; use Version::Dotted::Semantic; +with 'App::Changelord::Role::Changelog'; with 'App::Changelord::Role::ChangeTypes'; - -has changelog => ( is => 'lazy' ); - with 'App::Changelord::Role::Versions'; with 'App::Changelord::Role::Stats'; -sub _build_changelog ($self) { $self->parent_command->changelog } - sub run ($self) { my $bump = shift @ARGV; @@ -73,7 +73,7 @@ sub run ($self) { Bless($_)->keys( [ uniq qw/ version date changes /, sort keys %$_ ] ); } - path( $self->parent_command->source )->spew( YAML::Dump($change) ); + path( $self->source )->spew( YAML::Dump($change) ); say "new version minted: $version"; } diff --git a/lib/App/Changelord/Command/GitGather.pm b/lib/App/Changelord/Command/GitGather.pm index 8d0f8e8..94385fa 100644 --- a/lib/App/Changelord/Command/GitGather.pm +++ b/lib/App/Changelord/Command/GitGather.pm @@ -31,7 +31,7 @@ Git messages are compared to the regular expression configured at `project.commit_regex`. If none is found, it defaults to - ^(?[^:]+):(?.*?)(\[(?[^\]]+)\])?$ + ^(?[^: ]+):(?.*?)(\[(?[^\]]+)\])?$ The regular expression must capture a C field, and may capture a C and C as well. @@ -41,10 +41,7 @@ END_POD use Path::Tiny; use Git::Repository; -has changelog => ( is => 'lazy' ); - -sub _build_changelog ($self) { $self->parent_command->changelog } - +with 'App::Changelord::Role::Changelog'; with 'App::Changelord::Role::Versions'; with 'App::Changelord::Role::ChangeTypes'; @@ -59,7 +56,7 @@ has commit_regex => ( sub _build_commit_regex($self) { my $regex = $self->changelog->{project}{commit_regex}; - my $default = '^(?[^:]+):(?.*?)(\[(?[^\]]+)\])?$'; + my $default = '^(?[^: ]+):(?.*?)(\[(?[^\]]+)\])?$'; if(!$regex) { warn "project.commit_regex not configured, using the default /$default/\n"; $regex = $default; @@ -69,7 +66,7 @@ sub _build_commit_regex($self) { sub lower_bound($self) { # either the most recent commit in the current release - my @sha1s = grep { $_ } map { $_->{commit} } $self->next_release->{changes}->@*; + my @sha1s = grep { $_ } map { $_->{commit} } grep { ref } $self->next_release->{changes}->@*; return pop @sha1s if @sha1s; @@ -92,7 +89,7 @@ sub munge_message($self,$message) { } sub save_changelog($self) { - my $src = $self->parent_command->source; + my $src = $self->source; path($src)->spew( App::Changelord::Command::Init::serialize_changelog($self) ); } @@ -104,7 +101,7 @@ sub run ($self) { # figure out lower bound my $from = $self->lower_bound; - say "checking from ", ( $from || 'the dawn of time' ), " on"; + say "checking since ", ( $from || 'the dawn of time' ); my @messages = map { $self->munge_message($_) } $self->get_commits($from); @@ -121,7 +118,7 @@ sub run ($self) { $self->save_changelog; - say $self->parent_command->source, " updated"; + say $self->source, " updated"; } 1; diff --git a/lib/App/Changelord/Command/Init.pm b/lib/App/Changelord/Command/Init.pm index 4108ac8..a35b47e 100644 --- a/lib/App/Changelord/Command/Init.pm +++ b/lib/App/Changelord/Command/Init.pm @@ -12,13 +12,9 @@ use List::AllUtils qw/ first min uniq /; use Version::Dotted::Semantic; with 'App::Changelord::Role::ChangeTypes'; - -has changelog => ( is => 'lazy' ); - +with 'App::Changelord::Role::Changelog'; with 'App::Changelord::Role::Versions'; -sub _build_changelog ($self) { $self->parent_command->changelog } - sub serialize_changelog($self, $changelog = undef) { $changelog //= $self->changelog; @@ -42,7 +38,7 @@ sub serialize_changelog($self, $changelog = undef) { } sub run ($self) { - my $src = $self->parent_command->source; + my $src = $self->source; die "file '$src' already exists, aborting\n" if -f $src; my $change = { @@ -51,7 +47,7 @@ sub run ($self) { homepage => undef, with_stats => 'true', ticket_url => undef, - commit_regex => /^(?[^:]+):(?.*?)(\[(?[^\]]+)\])?$/, + commit_regex => q/^(?[^: ]+):(?.*?)(\[(?[^\]]+)\])?$/, }, change_types => $self->change_types, releases => [ diff --git a/lib/App/Changelord/Command/Print.pm b/lib/App/Changelord/Command/Print.pm new file mode 100644 index 0000000..54b586e --- /dev/null +++ b/lib/App/Changelord/Command/Print.pm @@ -0,0 +1,39 @@ +package App::Changelord::Command::Print; + +use 5.36.0; + +use Moo; +use CLI::Osprey + desc => 'print the changelog', + description_pod => <<'END'; +Render the full changelog. The default is to render the changelog +in markdow, but the option C<--json> can be used to have a JSON +version instead. + +To generate the changelog without the NEXT release, uses the +C<--no-next> option. +END + +with 'App::Changelord::Role::Changelog'; +with 'App::Changelord::Role::ChangeTypes'; +with 'App::Changelord::Role::Render'; + +option json => ( + is => 'ro', + default => 0, + doc => 'output schema as json', +); + +option next => ( + is => 'ro', + default => 1, + negatable => 1, + doc => 'include the NEXT release. Defaults to true.', +); + +sub run($self) { + no warnings 'utf8'; + print $self->as_markdown( $self->next ); +} + +'end of App::Changelog::Command::Print'; diff --git a/lib/App/Changelord/Command/Schema.pm b/lib/App/Changelord/Command/Schema.pm index dd65e3b..a32ccb5 100644 --- a/lib/App/Changelord/Command/Schema.pm +++ b/lib/App/Changelord/Command/Schema.pm @@ -4,7 +4,14 @@ package App::Changelord::Command::Schema; use 5.36.0; use Moo; -use CLI::Osprey; +use CLI::Osprey + doc => 'print JSON schema for the changelog format', + description_pod => <<'END'; +Print the JSON schema describing the data format used by changelord. + +By defaults prints the schema in YAML. Can also be printed as JSON +via the C<--json> option. +END use Path::Tiny; use JSON; diff --git a/lib/App/Changelord/Command/Validate.pm b/lib/App/Changelord/Command/Validate.pm index f86181c..9701fac 100644 --- a/lib/App/Changelord/Command/Validate.pm +++ b/lib/App/Changelord/Command/Validate.pm @@ -3,13 +3,19 @@ package App::Changelord::Command::Validate; use 5.36.0; use Moo; -use CLI::Osprey; +use CLI::Osprey + doc => 'validate the changelog yaml', + description_pod => <<'END'; +Validate the changelog against the JSON Schema used by changelord. +END use Path::Tiny; use JSON; use YAML::XS; use JSON::Schema::Modern; +with 'App::Changelord::Role::Changelog'; + option json => ( is => 'ro', default => 0, @@ -24,7 +30,7 @@ sub run($self) { my $result = JSON::Schema::Modern->new( output_format => 'detailed', )->evaluate( - $self->parent_command->changelog, + $self->changelog, YAML::XS::Load($schema), ); diff --git a/lib/App/Changelord/Command/Version.pm b/lib/App/Changelord/Command/Version.pm index c616917..af58493 100644 --- a/lib/App/Changelord/Command/Version.pm +++ b/lib/App/Changelord/Command/Version.pm @@ -13,38 +13,9 @@ use YAML::XS; use List::AllUtils qw/ first min /; use Version::Dotted::Semantic; +with 'App::Changelord::Role::Changelog'; with 'App::Changelord::Role::ChangeTypes'; - -has changelog => ( - is => 'lazy' -); - -sub _build_changelog($self){ $self->parent_command->changelog } - -sub latest_version($self){ - first { $_ } grep { $_ ne 'NEXT' } map { eval { $_->{version} } } $self->changelog->{releases}->@*; -} - -sub next_version($self) { - my $version = Version::Dotted::Semantic->new($self->latest_version // '0.0.0'); - - my $upcoming = $self->changelog->{releases}[0]; - - if( $upcoming->{version} and $upcoming->{version} ne 'NEXT') { - $upcoming = { changes => [] }; - } - - my %mapping = map { - my $level = $_->{level}; - map { $_ => $level } $_->{keywords}->@* - } $self->change_types->@*; - - my $bump =min 2, map { $_ eq 'major' ? 0 : $_ eq 'minor' ? 1 : 2 } map { $mapping{$_->{type}} || 'patch' } $upcoming->{changes}->@*; - - $version->bump($bump); - - return $version->normal; -} +with 'App::Changelord::Role::Versions'; sub run($self) { my $param = shift @ARGV; diff --git a/lib/App/Changelord/Command/changelog-schema.yml b/lib/App/Changelord/Command/changelog-schema.yml index 0f6aced..409587d 100644 --- a/lib/App/Changelord/Command/changelog-schema.yml +++ b/lib/App/Changelord/Command/changelog-schema.yml @@ -42,16 +42,18 @@ properties: - type: object additionalProperties: false properties: - version: { type: string } + version: { type: [ 'null', string ] } date: { type: ['null',string] } changes: { type: 'array', items: { $ref: '#/$defs/change' } } $defs: change: - type: object - required: [ desc ] - additionalProperties: false - properties: - desc: { type: string } - ticket: { type: [ string, 'null' ] } - type: { type: [ string, 'null' ] } - commit: { type: [ string, 'null' ] } + oneOf: + - type: string + - type: object + required: [ desc ] + additionalProperties: false + properties: + desc: { type: string } + ticket: { type: [ string, 'null' ] } + type: { type: [ string, 'null' ] } + commit: { type: [ string, 'null' ] } diff --git a/lib/App/Changelord/Role/Changelog.pm b/lib/App/Changelord/Role/Changelog.pm new file mode 100644 index 0000000..cc6d49a --- /dev/null +++ b/lib/App/Changelord/Role/Changelog.pm @@ -0,0 +1,21 @@ +package App::Changelord::Role::Changelog; + +use v5.36.0; + +use Moo::Role; +use CLI::Osprey; + +option source => ( + is => 'ro', + format => 's', + doc => q{changelog yaml file. Defaults to the env variable $CHANGELOG, or 'CHANGELOG.yml'}, + default => $ENV{CHANGELOG} || 'CHANGELOG.yml', +); + +has changelog => ( is => 'lazy' ); + +sub _build_changelog($self) { + return YAML::LoadFile($self->source) +} + +1; diff --git a/lib/App/Changelord/Role/Render.pm b/lib/App/Changelord/Role/Render.pm index 75d22ee..da1a68b 100644 --- a/lib/App/Changelord/Role/Render.pm +++ b/lib/App/Changelord/Role/Render.pm @@ -40,14 +40,18 @@ sub render_refs ( $self, %links ) { return $output . "\n"; } -sub as_markdown ($self) { +sub as_markdown ($self, $with_next = 1) { my $changelog = $self->changelog; my $output = $self->render_header; my $n = 0; $output .= join "\n", - map { $self->render_release( $_, $n++ ) } $changelog->{releases}->@*; + map { $self->render_release( $_, $n++ ) } + grep { + $with_next ? 1 : ( $_->{version} && $_->version ne 'NEXT' ) + } + $changelog->{releases}->@*; return $output; } diff --git a/lib/App/Changelord/Role/Versions.pm b/lib/App/Changelord/Role/Versions.pm index bc37c83..889d4a4 100644 --- a/lib/App/Changelord/Role/Versions.pm +++ b/lib/App/Changelord/Role/Versions.pm @@ -12,7 +12,7 @@ use feature 'try'; requires 'changelog'; sub latest_version($self){ - first { $_ } grep { $_ ne 'NEXT' } map { eval { $_->{version} } } $self->changelog->{releases}->@*; + first { $_ } grep { $_ ne 'NEXT' } map { eval { $_->{version} || '' } } $self->changelog->{releases}->@*, { version => 'v0.0.0' }; } sub next_version($self) { @@ -29,7 +29,10 @@ sub next_version($self) { map { $_ => $level } $_->{keywords}->@* } $self->change_types->@*; - my $bump =min 2, map { $_ eq 'major' ? 0 : $_ eq 'minor' ? 1 : 2 } map { $mapping{$_->{type}} || 'patch' } $upcoming->{changes}->@*; + no warnings; + my $bump =min 2, map { $_ eq 'major' ? 0 : $_ eq 'minor' ? 1 : 2 } map { $mapping{$_->{type}} || 'patch' } + map { ref ? $_ : { desc => $_ } } + $upcoming->{changes}->@*; $version->bump($bump); diff --git a/t/basic.t b/t/basic.t index 8781320..9129100 100644 --- a/t/basic.t +++ b/t/basic.t @@ -2,9 +2,9 @@ use 5.36.0; use Test2::V0; -use App::Changelord; +use App::Changelord::Command::Print; -my $change = App::Changelord->new( +my $change = App::Changelord::Command::Print->new( changelog => { project => { name => 'Foo' }, }