1use strict;
2use warnings;
3package CPAN::Meta::Requirements;
4our $VERSION = '2.125'; # VERSION
5# ABSTRACT: a set of version requirements for a CPAN dist
6
7
8use Carp ();
9use Scalar::Util ();
10use version 0.77 (); # the ->parse method
11
12
13my @valid_options = qw( bad_version_hook );
14
15sub new {
16  my ($class, $options) = @_;
17  $options ||= {};
18  Carp::croak "Argument to $class\->new() must be a hash reference"
19    unless ref $options eq 'HASH';
20  my %self = map {; $_ => $options->{$_}} @valid_options;
21
22  return bless \%self => $class;
23}
24
25sub _version_object {
26  my ($self, $version) = @_;
27
28  my $vobj;
29
30  eval {
31    $vobj  = (! defined $version)                ? version->parse(0)
32           : (! Scalar::Util::blessed($version)) ? version->parse($version)
33           :                                       $version;
34  };
35
36  if ( my $err = $@ ) {
37    my $hook = $self->{bad_version_hook};
38    $vobj = eval { $hook->($version) }
39      if ref $hook eq 'CODE';
40    unless (Scalar::Util::blessed($vobj) && $vobj->isa("version")) {
41      $err =~ s{ at .* line \d+.*$}{};
42      die "Can't convert '$version': $err";
43    }
44  }
45
46  # ensure no leading '.'
47  if ( $vobj =~ m{\A\.} ) {
48    $vobj = version->parse("0$vobj");
49  }
50
51  # ensure normal v-string form
52  if ( $vobj->is_qv ) {
53    $vobj = version->parse($vobj->normal);
54  }
55
56  return $vobj;
57}
58
59
60BEGIN {
61  for my $type (qw(minimum maximum exclusion exact_version)) {
62    my $method = "with_$type";
63    my $to_add = $type eq 'exact_version' ? $type : "add_$type";
64
65    my $code = sub {
66      my ($self, $name, $version) = @_;
67
68      $version = $self->_version_object( $version );
69
70      $self->__modify_entry_for($name, $method, $version);
71
72      return $self;
73    };
74
75    no strict 'refs';
76    *$to_add = $code;
77  }
78}
79
80
81sub add_requirements {
82  my ($self, $req) = @_;
83
84  for my $module ($req->required_modules) {
85    my $modifiers = $req->__entry_for($module)->as_modifiers;
86    for my $modifier (@$modifiers) {
87      my ($method, @args) = @$modifier;
88      $self->$method($module => @args);
89    };
90  }
91
92  return $self;
93}
94
95
96sub accepts_module {
97  my ($self, $module, $version) = @_;
98
99  $version = $self->_version_object( $version );
100
101  return 1 unless my $range = $self->__entry_for($module);
102  return $range->_accepts($version);
103}
104
105
106sub clear_requirement {
107  my ($self, $module) = @_;
108
109  return $self unless $self->__entry_for($module);
110
111  Carp::confess("can't clear requirements on finalized requirements")
112    if $self->is_finalized;
113
114  delete $self->{requirements}{ $module };
115
116  return $self;
117}
118
119
120sub requirements_for_module {
121  my ($self, $module) = @_;
122  my $entry = $self->__entry_for($module);
123  return unless $entry;
124  return $entry->as_string;
125}
126
127
128sub required_modules { keys %{ $_[0]{requirements} } }
129
130
131sub clone {
132  my ($self) = @_;
133  my $new = (ref $self)->new;
134
135  return $new->add_requirements($self);
136}
137
138sub __entry_for     { $_[0]{requirements}{ $_[1] } }
139
140sub __modify_entry_for {
141  my ($self, $name, $method, $version) = @_;
142
143  my $fin = $self->is_finalized;
144  my $old = $self->__entry_for($name);
145
146  Carp::confess("can't add new requirements to finalized requirements")
147    if $fin and not $old;
148
149  my $new = ($old || 'CPAN::Meta::Requirements::_Range::Range')
150          ->$method($version);
151
152  Carp::confess("can't modify finalized requirements")
153    if $fin and $old->as_string ne $new->as_string;
154
155  $self->{requirements}{ $name } = $new;
156}
157
158
159sub is_simple {
160  my ($self) = @_;
161  for my $module ($self->required_modules) {
162    # XXX: This is a complete hack, but also entirely correct.
163    return if $self->__entry_for($module)->as_string =~ /\s/;
164  }
165
166  return 1;
167}
168
169
170sub is_finalized { $_[0]{finalized} }
171
172
173sub finalize { $_[0]{finalized} = 1 }
174
175
176sub as_string_hash {
177  my ($self) = @_;
178
179  my %hash = map {; $_ => $self->{requirements}{$_}->as_string }
180             $self->required_modules;
181
182  return \%hash;
183}
184
185
186my %methods_for_op = (
187  '==' => [ qw(exact_version) ],
188  '!=' => [ qw(add_exclusion) ],
189  '>=' => [ qw(add_minimum)   ],
190  '<=' => [ qw(add_maximum)   ],
191  '>'  => [ qw(add_minimum add_exclusion) ],
192  '<'  => [ qw(add_maximum add_exclusion) ],
193);
194
195sub add_string_requirement {
196  my ($self, $module, $req) = @_;
197
198  Carp::confess("No requirement string provided for $module")
199    unless defined $req && length $req;
200
201  my @parts = split qr{\s*,\s*}, $req;
202
203
204  for my $part (@parts) {
205    my ($op, $ver) = $part =~ m{\A\s*(==|>=|>|<=|<|!=)\s*(.*)\z};
206
207    if (! defined $op) {
208      $self->add_minimum($module => $part);
209    } else {
210      Carp::confess("illegal requirement string: $req")
211        unless my $methods = $methods_for_op{ $op };
212
213      $self->$_($module => $ver) for @$methods;
214    }
215  }
216}
217
218
219sub from_string_hash {
220  my ($class, $hash) = @_;
221
222  my $self = $class->new;
223
224  for my $module (keys %$hash) {
225    my $req = $hash->{$module};
226    unless ( defined $req && length $req ) {
227      $req = 0;
228      Carp::carp("Undefined requirement for $module treated as '0'");
229    }
230    $self->add_string_requirement($module, $req);
231  }
232
233  return $self;
234}
235
236##############################################################
237
238{
239  package
240    CPAN::Meta::Requirements::_Range::Exact;
241  sub _new     { bless { version => $_[1] } => $_[0] }
242
243  sub _accepts { return $_[0]{version} == $_[1] }
244
245  sub as_string { return "== $_[0]{version}" }
246
247  sub as_modifiers { return [ [ exact_version => $_[0]{version} ] ] }
248
249  sub _clone {
250    (ref $_[0])->_new( version->new( $_[0]{version} ) )
251  }
252
253  sub with_exact_version {
254    my ($self, $version) = @_;
255
256    return $self->_clone if $self->_accepts($version);
257
258    Carp::confess("illegal requirements: unequal exact version specified");
259  }
260
261  sub with_minimum {
262    my ($self, $minimum) = @_;
263    return $self->_clone if $self->{version} >= $minimum;
264    Carp::confess("illegal requirements: minimum above exact specification");
265  }
266
267  sub with_maximum {
268    my ($self, $maximum) = @_;
269    return $self->_clone if $self->{version} <= $maximum;
270    Carp::confess("illegal requirements: maximum below exact specification");
271  }
272
273  sub with_exclusion {
274    my ($self, $exclusion) = @_;
275    return $self->_clone unless $exclusion == $self->{version};
276    Carp::confess("illegal requirements: excluded exact specification");
277  }
278}
279
280##############################################################
281
282{
283  package
284    CPAN::Meta::Requirements::_Range::Range;
285
286  sub _self { ref($_[0]) ? $_[0] : (bless { } => $_[0]) }
287
288  sub _clone {
289    return (bless { } => $_[0]) unless ref $_[0];
290
291    my ($s) = @_;
292    my %guts = (
293      (exists $s->{minimum} ? (minimum => version->new($s->{minimum})) : ()),
294      (exists $s->{maximum} ? (maximum => version->new($s->{maximum})) : ()),
295
296      (exists $s->{exclusions}
297        ? (exclusions => [ map { version->new($_) } @{ $s->{exclusions} } ])
298        : ()),
299    );
300
301    bless \%guts => ref($s);
302  }
303
304  sub as_modifiers {
305    my ($self) = @_;
306    my @mods;
307    push @mods, [ add_minimum => $self->{minimum} ] if exists $self->{minimum};
308    push @mods, [ add_maximum => $self->{maximum} ] if exists $self->{maximum};
309    push @mods, map {; [ add_exclusion => $_ ] } @{$self->{exclusions} || []};
310    return \@mods;
311  }
312
313  sub as_string {
314    my ($self) = @_;
315
316    return 0 if ! keys %$self;
317
318    return "$self->{minimum}" if (keys %$self) == 1 and exists $self->{minimum};
319
320    my @exclusions = @{ $self->{exclusions} || [] };
321
322    my @parts;
323
324    for my $pair (
325      [ qw( >= > minimum ) ],
326      [ qw( <= < maximum ) ],
327    ) {
328      my ($op, $e_op, $k) = @$pair;
329      if (exists $self->{$k}) {
330        my @new_exclusions = grep { $_ != $self->{ $k } } @exclusions;
331        if (@new_exclusions == @exclusions) {
332          push @parts, "$op $self->{ $k }";
333        } else {
334          push @parts, "$e_op $self->{ $k }";
335          @exclusions = @new_exclusions;
336        }
337      }
338    }
339
340    push @parts, map {; "!= $_" } @exclusions;
341
342    return join q{, }, @parts;
343  }
344
345  sub with_exact_version {
346    my ($self, $version) = @_;
347    $self = $self->_clone;
348
349    Carp::confess("illegal requirements: exact specification outside of range")
350      unless $self->_accepts($version);
351
352    return CPAN::Meta::Requirements::_Range::Exact->_new($version);
353  }
354
355  sub _simplify {
356    my ($self) = @_;
357
358    if (defined $self->{minimum} and defined $self->{maximum}) {
359      if ($self->{minimum} == $self->{maximum}) {
360        Carp::confess("illegal requirements: excluded all values")
361          if grep { $_ == $self->{minimum} } @{ $self->{exclusions} || [] };
362
363        return CPAN::Meta::Requirements::_Range::Exact->_new($self->{minimum})
364      }
365
366      Carp::confess("illegal requirements: minimum exceeds maximum")
367        if $self->{minimum} > $self->{maximum};
368    }
369
370    # eliminate irrelevant exclusions
371    if ($self->{exclusions}) {
372      my %seen;
373      @{ $self->{exclusions} } = grep {
374        (! defined $self->{minimum} or $_ >= $self->{minimum})
375        and
376        (! defined $self->{maximum} or $_ <= $self->{maximum})
377        and
378        ! $seen{$_}++
379      } @{ $self->{exclusions} };
380    }
381
382    return $self;
383  }
384
385  sub with_minimum {
386    my ($self, $minimum) = @_;
387    $self = $self->_clone;
388
389    if (defined (my $old_min = $self->{minimum})) {
390      $self->{minimum} = (sort { $b cmp $a } ($minimum, $old_min))[0];
391    } else {
392      $self->{minimum} = $minimum;
393    }
394
395    return $self->_simplify;
396  }
397
398  sub with_maximum {
399    my ($self, $maximum) = @_;
400    $self = $self->_clone;
401
402    if (defined (my $old_max = $self->{maximum})) {
403      $self->{maximum} = (sort { $a cmp $b } ($maximum, $old_max))[0];
404    } else {
405      $self->{maximum} = $maximum;
406    }
407
408    return $self->_simplify;
409  }
410
411  sub with_exclusion {
412    my ($self, $exclusion) = @_;
413    $self = $self->_clone;
414
415    push @{ $self->{exclusions} ||= [] }, $exclusion;
416
417    return $self->_simplify;
418  }
419
420  sub _accepts {
421    my ($self, $version) = @_;
422
423    return if defined $self->{minimum} and $version < $self->{minimum};
424    return if defined $self->{maximum} and $version > $self->{maximum};
425    return if defined $self->{exclusions}
426          and grep { $version == $_ } @{ $self->{exclusions} };
427
428    return 1;
429  }
430}
431
4321;
433# vim: ts=2 sts=2 sw=2 et:
434
435__END__
436
437=pod
438
439=encoding utf-8
440
441=head1 NAME
442
443CPAN::Meta::Requirements - a set of version requirements for a CPAN dist
444
445=head1 VERSION
446
447version 2.125
448
449=head1 SYNOPSIS
450
451  use CPAN::Meta::Requirements;
452
453  my $build_requires = CPAN::Meta::Requirements->new;
454
455  $build_requires->add_minimum('Library::Foo' => 1.208);
456
457  $build_requires->add_minimum('Library::Foo' => 2.602);
458
459  $build_requires->add_minimum('Module::Bar'  => 'v1.2.3');
460
461  $METAyml->{build_requires} = $build_requires->as_string_hash;
462
463=head1 DESCRIPTION
464
465A CPAN::Meta::Requirements object models a set of version constraints like
466those specified in the F<META.yml> or F<META.json> files in CPAN distributions.
467It can be built up by adding more and more constraints, and it will reduce them
468to the simplest representation.
469
470Logically impossible constraints will be identified immediately by thrown
471exceptions.
472
473=head1 METHODS
474
475=head2 new
476
477  my $req = CPAN::Meta::Requirements->new;
478
479This returns a new CPAN::Meta::Requirements object.  It takes an optional
480hash reference argument.  The following keys are supported:
481
482=over 4
483
484=item *
485
486<bad_version_hook> -- if provided, when a version cannot be parsed into
487
488a version object, this code reference will be called with the invalid version
489string as an argument.  It must return a valid version object.
490
491=back
492
493All other keys are ignored.
494
495=head2 add_minimum
496
497  $req->add_minimum( $module => $version );
498
499This adds a new minimum version requirement.  If the new requirement is
500redundant to the existing specification, this has no effect.
501
502Minimum requirements are inclusive.  C<$version> is required, along with any
503greater version number.
504
505This method returns the requirements object.
506
507=head2 add_maximum
508
509  $req->add_maximum( $module => $version );
510
511This adds a new maximum version requirement.  If the new requirement is
512redundant to the existing specification, this has no effect.
513
514Maximum requirements are inclusive.  No version strictly greater than the given
515version is allowed.
516
517This method returns the requirements object.
518
519=head2 add_exclusion
520
521  $req->add_exclusion( $module => $version );
522
523This adds a new excluded version.  For example, you might use these three
524method calls:
525
526  $req->add_minimum( $module => '1.00' );
527  $req->add_maximum( $module => '1.82' );
528
529  $req->add_exclusion( $module => '1.75' );
530
531Any version between 1.00 and 1.82 inclusive would be acceptable, except for
5321.75.
533
534This method returns the requirements object.
535
536=head2 exact_version
537
538  $req->exact_version( $module => $version );
539
540This sets the version required for the given module to I<exactly> the given
541version.  No other version would be considered acceptable.
542
543This method returns the requirements object.
544
545=head2 add_requirements
546
547  $req->add_requirements( $another_req_object );
548
549This method adds all the requirements in the given CPAN::Meta::Requirements object
550to the requirements object on which it was called.  If there are any conflicts,
551an exception is thrown.
552
553This method returns the requirements object.
554
555=head2 accepts_module
556
557  my $bool = $req->accepts_modules($module => $version);
558
559Given an module and version, this method returns true if the version
560specification for the module accepts the provided version.  In other words,
561given:
562
563  Module => '>= 1.00, < 2.00'
564
565We will accept 1.00 and 1.75 but not 0.50 or 2.00.
566
567For modules that do not appear in the requirements, this method will return
568true.
569
570=head2 clear_requirement
571
572  $req->clear_requirement( $module );
573
574This removes the requirement for a given module from the object.
575
576This method returns the requirements object.
577
578=head2 requirements_for_module
579
580  $req->requirements_for_module( $module );
581
582This returns a string containing the version requirements for a given module in
583the format described in L<CPAN::Meta::Spec> or undef if the given module has no
584requirements. This should only be used for informational purposes such as error
585messages and should not be interpreted or used for comparison (see
586L</accepts_module> instead.)
587
588=head2 required_modules
589
590This method returns a list of all the modules for which requirements have been
591specified.
592
593=head2 clone
594
595  $req->clone;
596
597This method returns a clone of the invocant.  The clone and the original object
598can then be changed independent of one another.
599
600=head2 is_simple
601
602This method returns true if and only if all requirements are inclusive minimums
603-- that is, if their string expression is just the version number.
604
605=head2 is_finalized
606
607This method returns true if the requirements have been finalized by having the
608C<finalize> method called on them.
609
610=head2 finalize
611
612This method marks the requirements finalized.  Subsequent attempts to change
613the requirements will be fatal, I<if> they would result in a change.  If they
614would not alter the requirements, they have no effect.
615
616If a finalized set of requirements is cloned, the cloned requirements are not
617also finalized.
618
619=head2 as_string_hash
620
621This returns a reference to a hash describing the requirements using the
622strings in the F<META.yml> specification.
623
624For example after the following program:
625
626  my $req = CPAN::Meta::Requirements->new;
627
628  $req->add_minimum('CPAN::Meta::Requirements' => 0.102);
629
630  $req->add_minimum('Library::Foo' => 1.208);
631
632  $req->add_maximum('Library::Foo' => 2.602);
633
634  $req->add_minimum('Module::Bar'  => 'v1.2.3');
635
636  $req->add_exclusion('Module::Bar'  => 'v1.2.8');
637
638  $req->exact_version('Xyzzy'  => '6.01');
639
640  my $hashref = $req->as_string_hash;
641
642C<$hashref> would contain:
643
644  {
645    'CPAN::Meta::Requirements' => '0.102',
646    'Library::Foo' => '>= 1.208, <= 2.206',
647    'Module::Bar'  => '>= v1.2.3, != v1.2.8',
648    'Xyzzy'        => '== 6.01',
649  }
650
651=head2 add_string_requirement
652
653  $req->add_string_requirement('Library::Foo' => '>= 1.208, <= 2.206');
654
655This method parses the passed in string and adds the appropriate requirement
656for the given module.  It understands version ranges as described in the
657L<CPAN::Meta::Spec/Version Ranges>. For example:
658
659=over 4
660
661=item 1.3
662
663=item >= 1.3
664
665=item <= 1.3
666
667=item == 1.3
668
669=item != 1.3
670
671=item > 1.3
672
673=item < 1.3
674
675=item >= 1.3, != 1.5, <= 2.0
676
677A version number without an operator is equivalent to specifying a minimum
678(C<E<gt>=>).  Extra whitespace is allowed.
679
680=back
681
682=head2 from_string_hash
683
684  my $req = CPAN::Meta::Requirements->from_string_hash( \%hash );
685
686This is an alternate constructor for a CPAN::Meta::Requirements object.  It takes
687a hash of module names and version requirement strings and returns a new
688CPAN::Meta::Requirements object.
689
690=for :stopwords cpan testmatrix url annocpan anno bugtracker rt cpants kwalitee diff irc mailto metadata placeholders metacpan
691
692=head1 SUPPORT
693
694=head2 Bugs / Feature Requests
695
696Please report any bugs or feature requests through the issue tracker
697at L<https://github.com/dagolden/CPAN-Meta-Requirements/issues>.
698You will be notified automatically of any progress on your issue.
699
700=head2 Source Code
701
702This is open source software.  The code repository is available for
703public review and contribution under the terms of the license.
704
705L<https://github.com/dagolden/CPAN-Meta-Requirements>
706
707  git clone https://github.com/dagolden/CPAN-Meta-Requirements.git
708
709=head1 AUTHORS
710
711=over 4
712
713=item *
714
715David Golden <dagolden@cpan.org>
716
717=item *
718
719Ricardo Signes <rjbs@cpan.org>
720
721=back
722
723=head1 COPYRIGHT AND LICENSE
724
725This software is copyright (c) 2010 by David Golden and Ricardo Signes.
726
727This is free software; you can redistribute it and/or modify it under
728the same terms as the Perl 5 programming language system itself.
729
730=cut
731