# PiFlash::Plugin - plugin extension interface for PiFlash
# by Ian Kluft

use strict;
use warnings;
use v5.14.0; # require 2011 or newer version of Perl

package PiFlash::Plugin;
$PiFlash::Plugin::VERSION = '0.4.3';
use autodie; # report errors instead of silently continuing ("die" actions are used as exceptions - caught & reported)
use parent 'PiFlash::Object';
use PiFlash::State;
use Module::Pluggable require => 1, search_path => [__PACKAGE__]; # RPM: perl-Module-Pluggable, DEB: libmodule-pluggable-perl

# ABSTRACT: plugin extension interface for PiFlash


# required parameter list
# class method
# used by PiFlash::Object for new() method
sub object_params
{
	return qw(name class);
}

# initialize enabled plugins
# class method
sub init_plugins
{
	# get list of enabled plugins from command line and config file
	my %enabled;
	if (PiFlash::State::has_cli_opt("plugin")) {
		foreach my $plugin ( split(/[^\w:]+/, PiFlash::State::cli_opt("plugin") // "")) {
			next if $plugin eq "";
			$plugin =~ s/^.*:://;
			$enabled{$plugin} = 1;
		}
	}
	if (PiFlash::State::has_config("plugin")) {
		foreach my $plugin ( split(/[^\w:]+/, PiFlash::State::config("plugin") // "")) {
			next if $plugin eq "";
			$plugin =~ s/^.*:://;
			$enabled{$plugin} = 1;
		}
	}

	# for each enabled plugin, allocate state storage, load its config (if any) and run its init method
	my @plugins_available = PiFlash::Plugin->plugins();
	foreach my $plugin (@plugins_available) {
		# fool function that it was called as class method
		# we don't call the subclass' method until we're sure the class is loaded
		# but we know it will inherit the method function from here
		my $modname = PiFlash::Plugin::get_modname($plugin);

		# check if the module is enabled by user from config or CLI
		if (exists $enabled{$modname}) {
			# load the plugin code if its symbol table doesn't already exist (not already defined by a loaded module)
			(defined(*{$plugin."::"})) or require $plugin;

			# verify it's a subclass of PiFlash::Plugin
			if ($plugin->isa("PiFlash::Plugin")) {
				# skip if its object/storage area exists
				if (PiFlash::State::has_plugin($modname)) {
					next;
				}

				# find any YAML documents addressed to this plugin from the config file
				my @data;
				my $plugin_docs = PiFlash::State::plugin("docs");
				if (exists $plugin_docs->{$modname}) {
					push @data, ("config" => $plugin_docs->{$modname});
				}

				# if the plugin class has an init() method, inherited PiFlash::Object->new() will call it
				PiFlash::State::plugin($modname, $plugin->new({name => $modname, class => $plugin, @data}));
			}
		}
	}
}

# derive module name from class name
# class method
sub get_modname
{
	my $class = shift;
	if ($class =~ /^PiFlash::Plugin::([A-Z]\w+)/) {
		return $1;
	}
	return;
}

# find the data/instance for the plugin
# class method
sub get_data
{
	my $class = shift;
	my $modname = $class->get_modname();
	return PiFlash::State::plugin($modname);
}

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

PiFlash::Plugin - plugin extension interface for PiFlash

=head1 VERSION

version 0.4.3

=head1 SYNOPSIS

 package PiFlash::Plugin::Example;
 use parent 'PiFlash::Plugin';

 # optional init class method - if defined it will be called upon creation of the plugin object
 sub init
 {
	my $self = shift;

	# perform any object initialization actions here
	$self->{data} = "value";

	# example: subscribe to PiFlash::Hook callbacks
	PiFlash::Hook::add("fs_mount", sub { ... code to run on callback ... });
	PiFlash::Hook::add("post_install", \&function_name);
 }

 # get a reference to the plugin's instance variable & data (same as $self in the init function)
 my $data = PiFlash::Plugin::Example->get_data;

=head1 DESCRIPTION

The PiFlash::Plugin module has class methods which manage all the plugins and
instance methods which are the base class inherited by each plugin.  L<PiFlash::Hook>
can be used to receive callback events at various stages of the PiFlash run.

To create a plugin for PiFlash, write a new class under the namespace of PiFlash::Plugin,
such as PiFlash::Plugin::Example.  All PiFlash plugins must be named under and inherit
from PiFlash::Plugin. Otherwise they will not be enabled or accessible.

If the plugin class contains or inherits an init() method, it will be called when the
plugin object is created. You don't need to write a new() routine, and shouldn't, because
PiFlash::Plugin provides one which must be used by all plugins. That will be called by
PiFlash during plugin initialization.

=head1 SEE ALSO

L<piflash>, L<PiFlash::State>, L<PiFlash::Hook>, 

=head1 AUTHOR

Ian Kluft <cpan-dev@iankluft.com>

=head1 COPYRIGHT AND LICENSE

This software is Copyright (c) 2017-2019 by Ian Kluft.

This is free software, licensed under:

  The Apache License, Version 2.0, January 2004

=cut