I would like to write a preprocessor that operates on a range of markup languages before they're processed into HTML by Jekyll. Ideally the user would simply create a file called _posts/xxyyzz.md.wmd, and Jekyll would preprocess it into xxyyzz.md using a plugin I provide, and then process that into HTML in the usual way.
It looks like Jekyll's Converter framework doesn't allow that, because the output_ext function is only given the final extension "wmd", preventing it from returning ".md" for ".md.wmd", ".textile" for ".textile.wmd", etc.
Is there a way to implement a chain of processing steps like this?
EDIT: grammar
Maybe you can try to use a Generator plugin that uses your wmd converter:
require "yourWmdConverter"
module Jekyll
class ConvertWmd < Jekyll::Generator
def initialize(config)
config['convert_wmd'] ||= true
end
def generate(site)
#site = site
site.posts.docs.each { |post| convertWmd post }
end
private
def convertWmd(post)
post.content = yourWmdConverter post.content
end
end
end
Related
I'm writing a jekyll plugin to create a custom tag. It takes an argument and spits out a string of HTML. I've got it mostly working - I can pass it arguments and get back HTML based on those arguments. Great.
Here's what has me stumped: I want to include the render of another plugin as part of my own.
My aspirational plugin is jekyll_icon_list, the plugin I want to use is jekyll-inline-svg. Here's the (abbreviated) code:
require 'jekyll_icon_list/version'
require 'jekyll'
require 'jekyll-inline-svg'
module JekyllIconList
class IconList < Liquid::Tag
def initialize(tag_name, raw_args, tokens)
#raw_args = raw_args
#tokens = tokens
super
end
def parse_arguments(raw_args, settings)
# (Unrelated stuff)
end
def generate_image(icon, settings, context)
# (Unrelated stuff)
# Problem Here:
Liquid::Tag.parse(
'svg',
icon,
#tokens,
Liquid::ParseContext.new
).render(context)
end
def render(context)
# Builds my HTML, using generate_image in the process
end
end
end
Liquid::Template.register_tag('iconlist', JekyllIconList::IconList)
This doesn't throw any errors, but it also doesn't return anything at all.
Other things I've tried:
Jekyll::Tags::JekylInlineSvg.new(
returns a private method error. Jekyll doesn't want me making my own tags directly.
'{% svg #{icon} %}'
Returns exactly that literally with the icon substituted in; jekyll clearly doesn't parse the same file twice.
I'm trying to figure it out from Jekyll's source, but I'm not so practiced at reading source code and keep hitting dead ends. Can anyone point me in the right direction? Much appreciated.
Answering my own question:
def build_svg(icon_filename)
tag = "{% svg #{icon_filename} %}"
liquid_parse(tag)
end
def liquid_parse(input)
Liquid::Template.parse(input).render(#context)
end
Basically create a tiny template consisting of the tag you want to call, and hand it off to Liquid for parsing.
Below is the dirty way, which I used before I found the proper way:
Jekyll::Tags::JekyllInlineSvg.send(:new, 'svg', icon_filename, #tokens).render(context)
I found this question and answer, and while it's correct, I wanted to provide a full end-to-end example.
I wanted to wrap Jekyll Scholar's {% cite %} tags in my own content:
module Jekyll
class RenderTimeTag < Liquid::Tag
def initialize(tag_name, text, tokens)
super
#text = text
end
def build_cite(content, context)
tag = "{% cite #{content} %}"
return liquid_parse(tag, context)
end
def liquid_parse(input, context)
template = Liquid::Template.parse(input)
template.render(context)
end
def render(context)
citation = build_cite(#text, context)
# Yeah, I know this is bad HTML:
"<span tabindex=\"0\" class=\"citeblock\">#{citation}</span>"
end
end
end
Liquid::Template.register_tag('pretty_cite', Jekyll::RenderTimeTag)
In a Jekyll site with many pages (not blog posts), I want to tweak the permalink of each page programatically. I tried a Generator plugin, something like:
module MySite
class MySiteGenerator < Jekyll::Generator
def generate(site)
site.pages.each do |page|
page.data['permalink'] = '/foo' + page.url
# real world manipulation of course more complicated
end
end
end
end
But although this would run and set the page.data['permalink'] field, the output was still the same.
Is there something I'm doing wrong, or is there a different way entirely of doing this? Thanks!
It can be easier to override the page class with something like this :
module Jekyll
class Page
alias orig_permalink permalink
def permalink
permalink = orig_permalink
newPermalink = "foo/#{permalink}"
end
end
end
Not tested.
I am writing a way to parse websites, each "scraper" has it's own way gather information, but there is plenty of common functionality between two methods.
Differences:
One scraper uses Nokogiri to open the page via css selectors
the other scraper uses an RSS feed to gather information
Similarities:
each scraper creates an "Event" object that has the following attributes:
title
date
description
if for the Nokogiri scraper, we do something like this:
event_selector = page.css(".div-class")
event_selector.each_with_index do |event, index|
date = Date.parse(event.text) #code I want to share
end
for the RSS scraper, we do something like this
open(url) do |rss|
feed = RSS::Parser.parse(rss)
feed.items.each do |event|
description = Sanitize.fragment(event.description)
date = description[/\d{2}-\d{2}-20\d{2}/]
date = Date.strptime(date, '%m-%d-%Y') #code I want to share
end
end
^^ The date is grabbed via a regex from the description and then converted into a Date object via the .strptime method
as you can see each scraper uses 2 different method calls/ways to find the date. How could I abstract this information into a class?
I was thinking of something like this:
class scrape
attr_accessor :scrape_url, :title, :description, :date, :url
def initialize(options = {})
end
def find_date(&block)
# Process the block??
end
end
and then in each of the scraper methods do something like
scrape = Scrape.new
date_proc = Proc.new {Date.parse(event.text)}
scrape.find_date(date_proc)
Is this the right way to go about this problem? In short I want to have common functionality of two website parsers to pass the desired code into a instance method of a "scrape" class. I would greatly appreciate any tips to tackle this scenario.
Edit: Maybe it would make more sense if I say that I want to find the "date" of an event, but the way I find it - the behavior - or the specific code that is run, is different.
You could use an Event builder. Something like this:
class Event::Builder
def date(raw)
#date = Date.strptime(raw, '%m-%d-%Y')
end
# ... more setters (title, description) ...
def build
Event.new(date: #date, ... more arguments ..)
end
end
And then, inside the scraper:
open(url) do |rss|
builder = Event::Builder.new
feed = RSS::Parser.parse(rss)
feed.items.each do |event|
description = Sanitize.fragment(event.description)
date = description[/\d{2}-\d{2}-20\d{2}/]
builder.date(date)
# ... set other attributes ...
event = builder.build
# do something with the event ...
end
end
You should look into the Strategy or Template patterns. These are ways of writing code that does different things depending on some state or configuration. Essentially you'd write a Scraper class and then sub class it as WebScraper and RssScraper. Each class would inherit from the Scraper class all the common functionality but only differ in their implementation of how to get the date, description, etc.
I would like to render all my emails from markdown with using a layout "email".
Have been investigating gems and options there are 2 gems I could both could not get to work:
maildown and markerb.
Has anyone implemented a method to render text,html emails with action mailer from a markdown mail template and also using a layout? Or knows a good updated writeup on this?
Markerb registers a .markerb file extension like so:
module Markerb
class Railtie < ::Rails::Railtie
config.markerb = Markerb
config.app_generators.mailer :template_engine => :markerb
end
end
and
require "redcarpet"
require "markerb/railtie"
module Markerb
mattr_accessor :processing_options, :renderer
##processing_options = {}
##renderer = Redcarpet::Render::HTML
class Handler
def erb_handler
#erb_handler ||= ActionView::Template.registered_template_handler(:erb)
end
def call(template)
compiled_source = erb_handler.call(template)
if template.formats.include?(:html)
"Redcarpet::Markdown.new(Markerb.renderer, Markerb.processing_options).render(begin;#{compiled_source};end).html_safe"
else
compiled_source
end
end
end
end
ActionView::Template.register_template_handler :markerb, Markerb::Handler.new
I need two versions of each of my posts in a very simple Jekyll setup: The public facing version and a barebones version with branding specifically for embedding.
I have one layout for each type:
post.html
post_embed.html
I could accomplish this just fine by making duplicates of each post file with different layouts in the front matter, but that's obviously a terrible way to do it. There must be a simpler solution, either at the level of the command line or in the front matter?
Update:
This SO question covers creating JSON files for each post. I really just need a generator to loop through each post, alter one value in the YAML front matter (embed_page=True) and feed it back to the same template. So each post is rendered twice, once with embed_page true and one with it false. Still don't have a full grasp of generators.
Here's my Jekyll plugin to accomplish this. It's probably absurdly inefficient, but I've been writing in Ruby for all of two days.
module Jekyll
# override write and destination functions to taking optional argument for pagename
class Post
def destination(dest, pagename)
# The url needs to be unescaped in order to preserve the correct filename
path = File.join(dest, CGI.unescape(self.url))
path = File.join(path, pagename) if template[/\.html$/].nil?
path
end
def write(dest, pagename="index.html")
path = destination(dest, pagename)
puts path
FileUtils.mkdir_p(File.dirname(path))
File.open(path, 'w') do |f|
f.write(self.output)
end
end
end
# the cleanup function was erasing our work
class Site
def cleanup
end
end
class EmbedPostGenerator < Generator
safe true
priority :low
def generate(site)
site.posts.each do |post|
if post.data["embeddable"]
post.data["is_embed"] = true
post.render(site.layouts, site.site_payload)
post.write(site.dest, "embed.html")
post.data["is_embed"] = false
end
end
end
end
end