http://qs321.pair.com?node_id=11139384

varanasi has asked for the wisdom of the Perl Monks concerning the following question:

Everlastingly Helpful Monks:

I have a module that contains a lot of constants for a web application. I also have many template toolkit templates that use varying sets of those constants. I'd like to include those constants in all my templates directly from my constants module rather than importing them to my script and then remembering to set the ones I need each time I process a template.

Here's an example of my constants module:

package Sam::Constants; use strict; use warnings; use base 'Exporter'; use constant PRE => 1; use constant REGULAR => 2; use constant POST => 3; our @EXPORT_OK = qw( PRE REGULAR POST ); 1;

I've fiddled with toolkit's PREPROCESS and STASH directives but got nowhere.

Thank you.

EDIT - here's an example of how I use the constants . . . .

---- list_txt.tt2 Here's a list for [% date %]: [% IF type == PRE %] PRE LIST [% list %] [% ELSIF type == REG %] REG LIST [% list_alt %] [% END %] [% INCLUDE email_footer_txt.tt2 %]

Here's roughly how the script currently uses the template . . .

use Sam::Constants qw(REG PRE POST); ... $content_ref->{PRE} = PRE; $content_ref->{REG} = REG; $content_ref->{list} = $list; $content_ref->{list_alt} = $list_alt; # for mailer and pass content variable to template my %email_args = ( from => $pool_ref->email, bcc => $pool_ref->admin->email1, text_template => 'list_txt.tt2', content_ref => $content_ref, ); require Template; my $tt = Template->new( { INCLUDE_PATH => $BASE_DIR . '/root/src/', INTERPOLATE => 1, } ) || die "$Template::ERROR\n"; $tt->process( $email_args{text_template}, $email_args{content_ref} ) || die $tt->error(), "\n";
I'd like to avoid having to put lines like these in my scripts:
$content_ref->{PRE} = PRE; $content_ref->{REG} = REG;

Replies are listed 'Best First'.
Re: Import Constants From File to Templates?
by haj (Vicar) on Dec 04, 2021 at 17:09 UTC

    In your current implementation you could (ab)use the EXPORTS_OK list to import the constants as template variables:

    my $content_ref = { map { $_ => Sam::Constants->$_ } @Sam::Constants::EXPORT_OK }

    You could also pre-load them into the configuration of your >TT object. Here's a complete example:

    use strict; use warnings; package Sam::Constants; use base 'Exporter'; use constant PRE => 1; use constant REGULAR => 2; use constant POST => 3; our @EXPORT_OK = qw( PRE REGULAR POST ); package main; use Template; my $text = <<EOT; The constant 'PRE' has the value [% PRE %] The constant 'REGULAR' has the value [% REGULAR %] The constant 'POST' has the value [% POST %] EOT my $tt = Template->new( { VARIABLES => { map { $_ => Sam::Constants->$_ } @Sam::Constants::EXPORT_OK }, } ) || die "$Template::ERROR\n"; $tt->process( \$text ) || die $tt->error(), "\n";
    When run, this prints:
    The constant 'PRE' has the value 1 The constant 'REGULAR' has the value 2 The constant 'POST' has the value 3
Re: Import Constants From File to Templates?
by kcott (Archbishop) on Dec 04, 2021 at 16:48 UTC

    G'day varanasi,

    "I have a module that contains a lot of constants ... I'd like to include those constants in all my templates directly from my constants module rather than importing them to my script and then remembering to set the ones I need each time I process a template."

    Rather than including all constants, or having to remember a long list of constant names, you could use tags. What follows is a contrived scenario to demonstrate the technique.

    I created a Sam::Constants module in /home/ken/tmp/pm_11139384/lib/Sam/Constants.pm:

    package Sam::Constants; use strict; use warnings; use constant { ABC_1 => 1, ABC_2 => 2, ABC_3 => 3, DEF_1 => 4, DEF_2 => 5, DEF_3 => 6, GHI_1 => 7, GHI_2 => 8, GHI_3 => 9, }; BEGIN { use Exporter 'import'; my @abc = qw{ABC_1 ABC_2 ABC_3}; my @def = qw{DEF_1 DEF_2 DEF_3}; my @ghi = qw{GHI_1 GHI_2 GHI_3}; my @abcdef = (@abc, @def); my @all = (@abc, @def, @ghi); our @EXPORT_OK = @all; our %EXPORT_TAGS = ( abc => [@abc], def => [@def], ghi => [@ghi], abcdef => [@abcdef], all => [@all], ); } 1;

    And an alias, perl_sam_const, to facilitate testing:

    $ alias perl_sam_const alias perl_sam_const='perl -I/home/ken/tmp/pm_11139384/lib -Mstrict -M +warnings -E'

    Now I can:

    # import a single constant $ perl_sam_const 'use Sam::Constants qw{ABC_2}; say ABC_2' 2 # check that other ABC_* constants are not imported $ perl_sam_const 'use Sam::Constants qw{ABC_2}; say for ABC_1, ABC_2, +ABC_3' Bareword "ABC_1" not allowed while "strict subs" in use at -e line 1. Bareword "ABC_3" not allowed while "strict subs" in use at -e line 1. Execution of -e aborted due to compilation errors. # import all ABC_* constants $ perl_sam_const 'use Sam::Constants qw{:abc}; say for ABC_1, ABC_2, A +BC_3' 1 2 3 # import all ABC_* and GHI_* constants (using two tags) $ perl_sam_const 'use Sam::Constants qw{:abc :ghi}; say for ABC_1, GHI +_2' 1 8 # import all ABC_* and DEF_* constants (using just one tag) $ perl_sam_const 'use Sam::Constants qw{:abcdef}; say for DEF_3, ABC_3 +' 6 3 # import all constants $ perl_sam_const 'use Sam::Constants qw{:all}; say for ABC_1, DEF_2, G +HI_3' 1 5 9

    See Exporter for an explanation of what I've done here, as well as details of other ways for doing this and other options available.

    — Ken

