File Coverage

blib/lib/App/PAUSE/cleanup.pm
Criterion Covered Total %
statement 49 140 35.0
branch 8 36 22.2
condition 0 6 0.0
subroutine 10 17 58.8
pod 0 4 0.0
total 67 203 33.0


line stmt bran cond sub pod time code
1             package App::PAUSE::cleanup;
2             our $AUTHORITY = 'cpan:YANICK';
3             # ABSTRACT: Manage (delete/undelete) your PAUSE files
4             $App::PAUSE::cleanup::VERSION = '0.0013';
5 1     1   117746 use strict;
  1         13  
  1         28  
6 1     1   6 use warnings;
  1         2  
  1         54  
7              
8 1     1   444 use Getopt::Usaginator <<_END_;
  1         21749  
  1         6  
9              
10             Usage: pause-cleanup
11              
12             --username Your PAUSE username
13             --password The password for the above
14             Instead of supplying your identity on the
15             commandline, you can setup \$HOME/.pause like so:
16              
17             user
18             password
19              
20             -d, --dump Dump the list of files to STDOUT
21            
22             -h, -?, --help This help
23              
24             _END_
25 1     1   930 use Getopt::Long qw/ GetOptions /;
  1         10729  
  1         3  
26              
27              
28 1     1   576 use Term::EditorEdit;
  1         62917  
  1         37  
29 1     1   407 use Config::Identity::PAUSE;
  1         40559  
  1         35  
30 1     1   745 use WWW::Mechanize;
  1         137784  
  1         42  
31 1     1   556 use Web::Query 'wq';
  1         67419  
  1         1501  
