Chienomi

Mimir Yokohama ウェブサイトの「タグ機能」の仕組み

ウェブサイト開発

  • TOP
  • Old Archives
  • Mimir Yokohama ウェブサイトの「タグ機能」の仕組み

Mimir Yokohamaのウェブサイトにこっそりとタグ機能が追加された。

だが、PureBuilder Simply自体にはタグ機能がない。 この実現方法は発想力勝負な部分があった。

ドキュメントにデータを持たせる

「記事情報」でも行われている方法として、Markdown YAML Frontmatter内に情報をもたせ、Pandocテンプレートで存在する場合だけエレメントを生成するような手法を取っている。

例えば帯域においては

title: 帯域
date: 2018-02-19
category: さくっと読み解く パソコン・スマホの機械とコトバ
accs: yes
author: Masaki Haruka
difficulty: 2
required_know: 1
lod: 2
pickable: no
target_readers:
  - 一般パソコンユーザー
  - 一般スマートフォンユーザー
  - パソコンに詳しくない人
tags:
  - パソコン
  - スマホ
  - 用語
  - 通信

というYAML Frontmatterが書かれている。 記事情報などは自動的に生成することができないため記事ごとにかかれており、若干執筆コストを上げている面もあるが、 なにしろMimir Yokohamaには力が入っているのでそれくらいどうということはない。 基本的なフォーマットをコピペしてしまえばそれほど難しくない部分でもある。

ちなみに、Pandocテンプレートで

$if(pickable)$
Something
$endif$

とした場合、pickable: no (つまりfalse)ならばここは生成されない。

この追加情報としてtagsが加わったのである。

問題は検索

タグを表示することは簡単だが、普通に考えればタグから同一タグの記事を辿りたいし、タグで検索もしたい。

単純な方法としてGoogleを使うこともできるのだが、それは必ずしもタグつきの記事が上位にくるわけではなく、思ったようには動作しない。 ちゃんと検索機能を用意する必要があったのだが、できればPureBuilder Simplyの枠組みの中で行いたいところである。

PureBuilder Simplyは原則として「MarkdownまたはReST文書から生成する」という前提になっており、 ACCSもindexデータベースからMarkdownドキュメントを生成し、このあとはPandocで処理している。

なのでPureBuilder Simplyの枠組みで処理するためにはMarkdownドキュメントを生成しなくてはいけない。

それなら全てのindexを探し回ってタグを集めればいいじゃない。

#!/usr/bin/ruby
require 'date'
require 'yaml'

$tags = Hash.new {|h,k| h[k] = [] }
accs = nil

ARGV.each do |arg|
  if(File.exist?(File.dirname(arg) + "/.accs.yaml"))
    accs = YAML.load(File.read(File.dirname(arg) + "/.accs.yaml"))
  else
    accs = nil
  end
  Marshal.load(File.read arg).each do |k,v|
    if v.key?("tags")
      v["tags"].each do |tag|
        $tags[tag].push({
          v["title"],
          (File.dirname(arg) + "/" + k.sub(/\.md$/, ".html")),
          v["date"],
          ( accs && accs["title"]),
          v["category"]
        })
      end
    end
  end
end


$tags.each do |tag, v|
  File.open("tags/#{tag}.md", "w") do |f|
    f.puts <<-EOF
---
title: 'タグ "\##{tag}" の検索'
date: #{Date.today}
pagetype: accsindex
indexpage: yes
---

  EOF
    v.sort_by {|i| [i[], i[].to_s, i[].to_s, i[]] }.reverse.each do |i|
      if i[] !~ %r:^/:
        i[] = "/" + i[]
      end
      f.printf("* [%s](%s) \\\n  %s%s @%s\n", i[], i[], i[].to_s, (i[] && "/" + i[]).to_s, i[])
    end
  end
end

File.open("tags/index.md", "w") do |f|
  f.puts <<-EOF
---
title: 'タグ一覧'
date: #{Date.today}
pagetype: accsindex
indexpage: yes
---

EOF
  $tags.keys.sort.each do |k|
    f.puts "* [#{k}](/tags/#{k}.html) (#{$tags[k].length})"
  end
end

ARGV.eachしているので、全ての.indexes.rbmを指定すれば良い。 あとはpbsimply-pandocで処理できるが、タグに登場した.indexes.rbmは実際に記事が存在しているものではなく拾ってほしくないので消しておく。

#!/usr/bin/zsh
setopt EXTENDED_GLOB

pbsimply-pandoc.rb .
ruby .tagcloud.rb **/.indexes.rbm~tags/.indexes.rbm
pbsimply-pandoc.rb tags
rm tags/.indexes.rbm

.indexes.rbmとして書き出すようにした意図の一部に、このように外部からドキュメントデータにアクセスするというものがあった。

これによってドキュメント解析しなくてもメタデータを利用して機能拡張してページに含むことができる。

テンプレートにとうとう限界が

だいぶ魔改造されているPandocテンプレートだが、今回は限界が垣間見えた。

タグクラウドらしくエントリ数の多いタグを大きく表示したいのだが、Pandocテンプレートに計算機能や比較機能がなく、CSSにもないため、 Pandocテンプレートだけでは実現できない。MarkdownにHTMLを直接書くという方法はあるが(Markdown自体はRubyで生成しているため)。

また、URIエンコーディングをする方法はデータを二重に持たせる以外になく、それでもふたつの値を同時にとるイテレータがPandocテンプレートにないため、URIだけでURIエンコーディングをおこなう方法がない。

eRubyを使ってもいいのだが、できれば使いたくない。 現時点ではタグクラウドの大きさ分けはしておらず、URIもURIエンコーディングせずに使用している。