6
\$\begingroup\$

I know I never have to reinvent the wheel, but I did because I need speed performance. Also, I need these features:

  • introspection or metaprogramming
  • actuate like API backend
  • private variables interpolation
  • embedded Perl code inside template like text template
  • avoid in the minimal no core modules
  • work with eval in environment secure (Eval::Closure)
  • have block or macros to process behavior like subroutines when you used block HTML and code Perl

Template::Toolkit has almost all the features listed above, but it is slow.

Is this code good?

package Myapp::Template;
use strict;
use warnings;

use Eval::Closure;
use B qw( perlstring );
use Carp qw( confess );

sub new {
    my $class = shift;
    return bless {}, $class;
}

sub parser {
   my ($self, $html_file, $hash ) = @_;

   my @delims =  qw( [% %] );
   my $regexp = join('|', map quotemeta($_), @delims);

   my @code;
   push @code, 'sub {';
   push @code, 'my $OUT = q();';
   map { push @code,  'my $'.$_.' = "'.$hash->{$_}.'"; ' } (keys %{$hash} );

   my @parts  = split /($regexp)/, $html_file;
   my $mode = 'text';
   while (@parts) {
     
     my $next = shift @parts;
     
     if ($next eq $delims[0]) {
       $mode = ($mode eq 'text') ? 'code' : confess("Impossible state");
       next;
     }
     if ($next eq $delims[1]) {
       $mode = ($mode eq 'code') ? 'text' : confess("Impossible state");
       next;
     }

     if ($mode eq 'text') {
       $code[-1] .= sprintf('$OUT .= %s;', perlstring($next));
     }
     elsif ($next =~ /\A=/) {
       $next =~ s/(\(\)|\(\s+?\))/\($self->{id_plugin}\)/g if exists $self->{id_plugin};
       $code[-1] .= sprintf('$OUT .= do { %s };', unpack("x1A*", $next), );
     }
     elsif ($next =~ /\A\sBLOCK/) {
       my $theblock = "sub " . unpack("x6A*", $next) . ' { my $OUT; ';
       $code[-1] .= sprintf(" %s;", $theblock);
     }
     elsif ($next =~ /\A\sPROCESS/) {
       my $runsub =  unpack("x8A*", $next);
       $code[-1] .= sprintf('$OUT .= %s;',  $runsub . "();");
     }
     elsif ($next =~ /\A\sEND/) {
       my $theblock = 'return $OUT; } ';
       $code[-1] .= sprintf(" %s;", $theblock);
     }
     else {
       $code[-1] .= sprintf(" %s;", $next);
     }

   }

   push @code, '$OUT;';
   push @code, '}';
   
   my ($thecode) = \@code;
   return eval_closure( source => $thecode )->();

}

sub get_blocks {
  my $self = shift;
  my $html_file = shift;
  my $thisblock = shift;
  my $id_plugin = shift;

  if (!defined $thisblock){
    my @blocks = $html_file =~ /(?>BLOCK\s+)(\w+)/g;
    return join "<br>", (@blocks);
  }
  else{

    $self->{id_plugin} = $id_plugin;
    my @blockprocess;
    my @main_block;
  
    my $check_block = sub {
      my $the_block = shift;
  
      for (split /\n/, $html_file) {
  
           if (/(?>BLOCK\s+)($the_block)/../(END)/){
             push @blockprocess, $1 if $_ =~ /(?>PROCESS\s+)(\w+)/;
             push @main_block, $_;
           }
  
      }
  
    };
  
    $check_block->($thisblock);
    map{ $check_block->($_) } @blockprocess;
  
    push @main_block, "[% PROCESS $thisblock %]";
    my $allbkocks = join "", @main_block;
    return  $self->parser($allbkocks);
  }

}


1;

Example HTML with block and process:

 [% BLOCK function_sub %]
 <div class="box">example</div>
 [% use Module::Runtime;
 my $var = 'this is not printing here';
 %]<div class="another">bla</div>
 [%= $var; #print %] 
 [% END %]

<div class="dashboard widgetBox">
   <div class="widgetTopBar">
       [% PROCESS function_sub %]          
   </div>

</div>
\$\endgroup\$
1
  • \$\begingroup\$ Can you give an example of how you use the code in a Perl script? \$\endgroup\$ Commented Apr 30, 2020 at 8:51

1 Answer 1

1
\$\begingroup\$

Overview

It is great that you:

  • Used strict and warnings
  • Leveraged other people's code by using the CPAN modules
  • Created a reusable module

Here are some adjustments for you to consider, mainly for coding style.

Documentation

You need to add usage documentation to the code to explain its purpose, how to use the methods and the syntax of the template itself. It is standard practice to
use plain old documentation (POD).

Refer to perlmodstyle for the recommended sections:

  • NAME
  • SYNOPSIS
  • DESCRIPTION
  • One or more sections or subsections giving greater detail of available methods and routines and any other relevant information.
  • etc.

It is customary to place the POD at the bottom of the file.

Adding documentation as POD also gives you manpage-like help with perldoc:

perldoc Myapp::Template

Namespace

It is best to import only what is needed to avoid namespace pollution. For example, change:

use Eval::Closure;

to:

use Eval::Closure qw(eval_closure);

I use this script to identify imported functions: which module exports are used?

Indentation

Using only 2 spaces for each indentation level is not enough for good readability. It is better to use 4 spaces.

perltidy can be used to automatically apply consistent formatting to your code.

Naming

The name of the package, Myapp, does not convey much meaning. Pehaps there is a better word that has meaning in the context of your project.

Map

While legal, I find map usage like this to be hard to understand:

map{ $check_block->($_) } @blockprocess;

Refer to the perlcritic policy:

Perl::Critic::Policy::BuiltinFunctions::ProhibitVoidMap

Don't use map in void contexts. Use a proper for loop.

Linting

Be more consistent with parentheses for built-in functions. You didn't use them for split, but you did for sprintf. The recommendation is to not use parens:

Perl::Critic::Policy::CodeLayout::ProhibitParensWithBuiltins

You can run perlcritic yourself to see other suggestions about your code.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.