File Coverage

blib/lib/Algorithm/Dependency.pm
Criterion Covered Total %
statement 55 58 94.8
branch 20 24 83.3
condition n/a
subroutine 12 13 92.3
pod 8 8 100.0
total 95 103 92.2


line stmt bran cond sub pod time code
1             package Algorithm::Dependency; # git description: c08ce7f
2             # ABSTRACT: Base class for implementing various dependency trees
3              
4             #pod =pod
5             #pod
6             #pod =head1 SYNOPSIS
7             #pod
8             #pod use Algorithm::Dependency;
9             #pod use Algorithm::Dependency::Source::File;
10             #pod
11             #pod # Load the data from a simple text file
12             #pod my $data_source = Algorithm::Dependency::Source::File->new( 'foo.txt' );
13             #pod
14             #pod # Create the dependency object, and indicate the items that are already
15             #pod # selected/installed/etc in the database
16             #pod my $dep = Algorithm::Dependency->new(
17             #pod source => $data_source,
18             #pod selected => [ 'This', 'That' ]
19             #pod ) or die 'Failed to set up dependency algorithm';
20             #pod
21             #pod # For the item 'Foo', find out the other things we also have to select.
22             #pod # This WON'T include the item we selected, 'Foo'.
23             #pod my $also = $dep->depends( 'Foo' );
24             #pod print $also
25             #pod ? "By selecting 'Foo', you are also selecting the following items: "
26             #pod . join( ', ', @$also )
27             #pod : "Nothing else to select for 'Foo'";
28             #pod
29             #pod # Find out the order we need to act on the items in.
30             #pod # This WILL include the item we selected, 'Foo'.
31             #pod my $schedule = $dep->schedule( 'Foo' );
32             #pod
33             #pod =head1 DESCRIPTION
34             #pod
35             #pod Algorithm::Dependency is a framework for creating simple read-only
36             #pod dependency hierarchies, where you have a set of items that rely on other
37             #pod items in the set, and require actions on them as well.
38             #pod
39             #pod Despite the most visible of these being software installation systems like
40             #pod the CPAN installer, or Debian apt-get, they are useful in other situations.
41             #pod This module intentionally uses implementation-neutral words, to avoid
42             #pod confusion.
43             #pod
44             #pod =head2 Terminology
45             #pod
46             #pod The term C refers to a single entity, such as a single software
47             #pod package, in the overall set of possible entities. Internally, this is a
48             #pod fairly simple object. See L for details.
49             #pod
50             #pod The term C
51             #pod already been acted up in the required way. For example, if the software
52             #pod package had already been installed, and didn't need to be re-installed,
53             #pod it would be C.
54             #pod
55             #pod The term C refers to a location that contains the master set of
56             #pod items. This will be very application specific, and might be a flat file,
57             #pod some form of database, the list of files in a folder, or generated
58             #pod dynamically.
59             #pod
60             #pod =head2 General Description
61             #pod
62             #pod Algorithm::Dependency implements algorithms relating to dependency
63             #pod hierarchies. To use this framework, all you need is a source for the master
64             #pod list of all the items, and a list of those already selected. If your
65             #pod dependency hierarchy doesn't require the concept of items that are already
66             #pod selected, simply don't pass anything to the constructor for it.
67             #pod
68             #pod Please note that the class Algorithm::Dependency does NOT implement an
69             #pod ordering, for speed and simplicity reasons. That is, the C it
70             #pod provides is not in any particular order. If item 'A' depends on item 'B',
71             #pod it will not place B before A in the schedule. This makes it unsuitable for
72             #pod things like software installers, as they typically would need B to be
73             #pod installed before A, or the installation of A would fail.
74             #pod
75             #pod For dependency hierarchies requiring the items to be acted on in a particular
76             #pod order, either top down or bottom up, see L.
77             #pod It should be more applicable for your needs. This is the the subclass you
78             #pod would probably use to implement a simple ( non-versioned ) package
79             #pod installation system. Please note that an ordered hierarchy has additional
80             #pod constraints. For example, circular dependencies ARE legal in a
81             #pod non-ordered hierarchy, but ARE NOT legal in an ordered hierarchy.
82             #pod
83             #pod =head2 Extending
84             #pod
85             #pod A module for creating a source from a simple flat file is included. For
86             #pod details see L. Information on creating
87             #pod a source for your particular use is in L.
88             #pod
89             #pod =head1 METHODS
90             #pod
91             #pod =cut
92              
93 8     8   365586 use 5.005;
  8         83  