32              
33              
34             my $agent = WWW::Mechanize->new;
35              
36             sub run {
37 0     0 0 0 my $self = shift;
38 0         0 my @arguments = @_;
39              
40 0         0 my ( $help, $username, $password, $dump );
41             {
42 0         0 local @ARGV = @arguments;
  0         0  
43 0         0 GetOptions(
44             'username=s' => \$username,
45             'password=s' => \$password,
46             'dump|d' => \$dump,
47             'help|h|?' => \$help,
48             );
49             }
50              
51 0 0       0 usage 0 if $help;
52              
53 0         0 my %identity = Config::Identity::PAUSE->load;
54 0 0       0 $username = $identity{user} unless defined $username;
55 0 0       0 $password = $identity{password} unless defined $password;
56              
57 0 0 0     0 usage '! Missing username and/or password' unless
58             defined $username && defined $password;
59              
60 0         0 $agent->credentials( "pause.perl.org:443", "PAUSE", $username, $password );
61              
62 0         0 print "> Logging in as $username\n";
63            
64 0         0 my $response = $agent->get( 'https://pause.perl.org/pause/authenquery?ACTION=delete_files' );
65              
66 0         0 my @filelist = $self->parse_filelist( $response->decoded_content );
67              
68 0 0       0 if ( $dump ) {
69 0         0 print join "\n", map { $_->{package_version} } @filelist;
  0         0  
70 0         0 print "\n";
71 0         0 return;
72             }
73              
74 0         0 my %package;
75 0         0 for my $file (@filelist) {
76 0         0 push @{ $package{$file->{package}} }, $file;
  0         0  
77             }
78              
79 0         0 my @document;
80 0         0 push @document, <<_END_;
81             # Logged in as $username
82             #
83             # Any line not beginning with 'delete', 'undelete', or 'keep' is ignored
84             # To take action on a release, remove the leading '#'
85             #
86             # delete Delete the .meta, .readme, and .tar.gz associated
87             # with the release
88             #
89             # undelete Undelete the .meta, .readme, and .tar.gz (remove
90             # from scheduled deletion
91             #
92             # keep Ignore the release
93             #
94             # By default, the latest version of each release is commented 'keep'
95             # Older versions are commented 'delete' (or 'undelete')
96             _END_
97              
98 0         0 for my $name (sort keys %package) {
99 0         0 my @filelist = @{ $package{$name} };
  0         0  
100 0         0 @filelist = sort { $a->{scheduled} cmp $b->{scheduled} or
101 0 0       0 $b->{tar_gz} cmp $a->{tar_gz} } @filelist;
102              
103 0         0 push @document, "$name:";
104              
105 0         0 my @latest = $self->extract_latest( \@filelist );
106              
107 0         0 for my $latest ( @latest ) {
108 0 0       0 if ( $latest->{scheduled} )
109 0         0 { push @document, "# undelete $latest->{package_version}" }
110 0         0 else { push @document, "# keep $latest->{package_version}" }
111             }
112              
113             push @document,
114             ( map {
115 0 0       0 my $operation = $_->{scheduled} ? "undelete" : "delete";
  0         0  
116 0         0 "# $operation $_->{package_version}"
117             } @filelist ),
118             '',
119             ;
120             }
121              
122 0         0 my $document = join "\n", @document;
123            
124             my $delete_undelete = Term::EditorEdit->edit( document => $document, process => sub {
125 0     0   0 my $edit = shift;
126 0         0 my ( @delete, @undelete );
127 0         0 my @content = split m/\n/, $edit->content;
128 0         0 for my $line ( @content ) {
129 0 0       0 next unless $line =~ m/^\s*(delete|undelete)\s*(\S+)/i;
130 0 0       0 if ( lc $1 eq 'delete' ) { push @delete, $2 }
  0         0  
131 0         0 else { push @undelete, $2 }
132             }
133 0         0 return { delete => \@delete, undelete => \@undelete };
134 0         0 } );
135              
136 0         0 my ( $delete, $undelete ) = @$delete_undelete{qw/ delete undelete /};
137              
138 0 0       0 if ( @$delete ) {
139 0         0 print "\n---\n";
140 0         0 print join "\n", '', ( map { " $_" } @$delete ), '', '';
  0         0  
141 0         0 print "> Really delete? If you wish to abort, hit ^C (CTRL-C) now!\n";
142 0         0 print "> Hit return to continue, or cancel with ^C\n";
143 0         0 my $nil = ;
144 0         0 my $count = scalar @$delete;
145 0         0 print "> Deleting $count\n";
146 0         0 $self->_delete( $delete );
147             }
148            
149 0 0       0 if ( @$undelete ) {
150 0         0 print "\n---\n";
151 0         0 print join "\n", '', ( map { " $_" } @$undelete ), '', '';
  0         0  
152 0         0 my $count = scalar @$undelete;
153 0         0 print "> Undeleting $count\n";
154 0         0 $self->_undelete( $undelete );
155             }
156              
157 0 0 0     0 unless ( @$delete || @$undelete ) {
158 0         0 print "> Nothing to do\n";
159             }
160             }
161              
162             sub parse_filelist {
163 0     0 0 0 my( $self, $document ) = @_;
164              
165 0         0 @{ wq($document)->find('input[name="pause99_delete_files_FILE"]')->map(sub{
166 0     0   0 my $tr = $_->parent->parent;
167 0         0 my $file = $tr->find('.file')->text;
168              
169 0         0 my $package = $file;
170 0 0       0 $package =~ s/-([\d\._]+)\.tar\.gz$//
171             or return ();
172 0         0 my $version = $1;
173              
174             return {
175 0         0 tar_gz => $file,
176             package => $package,
177             package_version => join( '-', $package, $version ),
178             version => $version,
179             size => $tr->find('.size')->text,
180             scheduled => !!($tr->find('.modified') =~ /Scheduled for deletion/),
181             };
182 0         0 }) };
183              
184              
185             }
186              
187             sub _delete {
188 0     0   0 my $self = shift;
189 0         0 $self->_submit( 'SUBMIT_pause99_delete_files_delete', @_ );
190             }
191              
192             sub _undelete {
193 0     0   0 my $self = shift;
194 0         0 $self->_submit( 'SUBMIT_pause99_delete_files_undelete', @_ );
195             }
196              
197             sub extract_latest {
198 5     5 0 10227 my $self = shift;
199 5         8 my $filelist = shift;
200              
201 5         13 my @latest;
202             my @filelist;
203 5         0 my $found;
204              
205 5         11 for my $file ( @$filelist ) {
206 13 100       41 if ( $file->{version} =~ m/_/ ) {
    100          
207 5 100       12 if ( ! @latest ) { push @latest, $file }
  3         6  
208 2         4 else { push @filelist, $file }
209             }
210             elsif ( ! $found ) {
211 5         7 $found = 1;
212 5         9 push @latest, $file;
213             }
214             else {
215 3         5 push @filelist, $file;
216             }
217             }
218              
219 5         10 @$filelist = @filelist;
220 5         15 return @latest;
221             }
222              
223             sub expand_filelist {
224 1     1 0 91 my $self = shift;
225 1         2 my $filelist = shift; # Actually, package_version
226              
227 1         3 my @filelist;
228 1         3 for my $package_version (@$filelist) {
229 5         8 my $pv = $package_version;
230 5         29 my ( $version ) = $pv =~ m/-([\d\._]+)$/;
231 5 100       15 if ( $version =~ m/_/ )
232 2         12 { push @filelist, "$pv.tar.gz" }
233 3         8 else { push @filelist, map { ( "$_.meta", "$_.readme", "$_.tar.gz" ) } $pv }
  3         16  
234             }
235              
236 1         13 return @filelist;
237             }
238              
239             sub _submit {
240 0     0     my $self = shift;
241 0           my $button = shift;
242 0           my $filelist = shift; # Actually, package_version
243            
244 0           my @filelist = $self->expand_filelist( $filelist );
245 0           $agent->get( 'https://pause.perl.org/pause/authenquery?ACTION=delete_files' );
246 0           $agent->tick( 'pause99_delete_files_FILE' => $_ ) for @filelist;
247 0           $agent->click( $button );
248             }
249              
250             1;
251              
252             __END__