#!perl # # Documentation, copyright and license is at the end of this file. # package File::Package; use 5.001; use strict; use warnings; use warnings::register; use vars qw($VERSION $DATE $FILE); $VERSION = '1.17'; $DATE = '2004/05/11'; $FILE = __FILE__; use File::Spec; use vars qw(@ISA @EXPORT_OK); require Exporter; @ISA= qw(Exporter); @EXPORT_OK = qw(load_package is_package_loaded eval_str); use vars qw(@import); # use SelfLoader; # 1; # __DATA__ ###### # # sub load_package { ###### # This subroutine uses no object data; therefore, # drop any class or object. # shift if UNIVERSAL::isa($_[0],__PACKAGE__); local @import; (my $program_module, @import) = @_; return "# The package name is empty. There is no package to load.\n" unless ($program_module); my $packages = $import[-1] && ref($import[-1]) eq 'ARRAY' ? pop @import : [$program_module]; my $error = ''; my $restore_warn = $SIG{__WARN__}; my $restore_croak = \&Carp::croak; my $restore_carp = \&Carp::crap; unless (File::Package->is_package_loaded( $program_module )) { ##### # Load the module # # On error when evaluating "require $program_module" only the last # line of STDERR, at least on one Perl, is return in $@. # Save the entire STDERR to a memory variable by using eval_str # $error = eval_str ("require $program_module;"); return "Cannot load $program_module\n\t" . $error if $error; ##### # Verify the package vocabulary is present # my @package_names = (); foreach (@$packages) { push @package_names, $_ unless File::Package->is_package_loaded($_, $program_module ); } return "# $program_module file but package(s) " . (join ',',@package_names) . " absent.\n" if @package_names; } #### # Import flagged symbols from load package into current package vocabulary. # if( @import ) { #### # Import does not work correctly when running under eval. Import # uses the caller stack to determine way to stuff the symbols. # The eval messes with the stack. Since not using an eval, need # to double check to make sure import does not die. #### # Poor man's eval where trap off the Carp::croak function. # The Perl authorities have Core::die locked down tight so # it is next to impossible to trap off of Core::die. Lucky # must everyone uses Carp::croak instead of just dieing. # # Anyway, get the benefit of a lot of stack gyrations to # formulate the correct error msg by Exporter::import. # $error = ''; no warnings; *Carp::carp = sub { $error .= (join '', @_); $error .= "\n" unless substr($error,-1,1) eq "\n"; }; *Carp::croak = sub { $error .= Carp::longmess (join '', @_) if $error; $error .= "\n" unless substr($error,-1,1) eq "\n"; goto IMPORT; # once croak can not continue }; use warnings; local $Exporter::ExportLevel = 1; if(@import == 1 && defined $import[0] && $import[0] eq '') { $program_module->import( ); } else { $program_module->import( @import ); } no warnings; IMPORT: *Carp::croak = $restore_croak; *Carp::carp= $restore_carp; } $SIG{__WARN__} = ref( $restore_warn ) ? $restore_warn : ''; return $error; } ##### # Many times, all the warnings do not get into the $@ string # sub eval_str { shift @_ if UNIVERSAL::isa($_[0],__PACKAGE__); my ($str) = @_; my $restore_warn = $SIG{__WARN__}; my $error_msg = ''; $SIG{__WARN__} = sub { $error_msg .= join '', @_; }; eval $str; $SIG{__WARN__} = ref( $restore_warn ) ? $restore_warn : ''; $error_msg = $@ . $error_msg if $@; $error_msg =~ s/\n/\n\t/g if $error_msg; $error_msg; } ###### # # sub is_package_loaded { ###### # This subroutine uses no object data; therefore, # drop any class or object. # shift @_ if UNIVERSAL::isa($_[0],__PACKAGE__); my ($package, $program_module) = @_; my $package_hash = $package . "::"; my $vocabulary = defined %$package_hash; my $version = $package . "::VERSION"; no strict; $version = $$version; use strict; $vocabulary &&= $version && 0 < length($version); $program_module = $package unless $program_module; my $require = File::Spec->catfile( split /::/, $program_module . '.pm'); my $inc = $INC{$require}; #### # Microsoft cannot make up its mind to use # Microsoft \ or Unix / for path separator. # # Just in case, running Microsoft, delete # Unix mirror name for the file # my $OS = $^O; unless ($OS) { # on some perls $^O is not defined require Config; $OS = $Config::Config{'osname'}; } $require =~ s|\\|/|g if $OS eq 'MSWin32';; $inc = $inc || $INC{$require}; ($vocabulary && $inc) ? 1 : ''; } 1 __END__ =head1 NAME File::Package - test load a pm and import symbols without eval and $@ misbehavoirs =head1 SYNOPSIS ########## # Subroutine interface # use File::Package qw(is_package_loaded load_package); $error = eval_str( $str ); $yes = is_package_loaded($package, $program_module); $error = load_package($program_module); $error = load_package($program_module, @import); $error = load_package($program_module, [@package_list]); $error = load_package($program_module, @import, [@package_list]); ########## # Class Interface # use File::Package; $yes = is_package_loaded($package, $program_module); $error = File::Package->load_package($program_module); $error = File::Package->load_package($program_module, @import); $error = File::Package->load_package($program_module, [@package_list]); $error = File::Package->load_package($program_module, @import, [@package_list]); # Note: [@pakage_list] are the same \@package_list to a subroutine =head1 DESCRIPTION In a perfect Perl, everything would behave exactly the same running under C. Many times the reason to use an C is the anticipation that the expression may die. When that happens, a perfect Perl would have deposited all the output from the C and C in C<$@>. Maybe you have a perfect Perl. However, it is shocking that there are some Perls on some platforms out in the wild that are mutants and are not perfect. A C under eval works just fine just to see if a program will load or not. If working locally, you can simply devise a quick debug setup and track down the problem. However, when running tests remotely, on different remote platforms, running continuously unattended where uptime is important, or any number of situations it is very helpful to have meaningful error messages when a problem arise. Thus, the reason to run under C is not only to avoid the C but also to pick up the error message returned by C in C<$@>. In certain situations it is extremely critical to obtain reliable error messages when a failure occurs. Well, a C failure returns a reasonble looking C<$@> except for one small thing. Not all the warnings make it to C<$@> at least on one Perl, probably more. And there can be quite a few warnings when loading a broken program module. It would be nice if everyone could update to a Perl where the C deposits all the warnings in C<$@>. But as the acient proverb says, "If wishes were horses, beggers would ride.". One workaround is to catch the warnings with C<$SIG{__WARN__}> when running the C under a C. This collects all the warnings which is good. Now when a load fails, the program does not die, it gracefully collects all the warnings and logs them or ships back. Now try the C under C and pick up the error messages. The C and C is big time "failure to communicate" aka the movie "Cool Hand Luke". The C uses the caller stack to determine where to stuff the symbols and there is a lot of C C gyrations such as making C look like C, trapping C and C. The C takes off on its own caller stack which to quote President Bush: "is not helpful". The C uses the C instead of C directly or else any efforts to get meaningfull error messages would be dead on arrival. Perl is designed so that it is nearly impossible to avoid a die unless running under a C. A workaround is hooking in a C that does not die and collecting the error messages. =head1 SUBROUTINES =head2 eval_str $error = eval_str( $str ); Runs C<$str> using C, trapping all the warnings from C and returning them as C<$error>. =head2 is_package_loaded $package = is_package_loaded($program_module, $package) The C subroutine determines if the C<$package> is present and the C<$progarm_module> loaded. If C<$package> is absent, 0 or '', C<$package> is set to the C. =head2 load_package $error = load_package($program_module, @import, [@package_list]); The C subroutine attempts to capture any load problems by loading the package with a "require " under an eval and capturing all the "warn" and $@ messages. If the C<$program_module> load is successful, the checks that the packages in the @package list are present. If @package list is absent, the C<$program_module> uses the C name as a list of one package. Although a program module and package have the same name syntax, they are entirely different. A program module is a file. A package is a hash of symbols, a symbol table. The Perl convention is that the names for each are the same which enhances the appearance that they are the same when in fact they are different. Thus, a program module may have a single package with a different name or many different packages. Finally the C<$program_module> subroutine will import the symbols in the C<@import> list. If C<@import> is absent C<$program_module> subroutine does not import any symbols; if C<@import> is '', all symbols are imported. A C<@import> of 0 usually results in an C<$error>. The C<$program_module> traps all load errors and all import C errors and returns them in the C<$error> string. One very useful application of the C subroutine is in test scripts. If a package does load, it is very helpful that the program does not die and reports the reason the package did not load. This information is readily available when loaded at a local site. However, it the load occurs at a remote site and the load crashes Perl, the remote tester usually will not have this information readily available. Other applications include using backup alternative software if a package does not load. For example if the package 'Compress::Zlib' did not load, an attempt may be made to use the gzip system command. =head1 BUGS The C cannot load program modules whose name contain the '-' characters. The 'eval' function used to trap the die errors believes it means subtraction. =head1 REQUIREMENTS Coming. =head1 DEMONSTRATION ######### # perl Package.d ### ~~~~~~ Demonstration overview ~~~~~ Perl code begins with the prompt => The selected results from executing the Perl Code follow on the next lines. For example, => 2 + 2 4 ~~~~~~ The demonstration follows ~~~~~ => use File::Package; => my $uut = 'File::Package'; => ################## => # Good Load => # => ### => my $error = $uut->load_package( 'File::Basename' ) '' => $error = $uut->load_package( '_File_::BadLoad' ) 'Cannot load _File_::BadLoad syntax error at E:/User/SoftwareDiamonds/installation/t/File/_File_/BadLoad.pm line 13, near "$FILE " Global symbol "$FILE" requires explicit package name at E:/User/SoftwareDiamonds/installation/t/File/_File_/BadLoad.pm line 13. Compilation failed in require at (eval 12) line 1. Scalar found where operator expected at E:/User/SoftwareDiamonds/installation/t/File/_File_/BadLoad.pm line 13, near "$FILE" (Missing semicolon on previous line?) ' => $uut->load_package( '_File_::BadPackage' ) '# _File_::BadPackage file but package(s) _File_::BadPackage absent. ' => $uut->load_package( '_File_::Multi' ) '# _File_::Multi file but package(s) _File_::Multi absent. ' => $error = $uut->load_package( '_File_::Hyphen-Test' ) 'Cannot load _File_::Hyphen-Test syntax error at (eval 15) line 1, near "require _File_::Hyphen-" Warning: Use of "require" without parens is ambiguous at (eval 15) line 1. ' => ################## => # No &File::Find::find import baseline => # => ### => !defined($main::{'find'}) '1' => ################## => # Load File::Find, Import &File::Find::find => # => ### => $error = $uut->load_package( 'File::Find', 'find', ['File::Find'] ) '' => ################## => # &File::Find::find imported => # => ### => defined($main::{'find'}) '1' => ################## => # &File::Find::finddepth not imported => # => ### => !defined($main::{'finddepth'}) '1' => ################## => # Import error => # => ### => $uut->load_package( 'File::Find', 'Jolly_Green_Giant') '"Jolly_Green_Giant" is not exported by the File::Find module Can't continue after import errors at D:/Perl/lib/Exporter/Heavy.pm line 127 Exporter::heavy_export('File::Find', 'main', 'Jolly_Green_Giant') called at D:/Perl/lib/Exporter.pm line 45 Exporter::import('File::Find', 'Jolly_Green_Giant') called at (eval 9) line 81 File::Package::load_package('File::Package', 'File::Find', 'Jolly_Green_Giant') called at E:\User\SoftwareDiamonds\installation\t\File\Package.d line 195 ' => ################## => # &File::Find::finddepth still no imported => # => ### => !defined($main::{'finddepth'}) '1' => ################## => # Import all File::Find functions => # => ### => $error = $uut->load_package( 'File::Find', '') '' => ################## => # &File::Find::finddepth imported => # => ### => defined($main::{'finddepth'}) '1' =head1 QUALITY ASSURANCE Running the test script C verifies the requirements for this module. The cover script for L automatically generated the C test script, C demo script, and C STD program module POD, from the C program module contents. The C program module is in the distribution file F. =head1 NOTES =head2 Author The holder of the copyright and maintainer is Esupport@SoftwareDiamonds.comE =head2 Copyright Notice Copyrighted (c) 2002 Software Diamonds All Rights Reserved =head2 Binding Requirements Notice Binding requirements are indexed with the pharse 'shall[dd]' where dd is an unique number for each header section. This conforms to standard federal government practices, L. In accordance with the License, Software Diamonds is not liable for any requirement, binding or otherwise. =head2 License Software Diamonds permits the redistribution and use in source and binary forms, with or without modification, provided that the following conditions are met: =over 4 =item 1 Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. =item 2 Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. =item 3 Commercial installation of the binary or source must visually present to the installer the above copyright notice, this list of conditions intact, that the original source is available at http://softwarediamonds.com and provide means for the installer to actively accept the list of conditions; otherwise, a license fee must be paid to Softwareware Diamonds. =back SOFTWARE DIAMONDS, http://www.softwarediamonds.com, PROVIDES THIS SOFTWARE 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SOFTWARE DIAMONDS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING USE OF THIS SOFTWARE, EVEN IF ADVISED OF NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE POSSIBILITY OF SUCH DAMAGE. =head1 SEE ALSO =over 4 =item L =item L =back =cut ### end of file ###