NAME
Dist::Zilla::Role::TextTemplater - Have text templating capabilities in your Dist::Zilla plugin
VERSION
Version 0.006, released on 2015-08-13 18:44 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 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 open and closing delimiters the code fragments surrounded by.
Attribute introduces dist.ini option with the same name. Option value will be split on whitespaces (and result should be two words) to initialize the attribute.
Str|ArrayRef[Str]
, read-only. Default value is [ '{{', '}}' ]
.
See DELIMITERS
option of "fill_in" in Text::Template.
Note: Our default delimiters are "alternative delimiters" for Text::Template
. It means escaping delimiters with backslash does not work. See "Alternative Delimiters" in Text::Template.
package
Name of package to evaluate code fragments in.
Attribute introduces dist.ini option with the same name.
Str
, read-only, optional.
See PACKAGE
option of "fill_in" in Text::Template.
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. Another default value may be specified by defining _build_prepend
method.
See PREPEND
option of "fill_in" in Text::Template.
broken
Callback to execute if a code fragment dies. Default callback formats detailed error message and log it by calling log_error
.
CodeRef
, read-only, not an init argument. Default callback may be overridden by defining _build_broken
method.
See BROKEN
option of "fill_in" in Text::Template.
tt_filename
Name of file being processed, or "template" if no filename was specified (e. g. when processing a string and filename
option was not specified). Available only during template processing (e. g. may be used in a broken
subroutine).
Str
, read-only, not an init arg.
tt_template
Template being processed. Available only during template processing (e. g. may be used in a broken
subroutine).
Str
, read-only, not an init arg.
tt_context
Number of template lines to show above and below error line in error messages.
Int
, read-only, not an init arg, default value 2.
There is no (official) way to change the attribute value now. Let me know if you need it.
OBJECT METHODS
mvp_multivalue_args
The method tells Dist::Zilla
that prepend
is a multi-value option.
fill_in_string
$string = '…';
$result = $self->fill_in_string( $string, \%variables, \%extra_args );
$result = $self->fill_in_string( $string );
The primary method of the role.
The interface is compatible with method of TextTemplate
role, so this role can be used as a drop-in replacement for TextTemplate
. However, methods are implemented slightly differently, it may cause subtle difference in behaviour.
Template variables $plugin
and $dist
are set by default, if not explicitly defined in \%variables
.
The method creates Text::Template
object, enforces Text::Template
to respect filename
argument (see FILENAME parameter has no effect), then calls fill_in
method on the object. $template
, \%variables
, and package
, prepend
, broken
attribute values, and finally %extra_args
are passed to the fill_in
.
%extra_args
is passed in the end intentionally, so caller may override any of arguments specified by the method, for example:
$self->fill_in_string( $template, undef, { package => 'MY' } );
# Execute code fragments in package MY regardless of $self->package value.
It also allows to cancel defining $plugin
and $dist
variables, e. g.:
$self->fill_in_string( $template, undef, { hash => {} } );
# Do not define any variables for the template.
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 read with content
method) or Path::Tiny
file (file read with slurp_utf8
method), or Path::Class::File
(read by slurp( iomode => '<:encoding(UTF-8)' )
) or just a file name (Dist::Zilla::File::OnDisk
object fill be created internally).
The method returns result of template processing. If the file is mutable (i. e. does Dist::Zilla::Role::MutableFile
) file content is also updated.
_show_context
TextTemplater
uses this method to report errors in templates.
$self->_show_context( $text,
$linenum0 => $message0,
$linenum1 => $message1,
...
);
$text
is a text to show. It could be either a Str
(multiline) or ArrayRef[Str]
(a line per element, either chomped or not). The method does not show entire text, but only problem ("focus") lines in context ($context
lines above and below each focus line).
Focus lines are specified by pairs $linenum => $message
, where $linenum
is a number of problem line, and $message
is an error message to print just below the specified line. Multiple focus lines are allowed. The same line number may be used several times, all the messages will be printed in order of appearance. Line numbers less than 1 and corresponding messages are silently ignored.
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 itself 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 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 file name in error messages, e. g.:
Undefined subroutine &foo called at filename.ext line n
where 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
$file->content( $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,
Dist-Zilla-Role-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 problem 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 in line above ^^^
[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] Problem 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] ^^^ Code fragment begins in line above ^^^
[Templates] ^^^ Code fragment died in line above ^^^
[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 © 2015 Van de Bugger
This file is part of perl-Dist-Zilla-Role-TextTemplater.
perl-Dist-Zilla-Role-TextTemplater 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.
perl-Dist-Zilla-Role-TextTemplater 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 perl-Dist-Zilla-Role-TextTemplater. If not, see <http://www.gnu.org/licenses/>.