File Coverage

blib/lib/RT/ClientX/GrabBugs.pm
Criterion Covered Total %
statement 52 110 47.2
branch 0 8 0.0
condition 0 3 0.0
subroutine 18 27 66.6
pod 2 2 100.0
total 72 150 48.0


line stmt bran cond sub pod time code
1 1     1   80702 use 5.010;
  1         4  
2 1     1   5 use strict;
  1         2  
  1         18  
3 1     1   4 use warnings;
  1         2  
  1         23  
4 1     1   551 use utf8;
  1         12  
  1         4  
5              
6             package RT::ClientX::GrabBugs;
7              
8             BEGIN {
9 1     1   46 $RT::ClientX::GrabBugs::AUTHORITY = 'cpan:TOBYINK';
10 1         16 $RT::ClientX::GrabBugs::VERSION = '0.005';
11             }
12              
13 1     1   453 use Moose;
  1         417129  
  1         6  
14 1     1   72337 use namespace::autoclean;
  1         7865  
  1         4  
15              
16 1     1   60 use Try::Tiny qw(try catch finally);
  1         2  
  1         61  
17 1     1   516 use Types::Standard qw(-types);
  1         64096  
  1         8  
18 1     1   4421 use Getopt::ArgvFile qw(argvFile);
  1         4507  
  1         7  
19 1     1   657 use Getopt::Long qw(GetOptionsFromArray);
  1         9197  
  1         5  
20 1     1   603 use RDF::Trine qw(literal blank iri);
  1         1301645  
  1         58  
21 1     1   517 use RT::Client::REST qw();
  1         28778  
  1         26  
22 1     1   407 use RT::Client::REST::Queue qw();
  1         405835  
  1         31  
23 1     1   8 use Cwd qw(cwd);
  1         2  
  1         63  
24 1     1   423 use Path::FindDev qw(find_dev);
  1         791  
  1         4  
25 1     1   545 use Digest::SHA1 qw(sha1_hex);
  1         629  
  1         60  
26              
27 1     1   7 use RDF::Trine::Namespace qw/rdf rdfs owl xsd/;
  1         3  
  1         13  
