NAME
Dist::Zilla::Role::TextTemplater - Have text templating capabilities in your Dist::Zilla plugin
VERSION
Version v0.8.7, released on 2018-02-27 21:17 UTC.
WHAT?
Dist-Zilla-Role-TextTemplater
is a Dist::Zilla
role, a replacement for standard role TextTemplate
. Both roles have the same great Text::Template
engine under the hood, but this one provides better control over the engine and much better error reporting.
This is Dist::Zilla::Role::TextTemplater
module documentation. Read this if you want to have text templating capabilities in your Dist::Zilla plugin.
If you are using a TextTemplater
-based plugin, read the manual. General topics like getting source, building, installing, bug reporting and some others are covered in the README.
SYNOPSIS
package Dist::Zilla::Plugin::YourPlugin;
use Moose;
use namespace::autoclean;
with 'Dist::Zilla::Role::Plugin';
with 'Dist::Zilla::Role::TextTemplater';
sub method {
my $self = shift( @_ );
...;
$result = $self->fill_in_string( $template );
...;
};
sub another_method {
my $self = shift( @_ );
...;
$self->fill_in_file( $file );
...;
};
__PACKAGE__->meta->make_immutable;
1;
DESCRIPTION
The role provides a consuming plugin with fill_in_string
and fill_in_file
methods and bunch of accompanying attributes and dist.ini options.
OBJECT ATTRIBUTES
delimiters
Pair of opening delimiter and closing delimiter to denote code fragments in template.
Attribute introduces dist.ini option with the same name. Option value will be split on whitespaces (result should be two items) to initialize the attribute.
Str|ArrayRef[Str]
, read-only. Default value is [ '{{', '}}' ]
.
See "Delimiters" in Dist::Zilla::Role::TextTemplater.
package
Name of package to evaluate code fragments in.
Attribute introduces dist.ini option with the same name.
Str
, read-only, optional.
See "Package" in Dist::Zilla::Role::TextTemplater.
prepend
Perl code to prepend to the beginning of every code fragment.
Attribute introduces dist.ini multi-value option with the same name.
ArrayRef[Str]
, read-only, auto dereferenced. Default value is empty array. Consumers may specify alternative default by defining _build_prepend
method.
See "Prepend" in Dist::Zilla::Role::TextTemplater.
tt_file
File being processed (either actual file or temporary InMemory
file when processing a string). Available only during template processing. May be used in tt_broken
method.
Object
, read-only, not an init arg.
tt_errors
Errors detected in template file, in format suitable for log_errors_in_file
(defined in ErrorLogger
role). May be used in tt_broken
method.
ArrayRef
, read-write, not an init arg.
tt_broken_count
Number of tt_broken
calls. The counter is increased before tt_broken
call.
Int
, read-only, not an init arg.
tt_broken_limit
If number of completed tt_broken
calls equals or exceeds this limit, processing stops.
Int
, read-only, not an init arg, default value 10.
There is no (official) way to change the attribute value now. Let me know if you need it.
OBJECT METHODS
tt_broken
This method is called if a code fragment dies. It formats error message(s) and sends it to the log by calling log_error
.
See BROKEN
option of "fill_in" in Text::Template.
mvp_multivalue_args
The method tells Dist::Zilla
that prepend
is a multi-value option.
tt_fill_in
$file = Dist::Zilla::File::OnDisk( ... ); # or
$file = Dist::Zilla::File::InMemory( ... ); # or
$file = Dist::Zilla::File::FromCode( ... );
$result = $self->fill_in_string( $file, \%variables, \%extra_args );
$result = $self->fill_in_string( $file );
Internal working horse of the role.
The method creates Text::Template
object, enforces Text::Template
to respect filename
argument (see FILENAME parameter has no effect), takes care about warnings, then calls fill_in
method on the object, making Text::Template
compilation errors (if found) more user-friendly.
$file->content
is passed to the Text::Template
constructor. \%variables
, \%extra_args
, and package
, prepend
, broken
attributes are combined and passed to both Text::Template
constructor and fill_in
method.
\%variables
become hash
Text::Template
option (see "HASH" in Text::Template for details). Variables plugin
(reference to object executing the method, i. e. $self
) and dist
(reference to Dist::Zilla
, i. e. $self->zilla
) are added to \%variables
automatically, if they are not exist.
package
, prepend
, broken
attributes become same-name Text::Template
options. \%extra_args
is expanded to list and passed last, so caller can override any option specified by tt_fill_in
(except filename
), for example:
$self->tt_fill_in( $file, undef, { package => 'MY' } );
will execute template code fragments in context of MY
package regardless of package
attribute. Another, a bit more complicated example:
$self->tt_fill_in( $file, undef, { hash => { } } );
processes template with no predefined variables: plugin
and dist
are added to \%variables
, but entire \%variables
is overridden by hash
extra argument.
tt_fill_in
takes special care about package: by default nested tt_fill_in
calls use the same package as the outermost call. Of course, if package
extra argument is explicitly specified in inner call, it takes priority over default package.
When defining variables with either \%variables
argument or hash
extra argument, remember that Text::Template
dereferences all the references. It especially important if you want to pass a reference to template:
$array = [ … ]; $hash = { … };
$self->tt_fill_in(
$file,
{ array => \$array, hash => \$hash, plugin => \$self },
);
In template, $array
will be a reference to array, $hash
— reference to hash, $plugin
— reference to plugin. If you forget to take references, e. g.:
$self->tt_fill_in(
$file,
{ array => $array, hash => $hash, plugin => $self },
);
template will have @array
(array, not reference to array), %hash
(hash, not reference to hash), and $plugin
will be (oops!) undefined.
Scalars can be passed either by reference or value:
$self->tt_fill_in(
$file,
{ str1 => "string", str2 => \"string" },
);
If $file
is mutable (i. e. does Dist::Zilla::Role::MutableFile
role), file content will be updated with result of template processing.
See "HASH" in Text::Template for more details.
fill_in_string
$template = '…';
$result = $self->fill_in_string( $template, \%variables, \%extra_args );
$result = $self->fill_in_string( $template );
The primary method of the role.
The fill_in_string
interface is compatible with the same-name method of TextTemplate
role, so this role can be used as a drop-in replacement for TextTemplate
. However, method is implemented slightly differently, it may cause subtle differences in behaviour.
The method creates temporary Dist::Zilla::File::InMemory
object with name "template"
(it can be overridden by filename
extra argument, though) and calls tt_fill_in
method, passing down temporary file, \%variables
and \%extra_args
.
fill_in_file
$file = Dist::Zilla::File::OnDisk->new( { … } ); # or
$file = Dist::Zilla::File::InMemory->new( { … } ); # or
$file = Dist::Zilla::File::FromCode->new( { … } ); # or
$file = Path::Tiny->new( 'filename' ); # or
$file = Path::Class::File->new( 'filename' ); # or
$file = 'filename.ext';
$result = $self->fill_in_file( $file, \%variables, \%extra_args );
$result = $self->fill_in_file( $file );
Similar to fill_in_string
, but the first argument is not a template but a file object or file name to read template from. File can be any of Dist::Zilla
file types (file is read with content
method) or Path::Tiny
file (file is read with slurp_utf8
method), or Path::Class::File
(read by slurp( iomode => '<:encoding(UTF-8)' )
) or just a file name (temporary Dist::Zilla::File::OnDisk
object is created internally).
Note that filename
extra argument is ignored, file name cannot be overridden.
The method returns result of template processing. If the file is mutable (i. e. does Dist::Zilla::Role::MutableFile
) file content is also updated.
Note: Dist::Zilla::Role::MutableFile
introduced in Dist::Zilla
version 5.000. In earlier versions there is no Dist::Zilla::Role::MutableFile
role and so, file content is never updated.
NOTES
Text::Template
option spelling
Text::Template
allows a programmer to use different spelling of options: all-caps, first-caps, all-lowercase, with and without leading dash, e. g.: HASH
, Hash
, hash
, -HASH
, -Hash
, -hash
. This is documented feature.
Text::Template
recommends to pick a style and stick with it. (BTW, Text::Template
documentation uses all-caps spelling.) This role picked all-lowercase style. This choice have subtle consequences. Let us consider an example:
$self->fill_in_string( $template, undef, { PACKAGE => 'MY' } );
Extra option PACKAGE
may or may not have effect, depending on value of package
attribute (i. e. presence or absence package
option in dist.ini file), because (this is not documented) spellings are not equal: different spellings have different priority. If PACKAGE
and package
are specified simultaneously, package
wins, PACKAGE
loses.
This feature gives you a choice. If you want to ignore option specified by the user in dist.ini and provide your value, use all-lowercase option name. If you want to provide default which can be overridden by the user, use all-caps options name.
filename
option
When Text::Template
reads template from a file, it uses the actual file name in error messages, e. g.:
Undefined subroutine &foo called at dir/filename.ext line n
where dir/filename.ext is the name of file containing the template. When Text::Template
processes a string, it uses word "template" instead of file name, e. g.:
Undefined subroutine &foo called at template line n
The option filename
allows the caller to override it:
$self->fill_in_file( $file, undef, { filename => 'Assa.txt' } );
Error message would look like:
Undefined subroutine &foo called at Assa.txt line n
It may seem this does not make much sense, but in our case (Dist::Zilla
and its plugins) Text::Template
always processes strings and never reads files, because reading files is a duty of Dist::Zilla::File::OnDisk
class. Thus, using filename
option is critical to provide good error messages. Actually, fill_in_file
implementation looks like
$self->fill_in_string(
$file->content,
undef,
{ filename => $file->name },
);
There are two problems with the option, though:
Text::Template
does not document this option.I believe it is a mistake and option should be documented.
Text::Template
ignores this option.I am sure this is a bug and hope it will be fixed eventually. I am afraid it will not be fixed soon, though.
Meanwhile,
TextTemplater
implements a workaround to let the option work, soTextTemplater
consumers can utilize thefilename
option.
WHY?
TextTemplate
role from Dist::Zilla
distribution v5.037 has the same great Text::Template
engine under the hood, but lacks of control and has awful error reporting.
Error Reporting
Let us consider an example. For sake of example simplicity, it contains only one file, dist.ini. Two files, lib/Assa.pm and lib/Assa.pod, are generated on-the-fly with GenerateFile
plugin.
Have a look at dist.ini:
name = Assa
version = 0.001
abstract = Example
[GenerateFile/lib/Assa.pm]
filename = lib/Assa.pm
content = package Assa; 1;
[GenerateFile/lib/Assa/Manual.pod]
filename = lib/Assa/Manual.pod
content = =head1 NAME
content =
content = {{$dst->name} - {{$dist->abstract}}
content =
content = Version {{$dist->version}}.
content =
content = {{$dist->license->notice}}
[TemplateFiles]
filename = lib/Assa.pm
filename = lib/Assa/Manual.pod
[MetaResources::Template]
homepage = https://example.org/release/{{$dist->name}}
license = {{$dist->license->url}}
(Do you see a typo? How many? Note this is a small example, real files are much larger.) Now let us build the distribution:
$ dzil build
[DZ] beginning to build Assa
[TemplateFiles] Filling in the template returned undef for:
[TemplateFiles] =head1 NAME
[TemplateFiles]
[TemplateFiles] {{$dst->name} - {{$dist->abstract}}
[TemplateFiles]
[TemplateFiles] Version {{$dist->version}}.
[TemplateFiles]
[TemplateFiles] {{$dist->license->notice}}
[TemplateFiles] Filling in the template returned undef for:
[TemplateFiles] =head1 NAME
[TemplateFiles]
[TemplateFiles] {{$dst->name} - {{$dist->abstract}}
[TemplateFiles]
[TemplateFiles] Version {{$dist->version}}.
[TemplateFiles]
[TemplateFiles] {{$dist->license->notice}}
at /home/vdb/.usr/opt/local-lib/lib/perl5/x86_64-linux-thread-multi/Moose/Meta/Method/Delegation.pm line 110.
Oops. What's happened? Where? Why? All we have is a highly unclear error message
Filling in the template returned undef for:
and file content printed twice. (Yep, if the file had 1000 lines, we would have it printed twice too.) We do not ever have a file name and have to guess it by the content. Good bug hunting, dude.
Ok, let us fix the problem (mistyped closing delimiter in the first line of file lib/Assa/Manual.pod) and build the distribution again:
$ dzil build
[DZ] beginning to build Assa
Can't call method "name" on an undefined value at template line 3.
Oops. Error message much is better now, but where the problem is? There are many templates in the project: lib/Assa.pm, lib/Assa/Manual.pod, and even resources in META.yml — all are generated from templates. Where is the problem? Good bug hunting for us all.
Such error reporting is simply unacceptable. I am a human, I often make mistakes, and I want the tool clearly warns me what and where the problem is, so I can fix it quickly. For example, in the first case I want to see:
$ dzil build
[DZ] beginning to build Assa
[Templates] Unmatched opening delimiter at lib/Assa/Manual.pod line 3.
[Templates] lib/Assa/Manual.pod:
[Templates] 1: =head1 NAME
[Templates] 2:
[Templates] 3: {{$dst->name} - {{$dist->abstract}}
[Templates] ^^^ Unmatched opening delimiter at lib/Assa/Manual.pod line 3. ^^^
[Templates] 4:
[Templates] 5: Version {{$dist->version}}.
[Templates] ... skipped 2 lines ...
Aborting...
In the second case:
$ dzil build
[DZ] beginning to build Assa
[Templates] Can't call method "name" on an undefined value at lib/Assa/Manual.pod line 3.
[Templates] Bad code fragment begins at lib/Assa/Manual.pod line 3.
[Templates] lib/Assa/Manual.pod:
[Templates] 1: =head1 NAME
[Templates] 2:
[Templates] 3: {{$dst->name}} - {{$dist->abstract}}
[Templates] ^^^ Can't call method "name" on an undefined value at lib/Assa/Manual.pod line 3. ^^^
[Templates] ^^^ Bad code fragment begins at lib/Assa/Manual.pod line 3. ^^^
[Templates] 4:
[Templates] 5: Version {{$dist->version}}.
[Templates] ... skipped 2 lines ...
Aborting...
TextTemplater
makes it real. All I need is using TextTemplater
-based plugins: Templates
, MetaResources::Template
(starting from v0.002).
Engine Control
TextTemplater
allows the end-user to specify delimiters
, package
and prepend
engine options in dist.ini file, while TextTemplate
allows to specify prepend
only programmatically, and does not allow to specify delimiters
and package
.
SEE ALSO
AUTHOR
Van de Bugger <van.de.bugger@gmail.com>
COPYRIGHT AND LICENSE
Copyright (C) 2015, 2016, 2018 Van de Bugger
License GPLv3+: The GNU General Public License version 3 or later <http://www.gnu.org/licenses/gpl-3.0.txt>.
This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law.