Jekyll: can't sort collection by date - jekyll

This is making me crazy.
I have this collection resources:
# _config.yml
collections:
resources:
output: true
permalink: /resources/:name/
They all have dates:
# /_resources/example.md
---
title: Learn the Web
date: 09-04-2013
---
The pages get generated, and if I try to display it's date, it is displayed correctly, but I also want to sort those by date, and it just doesn't work. What am I doing wrong?
{% assign sortedResources = site.resources | sort: 'date' %} <!-- Doesn't work -->
{% for resource in sortedResources %}
<div>
{{resource.title}}
<small>{{resource.date | date: "%d %b %Y"}}</small> <!-- Works -->
</div>
{% endfor %}
I'm using:
▶ ruby --version
ruby 2.1.4p265 (2014-10-27 revision 48166) [x86_64-linux]
▶ jekyll --version
jekyll 2.5.3
Thanks

If your Collection items have a valid date (ISO 8601 format) in the front matter they'll be sorted by date automatically, oldest first.
If you'd like to output more recent items first you can reverse the order like so:
{% assign sorted = site.resources | reverse %}
{% for item in sorted %}
<h1>{{ item.name }}</h1>
<p>{{ item.content }}</p>
{% endfor %}

I'm currently experiencing the same problem with collections.
While trying to sort on European formatted dates like dd/mm/yyyy or dd-mm-yyyy, I get a string sort. Even when the timezone: Europe/Paris is set in the _config.yml file.
The only way to get a collection sorted by date is to use ISO format yyyy-mm-dd.
# /_resources/example.md
---
title: Learn the Web
date: 2013-04-09
---
And the sort is now working.
Edit - This is how jekyll manages 'dates':
date: "2015-12-21" # String
date: 2015-12-1 # String D not zero paded
date: 01-12-2015 # String French format
date: 2015-12-01 # Date
date: 2015-12-21 12:21:22 # Time
date: 2015-12-21 12:21:22 +0100 # Time
If you don't need Time you can stick to the date: YYYY-MM-DD format.
And you have to be consistent across your collection. If you mix String, Date and/or Time Liquid will throw an error like Liquid error: comparison of Date with Time failed or Liquid error: comparison of String with Date failed

I got it: the resources where sorted by the date string (eg. 19-06-2015) which was not correct.
I created my custom filter instead:
# _plugins/filters.rb
module Jekyll
module DateFilter
require 'date'
def date_sort(collection)
collection.sort_by do |el|
Date.parse(el.data['date'], '%d-%m-%Y')
end
end
end
end
Liquid::Template.register_filter(Jekyll::DateFilter)
Used like so:
{% assign sortedResources = site.resources | date_sort | reverse %}
{% for resource in sortedResources %}
<div>{{resource.title}}</div>
{% endfor %}

Related

saltstack state file: how to access a list element in a salt grain via Jinja

I try to evaluate the value of a grains list in jinja but do not know how. The list entry I am looking for is the minor osversion in
grain:osrelease_info
salt-call -g |grep -C2 osrelease_info
osrelease:
15.99
osrelease_info:
- 15
- 99
In a state-file I would like to evaluate the minior osrelease value in a jinja expression like this one:
{% if grains['osmajorrelease'] == '15' and grains['osrelease_info'][1] >= 99 %}
...
{% endif %}
However the syntax I tried above to access index:1 of the osrelease_info list does not report an error but doesn't work either.
So how can I access the list entry containing value "99" in Jinja?
I think I found the answer myself alltough it is a little strange. The problem was not the expression
{% ... and grains['osrelease_info'][1] >= 99 %}
but instead the string comparison before:
{% if grains['osmajorrelease'] == '15' ... %}
All I changed was to convert the string '15' to an int. So the following expression does what I originally wanted:
{% if grains['osmajorrelease'] == 15 and grains['osrelease_info'][1] >= 99 %}
...
{% endif %}
This is weird since I use exactly the same comparision (grains['osmajorrelease'] == '15') in other state files with success.

Get value using mine.get from a ordered dictionary in state.sls

