Access site or post variables inside a jekyll converter - ruby

In liquid, I can access, say, {{ post.title }} or any variables that are defined in the markdown. How can I get at the equivalent of {{ post.title }} from a converter or a generator or a filter?

from a converter
No way. They are just converting content to hmtl.
from a generator
In the Generate method you receive the site object, you can then loop in site.pages and site.posts.
from a filter
Depending on what you pass to the filter you can get any page/post variable
module Jekyll
module MyFilters
def filter_name(page)
# do whatever with the page variables
end
end
end
Liquid::Template.register_filter(Jekyll::MyFilters)
Example call : {{ page | filter_name }}

Related

Custom liquid block retrieving context variables

How can I retrieve an assigned variable from within a custom Liquid Block in jekyll markdown?
I've read using assignments in templates, but I'm clearly missing something simple.
EDIT: This is only happens with Jekyll variables, not basic variables set in liquid.
Does not work:
The liquid block in markdown:
{% assign variable = site.variables | where "someval", "true" %}
{% customblock variable %}
{% endcustomblock %}
The Jekyll Plugin:
module Jekyll
module Tags
class CustomBlockTag < Liquid::Block
def initialize(tag_name, variable_name, options)
super
#variable = variable_name
end
def render(context)
puts context.scopes
puts context.key?(#variable)
puts context.find_variable(#variable)
puts context[#variable]
end
end
end
end
Liquid::Template.register_tag('customblock', Jekyll::Tags::CustomBlockTag)
The output:
{..., "variable"=> {<another map>} }
true
<blank>
<blank>
I don't use ruby very often, but as far as I can see in the source, it's just wrapping a dictionary lookup. What am I missing here?
The following works fine
The liquid block in markdown:
{% assign variable = "1" %}
{% customblock variable %}
{% endcustomblock %}
The Jekyll Plugin:
module Jekyll
module Tags
class CustomBlockTag < Liquid::Block
def initialize(tag_name, variable_name, options)
super
#variable = variable_name
end
def render(context)
puts context.scopes
puts context.key?(#variable)
puts context.find_variable(#variable)
puts context[#variable]
end
end
end
end
Liquid::Template.register_tag('customblock', Jekyll::Tags::CustomBlockTag)
The output:
{..., "variable"=>"1"}
true
1
1
There were a few red herrings that turned out to be the issue:
my variable is actually a Drop and is not a normal hash value. Specifically, it's a DocumentDrop which delegates to_s to the Document class.
The implementation of to_s prints out either the Document's output, content, or "NO CONTENT".
In my case, output and content were either a space or newline character, so that's all that was output. This is because the files only exist for their front matter, so there's no actual content.
The front matter is accessed via the Drop interface. So I was actually getting variable, there was just an empty string representation of it.
Without further ado, to access the front matter data: context[#variable]["my-front-matter-data"]
Well at least I feel less like a ruby newbie now.
Try changing {% assign variable = site.variables | where "someval", "true" %} to {% assign variable = site.variables | where: "someval", "true" %}.
Also, it doesn't look like variable is an option according to the docs. If you want to pass more information you'll need to use something like site.data or information defined in your jekyll initializer (usually called _config.yml).

Using a Volt::Model as a dictionary of Key/Value pairs

I would like to use a Volt::Model as a reactive dictionary in a view.
Ideally, I would like something like this:
<dl>
{{ dictionary.each do |key, val| }}
<dt>Term: {{ key }}</dt>
<dd>Definition: {{ val }}</dd>
{{ end }}
</dl>
Is this possible in Volt without using an ArrayModel?
Sorry, I don't have .each_pair working in bindings in Volt yet, its on the todo list. Yea, you can use .keys.each do |key| in the mean time.
Figured it out. This works:
<dl>
{{ dictionary.keys.each do |key| }}
<dt>Term: {{ key }}</dt>
<dd>Definition: {{ dictionary.get(key) }}</dd>
{{ end }}
</dl>
The version in the question should almost work. You just have to use each_pair instead of each.

Is it possible to generate a top-level index.html with Jekyll?

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.

Including external files in a Jekyll template

Is it possible to include a html file from another domain inside a Jekyll template? And if so what would the syntax be?
I'm not a Ruby or Jekyll developer, more or less asking on behalf of another so please forgive me if the answer is obvious! At least I couldn't quite find the answer with some initial research.
In essence we're trying to pull the markup of a footer from another domain, this is how production will work so we're actually just trying to simulate it in our template deliverables.
Cheers
You cannot do this within the template itself. However, you could define a custom Liquid tag that scrapes the markup of the remote page, and then put that tag into template. This would be in a file called e.g. plugins/remote_footer.rb
require 'nokogiri'
require 'open-uri'
require 'uri'
module Jekyll
class RemoteFooterTag < Liquid::Tag
def initialize(tag_name, markup, tokens)
#markup is what is defined in the tag. Lets make it a URL so devs
#don't have to update code if the URL changes.
url = markup
#check if the URL is valid
if url =~ URI::regexp
#grab the remote document with nokogiri
doc = Nokogiri::HTML(open(url))
#search the document for the HTML element you want
#node = doc.at_xpath("//div[#id='footer']")
else
raise 'Invalid URL passed to RemoteFooterTag'
end
super
end
def render(context)
output = super
if #node
node.to_s
else
"Something went wrong in RemoteFooterTag"
end
end
end
end
Liquid::Template.register_tag('remote_footer', Jekyll::RemoteFooterTag)
And then in your template:
{% remote_footer http://google.com %}
I threw this together quickly and didn't check if it runs, but hopefully it's enough to work with. Keep in mind that this will run once when the liquid parser runs on the page, and if the remote element changes that will not be reflected until the Jekyll site is rebuilt.
I just stumbled into this problem and I couldn't find any working solution addressing all the use cases I had so I wrote my own plugin.
N.B. This is the first piece of ruby I ever wrote.
require 'nokogiri'
require 'open-uri'
require 'uri'
class Jekyll::IncludeRemoteTag < Jekyll::Tags::IncludeTag
##remote_cache = {}
def initialize(tag_name, markup, tokens)
super
#url = #file
end
def validate_url(url)
if url !~ URI::regexp
raise ArgumentError.new <<-eos
Invalid syntax for include_remote tag. URL contains invalid characters or sequences:
#{url}
Valid syntax:
#{syntax_example}
eos
end
end
def syntax_example
"{% #{#tag_name} http://domain.ltd css=\".menu\" xpath=\"//div[#class='.menu']\" param=\"value\" param2=\"value\" %}"
end
def render(context)
#url = render_variable(context) || #url
validate_url(#url)
if #params
validate_params
#params = parse_params(context)
end
xpath = #params['xpath']
css = #params['css']
if ! html = ##remote_cache["#{#url}_#{xpath}"]
# fetch remote file
page = Nokogiri::HTML(open(#url))
# parse extract xpath/css fragments if necessary
node = page.at_xpath(xpath) if xpath
node = page.css(css) if css
node = page if !node
raise IOError.new "Error while parsing remote file '#{#url}': '#{xpath||css}' not found" if !node
# cache result
html = ##remote_cache["#{#url}_#{xpath}"] = node.to_s
end
begin
partial = Liquid::Template.parse(html)
context.stack do
context['include'] = #params
partial.render!(context)
end
rescue => e
raise Jekyll::Tags::IncludeTagError.new e.message, #url
end
end
end
Liquid::Template.register_tag('include_remote', Jekyll::IncludeRemoteTag)
And you'd use it like this:
<!-- fetch header.html -->
{% assign url = 'http://mything.me/_includes/header.html' %}
{% include_remote {{ url }} %}
<!-- fetch menu.html and extract div.menu -->
{% include_remote 'http://mything.me/_includes/menu.html' css="div.menu" links=site.data.menu %}
<!-- fetch menu.html and extract div.menu (xpath version) -->
{% include_remote 'http://mything.me/_includes/menu.html' xpath="div[#class='menu']" links=site.data.menu %}
It basically works exactly like a normal include file but it's remote.
Available for download here: https://gist.github.com/kilianc/a6d87879735d4a68b34f
License MIT.
You can include Liquid tag plugin for Jekyll, it worked for me.
Add this in your site's Gemfile:
group :jekyll_plugins do
gem 'jekyll-remote-include', :github => 'netrics/jekyll-remote-include'
end
Use the tag in your posts as follows:
{% remote_include https://raw.githubusercontent.com/jekyll/jekyll/master/README.markdown %}
Or
{% capture products %}
{% remote_include http://localhost:8888/products.json %}
{% endcapture %}
...
{{ products }}

How to make a value available in all Liquid templates

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

Resources