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