The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package CPAN::Changes::Release;

use strict;
use warnings;

use Text::Wrap   ();
use CPAN::Changes::Group;
use Scalar::Util qw(blessed);

sub new {
    my $class = shift;
    return bless {
        changes => {},
        @_,
    }, $class;
}

sub version {
    my $self = shift;

    if ( @_ ) {
        $self->{ version } = shift;
    }

    return $self->{ version };
}

sub date {
    my $self = shift;

    if ( @_ ) {
        $self->{ date } = shift;
    }

    return $self->{ date };
}

sub note {
    my $self = shift;

    if ( @_ ) {
        $self->{ note } = shift;
    }

    return $self->{ note };
}

sub changes {
    my $self = shift;

    if ( @_ ) {
        my $group = shift;
        return unless exists $self->{ changes }->{ $group };
        return $self->{ changes }->{ $group }->changes;
    }

    return { map { $_ => $self->{ changes }->{$_}->changes } keys %{ $self->{ changes } } };
}

sub add_changes {
    my $self  = shift;
    my $group = '';

    if ( ref $_[ 0 ] ) {
        $group = shift->{ group };
    }

    $self->get_group( $group )->add_changes( @_ );
}

sub set_changes {
    my $self  = shift;
    my $group = '';

    if ( ref $_[ 0 ] ) {
        $group = shift->{ group };
    }

    $self->get_group( $group )->set_changes(@_);
}

sub clear_changes {
    my $self = shift;
    $self->{ changes } = {};
}

sub groups {
    my $self = shift;
    my %args = @_;

    $args{ sort } ||= sub { sort @_ };

    return $args{ sort }->( keys %{ $self->{ changes } } );
}

sub get_group {
    my $self = shift;
    my $group = '';

    if ( $_[ 0 ] ) {
        $group = shift;
    }
    if ( !exists $self->{ changes }->{ $group } ) {
        $self->{ changes }->{ $group } = CPAN::Changes::Group->new( name => $group );
    }
    if ( not blessed $self->{changes}->{$group} ) {
       $self->{ changes }->{ $group } = CPAN::Changes::Group->new( name => $group , changes => $self->{changes}->{$group} );
    }

    return $self->{ changes }->{ $group };
}

sub attach_group {
    my $self = shift;
    my $group = shift;

    die "Not a group" unless blessed $group;

    my $name = $group->name;

    $self->{changes}->{$name} = $group;

}

sub group_values {
    my $self = shift;
    return map { $self->get_group( $_ ) } $self->groups( @_ );
}

sub add_group {
    my $self = shift;
    $self->{ changes }->{ $_ } = CPAN::Changes::Group->new( name =>  $_ ) for @_;
}

sub delete_group {
    my $self   = shift;
    my @groups = @_;

    @groups = ( '' ) unless @groups;

    delete $self->{ changes }->{ $_ } for @groups;
}

sub delete_empty_groups {
    my $self = shift;

    $self->delete_group($_->name)
        for grep { $_->is_empty } $self->group_values;
}

sub serialize {
    my $self = shift;
    my %args = @_;

    my $output = join( ' ', grep { defined && length } ( $self->version, $self->date, $self->note ) )
        . "\n";

    $output .= join "\n",
        map { $_->serialize } $self->group_values( sort => $args{ group_sort } );
    $output .= "\n";

    return $output;
}

1;

__END__

=head1 NAME

CPAN::Changes::Release - Information about a particular release

=head1 SYNOPSIS

    my $rel = CPAN::Changes::Release->new(
        version => '0.01',
        date    => '2009-07-06',
    );
    
    $rel->add_changes(
        { group => 'THINGS THAT MAY BREAK YOUR CODE' },
        'Return a Foo object instead of a Bar object in foobar()'
    );

=head1 DESCRIPTION

A changelog is made up of one or more releases. This object provides access
to all of the key data that embodies a release including the version number, 
date of release, and all of the changelog information lines. Any number of 
changelog lines can be grouped together under a heading.

=head1 METHODS

=head2 new( %args )

Creates a new release object, using C<%args> as the default data.

=head2 version( [ $version ] )

Gets/sets the version number for this release.

=head2 date( [ $date ] )

Gets/sets the date for this release.

=head2 note( [ $note ] )

Gets/sets the note for this release.

=head2 changes( [ $group ] )

Gets the list of changes for this release as a hashref of group/changes 
pairs. If a group name is specified, an array ref of changes for that group 
is returned. Should that group not exist, undef is returned.

=head2 add_changes( [ \%options ], @changes )

Appends a list of changes to the release. Specifying a C<group> option 
appends them to that particular group. NB: the default group is represented 
by and empty string.

    # Append to default group
    $release->add_changes( 'Added foo() function' );
    
    # Append to a particular group
    $release->add_changes( { group => 'Fixes' }, 'Fixed foo() function' );

=head2 set_changes( [ \%options ], @changes )

Replaces the existing list of changes with the supplied values. Specifying
a C<group> option will only replace change items in that group.

=head2 clear_changes( )

Clears all changes from the release.

=head2 groups( sort => \&sorting_function )

Returns a list of current groups in this release.

If I<sort> is provided, groups are
sorted according to the given function. If not,
they are sorted alphabetically.

=head2 add_group( @groups )

Creates an empty group under the names provided.

=head2 delete_group( @groups )

Deletes the groups of changes specified.

=head2 delete_empty_groups( )

Deletes all groups that don't contain any changes.

=head2 serialize( group_sort => \&sorting_function )

Returns the release data as a string, suitable for inclusion in a Changes 
file.

If I<group_sort> is provided, change groups are
sorted according to the given function. If not,
groups are sorted alphabetically.

=head2 get_group( [ $name ] )

Returns the internal L<CPAN::Changes::Group> object for the group C<$name>.

If C<$name> is not specified, the C<default> group C<('')> will be returned.

If C<$name> does not exist, a L<CPAN::Changes::Group> object will be created, and returned.

=head2 attach_group( $group_object )

Attach a L<CPAN::Changes::Group> object to the C<::Release>. Note that the name is B<not> specified,
as it is instead determined from C<< $group_object->name >>

=head2 group_values( sort => \&sorting_function )

Works like L</groups> but instead returns C<CPAN::Changes::Group> compatible objects.



=head1 SEE ALSO

=over 4

=item * L<CPAN::Changes::Spec>

=item * L<CPAN::Changes>

=item * L<Test::CPAN::Changes>

=back

=head1 AUTHOR

Brian Cassidy E<lt>bricas@cpan.orgE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright 2011-2013 by Brian Cassidy

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself. 

=cut