94 8     8   46 use strict;
  8         15  
  8         252  
95 8     8   3604 use Params::Util qw{_INSTANCE _ARRAY};
  8         31901  
  8         575  
96 8     8   3404 use Algorithm::Dependency::Item ();
  8         20  
  8         161  
97 8     8   3454 use Algorithm::Dependency::Source ();
  8         19  
  8         5067  
98              
99             our $VERSION = '1.111';
100              
101              
102             #####################################################################
103             # Constructor
104              
105             #pod =pod
106             #pod
107             #pod =head2 new %args
108             #pod
109             #pod The constructor creates a new context object for the dependency algorithms to
110             #pod act in. It takes as argument a series of options for creating the object.
111             #pod
112             #pod =over 4
113             #pod
114             #pod =item source => $Source
115             #pod
116             #pod The only compulsory option is the source of the dependency items. This is
117             #pod an object of a subclass of L. In practical terms,
118             #pod this means you will create the source object before creating the
119             #pod Algorithm::Dependency object.
120             #pod
121             #pod =item selected => [ 'A', 'B', 'C', etc... ]
122             #pod
123             #pod The C option provides a list of those items that have already been
124             #pod 'selected', acted upon, installed, or whatever. If another item depends on one
125             #pod in this list, we don't have to include it in the output of the C or
126             #pod C methods.
127             #pod
128             #pod =item ignore_orphans => 1
129             #pod
130             #pod Normally, the item source is expected to be largely perfect and error free.
131             #pod An 'orphan' is an item name that appears as a dependency of another item, but
132             #pod doesn't exist, or has been deleted.
133             #pod
134             #pod By providing the C flag, orphans are simply ignored. Without
135             #pod the C flag, an error will be returned if an orphan is found.
136             #pod
137             #pod =back
138             #pod
139             #pod The C constructor returns a new Algorithm::Dependency object on success,
140             #pod or C on error.
141             #pod
142             #pod =cut
143              
144             sub new {
145 18     18 1 6101 my $class = shift;
146 18         57 my %args = @_;
147 18 100       157 my $source = _INSTANCE($args{source}, 'Algorithm::Dependency::Source')
148             or return undef;
149              
150             # Create the object
151 17         65 my $self = bless {
152             source => $source, # Source object
153             selected => {},
154             }, $class;
155              
156             # Were we given the 'ignore_orphans' flag?
157 17 100       55 if ( $args{ignore_orphans} ) {
158 5         14 $self->{ignore_orphans} = 1;
159             }
160              
161             # Done, unless we have been given some selected items
162 17 100       103 _ARRAY($args{selected}) or return $self;
163              
164             # Make sure each of the selected ids exists
165 5         14 my %selected = ();
166 5         37 foreach my $id ( @{ $args{selected} } ) {
  5         16  
167             # Does the item exist?
168 15 50       42 return undef unless $source->item($id);
169              
170             # Is it a duplicate
171 15 50       39 return undef if $selected{$id};
172              
173             # Add to the selected index
174 15         33 $selected{$id} = 1;
175             }
176              
177 5         18 $self->{selected} = \%selected;
178 5         41 $self;
179             }
180              
181              
182              
183              
184              
185             #####################################################################
186             # Basic methods
187              
188             #pod =pod
189             #pod
190             #pod =head2 source
191             #pod
192             #pod The C method retrieves the L object
193             #pod for the algorithm context.
194             #pod
195             #pod =cut
196              
197 10     10 1 5147 sub source { $_[0]->{source} }
198              
199             #pod =pod
200             #pod
201             #pod =head2 selected_list
202             #pod
203             #pod The C method returns, as a list and in alphabetical order,
204             #pod the list of the names of the selected items.
205             #pod
206             #pod =cut
207              
208 5     5 1 11 sub selected_list { sort keys %{$_[0]->{selected}} }
  5         41  
