Skip to content

Commit

Permalink
Item14152: Implemented registring custom tags by extensions.
Browse files Browse the repository at this point in the history
Tags are registered with tagHandler keyword. See test_tag_handlers in
ExtensionsTests.

- Added extObject() method on Foswiki::Extensions. Returns currently
actibe object handling a named extension.

- Added extName() method to find extension's self name defined in
extension's module global variable $NAME.
  • Loading branch information
vrurg committed Sep 16, 2016
1 parent 2664046 commit 9f66599
Show file tree
Hide file tree
Showing 7 changed files with 211 additions and 26 deletions.
95 changes: 95 additions & 0 deletions UnitTestContrib/test/unit/ExtensionsTests.pm
Expand Up @@ -425,6 +425,101 @@ CFG_EXT
);
}

sub test_tag_handlers {
my $this = shift;

# Testing three macros registered by the same extension using three
# different approaches:
# 1. A macro to be handled by an extension method with the same name as
# macro's.
# 2. Similar to above but method code is declared as tagHandler's second
# parameter.
# 3. A macro class with Foswiki::Macro role.
# All three are refering the same extension object and it's autoincementing
# counter attribute. But as long as the first two approaches are extension's
# methods and rely upon valid $this parameter the class is obtaining
# extension's object by requesting application's extensions attribute.

my ($ext) = $this->_genExtModules( 1, <<'TAGH');
has counter => (
is => 'rw',
lazy => 1,
default => 0,
);
around counter => sub {
my $orig = shift;
my $this = shift;
my $val = $orig->($this);
$orig->( $this, $val + 1 );
return sprintf( '%03d', $val );
};
tagHandler 'TEST_EXT_MACRO1';
tagHandler TEST_EXT_MACRO2 => sub {
my $this = shift;
return 'TEST_EXT_MACRO2_' . $this->counter;
};
tagHandler TEST_CLASS_MACRO => 'Foswiki::Macros::TEST_CLASS_MACRO';
sub TEST_EXT_MACRO1 {
my $this = shift;
return "TEST_EXT_MACRO1_" . $this->counter;
}
TAGH

my $macroPackage = <<MPKG;
package Foswiki::Macros::TEST_CLASS_MACRO;
use Foswiki::Class qw(app);
extends qw(Foswiki::Object);
with qw(Foswiki::Macro);
sub expand {
my \$this = shift;
my \$myExt = \$this->app->extensions->extObject('$ext');
return "TEST_CLASS_MACRO_" . \$myExt->counter;
}
1;
MPKG

if ( !eval($macroPackage) || $@ ) {
Foswiki::Exception::Fatal->throw(
text => "Failed to compile macro class: " . $@ );
}

$this->reCreateFoswikiApp;

$this->test_topicObject->text('%TEST_EXT_MACRO1%');

$this->assert_str_equals( 'TEST_EXT_MACRO1_000',
$this->test_topicObject->expandMacros('%TEST_EXT_MACRO1%') );
$this->assert_str_equals( 'TEST_EXT_MACRO1_001',
$this->test_topicObject->expandMacros('%TEST_EXT_MACRO1%') );
$this->assert_str_equals( 'TEST_CLASS_MACRO_002',
$this->test_topicObject->expandMacros('%TEST_CLASS_MACRO%') );
$this->assert_str_equals( 'TEST_EXT_MACRO2_003',
$this->test_topicObject->expandMacros('%TEST_EXT_MACRO2%') );
}

sub test_extName_method {
my $this = shift;

my ($ext) = $this->_genExtModules( 1, <<'SNEXT');
our $NAME = "AutoGenExt";
SNEXT

$this->reCreateFoswikiApp;

$this->assert_str_equals( 'AutoGenExt',
$this->app->extensions->extName($ext) );
}

1;
__END__
Foswiki - The Free and Open Source Wiki, http://foswiki.org/
Expand Down
2 changes: 2 additions & 0 deletions core/lib/Foswiki/App.pm
Expand Up @@ -402,6 +402,7 @@ sub BUILD {
}
else {
my $plogin = $this->plugins->load;
$this->extensions->initialize;
$this->engine->user($plogin) if $plogin;
}

