I'm having trouble with getting some nested liquid tags to compile if the outer tag contains multiple sibling liquid tags. This works:
{% container %}
{% inner %}
Stuff Goes in here
{% endinner %}
{% endcontainer %}
but this doesn't
{% container %}
{% inner %}
Stuff Goes in here
{% endinner %}
{% inner %}
Stuff Goes in here
{% endinner %}
{% endcontainer %}
I get the following error:
Liquid Exception: Liquid syntax error (line 1): 'container' tag was never closed in /.../_posts/blah.markdown/#excerpt
Liquid Exception: Liquid syntax error (line 1): 'container' tag was never closed in _includes/head.html, included in _layouts/default.html
Notice that #excerpt in the first error? If I add an excerpt into the front matter. Everything works fine. My head.html include is the default one with a new jekyll site:
<meta name="description" content="{% if page.excerpt %}{{ page.excerpt | strip_html | strip_newlines | truncate: 160 }}{% else %}{{ site.description }}{% endif %}">
Removing the if statement in the head will make the error go away too. I'm completely confused as to why having multiple siblings would cause this error. Here's my simplified plugin code:
module Jekyll
class RenderContainer < Liquid::Block
def initialize(tag_name, contain, tokens)
super
end
def render(context)
"<div class=\"container\">#{super}</div>"
end
end
class RenderInner < Liquid::Block
def initialize(tag_name, contain, tokens)
super
end
def render(context)
"<div class=\"inner\">#{super}</div>"
end
end
end
Liquid::Template.register_tag('container', Jekyll::RenderContainer)
Liquid::Template.register_tag('inner', Jekyll::RenderInner)
I just ran into the same issue and apparently this is a known bug. I tried your plugin and the liquid tag that you can't get to work above:
{% container %}
{% inner %}
Stuff Goes in here
{% endinner %}
{% inner %}
Stuff Goes in here
{% endinner %}
{% endcontainer %}
... and once I added the recommended fix (see below) to my config.yml, the code worked after that.
excerpt_separator: ""
Note, be sure to restart Jekyll serve after adding this bit to the config file.
Related
I'm using Jekyll, and have stored some information in _data/some_data.yml, which contains data like this:
---
links:
- http://example.org/some/data
- http://example.org/some/other/data
Another record, _data/more_data.yml, contains data like this:
---
links: http://example.com
A third record, _data/yet_more_data.yml, contains data like this:
---
links:
"A title-based link": http://example.net
"Another title-based link": https://example.net/some/other/page
I will be parsing the data sources in the template, like this:
<pre>
{% for a_record in site.data %}
Parsing: {{ a_record[0] }}
Links:
<< PSUDOCODE >>
{% if a_record[1].links is a string %}
* [{{ a_record[1].links }}]({{ a_record[1].links }})
{% elseif a_record[1].links is an array %}
{% for link in a_record[1].links %}
* [{{ link }}]({{ link }}]
{% endfor %}
{% elseif a_record[1].links is a hash %}
{% for link in a_record[1].links %}
* [{{ link[0] }}]({{ link[1] }})
{% endfor %}
{% endif %}
<< /PSUDOCODE >>
- End Record -
{% endfor %}
</pre>
How can I work out if I'm looking at a string, an array or a hash?
I started writing a plugin to support this check, but it's not working out the way I expected - at all!
module Jekyll
module IsAFilter
def is_a_string(value)
if not value.instance_of?(::Hash)
if not value.instance_of?(::Array)
if value.instance_of?(::String)
return true
end
end
end
return false
end
def is_a_hash(value)
if not value.instance_of?(::Array)
if value.instance_of?(::Hash)
return true
end
end
return false
end
def is_an_array(value)
if not value.instance_of?(::Hash)
if value.instance_of?(::Array)
return true
end
end
return false
end
end
end
Liquid::Template.register_filter(Jekyll::IsAFilter)
To use that, I imagined that I could do {% if a_record[1].links | is_a_string %}{{ a_record[1].links }}{% endif %} but that always renders it as a string, even if it's an array or a hash?
And, before it's suggested, yes, I could say "you must always use the YAML hash format", but once I've finished writing this, I'm going to be handing control of the data to someone else, so I want to be sure that whatever they end up putting in there, it'll work.
In ruby you can use .is_a? DataType. E.g.
[1] pry(main)> "hello".is_a? String
=> true
[2] pry(main)> {key: 1}.is_a? Hash
=> true
[3] pry(main)> [1,2,3].is_a? Array
=> true
I had it so close! Thanks to #bashford7's answer, I amended my code to use the form value.is_a? String, but the problem was actually in my understanding of Jekyll...
I had to use this stanza:
{% assign check_variable = some_variable | some_filter_that_returns_true %}
{% if check_variable %}
USE THIS STANZA - IT WORKS
{% endif %}
Instead of what I assumed I could use, which was this stanza:
{% if some_variable | some_filter_that_returns_true %}
DON'T USE THIS STANZA - IT DOESN'T WORK
{% endif %}
My final plugin, if it's useful, looks like this:
module Jekyll
module IsAFilters
def is_a(value)
return value.inspect
end
def is_a_string(value)
if value.is_a? String
return true
end
return false
end
def is_a_hash(value)
if value.is_a? Hash
return true
end
return false
end
def is_an_array(value)
if value.is_a? Array
return true
end
return false
end
end
end
Liquid::Template.register_filter(Jekyll::IsAFilters)
I know there is a contains keyword so I can use:
{% if some_string contains sub_string %}
<!-- do_something -->
{% ... %}
But how can I check if a string ends with a particular substring?
I've tried this but it won't work:
{% if some_string.endswith? sub_string %}
<!-- do_something -->
{% ... %}
With Jekyll, I ended up writing a small module-wrapper that adds a filter:
module Jekyll
module StringFilter
def endswith(text, query)
return text.end_with? query
end
end
end
Liquid::Template.register_filter(Jekyll::StringFilter)
I use it like this:
{% assign is_directory = page.url | endswith: "/" %}
As a workaround you could use the string slice method
with as startIndex: some_string length - sub_string length and
stringLength: sub_string size
and if the result of the slice is the same as the sub_string -> the sub_string is at the end of some_string.
it's a bit clumpy in a liquid template but it would look like:
{% capture sub_string %}{{'subString'}}{% endcapture %}
{% capture some_string %}{{'some string with subString'}}{% endcapture %}
{% assign sub_string_size = sub_string | size %}
{% assign some_string_size = some_string | size %}
{% assign start_index = some_string_size | minus: sub_string_size %}
{% assign result = some_string | slice: start_index, sub_string_size %}
{% if result == sub_string %}
Found string at the end
{% else %}
Not found
{% endif %}
and if the some_string is empty or shorter than sub_string it works anyway because the slice result would be empty as well
We can use another solution with split filter.
{%- assign filename = 'main.js' -%}
{%- assign check = filename | split:'js' -%}
{% if check.size == 1 and checkArray[0] != filename %}
Found 'js' at the end
{% else %}
Not found 'js' at the end
{% endif %}
Here we go ^^.
Expanding the answer by #v20100v
It would be best to get the last item in the array after splitting, as the string may have multiple occurrences of the separator.
e.g., "test_jscript.min.js"
Something like the below:
{% assign check = filename | split:'.' | last %}
{% if check == "js" %}
Is a JS file
{% else %}
Is not a JS file
{% endif %}
I'm trying to create a static site using Jekyll, with content imported from Blogger. One of the things I want to do is have monthly archives, which I've been able to successfully generate using sample code I found:
module Jekyll
class ArchiveGenerator < Generator
safe true
def generate(site)
if site.layouts.key? 'archive_index'
site.posts.group_by{ |c| {"month" => c.date.month, "year" => c.date.year} }.each do |period, posts|
posts = posts.sort_by { |p| -p.date.to_f }
archive_dir = File.join(period["year"].to_s(), "%02d" % period["month"].to_s())
paginate(site, archive_dir, posts)
end
site.posts.group_by{ |c| {"year" => c.date.year} }.each do |period, posts|
posts = posts.sort_by { |p| -p.date.to_f }
archive_dir = period["year"].to_s()
paginate(site, archive_dir, posts)
end
end
end
def paginate(site, dir, posts)
pages = Pager.calculate_pages(posts, site.config['paginate'].to_i)
(1..pages).each do |num_page|
pager = Pager.new(site, num_page, posts, pages)
archive_path = "/" + dir
path = archive_path
if num_page > 1
path += "/page/#{num_page}"
end
newpage = ArchiveIndex.new(site, site.source, path, archive_path, posts)
newpage.pager = pager
site.pages << newpage
end
end
end
class ArchiveIndex < Page
def initialize(site, base, dir, base_path, posts)
#site = site
#base = base
#dir = dir
#name = 'index.html'
self.process(#name)
self.read_yaml(File.join(base, '_layouts'), 'archive_index.html')
self.data['base'] = base_path
end
end
end
What I'd like is for the top-level index.html file to be the monthly archive for the most recent month for which there are posts. However, I've tried various ways to specify the top-level directory as the target for the generated file, but have been unsuccessful. Is this possible in Jekyll?
You don't even need a plugin written in Ruby (which the code you posted in your question is) to display the post for the most recent month on the front page.
It's quite easy to do this in Liquid, which means that it will work on GitHub pages (the code in your question won't, because it's a plugin):
<ul>
{% for post in site.posts %}
{% assign currentmonth = post.date | date: "%B %Y" %}
{% if prevmonth == null or currentmonth == prevmonth %}
<li>{{ post.title }}</li>
{% else %}
{% break %}
{% endif %}
{% assign prevmonth = currentmonth %}
{% endfor %}
</ul>
Short explanation:
We're looping the posts, getting the month and year from each date
(post.date | date: "%B %Y" will return something like "July 2014")
The post will be displayed when the month/year of the current post is equal to the month/year of the previous post
month/year of the previous post will be empty in the beginning (prevmonth == null), so we'll always display the first post
when we reach the first post from a previous month, we'll exit the loop (with {% break %} - note that {% break %} wasn't supported in earlier Liquid versions)
Bonus:
If you can live with the monthly archives for all months on one single page, it's possible to generate that without plugins as well, with a similar approach.
I want to make a navigation sorted by weight on my Jekyll site. I'm using this plugin, but I want to show in navigation only pages with weight, instead of showing pages without weight at the end of the list.
So I changed the plugin like this:
module Jekyll
class WeightedPagesGenerator < Generator
safe true
def generate(site)
site.pages.each do |page|
if page.data["weight"] != nil
site.config['weighted_pages'] = site.pages.sort_by { |a|
a.data['weight'] }
end
end
end
end
end
But I get an error: Generating... /_plugins/sorted_navigation.rb:10:in `sort_by': comparison of NilClass with 2 failed (ArgumentError).
Any idea how to make this work?
Since Jekyll 2.2.0 you can sort an array of objects by any object property. Sorting by weight is now possible and far more efficient than older solutions (see https://stackoverflow.com/a/25513956/1548376)
I ended up not using this plugin. Instead I use liquid tags from this answer and now my navigation looks like this:
<nav>
<ul>
{% for weight in (1..5) %}
{% unless p.weight %}
{% for p in site.pages %}
{% if p.weight == weight %}
<li><a {% if p.url == page.url %}class="active"{% endif %} href="{{ p.url }}" title="{{ p.title }}">{{ p.title }}</a></li>
{% endif %}
{% endfor %}
{% endunless %}
{% endfor %}
</ul>
</nav>
I'm using Liquid with Sinatra and would like to make a certain value (Sinatra::Application.environment, specifically) available in all templates without defining it as a local in every get/post. Like so:
In app.rb (my main application file):
# nothing in here about the variable
get '/some/route' do
# or here
liquid :my_template
end
In app.rb--my main application file, or something I can require/include:
some_awesome_technique do
def app_env
Sinatra::Application.environment
end
end
In any template:
<p>
{% if environment == :development %}
Never see this in production
{% end %}
</p>
<!-- or even -->
<p>
{% if dev_mode %}
Or this...
{% endif %}
</p>
I don't really care about the implementation as long as I don't have to put redundant code in every route. Thanks in advance!
Something like this will work
before do
#env = Sinatra::Application.environment
end
then in your template:
{% if #env == :development %}
Boo!
{% endif %}