abw / Template2

Perl Template Toolkit v2
http://template-toolkit.org/
146 stars 94 forks source link

Extend Template Toolkit with a new directive that inherits from INCLUDE directive #59

Open hakonhagland opened 7 years ago

hakonhagland commented 7 years ago

Hi!

I am considering to use the Template Toolkit to parse LaTeX. I would like to create a system of snippets similar to the Perl module hierarchy of reusable code. This should help me not repeating paragraphs, sections, or chapters that are similar across several LaTeX documents.

Consider: main.tex

\documentclass[11pt,a4paper]{amsart}
\begin{document}
\title{Testing TTK}
\date{\today}
\maketitle

[[ IMPORT my/math/article/derivation1
   intro_sec='Introduction' ]]

From \eqref{eq:my:math:article:derivation1:1} it follows that ...

\bibliographystyle{plain}
\bibliography{myarticles}
\end{document}

and my/math/article/derivation1.tex

\section{[[ intro_sec ]]}
The equation of state is expressed in the form of the Helmholtz energy
as \cite{spa96:eqs}  
\begin{align}
  \label{eq:1}
  \frac{A(\rho, T)}{RT} = \phi(\delta, \tau) =
  \phi^o(\delta,\tau)+\phi^r(\delta, \tau)
\end{align}
where
\begin{align}
  \label{eq:2}
  \delta &= \frac{\rho}{\rho_c}\\
  \tau &= \frac{T_c}{T}.
\end{align}

So I created a new directive IMPORT that will be a superset of the INCLUDE directive. I would like the IMPORT directive to do the same as INCLUDE but also expand equation labels (and possibly more stuff). So \label{eq:1} should be converted to \label{eq:my:math:article:derivation1:1}, such that the parent document does not have to worry about conflicts in equation labels when importing a snippet.

My question is then: Is this a good idea, and is it possible to extend the Template Toolkit with new directives like IMPORT?

hakonhagland commented 7 years ago

I checked the source code and I think it should be possible to add this functionality as configuration option, for example PRE_COMPILE_HOOK. I did some basic testing and it is currently working (see the pull #60).

abw commented 7 years ago

Hi Håkon, thanks for the patch.

It's a long time since I've written any LaTeX so I might be missing something. My apologies in advance if that's the case.

If you need to encode the template name in the \label{eq:1} then perhaps you could use component.name?

For example, here's a main.tt2 which defines a MACRO called eq(n) which adds n onto a colon-delimited form of the component template name:

[%  MACRO eq(n)
      BLOCK;
        component.name.remove('\.\w+$').replace('/', ':', 1); ':'; n;
      END;

    INCLUDE my/math/derivation.tt2;
%]

Here's my/math/derivation.tt2

LABEL:[% eq(1) %]
LABEL:[% eq(2) %]

The output when I run tpage main.tt2:

LABEL:my:math:derivation:1
LABEL:my:math:derivation:2

Does that solve your problem?

The other possibility here would be to create your own custom Template::Provider module which implements a wrapper around the _load() method. That gives you a way to manipulate the template text before it is compiled.

I'm not averse to adding new functionality to TT but I do need to be convinced that it's sufficiently useful to enough people to warrant adding to the core.

hakonhagland commented 7 years ago

Hi abw. Thanks for the quick response and for letting me know about these options!

The macro seems nice, but I think I would try to keep the style \label{eq:1} instead of \label{[[ eq(1) ]]} in the template files. In this way, it will not confuse AucTeX mode in the Emacs editor. Of course I could try to reprogram the editing modes in Emacs such that they can understand this new type of labels, but I think that would be more difficult than changing how the Perl Template Toolkit works.

Creating a custom provider works fine though! For example, to get the same functionality as the PRE_COMPILE_HOOK suggested in the pull request (main.pl) I did the following:

use Template;
use Template::Config;
use Template::Provider::Latex;

use lib './lib';

$Template::Config::PROVIDER = 'Template::Provider::Latex';
$Template::Provider::Latex::PRE_COMPILE_HOOK = \&my_pre_compile_hook;

my $config = {
    INCLUDE_PATH => '.', 
    POST_CHOMP   => 1,               # cleanup whitespace
    START_TAG => quotemeta('[['),
    END_TAG   => quotemeta(']]'),
};

my $tt = Template->new($config);

$tt->process( 'test.tt2' )  || die $tt->error(), "\n";

sub my_pre_compile_hook {
    my ( $data, $filename, $name )   = @_;

    $name =~ s/\.tt2$//;
    $name =~ s{/}{:}g;
    $data =~ 
      s/ (\\(?:eqref|ref|label)\{)  ((?:eq|tab|fig):)  (.*?) (\}) 
       / _my_pre_compile_do_subs( $name, $1, $2, $3, $4 )/gex;

    return $data;
}

sub _my_pre_compile_do_subs {
    my ( $name, $pre, $type, $label, $post ) = @_;

    $label = $name . ':' . $label;
    return $pre . $type . $label . $post;
}

And Template::Provider::Latex (lib/Template/Provider/Latex.pm) :

package Template::Provider::Latex;

use parent 'Template::Provider';

our $PRE_COMPILE_HOOK;

sub _load {
    my ($self, $name, $alias) = @_;
    my ($template, $error) = $self->SUPER::_load($name, $alias);
    if ( defined $PRE_COMPILE_HOOK ) {
        $template->{text} = $PRE_COMPILE_HOOK->(
            $template->{text}, $template->{path}, $template->{name}
        );
    }

    return ( $template, $error );
}

1;

The test templates: test.tt2:

Start..
[[ INCLUDE my/derivation.tt2 ]]
End..

and my/derivation.tt2:

\begin{align}
  \label{eq:1}
  y = x^2
\end{align}

and finally, the output from running main.pl :

Start..
\begin{align}
  \label{eq:my:derivation:1}
  y = x^2
\end{align}
End..

So the config option PRE_COMPILE_HOOK may not be warranted since the same effect can be achieved by subclassing Template::Provider as shown above.

Anyway, I am glad I discovered this toolkit, it looks very powerful :) I wish to you a nice Christmas :)