<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -52,6 +52,7 @@ CPAN::PackageDetails - Create or read 02packages.details.txt.gz
 		intended_for =&gt; &quot;My private CPAN&quot;,
 		written_by   =&gt; &quot;$0 using CPAN::PackageDetails $CPAN::PackageDetails::VERSION&quot;,
 		last_updated =&gt; CPAN::PackageDetails-&gt;format_date,
+		allow_packages_only_once =&gt; 1,
 		);
 
 	$package_details-&gt;add_entry(
@@ -68,11 +69,11 @@ CPAN::PackageDetails - Create or read 02packages.details.txt.gz
 	
 =head1 DESCRIPTION
 
-CPAN uses an index file, 02packages.details.txt.gz, to map package names to
+CPAN uses an index file, F&lt;02packages.details.txt.gz&gt;, to map package names to
 distribution files. Using this module, you can get a data structure of that
 file, or create your own.
 
-There are two parts to the 02packages.details.txt.gz: a header and the index.
+There are two parts to the F&lt;02packages.details.txt.g&gt;z: a header and the index.
 This module uses a top-level C&lt;CPAN::PackageDetails&gt; object to control
 everything and comprise an C&lt;CPAN::PackageDetails::Header&gt; and
 C&lt;CPAN::PackageDetails::Entries&gt; object. The C&lt;CPAN::PackageDetails::Entries&gt;
@@ -93,7 +94,7 @@ Entry objects.
 
 =item new
 
-Create a new 02packages.details.txt.gz file. The C&lt;default_headers&gt;
+Create a new F&lt;02packages.details.txt.gz&gt; file. The C&lt;default_headers&gt;
 method shows you which values you can pass to C&lt;new&gt;. For instance:
 
 	my $package_details = CPAN::PackageDetails-&gt;new(
@@ -101,6 +102,9 @@ method shows you which values you can pass to C&lt;new&gt;. For instance:
 		columns =&gt; 'author, package name, version, path',
 		)
 
+If you specify the C&lt;allow_packages_only_once&gt; option with a true value
+and you try to add that package twice, the object will die. See C&lt;add_entry&gt;.
+
 =cut
 
 sub new
@@ -166,12 +170,13 @@ my %defaults = (
 	header_class    =&gt; 'CPAN::PackageDetails::Header',
 	entries_class   =&gt; 'CPAN::PackageDetails::Entries',
 	entry_class     =&gt; 'CPAN::PackageDetails::Entry',
+	allow_packages_only_once =&gt; 1,
 	);
 	
 sub default_headers
 	{ 
 	map { $_, $defaults{$_} } 
-		grep ! /^_class/, keys %defaults 
+		grep ! /_class|allow/, keys %defaults 
 	}
 
 sub CPAN::PackageDetails::Header::can
@@ -207,7 +212,7 @@ sub CPAN::PackageDetails::Header::AUTOLOAD
 # them to the right delegate
 my %Dispatch = (
 		header  =&gt; { map { $_, 1 } qw(get_header set_header header_exists columns_as_list) },
-		entries =&gt; { map { $_, 1 } qw(add_entry count as_unique_sorted_list) },
+		entries =&gt; { map { $_, 1 } qw(add_entry count as_unique_sorted_list already_added allow_packages_only_once) },
 	#	entry   =&gt; { map { $_, 1 } qw() },
 		);
 		
@@ -271,10 +276,11 @@ sub init
 		}
 	
 	$self-&gt;{entries} = $self-&gt;entries_class-&gt;new(
-		entry_class =&gt; $self-&gt;entry_class,
-		columns     =&gt; [ split /,\s+/, $config{columns} ],
+		entry_class              =&gt; $self-&gt;entry_class,
+		columns                  =&gt; [ split /,\s+/, $config{columns} ],
+		allow_packages_only_once =&gt; $config{allow_packages_only_once},
 		);
-
+	
 	$self-&gt;{header}  = $self-&gt;header_class-&gt;new(
 		_entries =&gt; $self-&gt;entries,
 		);
@@ -647,7 +653,7 @@ by passing the C&lt;entries_class&gt; option:
 	CPAN::PackageDetails-&gt;new(
 		...,
 		entries_class =&gt; $class,
-		)
+		);
 
 Note that you are responsible for loading the right class yourself.
 