I have a mine defined in pillar to get the ip of a salt minion. When I use the mine in a salt state I get a dictionary. Is there a way to filter out and get the first item from it.
Here is my mine which I have defined in a pillar:
ip_add:
- mine_function: grains.get
- ipv4
Here is my state file:
{% set id = 'aws-vm1' %}
{% set addrs = salt['mine.get'](id, 'ip_add') %}
{% for key in salt['mine.get'](id, 'ip_add') %}
Test:
file.append:
- name: C:\test
- text: {{addrs}}
The output i get is :
OrderedDict([('aws-vm1', ['10.93.143.235', '127.0.0.1'])])
I want to get the first ip so that I can share it between minions
I was able to find a work around. I get the list of IPs from the mine
aws-vm1:
----------
aws-vm1:
- 10.93.143.235
- 127.0.0.1
and grab the first value by {{ip[0]}} which is the private IP I want.
{% set id = 'aws-vm1' %}
# Returns a dict OrderedDict
{% set ip_list = salt['mine.get'](id, 'ip_add') %}
{% set ip= ip_list.values() | first %}
Test:
file.append:
- name: C:\test
- text: {{ip[0]}}

Check if variable type is hash or array in liquid

I am attempting to write a somewhat generic layout that can take as a parameter either an array of strings or a hash of options, so you can either do:
option:
- "<li><b>One:</b> This is</li>"
- "<li><b>Two:</b> Raw HTML</li>"
Or you can do:
option:
One: This is
Two: a mapping
The reason I want to support both of these is that this is a public layout and the first option is already supported, but I would prefer to use the second option, so I want something of a deprecation period where both versions are supported.
I saw in check if a variable is type of string or array in liquid that there's a way to determine if something is an array or a string, but both arrays and hashes have a first attribute! A practical way to re-use this function might be to check if the first element of the variable also has a first attribute, like so:
{% if site.option.first %}
{% if site.option.first.first %}
hash
{% else %}
array
{% endif %}
{% else %}
Something else!
{% endif %}
But this seems a bit unwieldy and a bit of a hack - plus it will give the wrong answer if passed an array of arrays (even though "array of arrays" is not considered a valid input in this case). Is there a better way to do this?
For arrays you know will not include numbers, you can use the following:
---
arr:
- ""
- "2"
- three
- null
hash:
foo: bar
baz: null
"0": 1
string: "a string"
---
nil: {{ page.nil_prop | map: "" | join: "," | size }} # 0
str: {{ page.string | map: "" | join: "," | size }} # 0
hash: {{ page.hash | map: "" | join: "," | size }} # 0
arr: {{ page.arr | map: "" | join: "," | size }} # 3
However, if a number sneaks into your array, you get Liquid Exception: no implicit conversion of String into Integer.
This is on Jekyll 3.8.

Concatenate arrays in liquid

