NAME
YATT::Lite::ArgMacro - define an argmacro as a Perl module
SYNOPSIS
In lib/MyApp/ArgMacro/Enum.pm (one module defines one argmacro):
package MyApp::ArgMacro::Enum;
use strict;
use warnings;
use YATT::Lite::ArgMacro
out => [qw(list=list)],
in => ['first=value/0', 'last=value!'];
sub on_expand {
(my MY $class, my CGen $cgen, my Args $args, my Vars $vars) = @_;
my Result $result = {};
my $firstExpr = $args->{first} ? $cgen->node_value($args->{first})
: $vars->{first}->default;
$result->{list} = sprintf(q{%s .. %s}, $firstExpr
, $cgen->node_value($args->{last}));
$result;
}
1;
In app.psgi:
my $site = MY->load_factory_for_psgi(
$0,
doc_root => "$FindBin::Bin/public",
argmacro => {enum => 'MyApp::ArgMacro::Enum'},
);
In templates:
<!yatt:widget bar
%enum(header=last);
%enum(label=last);
>
<yatt:foreach my=row list=label_list>...</yatt:foreach>
This is equivalent to the following template declaration:
<!yatt:argmacro enum=[list=list] first="value/0" last="value!">
my $firstExpr = $args->{first} ? $cgen->node_value($args->{first})
: $vars->{first}->default;
$result->{list} = sprintf(q{%s .. %s}, $firstExpr
, $cgen->node_value($args->{last}));
DESCRIPTION
An argmacro is a compile-time macro for widget arguments. When a widget declares %name;, the output args of the macro are added to the widget, and calls of the widget which pass the input args are rewritten by on_expand into the output args.
Usually argmacros are declared in templates with <!yatt:argmacro>. This module lets you define one in a Perl module instead, so that the code can be edited, checked (perl -c) and tested as ordinary Perl code.
Field name checking
use YATT::Lite::ArgMacro creates the following record classes and type name constants in the module:
Args(<Module>::Args) - fields are the input and refer argsVars(<Module>::Vars) - fields are the input and refer argsResult(<Module>::Result) - fields are the output argsMY(the module itself),CGen(YATT::Lite::CGen::Perl) andArgMacro(YATT::Lite::Core argmacro instance)
So, if you declare lexicals with these types like my Result $result, typos of field names like $result->{lsit} are detected when the module is compiled:
No such class field "lsit" in variable $result of type MyApp::ArgMacro::Enum::Result
Result->new creates a restricted hash, which also detects typos of dynamic keys at runtime.
Constants which are already defined in the module are kept as is.
IMPORT OPTIONS
out => [SPEC...]
Output args (required). Same as [...] part of <!yatt:argmacro name=[...]>.
in => [SPEC...]
Input args. Same as the rest args of <!yatt:argmacro>.
Each SPEC is "name" or "name=typespec" where typespec is the same as the argument spec of widgets, like value/0, value!, list. code and delegate types are not supported.
refer => [NAME...]
Args which the macro only refers to. They are not consumed by the macro:
They must be declared by the widget itself (not by the macro), and are passed to the widget as usual.
They do not trigger the macro. Only input args do.
They are not renamed by
%name(to=from);.Only names can be given (no typespec). The type follows the widget's declaration.
When the macro is triggered, on_expand gets the passed node as $args->{NAME} (undef if not passed), and the widget's declaration as $vars->{NAME} (undef if the widget does not declare it). The widget is not required to declare them; on_expand can die if it needs one, which is reported as a compile error.
use YATT::Lite::ArgMacro
out => [qw(greeting)],
in => [qw(who)],
refer => [qw(lang)];
<!yatt:widget hello lang="?en" %greet;>
This corresponds to edit of old YATT, which was only used this way.
METHODS
on_expand($cgen, $args, $vars, $argmacro, $node)
Must be implemented by the module. Called as a class method at compile time of each widget call which passes any of input args.
$cgen-
The code generator (YATT::Lite::CGen::Perl). Use
$cgen->node_value($node)to get Perl expression of an arg. $args-
Hash of input arg nodes actually passed to the widget, keyed by formal (not renamed) names. Refer args are also included if they are passed.
$vars-
Hash of variable declarations of input args (for type and default), and of refer args declared by the widget.
$argmacro-
The YATT::Lite::Core argmacro instance (with rename info).
$node-
The node (a plain array) of the widget call being expanded. Use YATT::Lite::Constants to access it, like
$node->[NODE_LNO].
Returns a hash of Perl expressions keyed by formal output arg names.
Note that each value is cast to the type of the output arg. So for value, list etc., the value works as a Perl expression, while for text or html, it is a literal text.
A value can also be an arg node, like $result->{list} = $args->{src}. Then the node is passed as the output arg (renamed), and the receiver handles it as if the caller wrote it directly. So passing through a variable (src=items) follows the rules of the receiver: escaping for widget args, type check of list= for <yatt:foreach> etc. An arg without value (src) is passed as list=src. (This corresponds to copy_node_renamed_as of old YATT.)
Helpers of $cgen
Typical argmacros build an expression (like calls of entity functions) from input args, and put it to an output arg. Since there is no variable declaration (formal) for each step, use the following methods of $cgen to turn an input arg node into an expression. The scope of the widget call is held by $cgen. They are also available in the body of <!yatt:argmacro> declared in templates (as $cgen and $node).
use YATT::Lite::Constants; # NODE_LNO
sub on_expand {
(my MY $class, my CGen $cgen, my Args $args, my Vars $vars
, my ArgMacro $argmacro, my $node) = @_;
my Result $result = {};
my $fileExpr = $cgen->try_pass_through($args->{file})
// $cgen->as_cast_node_to(text => $args->{file});
$result->{list} = sprintf q{$this->entity_filereader(%s)}, $fileExpr;
$result;
}
$cgen->try_pass_through($argNode, $default)-
If the arg passes a variable, returns the expression of the variable in the scope of the widget call (without escaping). Otherwise returns undef.
file=foo(bare identifier): variablefoo. An unknown variable is an error (with the line of the arg).file(no value): variablefile. If no such variable, returns$default(a Perl expression) if given, otherwise an error.Others (quoted, with entities etc.): undef.
$cgen->as_cast_node_to($type, $argNode)-
Returns the value of the arg as an expression of
$type(text,html,value,list,bool...), in the same way as passing it to a widget arg of that type.codeanddelegateare not supported. $cgen->as_expr_node($argNode, $default)-
Returns the value of the arg as a raw Perl expression (entities are not escaped). Returns
$defaultif the value is empty. $cgen->generror_at($lineno, $fmt, @args)-
Returns an error with the template file and the given line, like
die $cgen->generror_at($node->[NODE_LNO], ...). (Plaindieinon_expandhas no template position.) $cgen->find_var($name)-
Looks up a variable in the scope of the widget call. Returns undef if not found.
These correspond to methods of old YATT ($trans) as follows. $scope and $node are not needed, since $cgen holds the scope.
try_pass_through($scope, $node, $arg, $default) => try_pass_through($arg, $default)
faked_gentype($type => $scope, $node, $arg) => as_cast_node_to($type => $arg)
faked_genexpr($scope, $node, $arg, $default) => as_expr_node($arg, $default)
Differences: html type of as_cast_node_to follows widget args of YATT::Lite (text parts are not escaped), and errors of try_pass_through have the line of the arg (not of the call).
BYPASS BY EXPLICIT OUTPUT
If a widget call passes any output arg of an argmacro instance (by its renamed name, like header_list of %enum(header=last);), the macro is bypassed: on_expand is not called, and the given output args are passed to the widget as is.
In that case, input args of the same instance can not be given. Regardless of the order of attributes, it is a compile error:
argmacro %enum(header=last); is bypassed by explicit output 'header_list'; input 'header' can't be given
This applies to argmacros declared in templates too. (Old YATT called this disabled mode, whose result depended on the order of attributes.)
EXPANDING IN ELEMENT MACROS
An argmacro module can also be expanded on attributes of an element handled by an ordinary (element) macro like <yatt:foreach>. This is useful to add options to builtin macros. For example, in .htyattrc.pl:
use YATT::Lite::Macro;
use MyApp::ArgMacro::FileReader; # in: file, out: list=list
Macro foreach => sub {
my ($self, $node, @rest) = @_;
$node = MyApp::ArgMacro::FileReader->expand_element($self, $node);
$self->YATT::Lite::CGen::Perl::macro_foreach($node, @rest);
};
Then <yatt:foreach my=row file="data.csv"> is processed as <yatt:foreach my=row list="...">.
Module->expand_element($cgen, $node, rename => 'to=from')
If the element $node has any input args of the macro, returns a new node whose input args are replaced with the output args (put after the other args). Otherwise returns $node itself. In list context, returns ($node, $result) where $result is the hash returned by on_expand (undef if not expanded).
on_expandgets the element as$node, and$cgenhas the scope of the element (so "Helpers of $cgen" work as usual).The same "BYPASS BY EXPLICIT OUTPUT" rule applies.
renameis same as%name(to=from);.Refer args are given as
$args, but$varsfor them are undef (there is no widget).
This corresponds to YATT::ArgMacro->create_from($trans, $scope, $args, $rename) of old YATT, which returned ($macro, $copy).
REGISTRATION
Argmacro modules are registered by argmacro option of YATT::Lite::WebMVC0::SiteApp (or YATT::Lite::Factory). It is either a hash of argmacro name to module name, or a list of such hashes, each optionally paired with a namespace:
namespace => [qw(myteam myorg yatt perl)],
argmacro => [
[myorg => {enum => 'MyApp::ArgMacro::Enum'}], # namespace myorg
{pair => 'MyApp::ArgMacro::Pair'}, # without namespace
],
Registration without namespace is the same as registration in the primary namespace (the first element of namespace, myteam above). Modules are loaded on first use.
Following are registration errors (raised when the site is set up): the same name registered twice in the same namespace (including {foo => ...} and [myteam => {foo => ...}] above), a namespace which is not in namespace, and malformed specs.
Namespace of references
%name;-
Looked up in the following order: the template itself (including imported ones), its base templates, then the
argmacrooption in the primary namespace. So templates can override the registered argmacros. %ns:name;-
Looked up only in the
argmacrooption registered inns. Argmacros declared in templates (<!yatt:argmacro>) can not be referenced this way.nsmust be one ofnamespace.
With the example above:
%enum; # error, unless the template declares enum
%myorg:enum; # MyApp::ArgMacro::Enum
%pair; # MyApp::ArgMacro::Pair (unless the template declares pair)
%myteam:pair; # MyApp::ArgMacro::Pair
%yatt:pair; # error
Modified modules are not reloaded. Restart the process (like YATT::Lite::PageConsts).
PART OPTIONS
In the namespace yatt (literally), the argmacro syntax also sets options of the part being declared:
<!yatt:widget foo %yatt:no_last_newline;>
<!yatt:args %yatt:no_last_newline(0);>
%yatt:NAME(VALUE); calls $part->configure(yatt_NAME => VALUE), and %yatt:NAME; is the same as %yatt:NAME(1);. Only fields of the part whose name starts with yatt_ can be set this way. VALUE must be a single literal; more values or variables are errors.
If the part has no field yatt_NAME, %yatt:NAME; is looked up as an argmacro as described above. So part options take precedence over argmacros of the same name registered in the namespace yatt.
Currently following options are available:
- no_last_newline
-
Do not output the last newline of the part body.