4

This is a question about Template Toolkit for perl.

I render my templates with a little command-line utility that has the following option enabled

   DEBUG => Template::Constants::DEBUG_UNDEF,

The syntax is render <file.tt> var1 val1 var2 val2 .... This is very convenient because the user gets prompts about values that need to be defined, for example

$ render file.tt
undef error - var1 is undefined

$ render file.tt var1 foo 
undef error - var2 is undefined

$ render file.tt var1 foo var2 bar
... template renders correctly

For some (optional) values, templates provide defaults, for example:

[%- DEFAULT
    hostname = 0
%]

Then the template body would typically contain:

  [% IF hostname %] hostname = [% hostname %][% ELSE %][% -- a comment, variable hostname not provided %][% END %]

How do I make the above idiom work for variables where 0 is a valid value?

I want the following to happen:

render template.tt

Template renders:

  -- this is a comment, variable enable_networking not provided

For

  render template.tt enable_networking 0

I want

  enable_networking = 0

The problem is differentiating between defined values and false values. I have tried using -1 (instead of 0) both in the DEFAULT block and in the [% IF enable_networking == -1 %] statement.

However, the following DEFAULT block

[% DEFAULT enable_networking = -1 %]

will override value 0 specified on the command-line. (It sees a enable_networking is false and sets it to -1)

Are there any easy work-arounds (some config variable maybe?)

ikegami
  • 367,544
  • 15
  • 269
  • 518
az5112
  • 590
  • 2
  • 11

2 Answers2

3

To check if a variable is undefined or not you could check if its size method returns something greater than 0. Of course this case only aplies if the variable is not initialized or defined at all (enable_networking = '' has size = 1, the same with enable_networking = 0)

To check if a variable is not false... well... first you have to describe wich type of value is false.

In this case i will take size = 0 (or size doesn't exists) as undefined, -1 as false and everything else as true:

[% IF enable_networking.size and enable_networking != -1 %]
    enable_networking = [% enable_networking %]
[% ELSE %]
    -- a comment, variable enable_networking not provided
[% END %]

With the code above if you run

render template.tt enable_networking 0

The output will be enable_networking = 0

And if you run

render template.tt

The output will be -- a comment, variable enable_networking not provided even if you don't declare [% DEFAULT enable_networking = -1 %]

EDIT 1:

The length method is better suited for this job:

[% IF enable_networking.length and enable_networking != -1 %]
    enable_networking = [% enable_networking %]
[% ELSE %]
    -- a comment, variable enable_networking not provided
[% END %]

Using length instead of size also allows you to use enable_networking = '' as FALSE along with -1

EDIT 2:

Ok, after the comments i found a workaround that do the trick: the TRY - CATCH directives...

For optional variables that can have value of 0 the objective would be to TRY setting the variable value to itself, if the variable is defined the value will be assigned, otherwise we CATCH the undef error and set the default value. For any other type of variable we can use the DEFAULT directive:

[% DEFAULT
    hostname = 0
%]
[% TRY %] [% enable_networking = enable_networking %] [% CATCH %] [% enable_networking = -1; %] [% END %]

hostname = [% hostname %]
[% IF enable_networking != -1 AND enable_networking.length %] enable_networking = [% enable_networking %][% ELSE %]-- variable enable_networking not provided[% END %]

With this new template if you run

$ render template.tt
hostname = 0
-- variable enable_networking not provided

$ render template.tt enable_networking ""
hostname = 0
-- variable enable_networking not provided

$ render template.tt hostname myhost enable_networking 0
hostname = myhost
enable_networking = 0
kidkamek
  • 426
  • 4
  • 7
  • Well, no. The problem is that if you say `[% DEFAULT networking = -1 %]` it always overrides the value `0` the user specifies on the command-line (which ends up in `vars` in `$tt->process('template.tt', \%vars)`) The template toolkit assumes `0` to be false and overrides it with whatever the `DEFAULT` block says. And if you do not provide a value for `networking` inside the `DEFAULT` block, then rendering will die because of the `DEBUG => Template::Constants::DEBUG_UNDEF` setting. – az5112 Feb 25 '20 at 21:07
  • Oh! i understand now... you need the template toolkit to assume that 0 is not "false"... the problem there is that the `DEFAULT` directive always updates variables that are undefined or have no "true" value *in the Perl sense*... I don't know any method that can do that – kidkamek Feb 25 '20 at 23:53
  • Maybe you can see the problem the other way around... remove `DEBUG => Template::Constants::DEBUG_UNDEF` and the `DEFAULT` directive, and use the `THROW` directive whenever a required variable is undefined – kidkamek Feb 26 '20 at 00:03
  • I have been considering doing that - the problem is that I involves changing (and complicating) each existing and future template file - something I wanted to avoid. Another hack would be forcing users to type zero instead of 0 and then doing some hula hoop in templates that do need it. I don't like that one either.. – az5112 Feb 26 '20 at 19:36
  • Thinking about the `THROW` directive i found a workaround, see my edited answer!! – kidkamek Mar 05 '20 at 00:42
  • Thanks for suggestion, however passing any also undefined variable to a macro or routine makes it automatically defined no matter that prior it returned the size to be equal zero, as soon as you pass it to macro or routine it becomes size=1 sadly, dont know if its a bug then, and its even more strange, if you pass no parameter to macro, omitting it like this 'param1,,param3'. The param2 becomes having a value '1' , the length attribute did not work for me, only size – FantomX1 Apr 13 '21 at 10:10
2

All scalar values in TT have a .defined vmethod.

[% IF hostname.defined %] hostname = [% hostname %][% ELSE %][% -- a comment, variable hostname not provided %][% END %]

This is discussed in the manual.

Dave Cross
  • 68,119
  • 3
  • 51
  • 97
  • The `.defined` method will always return `true` because the variable is defined in the `[% DEFAULT %]` block. You are falling into the same trap as kidkamek. – az5112 Mar 01 '20 at 08:40