Jinja Math Helper

What This Is

In one of my Nikola sites (Bowling For Data) I wanted a simpler way to use pseudocode.js in posts (simpler than having to always insert the CDN URLs into the HTML) so I thought I'd copy what the .. has_math: metadata flag does, but it turns out to involve more code than I was hoping to need so I instead copied over the math template and hacked in what I needed.

I wanted to use it in a repository I'm starting up (Terribilis Ludum) so I copied the hacked template over to it but I couldn't get it to work. Then I realized that Bowling For Data is using mako while I had set up Terribilis Ludum for Jinja so I was going to have to re-do it. I find Jinja harder to understand than Mako, partly because their documentation is sparse (and just one long document) but also because Mako uses python-ish syntax while Jinja… doesn't. But I suppose it's a good idea to have both, so here's the Jinja version.

I was thinking it'd be better to make a separate template instead of replicng the math_helper.tmpl, but it gets called in the index page template so I'd have to hack that up too, which seemed like too much bother, especially since I don't really know what everything is doing in there.

The Macros

Pseudocode Macros

These define the macros to insert the pseudocode scripts into the HTML.

Code

{% macro pseudocode() %}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pseudocode@2.4.1/build/pseudocode.min.css">
{% endmacro %}

This is the current pseudocode.js version (as of October 11, 2023). There is also an URL that is just "latest" but the pseudocode.js documentation says that it isn't always up to date so thtis will require periodically checking on the version as time goes on if keeping up to date makes some kind of version.

Style

{% macro code_styles() %}
<script src="https://cdn.jsdelivr.net/npm/pseudocode@2.4.1/build/pseudocode.min.js">
</script>
{% endmacro %}

The Math Scripts Macro

This is the macro that configures MathJax. I tried changing it to MathJax 3 but the rendering went a little cuckoo so I had to go back to 2.7.5 like in the original.

{% macro math_scripts() %}
  <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_HTMLorMML" integrity="sha384-3lJUsx1TJHt7BA4udB5KPnDrlkO8T6J6v/op7ui0BbCjvZ9WqV4Xm6DTP6kQ/iBH" crossorigin="anonymous"></script>
  {% if mathjax_config %}
    {{ mathjax_config }}
  {% else %}
    <script type="text/x-mathjax-config">
      MathJax.Hub.Config({
	tex2jax: {
	    inlineMath: [['$','$'], ['\\(','\\)']],

I did change the inlineMath dollar sign - the original had the word "latex" after the dollar sign, I guess so it doesn't mess up cases where you want actual dollar signs on the same line.

Pseudocode Additional Config

This is extra configuration stuff I added. I suppose this could (maybe should) go into the conf.py file. Maybe I'll change it later.

	displayMath: [['$$','$$'], ['\\[','\\]']],
	processEscapes: true,
	processEnvironments: true,
    }
});

And this is just to close out the macro.

      </script>
  {% endif %}
{% endmacro %}

There was also a "math styles" macro but it only seemed to apply to katex and since I'm using MathJax I got rid of it.

The Post Checkers

These are the macros that get called in the index templates to check if the page needs the math rendering code.

Math Scripts

If the has_pseudocode flag is set I'm calling the math_scripts in the later macros that add the CSS because they insert stuff into the head of the HTML, not the body. This seems to help a little with getting the math in the pseudocode to render when the page is cached by the browser.

Math Scripts If Post

This first chunk runs if the post has .. has_math: true in the metadata. It's calling the math_scripts macro from the prior section.

{% macro math_scripts_ifpost(post) %}
  {% if post.has_math %}
   {{ math_scripts() }}

I added this next part for the pseudocode. I didn't build code to make a proper metadata setting for pseudocode so unlike the previous chunk which is able to check if post.has_math I'm checking the post.meta object for the string "has_pseudocode". So as long as the post metadata has .. has_pseudocode: <any text> it will put in the math and pseudocode.

{% elif post.meta("has_pseudocode") %}
  {# the call to math_scripts is in the CSS macros to put it in the HEAD #}
  {{ pseudocode() }}

And here's the end of the macro.

  {% endif %}
{% endmacro %}

Math Scripts If Posts

This macro is like the previous one except it gets used when on the default page which has the latest several posts displayed on it so we need to check if any of the posts has the metadata requesting math or pseudocode and only insert them once, even if multiple posts use them.

This came from nikola.

{% macro math_scripts_ifposts(posts) %}
    {% if posts|selectattr("has_math")|list %}
      {{ math_scripts() }}

I added this next section to get the pseudocode into the page. Since mako uses python syntax I could do it as a generator comprehension in one line in the other version, but I couldn't figure out how to do it with Jinja so I used a for-loop. It doesn't add a lot of extra code but there were two things that seemed unusual. One is that you can't break out of the for-loop so I needed a variable to check if I've already set up MathJax and pseudocode.js. The other is that for some reason Jinja's variables are by default local to the for-loop, you can't access anything declared outside of it and thus I needed to use the namespace function which makes it so you can use the object not_yet in the loop, which is what I used to check that we only call the math_scripts and pseudocode macros once.

    {% else %}
      {% set not_yet = namespace(set_up=true) %}
      {% for post in posts %}
	{% if post.meta("has_pseudocode") and not_yet.set_up %}
	  {{ pseudocode() }}
	  {% set not_yet.set_up = false %}
	{% endif %}
      {% endfor %}
    {% endif %}
{% endmacro %}

Math Styles

This is like the previous two sections except it inserts the CSS. This gets put into the head while the scripts get put into the body of the posts. According to StackOverflow putting javascript in the HEAD can block loading so javascript should go at the bottom. Is that what my P5 template is doing? I'll need to look into that.

It might be the reason that MathJax isn't rendering until I refresh the page sometimes, though, so I use it to insert MathJax into the HTML head when putting in pseudocode.js.

Math Styles If Post

Since I got rid of the math-style macro this just loads the pseudocode styling (if it's needed).

{% macro math_styles_ifpost(post) %}
    {% if post.meta("has_pseudocode") %}
      {{ math_scripts() }}
      {{ code_styles() }}
    {% endif %}
{% endmacro %}

Math Styles If Posts

And this is for the case where there are multiple posts on a page.

{% macro math_styles_ifposts(posts) %}
  {% set not_yet = namespace(set_up=true) %}
  {% for post in posts %}
    {% if post.meta("has_pseudocode") and not_yet.set_up %}
      {{ math_scripts() }}
      {{ code_styles() }}
      {% set not_yet.set_up = false %}
    {% endif %}
  {% endfor %}
{% endmacro %}

The Race Condition

There appears to be a race condition problem that happens sometimes if I reload the page too many times.(https://github.com/mathjax/MathJax/issues/1805#issuecomment-314433504). It prevents the math being completely rendered in the pseudocode (it converts the inline symbol (slash right-parethesis) to dollar signs, so it's doing something, but it leaves the rest of the latex as is). It can be fixed by clearing the cache but I decided to try and work around it a little.

It only affects the pseudocode so I moved the loading of the math_scripts into the styles macros because they insert it into the head and not the body (which mathjax says is preferred anyway). It doesn't bother the pseudocode to be in the body so I'm leaving it there.