jekyll/plugins/toc_builder_hook.rb (106 lines of code) (raw):
require 'yaml'
require 'json'
require 'kramdown'
Jekyll::Hooks.register :site, :post_read do |site|
toc_input = site.config['toc_input'] || '_SUMMARY.md'
builder = TocContentBuilder.new
toc_content = builder.build_toc(site, toc_input)
site.data['toc'] = toc_content
end
class TocContentBuilder
def build_toc(site, toc_input)
toc = []
content = File.read(File.join(site.source, toc_input))
kramdown_config = site.config['kramdown'].merge({:html_to_native => true})
kramdown_doc = Kramdown::Document.new(content, kramdown_config)
parent_id = nil
kramdown_doc.root.children.each do |c|
item = extract_from_node(c, parent_id)
if not item.nil? then
toc.concat(item)
parent_id = item[0][:id] if item[0][:type] == :header
end
end
# Removing headers of empty sections
delete_list = []
(0).upto(toc.length-1) do |i|
item = toc[i]
prev = toc[i-1] != nil ? toc[i-1] : nil
item_is_header = (item[:type] != nil and item[:type] == :header)
prev_is_header = (prev != nil and prev[:type] != nil and prev[:type] == :header)
if item_is_header and prev_is_header
delete_list.push(i-1)
end
end
delete_list.each do |del_index|
toc.delete_at(del_index)
end
toc
end
private
def extract_from_node(node, parent_id)
items = []
case node.type
when :ul
items.concat(extract_items(node, parent_id))
when :header
items.push(extract_header(node)) if node.options[:level] == 2
end
return items.length > 0 ? items : nil
end
def extract_items(ul_node, parent_id = nil)
items = []
ul_node.children.select { |n| n.type == :li }.each do |li_node|
items.push(extract_item(li_node, parent_id))
end
return items
end
def extract_item(li_node, parent_id)
item = {}
item[:parent_id] = parent_id unless parent_id.nil?
p = li_node.children[0]
case p.children[0].type
when :text
title = p.children[0].value.strip
item[:id] = title.gsub(' ', '_')
item[:title] = title
item[:type] = :placeholder
when :a
a = p.children[0]
href = a.attr['href']
is_external = href.start_with?('http://', 'https://', 'ftp://', '//')
if not is_external then
basename = href.chomp(File.extname(href)).sub(/^\//,'')
href = basename + '.html'
end
item[:id] = basename
item[:title] = get_text(a.children)
item[:url] = href
item[:path] = a.attr['href']
item[:is_external] = true if is_external
end
li_node.children.drop(1).each do |child|
pages = extract_items(child, item[:id]) if child.type == :ul
item[:pages] = pages if pages != nil
end
return item
end
def get_text(nodes)
nodes.reduce('') { |t, c| t + format(c.value) }
end
def format(item)
case item
when Symbol
Kramdown::Utils::Entities.entity(item.to_s).char
when String
item
end
end
def extract_header(header_node)
title = header_node.options[:raw_text].strip
return {
:id => title.gsub(' ', '_'),
:title => title,
:type => :header
}
end
end