File Coverage

blib/lib/App/UpdateCPANfile.pm
Criterion Covered Total %
statement 110 110 100.0
branch 23 26 88.4
condition 31 39 79.4
subroutine 25 25 100.0
pod 0 11 0.0
total 189 211 89.5


line stmt bran cond sub pod time code
1             package App::UpdateCPANfile;
2 1     1   234549 use 5.008001;
  1         12  
3 1     1   6 use strict;
  1         3  
  1         20  
4 1     1   5 use warnings;
  1         2  
  1         24  
5 1     1   549 use Module::CPANfile;
  1         17153  
  1         33  
6 1     1   485 use Module::CPANfile::Writer;
  1         128672  
  1         49  
7 1     1   782 use App::UpdateCPANfile::CPANfileSnapshotParser;
  1         3  
  1         47  
8 1     1   503 use App::UpdateCPANfile::PackageDetails;
  1         4  
  1         40  
9 1     1   448 use App::UpdateCPANfile::Change;
  1         3  
  1         31  
10 1     1   7 use Module::CoreList;
  1         3  
  1         13  
11              
12             our $VERSION = "1.0.0";
13              
14             sub new {
15 24     24 0 85057 my ($class, $path, $snapshot_path, $options) = @_;
16 24         159 bless {
17             path => $path,
18             snapshot_path => $snapshot_path,
19             options => $options,
20             }, $class;
21             }
22              
23             sub path {
24 34   100 34 0 1575 $_[0]->{path} // 'cpanfile';
25             }
26              
27             sub snapshot_path {
28 16   100 16 0 142 $_[0]->{snapshot_path} // 'cpanfile.snapshot';
29             }
30              
31             sub options {
32 74   100 74 0 425 $_[0]->{options} // {};
33             }
34              
35             sub parser {
36 49     49 0 133 my ($self) = @_;
37              
38 49   66     271 $self->{parser} //= Module::CPANfile->load($self->path);
39             }
40              
41             sub writer {
42 9     9 0 623 my ($self) = @_;
43              
44 9   66     59 $self->{writer} //= Module::CPANfile::Writer->new($self->path);
45             }
46              
47             sub package_details {
48 27     27 0 115 my ($self) = @_;
49              
50 27   66     319 $self->{package_details} //= App::UpdateCPANfile::PackageDetails->new;
51             }
52              
53             sub pin_dependencies {
54 2     2 0 22 my ($self) = @_;
55 2         8 my $changeset = $self->create_pin_dependencies_changeset;
56 2         301 $self->_save_changes_to_file($changeset);
57 2         618659 return $changeset;
58             }
59              
60             sub update_dependencies {
61 3     3 0 473 my ($self) = @_;
62 3         16 my $changeset = $self->create_update_dependencies_changeset;
63 3         20 my $writer = $self->writer;
64 3         644 $self->_save_changes_to_file($changeset);
65 3         974941 return $changeset;
66             }
67              
68             sub create_pin_dependencies_changeset {
69 13     13 0 367 my ($self) = @_;
70              
71 13         36 my $distributions = App::UpdateCPANfile::CPANfileSnapshotParser->scan_deps($self->snapshot_path);
72              
73 13         521 my $prereqs = $self->parser->prereqs;
74 13         15584 my $added_dependencies = [];
75              
76 13         27 my $all_phases = {};
77 13         46 for my $phase ($prereqs->phases) {
78 21         394 for my $type ($prereqs->types_in($phase)) {
79 25         689 $all_phases->{$type}++;
80             }
81             }
82              
83             # If arguments are omitted, it defaults to "runtime", "build" and "test" for phases and "requires" and "recommends" for types.
84 13         64 my $requirements = $prereqs->merged_requirements([$self->parser->prereqs->phases], [keys %$all_phases]);
85              
86 13         4553 for my $module (sort $requirements->required_modules) {
87 36 100       192 next if $self->_is_perl($module);
88 29         86 my $required_version = $requirements->requirements_for_module($module);
89 29         1308 my $installed_module = $self->_find_installed_module($distributions, $module);
90 29   66     651 my $installed_version = defined $installed_module && $installed_module->version_for($module);
91 29 50       643 next if $self->_is_core_module($module, $installed_version);
92 29 100 66     236 if (defined $installed_module && defined $installed_version && (! defined $required_version || $required_version ne "== $installed_version") && ($installed_version ne 'undef')) {
      66        
      100        
      100        
93 21         403 push @$added_dependencies, App::UpdateCPANfile::Change->new(package_name => $module, version => $installed_version, path => $installed_module->pathname);
94             }
95              
96             }
97              
98 13         45 return $self->_apply_filter($added_dependencies);
99             }
100              
101             sub create_update_dependencies_changeset {
102 11     11 0 3549 my ($self) = @_;
103              
104 11         52 my $prereqs = $self->parser->prereqs;
105              
106 11         8497 my $all_phases = {};
107 11         67 for my $phase ($prereqs->phases) {
108 18         439 for my $type ($prereqs->types_in($phase)) {
109 20         650 $all_phases->{$type}++;
110             }
111             }
112              
113             # If arguments are omitted, it defaults to "runtime", "build" and "test" for phases and "requires" and "recommends" for types.
114 11         42 my $requirements = $prereqs->merged_requirements([$self->parser->prereqs->phases], [keys %$all_phases]);
115              
116 11         4328 my $added_dependencies = [];
117              
118 11         44 for my $module (sort $requirements->required_modules) {
119 33         359 my $required_version = $requirements->requirements_for_module($module);
120 33 100       2205 next if $self->_is_perl($module, $required_version);
121              
122 26         165 my $package_object = $self->package_details->package_object($module);
123 26 50       447 next unless $package_object;
124 26         280 my $latest_version = $package_object->version;
125 26 100       450 next if $self->_is_core_module($module, $latest_version);
126 23 100 66     501 if (defined $latest_version && (! defined $required_version || $required_version ne "== $latest_version") && ($latest_version ne 'undef')) {
      66        
      100        
127 17         123 push @$added_dependencies, App::UpdateCPANfile::Change->new(package_name => $module, version => $latest_version, path => $package_object->path);
128             }
129             }
130 11         75 return $self->_apply_filter($added_dependencies);
131             }
132              
133             sub _find_installed_module {
134 29     29   63 my ($self, $distributions, $module) = @_;;
135 29         57 for my $dist (@$distributions) {
136 144 100       2963 return $dist if $dist->provides_module($module);
137             }
138 4         92 return undef;
139             }
140              
141             sub _is_perl {
142 69     69   201 my ($self, $module, $installed_version) = @_;
143 69         291 return $module eq 'perl';
144             }
145              
146             sub _is_core_module {
147 55     55   262 my ($self, $module, $target_version) = @_;
148 55 50       191 return unless defined $target_version;
149              
150 55         271 my $core_version = Module::CoreList::find_version($])->{$module};
151 55 100       1966 return unless defined $core_version;
152 6         46 return $core_version eq $target_version;
153             }
154              
155             sub _apply_filter {
156 24     24   79 my ($self, $changeset) = @_;
157 24 100       92 if (my $filter = $self->options->{filter}) {
158 1         13 $changeset = [ grep { $_->package_name =~ $filter } @$changeset ];
  2         9  
159             }
160 24 100       86 if (my $ignore_filter = $self->options->{'ignore-filter'}) {
161 1         4 $changeset = [ grep { $_->package_name !~ $ignore_filter } @$changeset ];
  2         8  
162             }
163              
164 24 100       62 if (my $limit = $self->options->{limit}) {
165 1         4 $changeset = [ splice(@$changeset, 0, $limit) ];
166             }
167 24         267 return $changeset;
168             }
169              
170             sub _save_changes_to_file {
171 5     5   20 my ($self, $changeset) = @_;
172 5         20 my $writer = $self->writer;
173              
174 5         263 for my $change (@$changeset) {
175 11         87 for my $prereq (@{$change->prereqs}) {
  11         43  
176 33         330 $writer->add_prereq(@$prereq);
177             }
178             }
179 5         71 $writer->save($self->path);
180             }
181              
182             1;
183             __END__
184              
185             =encoding utf-8
186              
187             =head1 NAME
188              
189             App::UpdateCPANfile - cpanfile updater
190              
191             =head1 SYNOPSIS
192              
193             use App::UpdateCPANfile;
194              
195             =head1 DESCRIPTION
196              
197             App::UpdateCPANfile reads cpanfile, pin dependencies, update dependencies and write back to cpanfile.
198              
199             =head1 SEE ALSO
200              
201             L<update-cpanfile> for command-line usage.
202              
203             =head1 LICENSE
204              
205             Copyright (C) hitode909.
206              
207             This library is free software; you can redistribute it and/or modify
208             it under the same terms as Perl itself.
209              
210             =head1 AUTHOR
211              
212             hitode909 E<lt>hitode909@gmail.comE<gt>
213              
214             =cut
215