209              
210             #pod =pod
211             #pod
212             #pod =head2 selected $name
213             #pod
214             #pod Given an item name, the C method will return true if the item is
215             #pod selected, false is not, or C if the item does not exist, or an error
216             #pod occurs.
217             #pod
218             #pod =cut
219              
220 13     13 1 59 sub selected { $_[0]->{selected}->{$_[1]} }
221              
222             #pod =pod
223             #pod
224             #pod =head2 item $name
225             #pod
226             #pod The C method fetches and returns the item object, as specified by the
227             #pod name argument.
228             #pod
229             #pod Returns an L object on success, or C if
230             #pod an item does not exist for the argument provided.
231             #pod
232             #pod =cut
233              
234 10     10 1 42 sub item { $_[0]->{source}->item($_[1]) }
235              
236              
237              
238              
239              
240             #####################################################################
241             # Main algorithm methods
242              
243             #pod =pod
244             #pod
245             #pod =head2 depends $name1, ..., $nameN
246             #pod
247             #pod Given a list of one or more item names, the C method will return
248             #pod a reference to an array containing a list of the names of all the OTHER
249             #pod items that also have to be selected to meet dependencies.
250             #pod
251             #pod That is, if item A depends on B and C then the C method would
252             #pod return a reference to an array with B and C. ( C<[ 'B', 'C' ]> )
253             #pod
254             #pod If multiple item names are provided, the same applies. The list returned
255             #pod will not contain duplicates.
256             #pod
257             #pod The method returns a reference to an array of item names on success, a
258             #pod reference to an empty array if no other items are needed, or C
259             #pod on error.
260             #pod
261             #pod =cut
262              
263             sub depends {
264 279     279 1 92573 my $self = shift;
265 279 50       796 my @stack = @_ or return undef;
266 279         501 my @depends = ();
267 279         456 my %checked = ();
268              
269             # Process the stack
270 279         721 while ( my $id = shift @stack ) {
271             # Does the id exist?
272             my $Item = $self->{source}->item($id)
273 772 100       2292 or $self->{ignore_orphans} ? next : return undef;
    100          
274              
275             # Skip if selected or checked
276 763 100       1798 next if $checked{$id};
277              
278             # Add its depends to the stack
279 686         1636 push @stack, $Item->depends;
280 686         1285 $checked{$id} = 1;
281              
282             # Add anything to the final output that wasn't one of
283             # the original input.
284 686 100       1172 unless ( scalar grep { $id eq $_ } @_ ) {
  750         2433  
285 396         1131 push @depends, $id;
286             }
287             }
288              
289             # Remove any items already selected
290 272         494 my $s = $self->{selected};
291 272         695 return [ sort grep { ! $s->{$_} } @depends ];
  396         1414  
292             }
293              
294             #pod =pod
295             #pod
296             #pod =head2 schedule $name1, ..., $nameN
297             #pod
298             #pod Given a list of one or more item names, the C method will return,
299             #pod as a reference to an array, the ordered list of items you should act upon.
300             #pod
301             #pod This would be the original names provided, plus those added to satisfy
302             #pod dependencies, in the preferred order of action. For the normal algorithm,
303             #pod where order it not important, this is alphabetical order. This makes it
304             #pod easier for someone watching a program operate on the items to determine
305             #pod how far you are through the task and makes any logs easier to read.
306             #pod
307             #pod If any of the names you provided in the arguments is already selected, it
308             #pod will not be included in the list.
309             #pod
310             #pod The method returns a reference to an array of item names on success, a
311             #pod reference to an empty array if no items need to be acted upon, or C
312             #pod on error.
313             #pod
314             #pod =cut
315              
316             sub schedule {
317 156     156 1 58947 my $self = shift;
318 156 50       530 my @items = @_ or return undef;
319              
320             # Get their dependencies
321 156 100       397 my $depends = $self->depends( @items ) or return undef;
322              
323             # Now return a combined list, removing any items already selected.
324             # We are allowed to return an empty list.
325 154         294 my $s = $self->{selected};
326 154         321 return [ sort grep { ! $s->{$_} } @items, @$depends ];
  351         1082  
327             }
328              
329             #pod =pod
330             #pod
331             #pod =head2 schedule_all;
332             #pod
333             #pod The C method acts the same as the C method, but
334             #pod returns a schedule that selected all the so-far unselected items.
335             #pod
336             #pod =cut
337              
338             sub schedule_all {
339 0     0 1   my $self = shift;
340 0           $self->schedule( map { $_->id } $self->source->items );
  0            
341             }
342              
343             1;
344              
345             __END__