File Coverage

blib/lib/Dist/Zilla/Plugin/CheckIssues.pm
Criterion Covered Total %
statement 54 79 68.3
branch 14 32 43.7
condition 7 13 53.8
subroutine 11 14 78.5
pod 0 3 0.0
total 86 141 60.9


line stmt bran cond sub pod time code
1 4     4   1144733 use strict;
  4         29  
  4         100  
2 4     4   18 use warnings;
  4         1177  
  4         1363  
3             package Dist::Zilla::Plugin::CheckIssues; # git description: v0.010-8-gf6e9be7
4             # vim: set ts=8 sts=4 sw=4 tw=115 et :
5             # ABSTRACT: Retrieve count of outstanding RT and github issues for your distribution
6             # KEYWORDS: plugin bugs issues rt github
7              
8             our $VERSION = '0.011';
9              
10 4     4   1862 use Moose;
  4         1265350  
  4         25  
11             with 'Dist::Zilla::Role::BeforeRelease';
12 4     4   26357 use List::Util 1.33 'any';
  4         108  
  4         308  
13 4     4   2797 use Term::ANSIColor 3.00 'colored';
  4         31022  
  4         2981  
14 4     4   2703 use Encode ();
  4         34278  
  4         93  
15 4     4   1737 use namespace::autoclean;
  4         28699  
  4         16  
