Liquid filter collection where not null - jekyll

In my front matter for some pages (not all) I have:
---
top-navigation:
order: 2
---
Using liquid I want to filter all site pages which have a top-navigation object and sort by top-navigation.order.
I'm trying sort:'top-navigation.order' but that's throwing an exception of undefined method [] for nil:NilClass. I tried where:"top-navigation", true but it's not equating truthy values.
How can I filter for pages that have top-navigation and then sort?

Two steps:
Create an array with pages that contains the top-navigation key.
We create an empty array and then push the items that have the key.
{% assign navposts = ''|split:''%}
{% for post in site.posts %}
{% if post.top-navigation %}
{% assign navposts = navposts|push:post%}
{% endif %}
{% endfor %}
Sort the above array by top-navigation.order
{% assign navposts = navposts|sort: "top-navigation.order"%}
Printing results:
{% for post in navposts %}
<br>{{ post.title }} - {{post.top-navigation}}
{% endfor %}
For pages use site.pages.

In Jekyll 3.2.0+ (and Github Pages) you can use the where_exp filter like so:
{% assign posts_with_nav = site.posts | where_exp: "post", "post.top-navigation" %}
Here, for each item in site.posts, we bind it to the 'post' variable and then evaluate the expression 'post.top-navigation'. If it evaluates truthy, then it will be selected.
Then, putting it together with the sorting, you'd have this:
{%
assign sorted_posts_with_nav = site.posts
| where_exp: "post", "post.top-navigation"
| sort: "top-navigation.order"
%}
Liquid also has the where filter which, when you don't give it a target value, selects all elements with a truthy value for that property:
{% assign posts_with_nav = site.posts | where: "top-navigation" %}
Unfortunately, this variant does not work with Jekyll.

Related

Condition based on the three first letters of a string?

In my Jinja template, model.DataType value can be user defined or built in. My requirenement is if model.DataType start with the three letters ARR, then do a specific operation.
Example of values:
ARRstruct124
ARR_int123
ARR123123
CCHAR
UUINT
etc.
{% set evenDataType = model.eventDataType %}
{%if evenDataType | regex_match('^ARR', ignorecase=False) %}
// do the operation
{%else%}
// do the operation
{% endif %}
With this template, I am getting the error
{%if evenDataType | regex_match('^ARR', ignorecase=False) %}
jinja2.exceptions.TemplateAssertionError: no filter named 'regex_match'
There is indeed no regex_match filter in the Jinja builtin filters. You might have found some examples using it, but this is an additional filter provided by Ansible, so it won't work outside of Ansible.
This said, your requirement does not need a regex to be fulfilled, you can use the startswith() method of a Python string.
So, you template should be:
{% set evenDataType = model.eventDataType %}
{% if evenDataType.startswith('ARR') %}
`evenDataType` starts with 'ARR'
{% else %}
`evenDataType` does not starts with 'ARR'
{% endif %}

Can I use Front Matter in Jekyll Liquid Filters?

I am attempting to create a related posts section. I have used loops and conditionals to achieve this before, but I wanted a more efficient and cleaner method. I used include variables to achieve a similar result, but for whatever reason if I attempt to use a post's front matter, I get an empty result. Example:
---
categories:
- Featured
---
{% assign featured-posts = site.posts | where: "categories", page.categories %}
{% assign featured-posts = site.posts | where: "categories", page.categories %}
where filter looks for a string in a string or in an array.
Here page.categories is an array to look for in an array. This will return an empty array.
My shortest way to get related posts with at least one common category.
{% assign related-posts = "" | split: "" %}
{% for c in page.categories %}
{% assign related-posts = related-posts | concat: site.categories[c] | uniq %}
{% endfor %}

Jekyll: sort collections by size

