#! /bin/false # Copyright (C) 2016-2018 Guido Flohr <guido.flohr@cantanea.com>, # all rights reserved. # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. package Qgoda::PluginUtils; $Qgoda::PluginUtils::VERSION = 'v0.9.3'; use strict; use Locale::TextDomain qw(qgoda); use File::Spec; use JSON qw(decode_json); use Scalar::Util qw(reftype); use Qgoda::Util qw(read_file empty); use base 'Exporter'; use vars qw(@EXPORT_OK); @EXPORT_OK = qw(load_plugins); sub search_local_plugins($$$); sub init_plugin($$$); my %languages = ( Perl => 'Qgoda::Plugger::Perl', Python => 'Qgoda::Plugger::Inline::Python', ); my %types = ( 'TT2::Filter' => 'Qgoda::PluginLoader::TT2::Filter', ); sub load_plugins { my ($q) = @_; my $logger = $q->logger('plugin-loader'); $logger->info(__("initializing plug-ins.")); my $config = $q->config; # Allow loading local Perl plug-ins. push @INC, $config->{srcdir}; my $modules_dir = File::Spec->catfile($config->{srcdir}, 'node_modules'); my %plugins; if (opendir my $dh, 'node_modules') { foreach my $subdir (grep { -e File::Spec->catfile('node_modules', $_, 'package.json') } grep !/^\./, readdir $dh) { my $package_dir = File::Spec->catfile('node_modules', $subdir); my $package_json = File::Spec->catfile($package_dir, 'package.json'); my $package = eval { decode_json read_file $package_json } or next; next if !$package->{qgoda}; $plugins{$subdir} = { package_dir => $package_dir, package_json => $package_json, }; } } foreach my $name (keys %plugins) { $logger->info(__x("plugin '{name}' found in 'node_modules'.", name => $name)); } my $plugin_dir = $config->{paths}->{plugins}; search_local_plugins(\%plugins, $plugin_dir, $logger); while (my ($name, $plugin) = each %plugins) { eval { init_plugin $name, $plugin, $logger; }; if ($@) { $logger->fatal(__x("plugin '{name}': {error}", name => $name, error => $@)); } } return 1; } sub search_local_plugins($$$) { my ($plugins, $plugin_dir, $logger) = @_; return 1 if !-e $plugin_dir; local *DIR; opendir DIR, $plugin_dir or $logger->fatal(__x("cannot open directory '{dir}': {error}!")); my @subdirs = grep {!/^[._]/} readdir DIR; foreach my $name (@subdirs) { $logger->info(__x("plugin '{name}' found in '{directory}'.", name => $name, directory => $plugin_dir)); my $package_dir = File::Spec->catfile($plugin_dir, $name); $plugins->{$name} = { package_dir => $package_dir, package_json => File::Spec->catfile($package_dir, 'package.json'), }; } return 1; } sub init_plugin($$$) { my ($name, $plugin, $logger) = @_; $logger->debug(__x('initializing plugin {name}.', name => $name)); my $package_json = $plugin->{package_json}; my $json = read_file $package_json; $logger->fatal(__x('error reading plugin package file {file}: {error}!', file => $package_json, error => $!)) if !defined $json; my $data = decode_json $json; my $plugin_data = $data->{qgoda}; $logger->fatal(__x("{file}: plugin definition (key 'qgoda') missing!", file => $package_json)) if !defined $plugin_data; $logger->fatal(__x("{file}: value for key 'qgoda' must be a dictionary!", file => $package_json)) if !(ref $plugin_data && 'HASH' eq reftype $plugin_data); $plugin_data->{main} ||= $data->{main}; $logger->fatal(__x("{file}: 'qgoda.main' or 'main' must point to main source file!", file => $package_json)) if (empty $plugin_data->{main} || ref $plugin_data->{main}); $plugin_data->{main} = File::Spec->catfile($plugin->{package_dir}, $plugin_data->{main}); my $language = $plugin_data->{language}; $logger->fatal(__x("{file}: language (qgoda.language) missing!", file => $package_json)) if empty $language; my $plugger_class = $languages{$language}; # TRANSLATORS: Language is a programming language. $logger->fatal(__x("{file}: unsupported language '{language}'!", file => $package_json, language => $language)) if empty $language; my $plugger_module = $plugger_class; $plugger_module =~ s{::}{/}g; $plugger_module .= '.pm'; require $plugger_module; $plugin_data->{plugger} = $plugger_class->new($plugin_data); return 1 if $plugger_class->native; my $type = $plugin_data->{type}; $logger->fatal(__x("{file}: type (qgoda.type) missing!", file => $package_json)) if empty $type; my $factory_class = $types{$type}; # TRANSLATORS: Language is a programming language. $logger->fatal(__x("{file}: unsupported plugin type '{type}'!", file => $package_json, type => $type)) if empty $type; my $factory_module = $factory_class; $factory_module =~ s{::}{/}g; $factory_module .= '.pm'; require $factory_module; my $plugin_loader = $factory_class->new; $plugin_data->{plugin_loader} = $plugin_loader; $plugin_loader->addPlugin($plugin_data); return 1; }