File Coverage

blib/lib/Dependency/Resolver.pm
Criterion Covered Total %
statement 62 62 100.0
branch 11 12 91.6
condition 4 4 100.0
subroutine 9 9 100.0
pod 1 5 20.0
total 87 92 94.5


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