Pass multiple arguments to custom plugin in jekyll - ruby

I'm working with jekyll to make a website.
And I built a custom table of content plugin with Ruby.
Here is the code:
require 'nokogiri'
module Jekyll
module TOCGenerator
TOC_CONTAINER_HTML = '<ul>%1</ul>'
def toc(html,op)
toc_top_tag = "h1"
item_number = 0
toc_html = ''
sub_url = html['url']
doc = Nokogiri::HTML(html['content'])
doc.css(toc_top_tag).each do |tag|
toc_html += create_level_html(sub_url, tag['id'], tag.text)
item_number += 1
end
return '' unless item_number > 0
if 0 < item_number
toc_table = TOC_CONTAINER_HTML
.gsub('%1', toc_html)
end
end
private
def create_level_html(url, anchor_id, tocText)
link = '%3'
.gsub('%1', url)
.gsub('%2', anchor_id.to_s)
.gsub('%3', tocText)
'<li>%1</li>'
.gsub('%1', link)
end
end
end
Liquid::Template.register_filter(Jekyll::TOCGenerator)
And in some document:
<div>
{{ page | toc }}
</div>
It works well.
To enhance its feature, I would like to add some argument to render toc. So I added argument head of the function just like this.
def toc(html,option)
But when I call the function in jekyll template, an error occurs like this:
Liquid Exception: Liquid error (line 41): wrong number of arguments (given 1, expected 2) in /_layouts/default.html
I have tried {{ (three,1) | toc }}, {{ three, 1 | toc }}, {{ three | 1 | toc }} to call the function with 2 arguments but the results turned out the same way.
How do I call a function in jekyll with multiple arguments?

This answer is unlikely to be relevant to the original poster, but if anyone has come here from Google, like I did, here's how I solved it.
Plugin code:
module Jekyll
module YourFilter
def yourFilter( input, arg1, arg2 )
# your code
end
end
end
Liquid::Template.register_filter(Jekyll::YourFilter)
Tag code in your content:
{{ 'Lorem ipsum' | yourFilter: 'argument 1', 'argument 2' }}
The key thing is that there's a semicolon after the filter name in the tag code. This seems to allow the plugin to parse multiple arguments, and not just the last one.

Related

Jekyll does not render md tables correctly if the table contains liquid tags

I am using a custom Liquid tag that, when building my site from .md files with Jekyll, replaces the value in the tag with a corresponding <span> element from a predefined list (similarly to how it works with custom Docbook entities).
The problem I've encountered is as follows: if this tag is in a table written with Markdown syntax, then this table will not render as such; instead, it transforms into a <p>...</p> element whose textContent is, well, the original markdown-formatted table as plain text. The <span> elements substituted by my Liquid tag are rendered correctly.
If I enclose the md table in {%raw%}{%endraw%}, then the table is rendered correctly except that, obviously, my custom tag is ignored.
Here's the Liquid tag I am using:
module Jekyll
class EntityHandler < Liquid::Tag
##dictionary = {}
file = File.open("./assets/definitions/entities.txt")
strings = file.readlines
strings.each do |s|
key, value = s.split("|")
##dictionary[key] = value.to_s
end
file.close
def initialize(name, input, tokens)
super
#input = input.strip
end
def render(context)
dictionary = ##dictionary
if dictionary.has_key?(#input.to_s)
output = dictionary.fetch_values(#input)
else
output = "<span class='btn-false'>ERROR: entity not found</span>"
end
return output
end
end
end
Liquid::Template.register_tag('ent', Jekyll::EntityHandler)
When used anywhere in .md files, {% ent X %} renders as <span>X</span>, which is what I expect. However, it does not work with .md tables. Thus, if I make a table like this:
| Cell one | Cell two |
| === | === |
| Cell three | {% ent X %} |
it returns:
<p>| Cell one | Cell two || === | === || Cell three | <span>X</span> <!-- actual entity value --> |</p>
instead of a <table/>.

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.

How can I pass multiple params in Jekyll with Liquid?

I have found a Ruby plugin for Jekyll to obfuscate an email address like this in a Jekyll webpage with Liquid
{{ site.email | mailObfuscate }}
However, I would like to pass multiple params to mailObfuscate
I have tried the following
{{ email=site.email, linkText='foo bar' | mailObfuscate }}
However, this gives an error when building my site
Liquid Warning: Liquid syntax error (line 89): Unexpected character = in "{{ email=site.email, linkText='foo bar' | mailObfuscate }}" in privacy.html
Liquid Exception: undefined method gsub' for nil:NilClass in privacy.html
Error: undefined methodgsub' for nil:NilClass
Error: Run jekyll build --trace for more information.
Running the trace gives the following error
1: from D:/Ruby26-x64/lib/ruby/2.6.0/uri/common.rb:103:in escape'
D:/Ruby26-x64/lib/ruby/2.6.0/uri/rfc2396_parser.rb:305:inescape':
undefined method `gsub' for nil:NilClass (NoMethodError)
The complete trace can be found at Pastebin
How can I pass multiple variables?
You need to modify the method to take a 2nd argument, then you can use it as the link text. Try this:
require "base64"
require "uri"
module ObfuscateMailAddress
def mailObfuscate(email_address, link_text )
base64Mail = Base64.strict_encode64(URI::encode(email_address))
# See http://techblog.tilllate.com/2008/07/20/ten-methods-to-obfuscate-e-mail-addresses-compared/
output = "<a href=\"#\" "
output += "data-contact=\"#{base64Mail}\" target=\"_blank\" "
output += "onfocus=\"this.href = 'mailto:' + atob(this.dataset.contact)\">"
output += "<script type=\"text/javascript\">document.write(atob(\"#{base64Mail}\"));</script>#{link_text}</a>"
return output
end
end
Liquid::Template.register_filter(ObfuscateMailAddress)
To pass multiple arguments in your liquid template, the syntax is a bit strange, see documentation. The string on the left side of the pipe automatically get's passed as the first argument to your ruby method, while additional arguments get passed with a colon.
{{ 'test#example.com' | mailObfuscate:'myLinkText' }}
But you also, if you are on Ruby >= 2.3, you can make your method more readable with no need for all the escape characters and better syntax highlighting in your editor if you change your method to use SQUIGGLY HEREDOC for your string definition, explicit return is not required in anycase. For Ruby < 2.3 you can still use regular HEREDOC just replace ~ with - but you just have extra indentation in your string, which is no problem anyway for rendered html.
def mailObfuscate(email_address, link_text )
base64Mail = Base64.strict_encode64(URI::encode(email_address))
ouput = <<~HTML
<a href="#" data-contact="#{base64Mail}" target="_blank"
onfocus="this.href = 'mailto:' + atob(this.dataset.contact)" >
<script type="text/javascript">
document.write(atob("#{base64Mail}"));
</script>
#{link_text}
</a>
HTML
end
And when it is called like this:
puts mailObfuscate('foo#bar.com', 'foobar')
It will render:
<a href="#" data-contact="Zm9vQGJhci5jb20=" target="_blank"
onfocus="this.href = 'mailto:' + atob(this.dataset.contact)" >
<script type="text/javascript">
document.write(atob("Zm9vQGJhci5jb20="));
</script>
foobar
</a>
As a side note, ruby style guide recommends we use snake_case for method names so you might wanna use mail_obfuscate for your method name instead.

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

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

Resources