diff --git a/features/hooks.feature b/features/hooks.feature index f1256484..6562a90a 100644 --- a/features/hooks.feature +++ b/features/hooks.feature @@ -6,7 +6,7 @@ Feature: Hooks Given I have a _plugins directory And I have a "_plugins/ext.rb" file with content: """ - Jekyll::Hooks.register Jekyll::Site, :reset do |site| + Jekyll::Hooks.register :site, :reset do |site| pageklass = Class.new(Jekyll::Page) do def initialize(site, base) @site = site @@ -32,7 +32,7 @@ Feature: Hooks And I have a "index.html" page that contains "{{ site.injected }}!" And I have a "_plugins/ext.rb" file with content: """ - Jekyll::Hooks.register Jekyll::Site, :pre_render do |site, payload| + Jekyll::Hooks.register :site, :pre_render do |site, payload| payload['site']['injected'] = 'myparam' end """ @@ -46,7 +46,7 @@ Feature: Hooks And I have a "page2.html" page that contains "page2" And I have a "_plugins/ext.rb" file with content: """ - Jekyll::Hooks.register Jekyll::Site, :post_read do |site| + Jekyll::Hooks.register :site, :post_read do |site| site.pages.delete_if { |p| p.name == 'page1.html' } end """ @@ -59,7 +59,7 @@ Feature: Hooks Given I have a _plugins directory And I have a "_plugins/ext.rb" file with content: """ - Jekyll::Hooks.register Jekyll::Site, :post_write do |site| + Jekyll::Hooks.register :site, :post_write do |site| firstpage = site.pages.first content = File.read firstpage.destination(site.dest) File.write(File.join(site.dest, 'firstpage.html'), content) @@ -74,7 +74,7 @@ Feature: Hooks Given I have a _plugins directory And I have a "_plugins/ext.rb" file with content: """ - Jekyll::Hooks.register Jekyll::Page, :post_init do |page| + Jekyll::Hooks.register :page, :post_init do |page| page.name = 'renamed.html' page.process(page.name) end @@ -88,7 +88,7 @@ Feature: Hooks Given I have a _plugins directory And I have a "_plugins/ext.rb" file with content: """ - Jekyll::Hooks.register Jekyll::Page, :pre_render do |page, payload| + Jekyll::Hooks.register :page, :pre_render do |page, payload| payload['myparam'] = 'special' if page.name == 'page1.html' end """ @@ -103,7 +103,7 @@ Feature: Hooks And I have a "index.html" page that contains "WRAP ME" And I have a "_plugins/ext.rb" file with content: """ - Jekyll::Hooks.register Jekyll::Page, :post_render do |page| + Jekyll::Hooks.register :page, :post_render do |page| page.output = "{{{{{ #{page.output.chomp} }}}}}" end """ @@ -115,7 +115,7 @@ Feature: Hooks And I have a "index.html" page that contains "HELLO FROM A PAGE" And I have a "_plugins/ext.rb" file with content: """ - Jekyll::Hooks.register Jekyll::Page, :post_write do |page| + Jekyll::Hooks.register :page, :post_write do |page| require 'fileutils' filename = page.destination(page.site.dest) FileUtils.mv(filename, "#{filename}.moved") @@ -129,7 +129,7 @@ Feature: Hooks And I have a "_plugins/ext.rb" file with content: """ # rot13 translate - Jekyll::Hooks.register Jekyll::Post, :post_init do |post| + Jekyll::Hooks.register :post, :post_init do |post| post.content.tr!('abcdefghijklmnopqrstuvwxyz', 'nopqrstuvwxyzabcdefghijklm') end @@ -148,7 +148,7 @@ Feature: Hooks """ # Add myvar = 'old' to posts before 2015-03-15, and myvar = 'new' for # others - Jekyll::Hooks.register Jekyll::Post, :pre_render do |post, payload| + Jekyll::Hooks.register :post, :pre_render do |post, payload| if post.date < Time.new(2015, 3, 15) payload['myvar'] = 'old' else @@ -170,7 +170,7 @@ Feature: Hooks And I have a "_plugins/ext.rb" file with content: """ # Replace content after rendering - Jekyll::Hooks.register Jekyll::Post, :post_render do |post| + Jekyll::Hooks.register :post, :post_render do |post| post.output.gsub! /42/, 'the answer to life, the universe and everything' end """ @@ -188,7 +188,7 @@ Feature: Hooks And I have a "_plugins/ext.rb" file with content: """ # Log all post filesystem writes - Jekyll::Hooks.register Jekyll::Post, :post_write do |post| + Jekyll::Hooks.register :post, :post_write do |post| filename = post.destination(post.site.dest) open('_site/post-build.log', 'a') do |f| f.puts "Wrote #{filename} at #{Time.now}" @@ -203,3 +203,125 @@ Feature: Hooks When I run jekyll build Then I should see "_site/2015/03/14/entry1.html at" in "_site/post-build.log" Then I should see "_site/2015/03/15/entry2.html at" in "_site/post-build.log" + + Scenario: Register a hook on multiple owners at the same time + Given I have a _plugins directory + And I have a "_plugins/ext.rb" file with content: + """ + Jekyll::Hooks.register [:page, :post], :post_render do |owner| + owner.output = "{{{{{ #{owner.output.chomp} }}}}}" + end + """ + And I have a "index.html" page that contains "WRAP ME" + And I have a _posts directory + And I have the following posts: + | title | date | layout | content | + | entry1 | 2015-03-14 | nil | entry one | + When I run jekyll build + Then I should see "{{{{{ WRAP ME }}}}}" in "_site/index.html" + And I should see "{{{{{
entry one
}}}}}" in "_site/2015/03/14/entry1.html" + + Scenario: Allow hooks to have a named priority + Given I have a _plugins directory + And I have a "_plugins/ext.rb" file with content: + """ + Jekyll::Hooks.register :page, :post_render, priority: :normal do |owner| + # first normal runs second + owner.output = "1 #{owner.output.chomp}" + end + Jekyll::Hooks.register :page, :post_render, priority: :high do |owner| + # high runs last + owner.output = "2 #{owner.output.chomp}" + end + Jekyll::Hooks.register :page, :post_render do |owner| + # second normal runs third (normal is default) + owner.output = "3 #{owner.output.chomp}" + end + Jekyll::Hooks.register :page, :post_render, priority: :low do |owner| + # low runs first + owner.output = "4 #{owner.output.chomp}" + end + """ + And I have a "index.html" page that contains "WRAP ME" + When I run jekyll build + Then I should see "2 3 1 4 WRAP ME" in "_site/index.html" + + Scenario: Alter a document right after it is initialized + Given I have a _plugins directory + And I have a "_plugins/ext.rb" file with content: + """ + Jekyll::Hooks.register :document, :pre_render do |doc, payload| + doc.data['text'] = doc.data['text'] << ' are belong to us' + end + """ + And I have a "_config.yml" file that contains "collections: [ memes ]" + And I have a _memes directory + And I have a "_memes/doc1.md" file with content: + """ + --- + text: all your base + --- + """ + And I have an "index.md" file with content: + """ + --- + --- + {{ site.memes.first.text }} + """ + When I run jekyll build + Then the _site directory should exist + And I should see "all your base are belong to us" in "_site/index.html" + + Scenario: Update a document after rendering it, but before writing it to disk + Given I have a _plugins directory + And I have a "_plugins/ext.rb" file with content: + """ + Jekyll::Hooks.register :document, :post_render do |doc| + doc.output.gsub! //, '
' + end + """ + And I have a "_config.yml" file with content: + """ + collections: + memes: + output: true + """ + And I have a _memes directory + And I have a "_memes/doc1.md" file with content: + """ + --- + text: all your base are belong to us + --- + {{ page.text }} + """ + When I run jekyll build + Then the _site directory should exist + And I should see "
all your base are belong to us" in "_site/memes/doc1.html"
+
+ Scenario: Perform an action after every document is written
+ Given I have a _plugins directory
+ And I have a "_plugins/ext.rb" file with content:
+ """
+ Jekyll::Hooks.register :document, :post_write do |doc|
+ open('_site/document-build.log', 'a') do |f|
+ f.puts "Wrote document #{doc.collection.docs.index doc} at #{Time.now}"
+ end
+ end
+ """
+ And I have a "_config.yml" file with content:
+ """
+ collections:
+ memes:
+ output: true
+ """
+ And I have a _memes directory
+ And I have a "_memes/doc1.md" file with content:
+ """
+ ---
+ text: all your base are belong to us
+ ---
+ {{ page.text }}
+ """
+ When I run jekyll build
+ Then the _site directory should exist
+ And I should see "Wrote document 0" in "_site/document-build.log"
diff --git a/lib/jekyll/document.rb b/lib/jekyll/document.rb
index cd407dfa..8b16dca4 100644
--- a/lib/jekyll/document.rb
+++ b/lib/jekyll/document.rb
@@ -175,11 +175,15 @@ module Jekyll
#
# Returns nothing.
def write(dest)
+ Jekyll::Hooks.trigger self, :post_render
+
path = destination(dest)
FileUtils.mkdir_p(File.dirname(path))
File.open(path, 'wb') do |f|
f.write(output)
end
+
+ Jekyll::Hooks.trigger self, :post_write
end
# Returns merged option hash for File.read of self.site (if exists)
diff --git a/lib/jekyll/hooks.rb b/lib/jekyll/hooks.rb
index bb551b2d..b660bd6f 100644
--- a/lib/jekyll/hooks.rb
+++ b/lib/jekyll/hooks.rb
@@ -1,51 +1,101 @@
module Jekyll
module Hooks
+ # Helps look up hooks from the registry by owner's class
+ OWNER_MAP = {
+ Jekyll::Site => :site,
+ Jekyll::Page => :page,
+ Jekyll::Post => :post,
+ Jekyll::Document => :document,
+ }.freeze
+
+ DEFAULT_PRIORITY = 20
+
+ # compatibility layer for octopress-hooks users
+ PRIORITY_MAP = {
+ low: 10,
+ normal: 20,
+ high: 30,
+ }.freeze
+
# initial empty hooks
@registry = {
- Jekyll::Site => {
+ :site => {
reset: [],
post_read: [],
pre_render: [],
post_write: [],
},
- Jekyll::Page => {
+ :page => {
post_init: [],
pre_render: [],
post_render: [],
post_write: [],
},
- Jekyll::Post => {
+ :post => {
post_init: [],
pre_render: [],
post_render: [],
post_write: [],
},
+ :document => {
+ pre_render: [],
+ post_render: [],
+ post_write: [],
+ },
}
+ # map of all hooks and their priorities
+ @hook_priority = {}
+
NotAvailable = Class.new(RuntimeError)
- # register a hook to be called later
- def self.register(klass, event, &block)
- unless @registry[klass]
+ # register hook(s) to be called later
+ def self.register(owners, event, priority: nil, &block)
+ Array(owners).each do |owner|
+ register_one(owner, event, priority: priority_value(priority), &block)
+ end
+ end
+
+ # Ensure the priority is a Fixnum
+ def self.priority_value(priority=nil)
+ return DEFAULT_PRIORITY unless priority
+ return priority if priority.is_a?(Fixnum)
+ PRIORITY_MAP[priority] || DEFAULT_PRIORITY
+ end
+
+ # register a single hook to be called later
+ def self.register_one(owner, event, priority: nil, &block)
+ unless @registry[owner]
raise NotAvailable, "Hooks are only available for the following " <<
"classes: #{@registry.keys.inspect}"
end
- unless @registry[klass][event]
- raise NotAvailable, "Invalid hook. #{klass} supports only the " <<
- "following hooks #{@registry[klass].keys.inspect}"
+ unless @registry[owner][event]
+ raise NotAvailable, "Invalid hook. #{owner} supports only the " <<
+ "following hooks #{@registry[owner].keys.inspect}"
end
- @registry[klass][event] << block
+ insert_hook owner, event, priority, &block
+ end
+
+ def self.insert_hook(owner, event, priority, &block)
+ @hook_priority[block] = "#{priority}.#{@hook_priority.size}".to_f
+ @registry[owner][event] << block
end
# interface for Jekyll core components to trigger hooks
def self.trigger(instance, event, *args)
- # proceed only if there are hooks to call
- return unless @registry[instance.class]
- return unless @registry[instance.class][event]
+ owner_symbol = OWNER_MAP[instance.class]
- @registry[instance.class][event].each do |hook|
+ # proceed only if there are hooks to call
+ return unless @registry[owner_symbol]
+ return unless @registry[owner_symbol][event]
+
+ # hooks to call for this owner and event
+ hooks = @registry[owner_symbol][event]
+
+ # sort and call hooks according to priority and load order
+ hooks.sort_by { |h| @hook_priority[h] }.each do |hook|
hook.call(instance, *args)
end
end
diff --git a/site/_docs/plugins.md b/site/_docs/plugins.md
index e7830e1a..b000c4dc 100644
--- a/site/_docs/plugins.md
+++ b/site/_docs/plugins.md
@@ -491,16 +491,16 @@ custom functionality every time Jekyll renders a post, you could register a
hook like this:
{% highlight ruby %}
-Jekyll::Hooks.register Jekyll::Post, :post_render do |post|
+Jekyll::Hooks.register :post, :post_render do |post|
# code to call after Jekyll renders a post
end
{% endhighlight %}
-Jekyll provides hooks for Jekyll::Site
, Jekyll::Page
-and Jekyll::Post
. In all cases, Jekyll calls your hooks with the
-container object as the first callback parameter. But in the case of
-:pre_render
, your hook will also receive a payload hash as a
-second parameter which allows you full control over the variables that are
+Jekyll provides hooks for :site
, :page
,
+:post
, and :document
. In all cases, Jekyll calls your
+hooks with the container object as the first callback parameter. But in the
+case of :pre_render
, your hook will also receive a payload hash as
+a second parameter which allows you full control over the variables that are
available while rendering.
The complete list of available hooks is below:
@@ -517,7 +517,7 @@ The complete list of available hooks is below:
Jekyll::Site
:site
:reset
Jekyll::Site
:site
:pre_render
Jekyll::Site
:site
:post_render
Jekyll::Site
:site
:post_write
Jekyll::Page
:page
:post_init
Jekyll::Page
:page
:pre_render
Jekyll::Page
:page
:post_render
Jekyll::Page
:page
:post_write
Jekyll::Post
:post
:post_init
Jekyll::Post
:post
:pre_render
Jekyll::Post
:post
:post_render
Jekyll::Post
:post
:post_write
After writing a post to disk
:document
:pre_render
Just before rendering a document
+:document
:post_render
After rendering a document, but before writing it to disk
+:document
:post_write
After writing a document to disk
+