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)
Related
I need to do something apparently very simple:
typedef enum {
{% for e in mylist %}
{{ e }} = 0x{{ '%04X' % (1 << loop.index0) }},
{%- endfor %}
ALL = 0x0FFF
} Sound_Region_t;
but this bombs with "jinja2.exceptions.TemplateSyntaxError: unexpected '<'"
Intention is to get something like:
typedef enum {
foo = 0x0001,
bar = 0x0002,
fie = 0x0004,
fom = 0x0008,
...,
ALL = 0x0FFF
} Sound_Region_t;
I.e.: value is a "walking bit" so I can "bitwise or" together them.
Same behavior if I try to use other variations including "{% with bit = 1 << loop.index %}" or similar.
What am I missing?
Jinja2 does not allow bitwise operators inside the templates, so we need to create a small global function that executes such operators and returns a number:
def leftshift(amount):
return 1 << amount
# Get or create a Jinja2 environment
from jinja2 import Environment
env = Environment(...)
# Add leftshift function to the global context
env.globals['leftshift'] = leftshift
And in the templates now we can call leftshift function with the loop index:
typedef enum {
{% for e in mylist %}
{{ e }} = 0x{{ '{:04X}'.format(leftshift(loop.index0))}},
{%- endfor %}
ALL = 0x0FFF
} Sound_Region_t;
I have tried the if condition based on the value defined in the django template
{% if randomgen == 2 %}
<p style="float:right;text-align: center;padding:5px 5px;"><b>{% randomgen %}1</p>
{% else %}
<p style="float:right;text-align: center;padding:5px 5px;"><b>{% randomgen %} 2</p>
{% endif %}
the randomgen is defined to pick in random between 1 and 2 and the value is being displayed correctly in tag but irrespective of the value it always going to else condition
register = template.Library()
#register.tag(name="randomgen")
def randomgen(parser, token):
items = []
bits = token.split_contents()
for item in bits:
items.append(item)
return RandomgenNode(items[1:])
def render(self, context):
arg1 = 0
arg2 = 10
if "float" in self.items:
result = random.randint(1,20)
elif not self.items:
result = random.randint(1,20)
else:
result = random.randint(1,2)
return result
In your HTML, set randomgen to another variable:
{% randomgen as rgen %}
Then use the newly set variable for your conditional:
{% if rgen == 2 %}
Honestly, I was surprised your code didn't work as your usage makes sense intuitively. Knowing that it doesn't work though, my guess is the template is comparing a function with an integer which is always going to return False. Good question!
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.
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 %}
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...