File Coverage

blib/lib/WWW/SourceForge/Project.pm
Criterion Covered Total %
statement 65 111 58.5
branch 10 22 45.4
condition 2 8 25.0
subroutine 14 20 70.0
pod 9 13 69.2
total 100 174 57.4


line stmt bran cond sub pod time code
1             package WWW::SourceForge::Project;
2 3     3   37025 use strict;
  3         5  
  3         82  
3 3     3   581 use WWW::SourceForge;
  3         4  
  3         52  
4 3     3   404 use WWW::SourceForge::User;
  3         3  
  3         64  
5 3     3   9 use LWP::Simple;
  3         3  
  3         31  
6 3     3   833 use Data::Dumper;
  3         3  
  3         2566  
7              
8             our $VERSION = '0.40';
9             our $DEFAULT_ICON = 'http://a.fsdn.com/con/img/project_default.png';
10              
11             =head2 new
12              
13             Usage:
14            
15             my $proj = new WWW::SourceForge::Project( id => 1234 );
16             my $proj2 = new WWW::SourceForge::Project( name => 'flightics' );
17              
18             my @admins = $proj->admins(); # WWW::SourceForge::User objects
19             my @developers = $proj->developers(); # Ditto
20              
21             Returns: WWW::SourceForge::Project object;
22              
23             =cut
24              
25             sub new {
26              
27 4     4 1 25 my ( $class, %parameters ) = @_;
28 4   33     30 my $self = bless( {}, ref($class) || $class );
29              
30 4         34 my $api = new WWW::SourceForge;
31 4         11 $self->{api} = $api;
32              
33 4         6 my $json;
34 4 50       18 if ( $parameters{id} ) {
    50          
35 0         0 warn('The API does not support project IDs any more');
36 0         0 return 0;
37             } elsif ( $parameters{name} ) {
38             $json = $api->call(
39             uri => '/p/' . $parameters{name}
40 4         21 );
41             } else {
42 0         0 warn('You must provide an id or name. Bad monkey.');
43 0         0 return 0;
44             }
45              
46 4         15 $self->{data} = $json;
47 4         27 return $self;
48             }
49              
50             =head2 admins
51              
52             @admins = $project->admins();
53              
54             Returns a list of WWW::SourceForge::User objects which are the admins on this
55             project.
56              
57             =cut
58              
59             sub admins {
60 11     11 1 8 my $self = shift;
61 11 100       25 return @{ $self->{data}->{_admins} } if ref( $self->{data}->{_admins} );
  10         19  
62              
63 1         2 my @admins;
64              
65 1         2 my $a_ref = $self->{data}->{maintainers};
66 1         3 foreach my $u_ref ( @$a_ref ) {
67 0         0 my $user = new WWW::SourceForge::User( username => $u_ref->{name} );
68 0         0 push @admins, $user;
69             }
70              
71 1         3 $self->{data}->{_admins} = \@admins;
72 1         4 return @admins;
73             }
74              
75             =head2 developers
76              
77             @devs = $project->devs();
78              
79             Returns a list of WWW::SourceForge::User objects which are the developers on
80             the project. This does not include the admins.
81              
82             =cut
83              
84             sub developers { # not admins
85 12     12 1 12 my $self = shift;
86 12 100       20 return @{ $self->{data}->{_developers} } if ref( $self->{data}->{_developers} );
  11         29  
87              
88 1         2 my @devs;
89              
90 1         3 my $a_ref = $self->{data}->{developers};
91 1         3 foreach my $u_ref ( @$a_ref ) {
92 12         162 my $user = new WWW::SourceForge::User( username => $u_ref->{name} );
93 12         47 push @devs, $user;
94             }
95              
96 1         5 $self->{data}->{_developers} = \@devs;
97 1         7 return @devs;
98             }
99              
100             =head2 users
101              
102             All project users - admins and non-admins.
103              
104             =cut
105              
106             sub users {
107 11     11 1 9 my $self = shift;
108              
109 11         16 my @users = ( $self->admins(), $self->developers() );
110 11         31 return @users;
111             }
112              
113             =head2 files
114              
115             List of recent released files
116              
117             =cut
118              
119             sub files {
120 0     0 1 0 my $self = shift;
121              
122 0 0       0 return @{ $self->{data}->{files} } if $self->{data}->{files};
  0         0  
123 0         0 my %args = @_;
124            
125 0         0 my $api = new WWW::SourceForge;
126             # http://sourceforge.net/api/file/index/project-id/14603/crtime/desc/rss
127              
128             # Passing a full uri feels evil, but it's necessary because the file
129             # api cares about argument order.
130             my $files = $self->{api}->call(
131 0         0 uri => '/projects/' . $self->{name} . '/rss',
132             format => 'rss',
133             );
134 0         0 print Dumper( $files );
135              
136 0         0 my @files;
137 0         0 foreach my $f ( @{ $files->{entries} } ) {
  0         0  
138 0         0 push @files, $f->{entry};
139             }
140 0         0 $self->{data}->{files} = \@files;
141              
142 0         0 return @files;
143             }
144              
145             =head2 latest_release
146              
147             Date of the latest released file. It's a string. The format is pretty
148             much guaranteed to change in the future. For example, it'll probably be
149             a DateTime object.
150              
151             =cut
152              
153             sub latest_release {
154 0     0 1 0 my $self = shift;
155 0         0 my @files = $self->files();
156 0         0 print Dumper(@files);
157              
158 0         0 return $files[0]->{pubDate}; # TODO This is an object, and
159             # presumably I should be calling
160             # object methods.
161             }
162              
163             =head2 downloads
164              
165             Download counts for the specified date range. If no date range is
166             supplied, assume the 7 days leading up to today.
167              
168             WARNING: This API is subject to change any moment. The downloads API
169             gives us a LOT more information than just a count, and it may be that we
170             want to expose all of it later one. Right now I just want a count.
171              
172             my $dl_count = $project->downloads(
173             start_date => '2012-07-01',
174             end_date -> '2012-07-25'
175             );
176              
177             =cut
178              
179             # https://sourceforge.net/projects/xbmc/files/stats/json?start_date=2010-05-01&end_date=2010-05-11
180             sub downloads {
181 0     0 1 0 my $self = shift;
182 0         0 my %args = @_;
183              
184 0   0     0 my $data_api = $self->{data_api} || WWW::SourceForge->new( api => 'download' );
185 0         0 $self->{data_api} = $data_api;
186              
187 0         0 my $json = $data_api->call( %args, project => $self->shortdesc() );
188              
189 0         0 return $json->{summaries}->{time}->{downloads};
190             }
191              
192             =head2 logo
193              
194             For Allura projects, the logo is at https://sourceforge.net/p/PROJECT/icon
195             For Classic projects, who the heck knows?
196              
197             WARNING WARNING WARNING
198             This method will break the next time SF redesigns the project summary
199             page. On the other hand, by then all projects will be Allura projects,
200             and the else clause will never execute.
201             WARNING WARNING WARNING
202              
203             =cut
204              
205             sub logo {
206 2     2 1 14 my $self = shift;
207 2         5 my %args = @_;
208              
209 2 50       50 if ( $self->type == 10 ) {
210 0         0 my $icon = 'http://sourceforge.net/p/' . $self->shortdesc() . '/icon';
211              
212             # Need to verify that it's actually there
213 0         0 my $verify = get( $icon );
214 0 0       0 return $verify
215             ? $icon
216             : $DEFAULT_ICON;
217             } else {
218              
219             # Screen scrape to get the icon
220             # my $psp_content = get( $self->psp() );
221 2         10 my $psp_content = $self->_psp_content();
222              
223 2 50       9 my $m = $1 if $psp_content =~ m/img itemscope.*? Icon" src="(.*?)"/s;
224 2 50       9 my $icon =
225             $m
226             ? 'http:' . $m
227             : $DEFAULT_ICON;
228 2         26 return $icon;
229             }
230             }
231              
232             # Fetch and cache PSP contents
233             sub _psp_content {
234 2     2   3 my $self = shift;
235 2 50       7 unless ( $self->{psp_content} ) {
236 2   50     7 $self->{psp_content} = get( $self->psp() ) || '';
237             }
238 2         482580 return $self->{psp_content};
239             }
240              
241             # Alias
242             sub icon {
243 0     0 0 0 my $self = shift;
244 0         0 return $self->logo( @_ );
245             }
246              
247             =head2 summary
248              
249             Returns summary statement of project, if any.
250              
251             WARNING WARNING WARNING
252             This method relies on particular HTML IDs, and so will break the next
253             time the site is redesigned. Hopefully by then this will be directly
254             available in the API.
255             WARNING WARNING WARNING
256              
257             =cut
258              
259             # Project Summary Page URL
260             sub psp {
261 2     2 0 3 my $self = shift;
262 2         12 return 'http://sourceforge.net/projects/'.$self->shortdesc();
263             }
264              
265             # Alias to shortdesc
266             sub unix_name {
267 0     0 0 0 my $self = shift;
268 0         0 return $self->shortdesc();
269             }
270              
271             =head2 activity
272              
273             Contents of the project activity RSS feed. It's an array, and each item
274             looks like
275              
276             {
277             'pubDate' => 'Tue, 12 Jun 2012 19:33:05 +0000',
278             'title' => 'sf-robot changed the public information on the Flight ICS project',
279             'link' => 'http://sourceforge.net/projects/flightics',
280             'description' => 'sf-robot changed the public information on the Flight ICS project'
281             }
282              
283             =cut
284              
285             sub activity {
286 0     0 1 0 my $self = shift;
287              
288             # Cached
289 0 0       0 return @{ $self->{data}->{activity} } if $self->{data}->{activity};
  0         0  
290              
291             my $rss = $self->{api}->call(
292 0         0 method => 'proj_activity',
293             project => $self,
294             );
295            
296 0         0 my @activity;
297 0         0 foreach my $e ( @{ $rss->{entries} } ) {
  0         0  
298 0         0 push @activity, $e->{entry};
299             }
300 0         0 $self->{data}->{activity} = \@activity;
301 0         0 return @activity;
302             }
303              
304             sub id {
305 2     2 0 16 return shift->{data}->{_id};
306             }
307              
308             =head2 Data access AUTOLOADER
309              
310             Handles most of the data access for the Project object. Some parts of
311             the data require special treatment.
312              
313             =cut
314              
315             sub AUTOLOAD {
316 6     6   403 my $self = shift;
317 6         8 our $AUTOLOAD;
318 6         5 my $sub = $AUTOLOAD;
319 6         30 $sub =~ s/^.*:://;
320 6         13 ( my $method = $sub ) =~ s/.*:://;
321 6         31 return $self->{data}->{$sub};
322             }
323              
324             =head1 NAME
325              
326             WWW::SourceForge::Project - SourceForge project objects
327              
328             =head1 SYNOPSIS
329              
330             Uses the SourceForge API to load project details. This is a work in
331             progress, and the interface will change. Mostly I'm just poking about to
332             see what this needs to support. Please feel free to play along.
333              
334             http://sf.net/projects/sfprojecttools/
335              
336             =head1 DESCRIPTION
337              
338             Implements a Perl interface to SourceForge projects. See http://sourceforge.net/p/forge/documentation/API/
339              
340             =head1 USAGE
341              
342             use WWW::SourceForge::Project;
343             my $project = WWW::SourceForge::Project->new( name => 'moodle' );
344             print $project->id();
345             print $project->type();
346             print $project->status();
347             print $project->latest_release();
348              
349             See the 'project_details.pl' script in scripts/perl/ for more details.
350              
351             =head1 BUGS
352              
353             None
354              
355             =head1 SUPPORT
356              
357             http://sourceforge.net/p/sfprojecttools/tickets/
358              
359             =head1 AUTHOR
360              
361             Rich Bowen
362             CPAN ID: RBOW
363             SourceForge
364             rbowen@sourceforge.net
365             http://sf.net
366              
367             =head1 COPYRIGHT
368              
369             This program is free software; you can redistribute
370             it and/or modify it under the same terms as Perl itself.
371              
372             The full text of the license can be found in the
373             LICENSE file included with this module.
374              
375              
376             =head1 SEE ALSO
377              
378             perl(1).
379              
380             =cut
381              
382             #################### main pod documentation end ###################
383              
384             1;