File Coverage

blib/lib/OrePAN2/Injector.pm
Criterion Covered Total %
statement 98 148 66.2
branch 16 44 36.3
condition 7 15 46.6
subroutine 22 26 84.6
pod 3 8 37.5
total 146 241 60.5


line stmt bran cond sub pod time code
1              
2             use strict;
3 5     5   78507 use warnings;
  5         20  
  5         135  
4 5     5   26 use utf8;
  5         8  
  5         142  
5 5     5   23  
  5         10  
  5         28  
6             use Archive::Extract ();
7 5     5   624 use Archive::Tar qw( COMPRESS_GZIP );
  5         153394  
  5         94  
8 5     5   3025 use CPAN::Meta ();
  5         161396  
  5         297  
9 5     5   495 use File::Basename qw( basename dirname );
  5         24770  
  5         131  
10 5     5   26 use File::Copy qw( copy );
  5         11  
  5         232  
11 5     5   2154 use File::Find qw( find );
  5         9969  
  5         254  
12 5     5   33 use File::Path qw( mkpath );
  5         8  
  5         183  
13 5     5   25 use File::Spec ();
  5         11  
  5         174  
14 5     5   25 use File::Temp qw( tempdir );
  5         16  
  5         73  
15 5     5   21 use File::pushd qw( pushd );
  5         9  
  5         185  
16 5     5   424 use HTTP::Tiny ();
  5         1028  
  5         206  
17 5     5   616 use MetaCPAN::Client ();
  5         25550  
  5         138  
18 5     5   481  
  5         254478  
  5         7837  