@@ -676,7 +682,7 @@ sub entries { $_[0]-&gt;{entries} }
 
 =back
 
-=head3 Methods is CPAN::PackageDetails::Entries
+=head3 Methods in CPAN::PackageDetails::Entries
 
 =over 4
 
@@ -695,16 +701,20 @@ to it, use C&lt;add_entry&gt;.
 
 	entry_class =&gt; the class to use for each entry object
 	columns     =&gt; the column names, in order that you want them in the output
-	
+
+If you specify the C&lt;allow_packages_only_once&gt; option with a true value
+and you try to add that package twice, the object will die. See C&lt;add_entry&gt;.
+
 =cut
 
 sub new { 
 	my( $class, %args ) = @_;
 	
 	my %hash = ( 
-		entry_class =&gt; 'CPAN::PackageDetails::Entry',
-		columns     =&gt; [],
-		entries     =&gt; [],
+		entry_class              =&gt; 'CPAN::PackageDetails::Entry',
+		allow_packages_only_once =&gt; 1,
+		columns                  =&gt; [],
+		entries                  =&gt; {},
 		%args
 		);
 		
@@ -735,21 +745,50 @@ this method counts duplicates as well.
 
 =cut
 
-sub count { scalar @{$_[0]-&gt;{entries}} }
+sub count 
+	{ 
+	my $self = shift;
+	
+	my $count = 0;
+	foreach my $package ( keys %{ $self-&gt;{entries} } )
+		{
+		$count += keys %{ $self-&gt;{entries}{$package} };
+		}
+		
+	return $count;
+	}
 
 =item entries
 
-Returns an array reference of Entry objects.
+Returns the Entries object.
 
 =cut
 
 sub entries { $_[0]-&gt;{entries} }
 
+=item allow_packages_only_once( [ARG] )
+
+=cut
+
+sub allow_packages_only_once
+	{	
+	$_[0]-&gt;{allow_packages_only_once} = $_[1] if defined $_[1];
+	
+	$_[0]-&gt;{allow_packages_only_once};
+	}
+	
 =item add_entry
 
 Add an entry to the collection. Call this on the C&lt;CPAN::PackageDetails&gt;
 object and it will take care of finding the right handler.
 
+If you've set C&lt;allow_packages_only_once&gt; to a true value (which is the
+default, too), C&lt;add_entry&gt; will die if you try to add another entry with
+the same package name even if it has a different or greater version. You can
+set this to a false value and add as many entries as you like then use
+C&lt;as_unqiue_sorted_list&gt; to get just the entries with the highest 
+versions for each package.
+
 =cut
 
 sub add_entry
@@ -766,22 +805,41 @@ sub add_entry
 		$args{'package name'} = $args{package_name};
 		delete $args{package_name};
 		}
-		
+	
+	$args{'version'} = 'undef' unless defined $args{'version'};
+	
 	unless( defined $args{'package name'} )
 		{
 		carp &quot;No 'package name' parameter!&quot;;
 		return;
 		}
 		
+	if( $self-&gt;allow_packages_only_once and $self-&gt;already_added( $args{'package name'} ) )
+		{
+		croak &quot;$args{'package name'} was already added to CPAN::PackageDetails!&quot;;
+		return;
+		}
+	
 	# should check for allowed columns here
-	push @{ $self-&gt;{entries} }, $self-&gt;entry_class-&gt;new( %args );
+	$self-&gt;{entries}{
+		$args{'package name'}
+		}{$args{'version'}
+			} = $self-&gt;entry_class-&gt;new( %args );
 	}
 
 sub _mark_as_dirty
 	{
 	delete $_[0]-&gt;{sorted};
 	}
-	
+
+=item already_added( PACKAGE )
+
+Returns true if there is already an entry for PACKAGE.
+
+=cut
+
+sub already_added { exists $_[0]-&gt;{entries}{$_[1]} }
+
 =item as_string
 
 Returns a text version of the Entries object. This calls C&lt;as_string&gt;
@@ -795,7 +853,9 @@ sub as_string
 	
 	my $string;
 	
-	foreach my $entry ( $self-&gt;as_unique_sorted_list )
+	my( $return ) = $self-&gt;as_unique_sorted_list;
+	
+	foreach my $entry ( @$return )
 		{
 		$string .= $entry-&gt;as_string( $self-&gt;columns );
 		}
