File Coverage

blib/lib/Dependency/Resolver.pm
Criterion Covered Total %
statement 66 66 100.0
branch 12 14 85.7
condition 7 8 87.5
subroutine 9 9 100.0
pod 1 5 20.0
total 95 102 93.1


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