19             my $class = shift;
20             my %args = @_ == 1 ? %{ $_[0] } : @_;
21 16     16 1 1808189 unless ( exists $args{directory} ) {
22 16 50       129 Carp::croak("Missing directory");
  0         0  
23 16 50       65 }
24 0         0 bless {
25             author => 'DUMMY',
26             %args
27 16         119 }, $class;
28             }
29              
30              
31             my ( $self, $source, $opts ) = @_;
32 32     32 1 1599 local $self->{author}
33             = $opts->{author} || $self->{author} || 'DUMMY';
34             local $self->{author_subdir} = $opts->{author_subdir} || q{};
35 16     16 1 106  
36             my $tarpath;
37 16   50     152 if ( $source =~ /(?:^git(?:\+\w+)?:|\.git(?:@.+)?$)/ )
38 16   100     103 { # steal from App::cpanminus::script
39             # git URL has to end with .git when you need to use pin @ commit/tag/branch
40 16         32 my ( $uri, $commitish ) = split /(?<=\.git)@/i, $source, 2;
41 16 50       460  
    100          
    100          
    50          
42             # git CLI doesn't support git+http:// etc.
43             $uri =~ s/^git\+//;
44 0         0 $tarpath = $self->inject_from_git( $uri, $commitish );
45             }
46             elsif ( $source =~ m{\Ahttps?://} ) {
47 0         0 $tarpath = $self->inject_from_http($source);
48 0         0 }
49             elsif ( -f $source ) {
50             $tarpath = $self->inject_from_file($source);
51 3         21 }
52             elsif ( $source =~ m/^[\w_][\w0-9:_]+$/ ) {
53              
54 8         41 my $c = MetaCPAN::Client->new( version => 'v1' )
55             || die "Could not get MetaCPAN::Client";
56              
57             my $mod = $c->module($source)
58 5   50     134 || die "Could not find $source";
59              
60             my $rel = $c->release( $mod->distribution )
61 5   50     1383 || die "Could not find distribution for $source";
62              
63             my $url = $rel->download_url
64 5   50     1034808 || die "Could not find url for $source";
65              
66             $tarpath = $self->inject_from_http($url);
67 5   50     665187 }
68             else {
69             die "Unknown source: $source\n";
70 5         124 }
71              
72             return File::Spec->abs2rel(
73 0         0 File::Spec->rel2abs($tarpath),
74             $self->directory
75             );
76 16         1521 }
77              
78             my ( $self, $basename ) = @_;
79              
80             my $author = uc( $self->{author} );
81             my $tarpath = File::Spec->catfile(
82             $self->directory, 'authors', 'id',
83 16     16 0 56 substr( $author, 0, 1 ),
84             substr( $author, 0, 2 ),
85 16         75 $author,
86             $self->{author_subdir},
87             $basename
88             );
89             mkpath( dirname($tarpath) );
90              
91             return $tarpath;
92 16         79 }
93              
94 16         7053 my ( $self, $source, $archive ) = @_;
95             my $tmpdir = tempdir( CLEANUP => 1 );
96 16         89 my $ae = Archive::Extract->new( archive => $archive );
97             $ae->extract( to => $tmpdir );
98             my $guard = pushd( glob("$tmpdir/*") );
99             $self->{author}->($source);
100 2     2   8 }
101 2         10  
102 2         657 my ( $self, $file ) = @_;
103 2         481  
104 2         629657 local $self->{author} = $self->_detect_author( $file, $file )
105 2         326 if ref $self->{author} eq "CODE";
106             my $basename = basename($file);
107             my $tarpath = $self->tarpath($basename);
108              
109 8     8 0 22 copy( $file, $tarpath )
110             or die "Copy failed $file $tarpath: $!\n";
111              
112 8 100       47 return $tarpath;
113 8         14552 }
114 8         36  
115             my ( $self, $url ) = @_;
116 8 50       54  
117             # If $self->{author} is not a code reference,
118             # then $tarpath is fixed before http request
119 8         3139 # and HTTP::Tiny->mirror works correctly.
120             # So we treat that case first.
121             if ( ref $self->{author} ne "CODE" ) {
122             my $basename = basename($url);
123 8     8 0 40 my $tarpath = $self->tarpath($basename);
124             my $response = HTTP::Tiny->new->mirror( $url, $tarpath );
125             unless ( $response->{success} ) {
126             die
127             "Cannot fetch $url($response->{status} $response->{reason})\n";
128             }
129 8 100       45 return $tarpath;
130 7         483 }
131 7         110  
132 7         80 my $tmpdir = tempdir( CLEANUP => 1 );
133 7 50       1302651 my $tmpfile = "$tmpdir/tmp.tar.gz";
134 0         0 my $response = HTTP::Tiny->new->mirror( $url, $tmpfile );
135             unless ( $response->{success} ) {
136             die "Cannot fetch $url($response->{status} $response->{reason})\n";
137 7         3259 }
138              
139             my $basename = basename($url);
140 1         4 local $self->{author} = $self->_detect_author( $url, $tmpfile );
141 1         277 my $tarpath = $self->tarpath($basename);
142 1         10 copy( $tmpfile, $tarpath )
143 1 50       88417 or die "Copy failed $tmpfile $tarpath: $!\n";
144 0         0  
145             my $mtime = ( stat $tmpfile )[9];
146             utime $mtime, $mtime, $tarpath;
147 1         304  
148 1         7 return $tarpath;
149 1         184 }
150 1 50       16  
151             my ( $self, $repository, $branch ) = @_;
152              
153 1         442 my $tmpdir = tempdir( CLEANUP => 1 );
154 1         18  
155             my ( $basename, $tar, $author ) = do {
156 1         89 my $guard = pushd($tmpdir);
157              
158             _run("git clone $repository");
159              
160 0     0 0   if ($branch) {
161             my $guard2 = pushd( [<*>]->[0] );
162 0           _run("git checkout $branch");
163             }
164 0            
165 0           my $author;
166             if ( ref $self->{author} eq "CODE" ) {
167 0           my $guard2 = pushd( [<*>]->[0] );
168             $author = $self->{author}->($repository);
169 0 0         }
170 0            
171 0           # The repository needs to contains META.json in repository.
172             my $metafname = File::Spec->catfile( [<*>]->[0], 'META.json' );
173             unless ( -f $metafname ) {
174 0           die "$repository does not have a META.json\n";
175 0 0         }
176 0            
177 0           my $meta = CPAN::Meta->load_file($metafname);
178              
179             my $name = $meta->{name};
180             my $version = $meta->{version};
181 0            
182 0 0         rename( [<*>]->[0], "$name-$version" )
183 0           or die $!;
184              
185             my $tmp_path = File::Spec->catfile(
186 0           $tmpdir,
187             );
188 0            
189 0           my $tar = Archive::Tar->new();
190             my @files = $self->list_files($tmpdir);
191 0 0         $tar->add_files(@files);
192              
193             ( "$name-$version.tar.gz", $tar, $author );
194 0           };
195              
196             local $self->{author} = $author if $author;
197             my $tarpath = $self->tarpath($basename);
198 0            
199 0           # Must be same partition.
200 0           my $tmp_tarpath = File::Temp::mktemp("${tarpath}.XXXXXX");
201             $tar->write( $tmp_tarpath, COMPRESS_GZIP );
202 0           unlink $tarpath if -f $tarpath;
203             rename( $tmp_tarpath => $tarpath )
204             or die $!;
205 0 0          
206 0           return $tarpath;
207             }
208              
209 0           my ( $self, $dir ) = @_;
210 0            
211 0 0         my @files;
212 0 0         find(
213             {
214             wanted => sub {
215 0           my $rel = File::Spec->abs2rel( $_, $dir );
216             my $top = [ File::Spec->splitdir($rel) ]->[1];
217             return if $top && $top eq '.git';
218             return unless -f $_;
219 0     0 0   push @files, $rel;
220             },
221 0           no_chdir => 1,
222             },
223             $dir,
224             );
225 0     0     return @files;
226 0           }
227 0 0 0        
228 0 0         print "% @_\n";
229 0            
230             system(@_) == 0 or die "ABORT\n";
231 0           }
232              
233             1;
234              
235 0            
236             =encoding utf-8
237              
238             =for stopwords DarkPAN orepan2-inject orepan2-indexer darkpan OrePAN1 OrePAN
239 0     0      
240             =head1 NAME
241 0 0          
242             OrePAN2::Injector - Inject a distribution to your DarkPAN
243              
244             =head1 SYNOPSIS
245              
246             use OrePAN2::Injector;
247              
248             my $injector = OrePAN2::Injector->new(directory => '/path/to/darkpan');
249              
250             $injector->inject(
251             'http://cpan.metacpan.org/authors/id/M/MA/MAHITO/Acme-Hoge-0.03.tar.gz',
252             { author => 'MAHITO' },
253             );
254              
255             =head1 DESCRIPTION
256              
257             OrePAN2::Injector allows you to inject a distribution into your DarkPAN.
258              
259             =head1 METHODS
260              
261             =head3 C<< my $injector = OrePAN2::Injector->new(%attr) >>
262              
263             Constructor. Here C<%attr> might be:
264              
265             =over 4
266              
267             =item * directory
268              
269             Your DarkPAN directory path. This is required.
270              
271             =item * author
272              
273             Default author of distributions.
274             If you omit this, then C<DUMMY> will be used.
275              
276             B<BETA>: As of OrePAN2 0.37,
277             the author attribute accepts a code reference, so that
278             you can calculate author whenever injecting distributions:
279              
280             my $author_cb = sub {
281             my $source = shift;
282             $source =~ m{authors/id/./../([^/]+)} ? $1 : "DUMMY";
283             };
284              
285             my $injector = OrePAN2::Injector->new(
286             directory => '/path/to/darkpan',
287             author => $author_cb,
288             );
289              
290             $injector->inject(
291             'http://cpan.metacpan.org/authors/id/M/MA/MAHITO/Acme-Hoge-0.03.tar.gz'
292             );
293             #=> Acme-Hoge-0.03 will be indexed with author MAHITO
294              
295             Note that the code reference C<$author_cb> will be executed
296             under the following circumstances:
297              
298             * the first argument is the $source argument to the inject method
299             * the working directory of it is the top level of the distribution in question
300              
301             =item * author_subdir
302              
303             This is an optional attribute. If present it means that directory elements
304             will be created following the author. This can be useful, for instance,
305             if you want to make your DarkPAN have paths that exactly match the paths
306             in CPAN. Sometimes CPAN paths look something like the following:
307              
308             authors/id/<author>/modules/...
309              
310             In the above case you can pass 'modules' as the value for author_subdir so
311             that the path OrePAN2 creates looks like the above path.
312              
313             =back
314              
315             =head3 C<< $injector->inject($source, \%option) >>
316              
317             Inject C<$source> to your DarkPAN. Here C<$source> is one of the following:
318              
319             =over 4
320              
321             =item * local archive file
322              
323             eg: /path/to/Text-TestBase-0.10.tar.gz
324              
325             =item * HTTP url
326              
327             eg: http://cpan.metacpan.org/authors/id/T/TO/TOKUHIROM/Text-TestBase-0.10.tar.gz
328              
329             =item * git repository
330              
331             eg: git://github.com/tokuhirom/Text-TestBase.git@master
332              
333             Note that you need to set up git repository as a installable git repo,
334             that is, you need to put a META.json in your repository.
335              
336             If you are using L<Minilla> or L<Milla>, your repository is already ready to install.
337              
338             Supports the following URL types:
339              
340             git+file://path/to/repo.git
341             git://github.com/plack/Plack.git@1.0000 # tag
342             git://github.com/plack/Plack.git@devel # branch
343              
344             They are compatible with L<cpanm>.
345              
346             =item * module name
347              
348             eg: Data::Dumper
349              
350             =back
351              
352             C<\%option> might be:
353              
354             =over 4
355              
356             =item * author
357              
358             Author of the distribution. This overrides C<new>'s author attribute.
359              
360             =back
361              
362             =head1 SEE ALSO
363              
364             L<orepan2-inject>
365              
366             =head1 LICENSE
367              
368             Copyright (C) tokuhirom.
369              
370             This library is free software; you can redistribute it and/or modify
371             it under the same terms as Perl itself.
372              
373             =head1 AUTHOR
374              
375             tokuhirom E<lt>tokuhirom@gmail.comE<gt>
376              
377             =cut