File Coverage

blib/lib/App/CpanfileSlipstop/Writer.pm
Criterion Covered Total %
statement 45 70 64.2
branch 7 16 43.7
condition 5 12 41.6
subroutine 11 14 78.5
pod 0 9 0.0
total 68 121 56.2


line stmt bran cond sub pod time code
1             package App::CpanfileSlipstop::Writer;
2 2     2   378 use strict;
  2         4  
  2         50  
3 2     2   9 use warnings;
  2         2  
  2         48  
4              
5 2     2   8 use List::Util qw(first);
  2         5  
  2         164  
6 2     2   833 use PPI::Document;
  2         194712  
  2         64  
7 2     2   748 use PPI::Find;
  2         1570  
  2         1445  
8              
9             sub new {
10 12     12 0 2602 my ($class, %opts) = @_;
11              
12             bless +{
13             cpanfile_path => $opts{cpanfile_path},
14             dry_run => $opts{dry_run},
15 12         63 }, $class;
16             }
17              
18 12     12 0 128 sub cpanfile_path { $_[0]->{cpanfile_path} }
19 0 0   0 0 0 sub dry_run { $_[0]->{dry_run} ? 1 : 0 }
20              
21             my $statement_finder = sub {
22             my (undef, $elem) = @_;
23              
24             return $elem->isa('PPI::Statement') && $elem->schild(0)->content eq 'requires';
25             };
26              
27             sub set_versions {
28 12     12 0 111 my ($self, $version_getter, $logger) = @_;
29              
30 12         46 my $doc = PPI::Document->new($self->cpanfile_path);
31 12         39296 my $requirements = $doc->find($statement_finder);
32              
33 12         127 for my $statement (@$requirements) {
34 44         206 my ($type, $module, @args) = $statement->schildren;
35              
36 44         528 my $version_range = $version_getter->($module->string);
37 44 100       84 next unless $version_range;
38              
39             my @words = grep {
40 40   100     64 !($_->isa('PPI::Token::Operator') || $_->content eq ';');
  58         233  
41             } @args;
42              
43 40 100       216 if (@words % 2 == 0) {
44             # insert VERSION
45             # - requires MODULE;
46             # - requries MODULE, KEY => VALUE;
47 31         78 $self->insert_version($module, $version_range);
48              
49 31         1018 $logger->({
50             type => 'insert',
51             module => $module->content,
52             before => undef,
53             after => $version_range,
54             quote => quote($module),
55             });
56             } else {
57             # replace VERSION
58             # - requries MODULE, VERSION;
59             # - requries MODULE, VERSION, KEY => VALUE;
60 9         15 my $current_version = $words[0];
61 9         21 $self->replace_version($module, $current_version, $version_range);
62 9         246 $logger->({
63             type => 'replace',
64             module => $module->content,
65             before => $current_version->content,
66             after => $version_range,
67             quote => quote($module),
68             });
69             }
70             }
71              
72 12         61 $self->writedown_cpanfile($doc);
73             }
74              
75             sub remove_versions {
76 0     0 0 0 my ($self, $logger) = @_;
77              
78 0         0 my $doc = PPI::Document->new($self->cpanfile_path);
79 0         0 my $requirements = $doc->find($statement_finder);
80              
81 0         0 for my $statement (@$requirements) {
82 0         0 my ($type, $module, @args) = $statement->schildren;
83              
84             my @words = grep {
85 0   0     0 !($_->isa('PPI::Token::Operator') || $_->content eq ';');
  0         0  
86             } @args;
87              
88 0 0       0 if (@words %2 == 1) {
89 0         0 my ($op, $version) = @args;
90              
91             # collect whitespaces between MODULE and VERSION
92 0         0 my $whitespaces = [];
93 0         0 my $token = $op->next_sibling;
94 0   0     0 while ($token && $token->isa('PPI::Token::Whitespace')) {
95 0         0 push @$whitespaces, $token;
96 0         0 $token = $token->next_sibling;
97             }
98              
99 0         0 $op->remove;
100 0         0 $_->remove for @$whitespaces;
101 0         0 $version->remove;
102              
103 0         0 $logger->({
104             type => 'delete',
105             module => $module->string,
106             before => $version->string,
107             after => undef,
108             quote => quote($module),
109             });
110             }
111             }
112              
113 0         0 $self->writedown_cpanfile($doc);
114             }
115              
116             sub writedown_cpanfile {
117 0     0 0 0 my ($self, $ppi_doc) = @_;
118              
119 0 0       0 return if $self->dry_run;
120              
121 0 0       0 open my $out, ">", $self->cpanfile_path
122             or die sprintf('%s, %s', $self->cpanfile_path, $!);
123 0         0 print $out $ppi_doc->serialize;
124 0         0 close $out;
125             }
126              
127             sub insert_version {
128 31     31 0 60 my ($self, $module_elem, $version_range) = @_;
129              
130 31         50 my $quote = quote($module_elem);
131 31         102 $module_elem->__insert_after(PPI::Token->new(qq{, $quote$version_range$quote}));
132             }
133              
134             sub replace_version {
135 9     9 0 18 my ($self, $module_elem, $version_elem, $version_range) = @_;
136              
137 9         15 my $quote = quote($module_elem);
138              
139             # The giving version on cpanfile must be a string or number for preventing to replace expressions.
140 9 50 66     33 return if !($version_elem->isa('PPI::Token::Quote') || $version_elem->isa('PPI::Token::Number'));
141              
142 9         33 my $prev_token = $version_elem->previous_sibling;
143 9         198 $version_elem->remove;
144 9         291 $prev_token->__insert_after(PPI::Token->new(qq{$quote$version_range$quote}));
145             }
146              
147             sub quote {
148 80     80 0 203 my ($elem) = @_;
149              
150 80 100       351 return $elem->isa('PPI::Token::Quote::Double') ? '"' : "'";
151             }
152              
153             1;