File Coverage

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


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