File Coverage

lib/Badger/Modules.pm
Criterion Covered Total %
statement 102 114 89.4
branch 18 34 52.9
condition 7 11 63.6
subroutine 13 15 86.6
pod 9 9 100.0
total 149 183 81.4


line stmt bran cond sub pod time code
1             #========================================================================
2             #
3             # Badger::Modules
4             #
5             # DESCRIPTION
6             # Module for loading and instantiating other modules.
7             #
8             # NOTE
9             # Badger::Factory is being cleaved in twain. Badger::Modules will
10             # implement the lower level parts related to finding and loading
11             # modules. Badger::Factory will be a subclass specialised for creating
12             # object instances.
13             #
14             # AUTHOR
15             # Andy Wardley
16             #
17             #========================================================================
18              
19             package Badger::Modules;
20              
21 1     1   495 use Carp;
  1         2  
  1         65  
22 1     1   5 use Badger::Debug ':dump';
  1         2  
  1         5  
23             use Badger::Class
24 1         22 version => 0.01,
25             debug => 0,
26             base => 'Badger::Prototype Badger::Exporter',
27             import => 'class',
28             utils => 'plural blessed textlike dotid camel_case',
29             accessors => 'item items',
30             words => 'ITEM ITEMS ISA TOLERANT BADGER_LOADED',
31             constants => 'PKG ARRAY HASH REFS ONCE DEFAULT',
32             constant => {
33             OBJECT => 'object',
34             FOUND => 'found',
35             FOUND_REF => 'found_ref',
36             PATH_SUFFIX => '_PATH',
37             NAMES_SUFFIX => '_NAMES',
38             DEFAULT_SUFFIX => '_DEFAULT',
39             },
40             methods => {
41             init => \&init_modules,
42             throws => \&item,
43             },
44             messages => {
45             no_item => 'No item(s) specified for factory to manage',
46             no_default => 'No default defined for %s factory',
47             bad_ref => 'Invalid reference for %s factory item %s: %s',
48             bad_method => q{Can't locate object method "%s" via package "%s" at %s line %s},
49             failed => q{Error loading %s module %s as %s: %s},
50 1     1   7 };
  1         1  
51              
52             our $ITEM = 'module';
53              
54              
55             sub init_modules {
56 3     3 1 7 my ($self, $config) = @_;
57 3         10 my $class = $self->class;
58 3         7 my ($item, $items);
59              
60 3         3 $self->debug("initialising modules: ", $self->dump_data($config)) if DEBUG;
61              
62             $config->{ tolerant } = $class->any_var(TOLERANT)
63 3 100       14 unless defined $config->{ tolerant };
64              
65             # 'item' and 'items' can be specified as config params or we look for
66             # $ITEM and $ITEMS variables in the current package or those of any
67             # base classes. NOTE: $ITEM and $ITEMS must be in the same package
68 3 100       8 unless ($item = $config->{ item }) {
69 2         5 foreach my $pkg ($class->heritage) {
70 1     1   7 no strict REFS;
  1         2  
  1         40  
71 1     1   6 no warnings ONCE;
  1         1  
  1         833  
72            
73 2 50       4 if (defined ($item = ${ $pkg.PKG.ITEM })) {
  2         4  
74 2         3 $items = ${ $pkg.PKG.ITEMS };
  2         5  
75 2         5 last;
76             }
77             }
78             }
79 3 50       7 return $self->error_msg('no_item')
80             unless $item;
81              
82             # use 'items' in config, or grokked from $ITEMS, or guess plural
83 3   33     18 $items = $config->{ items } || $items || plural($item);
84              
85 3         8 my $ipath = $item.PATH_SUFFIX;
86 3         4 my $inames = $item.NAMES_SUFFIX;
87 3         5 my $idefault = $item.DEFAULT_SUFFIX;
88            
89             # Merge all XXXX_PATH package vars with any 'xxxx_path' or 'path' config
90             # items. Ditto for XXXX_NAME / 'xxxx_name' / 'aka' and XXXXS/ 'xxxxs'
91            
92 3         11 my @path = @$config{ path => lc $ipath };
93 3         8 my @names = @$config{ names => lc $inames };
94 3         9 $self->{ path } = $class->list_vars(uc $ipath, @path);
95 3         11 $self->{ names } = $class->hash_vars(uc $inames, @names);
96 3         13 $self->{ $items } = $class->hash_vars(uc $items, $config->{ $items }); # TODO: this could clash
97 3         9 $self->{ tolerant } = $config->{ tolerant };
98 3         5 $self->{ items } = $items;
99 3         6 $self->{ item } = $item;
100 3         5 $self->{ loaded } = { }; # TODO: make this the same thing?
101              
102             $self->debug(
103             " Item: $self->{ item }\n",
104             "Items: $self->{ items }\n",
105             " Path: ", $ipath, ": ", $self->dump_data($self->{ path }), "\n",
106             "Names: ", $inames, ": ", $self->dump_data($self->{ names })
107 3         4 ) if DEBUG;
108              
109 3         8 return $self;
110             }
111              
112              
113             sub path {
114 0     0 1 0 my $self = shift->prototype;
115             return @_
116             ? ($self->{ path } = ref $_[0] eq ARRAY ? shift : [ @_ ])
117 0 0       0 : $self->{ path };
    0          
118             }
119              
120              
121             sub names {
122 1     1 1 13 my $self = shift->prototype;
123 1         2 my $names = $self->{ names };
124 1 50       4 if (@_) {
125 1 50       6 my $args = ref $_[0] eq HASH ? shift : { @_ };
126 1         7 @$names{ keys %$args } = values %$args;
127             }
128 1         3 return $names;
129             }
130              
131              
132             sub module_names {
133 10     10 1 14 my $self = shift;
134             my @bits =
135 10         21 map { camel_case($_) }
136 10         18 map { split /[\.]+/ } @_;
  10         34  
137 10         14 my %seen;
138              
139             return (
140 20         63 grep { ! $seen{ $_ }++ }
141 10         15 join( PKG, map { ucfirst $_ } @bits ),
  10         27  
142             join( PKG, @bits )
143             );
144             }
145              
146              
147             sub modules {
148 0     0 1 0 my $self = shift->prototype;
149 0         0 my $items = $self->{ $self->{ items } };
150 0 0       0 if (@_) {
151             # NOTE: this doesn't have any effect... it's a artefact from
152             # Badger::Factory... we need to change $self->{ loaded } to be
153             # $self->{ items }
154 0 0       0 my $args = ref $_[0] eq HASH ? shift : { @_ };
155 0         0 @$items{ keys %$args } = values %$args;
156             }
157 0         0 return $items;
158             }
159              
160              
161             sub module {
162 10     10 1 66 my $self = shift->prototype;
163 10         17 my $name = shift;
164 10         14 my $path = $self->{ path };
165 10         13 my $loaded = $self->{ loaded };
166 10         18 my ($module, $base, $alias, $found, $file, $symtab, @names, $size);
167            
168             # Run the name through the name map to handle any unusual capitalisation,
169             # spelling, aliases, etc.
170 10   66     31 $name = $self->{ names }->{ $name } || $name;
171              
172             # Then expand the name using whatever rules are in effect (e.g. the
173             # default which maps foo_bar to FooBar)
174             # FIXME: probably shouldn't do this if we found an entry in the names lookup
175 10         20 @names = $self->module_names($name);
176            
177             LOOKUP:
178 10         31 foreach $base (@$path) {
179 17         25 foreach $alias (@names) {
180 17         30 $module = join(PKG, $base, $alias);
181              
182             # TODO: look in $self->{ $items } for pre-defined result...
183            
184             # See if we've previously loaded a module with this name (true
185             # value) or failed to load a module (defined but false value)
186 17 50       50 if ($found = $loaded->{ $module }) {
    50          
187 0         0 $self->debug("$module has already been loaded") if DEBUG;
188 0         0 return $self->found( $name, $module );
189             }
190             elsif (defined $found) {
191 0         0 $self->debug("$module has previously been requested but not found") if DEBUG;
192 0         0 next;
193             }
194              
195             # Look to see if the module already has a symbol table defined
196 1     1   8 no strict REFS;
  1         2  
  1         463  
197 17         20 $symtab = \%{$module.PKG};
  17         80  
198              
199             # We have to be careful because symbols may be defined in a
200             # package's symbols table *before* the module is loaded (e.g. the
201             # $DEBUG package variable that Badger::Debug uses). So we only
202             # assume that the module is loaded if VERSION or BADGER_LOADED is
203             # defined
204 17 100 66     59 if ($symtab->{ VERSION } || $symtab->{ BADGER_LOADED }) {
205 3         8 $self->debug("found an existing VERSION/BADGER_LOADED in $module symbol table") if DEBUG;
206 3         10 return $self->found( $name, $module );
207             }
208            
209 14         20 $file = $module;
210 14         47 $file =~ s/::/\//g; # TODO: check Perl maps this to OS
211 14         22 $file .= '.pm';
212            
213 14         13 $self->debug("Attempting to load $module as $file") if DEBUG;
214              
215 14         18 eval {
216             # We use eval so that we can "use" the module and force any
217             # import hooks to run. But it might be better to load the
218             # module with "require" and then manually call import()
219 14         3359 require $file;
220             };
221              
222 14 100       386 if ($@) {
223 9         11 $self->debug("Failed to load $module: $@") if DEBUG;
224             # Don't confuse "Can't locate A/Module/Used/In/Your/Module.pm"
225             # messages with "Can't locate Your/Module.pm". The former is
226             # an error that should be reported, the latter isn't. We convert the
227             # class name to a regex that matches any non-word directory
228             # separators, e.g. Your::Module => Your\W+Module
229 9         17 my $qmfile = quotemeta($file);
230 9 100       180 $self->failed($name, $module, $@) if $@ !~ /^Can't locate $qmfile.*? in \@INC/;
231 8         26 next;
232             }
233              
234             # Some filesystems are case-insensitive (like Apple's HFS), so an
235             # attempt to load Badger::Example::foo may succeed, when the
236             # correct package name is actually Badger::Example::Foo. We
237             # double-check by looking to see if anything extra has been added
238             # to the symbol table.
239 5         6 $self->debug("$module symbol table keys: ", join(', ', keys %$symtab)) if DEBUG;
240 5 50       12 next unless %$symtab;
241              
242 5         6 $self->debug("calling $module->import") if DEBUG;
243              
244             # now call the import() method to fire any import actions
245 5         36 $module->import;
246              
247             # add the $BADGER_LOADED package variable to indicate that the
248             # module has been loaded and add an entry to the internal cache
249 5   100     7 ${ $module.PKG.BADGER_LOADED } ||= 1;
  5         25  
250 5         11 $loaded->{ $module } = $module;
251              
252 5         12 return $self->found( $name, $module );
253             }
254             }
255              
256             # add entry to indicate module not found
257 1         4 $loaded->{ $name } = 0;
258            
259 1         3 return $self->not_found($name);
260             }
261              
262              
263             sub found {
264             # my ($self, $name, $module) = @_;
265 8     8 1 33 return $_[2];
266             }
267              
268              
269             sub not_found {
270 1     1 1 3 my $self = shift;
271              
272             return $self->{ tolerant }
273             ? $self->decline_msg( not_found => $self->{ item } => @_ )
274 1 50       20 : $self->error_msg( not_found => $self->{ item } => @_ );
275             }
276              
277              
278             sub failed {
279 1     1 1 3 my $self = shift;
280 1         11 $self->error_msg( failed => $self->{ item }, @_ );
281             }
282              
283            
284              
285              
286             1;
287              
288             __END__