File Coverage

blib/lib/Dist/Zilla/Plugin/CheckIssues.pm
Criterion Covered Total %
statement 52 77 67.5
branch 14 28 50.0
condition 7 7 100.0
subroutine 11 14 78.5
pod 0 3 0.0
total 84 129 65.1


line stmt bran cond sub pod time code
1 4     4   512692 use strict;
  4         5  
  4         93  
2 4     4   11 use warnings;
  4         5  
  4         166  
3             package Dist::Zilla::Plugin::CheckIssues; # git description: v0.009-10-g49cc7be
4             # ABSTRACT: Retrieve count of outstanding RT and github issues for your distribution
5             # KEYWORDS: plugin bugs issues rt github
6             # vim: set ts=8 sts=4 sw=4 tw=115 et :
7              
8             our $VERSION = '0.010';
9              
10 4     4   1665 use Moose;
  4         805682  
  4         20  
11             with 'Dist::Zilla::Role::BeforeRelease';
12 4     4   17758 use List::Util 1.33 'any';
  4         96  
  4         266  
13 4     4   2804 use Term::ANSIColor 3.00 'colored';
  4         19161  
  4         1162  
14 4     4   1781 use Encode ();
  4         24378  
  4         78  
15 4     4   1375 use namespace::autoclean;
  4         19545  
  4         17  
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 4298031 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 11 my $self = shift;
105              
106 5         147 my $dist_name = $self->zilla->name;
107              
108 5         148 my @issues;
109              
110 5 100       149 if ($self->rt)
111             {
112 4         15 my %rt_data = $self->_rt_data_for_dist($dist_name);
113              
114             my $colour = $rt_data{open} ? 'bright_red'
115 4 50       53 : $rt_data{stalled} ? 'yellow'
    100          
116             : 'green';
117              
118             my @text = (
119             'Issues on RT (https://rt.cpan.org/Public/Dist/Display.html?Name=' . $dist_name . '):',
120 4   100     40 ' open: ' . ($rt_data{open} || 0) . ' stalled: ' . ($rt_data{stalled} || 0),
      100        
121             );
122              
123 4 50       134 @text = map { colored($_, $colour) } @text if $self->colour;
  0         0  
124 4         10 push @issues, @text;
125             }
126              
127 5 100 100     151 if ($self->github
128             and my ($owner_name, $repo_name) = $self->_github_owner_repo)
129             {
130 3         17 my $issue_count = $self->_github_issue_count($owner_name, $repo_name);
131 3 50       54 if (defined $issue_count)
132             {
133 3 50       11 my $colour = $issue_count ? 'bright_red' : 'green';
134              
135 3         17 my @text = (
136             'Issues on github (https://github.com/' . $owner_name . '/' . $repo_name . '):',
137             ' open: ' . $issue_count,
138             );
139              
140 3 50       108 @text = map { colored($_, $colour) } @text if $self->colour;
  0         0  
141 3         7 push @issues, @text;
142             }
143             }
144              
145 5         30 return @issues;
146             }
147              
148             sub before_release
149             {
150 3     3 0 524857 my $self = shift;
151              
152 3         23 $self->log($_) foreach $self->get_issues;
153             }
154              
155             sub _rt_data_for_dist
156             {
157 4     4   7 my ($self, $dist_name) = @_;
158              
159 4         35 my $json = $self->_rt_data_raw;
160 4 50       95 return if not $json;
161              
162 4         29 require JSON::MaybeXS; JSON::MaybeXS->VERSION('1.001000');
  4         90  
163 4         31 my $all_data = JSON::MaybeXS->new(utf8 => 0)->decode($json);
164 4 100       192 return if not $all_data->{$dist_name};
165              
166 3         35 my %rt_data;
167             $rt_data{open} = $all_data->{$dist_name}{counts}{active}
168 3         12 - $all_data->{$dist_name}{counts}{stalled};
169 3         5 $rt_data{stalled} = $all_data->{$dist_name}{counts}{stalled};
170 3         18 return %rt_data;
171             }
172              
173             sub _rt_data_raw
174             {
175 0     0     my $self = shift;
176              
177 0           $self->log_debug('fetching RT bug data...');
178 0           my $data = $self->_fetch('https://rt.cpan.org/Public/bugs-per-dist.json');
179 0 0         $self->log('could not fetch RT data?'), return if not $data;
180 0           return $data;
181             }
182              
183             sub _github_issue_count
184             {
185 0     0     my ($self, $owner_name, $repo_name) = @_;
186              
187 0           $self->log_debug('fetching github issues data...');
188              
189 0           my $json = $self->_fetch('https://api.github.com/repos/' . $owner_name . '/' . $repo_name);
190 0 0         $self->log('could not fetch github data?'), return if not $json;
191              
192 0           require JSON::MaybeXS; JSON::MaybeXS->VERSION('1.001000');
  0            
193 0           my $data = JSON::MaybeXS->new(utf8 => 0)->decode($json);
194 0           $data->{open_issues_count};
195             }
196              
197             sub _fetch
198             {
199 0     0     my ($self, $url) = @_;
200              
201 0           require HTTP::Tiny;
202 0           my $res = HTTP::Tiny->new->get($url);
203 0 0         return if not $res->{success};
204              
205 0           my $data = $res->{content};
206              
207 0           require HTTP::Headers;
208 0 0         if (my $charset = HTTP::Headers->new(%{ $res->{headers} })->content_type_charset)
  0            
209             {
210 0           $data = Encode::decode($charset, $data, Encode::FB_CROAK);
211             }
212              
213 0           return $data;
214             }
215              
216             __PACKAGE__->meta->make_immutable;
217              
218             __END__
219              
220             =pod
221              
222             =encoding UTF-8
223              
224             =head1 NAME
225              
226             Dist::Zilla::Plugin::CheckIssues - Retrieve count of outstanding RT and github issues for your distribution
227              
228             =head1 VERSION
229              
230             version 0.010
231              
232             =head1 SYNOPSIS
233              
234             In your F<dist.ini>:
235              
236             [CheckIssues]
237             rt = 1 ; default
238             github = 1 ; default
239             colour = 1 ; default
240              
241             [ConfirmRelease]
242              
243             =head1 DESCRIPTION
244              
245             This is a L<Dist::Zilla> plugin that retrieves the RT and/or github issue
246             counts for your distribution before release. Place it immediately before
247             C<[ConfirmRelease]> in your F<dist.ini> to give you an opportunity to abort the
248             release if you forgot to fix a bug or merge a pull request.
249              
250             =for Pod::Coverage mvp_aliases before_release get_issues
251              
252             =head1 CONFIGURATION OPTIONS
253              
254             =head2 C<rt>
255              
256             Checks your distribution's queue at L<https://rt.cpan.org/>. Defaults to true.
257             (You should leave this enabled even if you have your main issue list on github,
258             as sometimes tickets still end up on RT.)
259              
260             =head2 C<github>
261              
262             Checks the issue list on L<github|https://github.com> for your distribution; does
263             nothing if your distribution is not hosted on L<github|https://github.com>, as
264             listed in your distribution's metadata. Defaults to true.
265              
266             =head2 C<colour> or C<color>
267              
268             Uses L<Term::ANSIColor> to colour-code the results according to severity.
269             Defaults to true.
270              
271             =head2 C<repo_url>
272              
273             The URL of the github repository. This is fetched from the C<resources> field
274             in metadata, so it should not normally be specified manually.
275              
276             =head1 FUTURE FEATURES, MAYBE
277              
278             If I can find the right APIs to call, it would be nice to have a C<verbose>
279             option which fetches the actual titles of the open issues. Advice or patches welcome!
280              
281             Possibly other issue trackers? Does anyone even use any other issue trackers
282             anymore? :)
283              
284             =head1 ACKNOWLEDGEMENTS
285              
286             =for stopwords Ricardo Signes codereview
287              
288             Some code was liberally stolen from Ricardo Signes's
289             L<codereview tool|https://github.com/rjbs/misc/blob/master/code-review>.
290              
291             =head1 SEE ALSO
292              
293             =over 4
294              
295             =item *
296              
297             L<Dist::Zilla::Plugin::MetaResources> - manually add resource information (such as git repository) to metadata
298              
299             =item *
300              
301             L<Dist::Zilla::Plugin::GithubMeta> - automatically detect and add github repository information to metadata
302              
303             =item *
304              
305             L<Dist::Zilla::Plugin::AutoMetaResources> - configuration-based resource metadata provider
306              
307             =back
308              
309             =head1 SUPPORT
310              
311             Bugs may be submitted through L<the RT bug tracker|https://rt.cpan.org/Public/Dist/Display.html?Name=Dist-Zilla-Plugin-CheckIssues>
312             (or L<bug-Dist-Zilla-Plugin-CheckIssues@rt.cpan.org|mailto:bug-Dist-Zilla-Plugin-CheckIssues@rt.cpan.org>).
313              
314             There is also a mailing list available for users of this distribution, at
315             L<http://dzil.org/#mailing-list>.
316              
317             There is also an irc channel available for users of this distribution, at
318             L<C<#distzilla> on C<irc.perl.org>|irc://irc.perl.org/#distzilla>.
319              
320             I am also usually active on irc, as 'ether' at C<irc.perl.org>.
321              
322             =head1 AUTHOR
323              
324             Karen Etheridge <ether@cpan.org>
325              
326             =head1 COPYRIGHT AND LICENCE
327              
328             This software is copyright (c) 2014 by Karen Etheridge.
329              
330             This is free software; you can redistribute it and/or modify it under
331             the same terms as the Perl 5 programming language system itself.
332              
333             =cut