Jinja2 - break macros out across multiple files - jinja2

Given a lengthy number of Jinja2 macros in a file, let's call it macros.html.
I would like to break that file up into a number of smaller files, but have it appear the same outwardly when I call it with an import.
So for example, suppose I have
macros.html
{% macro A_1() %} A_1 {% endmacro %}
{% macro A_2() %} A_2 {% endmacro %}
{% macro A_3() %} A_3 {% endmacro %}
{% macro B_1() %} B_1 {% endmacro %}
{% macro B_1() %} B_1 {% endmacro %}
Elsewhere I import this with import "macros.html" as macros.
I would like to break macros.html down into multiple files, such as A.html and B.html in this example, like this:
A.html
{% macro A_1() %} A_1 {% endmacro %}
{% macro A_2() %} A_2 {% endmacro %}
{% macro A_3() %} A_3 {% endmacro %}
B.html
{% macro B_1() %} B_1 {% endmacro %}
{% macro B_1() %} B_1 {% endmacro %}
However I would like the files that used macros.html to be able to still include it with the import "macros.html" as macros.
I have tried a number of things, but they have not worked as expected. I typically get an error of jinja2.environment.TemplateModule object has no attribute 'A_1' when doing any of the following in macros.html
{% include "A.html" %}
{# or #}
{% from "A.html" import A_1 %}
The only option that seems to somewhat work is:
{% import "A.html" as XYZ %}
{% set A_1 = XYZ.A_1 %}
Unfortunately in this case the macros in A.html cannot access global macros from the main file, which differs from the behaviour when the macros were included all in macros.html.
In any case there is a lot of unnecessary repetition going on there, though, since I would be effectively importing anonymous module names for each file imported just to access and manually name each of its macro members.
It seems like there should be a better option.
One I have considered is writing my own file loader that loads and concatenates the glob of macros. Basically a pre-processor that creates "macros.html" from scratch.

You can use extends to inherit from existing macro files. If in 'A' you inherit from 'B' then import 'A' into a page you can call B's macros as if they are part of file 'A' without the extra imports in your page. As you requested it will appear to be the same file outwardly. This is how it's done:
{% extends 'macrosdir/file.html' %}

Related

Why do I get a Jekyll/Liquid syntax error when trying to access a folder outside of the current content

I get the following error:
Invalid syntax for include tag. File contains invalid characters or sequences: (ArgumentError)
../ossvlcm/go.md
Valid syntax:
{% include_relative file.ext param='value' param2='value' %}
The line of code throwing the error:
{% assign temp_url = nPath | append: chapter.file %}{% capture my_include %}{% include_relative {{ temp_url | append: '.md' }} %}{% endcapture %}
and nPath is create with:
{% if page.ePath %}
{% assign nPath = '../ossclvm/' %}
{% endif %}
What I'm trying to do is include a file in from the a different folder from where the index.md resides. It seems to get hung up on the '../' characters but I've seen examples that use this.
So what exactly is wrong and how do I access other content from different folders?
Found out that .. is not supported in Jekyll/Liquid.
I reformatted the folder structure to make this work, index files in the parent folders to the content files.
Not a great solution

Unable to access Jekyll posts' custom variables when accessed via the site.categories

In one of my posts I added this custom variable:
exclude-from-indexes: true
In the layout for the category this post appears in, I have:
{% for post in site.categories[page.category] %}
{% if post.exclude-from-indexes!=true %}
<li>{{ post.title }}</li>
{% endif %}
{% endfor %}
However, the page is still included.
If I add:
{{ post.exclude-from-indexes }}
The variable is not shown.
I have tried different key names, but this doesn't appear to work. If I do:
{{ post | inspect }}
The key is not shown.
Am I accessing or declaring the custom variables in the wrong way? Or am I accessing some strange type of "post" that doesn't have the custom variables?
I noticed in another page, if I do:
{% for post in site.posts %}
{{ post.exclude-from-indexes }}
{% endfor %}
The variable is shown. So I think it's something to do with the data stored in site.categories[page.category]...
Turns out it was the keyname. Using:
excludefromindexes=true
Fixes it.

Jekyll filter to remove pages from site.pages based on page.url?

While generating a Google site map for my (non-github) Jekyll site, I would like to exclude certain files based on the page URL (or file name). In shell-speak, something like
site.pages | grep -v forbidden_name
In Liquid, I imagine a signature something like
site.pages | exclude 'url', forbidden_name
In a related note, is there a catalog of the standard, built-in filters, tags, and generator? Something a bit handier than grep -Rl register_filter ~/.rvm/gems?
You can try something like
{% for p in site.pages %}
{% if p.url contains 'ca' %}
{% comment %}Do nothing{% endcomment %}
{% else %}
{{ p.title }}
{% endif %}
{% endfor %}
A little hacky an case unsensitive and no wild card.
I've made a list of tags and filters that works on Github.

Strip url to 1 word in Jekyll

I am building a Jekyll blog, and I have come across an issue with permalinks.
My permalinks to blog posts are set like this in
_config.yml:
permalink: /:page/:categories/:title
It outputs like this when navigating to a blog post:
http://localhost:4000/blog/travel/netherlands-trip-prequesites/
I have some static pages in the site: Blog, Travel
The variable page.url outputs this url: /blog/travel/netherlands-trip-prequesites
The code my navigation bar uses to highlight the current page (giving it an "active" class):
{% assign url = page.url|remove:'index.html' %}
{% for nav in site.navigation %}
{% if nav.href == url %}
<li class="active">{{nav.name}}</li>
{% else %}
<li>{{nav.name}}</li>
{% endif %}
{%endfor%}
It works great when navigating to static pages, however when I click a blog post it doesn't highlight the correct static page. (ex.: If i navigate to a blog post with the url /blog/smth/title it should automatically highlight "Blog" in my navigation. When I navigate to /travel/smth/title it should highlight "Travel")
What I'd like to do is to strip down the output of page.url to its first part. For example I'd like to stip the following output
/blog/travel/netherlands-trip-prequesites
down to
/blog/
Why? So I can use it to check which static page it belongs to and highlight it accordigly.
The easiest way is to use split:
{{ page.url | split:'/' | first }}
That will give you the URL content up to the first / character.
I managed to solve it with three filters:
{{ page.url | replace:'/',' ' | truncatewords: 1 | remove:'...' }}
page.url outputs: /page/cat/title, then replace removes the forward slashes producing: page cat title. truncatewords truncates the string down to one word, producing: page... (for some reason three dots gets inserted after the remaining word). After all this I only needed to remove those dots with remove and voilá, my final string: page.
Hope this helps someone.
The answer provided by PeterInvincible was almost perfect, however, there's no need to get piping to remove involved...
The following also will produce desired output
{{ page.url | replace:'/',' ' | truncatewords: 1,"" }}
And to save it to a variable use capture redirection
{{ capture url_base }}{{ page.url | replace:'/',' ' | truncatewords: 1,"" }}{{ endcapture }}
Which can be called via {{url_base}} or mixed with other processing calls.
Also for file paths instead of URLs page.dir works well if you're not using permalink settings for layout, check the gh-pages branch (specifically _includes/nav_gen.html for functional, though rough'round the edges, example) for hosted examples of similar code examples related to liquid syntax and other magic.
Edits & Updates
The above linked script is now live/mostly-working/modular and auto-serving parsed sub-directories viewed currently at the related https://s0ands0.github.io/Perinoid_Pipes/ project site providing examples of recursive parsing of directories. Including and modding for nearly any theme should be possible just check the commented section at the top for currently recognized commands that maybe passed at inclusion call... on that note of inclusion and modularization here's how to turn the above example code for directory parsing into a function
{% comment %}
# Save this to _include/dir_path_by_numbers.html
# import with the following assigning arguments if needed
# {% include dir_path_by_numbers.html directory_argument_path="blog" directory_argument_depth=1 %}
{% endcomment %}
{% assign default_arg_directory_path = page.url %}
{% assign default_arg_directory_depth = 1 %}
{% if directory_argument_path %}
{% assign directory_to_inspect = directory_argument_path %}
{% else %}
{% assign directory_to_inspect = default_arg_directory_path %}
{% endif %}
{% if directory_argument_depth %}
{% assign directory_to_inspect_depth = directory_argument_path %}
{% else %}
{% assign directory_to_inspect_depth = default_arg_directory_depth %}
{% endif %}
{% comment %}
# Defaults read and assigned now to output results
{% endcomment %}
{{ directory_to_inspect_depth | replace:'/',' ' | truncatewords: directory_to_inspect_depth,"" | remove_first: '/' | replace:' ','/' }}
The above should output directory path lengths of whatever size desired and maybe included as shown previously or if feeling adventurous try what's shown below; though for looping and recursive features look to the linked script for how I've worked around stack size restrictions.
{% capture dir_sub_path %}{{include dir_path_by_numbers.html directory_argument_path="blog" directory_argument_depth=1}}{% endcapture %}
Note above is just speculation, untested, and maybe more buggy than scripts tested and hosted publicly... in other words inspiration.
Simplest way would be using
if page.url contains
example:
<li class="{% if page.url contains '/docs/' %}current{% endif %}">
Docs