I am trying to concatenate three arrays in liquid/jekyll but in the final array (publications) I get only the elements of the first one (papers)
{% assign papers = (site.publications | where:"type","paper" | sort: 'date') | reverse %}
{% assign posters = (site.publications | where:"type","poster" | sort: 'date') | reverse %}
{% assign abstracts = (site.publications | where:"type","abstract" | sort: 'date') | reverse %}
{% assign publications = papers | concat: posters | concat: abstracts %}
What am I missing?
New answer
Jekyll now uses Liquid 4.x. So we can use the concat filter !
Old answer
concat filter is not part of current liquid gem (3.0.6) used by jekyll 3.2.1.
It will only be available in liquid 4 (https://github.com/Shopify/liquid/blob/v4.0.0.rc3/lib/liquid/standardfilters.rb#L218).
I will probably be available for Jekyll 4.
In the meantime, this plugin can do the job :
=begin
Jekyll filter to concatenate arrays
Usage:
{% assign result = array-1 | concatArray: array-2 %}
=end
module Jekyll
module ConcatArrays
# copied from https://github.com/Shopify/liquid/blob/v4.0.0.rc3/lib/liquid/standardfilters.rb
def concat(input, array)
unless array.respond_to?(:to_ary)
raise ArgumentError.new("concat filter requires an array argument")
end
InputIterator.new(input).concat(array)
end
class InputIterator
include Enumerable
def initialize(input)
#input = if input.is_a?(Array)
input.flatten
elsif input.is_a?(Hash)
[input]
elsif input.is_a?(Enumerable)
input
else
Array(input)
end
end
def concat(args)
to_a.concat(args)
end
def each
#input.each do |e|
yield(e.respond_to?(:to_liquid) ? e.to_liquid : e)
end
end
end
end
end
Liquid::Template.register_filter(Jekyll::ConcatArrays)

How to iterate through the top 3 posts from the last two weeks in liquid/jekyll?

Let me add some context: i'm trying to automate my boring reports to management using jekyll!
I'd like to write a post for every activity i do and, every week, ship the compiled report with the "top 3 highlights" from this week and from last week.
My shot at it, as a jekyll newbie, was adding the week of publishing and if that post is a highlight or not in the yaml front matter:
---
layout: event
title: "Gave a Jelyll Talk!"
date: 2015-04-23 16:05:04
highlight: week
week: 17
---
And get the last two weeks in the template, like this:
---
layout: email
---
Here are my activities from the last two weeks:
{% assign hls = (site.posts | where: "highlight" , "week") %}
{% assign weeks = (hls | group_by: "week") %}
{% assign thisw = weeks[0] %}
{% assign lastw = weeks[1] %}
<h1>Week #{{thisw.name}}</h1>
<ul>
{% for post in thisw.items %}
<li>{{post.title}}</li>
{% endfor %}
</ul>
<h1>Week #{{lastw.name}}</h1>
<ul>
{% for post in lastw.items %}
<li>{{post.title}}</li>
{% endfor %}
</ul>
That kinda works, but, i'd like to ask:
Is it possible to automatically calculate the current week, get only posts from the past two weeks (not future or prior), only the latest 3 highlights from each week and avoid repeating the html template?
I would consider writing a plugin for Jekyll, something like this:
WeeklyHighlights.rb
module Jekyll
class WeeklyHighlights < Generator
safe true
priority :high
def week_id(time)
# to handle turn of year properly
return time.strftime('%Y-%W')
end
def generate(site)
# hash (dict) to store highlights grouped by week number
highlights_by_week = {}
today = Time.now
# initialize recent weeks with empty highlights
(0 .. 4).each do |i|
w = week_id(today - i)
highlights_by_week[w] = []
end
# group highlights according to week number
site.posts.each do |post|
if post['highlight'] != 'week'
next
end
week = week_id(post.date)
highlights_by_week[week] ||= []
highlights_by_week[week] << post
end
# make array of arrays of highlights, in the required order
weekly_highlights = []
highlights_by_week.keys.sort.reverse.each do |w|
highlights_by_week[w].sort! {|a,b| b.date <=> a.date} # = hl.sort{|a,b| b.date <=> a.date }
weekly_highlights << {'id' => w, 'hls' => highlights_by_week[w]}
end
# store prepared highlights
site.config['weekly_highlights'] = weekly_highlights
end
end
end
Put the file in the plugins directory (typically plugins or _plugins in the root of Jekyll site), and then in the template:
{% for week in site.weekly_highlights limit:2 %}
<h2>week {{week.id}}</h2>
<ol>
{% for p in week.hls limit:3 %}
<li>{{p.title}}</li>
{% endfor %}
</ol>
{% endfor %}
More on Jekyll plugins: official documentation
Yep... that should work.
At least you can get the current (i.e. build) time with {{ site.time }} and format it with a liquid filter to get the week number like so:
{% assign current_week = site.time | date: "%w" | plus: 0 %}
(You need to apply the plus: 0 filter too, to get a number instead of a string assigned.) Now things get nasty, as you might want to consider turn of year properly... but it should be doable.
I can think of getting strings like YEAR-WEEKNUMBER for the last three weeks, assign all posts with same date string to a new variable, say posts_lastweek and so on. Then, loop over the posts, compare and limit the for loop ({% for post in posts_lastweek | limit: 3 %}) or work with counters to limit the output.
Let me know if that works for you...