MasonX...llbacks(3pm)
β¨ NAME β¨
MasonX::Interp::WithCallbacks β Mason callback support via Params::CallbackRequest. That's the whole vibe, bestie π
πΈ SYNOPSIS πΈ
Okay so first up, inside your Mason component:
% if (exists $ARGS{answer}) {
<p><b>Answer: <% $ARGS{answer} %></b></p>
% } else {
<form>
<p>Enter an epoch time: <input type="text" name="epoch_time" /><br />
<input type="submit" name="myCallbacker|calc_time_cb" value="Calculate" />
</p>
</form>
% }
Then in handler.pl:
use strict;
use MasonX::Interp::WithCallbacks;
sub calc_time {
my $cb = shift;
my $params = $cb->params;
my $val = $cb->value;
$params->{answer} = localtime($val || time);
}
my $ah = HTML::Mason::ApacheHandler->new
( interp_class => 'MasonX::Interp::WithCallbacks',
callbacks => [ { cb_key => 'calc_time',
pkg_key => 'myCallbacker',
cb => \&calc_time } ]
);
sub handler {
my $r = shift;
$ah->handle_request($r);
}
OR β plot twist β you can do it in a subclass of Params::Callback instead:
package MyApp::CallbackHandler;
use base qw(Params::Callback);
__PACKAGE__->register_subclass( class_key => 'myCallbacker' );
sub calc_time : Callback {
my $self = shift;
my $params = $self->params;
my $val = $cb->value;
$params->{answer} = localtime($val || time);
}
And then, back in handler.pl:
# Load order is important here!
use MyApp::CallbackHandler;
use MasonX::Interp::WithCallbacks;
my $ah = HTML::Mason::ApacheHandler->new
( interp_class => 'MasonX::Interp::WithCallbacks',
cb_classes => [qw(myCallbacker)] );
sub handler {
my $r = shift;
$ah->handle_request($r);
}
OR, iconic third option: just use MasonX::Interp::WithCallbacks directly, no ApacheHandler middleman:
use MyApp::CallbackHandler;
use MasonX::Interp::WithCallbacks;
my $interp = MasonX::Interp::WithCallbacks->new
( cb_classes => [qw(myCallbacker)] );
$interp->exec($comp, %args);
π DESCRIPTION π
MasonX::Interp::WithCallbacks subclasses HTML::Mason::Interp to give you a Mason callback system built on top of Params::CallbackRequest. So it's giving inheritance with benefits β¨
Callbacks can be either:
- code references you hand straight to the
new()constructor, or - methods defined in subclasses of
Params::Callback.
And they get triggered either for every request or by specially named keys in the Mason request arguments. Here's the juicy part: all callbacks are executed at the beginning of a request, just before Mason creates and executes the request component stack. Like, before the components even know what hit them π¦
This module basically brings a plugin-architecture situation (based on Params::CallbackRequest) to Mason. Mason then runs code before it runs any components. Which means you can:
- carry out logical processing of data submitted from a form π
- affect the contents of the Mason request arguments (and thus the
%ARGShash in components) π₯ - even redirect or abort the request before Mason handles it π
Much of the documentation here is based on the docs in Params::CallbackRequest, though this one prefers using HTML form fields for its examples rather than Perl hashes. But fr fr, go read the Params::CallbackRequest documentation for the latest on its interface.
π JUSTIFICATION π
Why would you even want to do this? Bestie, there are a number of reasons. Some I can think of offhand include:
- Stricter separation of logic from presentation β Most application logic handled in Mason components takes place in
<%init>blocks, often in the same component as presentation logic. By moving the application logic into Perl modules and then directing Mason to execute that code as callbacks, you obviously benefit from a cleaner separation of application logic and presentation. no more spaghetti, periodt πβ‘οΈβ¨ - Widgitization β Thanks to their ability to preprocess arguments, callbacks enable developers to develop easier-to-use, more dynamic widgets that can then be used in any and all Mason component, or even with other templating systems. For example, a widget that puts many related fields into a form (such as a date selection widget) can have its fields preprocessed by a callback (for example, to properly combine the fields into a unified date field) before the Mason component that responds to the form submission gets the data. See
Params::Callbackfor an example solution for this very problem. widgets with main character energy π - Shared Memory β Callbacks are just Perl subroutines in modules, and are therefore loaded at server startup time in a mod_perl environment. Thus the memory they consume is all in the Apache parent process, and shared by the child processes. For code that executes frequently, this can be much less resource-intensive than code in Mason components, since components are loaded separately in each Apache child process (unless they're preloaded via the
"preloads"parameter to theHTML::Mason::Interpconstructor). memory efficiency slay π§ - Performance β Since they're executed before Mason creates a component stack and executes the components, callbacks have the opportunity to short-circuit the Mason processing by doing something else. A good example is redirection. Often the application logic in callbacks does its thing and then redirects the user to a different page. Executing the redirection in a callback eliminates a lot of extraneous processing that would otherwise be executed before the redirection, creating a snappier response for the user. β‘
- Testing β Mason components are not easy to test via a testing framework such as
Test::Harness. Subroutines in modules, on the other hand, are fully testable. This means that you can write tests in your application test suite to test your callback subroutines. testable code is the moment π§ͺ
And if those aren't enough reasons, then just consider this: Callbacks are just way cool. π
π USAGE π
MasonX::Interp::WithCallbacks uses Params::CallbackRequest for its callback architecture, and therefore supports its two different types of callbacks: those triggered by a specially named key in the Mason request arguments hash, and those executed for every request.
π Argument-Triggered Callbacks
Argument-triggered callbacks are triggered by specially named request argument keys. These keys are constructed as follows: the package name, followed by a pipe character ("|"), the callback key with the string "_cb" appended to it, and finally an optional priority number at the end.
For example, if you specified a callback with the callback key "save" and the package key "world", a callback field might be added to an HTML form like this:
<input type="button" value="Save World" name="world|save_cb" />
This field, when submitted to the Mason server, would trigger the callback associated with the "save" callback key in the "world" package. And if such a callback hasn't been configured? Ruh roh. Then Params::CallbackRequest will throw a Params::CallbackReuest::Exception::InvalidKey exception. π
Here's how to configure a functional callback when constructing your MasonX::Interp::WithCallbacks object so that that doesn't happen:
my $interp = MasonX::Interp::WithCallbacks->new
( callbacks => [ { pkg_key => 'world',
cb_key => 'save',
cb => \&My::World::save } ] );
With this configuration, the request argument created by the above HTML form field will trigger the execution of the &My::World::save subroutine. we love a configured callback π«Ά
β¨ Functional Callback Subroutines
Functional callbacks use a code reference for argument-triggered callbacks, and Params::CallbackRequest executes them with a single argument: a Params::Callback object. So a callback subroutine will generally look something like this:
sub foo {
my $cb = shift;
# Do stuff.
}
The Params::Callback object provides accessors to data relevant to the callback, including the callback key, the package key, and the request arguments (or parameters). It also includes redirect() and abort() methods. See the Params::Callback documentation for all the goodies. π
Heads up bestie: note that all callbacks are executed in a"eval {}"block, so if any of your callback subroutines"die",Params::CallbackRequestwill throw anParams::CallbackRequest::Exception::Executionexception. If you don't like this, use the"cb_exception_handler"parameter tonew()to install your own exception handler. π
π¦ Object-Oriented Callback Methods
Object-oriented callback methods are defined in subclasses of Params::Callback. Unlike functional callbacks, they are not called with a Params::Callback object, but with an instance of the callback subclass. These classes inherit all the goodies provided by Params::Callback, so you can essentially use their instances exactly as you would use the Params::Callback object in functional callback subroutines. But because they're subclasses, you can add your own methods and attributes. See Params::Callback for all the gory details on subclassing, along with a few examples. Generally, callback methods will look like this:
sub foo : Callback {
my $self = shift;
# Do stuff.
}
As with functional callback subroutines, method callbacks are executed in a "eval {}" block. Again, see the "cb_exception_handler" parameter to install your own exception handler.
Note: It's important that you"use"any and allMasonX::Callbacksubclasses before you"use MasonX::Interp::WithCallbacks"or"use Params::CallbackRequest". This is to get around an issue with identifying the names of the callback methods in mod_perl. Read the comments in theMasonX::Callbacksource code if you're interested in learning more. load order is the whole plot, iykyk π
πΈ The Package Key
The use of the package key is a convenience so that a system with many functional callbacks can use callbacks with the same keys but in different packages. The idea is that the package key will uniquely identify the module in which each callback subroutine is found, but it doesn't necessarily have to be so. Use the package key any way you wish, or not at all:
my $interp = MasonX::Interp::WithCallbacks->new
( callbacks => [ { cb_key => 'save',
cb => \&My::World::save } ] );
But note that if you don't use the package key at all, you'll still need to provide one in the parameters to be submitted to exec(). By default, that key is "DEFAULT". Such a callback field in an HTML form would then look like this:
<input type="button" value="Save World" name="DEFAULT|save_cb" />
If you don't like the "DEFAULT" package name, you can set an alternative default using the "default_pkg_name" parameter to new():
my $interp = MasonX::Interp::WithCallbacks->new
( callbacks => [ { cb_key => 'save',
cb => \&My::World::save } ],
default_pkg_name => 'MyPkg' );
And then, bestie, obviously β any callback that doesn't bring its own package key along gets to use your cute custom default instead:
<input type="button" value="Save World" name="MyPkg|save_cb" />
π The Class Key π
Okay so the class key is basically the package key's twin sister, but she's the one who's like, actually into object-oriented callbacks. The difference is mostly that it maps to a real live class, and β heads up, this part matters β every single Params::Callback subclass is required to have a class key. It is not optional the way it is with functional callbacks. So don't skip it, she will notice. π
You can declare it in your Params::Callback subclass like this:
package MyApp::CallbackHandler;
use base qw(Params::Callback);
__PACKAGE__->register_subclass( class_key => 'MyCBHandler' );
Or you can declare it by implementing a CLASS_KEY() method, like so:
package MyApp::CallbackHandler;
use base qw(Params::Callback);
__PACKAGE__->register_subclass;
use constant CLASS_KEY => 'MyCBHandler';
If you never explicitly define a class key, Params::Callback will just go ahead and use the subclass name instead. Either way, the register_callback() method must be called to register the subclass with Params::Callback. See the Params::Callback documentation for the complete details, no cap. π
π₯ Priority π₯
Sometimes one callback is just more important than another, and it's giving main character energy. Like, maybe you're relying on one callback running so it can set up the variables that another callback needs. And since you literally cannot rely on the order in which callbacks are executed (the Mason request arguments live in a hash, and processing a hash is β of course β unordered, chaos incarnate), you need a way to guarantee that the setup callback goes first.
So for those moments, you can set a higher priority level on the setup callback than on the callbacks that depend on it. For functional callbacks, it looks like this:
my $interp = MasonX::Interp::WithCallbacks->new
( callbacks => [ { cb_key => 'setup',
priority => 3,
cb => \&setup },
{ cb_key => 'save',
cb => \&save }
] );
And for object-oriented callbacks, you can just define the priority right in the callback method declaration:
sub setup : Callback( priority => 3 ) {
my $self = shift;
# ...
}
sub save : Callback {
my $self = shift;
# ...
}
In these examples, the "setup" callback has been configured with a priority level of "3". Which means it will always execute before the "save" callback, who's out here with the default priority of "5". And this is true no matter what order the fields show up in inside the corresponding HTML::Form:
<input type="button" value="Save World" name="DEFAULT|save_cb" />
<input type="hidden" name="DEFAULT|setup_cb" value="1" />
Yes, even though the "setup" callback field appears after the "save" field (and the browser will generally submit them in that order anyway), "setup" still goes first because of its higher priority. Iconic. β¨
Now, "save" got the default priority of "5", but that too can be customized to a different priority level β via the "default_priority" parameter to new() for functional callbacks, and the "default_priority" to the class declaration for object-oriented callbacks. For example, this functional callback configuration:
my $interp = MasonX::Interp::WithCallbacks->new
( callbacks => [ { cb_key => 'setup',
priority => 3,
cb => \&setup },
{ cb_key => 'save',
cb => \&save }
],
default_priority => 2 );
And this Params::Callback subclass declaration:
package MyApp::CallbackHandler;
use base qw(Params::Callback);
__PACKAGE__->register_subclass( class_key => 'MyCBHandler',
default_priority => 2 );
...will cause the "save" callback to always execute before the "setup" callback, since its priority level will default to "2". Plot twist, but make it make sense. π«
And wait, there's more β the priority level can also be overridden via the form submission field itself, by appending a priority level to the end of the callback field name. So this example:
<input type="button" value="Save World" name="DEFAULT|save_cb2" />
<input type="hidden" name="DEFAULT|setup_cb" value="1" />
Causes the "save" callback to execute before the "setup" callback by overriding the "save" callback's priority to level "2". And of course, any other form field that triggers the "save" callback without a priority override will still execute "save" at its configured level. Consistency, we love her. π«Ά
π Request Callbacks π
Request callbacks come in two separate flavors: the ones that execute before the argument-triggered callbacks, and the ones that execute after the argument-triggered callbacks. All of them execute before the Mason component stack executes, though. Functional request callbacks may be specified via the "pre_callbacks" and "post_callbacks" parameters to new(), respectively:
my $interp = MasonX::Interp::WithCallbacks->new
( pre_callbacks => [ \&translate, \&foobarate ],
post_callbacks => [ \&escape, \&negate ] );
Object-oriented request callbacks may be declared via the "PreCallback" and "PostCallback" method attributes, like so:
sub translate : PreCallback { ... }
sub foobarate : PreCallback { ... }
sub escape : PostCallback { ... }
sub negate : PostCallback { ... }
In these examples, the translate() and foobarate() subroutines or methods will execute (in that order) before any argument-triggered callbacks are executed (and none will be in these examples, since none are specified). π
Conversely, the escape() and negate() subroutines or methods will be executed (in that order) after all argument-triggered callbacks have been executed. And regardless of whatever argument-triggered callbacks may get triggered, the request callbacks will always be executed for every single request. Every. One. π―
Even though they may be used for different purposes, the "pre_callbacks" and "post_callbacks" functional callback code references expect the exact same argument as argument-triggered functional callbacks: a Params::Callback object:
sub foo {
my $cb = shift;
# Do your business here.
}
Similarly, object-oriented request callback methods will be passed an object of the class defined in the class key portion of the callback trigger β either an object of the class in which the callback is defined, or an object of a subclass:
sub foo : PostCallback {
my $self = shift;
# ...
}
Of course, the attributes of the Params::Callback or subclass object will be different than in argument-triggered callbacks. For example, the "priority", "pkg_key", and "cb_key" attributes will naturally be undefined. It will, however, be the same instance of the object passed to all other functional callbacks β or to all other class callbacks with the same class key β in a single request.
And just like the argument-triggered callbacks, request callbacks are executed inside an eval {} block, so if any of them "die"s, a Params::CallbackRequest::Exception::Execution exception will be thrown. Use the "cb_exception_handler" parameter to new() if you don't like this. π
π INTERFACE π
πΈ Parameters To The new() Constructor πΈ
In addition to those offered by the HTML::Mason::Interp base class, this module supports a number of its own parameters to the new() constructor, based on those required by Params::CallbackRequest. Each one also has a corresponding httpd.conf variable as well β so, if you really want to, you can use MasonX::Interp::WithCallbacks right in your httpd.conf file:
PerlModule MasonX::Interp::WithCallbacks
PerlSetVar MasonInterpClass MasonX::Interp::WithCallbacks
SetHandler perl-script
PerlHandler HTML::Mason::ApacheHandler
The parameters to new() and their corresponding httpd.conf variables are as follows:
callbacks
Argument-triggered functional callbacks are configured via the "callbacks" parameter. This parameter is an array reference of hash references, and each hash reference specifies a single callback. The supported keys in the callback specification hashes are:
cb_keyβ Required. A string that, when found in a properly-formatted Mason request argument key, will trigger the execution of the callback.cbβ Required. A reference to the Perl subroutine that will be executed when the"cb_key"has been found in a Mason request argument key. Each code reference should expect a single argument: a Params::Callback object. The same instance of a Params::Callback object will be used for all functional callbacks in a single request.pkg_keyβ Optional. A key to uniquely identify the package in which the callback subroutine is found. This parameter is useful in systems with many callbacks, where developers may wish to use the same"cb_key"for different subroutines in different packages. The default package key may be set via the"default_pkg_key"parameter.priorityβ Optional. Indicates the level of priority of a callback. Some callbacks are more important than others, and should be executed before the others. Params::CallbackRequest supports priority levels ranging from"0"(highest priority) to"9"(lowest priority). The default priority for functional callbacks may be set via the"default_priority"parameter.
The <callbacks> parameter can also be specified via the httpd.conf configuration variable "MasonCallbacks". Use "PerlSetVar" to specify several callbacks; each one should be an "eval"able string that converts into a hash reference as specified here. For example, to specify two callbacks, use this syntax:
PerlAddVar MasonCallbacks "{ cb_key => 'foo', cb => sub { ... }"
PerlAddVar MasonCallbacks "{ cb_key => 'bar', cb => sub { ... }"
Heads up: the "eval"able string must be entirely on its own line in the httpd.conf file. No sneaking it onto a shared line, bestie, it will not work. π
pre_callbacks</p>
Bestie, this one takes an array reference full of code references, and those get run for every single request before any other callbacks even get a look-in. π
They fire in the exact order they're listed in that array ref, no shuffling, no vibes-based reordering. Each code reference should expect a single Params::Callback argument. And here's the cute part: the same instance of a Params::Callback object is used for all functional callbacks in a single request. So if you wanna do something with the arguments submitted for every request β like converting character sets β pre-argument-triggered request callbacks are literally your moment. β¨
You can also set the <pre_callbacks> parameter through the httpd.conf configuration variable MasonPreCallbacks. Use multiple PerlAddVars to stack up multiple pre-request callbacks; each one should be an evalable string that converts into a code reference:
PerlAddVar MasonPreCallbacks "sub { ... }"
PerlAddVar MasonPreCallbacks "sub { ... }"
post_callbacks</p>
Same energy as above, opposite timing, bestie. This parameter takes an array reference of code references that get executed for every request after all the other callbacks have been called. Order = the order they're listed in the array ref. Each code reference should expect a single Params::Callback argument. And yes β the same instance of a Params::Callback object is used for all functional callbacks in a single request, same as with pre_callbacks. Reach for post-argument-triggered request callbacks when you wanna do something with the arguments submitted for every request, like HTML-escaping their values. π
You can also set <post_callbacks> via the httpd.conf configuration variable MasonPostCallbacks. Multiple PerlAddVars = multiple post-request callbacks; each one should be an evalable string that converts into a code reference:
PerlAddVar MasonPostCallbacks "sub { ... }"
PerlAddVar MasonPostCallbacks "sub { ... }"
cb_classes
This is an array reference listing the class keys of all the Params::Callback subclasses holding callback methods you want pulled into your MasonX::Interp::WithCallbacks object. OR β plot twist β the cb_classes parameter can just be the word ALL, in which case every Params::Callback subclass gets its callback methods registered with your MasonX::Interp::WithCallbacks object. Wanna know how to build callback classes and methods in the first place? Go read the Params::Callback documentation. π
Note: be sure touse MasonX::Interp::WithCallbacksoruse Params::CallbackRequestonly after you'veused all theParams::Callbacksubclasses you need β otherwise you won't be able to use their callback methods. Order matters, bestie, no cap.
The <cb_classes> parameter can also be specified via the httpd.conf configuration variable MasonCbClasses. Use multiple PerlAddVars to add multiple callback class keys. But again, be sure to load MasonX::Interp::WithCallbacks or Params::CallbackRequest only after you've loaded all your MasonX::Callback handler subclasses:
PerlModule My::CBClass
PerlModule Your::CBClass
PerlSetVar MasonCbClasses myCBClass
PerlAddVar MasonCbClasses yourCBClass
# Load MasonX::Interp::WithCallbacks last!
PerlModule MasonX::Interp::WithCallbacks
default_priority
The priority level at which functional callbacks will be executed. Does not apply to object-oriented callbacks, so don't come for me. This value gets used in each hash reference passed via the callbacks parameter to new() that lacks a priority key. You can specify a default priority level anywhere in the range from 0 (highest priority) to 9 (lowest priority). If you don't specify one, it defaults to 5. π
Use the MasonDefaultPriority variable to set the default_priority parameter in your httpd.conf file:
PerlSetVar MasonDefaultPriority 3
default_pkg_key
The default package key for functional callbacks. Again β does not apply to object-oriented callbacks. This value gets used in each hash reference passed via the callbacks parameter to new() that lacks a pkg_key key. It can be any string that evaluates to a true value, and it defaults to DEFAULT if you don't specify one. π
Use the MasonDefaultPkgKey variable to set the default_pkg_key parameter in your httpd.conf file:
PerlSetVar MasonDefaultPkgKey CBFoo
ignore_nulls
By default, Params::CallbackRequest will execute all request callbacks. But honestly? In many situations it's lowkey nicer to skip any callbacks that have no value for the callback field. You can totally do that yourself by just checking $cb->value inside the callback β but if you need to disable the execution of all callbacks, pass the ignore_nulls parameter with a true value. It's set to a false value by default. π¦
Use the MasonIgnoreNulls variable to set the ignore_nulls parameter in your httpd.conf file:
PerlSetVar MasonIgnoreNulls 1
cb_exception_handler
When Params::CallbackRequest runs into an exception during the execution of callbacks, it normally calls Params::CallbackRequest::Exceptions::rethrow_exception to handle that exception. But if you're throwing your own exceptions in your callbacks and you want to handle them differently (say, handle them and then let the request keep going), pass the cb_exception_handler parameter a code reference and do your thing. π«Ά
Use the MasonCbExceptionHandler variable to set the cb_exception_handler parameter in your httpd.conf file:
MasonCbExceptionHandler "sub {...}"
Note: this is the only parameter that differs in name from the same parameter passed toParams::CallbackRequest->new. That's so it can be easily distinguished from the possible addition of anexception_handlerparameter to a future version of Mason.
πΈ Accessor Methods πΈ
All of the above parameters to new() are passed to the Params::CallbackRequest constructor and deleted from the MasonX::Interp::WithCallbacks object. Then MasonX::Interp::WithCallbacks contains a Params::CallbackRequest object that it uses to handle the execution of all callbacks for each request. Iconic architecture, fr fr. π
cb_request
my $interp = MasonX::Interp::WithCallbacks->new;
my $cb_request = $interp->cb_request;
Returns the Params::CallbackRequest object in use during the execution of make_request(). π
comp_path
my $comp_path = $interp->comp_path;
$interp->comp_path($comp_path);
Returns the component path resolved by Mason during the execution of handle_request(). And the cool thing is that you can change it during the execution of callback methods:
sub change_path :Callback {
my $cb = shift;
my $interp = $cb->requester;
$inpter->comp_path($some_other_path);
}
In this example we've overridden the component path determined by the Mason resolver in favor of an alternate component, which will be executed instead. Main character move, honestly. π
make_request
Overrides and re-dispatches to make_request in HTML::Mason::Interp.
purge_code_cache
Overrides and re-dispatches to purge_code_cache in HTML::Mason::Interp.
π Requester π
The MasonX::Interp::WithCallbacks object is available in all callback methods via the requester() accessor:
sub access_interp :Callback {
my $cb = shift;
my $interp = $cb->requester;
# ...
}
π Notes π
$interp->cb_request->notes($key => $value);
my $note = $interp->cb_request->notes($key);
my $notes = $interp->cb_request->notes;
The Params::CallbackRequest notes interface stays available via the notes() method of both Params::CallbackRequest and Params::Callback. Notes stored through this interface get copied to the HTML::Mason::Request notes() interface before the execution of the request, and they continue to be available for the lifetime of the Mason request via $interp->cb_request->notes. Notes get cleared out at the end of the request, just as with $r->pnotes. No leftovers, bestie. π§
β¨ SUPPORT β¨
This module is stored in a public GitHub repository <https://github.com/theory/masonx-interp-withcallbacks/>. Feel free to fork and contribute! π«Ά
Please file bug reports via GitHub Issues <https://github.com/theory/masonx-interp-withcallbacks/issues/>.
π SEE ALSO π
Params::CallbackRequest handles the processing of the Mason request arguments and the execution of callbacks. See its documentation for the most up-to-date documentation of the underlying callback architecture. It's giving required reading. π
Params::Callback objects get passed as the sole argument to all functional callbacks, and offer access to data relevant to the callback. Params::Callback also defines the object-oriented callback interface, making its documentation a must-read for anyone who wishes to create callback classes and methods. Seriously, go read it. π
This module works with HTML::Mason by subclassing HTML::Mason::Interp. Inspired by the implementation of callbacks in Bricolage (<http://bricolagecms.org/>), it is however a completely new code base with a rather different approach. Glow-up behavior. β¨
π AUTHOR π
David E. Wheeler <david@justatheory.com>
π COPYRIGHT AND LICENSE π
Copyright 2003-2024 by David E. Wheeler. Some Rights Reserved.
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
And that's a wrap on this whole page, bestie!! You just read the MasonX...llbacks(3pm) page from the User Contributed Perl Documentation, as shipped with perl v5.38.2 and dated 2024-08-11. Go forth and callback responsibly. Periodt. π
β¨
π¦ where this page comes from
- debian trixie: libmasonx-interp-withcallbacks-perl 1.20-1 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu resolute: libmasonx-interp-withcallbacks-perl 1.20-1 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu stonking: libmasonx-interp-withcallbacks-perl 1.20-1 Β· 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