]> git.vpit.fr Git - perl/modules/CPANPLUS-Dist-Gentoo.git/blob - lib/CPANPLUS/Dist/Gentoo/Atom.pm
76af6fe787c27053c129932c461f6347417db175
[perl/modules/CPANPLUS-Dist-Gentoo.git] / lib / CPANPLUS / Dist / Gentoo / Atom.pm
1 package CPANPLUS::Dist::Gentoo::Atom;
2
3 use strict;
4 use warnings;
5
6 =head1 NAME
7
8 CPANPLUS::Dist::Gentoo::Version - Gentoo atom object.
9
10 =head1 VERSION
11
12 Version 0.08
13
14 =cut
15
16 our $VERSION = '0.08';
17
18 =head1 DESCRIPTION
19
20 This class models Gentoo atoms.
21
22 =cut
23
24 use Carp         ();
25 use Scalar::Util ();
26
27 use overload (
28  '<=>' => \&_spaceship,
29  'cmp' => \&_cmp,
30  '""'  => \&_stringify,
31 );
32
33 use CPANPLUS::Dist::Gentoo::Version;
34
35 my $version_rx = $CPANPLUS::Dist::Gentoo::Version::version_rx;
36
37 =head1 METHODS
38
39 =head2 C<< new category => $category, name => $name [, version => $version, range => $range, ebuild => $ebuild ] >>
40
41 Creates a new L<CPANPLUS::Dist::Gentoo::Atom> object from the supplied C<$category>, C<$name>, C<$version>, C<$range> and C<$ebuild>.
42
43 =cut
44
45 sub new {
46  my $class = shift;
47  $class = ref($class) || $class;
48
49  my %args = @_;
50
51  my ($range, $category, $name, $version);
52  if (defined $args{name}) {
53   ($range, $category, $name, $version) = @args{qw/range category name version/};
54   Carp::confess('Category unspecified') unless defined $category;
55   /[^\w-]/ and Carp::confess('Invalid argument') for $name, $category;
56  } elsif (defined $args{atom}) {
57   my $atom = $args{atom};
58   $atom =~ m{^(<|<=|=|>=|>)?([\w-]+)/([\w-]+)(?-v?($version_rx))?$}
59                                                or Carp::confess('Invalid atom');
60   ($range, $category, $name, $version) = ($1, $2, $3, $4);
61  } else {
62   Carp::confess('Not enough information for building an atom object');
63  }
64
65  if (defined $version) {
66   unless (Scalar::Util::blessed($version)
67                               and $_->isa('CPANPLUS::Dist::Gentoo::Version')) {
68    $version = CPANPLUS::Dist::Gentoo::Version->new($version);
69   }
70  }
71
72  if (defined $version) {
73   if (defined $range) {
74    Carp::confess("Invalid range $range")
75                                       unless grep $range eq $_, qw|< <= = >= >|;
76   } else {
77    $range = '=';
78   }
79  } else {
80   Carp::confess('Range atoms require a valid version')
81                                             if defined $range and length $range;
82  }
83
84  bless {
85   category => $category,
86   name     => $name,
87   version  => $version,
88   range    => $range,
89   ebuild   => $args{ebuild},
90  }, $class;
91 }
92
93 =head2 C<new_from_ebuild $ebuild>
94
95 Creates a new L<CPANPLUS::Dist::Gentoo::Atom> object by inferring the category, name and version from the given C<$ebuild>
96
97 =cut
98
99 sub new_from_ebuild {
100  my $class = shift;
101  $class = ref($class) || $class;
102
103  my $ebuild = shift;
104  $ebuild = '' unless defined $ebuild;
105
106  $ebuild =~ m{/([\w-]+)/([\w-]+)/\2-v?($version_rx)\.ebuild$}
107                                              or Carp::confess('Invalid ebuild');
108  my ($category, $name, $version) = ($1, $2, $3);
109
110  return $class->new(
111   category => $category,
112   name     => $name,
113   version  => $version,
114   ebuild   => $ebuild,
115  );
116 }
117
118 BEGIN {
119  eval "sub $_ { \$_[0]->{$_} }" for qw/category name version range ebuild/;
120 }
121
122 =head2 C<category>
123
124 Read-only accessor to the atom category.
125
126 =head2 C<name>
127
128 Read-only accessor to the atom name.
129
130 =head2 C<version>
131
132 Read-only accessor to the L<CPANPLUS::Dist::Gentoo::Version> object associated with the atom.
133
134 =head2 C<range>
135
136 Read-only accessor to the atom range.
137
138 =head2 C<ebuild>
139
140 Read-only accessor to the path of an optional ebuild associated with the atom.
141
142 =head2 C<qualified_name>
143
144 Returns the qualified name for the atom, i.e. C<$category/$name>.
145
146 =cut
147
148 sub qualified_name { join '/', $_[0]->category, $_[0]->name }
149
150 sub _spaceship {
151  my ($a1, $a2, $r) = @_;
152
153  my $v1 = $a1->version;
154
155  my $v2;
156  if (Scalar::Util::blessed($a2) and $a2->isa(__PACKAGE__)) {
157   Carp::confess('Can\'t compare atoms of different packages')
158                     if $a1->category ne $a2->category or $a1->name ne $a2->name;
159   $v2 = $a2->version;
160  } else {
161   $v2 = $a2;
162  }
163
164  ($v1, $v2) = ($v2, $v1) if $r;
165
166  return $v1 <=> $v2;
167 }
168
169 sub _cmp {
170  my ($a1, $a2, $r) = @_;
171
172  my $s1 = $a1->qualified_name;
173  my $v1 = $a1->version;
174  $s1   .= "-$v1" if defined $v1;
175
176  my $s2;
177  if (Scalar::Util::blessed($a2) and $a2->isa(__PACKAGE__)) {
178   $s2    = $a2->qualified_name;
179   my $v2 = $a2->version;
180   $s2   .= "-$v2" if defined $v2;
181  } else {
182   $s2 = $a2;
183  }
184
185  $s1 cmp $s2;
186 }
187
188 sub _stringify {
189  my ($a) = @_;
190
191  my $atom = $a->qualified_name;
192
193  my $version = $a->version;
194  $atom = $a->range . $atom . '-' . $version if defined $version;
195
196  return $atom;
197 }
198
199 my %order = (
200  '<'  => -2,
201  '<=' => -1,
202   '=' =>  0,
203  '>=' =>  1,
204  '>'  =>  2,
205 );
206
207 =head2 C<and @atoms>
208
209 Compute the ranged atom representing the logical AND between C<@atoms> with the same category and name.
210
211 =cut
212
213 sub and {
214  shift unless length ref $_[0];
215
216  my $a1 = shift;
217  return $a1 unless @_;
218
219  my $a2 = shift;
220  $a2 = $a2->and(@_) if @_;
221
222  my $p1 = $a1->qualified_name;
223  my $p2 = $a2->qualified_name;
224  Carp::confess("Atoms for different packages $p1 and $p2") unless $p1 eq $p2;
225
226  my $v1 = $a1->version;
227  return $a2 unless defined $v1;
228  my $r1 = $a1->range; # Defined if $v1 is defined
229
230  my $v2 = $a2->version;
231  return $a1 unless defined $v2;
232  my $r2 = $a2->range; # defined if $v2 is defined
233
234  my $o1 = $order{$r1};
235  my $o2 = $order{$r2};
236
237  Carp::confess("Incompatible ranges $r1$p1 and $r2$p2") if $o1 * $o2 < 0;
238
239  if ($r2 eq '=') {
240   ($a1, $a2) = ($a2, $a1);
241   ($v1, $v2) = ($v2, $v1);
242   ($r1, $r2) = ($r2, $r1);
243   ($o1, $o2) = ($o2, $o1);
244  }
245
246  if ($r1 eq '=') {
247   my $r = $r2 eq '=' ? '==' : $r2;
248   Carp::confess("Version mismatch $v1 $r $v2") unless eval "\$a1 $r \$a2";
249   return $a1;
250  } elsif ($o1 > 0) {
251   return $a1 < $a2 ? $a2 : $a1;
252  } else {
253   return $a1 < $a2 ? $a1 : $a2;
254  }
255 }
256
257 =head2 C<fold @atoms>
258
259 Returns a list built from C<@atoms> but where there's only one atom for a given category and name.
260
261 =cut
262
263 sub fold {
264  shift unless length ref $_[0];
265
266  my %seen;
267  for my $atom (@_) {
268   my $key = $atom->qualified_name;
269
270   my $cur = $seen{$key};
271   $seen{$key} = defined $cur ? $cur->and($atom) : $atom;
272  }
273
274  return values %seen;
275 }
276
277 =pod
278
279 This class provides overloaded methods for numerical comparison, string comparison and strigification.
280
281 =head1 SEE ALSO
282
283 L<CPANPLUS::Dist::Gentoo>, L<CPANPLUS::Dist::Gentoo::Version>.
284
285 =head1 AUTHOR
286
287 Vincent Pit, C<< <perl at profvince.com> >>, L<http://www.profvince.com>.
288
289 You can contact me by mail or on C<irc.perl.org> (vincent).
290
291 =head1 BUGS
292
293 Please report any bugs or feature requests to C<bug-cpanplus-dist-gentoo at rt.cpan.org>, or through the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=CPANPLUS-Dist-Gentoo>.  I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
294
295 =head1 SUPPORT
296
297 You can find documentation for this module with the perldoc command.
298
299     perldoc CPANPLUS::Dist::Gentoo
300
301 =head1 COPYRIGHT & LICENSE
302
303 Copyright 2009 Vincent Pit, all rights reserved.
304
305 This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
306
307 =cut
308
309 1; # End of CPANPLUS::Dist::Gentoo::Atom