Insérer le code javascript au-dessus de y compris fichier à Jinja 2

Dans Jinja2, je voudrais la suite de travailler comme il semble comme il se doit, en cours d'exécution:

from jinja2 import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader('.'))
template = env.get_template('x.html')
print template.render()

Essentiellement l'objectif est de fusionner tous les javascript dans le <head> des balises à l'aide d'un un {% call js() %} /* some js */{% endcall %} macro.


x.html

<html>
<head>
  <script type="text/javascript>
  {% block head_js %}{% endblock %}
  </script>
  </head>
<body>
  {% include "y.html" %}
</body>
</html>

y.html

{% macro js() -%}
    // extend head_js
    {%- block head_js -%}
    {{ super() }}
    try { {{ caller() }} } catch (e) {
       my.log.error(e.name + ": " + e.message);
    }
    {%- endblock -%}
{%- endmacro %}

Some ... <div id="abc">text</div> ...

{% call js() %}
    // jquery parlance:
    $(function () {
        $("#abc").css("color", "red");
    });
{% endcall %}

Résultat attendu

Quand je le lance X.html par jinja2, je m'attends à ce que le résultat soit:

<html>
<head>
  <script type="text/javascript>
  try { {{ $("#abc").css("color", "red"); }} } catch (e) {
       usf.log.error(e.name + ": " + e.message);
    }
  </script>
  </head>
<body>
      Some ... <div id="abc">text</div> ...
</body>
</html>

Résultat réel

Les résultats réels ne sont pas encourageants. Je reçois un couple de types de potentiellement éclairant les erreurs, par exemple:

TypeError: macro 'js' ne prend aucun argument mot-clé 'appelant'

ou, lorsque j'essaie d'ajouter une autre base de macro comme

{% macro js2() -%}
{%- block head_js -%}
//     ... something
{%- endblock -%}
{%- endmacro %}

- Je obtenir de l'exception suivante

jinja2.des exceptions.TemplateAssertionError: bloc "head_js" défini deux fois

Je me sens comme si je suis en cours d'exécution dans un problème de conception au sujet de la préséance de la block balises sur le macro balises (c'est à dire les macros ne semblent pas encapsuler bloc de balises dans la façon dont je l'attends).


Je suppose que mes questions sont assez simples:

  1. Peut Jinja2 faire ce que je cherche? Si oui, comment?

  2. Si non, est-il un autre basé sur Python, moteur de template qui prend en charge ce type de modèle (par exemple, mako, genshi, etc.), qui fonctionnerait sans problème dans Google App Engine

Vous remercie pour la lecture - je vous remercie de votre entrée.

Brian


Edit:

Je suis en train d'écrire une extension pour résoudre ce problème. Je suis à mi-chemin-et en utilisant le code suivant:

from jinja2 import nodes, Environment, FileSystemLoader
from jinja2.ext import Extension

class JavascriptBuilderExtension(Extension):
    tags = set(['js', 'js_content'])

    def __init__(self, environment):
        super(JavascriptBuilderExtension, self).__init__(environment)
        environment.extend(
            javascript_builder_content = [],
        )

    def parse(self, parser):
        """Parse tokens """
        tag = parser.stream.next()
        return getattr(self, "_%s" % str(tag))(parser, tag)

    def _js_content(self, parser, tag):
        """ Return the output """
        content_list = self.environment.javascript_builder_content
        node = nodes.Output(lineno=tag.lineno)
        node.nodes = []

        for o in content_list:
            print "\nAppending node: %s" % str(o)
            node.nodes.extend(o[0].nodes)
        print "Returning node: %s \n" % node
        return node

    def _js(self, parser, tag):
        body = parser.parse_statements(['name:endjs'], drop_needle=True)
        print "Adding: %s" % str(body)
        self.environment.javascript_builder_content.append(body)
        return nodes.Const('<!-- Slurped Javascript -->')

env = Environment(
    loader      = FileSystemLoader('.'),
    extensions  = [JavascriptBuilderExtension],
    )

Il est simple d'ajouter du Javascript à la fin d'un modèle de ... par exemple

<html>
<head></head>
<body>
    {% js %}
    some javascript {{ 3 + 5 }}
    {% endjs %}
    {% js %}
    more {{ 2 }}
    {% endjs %}

<script type="text/javascript">
{% js_content %}
</script>
</body>
</html>

De course env.get_template('x.html').render() sera en partie éclairer les commentaires et les résultats attendus de:

<html>
<head>
  <script type="text/javascript>
  </script>
  </head>
<body>
    <!-- Slurped Javascript -->
    <!-- Slurped Javascript -->
<script type="text/javascript">
    some javascript 8
    more 2
</script>
</body>
</html>

Bien sûr, ce n'est pas la même chose que d'avoir le script dans la tête, comme espéré, mais au moins il est idéalement fusionné en un seul endroit.

Cependant, la solution n'est pas complète parce que quand vous avez une {% include "y.html" %} là, où "y.html" comprend un {% js %} déclaration, le {% js_content %} est appelée avant que l'inclusion de la {% js %} déclaration (c'est à dire x.html est entièrement analysée avant y.html commence).

J'ai aussi besoin, mais ne l'ont pas encore, inséré constante des nœuds qui aurait la statique javascript try/catch, j'ai indiqué que je voulais avoir. Ce n'est pas un problème.

Je suis heureux de faire des progrès, et je suis reconnaissant pour l'entrée.

J'ai ouvert la question connexe: Jinja2 compiler l'extension après le comprend


Modifier

Solution

class JavascriptBuilderExtension(Extension):
    tags = set(['js'])

    def __init__(self, environment):
        super(JavascriptBuilderExtension, self).__init__(environment)
        environment.extend(jbc = "",)

    def parse(self, parser):
        """Parse tokens """
        tag = parser.stream.next()
        body = parser.parse_statements(['name:endjs'], drop_needle=True)
        return nodes.CallBlock(
            self.call_method('_jbc', [], [], None, None),
            [], [], body
        ).set_lineno(tag.lineno)

    def _jbc(self, caller=None):
        self.environment.jbc += "\ntry { %s } catch (e) { ; };" % caller()
        return "<!-- Slurped -->"

Après avoir terminé, l'environnement contient une variable jbc qui a toutes les Javascript. Je peux insérer cette via, par exemple, string.Template.


Pourquoi voulez-vous tous les JS dans le head?
La cohérence, de la facilité de débogage, et de la séparation du code et du contenu. Je préfère ne pas avoir des centaines de <script> tags parsemé tout au long de ma HTML.
Assez juste, mais j'ai peur il n'y a pas de moyen simple de le faire... je ne suis pas sûr que vous pouvez avoir des blocs à l'intérieur des macros.
Je le soupçonne aussi, mais il ne me paraît pas entièrement indésirables motif que quelqu'un d'autre peut avoir résolu déjà.
Si vous utilisez extend au lieu de include vous pourriez le faire. Mais en raison de la séparation entre la parse et render étape, vous ne serez pas en mesure de modifier le contexte de la portée parent, jusqu'après c'est trop tard. Aussi, le Jinja contexte est censé être immuable.

OriginalL'auteur Brian M. Hunt | 2010-11-27