Re: Import Constants From File to Templates?
by LanX (Saint) on Dec 04, 2021 at 13:40 UTC
    I'm no Template::Toolkit expert and and it's not clear to me how you wanna use the constants, since you gave no example°.

    I just skimmed thru the docs and found two possible approaches:

    • Template Variables
      my $tt = Template->new({ VARIABLES => { version => 3.14, release => 'Sahara', }, }); my $vars = { serial_no => 271828, }; $tt->process('myfile', $vars);
    • Compile Time Constant Folding
      my $tt = Template->new({ CONSTANTS => { version => 3.14, release => 'skyrocket', col => { back => '#ffffff', fore => '#000000', }, myobj => My::Object->new(), mysub => sub { ... }, joint => ', ', }, });
    Constants in Perl are just implemented as subs with empty prototype

    Hence you could write a sub current_constants() which is introspecting your current package and return a list of pairs which maps each constant to it's name. Another way could be to check the exporter settings of the defining package.

    my $tt = Template->new({ CONSTANTS => { current_constants() }, });

    Once this works you should also be able to write a TT plugin to do this automatically.

    Question That's what you want?

    update

    °) this was written before the OP added his "EDIT" section.

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery

      Variation on the second approach you could load your values from an YAML file (or similarly JSON if that's your bag) rather than from code:

      use Template; use YAML::XS qw( Load LoadFile ); use Cpanel::JSON::XS qw( decode_json ); ## This could be an external file, and/or make this a plugin argument +that points to it. my $consts = Load( <<'EOT' ); --- PRE: 1 REGULAR: 2 POST: 3 EOT ## Alternately my $alternate = decode_json( <<'EOT' ); {"PRE": 1, "REGULAR": 2, "POST": 3, "Foo": "Bar" } EOT my $template = Template->new({ CONSTANTS => { y => $consts, j => $alternate }, CONSTANTS_NAMESPACE => 'C', }); __END__ [% IF type == C.j.REGULAR %] Blah blah I R REGULAR stuff. But JSON says foo is '[% c.j.Foo %]' [% END %]

      The cake is a lie.
      The cake is a lie.
      The cake is a lie.

      > Hence you could write a sub current_constants() which is introspecting your current package and return a list of pairs which maps each constant to it's name.

      Jeez, that was harder than I thought. Perl is also storing constants as scalar-refs if no glob is needed for other slots.

      (NB: I'm now returning a hash-ref)

      use strict; use warnings; #use Data::Dump; sub current_constants { #warn my ($pkg) = caller; my @result; no strict 'refs'; while ( my ($key,$val) = each %{"${pkg}::"} ) { #warn "*** ${pkg}::$key =>$val <", ref($val),">\n"; if (ref $val eq "SCALAR") { push @result, $key, $$val; next; } if ( defined (my $c_ref = *$val{CODE}) ) { my $proto = prototype $c_ref; if ( defined $proto && $proto eq "") { push @result, $key, &$c_ref; } } } return { @result }; } package Some::Package; use Test::More; use constant TEST1 => "TEST1"; use constant TEST2 => "TEST2"; sub const() {"const"}; # those should be ignored our $TEST1 = "bla"; our $scl = "Scalar"; our %hsh = (Hash=>"Hash"); our @arr = ("Array"); sub func {"bla"}; is_deeply( main::current_constants(), { const => "const", TEST1 => "TEST1", TEST2 => "TEST2" } ); done_testing;

      update

      just read again

      > I'd like to include those constants in all my templates directly from my constants module rather than importing them to my script and then remembering to set the ones I need each time I process a template.

      Well I don't know how to globally include constants into all TT templates, but please note that my solution to find doesn't require to import them first.

      Just change the sub to get the $pkg origin from an argument instead from caller..

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery

Re: Import Constants From File to Templates? (outside-in) (updated)
by LanX (Saint) on Dec 04, 2021 at 18:50 UTC
    > I'd like to include those constants in all my templates directly from my constants module rather than importing them to my script

    OK if you don't need those constants at all ...

    Instead of passing the exported constants each time with ->process

    >

    my $tt = Template->new( { INCLUDE_PATH => $BASE_DIR . '/root/src/', INTERPOLATE => 1, } ) || die "$Template::ERROR\n"; $tt->process( $email_args{text_template},

    you could make your config module export the initialized TT object

    package Sam::Constants; use Template; our $tt = Template->new( # export me { INCLUDE_PATH => $BASE_DIR . '/root/src/', INTERPOLATE => 1, CONSTANTS => { ... }, VARIABLES => { ... }, } ) || die "$Template::ERROR\n";

    (see also Re: Import Constants From File to Templates?)

    And if things like $BASE_DIR need to be flexible, provide an import() function.

    use Sam::Constants base_dir =>  "...";

    edit

    and if you have to manage different constructor setups, export functions which do the different construction and return the specialized $tt object.

    update

    package Sam::Constants; my @defaults_all = (... ); sub foo { return Template->new( @_ , @defaults_all, DEFAULTS_foo ) }; sub bar { return Template->new( @_ , @defaults_all, DEFAULTS_bar ) }; ... # other file use Sam::Constants qw/foo/; my $tt = foo(LOCAL_CFG); $tt->process();

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery