File Coverage

blib/lib/CPAN/Mini/Visit/Simple.pm
Criterion Covered Total %
statement 66 184 35.8
branch 8 94 8.5
condition 1 21 4.7
subroutine 19 29 65.5
pod 10 10 100.0
total 104 338 30.7


line stmt bran cond sub pod time code
1             package CPAN::Mini::Visit::Simple;
2 6     6   72874 use 5.010;
  6         39  
3 6     6   25 use strict;
  6         9  
  6         95  
4 6     6   20 use warnings;
  6         9  
  6         246  
5              
6             our $VERSION = '0.017';
7             $VERSION = eval $VERSION; ## no critic
8              
9 6     6   2972 use Archive::Extract;
  6         864752  
  6         237  
10 6     6   47 use Carp;
  6         9  
  6         348  
11 6     6   2766 use CPAN::Mini ();
  6         609333  
  6         141  
12 6     6   42 use Cwd;
  6         12  
  6         341  
13 6     6   31 use File::Basename qw( dirname basename );
  6         12  
  6         230  
14 6     6   30 use File::Find;
  6         19  
  6         217  
15 6     6   31 use File::Spec;
  6         11  
  6         105  
16 6     6   22 use File::Temp;
  6         10  
  6         321  
17 6     6   2142 use Path::Class;
  6         76574  
  6         271  
18 6     6   36 use Scalar::Util qw( reftype );
  6         11  
  6         231  
19 6         6505 use CPAN::Mini::Visit::Simple::Auxiliary qw(
20             $ARCHIVE_REGEX
21             dedupe_superseded
22             get_lookup_table
23             normalize_version_number
24 6     6   2580 );
  6         14  
