File Coverage

blib/lib/Dependency/Resolver.pm
Criterion Covered Total %
statement 68 68 100.0
branch 14 16 87.5
condition 7 8 87.5
subroutine 9 9 100.0
pod 1 5 20.0
total 99 106 93.4


line stmt bran cond sub pod time code
1             package Dependency::Resolver;
2             $Dependency::Resolver::VERSION = '0.07';
3 2     2   43544 use utf8;
  2         18  
  2         10  
4 2     2   1514 use Moose;
  2         967703  
  2         15  
5              
6             has modules => (
7             is => "rw",
8             isa => "HashRef",
9             default => sub { {} },
10             );
11              
12             has addr => (
13             is => "rw",
14             isa => "HashRef",
15             default => sub { {} },
16             );
17              
18             sub add {
19 2     2 0 1781 my ($self, @nodes) = @_;
20              
21 2         4 foreach my $n (@nodes){
22 10         11 push(@{$self->modules->{$n->{name}}}, $n);
  10         310  
23 10         298 $self->addr->{"$n"} = $n;
24             }
25 2         6 return 1;
26             }
27              
28             sub dep_resolv {
29 14     14 1 992 my ($self, $node, $resolved, $seen) = @_;
30              
31 14   100     31 $resolved ||= [];
32 14   100     30 $seen ||= [];
33              
34 14         16 push( @$seen, $node);
35 14         16 for my $dep_version ( @{$node->{deps}} ) {
  14         38  
36              
37 15         30 my $dep = $self->search_best_dep($dep_version);
38              
39             # if dep is not in resolved
40 15 100       36 if ( ! grep { $_ eq $dep} @$resolved ) {
  16         40  
41              
42             die sprintf "Circular reference detected: %s -> %s", $node->name, $dep->name
43 11 50       14 if ( grep { $_ eq $dep} @$seen);
  26         66  
44              
45 11         24 $self->dep_resolv( $dep, $resolved, $seen );
46             }
47             }
48 10         15 push( @$resolved, $node);
49 10         20 return $resolved;
50             }
51              
52              
53             sub parse_module_args {
54 27     27 0 38 my($self, $module) = @_;
55              
56 27         79 $module =~ s/\s+//g;
57 27         166 $module =~ m/^([A-Za-z0-9_:]+)([\s!<>=]+)(.*)$/;
58 27         65 my ($mod,$op,$ver) = ($1, $2, $3);
59 27 100       52 if ( ! defined $op ) { $mod = $module, $op = '>=', $ver = 0};
  10         15  
60 27         91 return ($mod,$op,$ver);
61             }
62              
63             sub search_best_dep{
64 22     22 0 64 my($self, $dep_args) = @_;
65              
66             # ex: dep_args : B >1, B<=3
67 22         30 my $result = [];
68 22         48 foreach my $dep ( split(/,/, $dep_args)){
69              
70 27         53 my($mod,$op,$ver) = $self->parse_module_args($dep);
71 27         92 my $modules = $self->get_modules($mod, $op, $ver);
72              
73 27         41 my %count = ();
74             $count{$_}++
75 27         109 for (@$result, @$modules);
76              
77 27         49 my @intersection = grep { $count{$_} == 2 } keys %count;
  47         99  
78              
79 27 100       54 if( $result->[0] ){
80 5         12 $result = [ $self->_addr_to_mod(@intersection) ];
81             }
82 22         56 else{ $result = $modules}
83             }
84              
85 22         51 $result = $self->_sort_by_version($result);
86 22 100       75 die "Module $dep_args non found ! " if ( ! defined $result->[-1] );
87             # returns the highest version
88 21         55 return $result->[-1];
89             }
90              
91              
92             sub _sort_by_version {
93 56     56   66 my ( $self, $nodes ) = @_;
94              
95 56         150 return [ sort { $a->{version} cmp $b->{version} } @$nodes ];
  29         98  
96             }
97              
98             sub _addr_to_mod {
99 5     5   10 my ( $self,@addrs ) = @_;
100              
101 5         8 return map { $self->addr->{$_} } @addrs;
  10         303  
102             }
103              
104              
105             sub get_modules{
106 35     35 0 316 my $self = shift;
107 35         42 my $mod = shift;
108 35   50     76 my $op = shift || '>=';
109 35   100     79 my $ver = shift || 0;
110 35 50       67 $op = '==' if $op eq '=';
111              
112              
113             die "Operator '$op' is unknown"
114 35 100       47 if (! grep { $op eq $_ } qw(== != < > >= <= ));
  210         368  
115              
116 34         43 my $modules = [];
117 34         41 foreach my $m (@{$self->modules->{$mod}}){
  34         1154  
118 82         126 my $mver = $m->{version};
119 82 100       3118 if ( eval "$mver $op $ver" ){
120 53         155 push(@$modules, $m);
121             }
122             }
123 34         89 return $self->_sort_by_version($modules);
124             }
125              
126              
127             =head1 NAME
128              
129             Dependency::Resolver - Simple Dependency resolving algorithm
130              
131             =head1 VERSION
132              
133             version 0.07
134              
135             based on http://www.electricmonk.nl/log/2008/08/07/dependency-resolving-algorithm/
136              
137             =head1 SYNOPSIS
138              
139             build dependencies tree
140              
141             my $a = { name => 'A' , version => 1, deps => [ 'B == 1', 'D']};
142             my $a2 = { name => 'A' , version => 2, deps => [ 'B => 2, B < 3', 'D']};
143             my $b1 = { name => 'B' , version => 1, deps => [ 'C == 1', 'E'] };
144             my $b2 = { name => 'B' , version => 2, deps => [ 'C == 2', 'E'] };
145             my $b3 = { name => 'B' , version => 3, deps => [ 'C == 3', 'E'] };
146             my $c1 = { name => 'C' , version => 1, deps => [ 'D', 'E'] };
147             my $c2 = { name => 'C' , version => 2, deps => [ 'D', 'E'] };
148             my $c3 = { name => 'C' , version => 3, deps => [ 'D', 'E'] };
149             my $d = { name => 'D' , version => 1};
150             my $e = { name => 'E' , version => 1};
151              
152             my $resolver = Dependency::Resolver->new;
153              
154             my $resolved = $resolver->dep_resolv($a);
155             # return [ $d, $e, $c1, $b1, $a ]
156              
157             $resolved = $resolver->dep_resolv($a2);
158             # return [ $d, $e, $c2, $b2, $a2 ]
159              
160             # method used by dep_resolv (get_module, search_best_dep)
161             $resolver->get_modules('B', '==', 1); # return [$b1]
162             $resolver->get_modules('B', '<=', 2); # return [$b1, $b2]
163             $resolver->get_modules('B', '>=', 1); # return [$b1, $b2, $b3]
164              
165             $resolver->search_best_dep('B >= 1'); # return $b3 (highest version)
166             $resolver->search_best_dep('B >= 1, B!=3'); # return $b2
167             $resolver->search_best_dep('B >= 1, B<=3, B!=3'); # return $b2
168              
169              
170             =head1 SUBROUTINES/METHODS
171              
172             =head2 dep_resolv($node)
173              
174             returns an arrayref of nodes resolved
175              
176             =head1 AUTHOR
177              
178             Daniel Brosseau, C<< <dab at catapulse.org> >>
179              
180             =head1 BUGS
181              
182             Please report any bugs or feature requests to C<bug-dependency-resolver at rt.cpan.org>, or through
183             the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Dependency-Resolver>. I will be notified, and then you'll
184             automatically be notified of progress on your bug as I make changes.
185              
186              
187              
188              
189             =head1 SUPPORT
190              
191             You can find documentation for this module with the perldoc command.
192              
193             perldoc Dependency::Resolver
194              
195              
196             You can also look for information at:
197              
198             =over 4
199              
200             =item * RT: CPAN's request tracker (report bugs here)
201              
202             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Dependency-Resolver>
203              
204             =item * AnnoCPAN: Annotated CPAN documentation
205              
206             L<http://annocpan.org/dist/Dependency-Resolver>
207              
208             =item * CPAN Ratings
209              
210             L<http://cpanratings.perl.org/d/Dependency-Resolver>
211              
212             =item * Search CPAN
213              
214             L<http://search.cpan.org/dist/Dependency-Resolver/>
215              
216             =back
217              
218              
219             =head1 ACKNOWLEDGEMENTS
220              
221              
222             =head1 LICENSE AND COPYRIGHT
223              
224             Copyright 2015 Daniel Brosseau.
225              
226             This program is free software; you can redistribute it and/or modify it
227             under the terms of the the Artistic License (2.0). You may obtain a
228             copy of the full license at:
229              
230             L<http://www.perlfoundation.org/artistic_license_2_0>
231              
232             Any use, modification, and distribution of the Standard or Modified
233             Versions is governed by this Artistic License. By using, modifying or
234             distributing the Package, you accept this license. Do not use, modify,
235             or distribute the Package, if you do not accept this license.
236              
237             If your Modified Version has been derived from a Modified Version made
238             by someone other than you, you are nevertheless required to ensure that
239             your Modified Version complies with the requirements of this license.
240              
241             This license does not grant you the right to use any trademark, service
242             mark, tradename, or logo of the Copyright Holder.
243              
244             This license includes the non-exclusive, worldwide, free-of-charge
245             patent license to make, have made, use, offer to sell, sell, import and
246             otherwise transfer the Package with respect to any patent claims
247             licensable by the Copyright Holder that are necessarily infringed by the
248             Package. If you institute patent litigation (including a cross-claim or
249             counterclaim) against any party alleging that the Package constitutes
250             direct or contributory patent infringement, then this Artistic License
251             to you shall terminate on the date that such litigation is filed.
252              
253             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
254             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
255             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
256             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
257             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
258             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
259             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
260             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
261              
262              
263             =cut
264              
265             1; # End of Dependency::Resolver