Get substring from string in Liquid? - function

I am working with Jekyll and I have the string balh blah blah&garbage **&*&% garbage <h1>TITLE</h1> &^*$%"
Is there a way to grab TITLE? I have looked at the functions here but I don't see something that I can use.

split to the rescue !
{% assign str = 'garbage <h1>TITLE</h1> moregarbage' %}
{% assign a = str | split: '<h1>' %}
We now have garbage in a[0] and TITLE</h1> moregarbage in a[1]
{% assign b = a[1] | split: '</h1>' %}
We now have TITLE in b[0] and moregarbage in b[1]

I know this is ancient, but for anyone else coming across it: https://shopify.github.io/liquid/basics/operators/
contains contains checks for the presence of a substring inside a
string.
{% if product.title contains "Pack" %} This product's title contains
the word Pack. {% endif %}
contains can also check for the presence of a string in an array of
strings.
{% if product.tags contains "Hello" %} This product has been tagged
with "Hello". {% endif %}
contains can only search strings. You cannot use it to check for an
object in an array of objects.

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 %}

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!

How to check if a variable is an integer in Jinja2?

The aim is to check whether a variable is an integer and if that is true insert hello.
Attempt
{% if int(variable) %} hello {% endif %}
Result
'int' is undefined"
To use the Jinja2 int builtin filter (which tries to cast the value to an int):
You need to use the filter format, like this:
{% if variable|int != 0 %} hello {% endif %}
By default, if casting to int fails it returns 0, but you can change this by specifying a different default value as the first parameter. Here I've changed it to -1 for a case where 0 might be a valid value for variable.
{% if variable|int(-1) != -1 %} hello {% endif %}
see the: Jinja2 Docs - int builtin filter for more info
To use the Jinja2 number builtin test (which returns true if the variable is already a number):
a better solution, rather than using the int filter (which will cast a integer like string to an int) is to use the builtin test number, like this:
{% if variable is number %} hello {% endif %}
see the: Jinja2 Docs - number builtin test
For anyone using Salt, this did not work for me when put is saltstack state.
{% if variable|number %} hello {% endif %}
This did work however:
{% if variable is number %} hello {% endif %}
None of these solutions worked for me, however this did :
{% if variable is even or variable is odd %}

Does Liquid have a "does not contain" or "not in array" operator?

When calling items from an array in a Liquid template, how do you call does not contain or not in array?
unless to the rescue !
Create an [A, B, C] array.
{% assign input = "A,B,C" | split:"," %}
unless print only if constrain is not met.
This prints nothing:
{% unless input contains 'A' %}No A{% endunless %}
This prints "No Z":
{% unless input contains 'Z' %}No Z{% endunless %}
you could do something like this:
{% if collection.tags contains 'tag' %}
{% else %}
do stuff!
{% endif %}

how to get a sorted tags_list in jekyll

I'm using jekyll-bootstrap to maintain a blog on GitHub.
I'd like to have a sorted tags_list. The tag with the most posts comes first. Then I can have a display that shows the first tags with bigger font-size and last tags with smaller font-size. And I also want a splice function.
If in python/Jinja2, I'd like some code like this:
{% for tag in sorted_tags[:10] %}
<li style="font-size:{{ tag.count }}px;">{{ tag.name }}</li>
{% endfor %}
What's the equivalent implementation in ruby/jekyll?
This is how I'm sorting by the number of posts in a tag (descending), without any plugins (i.e. GitHub Pages compatible).
It also works when your tag names contain spaces; only , and : are forbidden characters (but you can easily change those).
{% capture counts_with_tags_string %}{% for tag in site.tags %}{{ tag[1] | size | prepend:"000000" | slice:-6,6 }}:{{ tag[0] }}{% unless forloop.last %},{% endunless %}{% endfor %}{% endcapture %}
{% assign counts_with_tags = counts_with_tags_string | split:"," | sort | reverse %}
<ol>
{% for count_with_tag in counts_with_tags %}
{% assign tag = count_with_tag | split:":" | last %}
{% assign count = site.tags[tag] | size %}
<li>{{ tag }} ({{ count }})</li>
{% endfor %}
</ol>
It's super gross. What it does:
counts_with_tags_string is set to a string like 000005:first_tag,000010:second_tag,000002:third_tag. The zero-padded numbers are generated using the filter chain | prepend:"000000" | slice:-6,6.
This is split on commas and sorted lexicographically, which works because of the zero padding. The result is assigned to counts_with_tags.
Finally, we iterate over the elements and split each on : to find the original tag name. We could find the count in the same way, but because it's zero padded, it's easier to look it up using site.tags[tag] | size instead.
I thought that the tags array is sorted. Assuming so, you can do this:
{% for tag in site.tags %}
<li style="font-size: {{ tag[1].size }}px">{{ tag[0] }}</li>
{% endfor %}
That feels a little hacky, but it should work. Unfortunately, Liquid doesn't currently allow you to sort arrays within your templates. If you want to do any sorting on the array, you'd probably have to write a plugin to do so - it shouldn't be too complex. In fact, there's an existing plugin for sorting accessors that may do it: https://github.com/krazykylep/Jekyll-Sort
I only needed to do this in one place, on the page where my list of Tags was listed, so I wrote it as a Jekyll filter:
tag_index.html
<h2>Posts by Tag</h2>
<ul class="tags-list">
{{ site.tags | render_tags_list }}
</ul>
_plugins/filters.rb
module Jekyll
module Filters
def render_tags_list(tags)
sorted_tags = tags.keys.sort_by! { |tag| tag.downcase }
str = ''
sorted_tags.each { |tag|
str << '<li>' + tags[tag].size.to_s + ' - ' + tag + '</li>'
}
str
end
end
end
You could certainly just allow the filter to return sorted_tags above if you wanted to keep a better separation between "view" logic and programming logic, but my case was very simple. Trying to re-access a hash value by a specific key using Liquid templating wasn't a very concise process, or maybe I was just doing it wrong, but was much easier in Ruby.
I'm hosting my blog on github and wanted a solution to sorting a tag list that did not involve any jekyll plugins since Github doesn't allow for custom plugins (jekyll bootstrap attempts this as well). My post here doesn't really answer the question since I sort by tag name, and NOT by size. You can adapt this method to output the tag size as well in the string and then do some fancier spliting to get a different sort order (but it will be messy)
I was able to do this with the following code:
{% capture tagString %}{% for tag in site.tags %}{{ tag[0] }}{% unless forloop.last %}|{% endunless %}{% endfor %}{% endcapture %}
{% assign tags = tagString | split: '|' | sort: 'downcase' %}
<div id="cloud">
{% for tag in tags %}
{% assign number = site.tags[tag].size %}
{% assign slug = tag | downcase | replace: ' ', '_' %}
<span class="{% if number == 1 %}small{% elsif number <= 5 %}medium{% elsif number <= 10 %}large{% else %}huge{% endif %}">
{{ tag | downcase }}
</span>
{% endfor %}
</div>
It's kind of odd since I capture a string of tags (using | as separator) and then use that to create an array. After that point (in the loop) I can refer to the tag as tag and the list of sites that use that tag as site.tags[tag].
I use this on my blog:
https://github.com/kelsin/kelsin.github.io/blob/master/tags/index.html
The rest of the code is just how I've chosen to make a tag cloud on my tag page. Hope this helps someone else looking for a solution (without plugins)!