File Coverage

blib/lib/Algorithm/DependencySolver/Traversal.pm
Criterion Covered Total %
statement 24 24 100.0
branch n/a
condition n/a
subroutine 12 12 100.0
pod n/a
total 36 36 100.0


line stmt bran cond sub pod time code
1             package Algorithm::DependencySolver::Traversal;
2             $Algorithm::DependencySolver::Traversal::VERSION = '1.01';
3 2     2   2444 use Moose;
  2         4  
  2         60  
4 2     2   10592 use MooseX::FollowPBP;
  2         4  
  2         18  
5 2     2   5723 use MooseX::Method::Signatures;
  2         3  
  2         29  
6              
7 2     2   367 use List::MoreUtils qw(all uniq);
  2         4  
  2         22  
8              
9 2     2   890 use Data::Dumper;
  2         4  
  2         370  
10              
11             =head1 NAME
12              
13             Algorithm::DependencySolver::Traversal - A module for traversing a dependency graph
14              
15             =head1 VERSION
16              
17             version 1.01
18              
19             =head1 SYNOPSIS
20              
21             my $traversal = Algorithm::DependencySolver::Traversal->new(
22             Solver => $solver,
23             visit => sub {
24             my $operation = shift;
25             print "Visited operation: ", $operation->id, "\n";
26             },
27             );
28              
29             $traversal->run;
30              
31             =head1 DESCRIPTION
32              
33             Given an L<Algorithm::DependencySolver::Solver.pm> object, traverses it
34             in such a way that upon entering a node, all of its prerequisites will
35             have already been entered.
36              
37             =head2 Concurrency
38              
39             Currently this module is I<not> thread-safe. However, it has been
40             design in such a way that it should be easy to allow concurrency at a
41             later stage, without needing to break backwards compatibility.
42              
43             Note that if we allow concurrency, the C<visitable> list may be empty,
44             without indicating that the traversal is complete.
45              
46             =head1 METHODS
47              
48             =cut
49              
50              
51             has 'Solver' => (
52             is => 'ro',
53             isa => 'Algorithm::DependencySolver::Solver',
54             required => 1,
55             );
56              
57             has 'visitable' => (
58             is => 'rw',
59             # isa => 'ArrayRef[String]',
60             default => sub { [] },
61             );
62              
63             # indexed by $node->id; value is boolean
64             has 'visited' => (
65             is => 'ro',
66             # isa => 'HashRef[Bool]',
67             default => sub { {} },
68             );
69              
70             has 'visit' => (
71             is => 'rw',
72             isa => 'CodeRef',
73             default => sub { sub { 1 } },
74             );
75              
76             has 'choose' => (
77             is => 'ro',
78             isa => 'CodeRef',
79             default => sub { sub { shift } },
80             );
81              
82              
83              
84             =head2 C<choose>
85              
86             During the traversal, we maintain a list of nodes, C<visitable>, which
87             can be immediately visited. If this list is empty, the traversal is
88             complete.
89              
90             The C<choose> function is called to decide which node is C<visitable>
91             to visit next. Note that C<choose> is guaranteed to be called, even if
92             C<visitable> is a singleton (but not if it's empty).
93              
94             =cut
95              
96 2     2   41934 method choose() {
97             my $size = @{$self->get_visitable};
98             die "choose(): precondition for size failed" unless $size;
99             my $choice = $self->get_choose->(@{$self->get_visitable});
100             die "choose() function didn't make a choice! ($size)"
101             unless defined $choice and $choice ne '';
102             $self->set_visitable([grep {
103             not ($_ eq $choice)
104             } @{$self->get_visitable}]);
105             my $size_diff = $size - @{$self->get_visitable};
106             die "Bad choice; $size_diff" unless $size_diff == 1;
107             return $choice;
108             };
109              
110 2     2   89169 method _add_visitable(@nodes) {
111             for my $node (@nodes) {
112             unless (defined $node and $node ne '') {
113             die "_add_visitable(): nodes must be defined";
114             }
115             }
116             $self->set_visitable(
117             [uniq @nodes, @{$self->get_visitable}]
118             );
119             }
120              
121 2     2   77241 method _can_visit($node_id) {
122             return all {
123             $self->get_visited->{$_}
124             } $self->get_Solver->get_Graph->predecessors($node_id);
125             };
126              
127             =head2 dryrun
128              
129             Create a linear path and return it as an array of the arguments that
130             would have been passed into the C<visit> function.
131              
132             Use C<run_path> to run a path created by C<dryrun>.
133              
134             =cut
135              
136 2     2   38881 method dryrun() {
137             my $visit = $self->get_visit;
138             my @path;
139             $self->set_visit(sub {
140             push @path, \@_;
141             });
142             $self->run();
143             $self->set_visit($visit);
144             return \@path;
145             }
146              
147 2     2   74596 method run_path($path) {
148             for my $args (@$path) {
149             $self->get_visit(@$args);
150             }
151             }
152              
153 2     2   38566 method run() {
154              
155             die "Not a valid graph!" if $self->get_Solver->is_invalid;
156              
157             my $G = $self->get_Solver->get_Graph;
158              
159             $self->_add_visitable($G->predecessorless_vertices);
160              
161             while (@{$self->get_visitable}) {
162             my $node_id = $self->choose();
163             my $node = $self->get_Solver->get_nodes_index->{$node_id};
164             $self->get_visit->($node);
165             $self->get_visited->{$node_id} = 1;
166             $self->_add_visitable(
167             grep { $self->_can_visit($_) } $G->successors($node_id)
168             );
169             }
170             };
171              
172              
173 2     2   475 no Moose;
  2         4  
  2         15  
174             __PACKAGE__->meta->make_immutable;