File Coverage

blib/lib/WWW/Github/Files.pm
Criterion Covered Total %
statement 77 99 77.7
branch 27 44 61.3
condition 3 8 37.5
subroutine 14 22 63.6
pod 3 5 60.0
total 124 178 69.6


line stmt bran cond sub pod time code
1             package WWW::Github::Files;
2 1     1   848 use strict;
  1         3  
  1         46  
3 1     1   7 use warnings;
  1         1  
  1         35  
4 1     1   1219 use LWP::UserAgent;
  1         58569  
  1         43  
5 1     1   15 use JSON qw{decode_json};
  1         2  
  1         11  
6 1     1   215 use Carp;
  1         2  
  1         1209  
7              
8             our $VERSION = 0.13;
9              
10             sub new {
11 1     1 0 513 my ($class, %options) = @_;
12              
13 1 50       7 die "Please pass a author name"
14             unless exists $options{author};
15 1 50       5 die "Please pass a resp name"
16             unless exists $options{resp};
17 1 50 33     6 die "Please pass either a branch name or a commit"
18             unless exists $options{branch} or exists $options{commit};
19              
20 1         3 my $self = {};
21 1         2 foreach my $key (qw( author resp token branch commit self_token )) {
22 6 100       18 next unless exists $options{$key};
23 3         7 $self->{$key} = $options{$key};
24             }
25 1 50       4 if (not exists $self->{token}) {
26 1         11 $self->{ua} = LWP::UserAgent->new();
27 1 50       4153 $self->{ua}->default_header( Authorization => "token ".$self->{self_token} )
28             if exists $self->{self_token};
29             }
30 1         9 $self->{apiurl} = 'https://api.github.com/repos/'.$options{author}.'/'.$options{resp};
31 1         11 bless $self, $class;
32             }
33              
34             sub open {
35 4     4 1 890 my ($self, $path) = @_;
36 4 50       30 croak("Path should start with '/'! |$path|")
37             unless $path =~ m!^/!;
38 4         16 my $commit = $self->__fetch_root();
39 4         12 $path =~ s!/$!!;
40 4 100       15 $path = '/' if $path eq '';
41 4         21 my $f_data = $self->geturl("/contents$path?ref=$commit");
42 4 100       27 if (ref($f_data) eq 'ARRAY') {
    50          
43             # a directory
44 2         16 my ($name) = $path =~ m!/([^/]*)$!;
45 2 100       17 my $dir = {
46             FS => $self,
47             content => $f_data,
48             name => $name,
49             path => ( $path eq '/' ? '' : substr($path, 1) ),
50             };
51 2         57 return bless $dir, 'WWW::Github::Files::Dir';
52             }
53             elsif ($f_data->{type} eq 'file') {
54 2         23 return bless $f_data, 'WWW::Github::Files::File';
55             }
56             else {
57 0         0 croak('unrecognised file type for $path');
58             }
59             }
60              
61             sub get_file {
62 0     0 1 0 my ($self, $path) = @_;
63 0         0 return $self->open($path)->read();
64             }
65              
66             sub get_dir {
67 0     0 1 0 my ($self, $path) = @_;
68 0         0 return $self->open($path)->readdir();
69             }
70              
71             sub __fetch_root {
72 4     4   7 my $self = shift;
73 4         12 my $root = $self->{root_commit};
74 4 100       19 return $root if $root;
75              
76 1 50       5 if ($self->{branch}) {
77 1         7 my $b_data = $self->geturl('/branches/'.$self->{branch});
78 1         13 $root = $b_data->{commit}->{sha};
79             }
80             else {
81 0         0 my $c_data = $self->geturl('/git/commits/'.$self->{commit});
82 0         0 $root = $self->{commit};
83             }
84 1         3 $self->{root_commit} = $root;
85 1         3 return $root;
86             }
87              
88             sub geturl {
89 5     5 0 7 my ($self, $url, $method) = @_;
90 5   33     38 my $token = $self->{token} || $self->{ua};
91 5   50     23 $method ||= 'get';
92 5         34 my $res = $token->$method($self->{apiurl} . $url);
93 5 50       3127107 if (!$res->is_success()) {
94 0 0       0 if ($res->message() =~ m/Internal Server Error/) {
95             # retry
96 0         0 my $res2 = $token->$method($self->{apiurl} . $url);
97 0 0       0 if ($res2->is_success()) {
98 0         0 $res = $res2;
99             }
100 0         0 print STDERR $res2->message(), ", ", $res2->content, "\n";
101             }
102 0 0       0 if (!$res->is_success()) {
103 0         0 die "Failed to read $self->{apiurl}$url from github: ".$res->message(). ", ".$res->content;
104             }
105             }
106 5         107 my $content = $res->content;
107 5         1261 return decode_json($content);
108             }
109              
110             package WWW::Github::Files::File;
111 1     1   1745 use MIME::Base64 qw{decode_base64};
  1         945  
  1         656  