28             my $dbug = RDF::Trine::Namespace->new('http://ontologi.es/doap-bugs#');
29             my $dc = RDF::Trine::Namespace->new('http://purl.org/dc/terms/');
30             my $doap = RDF::Trine::Namespace->new('http://usefulinc.com/ns/doap#');
31             my $foaf = RDF::Trine::Namespace->new('http://xmlns.com/foaf/0.1/');
32             my $status = RDF::Trine::Namespace->new('http://purl.org/NET/cpan-uri/rt/status/');
33             my $prio = RDF::Trine::Namespace->new('http://purl.org/NET/cpan-uri/rt/priority/');
34              
35             has [qw/user pass/] => (
36             is => 'ro',
37             isa => Str,
38             required => 1,
39             );
40              
41             has server => (
42             is => 'ro',
43             isa => Str,
44             default => 'https://rt.cpan.org',
45             );
46              
47             has project_uri => (
48             is => 'ro',
49             isa => Str,
50             lazy => 1,
51             builder => '_build_project_uri',
52             );
53              
54             has queue => (
55             is => 'ro',
56             isa => Str,
57             lazy => 1,
58             builder => '_build_queue',
59             );
60              
61             has queue_model => (
62             is => 'ro',
63             isa => InstanceOf['RDF::Trine::Model'],
64             lazy => 1,
65             builder => '_build_queue_model',
66             );
67              
68             has dest => (
69             is => 'ro',
70             isa => Str | InstanceOf['Path::Tiny'],
71             default => sub { find_dev(cwd)->child('meta/rt-bugs.ttl') },
72             );
73              
74             sub main
75             {
76 0     0 1   my ($class, @argv) = @_;
77            
78 0           argvFile(
79             array => \@argv,
80             startupFilename => '.rt-grabbugs',
81             current => 1,
82             home => 1,
83             );
84            
85 0           my %opts;
86 0           GetOptionsFromArray(
87             \@argv,
88             \%opts,
89             qw/
90             queue=s
91             dest=s
92             user=s
93             pass=s
94             server=s
95             project_uri=s
96             /,
97             );
98            
99 0           $class->new(%opts)->process;
100             }
101              
102             sub _build_queue
103             {
104 0     0     my $self = shift;
105 0           my $root = find_dev(cwd);
106            
107 0           my $ini = $root->child('dist.ini');
108 0 0         if ($ini)
109             {
110 0           my @ini = grep /^;;/, do { my $fh = $ini->openr; <$fh> };
  0            
  0            
111 0           chomp @ini;
112             my %config = map {
113 0           s/(?:^;;\s*)|(?:\s*$)//g;
  0            
114 0           my ($key, $value) = split /\s*=\s*/, $_, 2;
115 0           $key => scalar(eval($value));
116             } @ini;
117 0 0         return $config{name} if $config{name};
118             }
119              
120 0           confess "Unable to determine RT queue. Please specify manually.";
121             }
122              
123             sub _build_project_uri
124             {
125 0     0     my $self = shift;
126 0           sprintf('http://purl.org/NET/cpan-uri/dist/%s/project', $self->queue);
127             }
128              
129             sub _build_queue_model
130             {
131 0     0     my $self = shift;
132 0           my $model = RDF::Trine::Model->new;
133            
134 0           warn sprintf "Logging in to %s\n", $self->server;
135            
136 0           my $rt;
137             try
138             {
139 0     0     $rt = RT::Client::REST->new(
140             server => $self->server,
141             timeout => 60,
142             );
143 0           push @{ $rt->_ua->{requests_redirectable} }, 'POST';
  0            
144 0           $rt->login(
145             username => $self->user,
146             password => $self->pass,
147             );
148             }
149             catch
150             {
151 0     0     require Data::Dumper;
152 0           die Data::Dumper::Dumper($_);
153 0           };
154            
155 0           warn sprintf "Retrieving queue for %s\n", $self->queue;
156            
157 0           my $queue = RT::Client::REST::Queue->new(
158             rt => $rt,
159             id => $self->queue,
160             )->retrieve;
161 0           my $tickets = $queue->tickets->get_iterator;
162            
163 0           while (my $ticket = $tickets->())
164             {
165 0           $self->_process_ticket($model, $queue, $ticket);
166             }
167              
168 0           return $model;
169             }
170              
171             my %EMAIL;
172             sub _process_ticket
173             {
174 0     0     my $self = shift;
175 0           my ($model, $queue, $ticket) = @_;
176            
177 0           warn sprintf("Processing RT#%d\n", $ticket->id);
178            
179 0           my $P = iri $self->project_uri;
180 0           my $T = iri sprintf('http://purl.org/NET/cpan-uri/rt/ticket/%d', $ticket->id);
181            
182 0           $model->add_statement($_) for (
183             RDF::Trine::Statement->new($P, $dbug->issue, $T),
184             RDF::Trine::Statement->new($T, $rdf->type, $dbug->Issue),
185             RDF::Trine::Statement->new($T, $dbug->id, literal($ticket->id)),
186             RDF::Trine::Statement->new($T, $dbug->page, iri sprintf('https://rt.cpan.org/Public/Bug/Display.html?id=%d', $ticket->id)),
187 0           RDF::Trine::Statement->new($T, $dbug->status, $status->${\ $ticket->status }),
188             RDF::Trine::Statement->new($T, $dc->created, literal($ticket->created, undef, $xsd->dateTime)),
189             RDF::Trine::Statement->new($T, $rdfs->label, literal($ticket->subject)),
190             );
191            
192 0           for my $email ($ticket->requestors) {
193             my $R = ($email =~ /\A(\w+)\@cpan.org\z/i)
194             ? iri(sprintf 'http://purl.org/NET/cpan-uri/person/%s', uc $1)
195 0 0 0       : ( $EMAIL{$email} ||= blank() );
196 0           $model->add_statement($_) for (
197             RDF::Trine::Statement->new($T, $dc->reporter, $R),
198             RDF::Trine::Statement->new($R, $rdf->type, $foaf->Agent),
199             RDF::Trine::Statement->new($R, $foaf->mbox_sha1sum, literal(sha1_hex(sprintf('mailto:%s', $email)))),
200             );
201             try {
202 0     0     RDF::Trine::Statement->new($R, $foaf->mbox, iri sprintf('mailto:%s', $email)),
203 0           };
204             }
205             }
206              
207             sub process
208             {
209 0     0 1   my $self = shift;
210            
211 0           my $model = $self->queue_model;
212            
213 0 0         my $ser = eval { require RDF::TrineX::Serializer::MockTurtleSoup }
  0            
214             ? 'RDF::TrineX::Serializer::MockTurtleSoup'
215             : 'RDF::Trine::Serializer::Turtle';
216            
217 0           warn sprintf("Writing to %s using %s\n", $self->dest, $ser);
218            
219 0           open my $fh, '>:encoding(UTF-8)', $self->dest;
220            
221 0           $ser->new(namespaces => {
222             dbug => 'http://ontologi.es/doap-bugs#',
223             dc => 'http://purl.org/dc/terms/',
224             doap => 'http://usefulinc.com/ns/doap#',
225             foaf => 'http://xmlns.com/foaf/0.1/',
226             rdfs => 'http://www.w3.org/2000/01/rdf-schema#',
227             rt => 'http://purl.org/NET/cpan-uri/rt/ticket/',
228             status => 'http://purl.org/NET/cpan-uri/rt/status/',
229             prio => 'http://purl.org/NET/cpan-uri/rt/priority/',
230             xsd => 'http://www.w3.org/2001/XMLSchema#',
231             })->serialize_model_to_file($fh, $model);
232            
233 0           $self;
234             }
235              
236             __PACKAGE__
237             __END__
238              
239             =head1 NAME
240              
241             RT::ClientX::GrabBugs - download bugs from an RT queue and dump them as RDF
242              
243             =head1 SYNOPSIS
244              
245             RT::ClientX::GrabBugs
246             ->new({
247             user => $rt_username,
248             pass => $rt_password,
249             queue => $rt_queue,
250             dest => './output_file.ttl',
251             })
252             ->process;
253              
254             =head1 DESCRIPTION
255              
256             This module downloads bugs from an RT queue and dumps them as RDF.
257              
258             =head2 Constructor
259              
260             =over
261              
262             =item C<< new(%attrs) >>
263              
264             Fairly standard Moosey C<new> constructor, accepting a hash of named
265             parameters.
266              
267             =item C<< main(@argv) >>
268              
269             Alternative constructor. Processes C<< @argv >> like command-line arguments.
270             e.g.
271              
272             RT::ClientX::GrabBugs->main('--user=foo', '--pass=bar',
273             '--queue=My-Module');
274              
275             This constructor uses L<Getopt::ArgvFile> to read additional options from
276             C<< ~/.rt-grabbugs >> and C<< ./.rt-grabbugs >>.
277              
278             The constructor supports the options "--user", "--pass", "--queue" and
279             "--dest".
280              
281             =back
282              
283             =head2 Attributes
284              
285             =over
286              
287             =item * C<server>, C<user>, C<pass>
288              
289             Details for logging into RT.
290              
291             =item * C<dest>
292              
293             The file name where you want to save the data. This defaults to
294             "./meta/rt-bugs.ttl".
295              
296             =item * C<queue>
297              
298             Queue to grab bugs for. Assuming that you're grabbing from rt.cpan.org, this
299             corresponds to a CPAN distribution (e.g. "RT-ClientX-GrabBugs").
300              
301             If not provided, this module will try to guess which queue you want. It does
302             this by looking for a file called "dist.ini" in the project directory. Within
303             this file, it looks for a line with the following format:
304              
305             ;; name="Foo-Bar"
306              
307             This type of line is commonly found in dist.ini files designed for
308             L<Dist::Inkt>. If you're using L<Dist::Zilla> it should be possible to add
309             such a line without breaking anything. (Dist::Zilla sees lines beginning with
310             a semicolon as comments.)
311              
312             =item * C<project_uri>
313              
314             URI to use for doap:Project in output.
315              
316             =item * C<queue_model>
317              
318             An RDF::Trine::Model generated by calling the C<add_to_model> method on each
319             bug in the C<queue_table> list. Here you probably want to rely on the default
320             model that the class builds.
321              
322             =back
323              
324             =head2 Methods
325              
326             =over
327              
328             =item * C<< process >>
329              
330             Saves the model from C<queue_model> to the destination C<dest> as Turtle.
331              
332             Returns C<$self>.
333              
334             =back
335              
336             =head1 BUGS
337              
338             Please report any bugs to
339             L<http://rt.cpan.org/Dist/Display.html?Queue=RT-ClientX-GrabBugs>.
340              
341             =head1 SEE ALSO
342              
343             L<RDF::DOAP>.
344              
345             =head1 AUTHOR
346              
347             Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
348              
349             =head1 COPYRIGHT AND LICENCE
350              
351             This software is copyright (c) 2012, 2014 by Toby Inkster.
352              
353             This is free software; you can redistribute it and/or modify it under
354             the same terms as the Perl 5 programming language system itself.
355              
356             =head1 DISCLAIMER OF WARRANTIES
357              
358             THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
359             WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
360             MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
361