| 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 |  | 604 | use Carp; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 69 |  | 
| 22 | 1 |  |  | 1 |  | 5 | use Badger::Debug ':dump'; | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 6 |  | 
| 23 |  |  |  |  |  |  | use Badger::Class | 
| 24 | 1 |  |  |  |  | 21 | 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 |  | 6 | }; | 
|  | 1 |  |  |  |  | 1 |  | 
| 51 |  |  |  |  |  |  |  | 
| 52 |  |  |  |  |  |  | our $ITEM = 'module'; | 
| 53 |  |  |  |  |  |  |  | 
| 54 |  |  |  |  |  |  |  | 
| 55 |  |  |  |  |  |  | sub init_modules { | 
| 56 | 3 |  |  | 3 | 1 | 7 | my ($self, $config) = @_; | 
| 57 | 3 |  |  |  |  | 8 | my $class = $self->class; | 
| 58 | 3 |  |  |  |  | 5 | my ($item, $items); | 
| 59 |  |  |  |  |  |  |  | 
| 60 | 3 |  |  |  |  | 4 | $self->debug("initialising modules: ", $self->dump_data($config)) if DEBUG; | 
| 61 |  |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  | $config->{ tolerant } = $class->any_var(TOLERANT) | 
| 63 | 3 | 100 |  |  |  | 10 | 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 |  |  |  | 9 | unless ($item = $config->{ item }) { | 
| 69 | 2 |  |  |  |  | 5 | foreach my $pkg ($class->heritage) { | 
| 70 | 1 |  |  | 1 |  | 8 | no strict   REFS; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 46 |  | 
| 71 | 1 |  |  | 1 |  | 7 | no warnings ONCE; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 751 |  | 
| 72 |  |  |  |  |  |  |  | 
| 73 | 2 | 50 |  |  |  | 3 | if (defined ($item = ${ $pkg.PKG.ITEM })) { | 
|  | 2 |  |  |  |  | 5 |  | 
| 74 | 2 |  |  |  |  | 3 | $items = ${ $pkg.PKG.ITEMS }; | 
|  | 2 |  |  |  |  | 4 |  | 
| 75 | 2 |  |  |  |  | 5 | last; | 
| 76 |  |  |  |  |  |  | } | 
| 77 |  |  |  |  |  |  | } | 
| 78 |  |  |  |  |  |  | } | 
| 79 | 3 | 50 |  |  |  | 8 | 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 |  |  | 19 | $items = $config->{ items } || $items || plural($item); | 
| 84 |  |  |  |  |  |  |  | 
| 85 | 3 |  |  |  |  | 7 | my $ipath    = $item.PATH_SUFFIX; | 
| 86 | 3 |  |  |  |  | 6 | my $inames   = $item.NAMES_SUFFIX; | 
| 87 | 3 |  |  |  |  | 7 | 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 |  |  |  |  | 10 | my @path  = @$config{ path  => lc $ipath  }; | 
| 93 | 3 |  |  |  |  | 6 | my @names = @$config{ names => lc $inames }; | 
| 94 | 3 |  |  |  |  | 13 | $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 |  |  |  |  | 4 | $self->{ item     } = $item; | 
| 100 | 3 |  |  |  |  | 7 | $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 | 7 | my $self  = shift->prototype; | 
| 123 | 1 |  |  |  |  | 2 | my $names = $self->{ names }; | 
| 124 | 1 | 50 |  |  |  | 4 | if (@_) { | 
| 125 | 1 | 50 |  |  |  | 5 | my $args = ref $_[0] eq HASH ? shift : { @_ }; | 
| 126 | 1 |  |  |  |  | 7 | @$names{ keys %$args } = values %$args; | 
| 127 |  |  |  |  |  |  | } | 
| 128 | 1 |  |  |  |  | 2 | return $names; | 
| 129 |  |  |  |  |  |  | } | 
| 130 |  |  |  |  |  |  |  | 
| 131 |  |  |  |  |  |  |  | 
| 132 |  |  |  |  |  |  | sub module_names { | 
| 133 | 10 |  |  | 10 | 1 | 12 | my $self = shift; | 
| 134 |  |  |  |  |  |  | my @bits = | 
| 135 | 10 |  |  |  |  | 23 | map { camel_case($_) } | 
| 136 | 10 |  |  |  |  | 14 | map { split /[\.]+/ } @_; | 
|  | 10 |  |  |  |  | 32 |  | 
| 137 | 10 |  |  |  |  | 14 | my %seen; | 
| 138 |  |  |  |  |  |  |  | 
| 139 |  |  |  |  |  |  | return ( | 
| 140 | 20 |  |  |  |  | 57 | grep { ! $seen{ $_ }++ } | 
| 141 | 10 |  |  |  |  | 14 | join( PKG, map { ucfirst $_ } @bits ), | 
|  | 10 |  |  |  |  | 20 |  | 
| 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 | 62 | my $self    = shift->prototype; | 
| 163 | 10 |  |  |  |  | 15 | my $name    = shift; | 
| 164 | 10 |  |  |  |  | 14 | my $path    = $self->{ path    }; | 
| 165 | 10 |  |  |  |  | 12 | my $loaded  = $self->{ loaded }; | 
| 166 | 10 |  |  |  |  | 13 | 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 |  |  | 26 | $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 |  |  |  |  | 32 | @names = $self->module_names($name); | 
| 176 |  |  |  |  |  |  |  | 
| 177 |  |  |  |  |  |  | LOOKUP: | 
| 178 | 10 |  |  |  |  | 16 | foreach $base (@$path) { | 
| 179 | 17 |  |  |  |  | 18 | 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 |  |  |  | 40 | 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 |  | 7 | no strict REFS; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 479 |  | 
| 197 | 17 |  |  |  |  | 17 | $symtab = \%{$module.PKG}; | 
|  | 17 |  |  |  |  | 82 |  | 
| 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 |  |  | 55 | if ($symtab->{ VERSION } || $symtab->{ BADGER_LOADED }) { | 
| 205 | 3 |  |  |  |  | 4 | $self->debug("found an existing VERSION/BADGER_LOADED in $module symbol table") if DEBUG; | 
| 206 | 3 |  |  |  |  | 8 | return $self->found( $name, $module ); | 
| 207 |  |  |  |  |  |  | } | 
| 208 |  |  |  |  |  |  |  | 
| 209 | 14 |  |  |  |  | 16 | $file = $module; | 
| 210 | 14 |  |  |  |  | 39 | $file =~ s/::/\//g;         # TODO: check Perl maps this to OS | 
| 211 | 14 |  |  |  |  | 17 | $file .= '.pm'; | 
| 212 |  |  |  |  |  |  |  | 
| 213 | 14 |  |  |  |  | 13 | $self->debug("Attempting to load $module as $file") if DEBUG; | 
| 214 |  |  |  |  |  |  |  | 
| 215 | 14 |  |  |  |  | 16 | 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 |  |  |  |  | 3196 | require $file; | 
| 220 |  |  |  |  |  |  | }; | 
| 221 |  |  |  |  |  |  |  | 
| 222 | 14 | 100 |  |  |  | 365 | 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 |  |  |  |  | 16 | my $qmfile = quotemeta($file); | 
| 230 | 9 | 100 |  |  |  | 162 | $self->failed($name, $module, $@) if $@ !~ /^Can't locate $qmfile.*? in \@INC/; | 
| 231 | 8 |  |  |  |  | 23 | 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 |  |  |  |  | 7 | $self->debug("$module symbol table keys: ", join(', ', keys %$symtab)) if DEBUG; | 
| 240 | 5 | 50 |  |  |  | 11 | next unless %$symtab; | 
| 241 |  |  |  |  |  |  |  | 
| 242 | 5 |  |  |  |  | 5 | $self->debug("calling $module->import") if DEBUG; | 
| 243 |  |  |  |  |  |  |  | 
| 244 |  |  |  |  |  |  | # now call the import() method to fire any import actions | 
| 245 | 5 |  |  |  |  | 33 | $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 |  |  | 6 | ${ $module.PKG.BADGER_LOADED } ||= 1; | 
|  | 5 |  |  |  |  | 22 |  | 
| 250 | 5 |  |  |  |  | 9 | $loaded->{ $module } = $module; | 
| 251 |  |  |  |  |  |  |  | 
| 252 | 5 |  |  |  |  | 13 | return $self->found( $name, $module ); | 
| 253 |  |  |  |  |  |  | } | 
| 254 |  |  |  |  |  |  | } | 
| 255 |  |  |  |  |  |  |  | 
| 256 |  |  |  |  |  |  | # add entry to indicate module not found | 
| 257 | 1 |  |  |  |  | 3 | $loaded->{ $name } = 0; | 
| 258 |  |  |  |  |  |  |  | 
| 259 | 1 |  |  |  |  | 5 | return $self->not_found($name); | 
| 260 |  |  |  |  |  |  | } | 
| 261 |  |  |  |  |  |  |  | 
| 262 |  |  |  |  |  |  |  | 
| 263 |  |  |  |  |  |  | sub found { | 
| 264 |  |  |  |  |  |  | # my ($self, $name, $module) = @_; | 
| 265 | 8 |  |  | 8 | 1 | 30 | return $_[2]; | 
| 266 |  |  |  |  |  |  | } | 
| 267 |  |  |  |  |  |  |  | 
| 268 |  |  |  |  |  |  |  | 
| 269 |  |  |  |  |  |  | sub not_found { | 
| 270 | 1 |  |  | 1 | 1 | 2 | my $self = shift; | 
| 271 |  |  |  |  |  |  |  | 
| 272 |  |  |  |  |  |  | return $self->{ tolerant } | 
| 273 |  |  |  |  |  |  | ? $self->decline_msg( not_found => $self->{ item } => @_ ) | 
| 274 | 1 | 50 |  |  |  | 19 | : $self->error_msg(   not_found => $self->{ item } => @_ ); | 
| 275 |  |  |  |  |  |  | } | 
| 276 |  |  |  |  |  |  |  | 
| 277 |  |  |  |  |  |  |  | 
| 278 |  |  |  |  |  |  | sub failed { | 
| 279 | 1 |  |  | 1 | 1 | 2 | my $self = shift; | 
| 280 | 1 |  |  |  |  | 11 | $self->error_msg( failed => $self->{ item }, @_ ); | 
| 281 |  |  |  |  |  |  | } | 
| 282 |  |  |  |  |  |  |  | 
| 283 |  |  |  |  |  |  |  | 
| 284 |  |  |  |  |  |  |  | 
| 285 |  |  |  |  |  |  |  | 
| 286 |  |  |  |  |  |  | 1; | 
| 287 |  |  |  |  |  |  |  | 
| 288 |  |  |  |  |  |  | __END__ |