25              
26             sub new {
27 5     5 1 5736 my ($class, $args) = @_;
28 5         8 my %data = ();
29 5 100       13 if ( ! $args->{minicpan} ) {
30             # Work around a bug in CPAN::Mini:
31             # https://rt.cpan.org/Public/Bug/Display.html?id=55272
32 2         11 my $config_file = CPAN::Mini->config_file({});
33 2 50 33     76 croak "CPAN::Mini config file not located: $!"
34             unless ( defined $config_file and -e $config_file );
35 2         9 my %config = CPAN::Mini->read_config;
36 2 50       334 if ( $config{local} ) {
37 2         5 $data{minicpan} = $config{local};
38             }
39             }
40             else {
41 3         5 $data{minicpan} = $args->{minicpan};
42             }
43             croak "Directory $data{minicpan} not found"
44 5 100       243 unless (-d $data{minicpan});
45              
46 4         34 my $id_dir = File::Spec->catdir($data{minicpan}, qw( authors id ));
47 4 100       118 croak "Absence of $id_dir implies no valid minicpan"
48             unless -d $id_dir;
49 3         7 $data{id_dir} = $id_dir;
50              
51 3         9 my $self = bless \%data, $class;
52 3         14 return $self;
53             }
54              
55             sub get_minicpan {
56 1     1 1 271 my $self = shift;
57 1         5 return $self->{minicpan};
58             }
59              
60             sub get_id_dir {
61 1     1 1 237 my $self = shift;
62 1         2 return $self->{id_dir};
63             }
64             sub identify_distros {
65 0     0 1   my ($self, $args) = @_;
66              
67             croak "Bad argument 'list' provided to identify_distros()"
68 0 0         if exists $args->{list};
69              
70 0 0         if ( defined $args->{start_dir} ) {
71             croak "Directory $args->{start_dir} not found"
72 0 0         unless (-d $args->{start_dir} );
73             croak "Directory $args->{start_dir} must be subdirectory of $self->{id_dir}"
74 0 0         unless ( $args->{start_dir} =~ m/\Q$self->{id_dir}\E/ );
75 0           $self->{start_dir} = $args->{start_dir};
76             }
77             else {
78 0           $self->{start_dir} = $self->{id_dir};
79             }
80              
81 0 0         if ( defined $args->{pattern} ) {
82             croak "'pattern' is a regex, which means it must be a REGEXP ref"
83 0 0         unless (reftype($args->{pattern}) eq 'REGEXP');
84             }
85              
86 0           my $found_ref = $self->_search_from_start_dir( $args );
87 0           $self->{list} = dedupe_superseded( $found_ref );
88 0           return 1;
89             }
90              
91             sub identify_distros_from_derived_list {
92 0     0 1   my ($self, $args) = @_;
93             croak "Bad argument 'start_dir' provided to identify_distros_from_derived_list()"
94 0 0         if exists $args->{start_dir};
95             croak "Bad argument 'pattern' provided to identify_distros_from_derived_list()"
96 0 0         if exists $args->{pattern};
97             croak "identify_distros_from_derived_list() needs 'list' element"
98 0 0         unless exists $args->{list};
99             croak "Value of 'list' must be array reference"
100 0 0         unless reftype($args->{list}) eq 'ARRAY';
101             croak "Value of 'list' must be non-empty"
102 0 0         unless scalar(@{$args->{list}});
  0            
103 0           $self->{list} = dedupe_superseded( $args->{list} );
104 0           return 1;
105             }
106              
107             sub _search_from_start_dir {
108 0     0     my ($self, $args) = @_;
109 0           my @found = ();
110             find(
111             {
112             follow => 0,
113             no_chdir => 1,
114 0     0     preprocess => sub { my @files = sort @_; return @files },
  0            
115             wanted => sub {
116 0 0   0     return unless /$ARCHIVE_REGEX/;
117 0 0         if ( defined $args->{pattern} ) {
118 0 0         return unless $_ =~ m/$args->{pattern}/;
119             }
120 0           push @found, File::Spec->canonpath($File::Find::name);
121             },
122             },
123             $self->{start_dir},
124 0           );
125 0           return \@found;
126             }
127              
128             sub say_list {
129 0     0 1   my ($self, $args) = @_;
130 0 0         if (not defined $args) {
131 0           say $_ for @{$self->{list}};
  0            
132             }
133             else {
134 0 0         croak "Argument must be hashref" unless reftype($args) eq 'HASH';
135 0 0         croak "Need 'file' element in hashref" unless exists $args->{file};
136             open my $FH, '>', $args->{file}
137 0 0         or croak "Unable to open handle to $args->{file} for writing";
138 0           say $FH $_ for @{$self->{list}};
  0            
139 0 0         close $FH
140             or croak "Unable to close handle to $args->{file} after writing";
141             }
142             }
143              
144             sub get_list {
145 0     0 1   my ($self) = @_;
146 0 0         return unless defined $self->{list};
147 0           return @{$self->{list}};
  0            
148             }
149              
150             sub get_list_ref {
151 0     0 1   my ($self) = @_;
152 0 0         return unless defined $self->{list};
153 0           return $self->{list};
154             }
155              
156             sub refresh_list {
157 0     0 1   my ($self, $args) = @_;
158             croak "Need 'derived_list' whose value is list of distributions needing refreshment"
159 0 0         unless exists $args->{derived_list};
160             croak "Value of 'derived_list' must be array reference"
161 0 0         unless reftype( $args->{derived_list} ) eq 'ARRAY';
162              
163             # Call identify_distros() with all arguments except 'derived_list',
164             # i.e., with 'start_dir' and/or 'pattern'.
165 0           my %reduced_args = map { $_ => 1 } @{ $args->{derived_list} };
  0            
  0            
166 0           delete $reduced_args{derived_list};
167 0           my $rv = $self->identify_distros( \%reduced_args );
168              
169             # So now we have an updated primary list ($self->{list}).
170             # We will need to make a hash out of that where they key is the stem of
171             # the distribution name and the value is the version.
172             # We will make a similar hash from the derived list.
173              
174 0           my $primary = get_lookup_table( $self->get_list_ref() );
175 0           my $derived = get_lookup_table( $args->{derived_list} );
176              
177 0           foreach my $stem ( keys %{$derived} ) {
  0            
178 0 0         if ( not exists $primary->{$stem} ) {
    0          
179 0           delete $derived->{$stem};
180             }
181             elsif ( $primary->{$stem}{version} > $derived->{$stem}{version} ) {
182 0           $derived->{$stem}{version} = $primary->{$stem}{version};
183 0           $derived->{$stem}{distro} = $primary->{$stem}{distro};
184             }
185             else {
186             # nothing to do
187             }
188             }
189              
190 0           return [ sort map { $derived->{$_}{distro} } keys %{$derived} ];
  0            
  0            
191             }
192              
193             sub visit {
194 0     0 1   my ($self, $args) = @_;
195 6     6   41 no warnings 'once';
  6         16  
  6         266  
196 0           local $Archive::Extract::PREFER_BIN = 1;
197 6     6   28 use warnings 'once';
  6         10  
  6         2863  
198 0 0         local $Archive::Extract::WARN = $args->{quiet} ? 0 : 1;
199             croak "Must have a list of distributions on which to take action"
200 0 0         unless defined $self->{list};
201             croak "'visit()' method requires 'action' subroutine reference"
202             unless (
203             ( defined ($args->{action}) )
204             and
205             ( defined reftype($args->{action}) )
206             and
207 0 0 0       ( reftype($args->{action}) eq 'CODE' )
      0        
208             );
209 0           my @action_args = ();
210 0 0         if ( defined $args->{action_args} ) {
211             croak "'action_args' must be array reference"
212             unless (
213             ( defined reftype($args->{action_args}) )
214             and
215 0 0 0       ( reftype($args->{action_args}) eq 'ARRAY' )
216             );
217 0           @action_args = @{ $args->{action_args} };
  0            
218             }
219 0 0         if ( defined $args->{do_not_visit} ) {
220             croak "'do_not_visit' must be array reference"
221             unless (
222             ( defined reftype($args->{do_not_visit}) )
223             and
224 0 0 0       ( reftype($args->{do_not_visit}) eq 'ARRAY' )
225             );
226             }
227 0           my $here = cwd();
228 0           my @visit_list;
229 0 0         if ( defined $args->{do_not_visit} ) {
230 0           my %do_not_visit = map { $_ => 1 } @{$args->{do_not_visit}};
  0            
  0            
231 0           for my $d (@{$self->{list}}) {
  0            
232 0 0         push @visit_list, $d unless $do_not_visit{$d};
233             }
234             }
235             else {
236 0           @visit_list = @{$self->{list}};
  0            
237             }
238              
239 0           LIST: foreach my $distro (@visit_list) {
240              
241 0           my $olderr;
242             # stderr > /dev/null if quiet
243 0 0         if ( not $Archive::Extract::WARN ) {
244 0           open $olderr, ">&STDERR";
245 0           open STDERR, ">", File::Spec->devnull;
246             }
247 0           my $tdir = File::Temp->newdir();
248 0 0         chdir $tdir or croak "Unable to change to temporary directory";
249 0           my $ae = Archive::Extract->new( archive => $distro );
250 0 0         my $extract_ok = $ae->extract( to => $tdir ) or do {
251 0           warn "Unable to extract $distro; skipping";
252 0 0         if ( not $Archive::Extract::WARN ) {
253 0           open STDERR, ">&", $olderr;
254 0           close $olderr;
255             }
256 0           next LIST;
257             };
258              
259             # restore stderr if quiet
260 0 0         if ( not $Archive::Extract::WARN ) {
261 0           open STDERR, ">&", $olderr;
262 0           close $olderr;
263             }
264             # Note: It's not clear what would cause $extract_ok to be false.
265             # Things that are not valid archives appear to be caught by
266             # Archive::Extract::new() and rendered as fatal. So following block
267             # is unlikely to be covered by test suite.
268 0 0 0       if ( ( not $extract_ok ) and $Archive::Extract::WARN ) {
269 0           carp "Couldn't extract '$distro'";
270 0           return;
271             }
272             # most distributions unpack a single directory that we must enter
273             # but some behave poorly and unpack to the current directory
274 0           my $dir = Path::Class::Dir->new();
275 0           my @children = $dir->children;
276 0 0 0       if ( ( @children == 1 ) and ( -d $children[0] ) ) {
277 0           chdir $children[0];
278             }
279              
280 0           &{$args->{action}}($distro, @action_args);# execute command
  0            
281 0 0         chdir $here or croak "Unable to change back to starting point";
282             }
283 0           return 1;
284             }
285              
286             1;