112              
113 0     0   0 sub is_file { 1 }
114 0     0   0 sub is_dir { 0 }
115              
116 10     10   1005 sub name { return $_[0]->{name} }
117 0     0   0 sub path { return '/'.$_[0]->{path} }
118              
119             sub read {
120 2     2   686 my $self = shift;
121 2 100       39 if (not $self->{content}) {
122             # this is a file object created from directory listing.
123             # need to fetch the content
124 1         7 my $f_data = $self->{FS}->open('/'.$self->{path});
125 1         26 $self->{$_} = $f_data->{$_} for (qw{ encoding content });
126             }
127 2 50       15 if ($self->{encoding} eq 'base64') {
128 2         33 return decode_base64($self->{content});
129             }
130             else {
131 0         0 die "can not handle encoding " . $self->{encoding} . " for file ". $self->{path};
132             }
133             }
134              
135             package WWW::Github::Files::Dir;
136              
137 0     0   0 sub is_file { 0 }
138 0     0   0 sub is_dir { 1 }
139              
140 4     4   28 sub name { return $_[0]->{name} }
141 0     0   0 sub path { return '/'.$_[0]->{path} }
142              
143             sub readdir {
144 2     2   432 my $self = shift;
145 2 100       22 if (not $self->{content}) {
146             # this is a file object created from directory listing.
147             # need to fetch the content
148 1         6 my $f_data = $self->{FS}->open('/'.$self->{path});
149 1         4 $self->{content} = $f_data->{content};
150             }
151 2         4 my @files;
152 2         4 foreach my $rec (@{ $self->{content} }) {
  2         7  
153 9         18 $rec->{FS} = $self->{FS};
154 9 100       25 if ($rec->{type} eq 'file') {
    50          
155 7         19 push @files, bless($rec, 'WWW::Github::Files::File');
156             }
157             elsif ($rec->{type} eq 'dir') {
158 2         9 push @files, bless($rec, 'WWW::Github::Files::Dir');
159             }
160             else {
161 0         0 croak('unrecognised file type: '.$rec->{type});
162             }
163             }
164 2         14 return @files;
165             }
166              
167             1;
168              
169             =head1 NAME
170              
171             WWW::Github::Files - Read files and directories from Github
172              
173             =head1 SYNOPSIS
174              
175             my $gitfiles = WWW::Github::Files->new(
176             author => 'semuel',
177             resp => 'site-lang-collab',
178             branch => 'master',
179             );
180              
181             my @files = $gitfiles->open('/')->readdir();
182              
183             =head1 DESCRIPTION
184              
185             Using Github API to browse a git resp easily and download files
186              
187             This modules is a thin warper around the API, just to make life easier
188              
189             =head1 ALTERNATIVES
190              
191             The easiest way to get a file off Github is to use the raw url:
192              
193             https://raw.github.com/semuel/perlmodule-WWW-Github-Files/master/MANIFEST
194              
195             This will return the content of this module's MANIFEST file. Easy, but
196             the file have to be public and you need to know beforehand where exactly
197             it is. (this method does not fetch directory content)
198              
199             Also, if you download two files under 'master', there is a chance that a
200             commit happened in the middle and you get two files from two different
201             versions of the respo. Of course you can fetch the current commit and
202             use it instead of master, but then it is less easy
203              
204             This module let you use Access Token for permission, and scan directories
205              
206             =HEAD1 MOCKING
207              
208             Need to write code that read files from Github and local repositories?
209             Check out L that uses the same interface
210             for local directory.
211              
212             =head1 CONSTRUCTOR OPTIONS
213              
214             =over 4
215              
216             =item author - resp author
217              
218             =item resp - resp name
219              
220             =item branch - The branch to read from
221              
222             Mutual exlusive with 'commit'.
223              
224             On first access the object will "lock" on the latest commit in this branch,
225             and from this point will serve files only from this commit
226              
227             =item commit - a specific commit to read from
228              
229             The object will retrive files and directories as they were after this commit
230              
231             =item token
232              
233             Optional Net::Oauth2 Access Token, for using in API calls.
234             If not specified, will make anonymous calls using LWP
235              
236             =item self_token
237              
238             Optional Github "Personal Access Token" to use for API authentication.
239              
240             =back
241              
242             =head1 METHODS
243              
244             =head2 open(path)
245              
246             receive path (which have to start with '/') and return file or dir object
247             for that location
248              
249             =head2 get_file(path)
250              
251             shortcut to $gitfiles->open(path)->read()
252              
253             =head2 get_dir(path)
254              
255             shortcut to $gitfiles->open(path)->readdir()
256              
257             =head1 FILE OBJECT METHODS
258              
259             =head2 name
260              
261             The name of the file
262              
263             =head2 path
264              
265             full path (+name) of the file
266              
267             =head2 is_file
268              
269             =head2 is_dir
270              
271             =head2 read
272              
273             returns the content of the file
274              
275             =head1 DIRECTORY OBJECT METHODS
276              
277             =head2 name
278              
279             The name of the directory
280              
281             =head2 path
282              
283             full path (+name) of the directory
284              
285             =head2 is_file
286              
287             =head2 is_dir
288              
289             =head2 readdir
290              
291             returns a list of file/dir objects that this directory contains
292              
293             =head1 AUTHOR
294            
295             Fomberg Shmuel, Eshmuelfomberg@gmail.comE
296            
297             =head1 COPYRIGHT AND LICENSE
298            
299             Copyright 2013 by Shmuel Fomberg.
300            
301             This library is free software; you can redistribute it and/or modify
302             it under the same terms as Perl itself.
303            
304             =cut