Evaluate a liquid "if" statement based on value of liquid filter

I have a custom Liquid filter I use in a Jekyll site,
{{ page.url | git_modified }}
Which generates the modification date from the git log (plugin code here).
Often I may add the additional filter to convert this to a string or XML schema, depending on context, e.g. {{ page.url | git_modified | date_to_string }}. Everything is hunky-dory unless for some reason my git_modified filter fails to return a time object for some post. In that case, I am trying to write a decent fail condition but cannot quite figure this out.
I'd like to just wrap my call in a liquid if statement to check if the variable is defined first:
{% if defined?( {{ page.url | git_modified }} %}
But I don't seem to be able to use Liquid tags ({{) inside Liquid block options ({%, %}). I thought I could get around this with Liquid capture:
{% capture page_modified %}{{ page.url | git_modified }}{% endcapture %}
{% if defined?(page_modified) %}
{{ page.url | git_modified | date_to_string }}
{% endif %}
but said variables do not seem to be available to the if statements. Any suggestions?
try doing it this way:
{% capture page_modified %}
{{ page.url }}
{% endcapture %}
{% if page_modified %}
{{ page.url }}
{% endif %}
If page_modified isn't defined, its value will be nil anyway, so just use the if construct as you would in pure Ruby. I tested here with jekyll 1.0.0.beta2 — jekyll new test, then created a file with the above code — and it worked. :)