Badger::Modules(3pm)
Badger::Modules(3pm) β User Contributed Perl Documentation, but make it a whole vibe π
okay so this is the manual page for Badger::Modules(3pm), and it's literally a module for loading modules β¨
πΈ NAME πΈ
Badger::Modules β a module for loading modules π«Ά
π¦ SYNOPSIS π¦
bestie, here's the tea. Four examples, except two of them are both called Example 4. Not a typo, that's just how it is π
Example 1 - using a Badger::Modules object
so you spin up a Badger::Modules object with a path that's an array ref of prefixes like 'My::Example' and 'Your::Example'. Then you ask for a module by its short name, and it loads either My::Example::Foo or Your::Example::Foo β exactly as the comment says. module() returns the module (class) name that was loaded, so then you can call new on it. Iconic.
use Badger::Modules;
my $modules = Badger::Modules->new(
path => ['My::Example','Your::Example'],
);
# load either My::Example::Foo or Your::Example::Foo
my $foo_module = $modules->module('foo');
# module() returns the module (class) name that was loaded
my $foo_object = $foo_module->new;
Example 2 - creating a Badger::Modules subclass
want your own project-specific module loader? just use base 'Badger::Modules', set our $PATH to a string like 'My::Project' or a list reference, and end with 1; because Perl says so. Periodt.
package My::Project::Modules;
use base 'Badger::Modules';
our $PATH = 'My::Project'; # or a list reference
1;
Example 3 - using the Badger::Modules subclass
now you can either create an object and call module('foo') on it, or call module('foo') as a class method. Both versions can die $modules->reason if something goes wrong. Slay.
use My::Project::Modules;
# either by creating an object...
my $modules = My::Project::Modules->new;
my $module = $modules->module('foo')
|| die $modules->reason;
# ...or by calling class methods
my $module = My::Project::Modules->module('foo')
|| die $modules->reason;
Example 4 - pre-loading modules (TODO)
this one is giving not yet, bestie because the comment literally says # This doesn't work yet. It's a TODO, so don't come for me.
# This doesn't work yet
use My::Project::Module
preload => 'foo bar baz';
Example 4 - pre-loading modules and exporting constants (MAYBE)
and here's the other Example 4, the MAYBE one. The comment says # Not sure about this. so it's a vibe check in progress. It shows use My::Project::Module with modules => 'foo bar baz'; and then my $object = FOO_MODULE->new; β cute.
# Not sure about this.
use My::Project::Module
modules => 'foo bar baz';
my $object = FOO_MODULE->new;
β¨ DESCRIPTION β¨
okay bestie, gather round π Badger::Modules is the module that loads other modules for you β specifically modules that live under a common namespace, or honestly, under several namespaces, because we contain multitudes. π¦
It is literally perfect for loading plugin extension modules into an application when you don't know in advance which modules might be required. The iconic example is Badger::Codecs, which loads Badger::Codec modules for encoding and decoding data in various formats. β¨
And ngl? It's also useful even when you do know which modules you want to use β or when you think you do π. Delegating the task of loading modules to one central Badger::Modules object means you can change which modules get loaded later, simply by changing the configuration for the Badger::Modules object. no code surgery required, just config, periodt. π
Badger::Modules can be used as a stand-alone module, or as a base class for creating your own specialised sub-classes. A subclass of Badger::Modules can pre-define default values for configuration options β like, you might want a Your::App::Plugins module that's already configured to load modules under the Your::App::Plugin namespace by default. so convenient. π
Subclasses can also override methods to change the way it works, or to affect what happens after a module gets loaded. Badger::Factory is an example of exactly that kind of module: it provides additional methods for dynamically creating objects and relies on the underlying functionality provided by Badger::Modules to make sure the relevant modules actually get loaded. teamwork, fr fr. π«Ά
π What's the Problem? π
Consider this little code fragment, showing a subroutine that creates and uses a Your::App::Widget object:
use Your::App::Widget;
sub some_code {
my $widget = Your::App::Widget->new;
$widget->do_something;
}
One of the benefits of object oriented programming is that objects of equivalent types are interchangeable. Translation: we should be able to swap out the Your::App::Widget object for a different implementation, as long as it has the same interface in terms of the methods it implements. In strictly typed programming languages that equivalence is enforced rigidly β both objects have to share a common base class, expose the same interface, implement a particular role, or whatever other mechanism the language dreamed up. In loosely typed languages like Perl, we have to rely on duck typing: if it looks like a duck, floats like a duck and quacks like a duck then it is a duck (or is close enough to being a duck for practical purposes). π¦
So for example, we might want to use a dummy widget object for test purposes:
use Your::App::MockObject::Widget;
sub some_code {
my $widget = Your::App::MockObject::Widget->new;
$widget->do_something;
}
Or perhaps use a C implementation of a module on platforms that support it:
use Your::App::XS::Widget;
sub some_code {
my $widget = Your::App::XS::Widget->new;
$widget->do_something;
}
Or maybe an implementation with additional debugging facilities for use during development, but not in production code:
use Your::App::Developer::Widget;
sub some_code {
my $widget = Your::App::Developer::Widget->new;
$widget->do_something;
}
And by now the problem should be glaring at you like an unread notification. π To use a different implementation of the widget object, we have to go and manually change the code. Every single occurrence of Your::App::Widget in every module of your application has to be changed to the new module name. e v e r y o n e.
Of course, if you were doing this in real life you'd probably end up defining a variable to store the name of the relevant class. Something like this, perhaps:
use Your::App::Widget;
our $WIDGET_CLASS = 'Your::App::Widget';
sub some_code {
my $widget = $WIDGET_CLASS->new;
$widget->do_something;
}
This works well in simple cases. But if you've designed your application to be suitably modular (thereby promoting reusability of the individual components and extensibility of the system as a whole β look at you, architect π) then you may have a whole bunch of different modules to load, all of which need similar variables:
use Your::App::Widget;
use Your::App::Doodah;
use Your::App::Thingy;
our $WIDGET_CLASS = 'Your::App::Widget';
our $DOODAH_CLASS = 'Your::App::Doodah';
our $THINGY_CLASS = 'Your::App::Thingy';
Not only is the repetition of Your::App in the above code a red flag for refactoring in itself, but we also have to consider the issue of sharing these variables among the various modules that might need access to them. π©
But before we fall too deep into that rabbit hole, let's jump through the looking glass and see how Badger::Modules can be used to tackle the problem. πβ¨
π¦ Using Badger::Modules π¦
Badger::Modules can be used as a stand-alone module for loading other modules in a particular namespace. This example creates a Badger::Modules object for loading modules under the Your::App namespace:
use Badger::Modules;
my $modules = Badger::Modules->new(
path => 'Your::App'
);
Here we've created a Badger::Modules object which loads modules under the Your::App namespace. And to create a Your::App::Widget object, we can now write:
my $wclass = $modules->module('Widget');
my $widget = $wclass->new;
The module() method maps the argument passed to a full class name (Your::App::Widget), loads the module if it hasn't already been loaded, and then returns the class name. Obsessed. π
Of course, we could also combine that into a single expression, because we love a one-liner:
my $widget = $modules->module('Widget')->new;
The path configuration option can be specified as a reference to a list of namespaces. Badger::Modules will try each one in turn until it finds a matching module:
my $modules = Badger::Modules->new(
path => ['My::App','Your::App'],
);
Now when you request the Widget module you'll get My::App::Widget returned if it exists, or Your::App::Widget if it doesn't. first come, first served, bestie. π
If neither is available then an error will be thrown as a Badger::Exception object containing an error message of the format module not found: Widget. You can set the item configuration option to something other than module to change that message. For example, setting the item to plugin will generate a plugin not found: Widget message:
my $modules = Badger::Modules->new(
item => 'plugin',
path => ['My::App::Plugin','Your::App::Plugin'],
);
If you would rather have the module() method return undef to indicate that a module can't be found, then set the tolerant configuration option to any true value:
my $modules = Badger::Modules->new(
path => ['My::App','Your::App'],
tolerant => 1,
);
It's then up to you to check the return value and handle the case where it's undefined. The error() method (inherited from the Badger::Base base class) can be used to return an error message for the purposes of friendly error reporting:
my $module = $modules->module('Widget')
|| die $modules->error; # module not found: Widget
β οΈ heads up though: any other errors encountered while loading a module will be reported usingcroak, regardless of thetolerantoption. These usually indicate syntax errors requiring immediate attention and thereby warrant the full backtrace thatcroakprovides. no hiding those ones, sorry bestie. π
π§ Mapping Names π§
[ROUGH DRAFT]
Name is tried as-is first:
Your::App + Widget = Your::App::Widget
Then we try camel casing it:
Your::App + nice_widget = Your::App::NiceWidget
This allows us to specify names in lower case with underscores separating words, and have them automatically mapped to the correct CamelCase representation for module names. β¨
Lower case + underscores not only looks nicer (IMHO, YMMV) but can also help to eliminate problems on filesystems like HFS that are case insensitive by default. If you're relying on the difference between, say, CGI and cgi in a module name then you're going to have a world of pain the first time you (or someone else) tries to use that code on a shiny new Mac. And yes, that's me speaking from personal experience :-) π
You may think this is a brain-dead stupid thing to do. You may be right. But there are brain-dead stupid filesystems out there that we have to accommodate. π
π Defining a Badger::Modules Subclass π
Badger::Modules can be used as a base class for your own module-loading modules. Here's a complete example:
package My::App::Plugins;
use base 'Badger::Modules';
our $PATH = ['My::App::Plugin', 'Your::App::Plugin'];
1;
The $PATH package variable can be defined to provide the default search path. The $ITEM, $ITEMS, $NAMES and $TOLERANT package variables (not shown) can also be used to set the default values for the corresponding configuration options. π
You can then use your subclass like this:
use My::App::Plugins;
my $plugins = My::App::Plugins->new;
my $plugin = $plugins->module('example');
This will load either My::App::Plugin::Example or Your::App::Plugin::Example, or throw an error to report that the example module can't be loaded.
You can provide additional configuration options when you create your subclass object. Any path elements specified will be searched after those defined in the $PATH package variable:
use My::App::Plugins;
my $plugins = My::App::Plugins->new(
path => 'Our::App::Plugins',
);
my $plugin = $plugins->module('example');
This will load My::App::Plugin::Example, Your::App::Plugin::Example, Our::App::Plugin::Example or throw an error. slay. π
π Using Badger::Modules as a Singleton π
You can call the module() method as a class method against Badger::Modules or any subclass of it:
use My::App::Plugins;
my $plugin = My::App::Plugins->module('example');
In this case the module() method fetches a singleton prototype object to use (creating it via a call to new(), if necessary). The same prototype object will be re-used for any subsequent class methods. one object, endless re-use, main character energy fr fr πβ¨
π CONFIGURATION OPTIONS π
πΈ path / module_path
okay so this one lets you tell the thing where to go looking for modules β one or more base namespaces, bestie. And if you've got multiple, you pass them as a reference to an array. it's giving "here's my list, go find them".
# single path location
my $modules = Badger::Modules->new(
path => 'My::Modules',
);
# multiple path locations
my $modules = Badger::Modules->new(
path => ['My::Modules', 'Your::Modules'],
);
module_path is literally just an alias for path. same vibe, different name. no cap.
my $modules = Badger::Modules->new(
module_path => ['My::Modules', 'Your::Modules'],
);
and here's the cute part: if you've set the item config option, then the name of the module_path option changes to match it. slay.
# setting item to 'wibble' provides 'wibble_path' as alias to 'path'
my $modules = Badger::Modules->new(
item => 'wibble',
wibble_path => ['My::Modules', 'Your::Modules'],
);
an $ITEM package variable defined in a subclass module does the exact same thing, bestie:
package My::App::Plugins;
use base 'Badger::Modules';
our $ITEM = 'plugin';
1;
package main;
use My::App::Plugins;
my $plugins = My::App::Plugins->new(
plugin_path => ['My::Plugin', 'Your::Plugin'],
);
π item
this option lets you rename the things the module loads. the default is the generic term module. you might wanna change it so your config options, error messages etc. actually sound like they belong to your app. main character energy for your naming, honestly.
my $modules = Badger::Modules->new(
item => 'wibble',
wibble_path => ['My::Modules', 'Your::Modules'],
);
a default value can come from an $ITEM package variable in a subclass of Badger::Modules.
package My::App::Plugins;
use base 'Badger::Modules';
our $ITEM = 'plugin';
1;
another side effect of setting item: it lets you specify the path option with the item name as a prefix. π¦
my $modules = Badger::Modules->new(
item => 'plugin',
plugin_path => ['My::App::Plugin', 'Your::App::Plugin'],
);
and that's highkey useful when you've got several different module loaders hanging out in one app and you want to avoid confusion between all the different path config options. periodt.
β¨ items
this is where you specify the correct plural form of the item name, for those cases where the singular doesn't pluralise regularly β and "regularly" here means "something the plural() function can handle". π
my $modules = Badger::Modules->new(
# highly contrived example
item => 'attorney_general',
items => 'attorneys_general',
);
a default value can come from an $ITEMS package variable in a subclass of Badger::Modules.
package My::App::Plugins;
use base 'Badger::Modules';
our $ITEM = 'attorney_general';
our $ITEMS = 'attorneys_general';
1;
heads up though: this isn't used in the base class at all β but some subclasses rely on it to generate useful error messages. iykyk.
π names
so this one gives you an explicit mapping for module names that might get requested via the module() method. the default behaviour is to camel case module names separated by underscores. like, if you ask for a foo_bar module, it'll look for a FooBar module in any of the path locations.
that works well enough for most modules, but some of them just do not capitalise consistently. modules with acronyms like URL in their names are typically prone to a dose of fail. π
$module = $modules->module('url'); # looks for XXX::Url not XXX::URL
if you specify the name with the correct capitalisation then you'll have zero problems, bestie:
$module = $modules->module('URL');
and if, like me, you prefer to be case-insensitive throughout and just let the module loader worry about the correct capitalisation, then names is your friend. you can use it to define any number of simple aliases for the module() method to use. π«Ά
$modules = Badger::Modules->new(
path => ['My::Plugin', 'Your::Plugin'],
names => {
url => 'URL',
cgi => 'CGI',
foo => 'iFoo::XS'
}
);
note that the values you specify in thenameshash array are partial module names. they still get applied to the base paths specified in thepathoption to generate complete candidate module paths. so don't go putting the whole path in there, okay?
π tolerant
this option decides what happens when a module requested via the module() method can't be found. in the usual case, tolerant is not set, and module() will throw a module not found: XXX error. but if tolerant is set, the method will instead return undef. no drama, no throw. β¨
π METHODS π
πΈ new()
constructor method to create a new Badger::Modules object. inherited from the Badger::Base base class. classic.
π¦ module($name)
method to load a module identified by $name.
[ROUGH DRAFT]
- name is aliased via names lookup table
- name is expanded to various possible capitalisations
- each base namespace in path is tried...
- with each name...
- until one is located and loaded, in which case
found()is called (orfailed()if an error occurred while loading the module) - or we exhaust all possibilities, in which case
not_found()is called.
π modules()
this method can be used to get or set the internal mapping of names to modules. it's not used at present... there's some more refactoring to be done with Badger::Factory to sort out how this is gonna work. so like, don't get attached. π
π path()
method to get or set the module search path. called with no arguments, it returns a reference to a list of the current search path namespaces.
my $path = $modules->path;
you can call it with arguments to set a new search path. one or more module namespaces can be specified as arguments:
$modules->path('My::App', 'Your::App');
these can also be specified as a reference to an array. either way, bestie:
# either
$modules->path(['My::App', 'Your::App']);
# or
@namespaces = ('My::App', 'Your::App');
$modules->path(\@namespaces);
β names()
(yes, this is just... the heading. iconic of it.)
π§ INTERNAL METHODS π§
π init_modules($config)
internal initialisation method used to prepare newly created Badger::Modules objects. the init() method is an alias to init_modules(), so that the default new() method inherited from the Badger::Base base class has something to call.
if you subclass the Badger::Modules module and define your own init() method, then it should call the init_modules() to do the base class initialisation β either before, after, or in between blocks of your own code. your call, your vibes.
package Your::Modules;
use base 'Badger::Modules';
sub init {
my ($self, $config) = @_;
# your code here
$self->init_modules($config);
# more of your code here
return $self;
}
this has the same effect as calling $self->SUPER::init($config), but with less ambiguity in the face of multiple inheritance (usually considered a bad thing to be avoided wherever possible) or in obscure cases where you're monkeying around with the heritage (i.e. base classes) of a module and Perl can't reliably resolve the correct init() method at compile time. so like... it's the safer bestie. π«Ά
π module_names($name)
this method maps the name passed as an argument to the correct case (or variations of case) for Perl modules.
TODO: More on this. Method should possibly be renamed to expand_names()?
π¦ found($name,$module)
this method is called by the module() method when a requested module is found. the implementation in the base class simply returns the module name passed to it as the second argument. this becomes the return value for the successful invocation of the module() method.
subclasses may redefine this method to perform some other functionality. go off. π
πΈ not_found($name)
this method is called by the module() method when a requested module cannot be found. the default behaviour for this implementation in the base class throws an error (via the error method inherited from the Badger::Base base class). if the tolerant configuration option is set to a true value, then it instead returns undef by calling the decline() method, also inherited from Badger::Base.
subclasses may redefine this method to perform some other functionality.
π failed($message)
this method is used internally to report a failure to load a module. that's it. that's the whole job. π
π AUTHOR π
Andy Wardley <http://wardley.org/>
π COPYRIGHT π
Copyright (C) 2006-2010 Andy Wardley. All Rights Reserved.
This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
π₯ SEE ALSO π₯
- Badger::Factory
okay bestie that's literally the whole page, we did it, we ATE π β¨ this was Badger::Modules(3pm) from perl v5.36.0, dated 2023-08-28 β go forth and load your modules, case-insensitively, tolerantly, and with immaculate vibes π¦πΈπ«Ά
π¦ where this page comes from
- debian trixie: libbadger-perl 0.16-3 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu noble: libbadger-perl 0.16-3 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu resolute: libbadger-perl 0.16-3 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms