Hiki Atomプラグイン
Atomプラグイン
Hikiの最近の更新をAtomフィードで配信するプラグインです。
v0.8(2010年10月25日)から、ページ毎にAtom Entryも配信できるようになりました。もしHikiをAtomPub対応にしようという心意気のある方がいれば手間を省くのにどうぞ。
Rackブランチではなく、オリジナルのHiki用です(バージョン0.8.8.1で動作確認済み)。
Rackブランチ版のプラグインはRackPluginAtomをご覧ください。このHikidashiでもこれを使っています。
ダウンロード
インストール
上のファイルをダウンロードして展開すると
- atom.rb
- en/atom.rb
- ja/atom.rb
の三ファイルが出てきます。 それぞれ
- misc/plugin
- misc/plugin/en
- misc/plugin/ja
ディレクトリーに入れてください。
プラグインを有効化すれば自動でAtomフィードが配信されます。
配信する内容(全文かダイジェストか、どんなフォーマットか)、Atom Entryを配信するかどうか、「Atom」「Atom Entry」という文字をページ内にも表示するかどうか、Atom Feedで何件の記事を配信するかは、管理画面から選択できます。
ソース
atom.rb
# = $Id: atom.rb, v1.0 2011-01-31
# Copyright (C) 2011 KITAITI Makoto <KitaitiMakoto@gmail.com>
#
# A Hiki plugin to syndicate Atom feed
#
# rss.rb plugin is very helpful for this plugin, thanks!
#
# == Requirements
# * rss library which can make Atom feeds
# (If your rss lib cannot, download from
# http://www.cozmixng.org/~rwiki/?cmd=view;name=RSS+Parser)
#
# == Environment
# Operation checked under:
# * Ruby 1.8.7
# * Hiki 0.8.8.1
#
# == To Do
# * Refactoring(Extract Method)
require 'time'
def atom
if @conf['atom.entry.enable'] && @page
atom_entry(@page)
else
atom_feed
end
nil # Don't move to the 'FrontPage'
end
def atom_feed
page_count = @conf['atom.count'] || atom_default_page_count
pages = atom_recent_updates(page_count)
last_modified = pages.first.values[0][:last_modified]
header = {}
if_modified_since = ENV['HTTP_IF_MODIFIED_SINCE']
if_modified_since = Time.parse(if_modified_since) if if_modified_since
if if_modified_since and last_modified <= if_modified_since
header['status'] = 'NOT_MODIFIED'
print @cgi.header(header)
else
body = atom_body(pages)
header['Last-Modified'] = last_modified.httpdate
header['type'] = 'application/atom+xml'
header['charset'] = body.encoding
header['Content-Language'] = @conf.lang
header['Pragma'] = 'no-cache'
header['Cache-Control'] = 'no-cache'
print @cgi.header(header)
print euc_to_utf8(body.to_s)
end
end
def atom_entry(page_name)
page = @db.info(page_name)
return print @cgi.header({'status' => 'NOT_FOUND'}) unless page
return print @cgi.header({'status' => 'FORBIDDEN'}) if respond_to?(:viewable?) && !viewable?(page.keys[0].to_s) # for private-view.rb plugin
last_modified = page[:last_modified]
header = {}
if_modified_since = ENV['HTTP_IF_MODIFIED_SINCE']
if_modified_since = Time.parse(if_modified_since) if if_modified_since
if if_modified_since and last_modified <= if_modified_since
header['status'] = 'NOT_MODIFIED'
print @cgi.header(header)
else
require 'rss/maker'
body = RSS::Maker.make('atom:entry') do |maker|
atom_make_entry(maker, {page_name => page }, :default_author => 'anonymous', :mode => :html_full)
end
header['Last-Modified'] = last_modified.httpdate
header['type'] = 'application/atom+xml'
header['charset'] = body.encoding
header['Content-Language'] = @conf.lang
header['Pragma'] = 'no-cache'
header['Cache-Control'] = 'no-cache'
print @cgi.header(header)
print euc_to_utf8(body.to_s)
end
end
def atom_recent_updates(page_count = atom_default_page_count)
pages = @db.page_info
pages.reject! {|page| private_view_private_page?(page.keys[0])} if respond_to? :private_view_private_page? # for private-view.rb plugin
pages.sort_by do |p|
p[p.keys[0]][:last_modified]
end.last(page_count).reverse
end
def atom_body(pages)
require 'rss/maker'
RSS::Maker.make('atom') do |maker|
maker.encoding = 'UTF-8'
maker.channel.author = @conf.author_name
maker.channel.about = @conf.index_url + '?c=atom'
maker.channel.title = @conf.site_name + ' : ' + label_atom_recent
maker.channel.description = @conf.site_name + ' ' + label_atom_recent
maker.channel.language = @conf.lang
maker.channel.date = pages.first.values[0][:last_modified]
maker.channel.rights = 'Copyright (C) ' + @conf.author_name
maker.channel.generator do |generator|
generator.uri = 'http://hikiwiki.org/'
generator.version = ::Hiki::VERSION
generator.content = 'Hiki'
end
maker.channel.links.new_link do |link|
link.rel = 'self'
link.type = 'application/atom+xml'
link.href = maker.channel.about
end
maker.channel.links.new_link do |link|
link.rel = 'alternate'
link.type = 'text/html'
link.href = @conf.index_url + '?c=recent'
end
pages.each do |page|
atom_make_entry(maker, page)
end
end
end
def atom_make_entry(maker, page, options = {})
maker.items.new_item do |item|
name = page.keys[0]
uri = @conf.index_url + '?' + name.escape
item.title = page_name(name)
item.link = uri
item.author = options[:default_author] if options[:default_author]
item.author = page[name][:editor] if page[name][:editor]
item.date = page[name][:last_modified].utc.strftime('%Y-%m-%dT%H:%M:%S+00:00')
item.content.type = 'html'
item.content.content = atom_make_content(page, options[:mode])
end
end
def atom_make_content(page, mode = nil)
mode ||= @conf['atom.mode']
raise ArgumentError, "Unknown mode: #{mode}" unless [:unidiff, :worddiff_digest, :worddiff_full, :html_full].include?(mode)
name = page.keys[0]
src = @db.load_backup(name) || ''
dst = @db.load(name) || ''
case mode
when :unidiff
content = h(unified_diff(src, dst)).strip.gsub(/\n/, "<br>\n").gsub(/ /, ' ')
when :worddiff_digest
content = word_diff(src, dst, true).strip.gsub(/\n/, "<br>\n")
when :worddiff_full
content = word_diff(src, dst).strip.gsub(/\n/, "<br>\n")
when :html_full
tokens = @db.load_cache(name)
unless tokens
parser = @conf.parser.new(@conf)
tokens = parser.parse(@db.load(name))
@db.save_cache(name, tokens)
end
tmp = @conf.use_plugin
begin
@conf.use_plugin = false
formatter = @conf.formatter.new(tokens, @db, Plugin.new(@conf.options, @conf), @conf)
content = formatter.to_s
ensure
@conf.use_plugin = tmp
end
end
if content.empty?
content = shorten(dst).strip.gsub(/\n/, "<br>\n")
end
content
end
def atom_max_page_count; 50; end
def atom_default_page_count; 10; end
add_body_enter_proc do
@conf['atom.mode'] ||= :unidiff
@conf['atom.menu'] ? add_plugin_command('atom', 'Atom') : add_plugin_command('atom', nil)
end
add_header_proc do
%Q| <link rel="alternate" type="application/atom+xml" title="Atom" href="#{@conf.index_url}?c=atom">\n|
end
if @conf['atom.entry.enable']
if @conf['atom.entry.menu-display']
add_menu_proc {%Q|<a href="#{@conf.index_url}?c=atom;p=#{@page.escape}">Atom Entry</a>|} if @page
end
add_header_proc { %Q| <link rel="alternate" type="application/atom+xml" title="Atom Entry" href="#{@conf.index_url}?c=atom;p=#{@page.escape}">| } if @page
end
def atom_saveconf
if @mode == 'saveconf'
@conf['atom.mode'] = @cgi.params['atom.mode'][0].intern
@conf['atom.count'] = [@cgi.params['atom.count'][0].to_i, atom_max_page_count].min
@conf['atom.count'] = [@conf['atom.count'], 1].max
end
end
if @cgi.params['conf'][0] == 'atom' && @mode == 'saveconf'
@conf['atom.menu'] = (@cgi.params['atom.menu'][0] == 'true')
@conf['atom.entry.enable'] = (@cgi.params['atom.entry.enable'][0] == 'true')
@conf['atom.entry.menu-display'] = (@cgi.params['atom.entry.menu-display'][0] == 'true')
end
add_conf_proc('atom', label_atom_config) do
atom_saveconf
str = <<HTML
<h3 class="subtitle">#{label_atom_mode_title}</h3>
<p><select name="atom.mode">
HTML
label_atom_mode_candidate.each_pair do |value, label|
str << %Q|<option value="#{value}"#{' selected' if @conf['atom.mode'] == value}>#{label}</option>\n|
end
str << "</select></p>\n"
str << <<HTML
<h3 class="subtitle">#{label_atom_menu_title}</h3>
<p><label><input type="radio" name="atom.menu" value="true"
#{'checked="checked"' if @conf['atom.menu']}>
#{label_atom_menu_enable}</label>
<label><input type="radio" name="atom.menu" value="false"
#{'checked="checked"' unless @conf['atom.menu']}>
#{label_atom_menu_disable}</label>\n
HTML
str << <<HTML
<h3 class="subtitle">#{label_atom_count_title}</h3>
<p><input name="atom.count" size="4"
value="#{@conf['atom.count']}">
#{label_atom_count_unit}(#{label_atom_max_page_count})</p>
HTML
str << <<HTML
<h3 class="subtitle">#{label_atom_entry_enable_title}</h3>
<p><label><input type="radio" name="atom.entry.enable" value="true"
#{'checked="checked"' if @conf['atom.entry.enable']}>
#{label_atom_entry_enable}</label>
<label><input type="radio" name="atom.entry.enable" value="false"
#{'checked="checked"' unless @conf['atom.entry.enable']}>
#{label_atom_entry_disable}</label></p>
<h3 class="subtitle">#{label_atom_entry_menu_display_title}</h3>
<p><label><input type="radio" name="atom.entry.menu-display" value="true"
#{'checked="checked"' if @conf['atom.entry.menu-display']}>
#{label_atom_entry_menu_display}</label>
<label><input type="radio" name="atom.entry.menu-display" value="false"
#{'checked="checked"' unless @conf['atom.entry.menu-display']}>
#{label_atom_entry_menu_hide}</label></p>
HTML
end
export_plugin_methods :atom
en/atom.rb
# en/atom.rb
def label_atom_recent
'Recent Changes'
end
def label_atom_config; 'Atom syndication'; end
def label_atom_mode_title; 'Select the format of the feed.' end
def label_atom_mode_candidate
{
:unidiff => 'unified diff',
:worddiff_digest => 'word diff(digest)',
:worddiff_full => 'word diff(full text)',
:html_full => 'HTML(full text)',
}
end
def label_atom_menu_title; 'Add Atom Feed menu'; end
def label_atom_menu_candidate
[ 'Yes', 'No' ]
end
def label_atom_count_title; 'The count of syndicated feeds'; end
def label_atom_count_unit; 'pages'; end
def label_atom_max_page_count; "#{atom_max_page_count} at a max"; end
def label_atom_entry_enable_title; 'Syndicate Atom Entry per page'; end
def label_atom_entry_enable; 'Yes'; end
def label_atom_entry_disable; 'No'; end
def label_atom_entry_menu_display_title; 'Add Atom Entry menu'; end
def label_atom_entry_menu_display; 'Yes'; end
def label_atom_entry_menu_hide; 'No'; end
ja/atom.rb
# -*- coding: euc-jp -*-
# ja/atom.rb
def label_atom_recent
'更新日時順'
end
def label_atom_config; 'Atom の配信'; end
def label_atom_mode_title; 'Atom Feedのフォーマット'; end
def label_atom_mode_candidate
{
:unidiff => 'unified diff 形式',
:worddiff_digest => 'word diff 形式 (ダイジェスト)',
:worddiff_full => 'word diff 形式 (全文)',
:html_full => 'HTML 形式 (全文)',
}
end
def label_atom_menu_title; 'Atom Feedメニューの表示'; end
def label_atom_menu_candidate
[ 'する', 'しない' ]
end
def label_atom_count_title; 'Atom Feedで配信するページの数'; end
def label_atom_count_unit; '件'; end
def label_atom_max_page_count; "最大#{atom_max_page_count}件"; end
def label_atom_entry_enable_title; '各ページのAtom Entryの配信'; end
def label_atom_entry_enable; 'する'; end
def label_atom_entry_disable; 'しない'; end
def label_atom_entry_menu_display_title; '各ページのAtom Entryメニューの表示'; end
def label_atom_entry_menu_display; 'する'; end
def label_atom_entry_menu_hide; 'しない'; end
メモ
- エントリーごとにキャッシュを作って、古いページのキャッシュと新しいページから作ったエントリーとを組み合わせてフィードを構築するとちょっとパフォーマンスがいいかも知れない。
- Ruby標準添付のrssライブラリーを使っているが、特定のエントリーだけ構築済みのXML要素を使うってできるんだろうか。
- そう言えば、例えばrss-showプラグインなんかを使って動的にページを作るやつのキャッシュは考えなきゃいけない、よくある問題だけど。Ajaxで読み込んじゃうのがいいと思うんだよな。
- Atom Entry配信の際、毎回info.dbにアクセスしなければならないのが気に食わないが、HTTPのIf-Modified-Since付きのリクエストに対応する為には仕方が無い。大域幅を節約するかサーバー負荷を減らすかっていう問題だが。
- キャッシュファイルを作るんだったら、それを読み込んで解析してしまえばそのページのLast-Modifiedは出るから、info.dbに触らないでもIf-Modified-Sinceに対応できるかも知れない。その場合は勿論Atom Entryを解析する負荷がサーバーに掛かるが、とても壊れやすいinfo.dbに触るよりはましなんじゃないかという気がする。それに、Last-Modifiedとentry要素のペアでキャッシュしてもいいしね。
- キーワード毎の最新記事を配信できるようにしたい。
Referer | 296 | 287 | 113 | 38 | 15 | 13 | 13 | 10 | 10 | 9 | 7 | 7 | 6 | 6 | 6 | 6 | 5 | 5 | 5 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
Keyword(s):
References:[自作プラグイン]