Skip to content

Instantly share code, notes, and snippets.

@lo48576
Last active November 2, 2015 08:47
Show Gist options
  • Select an option

  • Save lo48576/dd2f65fd6e3cc7490241 to your computer and use it in GitHub Desktop.

Select an option

Save lo48576/dd2f65fd6e3cc7490241 to your computer and use it in GitHub Desktop.
階層化されるタグをjekyllで実現するためのプラグイン。 `_plugins` ディレクトリに入れればおkなはず。 http://l1048576.github.io/blog/2015/03/01/new-blog.html
module Jekyll
class HierarchialTag
def self.decompose_path(path)
path.split('/').take_while{|frag|
# 潜在的な危険性のある文字列を発見したら、そのパスについては
# 危険な文字列以降を捨てる。
# (たとえば'a/b/../c'であれば'a/b'までが使われる)。
# 空文字列でない
!frag.empty? &&
# '..'でない
(frag != '..') &&
# '\\'を含まない
!frag.include?('\\') &&
# '*'を含まない
!frag.include?('*') &&
# '~'を含まない
!frag.include?('~')
}
end
# String -> [String]
def self.ascend_path(path)
path_list = []
decompose_path(path).each_with_object([]){|frag, current_path|
path_list << (current_path << frag).join('/')
}
return path_list
end
end
module HierarchialTagFilter
def ascend_path(path)
HierarchialTag.ascend_path(path)
end
# [[String]] -> [[[String]]]
def decompose_hierarchial_tags(tags)
tags.map{|tag|
ascend_path(tag)
}.uniq.sort.map{|path_list|
path_list.map{|path| make_path_name_pair(path)}
}
end
def make_path_name_pair(path)
# first: path, second: name
return [path, path.match(/[^\/]+$/){|md| md[0]}]
end
end
class HierarchialTagTree < Liquid::Tag
def initialize(tag_name, arg, tokens)
super
@arg = arg.strip
end
# HTMLのulとliでツリーを作る。
def render(context)
#@site_url = context['site.url']
@context = context
@site_tags = @context.registers[:site].tags
if @arg.empty? || context[@arg].nil?
tagdir = ""
root_tag = ""
tags = @site_tags.keys
else
depth = 0
tagdir = HierarchialTag.decompose_path(context[@arg]).
tap{|frags|
depth = frags.count
root_tag = frags.join('/')
frags.pop
}.join('/')
tags = @site_tags.keys.
map{|s| HierarchialTag.decompose_path(s).join('/')}.
select{|s| s.start_with?("#{root_tag}/")}.
map{|s| s.split('/').drop(depth-1).join('/')}
end
result = '<ul class="tag-hierarchy-tree">' + "\n"
if tags.empty?
result << "<li><a href=\"/#{HierarchialTagPageGenerator::TAG_DIR}/#{tagdir}/\">#{root_tag}</a></li>"
else
result << render_tags_array(tagdir, tags.map{|t| HierarchialTag.decompose_path(t)}, root_tag)
end
result << '</ul>'
end
private
def render_tags_array(base_path, tags, root_tag="")
output = ""
base_path += "/" if !base_path.empty?
tags.group_by(&:first).
sort.each{|name, frags_arr|
current_path = base_path+name
current_path_pages = @site_tags[current_path]
exact_match_num = current_path_pages.count
children_frags_arr = frags_arr.map{|frags| frags.drop(1)}.delete_if(&:empty?)
# 厳密マッチするものが無く、かつ子要素がひとつだけであれば、
# 複数階層を同時に表示する。
children_frags_group = children_frags_arr.group_by(&:first)
if (exact_match_num == 0) && (children_frags_group.count == 1)
output << render_tags_array(
current_path,
children_frags_group.flatten[1],
(root_tag.empty? ? name : root_tag)+'/'+children_frags_group.flatten.first
)
else
subtag_match_num = 0
# FIXME: render_tags_array呼び出しの度に何度もカウントするの、効率悪い
subtag_match_num = frags_arr.map{|frags| base_path+frags.join('/')}.inject(current_path_pages){|pages, key|
pages.concat(@site_tags[key])
}.uniq.count
#output << "<li><a href=\"#{@site_url}/#{HierarchialTagPageGenerator::TAG_DIR}/#{current_path}\">"
output << "<li><a href=\"/#{HierarchialTagPageGenerator::TAG_DIR}/#{current_path}\">"
output << (root_tag.empty? ? name : root_tag)
output << " [#{exact_match_num}/#{subtag_match_num}]</a>"
# タグの子孫が存在すれば再帰的に表示
if !children_frags_arr.empty?
output << "<ul>\n" << render_tags_array(current_path, children_frags_arr) << "</ul>"
end
output << "</li>\n"
end
}
return output
end
end
end
Liquid::Template.register_filter(Jekyll::HierarchialTagFilter)
Liquid::Template.register_tag('tag_hierarchy_tree', Jekyll::HierarchialTagTree)
module Jekyll
class HierarchialTagPage < Page
def initialize(site, base, dir, tag_root)
# tag_rootは危険なタグでないことが、呼び出し元の
# HierarchialTagPageGenerator::generate()によって保証されている。
@site, @base, @dir = site, base, dir
@name = 'index.html'
self.process(@name)
raise 'name is null' unless @name
self.read_yaml(File.join(base, '_layouts'), 'hierarchial_tag_index.html')
self.data['title'] = "Entries of #{tag_root}"
# tagと一致するか、"#{tag_root}/"で始まるタグを持つpostを全て挙げ(最初の())、
# それらのタグを持つpostを列挙する(injectとconcat)。
# site.tagsは、存在しないタグについては空の配列([])を返すので、気にせず
# そのままconcatして問題ない。
self.data['posts'] = (site.tags.keys.select{|s| s.start_with?("#{tag_root}/")} << tag_root).
inject([]){|posts, tag|
posts.concat(site.tags[tag])
}.uniq
self.data['tag'] = tag_root
end
end
class HierarchialTagPageGenerator < Generator
safe true
TAG_DIR = 'blog/tags'
def generate(site)
site.tags.keys.map{|tag|
# 一覧を取り出すべきタグのリストを作ると同時に、ここで
# 危険なタグ("/../"や'\\'を含むなど)を除外する。
HierarchialTag.ascend_path(tag)
}.flatten.uniq.sort.each{|tag|
site.pages << HierarchialTagPage.new(site, site.source, File.join(TAG_DIR, tag), tag)
}
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment