Search code examples
jinja2

Numbering using Jinja2


I would like to automate the numbering in my markdown file using pure Jinja2 implementation.

I have been able to achieve single-level numbering using the following:

{% set number = 1 %}
{% macro set_number() -%}
  {{ number }}
  {%- set number = number + 1 -%}
{%- endmacro %}

And then, when i call set_number(); for example:

## Test Heading {{ set_number() }}

it renders just fine.

However, it is second-level numbering that i am struggling with. I would like to have a numbering scheme such as 1, 2a, 2b, 2c, 3, 4a, 4b... and so on. I have tried the following code, but it doesn't work.

Option 1

{% set number = 1 %}
{% set sub_number = 'a' %}

{% macro set_number() -%}
  {{ number }}{{ sub_number }}
  {%- set sub_number = sub_number|int + 1 %}
  {%- set sub_number_2 = sub_number|string %}
  {%- if sub_number_2 > 'z' -%}
    {%- set number = number + 1 -%} 
    {%- set sub_number = 'a' -%}
  {%- endif -%}
{%- endmacro %}

And then, when i call the function; for example:

## Test heading {{ set_number() }}
## Test heading {{ set_number() }}
## Test heading {{ set_number() }}
## Test heading {{ set_number() }}

The output of option 1 is as follows:

Test heading 1a  
Test heading 11  
Test heading 12  
Test heading 13  
Test heading 14  

Option 2

I also tried the following, but it does not work. The Python function chr() and ord() do not work:

{% set number = 1 %}
{% set sub_number = 'a' %}
{% macro set_number() -%}
  {{ number }}{{ sub_number }}
  {%- set sub_number = chr(ord(sub_number) + 1) %}
  {%- if sub_number > 'z' -%}
    {%- set number = number + 1 -%}
    {%- set sub_number = 'a' -%}
  {%- endif -%}
{%- endmacro %}

Can someone please guide me here.


Solution

  • Jinja has a cycler filter that can be used for this if we provide the letters as a list. We can use letter.current attribute the check whether we need to increase the header number by one.

    {% set heading = namespace(number=0) %}
    {% set letter = cycler(*'abcdefghijklmnopqrstuvwxyz'|list) %}
    
    {% macro set_header() -%}
      {%- if letter.current == 'a' %}
        {%- set heading.number = heading.number + 1 %}
      {%- endif %}
      {{ heading.number }}{{ letter.next() }}
    {%- endmacro %}
    

    This will produce: 1a, 1b, 1c, ... 1z, 2a, 2b.

    Manually turn on the letter subheader

    In this version we can turn on the letter subheader with the sub=True parameter of our macro. We also need a new tracking variable in the namespace object (heading.last_sub) that tells us when we are in a letter series.

    {% set heading = namespace(number=0, last_sub=None) %}
    {% set letter = cycler(*'abcdefghijklmnopqrstuvwxyz'|list) %}
    
    {%- macro set_header(sub=False) %}
    {%- if sub == False or heading.last_sub == False %}
    {%- set heading.number = heading.number + 1 %}
    {%- set reset = letter.reset() %}
    {%- endif %}
    {{ heading.number }}{% if sub %}{{ letter.next() }}{% endif %}
    {%- set heading.last_sub = sub %}
    {%- endmacro %}
    

    For this code:

    {{ set_header() }}
    {{ set_header() }}
    {{ set_header(True) }}
    {{ set_header(True) }}
    {{ set_header(True) }}
    {{ set_header() }}
    {{ set_header() }}
    {{ set_header(True) }}
    {{ set_header(True) }}
    

    it will produce the following series: 1, 2, 3a, 3b, 3c, 4, 5, 6a, 6b.