16              
17             has [qw(rt github colour)] => (
18             is => 'ro', isa => 'Bool',
19             default => 1,
20             );
21              
22             has repo_url => (
23             is => 'rw', isa => 'Str',
24             lazy => 1,
25             default => sub {
26             my $self = shift;
27              
28             my $url;
29             if ($self->zilla->built_in)
30             {
31             # we've already done a build, so distmeta is available
32             my $distmeta = $self->zilla->distmeta;
33             $url = (($distmeta->{resources} || {})->{repository} || {})->{url} || '';
34             }
35             else
36             {
37             # no build (we're probably running the command): we cannot simply
38             # call ->distmeta because various plugins will cause side-effects
39             # with invalid assumptions (no files have been gathered, etc) --
40             # so we just query a short list of plugins that we know can
41             # provide repository resource metadata
42             my @plugins = grep {
43             my $plugin = $_;
44             any { $plugin->isa('Dist::Zilla::Plugin::' . $_) }
45             qw(MetaResources AutoMetaResources GithubMeta GitHub::Meta Repository)
46             } @{ $self->zilla->plugins_with(-MetaProvider) };
47              
48             $self->log('Cannot find any resource metadata-providing plugins to run!')
49             if not @plugins;
50              
51             foreach my $plugin (@plugins)
52             {
53             $self->log_debug([ 'calling metadata for %s', $plugin->plugin_name ]);
54             my $plugin_meta = $plugin->metadata;
55             $url = (($plugin_meta->{resources} || {})->{repository} || {})->{url} || '';
56             last if $url;
57             }
58             }
59             $url;
60             },
61             );
62              
63             # owner_name, repo_name
64             has _github_owner_repo => (
65             isa => 'ArrayRef[Str]',
66             init_arg => undef,
67             lazy => 1,
68             default => sub {
69             my $self = shift;
70              
71             if (my $url = $self->repo_url)
72             {
73             $self->log_debug([ 'getting issue data for %s...', $url ]);
74             my ($owner_name, $repo_name) = $url =~ m{github\.com[:/]([^/]+)/([^/]+?)(?:/|\.git|$)};
75             return [ $owner_name, $repo_name ] if $owner_name and $repo_name;
76             }
77              
78             $self->log('failed to find a github repo in metadata');
79             [];
80             },
81             traits => ['Array'],
82             handles => { _github_owner_repo => 'elements' },
83             );
84              
85 3     3 0 7171741 sub mvp_aliases { +{ color => 'colour' } }
86              
87             # metaconfig is unimportant for this distribution since it does not alter the
88             # built distribution in any way
89             around dump_config => sub
90             {
91             my ($orig, $self) = @_;
92             my $config = $self->$orig;
93              
94             my $data = {
95             blessed($self) ne __PACKAGE__ ? ( version => $VERSION ) : (),
96             };
97             $config->{+__PACKAGE__} = $data if keys %$data;
98              
99             return $config;
100             };
101              
102             sub get_issues
103             {
104 5     5 0 64 my $self = shift;
105              
106 5         302 my $dist_name = $self->zilla->name;
107              
108 5         347 my @issues;
109              
110 5 100       202 if ($self->rt)
111             {
112 4         33 my %rt_data = $self->_rt_data_for_dist($dist_name);
113 4 100 66     41 if (defined $rt_data{open} and defined $rt_data{stalled}) {
114             my $colour = $rt_data{open} ? 'bright_red'
115 3 0       21 : $rt_data{stalled} ? 'yellow'
    50          
116             : 'green';
117              
118             my @text = (
119             'Issues on RT (https://rt.cpan.org/Public/Dist/Display.html?Name=' . $dist_name . '):',
120 3   50     39 ' open: ' . ($rt_data{open} || 0) . ' stalled: ' . ($rt_data{stalled} || 0),
      50        
121             );
122              
123 3 50       114 @text = map colored($_, $colour), @text if $self->colour;
124 3         12 push @issues, @text;
125             }
126             }
127              
128 5 100 100     419 if ($self->github
129             and my ($owner_name, $repo_name) = $self->_github_owner_repo)
130             {
131 3         23 my $issue_count = $self->_github_issue_count($owner_name, $repo_name);
132 3 50       79 if (defined $issue_count)
133             {
134 3 50       11 my $colour = $issue_count ? 'bright_red' : 'green';
135              
136 3         10 my $url = 'https://github.com/'.$owner_name.'/'.$repo_name;
137 3         15 my @text = (
138             'Issues and/or pull requests on github ('.$url.'/issues and '.$url.'/pulls):',
139             ' open: ' . $issue_count,
140             );
141              
142 3 50       148 @text = map colored($_, $colour), @text if $self->colour;
143 3         11 push @issues, @text;
144             }
145             }
146              
147 5         42 return @issues;
148             }
149              
150             sub before_release
151             {
152 3     3 0 872340 my $self = shift;
153              
154 3         133 $self->log($_) foreach $self->get_issues;
155             }
156              
157             sub _rt_data_for_dist
158             {
159 4     4   20 my ($self, $dist_name) = @_;
160              
161 4         53 my $json = $self->_rt_data_raw;
162 4 50       240 return if not $json;
163              
164 4         153 require JSON::MaybeXS; JSON::MaybeXS->VERSION('1.001000');
  4         154  
165 4         562 my $all_data = JSON::MaybeXS->new(utf8 => 0)->decode($json);
166 4 100       319 return if not $all_data->{$dist_name};
167              
168 3         11 my %rt_data;
169             $rt_data{open} = $all_data->{$dist_name}{counts}{active}
170 3         15 - $all_data->{$dist_name}{counts}{stalled};
171 3         9 $rt_data{stalled} = $all_data->{$dist_name}{counts}{stalled};
172 3         19 return %rt_data;
173             }
174              
175             sub _rt_data_raw
176             {
177 0     0     my $self = shift;
178              
179 0           $self->log_debug('fetching RT bug data...');
180 0           my $data = $self->_fetch('https://rt.cpan.org/Public/bugs-per-dist.json');
181 0 0         return if not $data;
182 0           return $data;
183             }
184              
185             sub _github_issue_count
186             {
187 0     0     my ($self, $owner_name, $repo_name) = @_;
188              
189 0           $self->log_debug('fetching github issues data...');
190              
191 0           my $json = $self->_fetch('https://api.github.com/repos/' . $owner_name . '/' . $repo_name);
192 0 0         return if not $json;
193              
194 0           require JSON::MaybeXS; JSON::MaybeXS->VERSION('1.001000');
  0            
195 0           my $data = JSON::MaybeXS->new(utf8 => 0)->decode($json);
196 0           $data->{open_issues_count};
197             }
198              
199             sub _fetch
200             {
201 0     0     my ($self, $url) = @_;
202              
203 0           require HTTP::Tiny;
204 0           my $res = HTTP::Tiny->new->get($url);
205 0 0         if (not $res->{success}) {
206             $self->log('could not fetch from '.$url.': got '
207 0 0 0       .($res->{status} && $res->{content} ? $res->{status}.' '.$res->{content} : 'unknown'));
208 0           return;
209             }
210              
211 0           my $data = $res->{content};
212              
213 0           require HTTP::Headers;
214 0 0         if (my $charset = HTTP::Headers->new(%{ $res->{headers} })->content_type_charset)
  0            
215             {
216 0           $data = Encode::decode($charset, $data, Encode::FB_CROAK);
217             }
218              
219 0           return $data;
220             }
221              
222             __PACKAGE__->meta->make_immutable;
223              
224             __END__
225              
226             =pod
227              
228             =encoding UTF-8
229              
230             =head1 NAME
231              
232             Dist::Zilla::Plugin::CheckIssues - Retrieve count of outstanding RT and github issues for your distribution
233              
234             =head1 VERSION
235              
236             version 0.011
237              
238             =head1 SYNOPSIS
239              
240             In your F<dist.ini>:
241              
242             [CheckIssues]
243             rt = 1 ; default
244             github = 1 ; default
245             colour = 1 ; default
246              
247             [ConfirmRelease]
248              
249             =head1 DESCRIPTION
250              
251             This is a L<Dist::Zilla> plugin that retrieves the RT and/or github issue
252             and pull request counts for your distribution before release. Place it immediately before
253             C<[ConfirmRelease]> in your F<dist.ini> to give you an opportunity to abort the
254             release if you forgot to fix a bug or merge a pull request.
255              
256             =for Pod::Coverage mvp_aliases before_release get_issues
257              
258             =head1 CONFIGURATION OPTIONS
259              
260             =head2 C<rt>
261              
262             Checks your distribution's queue at L<https://rt.cpan.org/>. Defaults to true.
263             (You should leave this enabled even if you have your main issue list on github,
264             as sometimes tickets still end up on RT.)
265              
266             =head2 C<github>
267              
268             Checks the issue list on L<github|https://github.com> for your distribution; does
269             nothing if your distribution is not hosted on L<github|https://github.com>, as
270             listed in your distribution's metadata. Defaults to true.
271              
272             =head2 C<colour> or C<color>
273              
274             Uses L<Term::ANSIColor> to colour-code the results according to severity.
275             Defaults to true.
276              
277             =head2 C<repo_url>
278              
279             The URL of the github repository. This is fetched from the C<resources> field
280             in metadata, so it should not normally be specified manually.
281              
282             =head1 FUTURE FEATURES, MAYBE
283              
284             If I can find the right APIs to call, it would be nice to have a C<verbose>
285             option which fetches the actual titles of the open issues. Advice or patches welcome!
286              
287             Possibly other issue trackers? Does anyone even use any other issue trackers
288             anymore? :)
289              
290             =head1 ACKNOWLEDGEMENTS
291              
292             =for stopwords Ricardo Signes codereview
293              
294             Some code was liberally stolen from Ricardo Signes's
295             L<codereview tool|https://github.com/rjbs/misc/blob/master/code-review>.
296              
297             =head1 SEE ALSO
298              
299             =over 4
300              
301             =item *
302              
303             L<Dist::Zilla::Plugin::MetaResources> - manually add resource information (such as git repository) to metadata
304              
305             =item *
306              
307             L<Dist::Zilla::Plugin::GithubMeta> - automatically detect and add github repository information to metadata
308              
309             =item *
310              
311             L<Dist::Zilla::Plugin::AutoMetaResources> - configuration-based resource metadata provider
312              
313             =back
314              
315             =head1 SUPPORT
316              
317             Bugs may be submitted through L<the RT bug tracker|https://rt.cpan.org/Public/Dist/Display.html?Name=Dist-Zilla-Plugin-CheckIssues>
318             (or L<bug-Dist-Zilla-Plugin-CheckIssues@rt.cpan.org|mailto:bug-Dist-Zilla-Plugin-CheckIssues@rt.cpan.org>).
319              
320             There is also a mailing list available for users of this distribution, at
321             L<http://dzil.org/#mailing-list>.
322              
323             There is also an irc channel available for users of this distribution, at
324             L<C<#distzilla> on C<irc.perl.org>|irc://irc.perl.org/#distzilla>.
325              
326             I am also usually active on irc, as 'ether' at C<irc.perl.org>.
327              
328             =head1 AUTHOR
329              
330             Karen Etheridge <ether@cpan.org>
331              
332             =head1 COPYRIGHT AND LICENCE
333              
334             This software is copyright (c) 2014 by Karen Etheridge.
335              
336             This is free software; you can redistribute it and/or modify it under
337             the same terms as the Perl 5 programming language system itself.
338              
339             =cut