Last active
November 2, 2015 08:47
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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