I want to sort my Jekyll collections by the number of documents that are in each collection.
Each collection in the site.collections variable has a docs field, and the docs field (which is an array of documents) has a size field, which is the number of documents in this collection (see documentation).
However, something like this doesn't work:
{% assign sorted = site.collections | sort: 'docs.size' %}
{% for coll in sorted %}
...
{% endfor %}
It results in a
Liquid Exception: no implicit conversion of String into Integer
It seems that the argument to sort can only be an immediate field of the type of object being sorted, and not a field of a field thereof.
Is there a way to achieve sorting the collections by the number of documents they contain?
Build an array of the available sizes:
{% assign sorted = '' | split: "" %}
{% for coll in site.collections %}
{% assign sorted = sorted| append: coll.docs.size %}
{% endfor %}
Sort the above array.
Iterate the above array and all your collections printing only the collection whose size matches the sorted array number.
Ok, I achieved it in a rather ugly way, along the lines of marcanuy's answer.
<!-- Create a comma-separated string of all the sizes of the collections -->
{% for coll in site.collections %}
{% if coll.title %}
{% if coll.docs.size < 10 %}
{% assign str = coll.docs.size | prepend: "00" %}
{% elsif coll.docs.size < 100 %}
{% assign str = coll.docs.size | prepend: "0" %}
{% else %}
{% assign str = coll.docs.size %}
{% endif %}
{% assign sizes = sizes | append: str | append: "," %}
{% endif %}
{% endfor %}
<!-- Remove last comma of string -->
{% assign length = sizes | size | minus: 1 %}
{% assign sizes = sizes | slice: 0, length %}
<!-- Split string into array, sort DESC, and remove duplicate elements -->
{% assign sizes = sizes | split: "," | sort | reverse | uniq %}
<!-- Iterate through sizes, and for each size print those collections that have this size -->
{% for s in sizes %}
{% for coll in site.collections %}
{% assign i = s | plus: 0 %}
{% if coll.docs.size == i %}
<p>{{ coll.title }}: {{ i }} documents</p>
{% endif %}
{% endfor %}
{% endfor %}
The main difficulty is that an array of sizes created like this, is an array of strings, and sorting it results in an alphabetical sort order, rather than in an numerical one (e.g. "15" comes before "2").
To remedy this, I prepend "00" to numbers less than 10, and "0" to number less than 100. This makes the alphabetical sort order coincide with the desired numerical sort order.
Then I iterate through these sizes (which are still strings), and convert them to integers on the fly (by plus: 0) so that I can compare them to the docs.size field of each collection.
It's pretty verbose, but since this is executed only when the site is generated, and not at each request in production mode, it's ok.
Still, better solutions are welcome!

Filter Array Liquid in Jekyll

I have an include which renders my posts in a certain format. I am passing the posts to the include like below:
{% include post-list.html posts=site.posts %}
However, I would like to filter out a certain category before passing it to the include. Does anyone have any ideas how I can achieve this?
You can do it defining a variable containing an array with all the posts not containing a specific category and then passing that to includes, for example to filter posts containing the category jekyll:
We create the array {% assign notjekyllposts = "" | split: "" %}
Loop all posts {% for apost in site.posts %}
Filter those that does not contain the jekyll category adding them to the array: {% assign notjekyllposts = notjekyllposts | push: apost %}
Pass the variable to the include tag
Putting it all together:
{% assign notjekyllposts = "" | split: "" %}
{% for apost in site.posts %}
{% unless apost.categories contains 'jekyll' %}
{% assign notjekyllposts = notjekyllposts | push: apost %}
{% endunless %}
{% endfor %}
{% include post-list.html posts=notjekyllposts %}

jekyll assign concat in a loop?

I would like to organize a page based on the number of pages that pass a filter.
I have tried to append truthy pages to a collection but it doesn't work.
{% assign homepage_posts = [] %}
{% for my_page in site.pages %}
{% if my_page.homepage %}
{% assign homepage_posts = homepage_posts | concat: [my_page] %}
{% endif %}
{% endfor %}
<h1>size{{homepage_posts.size}}</h1>
<h1>{{homepage_posts}}</h1>
This is not working. Does concat only work with strings?
Jekyll will use Liquid 4 soon. But, for now, no concat.
In your case you can :
Create an empty array (bracket notation doesn't work in liquid) : {% assign homepage_posts = "" | split:"/" %}
{{ homepage_posts | inspect }} --> output : []
And push elements in it :
{% for my_page in site.pages %}
{% if my_page.homepage %}
{% assign homepage_posts = homepage_posts | push: mypage %}
{% endif %}
{% endfor %}
{{ homepage_posts | inspect }}
concat filter only works with arrays and will be available in Jekyll when it upgrades to Liquid 4.*:
concat
Concatenates (combines) an array with another array. The resulting
array contains all the elements of the original arrays. concat will
not remove duplicate entries from the concatenated array unless you
also use the uniq filter.
To filter pages containing a specific attribute (in this case homepage: true) you can use a where filter.
Having a page with front matter:
---
homepage: true
---
Then you can have the pages with the homepage: true attribute like:
{% assign homepages = site.pages | where:"homepage","true" %}