Re: CPANPLUS working again on VMS Re: [PATCH@32279] Upgrade File::Fetch to 0.13_04...
[p5sagit/p5-mst-13.2.git] / lib / CPANPLUS / Internals / Utils.pm
CommitLineData
6aaee015 1package CPANPLUS::Internals::Utils;
2
3use strict;
4
5use CPANPLUS::Error;
6use CPANPLUS::Internals::Constants;
7
8use Cwd;
9use File::Copy;
10use Params::Check qw[check];
11use Module::Load::Conditional qw[can_load];
12use Locale::Maketext::Simple Class => 'CPANPLUS', Style => 'gettext';
13
14local $Params::Check::VERBOSE = 1;
15
16=pod
17
18=head1 NAME
19
20CPANPLUS::Internals::Utils
21
22=head1 SYNOPSIS
23
24 my $bool = $cb->_mkdir( dir => 'blah' );
25 my $bool = $cb->_chdir( dir => 'blah' );
26 my $bool = $cb->_rmdir( dir => 'blah' );
27
28 my $bool = $cb->_move( from => '/some/file', to => '/other/file' );
29 my $bool = $cb->_move( from => '/some/dir', to => '/other/dir' );
30
31 my $cont = $cb->_get_file_contents( file => '/path/to/file' );
32
33
34 my $version = $cb->_perl_version( perl => $^X );
35
36=head1 DESCRIPTION
37
38C<CPANPLUS::Internals::Utils> holds a few convenience functions for
39CPANPLUS libraries.
40
41=head1 METHODS
42
43=head2 $cb->_mkdir( dir => '/some/dir' )
44
45C<_mkdir> creates a full path to a directory.
46
47Returns true on success, false on failure.
48
49=cut
50
51sub _mkdir {
52 my $self = shift;
53
54 my %hash = @_;
55
56 my $tmpl = {
57 dir => { required => 1 },
58 };
59
60 my $args = check( $tmpl, \%hash ) or (
61 error(loc( Params::Check->last_error ) ), return
62 );
63
64 unless( can_load( modules => { 'File::Path' => 0.0 } ) ) {
65 error( loc("Could not use File::Path! This module should be core!") );
66 return;
67 }
68
69 eval { File::Path::mkpath($args->{dir}) };
70
71 if($@) {
72 chomp($@);
73 error(loc(qq[Could not create directory '%1': %2], $args->{dir}, $@ ));
74 return;
75 }
76
77 return 1;
78}
79
80=pod
81
82=head2 $cb->_chdir( dir => '/some/dir' )
83
84C<_chdir> changes directory to a dir.
85
86Returns true on success, false on failure.
87
88=cut
89
90sub _chdir {
91 my $self = shift;
92 my %hash = @_;
93
94 my $tmpl = {
95 dir => { required => 1, allow => DIR_EXISTS },
96 };
97
98 my $args = check( $tmpl, \%hash ) or return;
99
100 unless( chdir $args->{dir} ) {
101 error( loc(q[Could not chdir into '%1'], $args->{dir}) );
102 return;
103 }
104
105 return 1;
106}
107
108=pod
109
110=head2 $cb->_rmdir( dir => '/some/dir' );
111
112Removes a directory completely, even if it is non-empty.
113
114Returns true on success, false on failure.
115
116=cut
117
118sub _rmdir {
119 my $self = shift;
120 my %hash = @_;
121
122 my $tmpl = {
123 dir => { required => 1, allow => IS_DIR },
124 };
125
126 my $args = check( $tmpl, \%hash ) or return;
127
128 unless( can_load( modules => { 'File::Path' => 0.0 } ) ) {
129 error( loc("Could not use File::Path! This module should be core!") );
130 return;
131 }
132
133 eval { File::Path::rmtree($args->{dir}) };
134
135 if($@) {
136 chomp($@);
137 error(loc(qq[Could not delete directory '%1': %2], $args->{dir}, $@ ));
138 return;
139 }
140
141 return 1;
142}
143
144=pod
145
146=head2 $cb->_perl_version ( perl => 'some/perl/binary' );
147
148C<_perl_version> returns the version of a certain perl binary.
149It does this by actually running a command.
150
151Returns the perl version on success and false on failure.
152
153=cut
154
155sub _perl_version {
156 my $self = shift;
157 my %hash = @_;
158
159 my $perl;
160 my $tmpl = {
161 perl => { required => 1, store => \$perl },
162 };
163
164 check( $tmpl, \%hash ) or return;
165
166 my $perl_version;
167 ### special perl, or the one we are running under?
168 if( $perl eq $^X ) {
169 ### just load the config
170 require Config;
171 $perl_version = $Config::Config{version};
172
173 } else {
174 my $cmd = $perl .
175 ' -MConfig -eprint+Config::config_vars+version';
176 ($perl_version) = (`$cmd` =~ /version='(.*)'/);
177 }
178
179 return $perl_version if defined $perl_version;
180 return;
181}
182
183=pod
184
185=head2 $cb->_version_to_number( version => $version );
186
187Returns a proper module version, or '0.0' if none was available.
188
189=cut
190
191sub _version_to_number {
192 my $self = shift;
193 my %hash = @_;
194
195 my $version;
196 my $tmpl = {
197 version => { default => '0.0', store => \$version },
198 };
199
200 check( $tmpl, \%hash ) or return;
201
202 return $version if $version =~ /^\.?\d/;
203 return '0.0';
204}
205
206=pod
207
208=head2 $cb->_whoami
209
210Returns the name of the subroutine you're currently in.
211
212=cut
213
214sub _whoami { my $name = (caller 1)[3]; $name =~ s/.+:://; $name }
215
216=pod
217
218=head2 _get_file_contents( file => $file );
219
220Returns the contents of a file
221
222=cut
223
224sub _get_file_contents {
225 my $self = shift;
226 my %hash = @_;
227
228 my $file;
229 my $tmpl = {
230 file => { required => 1, store => \$file }
231 };
232
233 check( $tmpl, \%hash ) or return;
234
235 my $fh = OPEN_FILE->($file) or return;
236 my $contents = do { local $/; <$fh> };
237
238 return $contents;
239}
240
241=pod $cb->_move( from => $file|$dir, to => $target );
242
243Moves a file or directory to the target.
244
245Returns true on success, false on failure.
246
247=cut
248
249sub _move {
250 my $self = shift;
251 my %hash = @_;
252
253 my $from; my $to;
254 my $tmpl = {
255 file => { required => 1, allow => [IS_FILE,IS_DIR],
256 store => \$from },
257 to => { required => 1, store => \$to }
258 };
259
260 check( $tmpl, \%hash ) or return;
261
262 if( File::Copy::move( $from, $to ) ) {
263 return 1;
264 } else {
265 error(loc("Failed to move '%1' to '%2': %3", $from, $to, $!));
266 return;
267 }
268}
269
270=pod $cb->_copy( from => $file|$dir, to => $target );
271
272Moves a file or directory to the target.
273
274Returns true on success, false on failure.
275
276=cut
277
278sub _copy {
279 my $self = shift;
280 my %hash = @_;
281
282 my($from,$to);
283 my $tmpl = {
284 file =>{ required => 1, allow => [IS_FILE,IS_DIR],
285 store => \$from },
286 to => { required => 1, store => \$to }
287 };
288
289 check( $tmpl, \%hash ) or return;
290
291 if( File::Copy::copy( $from, $to ) ) {
292 return 1;
293 } else {
294 error(loc("Failed to copy '%1' to '%2': %3", $from, $to, $!));
295 return;
296 }
297}
298
299=head2 $cb->_mode_plus_w( file => '/path/to/file' );
300
301Sets the +w bit for the file.
302
303Returns true on success, false on failure.
304
305=cut
306
307sub _mode_plus_w {
308 my $self = shift;
309 my %hash = @_;
310
311 require File::stat;
312
313 my $file;
314 my $tmpl = {
315 file => { required => 1, allow => IS_FILE, store => \$file },
316 };
317
318 check( $tmpl, \%hash ) or return;
319
320 ### set the mode to +w for a file and +wx for a dir
321 my $x = File::stat::stat( $file );
322 my $mask = -d $file ? 0100 : 0200;
323
324 if( $x and chmod( $x->mode|$mask, $file ) ) {
325 return 1;
326
327 } else {
328 error(loc("Failed to '%1' '%2': '%3'", 'chmod +w', $file, $!));
329 return;
330 }
331}
332
333=head2 $uri = $cb->_host_to_uri( scheme => SCHEME, host => HOST, path => PATH );
334
335Turns a CPANPLUS::Config style C<host> entry into an URI string.
336
337Returns the uri on success, and false on failure
338
339=cut
340
341sub _host_to_uri {
342 my $self = shift;
343 my %hash = @_;
344
345 my($scheme, $host, $path);
346 my $tmpl = {
5bc5f6dc 347 scheme => { required => 1, store => \$scheme },
348 host => { default => 'localhost', store => \$host },
349 path => { default => '', store => \$path },
6aaee015 350 };
351
352 check( $tmpl, \%hash ) or return;
353
5879cbe1 354 ### it's an URI, so unixify the path.
355 ### VMS has a special method for just that
356 $path = ON_VMS
357 ? VMS::Filespec::unixify($path)
358 : File::Spec::Unix->catdir( File::Spec->splitdir( $path ) );
6aaee015 359
360 return "$scheme://" . File::Spec::Unix->catdir( $host, $path );
361}
362
363=head2 $cb->_vcmp( VERSION, VERSION );
364
365Normalizes the versions passed and does a '<=>' on them, returning the result.
366
367=cut
368
369sub _vcmp {
370 my $self = shift;
371 my ($x, $y) = @_;
372
373 s/_//g foreach $x, $y;
374
375 return $x <=> $y;
376}
377
378=head2 $cb->_home_dir
379
380Returns the user's homedir, or C<cwd> if it could not be found
381
382=cut
383
384sub _home_dir {
385 my @os_home_envs = qw( APPDATA HOME USERPROFILE WINDIR SYS$LOGIN );
386
387 for my $env ( @os_home_envs ) {
388 next unless exists $ENV{ $env };
389 next unless defined $ENV{ $env } && length $ENV{ $env };
390 return $ENV{ $env } if -d $ENV{ $env };
391 }
392
393 return cwd();
394}
395
396=head2 $path = $cb->_safe_path( path => $path );
397
5bc5f6dc 398Returns a path that's safe to us on Win32 and VMS.
399
400Only cleans up the path on Win32 if the path exists.
401
402On VMS, it encodes dots to _ using C<VMS::Filespec::vmsify>
6aaee015 403
404=cut
405
406sub _safe_path {
407 my $self = shift;
408
409 my %hash = @_;
410
411 my $path;
412 my $tmpl = {
413 path => { required => 1, store => \$path },
414 };
415
416 check( $tmpl, \%hash ) or return;
417
5bc5f6dc 418 if( ON_WIN32 ) {
419 ### only need to fix it up if there's spaces in the path
420 return $path unless $path =~ /\s+/;
421
422 ### or if we are on win32
423 return $path if $^O ne 'MSWin32';
6aaee015 424
5bc5f6dc 425 ### clean up paths if we are on win32
426 return Win32::GetShortPathName( $path ) || $path;
427
428 } elsif ( ON_VMS ) {
429 ### XXX According to John Malmberg, there's an VMS issue:
430 ### catdir on VMS can not currently deal with directory components
431 ### with dots in them.
432 ### Fixing this is a a three step procedure, which will work for
433 ### VMS in its traditional ODS-2 mode, and it will also work if
434 ### VMS is in the ODS-5 mode that is being implemented.
5879cbe1 435 ### If the path is already in VMS syntax, assume that we are done.
436
437 ### VMS format is a path with a trailing ']' or ':'
438 return $path if $path =~ /\:|\]$/;
5bc5f6dc 439
440 ### 1. Make sure that the value to be converted, $path is
441 ### in UNIX directory syntax by appending a '/' to it.
442 $path .= '/' unless $path =~ m|/$|;
443
444 ### 2. Use VMS::Filespec::vmsify($path . '/') to convert the dots to
445 ### underscores if needed. The trailing '/' is needed as so that
446 ### C<vmsify> knows that it should use directory translation instead of
447 ### filename translation, as filename translation leaves one dot.
448 $path = VMS::Filespec::vmsify( $path );
449
450 ### 3. Use $path = File::Spec->splitdir( VMS::Filespec::vmsify(
451 ### $path . '/') to remove the directory delimiters.
452
453 ### From John Malmberg:
454 ### File::Spec->catdir will put the path back together.
455 ### The '/' trick only works if the string is a directory name
456 ### with UNIX style directory delimiters or no directory delimiters.
457 ### It is to force vmsify to treat the input specification as UNIX.
458 ###
459 ### There is a VMS::Filespec::unixpath() to do the appending of the '/'
460 ### to the specification, which will do a VMS::Filespec::vmsify()
461 ### if needed.
462 ### However it is not a good idea to call vmsify() on a pathname
463 ### returned by unixify(), and it is not a good idea to call unixify()
464 ### on a pathname returned by vmsify(). Because of the nature of the
465 ### conversion, not all file specifications can make the round trip.
466 ###
467 ### I think that directory specifications can safely make the round
468 ### trip, but not ones containing filenames.
469 $path = File::Spec->catdir( File::Spec->splitdir( $path ) )
470 }
471
472 return $path;
6aaee015 473}
474
475
476=head2 ($pkg, $version, $ext) = $cb->_split_package_string( package => PACKAGE_STRING );
477
478Splits the name of a CPAN package string up in it's package, version
479and extension parts.
480
481For example, C<Foo-Bar-1.2.tar.gz> would return the following parts:
482
483 Package: Foo-Bar
484 Version: 1.2
485 Extension: tar.gz
486
487=cut
488
489{ my $del_re = qr/[-_\+]/i; # delimiter between elements
490 my $pkg_re = qr/[a-z] # any letters followed by
491 [a-z\d]* # any letters, numbers
492 (?i:\.pm)? # followed by '.pm'--authors do this :(
493 (?: # optionally repeating:
494 $del_re # followed by a delimiter
495 [a-z] # any letters followed by
496 [a-z\d]* # any letters, numbers
497 (?i:\.pm)? # followed by '.pm'--authors do this :(
498 )*
499 /xi;
500
501 my $ver_re = qr/[a-z]*\d+[a-z]* # contains a digit and possibly letters
502 (?:
503 [-._] # followed by a delimiter
504 [a-z\d]+ # and more digits and or letters
505 )*?
506 /xi;
507
508 my $ext_re = qr/[a-z] # a letter, followed by
509 [a-z\d]* # letters and or digits, optionally
510 (?:
511 \. # followed by a dot and letters
512 [a-z\d]+ # and or digits (like .tar.bz2)
513 )? # optionally
514 /xi;
515
516 my $ver_ext_re = qr/
517 ($ver_re+) # version, optional
518 (?:
519 \. # a literal .
520 ($ext_re) # extension,
521 )? # optional, but requires version
522 /xi;
523
524 ### composed regex for CPAN packages
525 my $full_re = qr/
526 ^
527 ($pkg_re+) # package
528 (?:
529 $del_re # delimiter
530 $ver_ext_re # version + extension
531 )?
532 $
533 /xi;
534
535 ### composed regex for perl packages
536 my $perl = PERL_CORE;
537 my $perl_re = qr/
538 ^
539 ($perl) # package name for 'perl'
540 (?:
541 $ver_ext_re # version + extension
542 )?
543 $
544 /xi;
545
546
547sub _split_package_string {
548 my $self = shift;
549 my %hash = @_;
550
551 my $str;
552 my $tmpl = { package => { required => 1, store => \$str } };
553 check( $tmpl, \%hash ) or return;
554
555
556 ### 2 different regexes, one for the 'perl' package,
557 ### one for ordinary CPAN packages.. try them both,
558 ### first match wins.
559 for my $re ( $full_re, $perl_re ) {
560
561 ### try the next if the match fails
562 $str =~ $re or next;
563
564 my $pkg = $1 || '';
565 my $ver = $2 || '';
566 my $ext = $3 || '';
567
568 ### this regex resets the capture markers!
569 ### strip the trailing delimiter
570 $pkg =~ s/$del_re$//;
571
572 ### strip the .pm package suffix some authors insist on adding
573 $pkg =~ s/\.pm$//i;
574
575 return ($pkg, $ver, $ext );
576 }
577
578 return;
579 }
580}
581
5bc5f6dc 582{ my %escapes = map {
583 chr($_) => sprintf("%%%02X", $_)
584 } 0 .. 255;
585
586 sub _uri_encode {
587 my $self = shift;
588 my %hash = @_;
589
590 my $str;
591 my $tmpl = {
592 uri => { store => \$str, required => 1 }
593 };
594
595 check( $tmpl, \%hash ) or return;
596
597 ### XXX taken straight from URI::Encode
598 ### Default unsafe characters. RFC 2732 ^(uric - reserved)
599 $str =~ s|([^A-Za-z0-9\-_.!~*'()])|$escapes{$1}|g;
600
601 return $str;
602 }
603
604
605 sub _uri_decode {
606 my $self = shift;
607 my %hash = @_;
608
609 my $str;
610 my $tmpl = {
611 uri => { store => \$str, required => 1 }
612 };
613
614 check( $tmpl, \%hash ) or return;
615
616 ### XXX use unencode routine in utils?
617 $str =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
618
619 return $str;
620 }
621}
622
623sub _update_timestamp {
624 my $self = shift;
625 my %hash = @_;
626
627 my $file;
628 my $tmpl = {
629 file => { required => 1, store => \$file, allow => FILE_EXISTS }
630 };
631
632 check( $tmpl, \%hash ) or return;
633
634 ### `touch` the file, so windoze knows it's new -jmb
635 ### works on *nix too, good fix -Kane
636 ### make sure it is writable first, otherwise the `touch` will fail
637
638 my $now = time;
639 unless( chmod( 0644, $file) && utime ($now, $now, $file) ) {
640 error( loc("Couldn't touch %1", $file) );
641 return;
642 }
643
644 return 1;
645}
646
647
6aaee015 6481;
649
650# Local variables:
651# c-indentation-style: bsd
652# c-basic-offset: 4
653# indent-tabs-mode: nil
654# End:
655# vim: expandtab shiftwidth=4: