File Coverage

blib/lib/Bundler/MultiGem/Model/Gem.pm
Criterion Covered Total %
statement 55 136 40.4
branch 6 24 25.0
condition 0 3 0.0
subroutine 19 26 73.0
pod 17 17 100.0
total 97 206 47.0


line stmt bran cond sub pod time code
1             package Bundler::MultiGem::Model::Gem;
2              
3 2     2   79332 use 5.006;
  2         15  
4 2     2   10 use strict;
  2         6  
  2         37  
5 2     2   9 use warnings;
  2         4  
  2         61  
6              
7 2     2   13 use Exporter qw(import);
  2         4  
  2         95  
8             our @EXPORT = qw(gem_vname gem_vmodule_name norm_v);
9 2     2   1030 use common::sense;
  2         26  
  2         11  
10              
11 2     2   952 use Bundler::MultiGem::Utl::InitConfig qw(ruby_constantize);
  2         6  
  2         127  
12 2     2   450 use File::Spec::Functions qw(catfile catdir);
  2         844  
  2         119  
13 2     2   13 use File::Find;
  2         3  
  2         4330  
14              
15             =head1 NAME
16              
17             Bundler::MultiGem::Model::Gem - The utility to install multiple versions of the same ruby gem
18              
19             =head1 VERSION
20              
21             Version 0.02
22              
23             =cut
24              
25             our $VERSION = '0.02';
26              
27             =head1 SYNOPSIS
28              
29             This module contains utility functions for manipulating gems
30              
31             =head1 SUBROUTINES
32              
33             =head2 new
34              
35             Takes an hash reference parameter. You should provide:
36             * C: string, gem name in repository
37             * C: string, gem main module name
38             * C: string, e.g. C<"https://rubygems.org">
39             * C: array ref of strings, e.g. C<[qw( 0.0.5 0.1.0 )]>
40              
41             my $config = {
42             name => "jsonschema_serializer",
43             main_module => "JsonschemaSerializer",
44             source => "https://rubygems.org",
45             versions => [qw( 0.0.5 0.1.0 )]
46             };
47             my $gem = Bundler::MultiGem::Model::Gem->new($config);
48              
49             =cut
50              
51             sub new {
52 1     1 1 572 my $class = shift;
53 1         4 my $self = { config => shift };
54 1         3 bless $self, $class;
55 1         2 return $self;
56             }
57              
58             =head2 config
59             config getter
60             =cut
61             sub config {
62 7     7 1 19 my ($self, $key) = @_;
63 7 100       19 if (!defined $key) {
64 1         9 return $self->{config};
65             }
66 6         28 return $self->{config}->{$key}
67             }
68              
69             =head2 name
70              
71             C getter
72              
73             $gem->name; # "jsonschema_serializer"
74              
75             =cut
76             sub name {
77 2     2 1 4 my $self = shift;
78 2         6 return $self->config("name")
79             }
80              
81             =head2 source
82              
83             C getter
84              
85             $gem->source; # "https://rubygems.org"
86              
87             =cut
88             sub source {
89 1     1 1 3 my $self = shift;
90 1         3 return $self->config("source")
91             }
92              
93             =head2 main_module
94              
95             C getter
96              
97             $gem->main_module; # "JsonschemaSerializer"
98              
99             =cut
100             sub main_module {
101 2     2 1 4 my $self = shift;
102 2         6 return $self->config("main_module")
103             }
104              
105             =head2 versions
106              
107              
108             C getter
109              
110             $gem->versions; # [qw( 0.0.5 0.1.0 )]
111              
112             =cut
113             sub versions {
114 1     1 1 3 my $self = shift;
115 1         4 return $self->config("versions")
116             }
117              
118             =head2 vname
119              
120             C getter: combine gem name and version and format it
121             * C: string, a gem version reference
122              
123             $gem->name; # "jsonschema_serializer"
124             my $v = "0.0.5";
125             $gem->vname($v); # "v005-jsonschema_serializer"
126              
127              
128             =cut
129              
130             sub vname {
131 2     2 1 48 my ($self, $v) = @_;
132 2 100       7 if (!defined $v) {
133 1         10 die "You need to provide a version to vname method";
134             }
135 1         4 return Bundler::MultiGem::Model::Gem::gem_vname($self->name, $v);
136             }
137              
138             =head2 vmodule_name
139              
140             C getter: combine gem name and version and format it
141             * C: string, a gem version reference
142              
143             $gem->main_module; # "JsonschemaSerializer"
144             my $v = "0.0.5";
145             $gem->vmodule_name($v); # "V005::JsonschemaSerializer"
146              
147             =cut
148              
149             sub vmodule_name {
150 2     2 1 37 my ($self, $v) = @_;
151 2 100       7 if (!defined $v) {
152 1         11 die "You need to provide a version to vmodule_name method";
153             }
154 1         4 return Bundler::MultiGem::Model::Gem::gem_vmodule_name($self->main_module, $v);
155             }
156              
157             =head2 apply
158              
159             This function apply all the transformation to the gem versions to be reused in the same C:
160             C, a C instance
161              
162             For each gem version it will:
163             * fetch the gem version
164             * extract it into a target directory
165             * C
166             * C
167             * rename the lib directory including the version name
168             * for each C, C and C apply C
169              
170             Caveats:
171             * if you are renaming C to C, also partial matches will be renamed: e.g. C
172             * this is intended for benchmarking simple gems, it may break with very complex gems
173             * the use case leading this development was benchmarking serialization gems performance
174             =cut
175              
176             sub apply {
177 0     0 1 0 my ($self, $dir) = @_;
178 0         0 my $pkg_dir = $dir->dirs("pkg");
179 0         0 my $target_dir = $dir->dirs("target");
180 0         0 my @gemfile_statements = ();
181 0         0 foreach my $v (@{$self->versions}) {
  0         0  
182 0         0 my $normv = Bundler::MultiGem::Model::Gem::norm_v($v);
183 0         0 my $gem_vname = $self->vname($v);
184 0         0 my $gem_path = catfile( $pkg_dir, "${gem_vname}.gem" );
185 0         0 my $extracted_dir = catdir( $target_dir, $gem_vname );
186              
187 0         0 $self->fetch($gem_path, $v);
188 0         0 Bundler::MultiGem::Model::Gem::unpack_gem($gem_path, $target_dir);
189              
190 0         0 $self->process_gemfile($v, $extracted_dir);
191              
192 0         0 my $lib_dir = catdir( $extracted_dir, 'lib' );
193             # process main gem module
194 0         0 $self->rename_main_file($v, $lib_dir);
195              
196             # Rename gem name dir in lib directory
197 0 0       0 rename( catdir( $lib_dir, $self->name ), catdir( $lib_dir, $gem_vname )) ||
198             warn catdir( $lib_dir, $self->name ) . "does not exists: $!";
199              
200             # Process all ruby files
201 0         0 my @ruby_files = ();
202             find(
203             {
204             wanted => sub {
205 0     0   0 my $F = $File::Find::name;
206 0 0       0 push @ruby_files, $F if ($F =~ /(rb|rake|Rakefile)$/)
207             },
208 0         0 no_chdir => 1
209             },
210             $extracted_dir
211             );
212              
213 0         0 foreach my $f (@ruby_files) {
214 0         0 $self->process_single_file($v, $f);
215             }
216              
217 0         0 print $gem_vname . " completed!\n";
218 0         0 push @gemfile_statements, "gem '$gem_vname', path: '$extracted_dir'";
219             }
220 0         0 print "Process completed.\n\n";
221 0         0 print "You can add to your Gemfile something like:\n";
222 0         0 foreach (@gemfile_statements) { print "$_\n"; }
  0         0  
223             }
224              
225              
226             =head2 process_gemfile
227              
228             Manipulates original gemfile as follows:
229             * rename C for v C<0.1.0> into C
230             * remove line importing gem version (usually C)
231             * replace main_module version with the actual version (C with C<'0.1.0'>)
232             * replace gem name reference with gem vname (C with C)
233             * replace gem main_module reference with gem vmodule_name (C with C)
234             * unlink the original C
235              
236             =cut
237              
238             sub process_gemfile {
239 0     0 1 0 my ($self, $v, $extracted_dir) = @_;
240 0         0 my ($n, $vn, $mm, $vmn) = (
241             $self->name, $self->vname($v), $self->main_module, $self->vmodule_name($v)
242             );
243 0         0 my $gemspec = catfile($extracted_dir, $n . ".gemspec");
244 0         0 my $new_gemspec = catfile($extracted_dir, $vn . ".gemspec");
245              
246             # Process .gemspec
247 0 0       0 open(GEMSPEC, "<${gemspec}") || die "Can't open ${gemspec}: $!";
248 0 0       0 open(NEW_GEMSPEC, ">${new_gemspec}") || die "Can't open ${new_gemspec}: $!";
249              
250 0         0 while( my $line = ){
251 0 0       0 if ( $line =~ /${n}\/version/ ) { next; }
  0         0  
252 0         0 for ($line) {
253             # Replace version reference from file
254 0         0 s/${mm}::VERSION/'$v'/;
255 0         0 s/${n}/${vn}/g;
256 0         0 s/${mm}/${vmn}/g;
257             }
258              
259 0         0 print NEW_GEMSPEC $line;
260             }
261 0         0 close(NEW_GEMSPEC);
262 0         0 close(GEMSPEC);
263              
264 0   0     0 unlink $gemspec || warn "Could not unlink ${gemspec}: $!";
265             }
266              
267             =head2 rename_main_file
268              
269             Manipulates original gemfile as follows:
270             * rename C for v C<0.1.0> into C
271             * add on top of C: C for namespacing
272             * copy the rest of original below
273              
274             This step allows to add a namespace with the gem version (a kind of shading).
275             All the other replacement are done with other rb, rake and Rakfile files.
276              
277             =cut
278              
279             sub rename_main_file {
280 0     0 1 0 my ($self, $v, $lib_dir) = @_;
281              
282 0         0 my $normv = Bundler::MultiGem::Model::Gem::norm_v($v);
283 0         0 my $new_main_module = ruby_constantize($normv);
284              
285 0         0 my $main_module_file = catfile( $lib_dir, $self->name . ".rb" );
286 0         0 my $new_main_module_file = catfile( $lib_dir, $self->vname($v) . ".rb" );
287              
288 0 0       0 if ( -e $main_module_file ) {
289 0 0       0 open(ORIGINAL, "<${main_module_file}") ||
290             die "Can't open ${main_module_file}: $!";
291 0         0 my @file_content = ;
292 0         0 close(ORIGINAL);
293              
294 0 0       0 open(NEW_FILE, ">${new_main_module_file}") ||
295             die "Can't open ${new_main_module_file}: $!";
296 0         0 print NEW_FILE "module ${new_main_module}; end\n";
297 0         0 foreach my $line (@file_content) { print NEW_FILE $line; }
  0         0  
298 0         0 close(NEW_FILE);
299              
300 0         0 unlink $main_module_file;
301             }
302             }
303              
304             =head2 process_single_file
305              
306             Manipulates each file as follows:
307             * create a backup of the original file C<.bak>
308             * replace gem name reference with gem vname (C with C)
309             * replace gem main_module reference with gem vmodule_name (C with C)
310             * unlink the backup C<.bak>
311              
312             =cut
313              
314             sub process_single_file {
315 0     0 1 0 my ($self, $v, $f) = @_;
316              
317 0         0 my ($n, $vn, $mm, $vmn) = (
318             $self->name, $self->vname($v), $self->main_module, $self->vmodule_name($v)
319             );
320 0         0 my $bkp = $f . ".bak";
321 0         0 rename($f, $bkp);
322 0         0 open(I, "<$bkp");
323 0         0 open(O, ">$f");
324 0         0 while(my $line = ) {
325 0         0 for ($line) {
326 0         0 s/${n}/${vn}/g;
327 0         0 s/${mm}/${vmn}/g;
328             }
329 0         0 print O $line;
330             }
331 0         0 close(O);
332 0         0 close(I);
333 0         0 unlink $bkp;
334             }
335              
336             =head2 fetch
337              
338             This is an alias of the system command C
339              
340             my $gem->name; # "foo"
341             my $fp = "pkg/v010-foo.gem"
342             my $gv = "0.1.0";
343             $gem->fetch($fp, $gv); # if $fp found, do nothing, else fetch gem version and rename it
344              
345             =cut
346              
347             sub fetch {
348 0     0 1 0 my ( $self, $fp, $gv ) = (@_);
349 0 0       0 if (! -f $fp ) {
350 0         0 my $cmd = "gem fetch " . $self->name . " --version " . $gv .
351             " --source " . $self->source;
352 0         0 system("$cmd");
353 0         0 rename( $self->name . "-$gv" . ".gem", $fp );
354             }
355             }
356              
357             =head1 EXPORTS
358              
359             =head2 gem_vname
360              
361             Reusable function to combine and format gem name and version
362              
363             use Bundler::MultiGem::Model::Gem qw(gem_vname);
364             gem_vname("foo", "0.1.0"); # v010-foo
365             gem_vname("foo_bar", "0.1.0"); # v010-foo_bar
366             gem_vname("foo-bar", "0.1.0"); # v010-foo-bar
367              
368             =cut
369              
370             sub gem_vname {
371 2     2 1 1584 my ($gem_name, $v) = @_;
372 2         8 join('-', (norm_v($v), $gem_name));
373             }
374              
375             =head2 gem_vmodule_name
376              
377             Reusable function to combine and format gem main module and version
378              
379             use Bundler::MultiGem::Model::Gem qw(gem_vmodule_name);
380             gem_vname("Foo", "0.1.0"); # V010::Foo
381             gem_vname("FooBar", "0.1.0"); # V010::FooBar
382             gem_vname("Foo::Bar", "0.1.0"); # V010::Foo::Bar
383              
384             =cut
385             sub gem_vmodule_name {
386 3     3 1 633 my ($gem_module_name, $v) = @_;
387 3         9 ruby_constantize(join('-', (norm_v($v), $gem_module_name)));
388             }
389              
390             =head2 norm_v
391              
392             Utility function to normalize version name
393              
394             use Bundler::MultiGem::Model::Gem qw(norm_v);
395             norm_v("123"); #v123
396             norm_v("1.23"); #v123
397             norm_v("12.3"); #v123
398             norm_v("1.2.3"); #v123
399             =cut
400              
401             sub norm_v {
402 7     7 1 590 my $v = shift;
403 7         17 for ($v) {
404 7         38 s/\.//g;
405             }
406 7         45 "v${v}";
407             }
408              
409             =head2 unpack_gem
410              
411             This is an alias of the system command C
412              
413             =cut
414              
415             sub unpack_gem {
416 0     0 1   my ($gem_filepath, $target_dir) = @_;
417 0           system("gem unpack ${gem_filepath} --target ${target_dir}");
418             }
419              
420             =head1 AUTHOR
421              
422             Mauro Berlanda, C<< >>
423              
424             =head1 BUGS
425              
426             Please report any bugs or feature requests to L, or through
427             the web interface at L. I will be notified, and then you'll
428             automatically be notified of progress on your bug as I make changes.
429              
430             =head1 SUPPORT
431              
432             You can find documentation for this module with the perldoc command.
433              
434             perldoc Bundler::MultiGem::Directories
435              
436              
437             You can also look for information at:
438              
439             =over 2
440              
441             =item * RT: CPAN's request tracker (report bugs here)
442              
443             L
444              
445             =item * Github Repository
446              
447             L
448              
449             =back
450              
451              
452             =head1 ACKNOWLEDGEMENTS
453              
454              
455             =head1 LICENSE AND COPYRIGHT
456              
457             Copyright 2018 Mauro Berlanda.
458              
459             This program is free software; you can redistribute it and/or modify it
460             under the terms of the the Artistic License (2.0). You may obtain a
461             copy of the full license at:
462              
463             L
464              
465             Any use, modification, and distribution of the Standard or Modified
466             Versions is governed by this Artistic License. By using, modifying or
467             distributing the Package, you accept this license. Do not use, modify,
468             or distribute the Package, if you do not accept this license.
469              
470             If your Modified Version has been derived from a Modified Version made
471             by someone other than you, you are nevertheless required to ensure that
472             your Modified Version complies with the requirements of this license.
473              
474             This license does not grant you the right to use any trademark, service
475             mark, tradename, or logo of the Copyright Holder.
476              
477             This license includes the non-exclusive, worldwide, free-of-charge
478             patent license to make, have made, use, offer to sell, sell, import and
479             otherwise transfer the Package with respect to any patent claims
480             licensable by the Copyright Holder that are necessarily infringed by the
481             Package. If you institute patent litigation (including a cross-claim or
482             counterclaim) against any party alleging that the Package constitutes
483             direct or contributory patent infringement, then this Artistic License
484             to you shall terminate on the date that such litigation is filed.
485              
486             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
487             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
488             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
489             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
490             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
491             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
492             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
493             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
494              
495              
496             =cut
497              
498             1;