@@ -811,29 +871,42 @@ largest version number seen.
 
 In scalar context this returns the count of the number of unique entries.
 
+Once called, it caches its result until you add more entries.
+
 =cut
 
 sub as_unique_sorted_list
 	{
 	my( $self ) = @_;
 
+	
 	unless( ref $self-&gt;{sorted} eq ref [] )
 		{
+		$self-&gt;{sorted} = [];
+		
 		my %Seen;
 
 		my( $k1, $k2 ) = ( $self-&gt;columns )[0,1];
 
-		$self-&gt;{sorted} = [
-			grep { ! $Seen{ $_-&gt;{$k1} }++ }
-				sort { $a-&gt;{$k1} cmp $b-&gt;{$k1} || $b-&gt;{$k2} &lt;=&gt; $a-&gt;{$k2} } 
-				@{ $self-&gt;entries }
-			];
-		}
+		my $e = $self-&gt;entries;
 		
-	return wantarray ? 
-		@{ $self-&gt;{sorted} } 
+	# We only want the latest versions of everything:
+		foreach my $package ( sort keys %$e )
+			{
+			my( $highest_version ) = 
+				sort { $e-&gt;{$package}{$b} &lt;=&gt; $e-&gt;{$package}{$b} }
+				keys %{ $e-&gt;{$package} };
+			
+			push @{ $self-&gt;{sorted} }, $e-&gt;{$package}{$highest_version};
+			}
+		}
+	
+	my $return = wantarray ? 
+		$self-&gt;{sorted} 
 			:
-		scalar  @{ $self-&gt;{sorted} }
+		scalar  @{ $self-&gt;{sorted} };
+	
+	return $return;
 	}
 
 }</diff>
      <filename>lib/PackageDetails.pm</filename>
    </modified>
    <modified>
      <diff>@@ -19,7 +19,7 @@ can_ok( $class, 'already_added' );
 my $basename = 'three_entries.gz';
 
 my $package_details = $class-&gt;new;
