2
votes

I am trying to write a recursive Jekyll navigation template (include) as described in "Nested tree navigation with recursion". I have a minimal example committed in jekyll-min, which basically has:

  • two top-level dirs, each with one page
  • another dir under the second top-level dir, containing one page
  • a navigation template (_includes/docs_contents.html) that loops through the top-level dirs and initiates recursive traversal for each
  • a recursive include (_includes/nav.html) that accepts a navigation entry, renders its title and child links, and invokes itself recursively for any dirs in its children list
  • a layout (_layouts/doc.html) that renders the navigation pane and content for each page

I'm using Ruby v2.7.0 and Jekyll v3.8.5.

# docs structure

_docs
|
|_a/
| |_index.md
|
|_b/
  |_index.md
  |
  |_1/
    |_index.md  
# _data/docs-nav.yml

- title: a
  docs:
    - link: /a/
- title: b
  docs:
    - link: /b/
    - title: 1
      docs:
        - link: /b/1/
# _includes/nav.html

{% assign section=include.nav %}
<div class="ui accordion">
    <div class="title active">
        <i class="dropdown icon"></i>
        {{ section.title }}
    </div>
    <div class="content active">
        <div class="ui vertical text menu">
            {% for item in section.docs %}
            {% if item.link %}
            {%- assign p = site.documents | where: "url", item.link | first %}
            <a {%- if page.url== p.url %} class="current item" {% endif %} class="item" href="{{ p.url }}">
                {{ p.menu_name | default: p.title }}
            </a>
            {% endif %}
            {% if item.docs %}
            {% include nav.html nav=item %}
            {% endif %}
            {% endfor %}
        </div>
    </div>
</div>
# _includes/docs_contents.html

<div class="unit one-fifth hide-on-mobiles">
    <aside>
        {% for section in site.data.docs_nav %}
        {% include nav.html nav=section %}
        {% endfor %}
    </aside>
</div>
# _layouts/doc.html

---
title: Docs
description: version 1.0
---

<html>
<body>
{% include docs_contents.html %}
{{ content }}
</body>
</html>

As far as I understand, for each page the navigation template render should work like this:

  1. _layouts/doc.html
  2. _includes/docs_contents.html: iterate root level entries, calling _nav for each
  3. _nav(/a/ entry): render title, iterate docs, render /a/ link, and quit
  4. _nav(/b/ entry): render title, iterate docs, render /b/ link, and then call _nav(/b/1/ entry)
  5. _nav(/b/1/ entry): render title, iterate docs, render /b/1/ link, and quit
  6. _nav(/b/ entry) (already in stack): quit
  7. _includes/docs_contents.html: quit

However, when I perform a bundle exec jekyll build I get:

  Liquid Exception: Liquid error (/mnt/e/ThirdParty/jekyll-min/_includes/docs_contents.html line 17): 
Nesting too deep included in /_layouts/doc.html
jekyll 3.8.5 | Error:  Liquid error (/mnt/e/ThirdParty/jekyll-min/_includes/docs_contents.html line 17): 
Nesting too deep included
Traceback (most recent call last):

[...]

What is the problem with my content or the recursive template? I have been struggling with this for hours with no luck.

JEKYLL_LOG_LEVEL=debug

didn't produce any additional useful info.

The actual document structure is more complex and could go arbitrarily deep, so writing a non-recursive template to manually handle nested levels may not be an option.

1
Plus one for appropriate'ness for the site! :)rogerdpack
Is this the absolute minimum you can boil the code, input data and error down too and still duplicate the problem? Anything beyond that gets in the way of determining the problem. "MCVE" and "How To Ask Questions The Smart Way" are good reads, as is See "Don’t use “click here” and other common hyperlink mistakes".the Tin Man
@theTinMan thanks for the edits and the guides! I wanted to retain some of the document structures in order to demonstrate what I am trying to achieve (rendering of a recursive navigation tree) and to adhere to Jekyll's configuration conventions... I could further trim off some CSS classes etc, but this is pretty much the minimal that I could come up withJanaka Bandara

1 Answers

1
votes

Excellent question.

With help of {{ myvar | inspect }} and a flag limiting recursion, I've successfully debugged your code and understood why this infinite recursion occurs.

It comes from the fact that the section variable in docs_contents.html is assigned by in a for loop and freezed : it cannot be changed.

The first time you include nav.html, {% assign section=include.nav %} is not changing section and your code just use the one assigned in your for loop.

When you recurse and call nav.html a second time it will use the same freezed global section variable and recurse indefinitely.

The solution is to change your variable name in nav.html from section to something else. eg: sub_section, and it will work, because this new variable will not be freezed and can be reassigned as needed during recursion.

{% assign sub_section=include.nav %}
{{ sub_section.title }}
{% for item in sub_section.docs %}
...

If you want to experiment here is my test code with some comments :

docs_contents.html

{% for section in site.data.docs_nav %}

{% comment %} ++++ Try to reassign "section" ++++ {% endcomment %}
{% assign section = "yolo from docs_contents.html" %}

{% assign recursion = 0 %}
<pre>
&gt;&gt; docs_contents.html
++++ "recursion" var is assigned and becomes global
recursion : {{ recursion | inspect }}
++++ "section" is freezed to loop value ++++
including nav with include nav.html nav=section &gt;&gt; {{ section | inspect }}
</pre>

{% include nav.html nav=section %}
{% endfor %}

nav.html

{% comment %} ++++ Try to reassign "section" ++++ {% endcomment %}
{% assign section = "yolo from nav.html" %}

<pre>
    &gt;&gt; nav.hml
    recursion : {{ recursion }}
    include.nav : {{ include.nav | inspect }}
    ++++ "section" is freezed to loop value ++++
    section : {{ section | inspect }}
</pre>

{% comment %} ++++ useless assignement ++++ {% endcomment %}
{% assign section=include.nav %}

{% for item in section.docs %}
  {% if item.link %}
    {%- assign p = site.documents | where: "url", item.link | first %}
    <a {%- if page.url== p.url %} class="current item" {% endif %} class="item" href="{{ p.url }}">
    {{ p.menu_name | default: p.title }}
    </a>
  {% endif %}

  {% comment %}++++ limiting recursion to 2 levels ++++{% endcomment %}
  {% if item.docs and recursion < 2 %}
    {% comment %}++++ incrementing "recursion" global variable ++++{% endcomment %}
    {% assign recursion = recursion | plus: 1 %}
    {% include nav.html nav=item %}
  {% endif %}

{% endfor %}