From 1307060127fc01c5ffe4d355e11bc747984cc52a Mon Sep 17 00:00:00 2001 From: Mike Date: Sun, 3 Oct 2010 18:24:08 -0400 Subject: [PATCH] [#325 state:resolved] Added IO::String, Path::Class and Sub::Install to extlib. --- {t/lib => extlib}/IO/String.pm | 0 extlib/Path/Class.pm | 177 +++++++++ extlib/Path/Class/Dir.pm | 653 +++++++++++++++++++++++++++++++++ extlib/Path/Class/Entity.pm | 86 +++++ extlib/Path/Class/File.pm | 349 ++++++++++++++++++ extlib/Sub/Install.pm | 329 +++++++++++++++++ 6 files changed, 1594 insertions(+) rename {t/lib => extlib}/IO/String.pm (100%) create mode 100644 extlib/Path/Class.pm create mode 100644 extlib/Path/Class/Dir.pm create mode 100644 extlib/Path/Class/Entity.pm create mode 100644 extlib/Path/Class/File.pm create mode 100644 extlib/Sub/Install.pm diff --git a/t/lib/IO/String.pm b/extlib/IO/String.pm similarity index 100% rename from t/lib/IO/String.pm rename to extlib/IO/String.pm diff --git a/extlib/Path/Class.pm b/extlib/Path/Class.pm new file mode 100644 index 000000000..68feeb870 --- /dev/null +++ b/extlib/Path/Class.pm @@ -0,0 +1,177 @@ +package Path::Class; + +$VERSION = '0.21'; +@ISA = qw(Exporter); +@EXPORT = qw(file dir); +@EXPORT_OK = qw(file dir foreign_file foreign_dir); + +use strict; +use Exporter; +use Path::Class::File; +use Path::Class::Dir; + +sub file { Path::Class::File->new(@_) } +sub dir { Path::Class::Dir ->new(@_) } +sub foreign_file { Path::Class::File->new_foreign(@_) } +sub foreign_dir { Path::Class::Dir ->new_foreign(@_) } + + +1; +__END__ + +=head1 NAME + +Path::Class - Cross-platform path specification manipulation + +=head1 SYNOPSIS + + use Path::Class; + + my $dir = dir('foo', 'bar'); # Path::Class::Dir object + my $file = file('bob', 'file.txt'); # Path::Class::File object + + # Stringifies to 'foo/bar' on Unix, 'foo\bar' on Windows, etc. + print "dir: $dir\n"; + + # Stringifies to 'bob/file.txt' on Unix, 'bob\file.txt' on Windows + print "file: $file\n"; + + my $subdir = $dir->subdir('baz'); # foo/bar/baz + my $parent = $subdir->parent; # foo/bar + my $parent2 = $parent->parent; # foo + + my $dir2 = $file->dir; # bob + + # Work with foreign paths + use Path::Class qw(foreign_file foreign_dir); + my $file = foreign_file('Mac', ':foo:file.txt'); + print $file->dir; # :foo: + print $file->as_foreign('Win32'); # foo\file.txt + + # Interact with the underlying filesystem: + + # $dir_handle is an IO::Dir object + my $dir_handle = $dir->open or die "Can't read $dir: $!"; + + # $file_handle is an IO::File object + my $file_handle = $file->open($mode) or die "Can't read $file: $!"; + +=head1 DESCRIPTION + +C is a module for manipulation of file and directory +specifications (strings describing their locations, like +C<'/home/ken/foo.txt'> or C<'C:\Windows\Foo.txt'>) in a cross-platform +manner. It supports pretty much every platform Perl runs on, +including Unix, Windows, Mac, VMS, Epoc, Cygwin, OS/2, and NetWare. + +The well-known module C also provides this service, but +it's sort of awkward to use well, so people sometimes avoid it, or use +it in a way that won't actually work properly on platforms +significantly different than the ones they've tested their code on. + +In fact, C uses C internally, wrapping all +the unsightly details so you can concentrate on your application code. +Whereas C provides functions for some common path +manipulations, C provides an object-oriented model of the +world of path specifications and their underlying semantics. +C doesn't create any objects, and its classes represent +the different ways in which paths must be manipulated on various +platforms (not a very intuitive concept). C creates +objects representing files and directories, and provides methods that +relate them to each other. For instance, the following C +code: + + my $absolute = File::Spec->file_name_is_absolute( + File::Spec->catfile( @dirs, $file ) + ); + +can be written using C as + + my $absolute = Path::Class::File->new( @dirs, $file )->is_absolute; + +or even as + + my $absolute = file( @dirs, $file )->is_absolute; + +Similar readability improvements should happen all over the place when +using C. + +Using C can help solve real problems in your code too - +for instance, how many people actually take the "volume" (like C +on Windows) into account when writing C-using code? I +thought not. But if you use C, your file and directory objects +will know what volumes they refer to and do the right thing. + +The guts of the C code live in the C +and C modules, so please see those +modules' documentation for more details about how to use them. + +=head2 EXPORT + +The following functions are exported by default. + +=over 4 + +=item file + +A synonym for C<< Path::Class::File->new >>. + +=item dir + +A synonym for C<< Path::Class::Dir->new >>. + +=back + +If you would like to prevent their export, you may explicitly pass an +empty list to perl's C, i.e. C. + +The following are exported only on demand. + +=over 4 + +=item foreign_file + +A synonym for C<< Path::Class::File->new_foreign >>. + +=item foreign_dir + +A synonym for C<< Path::Class::Dir->new_foreign >>. + +=back + +=head1 Notes on Cross-Platform Compatibility + +Although it is much easier to write cross-platform-friendly code with +this module than with C, there are still some issues to be +aware of. + +=over 4 + +=item * + +Some platforms, notably VMS and some older versions of DOS (I think), +all filenames must have an extension. Thus if you create a file +called F and then ask for a list of files in the directory +F, you may find a file called F instead of the F you +were expecting. Thus it might be a good idea to use an extension in +the first place. + +=back + +=head1 AUTHOR + +Ken Williams, KWILLIAMS@cpan.org + +=head1 COPYRIGHT + +Copyright (c) Ken Williams. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the same terms as Perl itself. + + +=head1 SEE ALSO + +Path::Class::Dir, Path::Class::File, File::Spec + +=cut diff --git a/extlib/Path/Class/Dir.pm b/extlib/Path/Class/Dir.pm new file mode 100644 index 000000000..edc9ff863 --- /dev/null +++ b/extlib/Path/Class/Dir.pm @@ -0,0 +1,653 @@ +package Path::Class::Dir; + +$VERSION = '0.21'; + +use strict; +use Path::Class::File; +use Carp(); +use base qw(Path::Class::Entity); + +use IO::Dir (); +use File::Path (); + +# updir & curdir on the local machine, for screening them out in +# children(). Note that they don't respect 'foreign' semantics. +my $Updir = __PACKAGE__->_spec->updir; +my $Curdir = __PACKAGE__->_spec->curdir; + +sub new { + my $self = shift->SUPER::new(); + + # If the only arg is undef, it's probably a mistake. Without this + # special case here, we'd return the root directory, which is a + # lousy thing to do to someone when they made a mistake. Return + # undef instead. + return if @_==1 && !defined($_[0]); + + my $s = $self->_spec; + + my $first = (@_ == 0 ? $s->curdir : + $_[0] eq '' ? (shift, $s->rootdir) : + shift() + ); + + ($self->{volume}, my $dirs) = $s->splitpath( $s->canonpath($first) , 1); + $self->{dirs} = [$s->splitdir($s->catdir($dirs, @_))]; + + return $self; +} + +sub file_class { "Path::Class::File" } + +sub is_dir { 1 } + +sub as_foreign { + my ($self, $type) = @_; + + my $foreign = do { + local $self->{file_spec_class} = $self->_spec_class($type); + $self->SUPER::new; + }; + + # Clone internal structure + $foreign->{volume} = $self->{volume}; + my ($u, $fu) = ($self->_spec->updir, $foreign->_spec->updir); + $foreign->{dirs} = [ map {$_ eq $u ? $fu : $_} @{$self->{dirs}}]; + return $foreign; +} + +sub stringify { + my $self = shift; + my $s = $self->_spec; + return $s->catpath($self->{volume}, + $s->catdir(@{$self->{dirs}}), + ''); +} + +sub volume { shift()->{volume} } + +sub file { + local $Path::Class::Foreign = $_[0]->{file_spec_class} if $_[0]->{file_spec_class}; + return $_[0]->file_class->new(@_); +} + +sub dir_list { + my $self = shift; + my $d = $self->{dirs}; + return @$d unless @_; + + my $offset = shift; + if ($offset < 0) { $offset = $#$d + $offset + 1 } + + return wantarray ? @$d[$offset .. $#$d] : $d->[$offset] unless @_; + + my $length = shift; + if ($length < 0) { $length = $#$d + $length + 1 - $offset } + return @$d[$offset .. $length + $offset - 1]; +} + +sub subdir { + my $self = shift; + return $self->new($self, @_); +} + +sub parent { + my $self = shift; + my $dirs = $self->{dirs}; + my ($curdir, $updir) = ($self->_spec->curdir, $self->_spec->updir); + + if ($self->is_absolute) { + my $parent = $self->new($self); + pop @{$parent->{dirs}}; + return $parent; + + } elsif ($self eq $curdir) { + return $self->new($updir); + + } elsif (!grep {$_ ne $updir} @$dirs) { # All updirs + return $self->new($self, $updir); # Add one more + + } elsif (@$dirs == 1) { + return $self->new($curdir); + + } else { + my $parent = $self->new($self); + pop @{$parent->{dirs}}; + return $parent; + } +} + +sub relative { + # File::Spec->abs2rel before version 3.13 returned the empty string + # when the two paths were equal - work around it here. + my $self = shift; + my $rel = $self->_spec->abs2rel($self->stringify, @_); + return $self->new( length $rel ? $rel : $self->_spec->curdir ); +} + +sub open { IO::Dir->new(@_) } +sub mkpath { File::Path::mkpath(shift()->stringify, @_) } +sub rmtree { File::Path::rmtree(shift()->stringify, @_) } + +sub remove { + rmdir( shift() ); +} + +sub recurse { + my $self = shift; + my %opts = (preorder => 1, depthfirst => 0, @_); + + my $callback = $opts{callback} + or Carp::croak( "Must provide a 'callback' parameter to recurse()" ); + + my @queue = ($self); + + my $visit_entry; + my $visit_dir = + $opts{depthfirst} && $opts{preorder} + ? sub { + my $dir = shift; + $callback->($dir); + unshift @queue, $dir->children; + } + : $opts{preorder} + ? sub { + my $dir = shift; + $callback->($dir); + push @queue, $dir->children; + } + : sub { + my $dir = shift; + $visit_entry->($_) foreach $dir->children; + $callback->($dir); + }; + + $visit_entry = sub { + my $entry = shift; + if ($entry->is_dir) { $visit_dir->($entry) } # Will call $callback + else { $callback->($entry) } + }; + + while (@queue) { + $visit_entry->( shift @queue ); + } +} + +sub children { + my ($self, %opts) = @_; + + my $dh = $self->open or Carp::croak( "Can't open directory $self: $!" ); + + my @out; + while (defined(my $entry = $dh->read)) { + next if !$opts{all} && $self->_is_local_dot_dir($entry); + next if ($opts{no_hidden} && $entry =~ /^\./); + push @out, $self->file($entry); + $out[-1] = $self->subdir($entry) if -d $out[-1]; + } + return @out; +} + +sub _is_local_dot_dir { + my $self = shift; + my $dir = shift; + + return ($dir eq $Updir or $dir eq $Curdir); +} + +sub next { + my $self = shift; + unless ($self->{dh}) { + $self->{dh} = $self->open or Carp::croak( "Can't open directory $self: $!" ); + } + + my $next = $self->{dh}->read; + unless (defined $next) { + delete $self->{dh}; + return undef; + } + + # Figure out whether it's a file or directory + my $file = $self->file($next); + $file = $self->subdir($next) if -d $file; + return $file; +} + +sub subsumes { + my ($self, $other) = @_; + die "No second entity given to subsumes()" unless $other; + + $other = $self->new($other) unless UNIVERSAL::isa($other, __PACKAGE__); + $other = $other->dir unless $other->is_dir; + + if ($self->is_absolute) { + $other = $other->absolute; + } elsif ($other->is_absolute) { + $self = $self->absolute; + } + + $self = $self->cleanup; + $other = $other->cleanup; + + if ($self->volume) { + return 0 unless $other->volume eq $self->volume; + } + + # The root dir subsumes everything (but ignore the volume because + # we've already checked that) + return 1 if "@{$self->{dirs}}" eq "@{$self->new('')->{dirs}}"; + + my $i = 0; + while ($i <= $#{ $self->{dirs} }) { + return 0 if $i > $#{ $other->{dirs} }; + return 0 if $self->{dirs}[$i] ne $other->{dirs}[$i]; + $i++; + } + return 1; +} + +sub contains { + my ($self, $other) = @_; + return !!(-d $self and (-e $other or -l $other) and $self->subsumes($other)); +} + +1; +__END__ + +=head1 NAME + +Path::Class::Dir - Objects representing directories + +=head1 SYNOPSIS + + use Path::Class qw(dir); # Export a short constructor + + my $dir = dir('foo', 'bar'); # Path::Class::Dir object + my $dir = Path::Class::Dir->new('foo', 'bar'); # Same thing + + # Stringifies to 'foo/bar' on Unix, 'foo\bar' on Windows, etc. + print "dir: $dir\n"; + + if ($dir->is_absolute) { ... } + if ($dir->is_relative) { ... } + + my $v = $dir->volume; # Could be 'C:' on Windows, empty string + # on Unix, 'Macintosh HD:' on Mac OS + + $dir->cleanup; # Perform logical cleanup of pathname + $dir->resolve; # Perform physical cleanup of pathname + + my $file = $dir->file('file.txt'); # A file in this directory + my $subdir = $dir->subdir('george'); # A subdirectory + my $parent = $dir->parent; # The parent directory, 'foo' + + my $abs = $dir->absolute; # Transform to absolute path + my $rel = $abs->relative; # Transform to relative path + my $rel = $abs->relative('/foo'); # Relative to /foo + + print $dir->as_foreign('Mac'); # :foo:bar: + print $dir->as_foreign('Win32'); # foo\bar + + # Iterate with IO::Dir methods: + my $handle = $dir->open; + while (my $file = $handle->read) { + $file = $dir->file($file); # Turn into Path::Class::File object + ... + } + + # Iterate with Path::Class methods: + while (my $file = $dir->next) { + # $file is a Path::Class::File or Path::Class::Dir object + ... + } + + +=head1 DESCRIPTION + +The C class contains functionality for manipulating +directory names in a cross-platform way. + +=head1 METHODS + +=over 4 + +=item $dir = Path::Class::Dir->new( , , ... ) + +=item $dir = dir( , , ... ) + +Creates a new C object and returns it. The +arguments specify names of directories which will be joined to create +a single directory object. A volume may also be specified as the +first argument, or as part of the first argument. You can use +platform-neutral syntax: + + my $dir = dir( 'foo', 'bar', 'baz' ); + +or platform-native syntax: + + my $dir = dir( 'foo/bar/baz' ); + +or a mixture of the two: + + my $dir = dir( 'foo/bar', 'baz' ); + +All three of the above examples create relative paths. To create an +absolute path, either use the platform native syntax for doing so: + + my $dir = dir( '/var/tmp' ); + +or use an empty string as the first argument: + + my $dir = dir( '', 'var', 'tmp' ); + +If the second form seems awkward, that's somewhat intentional - paths +like C or C<\Windows> aren't cross-platform concepts in the +first place (many non-Unix platforms don't have a notion of a "root +directory"), so they probably shouldn't appear in your code if you're +trying to be cross-platform. The first form is perfectly natural, +because paths like this may come from config files, user input, or +whatever. + +As a special case, since it doesn't otherwise mean anything useful and +it's convenient to define this way, C<< Path::Class::Dir->new() >> (or +C) refers to the current directory (C<< File::Spec->curdir >>). +To get the current directory as an absolute path, do C<< +dir()->absolute >>. + +Finally, as another special case C will return undef, +since that's usually an accident on the part of the caller, and +returning the root directory would be a nasty surprise just asking for +trouble a few lines later. + +=item $dir->stringify + +This method is called internally when a C object is +used in a string context, so the following are equivalent: + + $string = $dir->stringify; + $string = "$dir"; + +=item $dir->volume + +Returns the volume (e.g. C on Windows, C on Mac OS, +etc.) of the directory object, if any. Otherwise, returns the empty +string. + +=item $dir->is_dir + +Returns a boolean value indicating whether this object represents a +directory. Not surprisingly, C objects always +return false, and C objects always return true. + +=item $dir->is_absolute + +Returns true or false depending on whether the directory refers to an +absolute path specifier (like C or C<\Windows>). + +=item $dir->is_relative + +Returns true or false depending on whether the directory refers to a +relative path specifier (like C or C<./dir>). + +=item $dir->cleanup + +Performs a logical cleanup of the file path. For instance: + + my $dir = dir('/foo//baz/./foo')->cleanup; + # $dir now represents '/foo/baz/foo'; + +=item $dir->resolve + +Performs a physical cleanup of the file path. For instance: + + my $dir = dir('/foo//baz/../foo')->resolve; + # $dir now represents '/foo/foo', assuming no symlinks + +This actually consults the filesystem to verify the validity of the +path. + +=item $file = $dir->file( , , ..., ) + +Returns a C object representing an entry in C<$dir> +or one of its subdirectories. Internally, this just calls C<< +Path::Class::File->new( @_ ) >>. + +=item $subdir = $dir->subdir( , , ... ) + +Returns a new C object representing a subdirectory +of C<$dir>. + +=item $parent = $dir->parent + +Returns the parent directory of C<$dir>. Note that this is the +I parent, not necessarily the physical parent. It really +means we just chop off entries from the end of the directory list +until we cain't chop no more. If the directory is relative, we start +using the relative forms of parent directories. + +The following code demonstrates the behavior on absolute and relative +directories: + + $dir = dir('/foo/bar'); + for (1..6) { + print "Absolute: $dir\n"; + $dir = $dir->parent; + } + + $dir = dir('foo/bar'); + for (1..6) { + print "Relative: $dir\n"; + $dir = $dir->parent; + } + + ########### Output on Unix ################ + Absolute: /foo/bar + Absolute: /foo + Absolute: / + Absolute: / + Absolute: / + Absolute: / + Relative: foo/bar + Relative: foo + Relative: . + Relative: .. + Relative: ../.. + Relative: ../../.. + +=item @list = $dir->children + +Returns a list of C and/or C +objects listed in this directory, or in scalar context the number of +such objects. Obviously, it is necessary for C<$dir> to +exist and be readable in order to find its children. + +Note that the children are returned as subdirectories of C<$dir>, +i.e. the children of F will be F and F, not +F and F. + +Ordinarily C will not include the I and I +entries C<.> and C<..> (or their equivalents on non-Unix systems), +because that's like I'm-my-own-grandpa business. If you do want all +directory entries including these special ones, pass a true value for +the C parameter: + + @c = $dir->children(); # Just the children + @c = $dir->children(all => 1); # All entries + +In addition, there's a C parameter that will exclude all +normally "hidden" entries - on Unix this means excluding all entries +that begin with a dot (C<.>): + + @c = $dir->children(no_hidden => 1); # Just normally-visible entries + + +=item $abs = $dir->absolute + +Returns a C object representing C<$dir> as an +absolute path. An optional argument, given as either a string or a +C object, specifies the directory to use as the base +of relativity - otherwise the current working directory will be used. + +=item $rel = $dir->relative + +Returns a C object representing C<$dir> as a +relative path. An optional argument, given as either a string or a +C object, specifies the directory to use as the base +of relativity - otherwise the current working directory will be used. + +=item $boolean = $dir->subsumes($other) + +Returns true if this directory spec subsumes the other spec, and false +otherwise. Think of "subsumes" as "contains", but we only look at the +I, not whether C<$dir> actually contains C<$other> on the +filesystem. + +The C<$other> argument may be a C object, a +C object, or a string. In the latter case, we +assume it's a directory. + + # Examples: + dir('foo/bar' )->subsumes(dir('foo/bar/baz')) # True + dir('/foo/bar')->subsumes(dir('/foo/bar/baz')) # True + dir('foo/bar' )->subsumes(dir('bar/baz')) # False + dir('/foo/bar')->subsumes(dir('foo/bar')) # False + + +=item $boolean = $dir->contains($other) + +Returns true if this directory actually contains C<$other> on the +filesystem. C<$other> doesn't have to be a direct child of C<$dir>, +it just has to be subsumed. + +=item $foreign = $dir->as_foreign($type) + +Returns a C object representing C<$dir> as it would +be specified on a system of type C<$type>. Known types include +C, C, C, C, and C, i.e. anything for which +there is a subclass of C. + +Any generated objects (subdirectories, files, parents, etc.) will also +retain this type. + +=item $foreign = Path::Class::Dir->new_foreign($type, @args) + +Returns a C object representing C<$dir> as it would +be specified on a system of type C<$type>. Known types include +C, C, C, C, and C, i.e. anything for which +there is a subclass of C. + +The arguments in C<@args> are the same as they would be specified in +C. + +=item @list = $dir->dir_list([OFFSET, [LENGTH]]) + +Returns the list of strings internally representing this directory +structure. Each successive member of the list is understood to be an +entry in its predecessor's directory list. By contract, C<< +Path::Class->new( $dir->dir_list ) >> should be equivalent to C<$dir>. + +The semantics of this method are similar to Perl's C or +C functions; they return C elements starting at +C. If C is omitted, returns all the elements starting +at C up to the end of the list. If C is negative, +returns the elements from C onward except for C<-LENGTH> +elements at the end. If C is negative, it counts backward +C elements from the end of the list. If C and +C are both omitted, the entire list is returned. + +In a scalar context, C with no arguments returns the +number of entries in the directory list; C returns +the single element at that offset; C returns +the final element that would have been returned in a list context. + +=item $fh = $dir->open() + +Passes C<$dir> to C<< IO::Dir->open >> and returns the result as an +C object. If the opening fails, C is returned and +C<$!> is set. + +=item $dir->mkpath($verbose, $mode) + +Passes all arguments, including C<$dir>, to C<< File::Path::mkpath() +>> and returns the result (a list of all directories created). + +=item $dir->rmtree($verbose, $cautious) + +Passes all arguments, including C<$dir>, to C<< File::Path::rmtree() +>> and returns the result (the number of files successfully deleted). + +=item $dir->remove() + +Removes the directory, which must be empty. Returns a boolean value +indicating whether or not the directory was successfully removed. +This method is mainly provided for consistency with +C's C method. + +=item $dir_or_file = $dir->next() + +A convenient way to iterate through directory contents. The first +time C is called, it will C the directory and read the +first item from it, returning the result as a C or +C object (depending, of course, on its actual +type). Each subsequent call to C will simply iterate over the +directory's contents, until there are no more items in the directory, +and then the undefined value is returned. For example, to iterate +over all the regular files in a directory: + + while (my $file = $dir->next) { + next unless -f $file; + my $fh = $file->open('r') or die "Can't read $file: $!"; + ... + } + +If an error occurs when opening the directory (for instance, it +doesn't exist or isn't readable), C will throw an exception +with the value of C<$!>. + +=item $dir->recurse( callback => sub {...} ) + +Iterates through this directory and all of its children, and all of +its children's children, etc., calling the C subroutine for +each entry. This is a lot like what the C module does, +and of course C will work fine on C objects, +but the advantage of the C method is that it will also feed +your callback routine C objects rather than just pathname +strings. + +The C method requires a C parameter specifying +the subroutine to invoke for each entry. It will be passed the +C object as its first argument. + +C also accepts two boolean parameters, C and +C that control the order of recursion. The default is a +preorder, breadth-first search, i.e. C<< depthfirst => 0, preorder => 1 >>. +At the time of this writing, all combinations of these two parameters +are supported I C<< depthfirst => 0, preorder => 0 >>. + +=item $st = $file->stat() + +Invokes C<< File::stat::stat() >> on this directory and returns a +C object representing the result. + +=item $st = $file->lstat() + +Same as C, but if C<$file> is a symbolic link, C +stats the link instead of the directory the link points to. + +=item $class = $file->file_class() + +Returns the class which should be used to create file objects. + +Generally overridden whenever this class is subclassed. + +=back + +=head1 AUTHOR + +Ken Williams, kwilliams@cpan.org + +=head1 SEE ALSO + +Path::Class, Path::Class::File, File::Spec + +=cut diff --git a/extlib/Path/Class/Entity.pm b/extlib/Path/Class/Entity.pm new file mode 100644 index 000000000..a87d6ad9b --- /dev/null +++ b/extlib/Path/Class/Entity.pm @@ -0,0 +1,86 @@ +package Path::Class::Entity; + +$VERSION = '0.21'; + +use strict; +use File::Spec; +use File::stat (); +use Cwd; + +use overload + ( + q[""] => 'stringify', + 'bool' => 'boolify', + fallback => 1, + ); + +sub new { + my $from = shift; + my ($class, $fs_class) = (ref($from) + ? (ref $from, $from->{file_spec_class}) + : ($from, $Path::Class::Foreign)); + return bless {file_spec_class => $fs_class}, $class; +} + +sub is_dir { 0 } + +sub _spec_class { + my ($class, $type) = @_; + + die "Invalid system type '$type'" unless ($type) = $type =~ /^(\w+)$/; # Untaint + my $spec = "File::Spec::$type"; + eval "require $spec; 1" or die $@; + return $spec; +} + +sub new_foreign { + my ($class, $type) = (shift, shift); + local $Path::Class::Foreign = $class->_spec_class($type); + return $class->new(@_); +} + +sub _spec { (ref($_[0]) && $_[0]->{file_spec_class}) || 'File::Spec' } + +sub boolify { 1 } + +sub is_absolute { + # 5.6.0 has a bug with regexes and stringification that's ticked by + # file_name_is_absolute(). Help it along with an explicit stringify(). + $_[0]->_spec->file_name_is_absolute($_[0]->stringify) +} + +sub is_relative { ! $_[0]->is_absolute } + +sub cleanup { + my $self = shift; + my $cleaned = $self->new( $self->_spec->canonpath($self) ); + %$self = %$cleaned; + return $self; +} + +sub resolve { + my $self = shift; + my $cleaned = $self->new( Cwd::realpath($self->stringify) ); + + # realpath() always returns absolute path, kind of annoying + $cleaned = $cleaned->relative if $self->is_relative; + + %$self = %$cleaned; + return $self; +} + +sub absolute { + my $self = shift; + return $self if $self->is_absolute; + return $self->new($self->_spec->rel2abs($self->stringify, @_)); +} + +sub relative { + my $self = shift; + return $self->new($self->_spec->abs2rel($self->stringify, @_)); +} + +sub stat { File::stat::stat("$_[0]") } +sub lstat { File::stat::lstat("$_[0]") } + +1; diff --git a/extlib/Path/Class/File.pm b/extlib/Path/Class/File.pm new file mode 100644 index 000000000..ee896baae --- /dev/null +++ b/extlib/Path/Class/File.pm @@ -0,0 +1,349 @@ +package Path::Class::File; + +$VERSION = '0.21'; + +use strict; +use Path::Class::Dir; +use base qw(Path::Class::Entity); +use Carp; + +use IO::File (); + +sub new { + my $self = shift->SUPER::new; + my $file = pop(); + my @dirs = @_; + + my ($volume, $dirs, $base) = $self->_spec->splitpath($file); + + if (length $dirs) { + push @dirs, $self->_spec->catpath($volume, $dirs, ''); + } + + $self->{dir} = @dirs ? $self->dir_class->new(@dirs) : undef; + $self->{file} = $base; + + return $self; +} + +sub dir_class { "Path::Class::Dir" } + +sub as_foreign { + my ($self, $type) = @_; + local $Path::Class::Foreign = $self->_spec_class($type); + my $foreign = ref($self)->SUPER::new; + $foreign->{dir} = $self->{dir}->as_foreign($type) if defined $self->{dir}; + $foreign->{file} = $self->{file}; + return $foreign; +} + +sub stringify { + my $self = shift; + return $self->{file} unless defined $self->{dir}; + return $self->_spec->catfile($self->{dir}->stringify, $self->{file}); +} + +sub dir { + my $self = shift; + return $self->{dir} if defined $self->{dir}; + return $self->dir_class->new($self->_spec->curdir); +} +BEGIN { *parent = \&dir; } + +sub volume { + my $self = shift; + return '' unless defined $self->{dir}; + return $self->{dir}->volume; +} + +sub basename { shift->{file} } +sub open { IO::File->new(@_) } + +sub openr { $_[0]->open('r') or croak "Can't read $_[0]: $!" } +sub openw { $_[0]->open('w') or croak "Can't write $_[0]: $!" } + +sub touch { + my $self = shift; + if (-e $self) { + my $now = time(); + utime $now, $now, $self; + } else { + $self->openw; + } +} + +sub slurp { + my ($self, %args) = @_; + my $iomode = $args{iomode} || 'r'; + my $fh = $self->open($iomode) or croak "Can't read $self: $!"; + + if ($args{chomped} or $args{chomp}) { + chomp( my @data = <$fh> ); + return wantarray ? @data : join '', @data; + } + + local $/ unless wantarray; + return <$fh>; +} + +sub remove { + my $file = shift->stringify; + return unlink $file unless -e $file; # Sets $! correctly + 1 while unlink $file; + return not -e $file; +} + +1; +__END__ + +=head1 NAME + +Path::Class::File - Objects representing files + +=head1 SYNOPSIS + + use Path::Class qw(file); # Export a short constructor + + my $file = file('foo', 'bar.txt'); # Path::Class::File object + my $file = Path::Class::File->new('foo', 'bar.txt'); # Same thing + + # Stringifies to 'foo/bar.txt' on Unix, 'foo\bar.txt' on Windows, etc. + print "file: $file\n"; + + if ($file->is_absolute) { ... } + if ($file->is_relative) { ... } + + my $v = $file->volume; # Could be 'C:' on Windows, empty string + # on Unix, 'Macintosh HD:' on Mac OS + + $file->cleanup; # Perform logical cleanup of pathname + $file->resolve; # Perform physical cleanup of pathname + + my $dir = $file->dir; # A Path::Class::Dir object + + my $abs = $file->absolute; # Transform to absolute path + my $rel = $file->relative; # Transform to relative path + +=head1 DESCRIPTION + +The C class contains functionality for manipulating +file names in a cross-platform way. + +=head1 METHODS + +=over 4 + +=item $file = Path::Class::File->new( , , ..., ) + +=item $file = file( , , ..., ) + +Creates a new C object and returns it. The +arguments specify the path to the file. Any volume may also be +specified as the first argument, or as part of the first argument. +You can use platform-neutral syntax: + + my $dir = file( 'foo', 'bar', 'baz.txt' ); + +or platform-native syntax: + + my $dir = dir( 'foo/bar/baz.txt' ); + +or a mixture of the two: + + my $dir = dir( 'foo/bar', 'baz.txt' ); + +All three of the above examples create relative paths. To create an +absolute path, either use the platform native syntax for doing so: + + my $dir = dir( '/var/tmp/foo.txt' ); + +or use an empty string as the first argument: + + my $dir = dir( '', 'var', 'tmp', 'foo.txt' ); + +If the second form seems awkward, that's somewhat intentional - paths +like C or C<\Windows> aren't cross-platform concepts in the +first place, so they probably shouldn't appear in your code if you're +trying to be cross-platform. The first form is perfectly fine, +because paths like this may come from config files, user input, or +whatever. + +=item $file->stringify + +This method is called internally when a C object is +used in a string context, so the following are equivalent: + + $string = $file->stringify; + $string = "$file"; + +=item $file->volume + +Returns the volume (e.g. C on Windows, C on Mac OS, +etc.) of the object, if any. Otherwise, returns the empty string. + +=item $file->basename + +Returns the name of the file as a string, without the directory +portion (if any). + +=item $file->is_dir + +Returns a boolean value indicating whether this object represents a +directory. Not surprisingly, C objects always +return false, and C objects always return true. + +=item $file->is_absolute + +Returns true or false depending on whether the file refers to an +absolute path specifier (like C or C<\Windows\Foo.txt>). + +=item $file->is_relative + +Returns true or false depending on whether the file refers to a +relative path specifier (like C or C<.\Foo.txt>). + +=item $file->cleanup + +Performs a logical cleanup of the file path. For instance: + + my $file = file('/foo//baz/./foo.txt')->cleanup; + # $file now represents '/foo/baz/foo.txt'; + +=item $dir->resolve + +Performs a physical cleanup of the file path. For instance: + + my $dir = dir('/foo/baz/../foo.txt')->resolve; + # $dir now represents '/foo/foo.txt', assuming no symlinks + +This actually consults the filesystem to verify the validity of the +path. + +=item $dir = $file->dir + +Returns a C object representing the directory +containing this file. + +=item $dir = $file->parent + +A synonym for the C method. + +=item $abs = $file->absolute + +Returns a C object representing C<$file> as an +absolute path. An optional argument, given as either a string or a +C object, specifies the directory to use as the base +of relativity - otherwise the current working directory will be used. + +=item $rel = $file->relative + +Returns a C object representing C<$file> as a +relative path. An optional argument, given as either a string or a +C object, specifies the directory to use as the base +of relativity - otherwise the current working directory will be used. + +=item $foreign = $file->as_foreign($type) + +Returns a C object representing C<$file> as it would +be specified on a system of type C<$type>. Known types include +C, C, C, C, and C, i.e. anything for which +there is a subclass of C. + +Any generated objects (subdirectories, files, parents, etc.) will also +retain this type. + +=item $foreign = Path::Class::File->new_foreign($type, @args) + +Returns a C object representing a file as it would +be specified on a system of type C<$type>. Known types include +C, C, C, C, and C, i.e. anything for which +there is a subclass of C. + +The arguments in C<@args> are the same as they would be specified in +C. + +=item $fh = $file->open($mode, $permissions) + +Passes the given arguments, including C<$file>, to C<< IO::File->new >> +(which in turn calls C<< IO::File->open >> and returns the result +as an C object. If the opening +fails, C is returned and C<$!> is set. + +=item $fh = $file->openr() + +A shortcut for + + $fh = $file->open('r') or croak "Can't read $file: $!"; + +=item $fh = $file->openw() + +A shortcut for + + $fh = $file->open('w') or croak "Can't write $file: $!"; + +=item $file->touch + +Sets the modification and access time of the given file to right now, +if the file exists. If it doesn't exist, C will I it +exist, and - YES! - set its modification and access time to now. + +=item $file->slurp() + +In a scalar context, returns the contents of C<$file> in a string. In +a list context, returns the lines of C<$file> (according to how C<$/> +is set) as a list. If the file can't be read, this method will throw +an exception. + +If you want C run on each line of the file, pass a true value +for the C or C parameters: + + my @lines = $file->slurp(chomp => 1); + +You may also use the C parameter to pass in an IO mode to use +when opening the file, usually IO layers (though anything accepted by +the MODE argument of C is accepted here). Just make sure it's +a I mode. + + my @lines = $file->slurp(iomode => ':crlf'); + my $lines = $file->slurp(iomode => '<:encoding(UTF−8)'); + +The default C is C. + +=item $file->remove() + +This method will remove the file in a way that works well on all +platforms, and returns a boolean value indicating whether or not the +file was successfully removed. + +C is better than simply calling Perl's C function, +because on some platforms (notably VMS) you actually may need to call +C several times before all versions of the file are gone - +the C method handles this process for you. + +=item $st = $file->stat() + +Invokes C<< File::stat::stat() >> on this file and returns a +C object representing the result. + +=item $st = $file->lstat() + +Same as C, but if C<$file> is a symbolic link, C +stats the link instead of the file the link points to. + +=item $class = $file->dir_class() + +Returns the class which should be used to create directory objects. + +Generally overridden whenever this class is subclassed. + +=back + +=head1 AUTHOR + +Ken Williams, kwilliams@cpan.org + +=head1 SEE ALSO + +Path::Class, Path::Class::Dir, File::Spec + +=cut diff --git a/extlib/Sub/Install.pm b/extlib/Sub/Install.pm new file mode 100644 index 000000000..d05390d5b --- /dev/null +++ b/extlib/Sub/Install.pm @@ -0,0 +1,329 @@ +package Sub::Install; + +use warnings; +use strict; + +use Carp; +use Scalar::Util (); + +=head1 NAME + +Sub::Install - install subroutines into packages easily + +=head1 VERSION + +version 0.925 + +=cut + +our $VERSION = '0.925'; + +=head1 SYNOPSIS + + use Sub::Install; + + Sub::Install::install_sub({ + code => sub { ... }, + into => $package, + as => $subname + }); + +=head1 DESCRIPTION + +This module makes it easy to install subroutines into packages without the +unslightly mess of C or typeglobs lying about where just anyone can +see them. + +=head1 FUNCTIONS + +=head2 install_sub + + Sub::Install::install_sub({ + code => \&subroutine, + into => "Finance::Shady", + as => 'launder', + }); + +This routine installs a given code reference into a package as a normal +subroutine. The above is equivalent to: + + no strict 'refs'; + *{"Finance::Shady" . '::' . "launder"} = \&subroutine; + +If C is not given, the sub is installed into the calling package. + +If C is not a code reference, it is looked for as an existing sub in the +package named in the C parameter. If C is not given, it will look +in the calling package. + +If C is not given, and if C is a name, C will default to C. +If C is not given, but if C is a code ref, Sub::Install will try to +find the name of the given code ref and use that as C. + +That means that this code: + + Sub::Install::install_sub({ + code => 'twitch', + from => 'Person::InPain', + into => 'Person::Teenager', + as => 'dance', + }); + +is the same as: + + package Person::Teenager; + + Sub::Install::install_sub({ + code => Person::InPain->can('twitch'), + as => 'dance', + }); + +=head2 reinstall_sub + +This routine behaves exactly like C>, but does not emit a +warning if warnings are on and the destination is already defined. + +=cut + +sub _name_of_code { + my ($code) = @_; + require B; + my $name = B::svref_2object($code)->GV->NAME; + return $name unless $name =~ /\A__ANON__/; + return; +} + +# See also Params::Util, to which this code was donated. +sub _CODELIKE { + (Scalar::Util::reftype($_[0])||'') eq 'CODE' + || Scalar::Util::blessed($_[0]) + && (overload::Method($_[0],'&{}') ? $_[0] : undef); +} + +# do the heavy lifting +sub _build_public_installer { + my ($installer) = @_; + + sub { + my ($arg) = @_; + my ($calling_pkg) = caller(0); + + # I'd rather use ||= but I'm whoring for Devel::Cover. + for (qw(into from)) { $arg->{$_} = $calling_pkg unless $arg->{$_} } + + # This is the only absolutely required argument, in many cases. + Carp::croak "named argument 'code' is not optional" unless $arg->{code}; + + if (_CODELIKE($arg->{code})) { + $arg->{as} ||= _name_of_code($arg->{code}); + } else { + Carp::croak + "couldn't find subroutine named $arg->{code} in package $arg->{from}" + unless my $code = $arg->{from}->can($arg->{code}); + + $arg->{as} = $arg->{code} unless $arg->{as}; + $arg->{code} = $code; + } + + Carp::croak "couldn't determine name under which to install subroutine" + unless $arg->{as}; + + $installer->(@$arg{qw(into as code) }); + } +} + +# do the ugly work + +my $_misc_warn_re; +my $_redef_warn_re; +BEGIN { + $_misc_warn_re = qr/ + Prototype\ mismatch:\ sub\ .+? | + Constant subroutine \S+ redefined + /x; + $_redef_warn_re = qr/Subroutine\ \S+\ redefined/x; +} + +my $eow_re; +BEGIN { $eow_re = qr/ at .+? line \d+\.\Z/ }; + +sub _do_with_warn { + my ($arg) = @_; + my $code = delete $arg->{code}; + my $wants_code = sub { + my $code = shift; + sub { + my $warn = $SIG{__WARN__} ? $SIG{__WARN__} : sub { warn @_ }; ## no critic + local $SIG{__WARN__} = sub { + my ($error) = @_; + for (@{ $arg->{suppress} }) { + return if $error =~ $_; + } + for (@{ $arg->{croak} }) { + if (my ($base_error) = $error =~ /\A($_) $eow_re/x) { + Carp::croak $base_error; + } + } + for (@{ $arg->{carp} }) { + if (my ($base_error) = $error =~ /\A($_) $eow_re/x) { + return $warn->(Carp::shortmess $base_error); + } + } + ($arg->{default} || $warn)->($error); + }; + $code->(@_); + }; + }; + return $wants_code->($code) if $code; + return $wants_code; +} + +sub _installer { + sub { + my ($pkg, $name, $code) = @_; + no strict 'refs'; ## no critic ProhibitNoStrict + *{"$pkg\::$name"} = $code; + return $code; + } +} + +BEGIN { + *_ignore_warnings = _do_with_warn({ + carp => [ $_misc_warn_re, $_redef_warn_re ] + }); + + *install_sub = _build_public_installer(_ignore_warnings(_installer)); + + *_carp_warnings = _do_with_warn({ + carp => [ $_misc_warn_re ], + suppress => [ $_redef_warn_re ], + }); + + *reinstall_sub = _build_public_installer(_carp_warnings(_installer)); + + *_install_fatal = _do_with_warn({ + code => _installer, + croak => [ $_redef_warn_re ], + }); +} + +=head2 install_installers + +This routine is provided to allow Sub::Install compatibility with +Sub::Installer. It installs C and C methods into +the package named by its argument. + + Sub::Install::install_installers('Code::Builder'); # just for us, please + Code::Builder->install_sub({ name => $code_ref }); + + Sub::Install::install_installers('UNIVERSAL'); # feeling lucky, punk? + Anything::At::All->install_sub({ name => $code_ref }); + +The installed installers are similar, but not identical, to those provided by +Sub::Installer. They accept a single hash as an argument. The key/value pairs +are used as the C and C parameters to the C routine +detailed above. The package name on which the method is called is used as the +C parameter. + +Unlike Sub::Installer's C will not eval strings into code, but +will look for named code in the calling package. + +=cut + +sub install_installers { + my ($into) = @_; + + for my $method (qw(install_sub reinstall_sub)) { + my $code = sub { + my ($package, $subs) = @_; + my ($caller) = caller(0); + my $return; + for (my ($name, $sub) = %$subs) { + $return = Sub::Install->can($method)->({ + code => $sub, + from => $caller, + into => $package, + as => $name + }); + } + return $return; + }; + install_sub({ code => $code, into => $into, as => $method }); + } +} + +=head1 EXPORTS + +Sub::Install exports C and C only if they are +requested. + +=head2 exporter + +Sub::Install has a never-exported subroutine called C, which is used +to implement its C routine. It takes a hashref of named arguments, +only one of which is currently recognize: C. This must be an arrayref +of subroutines to offer for export. + +This routine is mainly for Sub::Install's own consumption. Instead, consider +L. + +=cut + +sub exporter { + my ($arg) = @_; + + my %is_exported = map { $_ => undef } @{ $arg->{exports} }; + + sub { + my $class = shift; + my $target = caller; + for (@_) { + Carp::croak "'$_' is not exported by $class" if !exists $is_exported{$_}; + install_sub({ code => $_, from => $class, into => $target }); + } + } +} + +BEGIN { *import = exporter({ exports => [ qw(install_sub reinstall_sub) ] }); } + +=head1 SEE ALSO + +=over + +=item L + +This module is (obviously) a reaction to Damian Conway's Sub::Installer, which +does the same thing, but does it by getting its greasy fingers all over +UNIVERSAL. I was really happy about the idea of making the installation of +coderefs less ugly, but I couldn't bring myself to replace the ugliness of +typeglobs and loosened strictures with the ugliness of UNIVERSAL methods. + +=item L + +This is a complete Exporter.pm replacement, built atop Sub::Install. + +=back + +=head1 AUTHOR + +Ricardo Signes, C<< >> + +Several of the tests are adapted from tests that shipped with Damian Conway's +Sub-Installer distribution. + +=head1 BUGS + +Please report any bugs or feature requests through the web interface at +L. I will be notified, and then you'll automatically be +notified of progress on your bug as I make changes. + +=head1 COPYRIGHT + +Copyright 2005-2006 Ricardo Signes, All Rights Reserved. + +This program is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=cut + +1;