-ok( $package_details-&gt;$method, &quot;allow_packages_only_once is true by default&quot; );
+ok( $package_details-&gt;$method, &quot;$method is true by default&quot; );
 
 my @entries = (
 	[ qw( Animal::Cat::Buster 1.23 ) ],
@@ -52,7 +52,7 @@ my $rc = eval {
 my $at = $@;
 
 ok( ! defined $rc, &quot;Re-added $entries[0][0] had problems (good)&quot; );
-like( $at, qr/already been added/, &quot;Error message notes that $entries[0][0] is already indexed&quot; );
+like( $at, qr/was already added/, &quot;Error message notes that $entries[0][0] is already indexed&quot; );
 }
 
 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # </diff>
      <filename>t/allow_packages_only_once.t</filename>
    </modified>
    <modified>
      <diff>@@ -40,9 +40,17 @@ is( $package_details-&gt;count, scalar @entries_to_add,
 		&quot;Count is the same number as added entries&quot;);
 
 my @columns = ( 'package_name', qw(version path));
-foreach my $entry ( @{ $package_details-&gt;entries-&gt;{entries} } )
-	{
-	ok( length( $entry-&gt;as_string( @columns ) ) &gt; 1, &quot;Some sort of string comes back from entry&quot;)
+my $entries = $package_details-&gt;entries-&gt;{entries}; # XXX yuck
+
+foreach my $package ( keys %$entries )
+	{	
+	my $package_hash = $entries-&gt;{$package};
+	
+	foreach my $hash ( values %$package_hash )
+		{
+		my $entry = $hash;
+		ok( length( $entry-&gt;as_string( @columns ) ) &gt; 1, &quot;Some sort of string comes back from entry&quot;)
+		}
 	}
 
 }</diff>
      <filename>t/as_string.t</filename>
    </modified>
    <modified>
      <diff>@@ -18,6 +18,7 @@ ok( -e $file, &quot;Test file $file exists&quot; );
 my $lines = 1439;
 
 my $package_details = $class-&gt;$method( $file );
+
 isa_ok( $package_details, $class );
 is( $package_details-&gt;source_file, $file, &quot;Get back the right filename&quot;);
 </diff>
      <filename>t/read.t</filename>
    </modified>
    <modified>
      <diff>@@ -6,6 +6,7 @@ new.t
 read.t
 format_date.t
 get_header.t
+allow_packages_only_once.t
 unique_sorted.t
 as_string.t
 write_file.t</diff>
      <filename>t/test_manifest</filename>
    </modified>
    <modified>
      <diff>@@ -1,4 +1,6 @@
 use Test::More 'no_plan';
+use strict;
+use warnings;
 
 use File::Spec::Functions;
 use File::Temp;
@@ -14,15 +16,16 @@ use_ok( $class );
 {
 my $basename = 'three_entries.gz';
 
-my $package_details = $class-&gt;new;
+my $package_details = $class-&gt;new( allow_packages_only_once =&gt; 0 );
 isa_ok( $package_details, $class );
 can_ok( $package_details, $method );
 
+is( $package_details-&gt;allow_packages_only_once, 0, &quot;allow_packages_only_once is false&quot; );
+
 can_ok( $package_details-&gt;entries, $method );
 
 my @entries_to_add = (
 	[ 'Foo::Bar', '1.01',    '/a/b/c/Foo-1.01.tgz'],
-	[ 'Foo::Bar', '1.01',    '/a/b/c/Foo-1.01.tgz'],
 	[ 'Foo::Bar', '1.02',    '/a/b/c/Foo-1.02.tgz'],
 	[ 'Foo::Baz', '1.02_01', '/a/b/c/Foo-Baz-1.02_01.tgz'],	
 	[ 'Quux',     '2800',    '/a/b/c/Quux-2800.tgz'],	
@@ -30,28 +33,29 @@ my @entries_to_add = (
 	
 foreach my $tuple ( @entries_to_add )
 	{
-	my $rc = $package_details-&gt;add_entry(
+	my $rc = eval { $package_details-&gt;add_entry(
 		'package name' =&gt; $tuple-&gt;[0],
 		version        =&gt; $tuple-&gt;[1],
 		path           =&gt; $tuple-&gt;[2],
-		);
+		) };
 		
 	ok( $rc, &quot;Added entry for $tuple-&gt;[0]&quot; );
 	}
-	
+
 is( $package_details-&gt;count, scalar @entries_to_add, 
 		&quot;Count is the same number as added entries&quot; );
 
 is( scalar $package_details-&gt;$method, 3,
 	&quot;There are only three unique packages&quot; );
 	
-my @list = $package_details-&gt;$method;
-is( $list[0]-&gt;{'package name'}, 'Foo::Bar', &quot;Foo::Bar is the first one in the list&quot; );
-is( $list[0]-&gt;{version}, '1.02',     &quot;Foo::Bar has latest version&quot; );
+my( $list )= $package_details-&gt;$method;
+
+is( $list-&gt;[0]{'package name'}, 'Foo::Bar', &quot;Foo::Bar is the first one in the list&quot; );
+is( $list-&gt;[0]{version}, '1.02',     &quot;Foo::Bar has latest version&quot; );
 
-is( $list[1]-&gt;{'package name'}, 'Foo::Baz', &quot;Foo::Baz is the second one in the list&quot; );
+is( $list-&gt;[1]{'package name'}, 'Foo::Baz', &quot;Foo::Baz is the second one in the list&quot; );
 
-is( $list[-1]-&gt;{'package name'}, 'Quux', &quot;Quux is the last one in the list&quot; );
+is( $list-&gt;[-1]{'package name'}, 'Quux', &quot;Quux is the last one in the list&quot; );
 
 
 #my $string = $package_details-&gt;as_string;</diff>
      <filename>t/unique_sorted.t</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>64ee82b3faf1cf82b02143db68fb71a42a12b861</id>
    </parent>
  </parents>
  <author>
    <name>brian d foy</name>
    <email>brian.d.foy@gmail.com</email>
  </author>
  <url>http://github.com/briandfoy/cpan-packagedetails/commit/8df78dbe93ea0f32e40a6b4899356352f237825e</url>
  <id>8df78dbe93ea0f32e40a6b4899356352f237825e</id>
  <committed-date>2009-06-10T16:41:06-07:00</committed-date>
  <authored-date>2009-06-10T16:41:06-07:00</authored-date>
  <message>* Added allow_packages_only_once and already_added</message>
  <tree>f4d56c8d981e4593d915681177ffb8ddfda9c96c</tree>
  <committer>
    <name>brian d foy</name>
    <email>brian.d.foy@gmail.com</email>
  </committer>
</commit>