Expand Down Expand Up @@ -1192,6 +1193,7 @@ sub systemMessage {
my $this = shift;
if (@_) {
push @{ $this->system_messages }, @_;
return;
}

# SMELL Something better than %BR% shall be used here.
Expand Down
21 changes: 21 additions & 0 deletions core/lib/Foswiki/Class.pm
Expand Up @@ -239,6 +239,26 @@ sub _handler_extBefore (@) {
Foswiki::Extensions::registerDeps( $_, $target ) foreach @_;
}

sub _handler_tagHandler ($;$) {
my $target = caller;

# Handler could be a class name doing Foswiki::Macro role or a sub to be
# installed as target's hadnling method.
my ( $tagName, $tagHandler ) = @_;

if ( ref($tagHandler) eq 'CODE' ) {

# If second argument is a code ref then we install method with the same
# name as macro name.
_inject_code( $target, $tagName, $tagHandler );
Foswiki::Extensions::registerExtTagHandler( $target, $tagName );
}
else {
Foswiki::Extensions::registerExtTagHandler( $target, $tagName,
$tagHandler );
}
}

sub _install_extension {
my ( $class, $target ) = @_;

Expand All @@ -248,6 +268,7 @@ sub _install_extension {
_inject_code( $target, 'extClass', \&_handler_extClass );
_inject_code( $target, 'extAfter', \&_handler_extAfter );
_inject_code( $target, 'extBefore', \&_handler_extBefore );
_inject_code( $target, 'tagHandler', \&_handler_tagHandler );
}

sub _handler_pluggable ($&) {
Expand Down
54 changes: 48 additions & 6 deletions core/lib/Foswiki/Extensions.pm
Expand Up @@ -39,6 +39,7 @@ our @extModules
our %registeredModules; # Modules registered with registerExtModule().
our %extSubClasses; # Subclasses registered by extensions.
our %extDeps; # Module dependecies. Influences the order of extension objects.
our %extTags; # Tags registered by extensions.
our %pluggables; # Pluggable methods
our %plugMethods; # Extension registered plug methods.

Expand Down Expand Up @@ -142,8 +143,6 @@ sub BUILD {
my $this = shift;

$this->loadExtensions;

$this->initializeExtensions;
}

sub normalizeExtName {
Expand All @@ -159,17 +158,29 @@ sub normalizeExtName {

sub extEnabled {
my $this = shift;
my ($extName) = @_;
my ($ext) = @_;

$extName = $this->normalizeExtName($extName);
my $extName = $this->normalizeExtName($ext);

return defined $this->disabledExtensions->{$extName} ? undef : $extName;
}

sub extObject {
my $this = shift;
my ($ext) = @_;

my $extName = $this->normalizeExtName($ext);

return $this->extensions->{$extName};
}

sub isBadVersion {
my $this = shift;
my ($extName) = @_;

return "Extension module $extName not a subclass of Foswiki::Extension"
unless $extName->isa('Foswiki::Extension');

my @apiScalar = grep { /::API_VERSION$/ } Devel::Symdump->scalars($extName);

return "No \$API_VERSION scalar defined in $extName"
Expand Down Expand Up @@ -267,8 +278,16 @@ sub loadExtensions {
}
}

sub initializeExtensions {
sub initialize {
my $this = shift;

# Register macro tag handlers for enabled extensions.
foreach my $tag ( keys %extTags ) {
if ( $this->extEnabled( $extTags{$tag}{extension} ) ) {
my $handler = $extTags{$tag}{class} // $extTags{$tag}{extension};
$this->app->macros->registerTagHandler( $tag, $handler );
}
}
}

sub _extVisit {
Expand Down Expand Up @@ -483,7 +502,7 @@ sub prepareDisabledExtensions {
Foswiki::Exception::Fatal->throw(
text => "Environment variable $envVar is a ref to "
. $reftype
. " but ARRAY excepted" )
. " but ARRAY or scalar string expected" )
unless $reftype eq 'ARRAY';
}
else {
Expand Down Expand Up @@ -760,6 +779,20 @@ sub _callPluggable {
return $origCode->( $params{object}, @{ $params{args} } );
}

# Universal methods supporting static, on class, and object calls.
sub extName {
shift if ref( $_[0] ) && $_[0]->isa('Foswiki::Extensions');
my ($extName) = @_;

my $name = Foswiki::fetchGlobal( "\$" . $extName . "::NAME" );

unless ($name) {
( $name = $extName ) =~ s/^Foswiki::Extension:://;
}

return $name // '';
}

=begin TML
---++ Static methods
Expand All @@ -783,6 +816,15 @@ sub registerExtModule {
$registeredModules{$extModule} = 1;
}

sub registerExtTagHandler {
my ( $extModule, $tagName, $tagClass ) = @_;

$extTags{$tagName} = {
extension => $extModule,
( defined $tagClass ? ( class => $tagClass ) : () ),
};
}

sub registerDeps {
my $extModule = shift;

Expand Down
1 change: 0 additions & 1 deletion core/lib/Foswiki/Macro.pm
Expand Up @@ -4,7 +4,6 @@ package Foswiki::Macro;
use v5.14;

use Moo::Role;
use namespace::clean;

requires 'expand';

Expand Down
59 changes: 43 additions & 16 deletions core/lib/Foswiki/Macros.pm
@@ -1,7 +1,6 @@
# See bottom of file for license and copyright information

package Foswiki::Macros;
use v5.14;

use Foswiki qw(%regex expandStandardEscapes);
use Foswiki::Attrs ();
Expand Down Expand Up @@ -84,9 +83,12 @@ sub registerTagHandler {
$this->app->logger->log( 'warning', "Re-registering existing tag " . $tag, )
if exists $this->registered->{$tag};

Foswiki::Exception::Fatal->throw(
text => "Tag handler object doesn't do Foswiki::Macro role" )
unless ref($handler) eq 'CODE' || $handler->does('Foswiki::Macro');
Foswiki::Exception::Fatal->throw( text =>
"Invalid tag handler object: must be a code, a Foswiki::Macro, or a Foswiki::Extension"
)
unless ref($handler) eq 'CODE'
|| $handler->does('Foswiki::Macro')
|| $handler->isa('Foswiki::Extension');

$this->registered->{$tag} = $handler;
if ( $syntax && $syntax eq 'context-free' ) {
Expand Down Expand Up @@ -663,6 +665,9 @@ sub execMacro {
my $this = shift;
my ( $macroName, $attrs, $topicObject, @macroArgs ) = @_;

my $app = $this->app;
my $extensions = $app->extensions;

my $rc;

# vrurg Macro could either be a reference to an object or a sub. Though
Expand All @@ -686,22 +691,44 @@ sub execMacro {
if ( ref( $this->registered->{$macroName} ) eq 'CODE' ) {
$rc =
$this->registered->{$macroName}
->( $this->app, $attrs, $topicObject, @macroArgs );
->( $app, $attrs, $topicObject, @macroArgs );
}
else {
# Create macro object unless it already exists.
unless ( defined $this->_macros->{$macroName} ) {
$this->_macros->{$macroName} =
$this->create( $this->registered->{$macroName} );
ASSERT( $this->_macros->{$macroName}->does('Foswiki::Macro'),
"Invalid macro module "
. $this->registered->{$macroName}
. "; must do Foswiki::Macro role" )
if DEBUG;
my $macroObj;
my $macroClass = $this->registered->{$macroName};
my $methodName = 'expand';

if ( UNIVERSAL::isa( $macroClass, 'Foswiki::Extension' ) ) {
if ( $extensions->extEnabled($macroClass) ) {
$macroObj = $this->app->extensions->extObject($macroClass);
$methodName = $macroName;
}
else {
# SMELL Some better way to report macro from a disabled
# extension? Anyway, this must not happen unless an extension
# has been disabled manually after extensions have been
# initialized.
$rc = "$macroName disabled, check extension "
. $extensions->extName($macroClass);
}
}
$rc =
$this->_macros->{$macroName}
->expand( $attrs, $topicObject, @macroArgs );
else {
if ( defined $this->_macros->{$macroName} ) {
$macroObj = $this->_macros->{$macroName};
}
else {
$macroObj = $this->_macros->{$macroName} =
$this->create( $this->registered->{$macroName} );
ASSERT( $this->_macros->{$macroName}->does('Foswiki::Macro'),
"Invalid macro module "
. $this->registered->{$macroName}
. "; must do Foswiki::Macro role" )
if DEBUG;
}
}
$rc = $macroObj->$methodName( $attrs, $topicObject, @macroArgs )
if defined $macroObj;
}

return $rc;
Expand Down
5 changes: 2 additions & 3 deletions core/lib/Foswiki/Meta.pm
@@ -1,5 +1,7 @@
# See bottom of file for license and copyright information

package Foswiki::Meta;

=begin TML
---+ package Foswiki::Meta
Expand Down Expand Up @@ -104,9 +106,6 @@ the function or parameter.
=cut

package Foswiki::Meta;
use v5.14;

use Try::Tiny;
use Assert;
use Errno 'EINTR';
Expand Down

0 comments on commit 9f66599

Please sign in to comment.