File Coverage

blib/lib/later.pm
Criterion Covered Total %
statement 64 68 94.1
branch 17 24 70.8
condition 1 2 50.0
subroutine 12 12 100.0
pod n/a
total 94 106 88.6


line stmt bran cond sub pod time code
1             #
2             # $Id: later.pm,v 1.9 2007-01-25 15:51:10 erwan Exp $
3             #
4             # postpone using a module until it is needed at runtime
5             #
6             # 2007-01-09 erwan First version
7             # 2007-01-22 erwan Support import arguments and object orientation
8             # 2007-01-23 erwan Support recursive 'use later' calls
9             # 2007-01-25 erwan More pod + debug messages
10             # 2014-04-23 aelshesh Patch to support autoload subs
11             #
12              
13             package later;
14              
15 10     10   396757 use strict;
  10         110  
  10         402  
16 10     10   567 use 5.006;
  10         314  
  10         401  
17 9     9   67 use warnings;
  9         18  
  9         311  
18 9     9   13179 use Symbol;
  9         18419  
  9         947  
19 9     9   6760 use Data::Dumper;
  9         61381  
  9         661  
20 9     9   83 use Carp qw(croak);
  9         19  
  9         687  
21              
22             our $VERSION = '0.05';
23              
24 9     9   50 use vars qw($DEBUG);
  9         18  
  9         617  
25             BEGIN {
26 9   50 9   13671 $DEBUG ||= 0;
27             }
28              
29             #--------------------------------------------------------------------
30             #
31             # '%modules' has the following structure:
32             #
33             # # name of the package doing 'use later'
34             # %modules = ( 'caller_package' => {
35             # # module to be imported in the caller_package, and with which import arguments
36             # 'module_1' => [ @import_arguments ],
37             # 'module_2' => [ @import_arguments ],
38             # ...
39             # }
40             #
41              
42             my %modules;
43              
44             #--------------------------------------------------------------------
45             #
46             # import - store the name and import arguments of a module to
47             # import later on in the caller package
48             #
49              
50             sub import {
51 13     13   1292 my $self = shift @_;
52 13         36 my $caller = caller(0);
53 13         124 my $latered = shift @_;
54              
55 13 100       415 croak "'use later' must be followed by a module name and eventual import arguments" if (!defined $latered);
56              
57             # NOTE: should we check that the $latered module exists? -> no: enable on the fly generation of modules...
58             # NOTE: if the caller package or the delayed package define an AUTOLOAD of their own, we will have a conflict...
59              
60             # add an AUTOLOAD to the caller module
61 12 100       54 if (!exists $modules{$caller}) {
62 11         30 $modules{$caller} = {};
63 11 50       41 print "use later: setting AUTOLOAD in package $caller\n" if ($DEBUG);
64 11         19 *{ qualify_to_ref('AUTOLOAD',$caller) } = *{ qualify_to_ref('_autoload','later') };
  11         402  
  11         47  
65             }
66              
67 12         252 $modules{$caller}->{$latered} = \@_;
68              
69             # add an AUTOLOAD even to the postponed module, to handle full name calls (ex: My::Module::foo() )
70 12 50       46 print "use later: setting AUTOLOAD in package $latered\n" if ($DEBUG);
71 12         18 *{ qualify_to_ref('AUTOLOAD',$latered) } = *{ qualify_to_ref('_autoload','later') };
  12         205  
  12         41  
72             }
73              
74             #--------------------------------------------------------------------
75             #
76             # _autoload - act as the caller package's AUTOLOAD. does a number
77             # of 'use $module' and call the undefined sub if it now defined
78             # otherwise die
79             #
80              
81             sub _autoload {
82              
83             # looking up a missing DESTROY sub is ok...
84 13 50   13   7282 return if ($later::AUTOLOAD =~ /::DESTROY$/);
85              
86 13         50 my ($pkg,$filename,$line) = caller;
87              
88             # do a 'use module' on each postponed module inside the caller's package
89 13 50       54 if (exists $modules{$pkg}) {
90 13         22 foreach my $module (keys %{$modules{$pkg}}) {
  13         62  
91 10         19 _use_module($pkg,$module,@{$modules{$pkg}->{$module}});
  10         49  
92 8         72 delete $modules{$pkg}->{$module};
93 8 50       107 if ($module->can('autoload')) {
94 0         0 *{ qualify_to_ref('AUTOLOAD', $module) } = *{ qualify_to_ref('autoload', $module) };
  0         0  
  0         0  
95             }
96            
97             }
98             }
99              
100             # if the originally undefined sub is now available, call it
101 11 100       129 if ($pkg->can($later::AUTOLOAD)) {
102 8         43 goto &$later::AUTOLOAD;
103             }
104            
105             # sub is still undefined. let's die...
106 3         28 die "Undefined subroutine &$later::AUTOLOAD called at $filename line $line\n";
107             }
108              
109             #--------------------------------------------------------------------
110             #
111             # _use_module - use a module with the right parameters
112             #
113              
114             sub _use_module {
115 10     10   31 my ($caller,$module,@args) = @_;
116              
117 10 50       42 print "use later: using module $module in package $caller\n" if ($DEBUG);
118              
119             # the main issue with the delayed 'eval "package; use *"' method is how
120             # to pass import arguments to the used module. I unfortunately didn't
121             # find any better way than converting them to a string with Data::Dumper.
122             # ugly. does not keep coderefs passed as import arguments...
123              
124             # change Data::Dumper indentation temporarily
125 10         26 my $indent = $Data::Dumper::Indent;
126 10         18 $Data::Dumper::Indent = 0;
127              
128 10         22 my $str = "";
129 10 100       43 if (@args) {
130 3         21 $str = Dumper(\@args);
131 3 50       590 $str =~ s/^\$VAR1 = \[(.*)\];[\n\r]*$/$1/mg
132             || croak "use later: Failed to parse import arguments for module $module inside package $caller. Arguments are: $str";
133             }
134 10         17 $Data::Dumper::Indent = $indent;
135              
136             # now use the delayed module
137 10     8   928 eval "package $caller; use $module $str;";
  8         3052  
  6         1215  
  6         209  
  0            
138 10 100       319 if ($@) {
139 2         380 croak "use later: Failed to use package $module inside package $caller.\nEval says: $@";
140             }
141             }
142              
143             1;
144              
145             __END__