Evented-API-Engine
view release on metacpan or search on metacpan
lib/Evented/API/Engine.pm view on Meta::CPAN
# Copyright (c) 2017, Mitchell Cooper
#
# the Evented API Engine is in charge of loading and unloading modules.
# it also handles dependencies and feature availability.
#
package Evented::API::Engine;
use warnings;
use strict;
use 5.010;
use JSON::XS;
use Scalar::Util qw(weaken blessed);
use Module::Loaded qw(mark_as_loaded mark_as_unloaded is_loaded);
use Evented::Object;
use parent 'Evented::Object';
our $VERSION = '4.13';
use Evented::API::Module;
use Evented::API::Events;
use Evented::Object::Hax qw(set_symbol make_child);
=head1 NAME
B<Evented::API::Engine> - an Evented API Engine for Perl applications.
=head1 SYNOPSIS
Main
my $api = Evented::API::Engine->new;
$api->load_module('My::Module');
My::Module
# Module metadata
#
# @name: 'My::Module'
# @package: 'M::My::Module'
# @description:
#
# @depends.modules+ 'Some::Other'
# @depends.modules+ 'Another::Yet'
#
# @author.name: 'Mitchell Cooper'
# @author.website: 'https://github.com/cooper'
#
package M::My::Module;
use warnings;
use strict;
use 5.010;
# Auto-exported variables
our ($api, $mod);
# Default initializer
sub init {
say 'Loading ', $mod->name;
# indicates load success
return 1;
}
# Default deinitializer
sub void {
say 'Bye!';
# indicates unload success
return 1;
}
lib/Evented/API/Engine.pm view on Meta::CPAN
# find packages
if (!$info->{package}) {
$api->Log($mod_name, "Load FAILED: \@package is required");
return;
}
$info->{package} = [ $info->{package} ]
if ref $info->{package} ne 'ARRAY';
my @pkgs = @{ $info->{package} };
my $pkg = $pkgs[0] or return; # main pkg (module constructor)
# LOAD DEPENDENCIES
#------------------------
# load required module dependencies here.
# consider: if the module fails to load, the dependencies remain loaded.
$api->_load_module_requirements($info) or return;
# CREATE MODULE OBJECT
#------------------------
# make the module package a child of Evented::API::Module
# unless 'no_bless' is true.
my $constructor = 'Evented::API::Module';
unless ($info->{no_bless}) {
make_child($pkg, $constructor);
$pkg->isa($constructor) or return;
$constructor = $pkg;
}
# create the module object.
my $mod = $constructor->new(
%$info,
dir => $mod_dir,
reloading => $reloading
);
# the constructor returned bogus or nothing.
if (!$mod || !$mod->isa('Evented::API::Module')) {
$api->Log($mod_name,
"Load FAILED: Constructor $constructor->new() failed");
_package_unload(@pkgs);
return;
}
# Safe point - the module object is available and safe for use.
# add it to the list of loaded modules.
push @{ $api->{loaded} }, $mod;
# store dependecy module objects.
$mod->{dependencies} = [
map { $api->get_module($_) } # this definitely is an arrayref;
@{ $info->{depends}{modules} } # verified @ ->_load_module_requirements
];
# make the API Engine listen to the events of the module.
# hold a weak reference to the API engine.
$mod->add_listener($api, 'module');
weaken($mod->{api} = $api);
# here we fire an event which will export symbols for convenient use
# within the module packages. see Module.pm for defaults.
$mod->fire(set_variables => $_) for @pkgs;
# EVALUATE
#------------------------
my $return;
$mod->Debug("Evaluating $mod_last_name.pm");
{
# disable warnings on redefinition.
no warnings 'redefine';
# capture other warnings.
local $SIG{__WARN__} = sub {
my $warn = shift;
chomp $warn;
$mod->Log("WARNING: $warn");
};
# do() the file.
$return = do "$mod_dir/$mod_last_name.pm";
}
# an error occurred in loading.
if (!$return || $return != $mod) {
my $error = $@ || $! || 'Package did not return module object';
$mod->Log('Load FAILED: '.$error);
$api->_abort_module_load($mod);
return;
}
# INITIALIZE
#------------------------
# initialize the module. returns false on fail.
$mod->_do_init or return;
# Safe point - the module will certainly remain loaded.
# POST-LOAD
#------------------------
# fire the 'load' event to indicate it has finished loading.
$mod->fire('load');
$mod->Log("Loaded successfully ($$mod{version})");
# mark the packages as loaded.
for my $package (@pkgs) {
mark_as_loaded($package) unless is_loaded($package);
}
# load postponed companion submodules, if any.
$api->_load_companion_submodules($mod);
lib/Evented/API/Engine.pm view on Meta::CPAN
# not blessed, search for module.
if (!blessed $mod) {
$mod = $api->get_module($mod);
if (!$mod) {
$api->Log($_[1], 'Reload: not loaded');
next;
}
}
# unload the module.
$mod->{reloading} = 1;
# ($mod, $unload_dependents, $unload_parent, $unloading_submodule, $reloading)
$api->unload_module($mod, 1, 1, undef, 1) or return;
}
# load all of the modules that were unloaded again
# (if they weren't already loaded, probably as dependencies).
my $unloaded = delete $api->{r_unloaded};
while (my $mod_name = shift @$unloaded) {
next if $api->module_loaded($mod_name);
# ($mod_name, $dirs, $is_submodule, $reloading);
$count++ if $api->load_module($mod_name, undef, undef, 1);
}
return $count;
}
=head2 $api->reload_modules(@mods)
Reloads one or more modules at once. See C<< ->reload_module() >>.
B<Parameters>
=over
=item *
B<@mods> - module objects or names to reload.
=back
B<Returns>
Number of modules reloaded successfully, false if all failed.
=cut
sub reload_modules;
*reload_modules = *reload_module;
############################
### COMPANION SUBMODULES ###
############################
sub _add_companion_submodule_wait {
my ($api, $mod, $mod_name, $submod_name) = @_;
# postpone load until the companion is loaded.
# hold a weak reference to the module waiting.
my $waits = $api->{companion_waits}{$mod_name} ||= [];
my $ref = [ $mod, $submod_name ]; weaken($ref->[0]);
push @$waits, $ref;
# if it is already loaded, go ahead and load the submodule.
if (my $loaded = $api->get_module($mod_name)) {
return $api->_load_companion_submodules($loaded);
}
# false return indicates not yet loaded.
return;
}
sub _load_companion_submodules {
my ($api, $mod) = @_;
my $waits = delete $api->{companion_waits}{ $mod->name } or return;
ref $waits eq 'ARRAY' or return;
my $status;
foreach (@$waits) {
my ($parent_mod, $submod_name) = @$_;
# load the submodule.
$parent_mod->Log("Loading companion submodule");
my $submod = $parent_mod->load_submodule($submod_name) or next;
# remember that this submodule depends on $mod.
push @{ $submod->{companions} ||= [] }, $mod;
}
return $status;
}
########################
### FETCHING MODULES ###
########################
=head2 $api->get_module($mod_name)
Fetches a loaded module object.
B<Parameters>
=over
=item *
B<$mod_name> - name of the module to find.
=back
B<Returns>
L<Module object on success|Evented::API::Module>, false otherwise.
=cut
# returns the module object of a full module name.
sub get_module {
my ($api, $mod_name) = @_;
foreach (@{ $api->{loaded} }) {
( run in 2.952 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )