Including external files in a Jekyll template - ruby

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

Related

Jekyll custom plugin ignored in post.html page

I created a Jekyll tag plugin, which works fine on all template (index/about/contact/default/page) except _layout/post.html. I have tried looking for a solution on Stackoverflow and Google, but I haven't found one yet.
I created a new "products" collection within which my custom Liquid-tag plugin called to render a block of HTML code on post.html. Here's the plugin,
module Jekyll
class AmazonAffiateHelper < Liquid::Tag
def initialize(tag_name, input, tokens)
super
#input = input
end
def render(context)
input_split = split_params(#input)
output = ""
if (input_split.length > 0)
href = input_split[0].strip
src = input_split[1].strip
src2 = input_split[2].strip
href2 = input_split[3].strip
output = "<a href=\"#{href}\" target=\"_blank\"><img border=\"0\" src=\"#{src}\""
output += " width=\"150\" class=\"center-block amazon-fix \"></a>"
output += " <img src=\"#{src2}\" width=\"1\" height=\"1\" border=\"0\""
output += " alt=\"\" style=\"border:none !important; margin:0px !important;\"/>"
output += " <br><br><a href=\"#{href2}\" class=\"btn btn-amazon amazon-fix\">"
output += "BUY ON AMAZON</a>"
end
return output
end
def split_params(params)
return params.split("|")
end
end
end
Liquid::Template.register_tag('amazon', Jekyll::AmazonAffiateHelper)
Resulting output for example is something like this,
Here's is a book you might like, {% amazon variable|variable2|variable3|variable4 %}
And I am not getting anything useful from bundler exec jekyll server --trace
What am I missing? Thanks in advance
PS: I am not a coder, but I am learning.
I have managed to work around it by creating a new jekyll-block plugin. Not sure why I need to do it this in the first place, but it works, and the solution is below - I am sure there must be a better way of doing it.
module Jekyll
class RecommendationBlock < Liquid::Block
def initialize(tag_name, markup, tokens)
super
#input = markup
end
def render(context)
contents = super
# very important for this solution
content = Liquid::Template.parse(contents).render context
output = content
return output
end
end
end
Liquid::Template.register_tag("recommendation", Jekyll::RecommendationBlock)
And then, on the post.html, I loop through products collection
{% for product in site.products limit 5}
# product.content has the custom amazon tag {% amazon variable1|variable2...%}, which is cause the problem
{% recommendation recom }{{ product }}{% endrecommendation %}
{% endfor}
I hope it helps someone if they run into the same problem.
Thanks everyone.

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).

Jekyll/Liquid: Create custom tag/filter with parameters

I'm trying to write a custom tag for my Jekyll-based site that receives a bibtex string and replaces/removes some content.
The tag receives a bibtex string like this:
#article{heineman2001component,
title={Component-based software engineering},
author={Heineman, George T and Councill, William T},
journal={Putting the pieces together},
pages={5},
year={2001}
}
and the ruby code is the following:
module Jekyll
class RenderBibTag < Liquid::Tag
def initialize(tag_name, input, tokens)
super
#input = input
end
def render(context)
output = (#input)#.gsub(/\bjournal\b[\w\s= \{\-\.\,\(\)]+\},/,'')
return output;
end
end
end
Liquid::Template.register_tag('render_bib', Jekyll::RenderBibTag)
Using the tag from the Jekyll template as follows works fine
{%render_bib bibstring %} #bibstring is the string above
When I try to use a Jekyll variable (e.g., page.bibtex which has the bibtex string)
{%render_bib page.bibtex %}
it does not recognise/pass the string.
Any thoughts?
The solution I found uses filters instead of tags
(First time answering my own question)
module Jekyll
module BibFilter
REGEXP = /\bjournal\b[\w\s= \{\-\.\,\(\)\-\:\+\'\/\..]+\},?/
def bibFilter(bib)
#filter text using regexp
str = "#{bib}".gsub(REGEXP,'')
#print filtered text
"#{str}"
end
end
end
Liquid::Template.register_filter(Jekyll::BibFilter)

Jekyll - generating JSON files alongside the HTML files

I'd like to make Jekyll create an HTML file and a JSON file for each page and post. This is to offer a JSON API of my Jekyll blog - e.g. a post can be accessed either at /posts/2012/01/01/my-post.html or /posts/2012/01/01/my-post.json
Does anyone know if there's a Jekyll plugin, or how I would begin to write such a plugin, to generate two sets of files side-by-side?
I was looking for something like this too, so I learned a bit of ruby and made a script that generates JSON representations of Jekyll blog posts. I’m still working on it, but most of it is there.
I put this together with Gruntjs, Sass, Backbonejs, Requirejs and Coffeescript. If you like, you can take a look at my jekyll-backbone project on Github.
# encoding: utf-8
#
# Title:
# ======
# Jekyll to JSON Generator
#
# Description:
# ============
# A plugin for generating JSON representations of your
# site content for easy use with JS MVC frameworks like Backbone.
#
# Author:
# ======
# Jezen Thomas
# jezenthomas#gmail.com
# http://jezenthomas.com
module Jekyll
require 'json'
class JSONGenerator < Generator
safe true
priority :low
def generate(site)
# Converter for .md > .html
converter = site.getConverterImpl(Jekyll::Converters::Markdown)
# Iterate over all posts
site.posts.each do |post|
# Encode the HTML to JSON
hash = { "content" => converter.convert(post.content)}
title = post.title.downcase.tr(' ', '-').delete("’!")
# Start building the path
path = "_site/dist/"
# Add categories to path if they exist
if (post.data['categories'].class == String)
path << post.data['categories'].tr(' ', '/')
elsif (post.data['categories'].class == Array)
path << post.data['categories'].join('/')
end
# Add the sanitized post title to complete the path
path << "/#{title}"
# Create the directories from the path
FileUtils.mkpath(path) unless File.exists?(path)
# Create the JSON file and inject the data
f = File.new("#{path}/raw.json", "w+")
f.puts JSON.generate(hash)
end
end
end
end
There are two ways you can accomplish this, depending on your needs. If you want to use a layout to accomplish the task, then you want to use a Generator. You would loop through each page of your site and generate a new .json version of the page. You could optionally make which pages get generated conditional upon the site.config or the presence of a variable in the YAML front matter of the pages. Jekyll uses a generator to handle slicing blog posts up into indices with a given number of posts per page.
The second way is to use a Converter (same link, scroll down). The converter will allow you to execute arbitrary code on your content to translate it to a different format. For an example of how this works, check out the markdown converter that comes with Jekyll.
I think this is a cool idea!
Take a look at JekyllBot and the following code.
require 'json'
module Jekyll
class JSONPostGenerator < Generator
safe true
def generate(site)
site.posts.each do |post|
render_json(post,site)
end
site.pages.each do |page|
render_json(page,site)
end
end
def render_json(post, site)
#add `json: false` to YAML to prevent JSONification
if post.data.has_key? "json" and !post.data["json"]
return
end
path = post.destination( site.source )
#only act on post/pages index in /index.html
return if /\/index\.html$/.match(path).nil?
#change file path
path['/index.html'] = '.json'
#render post using no template(s)
post.render( {}, site.site_payload)
#prepare output for JSON
post.data["related_posts"] = related_posts(post,site)
output = post.to_liquid
output["next"] = output["next"].id unless output["next"].nil?
output["previous"] = output["previous"].id unless output["previous"].nil?
#write
#todo, figure out how to overwrite post.destination
#so we can just use post.write
FileUtils.mkdir_p(File.dirname(path))
File.open(path, 'w') do |f|
f.write(output.to_json)
end
end
def related_posts(post, site)
related = []
return related unless post.instance_of?(Post)
post.related_posts(site.posts).each do |post|
related.push :url => post.url, :id => post.id, :title => post.to_liquid["title"]
end
related
end
end
end
Both should do exactly what you want.

Nesting two custom Liquid tags that have optional parameters

Is it possible to nest custom Liquid tags written in ruby if one class has multiple optional tokens passed in as parameters? This question is rather hard for me to describe without providing the relevant example. Please excuse me if this question appears to be too specific a use case.
Given the following ruby code, sourced from Octopress (a jekyll fork), which creates a custom Liquid tag to parse tags.
# Title: Simple Image tag for Jekyll
# Authors: Brandon Mathis http://brandonmathis.com
# Felix Schäfer, Frederic Hemberger
# Description: Easily output images with optional class names, width, height, title and alt attributes
#
# Syntax {% img [class name(s)] [http[s]:/]/path/to/image [width [height]] [title text | "title text" ["alt text"]] %}
#
# Examples:
# {% img /images/ninja.png Ninja Attack! %}
# {% img left half http://site.com/images/ninja.png Ninja Attack! %}
# {% img left half http://site.com/images/ninja.png 150 150 "Ninja Attack!" "Ninja in attack posture" %}
#
# Output:
# <img src="/images/ninja.png">
# <img class="left half" src="http://site.com/images/ninja.png" title="Ninja Attack!" alt="Ninja Attack!">
# <img class="left half" src="http://site.com/images/ninja.png" width="150" height="150" title="Ninja Attack!" alt="Ninja in attack posture">
#
module Jekyll
class ImageTag < Liquid::Tag
#img = nil
def initialize(tag_name, markup, tokens)
attributes = ['class', 'src', 'width', 'height', 'title']
if markup =~ /(?<class>\S.*\s+)?(?<src>(?:https?:\/\/|\/|\S+\/)\S+)(?:\s+(?<width>\d+))?(?:\s+(?<height>\d+))?(?<title>\s+.+)?/i
#img = attributes.reduce({}) { |img, attr| img[attr] = $~[attr].strip if $~[attr]; img }
if /(?:"|')(?<title>[^"']+)?(?:"|')\s+(?:"|')(?<alt>[^"']+)?(?:"|')/ =~ #img['title']
#img['title'] = title
#img['alt'] = alt
else
#img['alt'] = #img['title'].gsub!(/"/, '"') if #img['title']
end
#img['class'].gsub!(/"/, '') if #img['class']
end
super
end
def render(context)
if #img
"<img #{#img.collect {|k,v| "#{k}=\"#{v}\"" if v}.join(" ")}>"
else
"Error processing input, expected syntax: {% img [class name(s)] [http[s]:/]/path/to/image [width [height]] [title text | \"title text\" [\"alt text\"]] %}"
end
end
end
end
Liquid::Template.register_tag('img', Jekyll::ImageTag)
What is the best way to create another custom tag for exhibiting the same functionality for the [<img>] element, but nested within a [<figure>] element and perhaps displaying the image alt description or an additional token as a [<figcaption>] element which could potentially include its own link? Or possibly even a series of class names for the element that say whether or not it should be centered or not.
In other words, I might want the output to be something like:
<figure class=center>
<img src="/contra.jpg" alt="One of the greatest nintendo games of all time">
<figcaption>Up Up Down Down Left Right Left Right B A B A Watch on Youtube</figcaption>
</figure>
Am I wrong to assume that it is possible to nest custom Liquid tags? I'm sure I could rewrite the existing code a second time and modify it slightly to handle the additional attribute for [<figcaption>], but this seems rather redundant and against DRY principles. And as it currently stands, I'm rather confused as to how I might account for a possible, additional token given that the existing class takes optional tokens itself.
What I needed to do was to create a Liquid Block, not a Liquid Tag. This solution allows one to nest other Liquid tags and even other Liquid blocks in theory, within the figure which is exactly what one would expect for a [<figure>] tag.
Since Markdown does not currently support HTML5, this Liquid based solution is a nice compromise.
# Example:
#
# {% fig This is my caption! http://site.com/link.html Link Caption %}
# {% img center http://site.com/images/mylinks.png A collection of my favorite links %}
# {% endfig %}
#
# Output:
#
# <figure class='center'>
# <img class="center" src="http://site.com/images/mylinks.png" title="A collection of my favorite links" >
# <figcaption>This is my caption!<a href='http://site.com/link.html'>Link Caption </a></figcaption>
#</figure>
#
#
module Jekyll
class FigureTag < Liquid::Block
include TemplateWrapper
CaptionUrl = /(\S[\S\s]*)\s+(https?:\/\/\S+)\s+(.+)/i
Caption = /(\S[\S\s]*)/
def initialize(tag_name, markup, tokens)
#title = nil
#caption = nil
if markup =~ CaptionUrl
#caption = "\n\t\t<figcaption>#{$1}<a href='#{$2}'>#{$3}</a></figcaption>\n\t"
elsif markup =~ Caption
#caption = "\n\t\t<figcaption>#{$1}</figcaption>\n\t"
end
super
end
def render(context)
output = super
fig = super.join
source = "\t<figure class='center'>\n\t\t"
markdown = RDiscount.new(fig.lstrip).to_html[/<p>(.+)<\/p>/i]
source += $1
source += #caption if #caption
source += "</figure>"
source = safe_wrap(source)
source
end
end
end
Liquid::Template.register_tag('fig', Jekyll::FigureTag)

Resources