File Coverage

blib/lib/App/UpdateCPANfile.pm
Criterion Covered Total %
statement 120 120 100.0
branch 25 28 89.2
condition 31 39 79.4
subroutine 26 26 100.0
pod 0 11 0.0
total 202 224 90.1


line stmt bran cond sub pod time code
1             package App::UpdateCPANfile;
2 1     1   263262 use 5.010001;
  1         4  
3 1     1   6 use strict;
  1         3  
  1         110  
4 1     1   18 use warnings;
  1         3  
  1         37  
5 1     1   623 use Module::CPANfile;
  1         19395  
  1         46  
6 1     1   616 use Module::CPANfile::Writer;
  1         140295  
  1         47  
7 1     1   696 use App::UpdateCPANfile::CPANfileSnapshotParser;
  1         6  
  1         50  
8 1     1   667 use App::UpdateCPANfile::PackageDetails;
  1         5  
  1         45  
9 1     1   673 use App::UpdateCPANfile::Change;
  1         5  
  1         42  
10 1     1   12 use Module::CoreList;
  1         3  
  1         13  
11 1     1   37 use List::Util qw(shuffle);
  1         3  
  1         1435  
12              
13             our $VERSION = "1.1.1";
14              
15             sub new {
16 27     27 0 93140 my ($class, $path, $snapshot_path, $options) = @_;
17 27         154 bless {
18             path => $path,
19             snapshot_path => $snapshot_path,
20             options => $options,
21             }, $class;
22             }
23              
24             sub path {
25 37   100 37 0 1428 $_[0]->{path} // 'cpanfile';
26             }
27              
28             sub snapshot_path {
29 19   100 19 0 182 $_[0]->{snapshot_path} // 'cpanfile.snapshot';
30             }
31              
32             sub options {
33 110   100 110 0 535 $_[0]->{options} // {};
34             }
35              
36             sub parser {
37 55     55 0 134 my ($self) = @_;
38              
39 55   66     346 $self->{parser} //= Module::CPANfile->load($self->path);
40             }
41              
42             sub writer {
43 9     9 0 544 my ($self) = @_;
44              
45 9   66     66 $self->{writer} //= Module::CPANfile::Writer->new($self->path);
46             }
47              
48             sub package_details {
49 27     27 0 118 my ($self) = @_;
50              
51 27   66     311 $self->{package_details} //= App::UpdateCPANfile::PackageDetails->new;
52             }
53              
54             sub pin_dependencies {
55 2     2 0 14 my ($self) = @_;
56 2         9 my $changeset = $self->create_pin_dependencies_changeset;
57 2         249 $self->_save_changes_to_file($changeset);
58 2         618222 return $changeset;
59             }
60              
61             sub update_dependencies {
62 3     3 0 429 my ($self) = @_;
63 3         15 my $changeset = $self->create_update_dependencies_changeset;
64 3         18 my $writer = $self->writer;
65 3         591 $self->_save_changes_to_file($changeset);
66 3         999857 return $changeset;
67             }
68              
69             sub create_pin_dependencies_changeset {
70 16     16 0 435 my ($self) = @_;
71              
72 16         48 my $distributions = App::UpdateCPANfile::CPANfileSnapshotParser->scan_deps($self->snapshot_path);
73              
74 16         687 my $prereqs = $self->parser->prereqs;
75 16         19366 my $added_dependencies = [];
76              
77 16         44 my $all_phases = {};
78 16         59 for my $phase ($prereqs->phases) {
79 26         498 for my $type ($prereqs->types_in($phase)) {
80 30         894 $all_phases->{$type}++;
81             }
82             }
83              
84             # If arguments are omitted, it defaults to "runtime", "build" and "test" for phases and "requires" and "recommends" for types.
85 16         62 my $requirements = $prereqs->merged_requirements([$self->parser->prereqs->phases], [keys %$all_phases]);
86              
87 16         5539 for my $module (sort $requirements->required_modules) {
88 45 100       254 next if $self->_is_perl($module);
89 36         117 my $required_version = $requirements->requirements_for_module($module);
90 36         1662 my $installed_module = $self->_find_installed_module($distributions, $module);
91 36   66     810 my $installed_version = defined $installed_module && $installed_module->version_for($module);
92 36 50       774 next if $self->_is_core_module($module, $installed_version);
93 36 100 66     295 if (defined $installed_module && defined $installed_version && (! defined $required_version || $required_version ne "== $installed_version") && ($installed_version ne 'undef')) {
      66        
      100        
      100        
94 28         502 push @$added_dependencies, App::UpdateCPANfile::Change->new(package_name => $module, version => $installed_version, path => $installed_module->pathname);
95             }
96              
97             }
98              
99 16         67 return $self->_apply_filter($added_dependencies);
100             }
101              
102             sub create_update_dependencies_changeset {
103 11     11 0 3330 my ($self) = @_;
104              
105 11         52 my $prereqs = $self->parser->prereqs;
106              
107 11         8913 my $all_phases = {};
108 11         74 for my $phase ($prereqs->phases) {
109 18         493 for my $type ($prereqs->types_in($phase)) {
110 20         664 $all_phases->{$type}++;
111             }
112             }
113              
114             # If arguments are omitted, it defaults to "runtime", "build" and "test" for phases and "requires" and "recommends" for types.
115 11         58 my $requirements = $prereqs->merged_requirements([$self->parser->prereqs->phases], [keys %$all_phases]);
116              
117 11         4302 my $added_dependencies = [];
118              
119 11         43 for my $module (sort $requirements->required_modules) {
120 33         400 my $required_version = $requirements->requirements_for_module($module);
121 33 100       2166 next if $self->_is_perl($module, $required_version);
122              
123 26         121 my $package_object = $self->package_details->package_object($module);
124 26 50       467 next unless $package_object;
125 26         263 my $latest_version = $package_object->version;
126 26 100       408 next if $self->_is_core_module($module, $latest_version);
127 23 100 66     538 if (defined $latest_version && (! defined $required_version || $required_version ne "== $latest_version") && ($latest_version ne 'undef')) {
      66        
      100        
128 17         128 push @$added_dependencies, App::UpdateCPANfile::Change->new(package_name => $module, version => $latest_version, path => $package_object->path);
129             }
130             }
131 11         65 return $self->_apply_filter($added_dependencies);
132             }
133              
134             sub _find_installed_module {
135 36     36   76 my ($self, $distributions, $module) = @_;;
136 36         74 for my $dist (@$distributions) {
137 177 100       3519 return $dist if $dist->provides_module($module);
138             }
139 4         89 return undef;
140             }
141              
142             sub _is_perl {
143 78     78   220 my ($self, $module, $installed_version) = @_;
144 78         330 return $module eq 'perl';
145             }
146              
147             sub _is_core_module {
148 62     62   236 my ($self, $module, $target_version) = @_;
149 62 50       186 return unless defined $target_version;
150              
151 62         308 my $core_version = Module::CoreList::find_version($])->{$module};
152 62 100       1966 return unless defined $core_version;
153 8         49 return $core_version eq $target_version;
154             }
155              
156             sub _apply_filter {
157 27     27   91 my ($self, $changeset) = @_;
158 27 100       99 if (my $filter = $self->options->{filter}) {
159 1         12 $changeset = [ grep { $_->package_name =~ $filter } @$changeset ];
  2         8  
160             }
161 27 100       83 if (my $ignore_filter = $self->options->{'ignore-filter'}) {
162 1         8 $changeset = [ grep { $_->package_name !~ $ignore_filter } @$changeset ];
  2         8  
163             }
164              
165 27 100       77 if ($self->options->{shuffle}) {
166 2         22 $changeset = [ shuffle(@$changeset) ];
167             }
168              
169 27 100       67 if (my $limit = $self->options->{limit}) {
170 4         11 my $limited_changeset = [ splice(@$changeset, 0, $limit) ];
171 4         10 my $is_changed_path = { map { $_->path => 1 } @$limited_changeset };
  4         14  
172 4         9 my $associated_changeset = [ grep { $is_changed_path->{ $_->path } } @$changeset ];
  5         11  
173 4         17 $changeset = [ @$limited_changeset, @$associated_changeset ];
174             }
175 27         326 return $changeset;
176             }
177              
178             sub _save_changes_to_file {
179 5     5   20 my ($self, $changeset) = @_;
180 5         19 my $writer = $self->writer;
181              
182 5         262 for my $change (@$changeset) {
183 11         87 for my $prereq (@{$change->prereqs}) {
  11         42  
184 33         339 $writer->add_prereq(@$prereq);
185             }
186             }
187 5         69 $writer->save($self->path);
188             }
189              
190             1;
191             __END__
192              
193             =encoding utf-8
194              
195             =head1 NAME
196              
197             App::UpdateCPANfile - cpanfile updater
198              
199             =head1 SYNOPSIS
200              
201             use App::UpdateCPANfile;
202              
203             =head1 DESCRIPTION
204              
205             App::UpdateCPANfile reads cpanfile, pin dependencies, update dependencies and write back to cpanfile.
206              
207             =head1 SEE ALSO
208              
209             L<update-cpanfile> for command-line usage.
210              
211             =head1 LICENSE
212              
213             Copyright (C) hitode909.
214              
215             This library is free software; you can redistribute it and/or modify
216             it under the same terms as Perl itself.
217              
218             =head1 AUTHOR
219              
220             hitode909 E<lt>hitode909@gmail.comE<gt>
221              
222             =cut
223