日記を更新すると、TDiary::Config#data_path/category/ 以下の、カテゴリごとに作られるキャッシュファイルがずいぶんたくさん更新される。全部ではないが半分近い 21のファイルが更新されていた。日記の内容はというと一つのカテゴリしか使っていない。
どこのコードが無駄にファイルを更新しているのかと絞っていくと、category.rbの中の Category::Cache#replace_sectionsだとわかった。ではこの replace_sectionsが悪いのかというとそうではない。replace_sectionsの中の
PStore.new(cache_file(c)).transaction do |db| end
に囲まれた部分をすべてコメントアウトしても 21のキャッシュファイルが一斉に更新されたのだから。一部の PStoreファイルは開いて閉じるだけで常に更新されるのだとしか考えられない。
PStoreは transactionの前後で Marshal::dump の戻り値のサイズと MD5が変化したかどうかを見て、変更があったかどうかを判断し、ファイルに書き込みをするかしないかを決めている。Marshal::load/dump が対称ではないのだろか。(そもそも Hashを Marshal::dumpした結果が一定だと仮定してよいのだろうか*)
原因が何であるにせよサーバーの pstore.rbを書き換えるわけにもいかないので、tDiaryの category.rbに対策を施した。
読み出し専用であることが予めわかっている PStore#transactionはすべて trueを引数にして(readonly=trueの意)呼び出す。実はそういう transactionはすべて PStore#abortで終わっているのでこの対策は必要ないのだが、ファイルを排他ロックすることと MD5を計算する手間が省けるので一応。
肝心の Category::Cache#replace_sectionsは transactionの開始前に変更があるのかどうかがわからないので PStore#transaction(true)は使えない。日付の削除が空振りに終わったときにだけ PStore#abortを呼ぶことにする。これで必要最低限のキャッシュファイルだけが更新されるようになった。
# # cache each section of diary # used in update_proc # def replace_sections(diary) return if diary.nil? or !diary.categorizable? categorized = categorize_diary(diary) categories = restore_categories deleted = [] ymd = diary.date.strftime('%Y%m%d') categories.each do |c| PStore.new(cache_file(c)).transaction do |db| db['category'] = {} unless db.root?('category') if categorized[c] and diary.visible? db['category'].update(categorized[c]) else # diary is invisible or sections of this category is deleted db.abort unless db['category'].delete(ymd) deleted << c if db['category'].empty? end end end if !deleted.empty? deleted.each do |c| File.unlink(cache_file(c)) end replace_categories(categories - deleted) end end
* 追記:最近の Ruby(1.8.7だか 1.9.0)は Hashの keyの順序を保存しているみたいだけど。
2.2.0に変更すると半分程度の月の日記データ(YYYYMM.td2)が消えた。残ってる月も表示されるのは一、二件程度。原因を調べると、もちろん原因は自分にあったんだけど、2.2.0の更新された tdiary/wiki_style.rbに、DIFF差分をもとに過去に自分が加えた変更をもう一度加えたときに行頭の - を一カ所取り除き忘れたのが原因だった。
if /\A(?:http|https|ftp|mailto)\z/ =~ scheme
を、
if /\A(?:http|https|ftp|mailto|javascript)\z/ =~ scheme
こう(↑)すべきところ、
- if /\A(?:http|https|ftp|mailto|javascript)\z/ =~ scheme
こう(↑)してしまったのが原因。
Rubyでは ifといえども値を返すので、Stringに単項マイナスのメソッドは存在しないというエラーになっていたんだけど、そのエラーは tdiary/defaultio.rbの restore()で rescue NameErrorされて捨てられていました。
スクリプトのエラー(原因は自分だけど)で日記データが消えるのはウレシクナイ。スクリプトの間違いは起こりやすく修正も容易だけど、消えた日記データは復活しないのだから。バックアップをとっていてさえ直前のバックアップより新しいものは元に戻しようがない。
エラーで表示できない日記データでも TDiary::WikiDiaryなどのかわりに TDiary::BadDiary などとして保持しておいてほしいな(そしてデータファイルを更新するときには正常なデータと一緒に書き込む)、と思いつきだけで書く。
(怖くなってサーバーの tDiaryがまだアップデートできてません)
セクションごとに表示されるようになったのはもちろんのこと、飛び先が同一ページ内になったことが嬉しい。プライベートな変更(→20040228p02, →20050401p01)は維持するのが面倒くさいし、アップデートの失敗例が直上にあるので。
数日前からこちら(SmallStyle - category プラグインを利用した タグクラウド 表示プラグイン)から入手したタグクラウド表示プラグインを手直ししたものを使っている。
直した理由は、日記を書かなくても毎日キャッシュが更新されるとか(<それ日記じゃない)、すぐに組み立てられる URLをキャッシュに含めなくてもいいじゃないかとか、addとか print_htmlというメソッドにプラグインの名前空間を汚されたくないとか、プラグインファイルの地の部分で returnするとどこへ戻るんだろう無事に戻れるんだろうかとか、そういうこと。いじった結果、ファイルサイズが増加し、キャッシュサイズもたぶん増加し、計算量が増加して、機能はほとんど変わらず、なので変更点は(この人目につかない日記にも)書けない。
本題。変更の過程でフォントサイズの指定をクラスからインラインスタイルに変更し、style="font-size: xxx%" というのを HTMLタグに埋め込むことにした。そして、フォントサイズのパーセンテージの決定方法を以前自分が書いたものから流用した。(ここが問題)。
以前書いたものはタグの出現数の分布を、最小フォントサイズ(100%)から最大フォントサイズ(200%)の間にそのままマップするものだった。これだと一つだけ突出したタグがあると他がみんなどんぐりの背比べになってしまう。では出現数の二乗根を使うとどうなるか、実験してみた。
[1, 2, 3, 10, 20, 50] # タグの出現数のリスト => [100, 102, 104, 118, 138, 200] # 出現数をそのまま使って font-size(100%-200%)を求めた => [100, 106, 112, 135, 157, 200] # √出現数 => [100, 117, 128, 158, 176, 200] # log(e)(出現数)
ここの日記のように一部のタグだけが突出していて、その他のほとんどのタグの出現数が 10以下のような場合は、出現数の自然対数を使うと出現数のわずかな増加にフォントサイズが敏感に反応する。というわけで、Rubyでは sqrtと logの時間コストがほとんど同じであることだし、タイトルを無視して logを使ってフォントサイズを決めることにした。
単純に出現数を使った場合、上で書いたように一つを除いて豆粒サイズになる問題があったが、logを使った場合は一部を除いほとんどが最大に近いサイズになってしまう問題が発覚した。あいだをとってルート。
http://www.machu.jp/diary/20070719.html#p02
はてブ指数というものに使われているという。定義は「N以上の数がN個以上含まれる場合の最大のN」
これなら一部のカテゴリがどれだけ突出していていようとも、「N以上である」としか評価されないわけだ。たとえば求めた H指数が 100だったとき、値が100の要素も10000の要素も「100以上である」とだけ評価されているわけで、重みは等しい。なるほど。でも H指数をフォントサイズの決定にどのように使おう。
10月22日に書いたように「カテゴリ」表示モードは「最新」「月」表示と見た目が違って違和感があるので、カテゴリ表示を最新表示と月表示に近づけてみた。(この日記で実働中)。
新しく表示モード(TDiaryViewを継承したクラス)を作るのは手間だし、TDiaryLatestや TDiaryMonthからのコピペばっかりになることが想像できるので IOのラッパを作って TDiaryLatestや TDiaryMonthを騙すことに。
transactionメソッドを引っかけて元々の IO(DefaultIOか PStoreIO)が渡してきた diariesから特定のカテゴリを持たないセクションを取り除いて呼び出し元に渡す。
transactionの呼び出し元に不完全な日記データが渡る関係上、日記の変更は捨てる。(読み出し専用)
個々の日記のURLを ?date=yyyymmdd から yyyymmdd.html に、mod_rewriteを使って書き換えてる場合、RewriteRuleの最後に [QSA] (query string append)フラグを付けて書き換えた URLにクエリストリングをくっつけてもらう必要がある。クエリストリングはカテゴリを指定するのに使う。mod_rewriteを使っていない場合は .htaccessの変更は必要ない。
RewriteEngine on RewriteBase /ds14050/diary RewriteRule ^([0-9]+)(-[0-9]+)?\.html$ index.rb?date=$1$2 [QSA]
カテゴリの指定に categoryという CGIパラメータを使うので、最新表示にカテゴリフィルタをかけたときの URLが index.rb?category=カテゴリ になり、最後の else まで落ちて最新表示が選択される前に、従来のカテゴリ表示が選択されてしまう。これまでのカテゴリ表示は使わないので該当する二行をコメントアウトする。
if @cgi.valid?( 'comment' ) then …… elsif @cgi.valid?( 'date' ) …… # elsif @cgi.valid?( 'category' ) # tdiary = TDiary::TDiaryCategoryView::new( @cgi, "category.rhtml", conf ) elsif @cgi.valid?( 'search' ) …… else …… end
騙すのは TDiaryLatestと TDiaryMonth。それぞれの initialize()で日記データを読み出す前に、@ioを置き換えるコードを追加する。CategorizedIOは読み出し専用なのでこれ以後 日記の変更はできなくなるが、どちらも日記の変更はしていない(と思う)ので影響はない。
また categoryパラメータが与えられたときにキャッシュを無効にするために cache_file() も変更している。
class TDiaryView < TDiaryBase
(skip)
def load_categorizedio( baseio ) # CategorizedIO is an IO wrapper and is readonly. return baseio if not @conf or not @cgi require 'tdiary/categorizedio' category_dir = File.join(@conf.data_path, 'category') categories = ( @cgi.params['category'] || [] ) return CategorizedIO.new( baseio, category_dir, categories ) end end # # class TDiaryDay # show day mode view # class TDiaryDay < TDiaryView
(skip)
class TDiaryMonth < TDiaryView def initialize( cgi, rhtml, conf ) super @io = load_categorizedio( @io ) begin
(skip)
protected def cache_file( prefix ) @cgi.valid?( 'category' ) ? nil : "#{prefix}#{@rhtml.sub( /month/, @date.strftime( '%Y%m' ) ).sub( /\.rhtml$/, '.rb' )}" end end # class TDiaryMonth
(skip)
class TDiaryLatest < TDiaryView def initialize( cgi, rhtml, conf ) super @io = load_categorizedio( @io )
(snip)
def cache_file( prefix ) if @cgi.params['date'][0] or @cgi.valid?( 'category' ) then nil else "#{prefix}#{@rhtml.sub( /\.rhtml$/, '.rb' )}" end end
(skip)
end # class TDiaryLatest
新規追加ファイル。貼り付けるには厳しい量だな。貼るけど。
既存の IOのメソッドを置き換えるのは transaction() と calendar() の二つ。
transaction() は該当するカテゴリを持たないセクションをはじく機能を持つ。
calendar() は該当するカテゴリを持つセクション の存在する年月のみを返す。calendarを作成するために category.rbプラグインが作成するキャッシュを利用するのでキャッシュが壊れていたり不完全だったりすると影響を受ける。
HOWTO-make-plugin.html には TDiary::Plugin@yearsには全日記の年月データが含まれていると明記されているので、@yearsの元になる calendarを上書きすると category.rbや squeeze.rbなど独自に日記を読み込むプラグインが期待通り動かない可能性がある。
@io.transactionで TDiary::TDiaryBase::DIRTY_NONEを返しても DefaultIOがデータを更新することがある。キャッシュが存在しない場合がそう。yieldを呼び出した後で、CategorizedIOがセクションを削除したりした diariesを元にキャッシュを作成してしまう。今は tdiary/defaultio.rbを書き換えて yieldを呼び出す前に必要ならキャッシュを作成してしまって、yieldの戻り値が DIRTY_NONE以外だった場合は、もう一度キャッシュを作成しなおすようにしているが、単純に tdiary/caretorizedio.rbで DefaultIO#transactionを breakで抜けてもいい。 DefaultIO#transactionを抜けてから diariesをいじるようにして解決済み。
表示しないセクションを削除してしまうとセクションナンバーが変わってしまい、それに伴って URLも変わってしまうので混乱を招くもとになっていた。 12月28日に tdiary/categorizedio.rbを修正して解決済み。
require 'delegate' require 'pstore' module TDiary class CategorizedIO < SimpleDelegator def initialize(baseio, category_dir, categories=nil) super(baseio); @dir = category_dir; set_categories(categories); end def set_categories(categories) @categories = (categories || []); @calendar = nil; # update calendar end def categorized? return(@categories and !@categories.empty?); end def calendar return __getobj__.calendar unless categorized?; @calendar = category_calendar unless @calendar; return @calendar; end # 読み出し専用。ブロック引数を変更しても元々の IO(DefaultIO or PStoreIO)には届きません。 def transaction(date, &block) if(not categorized?) return __getobj__.transaction(date, &block); elsif(! block) # nothing to do. elsif(in_calendar?(date)) diaries = nil; __getobj__.transaction(date){|diar_es| diaries = diar_es; TDiaryBase::DIRTY_NONE; } if(diaries) filtered_diaries = category_filter(diaries); dirty = yield(filtered_diaries); end else yield({}); end end private def category_filter(diaries) filtered = {}; diaries.each{|ymd, diary| next unless diary.categorizable?; diary_is_empty = true; diary.each_section{|section| if((@categories - section.categories).empty?) diary_is_empty = false; else hide_section(section); end } filtered[ymd] = diary unless diary_is_empty; } return filtered; end def hide_section(section) section.extend(HiddenSection); end def in_calendar?(date) y, m = "000#{date.year}"[-4,4], "0#{date.month}"[-2,2]; cal = self.calendar; return(cal.has_key?(y) && cal[y].include?(m)); end # returns intersection of all @categories' calendar. def category_calendar years = __getobj__.calendar; categories = (@categories || []); ymdp_array = nil; categorize(categories, years).each{|ctgr, ctgr_cache| a = []; ctgr_cache.each{|ymd, day_cache| day_cache.each{|cache| section_index, = *cache; a.push(ymd+'p'+section_index.to_s); } }; ymdp_array = ymdp_array.nil? ? a : (ymdp_array & a); break if ymdp_array.empty?; }; category_calendar = {}; ymdp_array.each{|ymdp| y, m = ymdp[0,4], ymdp[4,2]; months = (category_calendar[y] ||= []); months.push(m) unless months.member?(m); } return category_calendar; end # following code is from plugin/category.rb (--;) !DRY # # categorize sections of category of years # # {"category" => {"yyyymmdd" => [[idx, title, excerpt], ...], ...}, ...} # def categorize(category, years) categories = category - ['ALL'] if categories.empty? categories = restore_categories else categories &= restore_categories end categorized = {} categories.each do |c| PStore.new(cache_file(c)).transaction do |db| categorized[c] = db['category'] db.abort end categorized[c].keys.each do |ymd| y, m = ymd[0,4], ymd[4,2] if years[y].nil? or !years[y].include?(m) categorized[c].delete(ymd) end end categorized.delete(c) if categorized[c].empty? end categorized end # # restore category names # ["category1", "category2", ...] # def restore_categories list = nil PStore.new(cache_file).transaction do |db| list = db['category'] if db.root?('category') db.abort end list || [] end def cache_file(category = nil) if category "#{@dir}/#{ERB::Util.u( category ).gsub(/%20/,'+')}".untaint else "#{@dir}/category_list" end end end module HiddenSection def html4(date, idx, opt) return <<-"HTML"; <% section_enter_proc( Time::at( #{date.to_i} ) )%> <% section_leave_proc( Time::at( #{date.to_i} ) )%> HTML end def chtml(date, idx, opt) return html4(date, idx, opt); end def subtitle_to_html return ''; end def body_to_html return ''; end def stripped_subtitle_to_html return ''; end end end
ナビリンクとカレンダーを categoryパラメータ対応に。c_anchor() がカテゴリに対応した anchor()で、この後で category.rbに追加する。
def navi_user_latest anchor = method(respond_to?(:c_anchor) ? :c_anchor : :anchor) result = '' result << navi_item( "#{@index}#{anchor.call( @conf['ndays.next'] + '-' + @conf.latest_limit.to_s )}", navi_next_ndays ) if @conf['ndays.next'] and not bot? result << navi_item( @index, navi_latest ) if @cgi.params['date'][0] result << navi_item( "#{@index}#{anchor.call( @conf['ndays.prev'] + '-' + @conf.latest_limit.to_s )}", navi_prev_ndays ) if @conf['ndays.prev'] and not bot? result end
def navi_user_month …… anchor = method(respond_to?(:c_anchor) ? :c_anchor : :anchor) result = '' result << navi_item( "#{@index}#{anchor.call( prev_month )}", navi_prev_month ) if prev_month and not bot? result << navi_item( @index, navi_latest ) result << navi_item( "#{@index}#{anchor.call( next_month )}", navi_next_month ) if next_month and not bot? result end
def calendar result = %Q[<div class="calendar">\n] @years.keys.sort.each do |year| result << %Q[<div class="year">#{year}|] @years[year.to_s].sort.each do |month| m = "#{year}#{month}" if(respond_to? :c_anchor) result << %Q[<a href="#{@index}#{c_anchor m}">#{month}</a>|] else result << %Q[<a href="#{@index}#{anchor m}">#{month}</a>|] end end result << "</div>\n" end result << "</div>" end
c_anchor プラグインメソッドを追加。カテゴリパラメータに対応した anchorメソッド。
def c_anchor(s) a = anchor(s) return a unless @category_info a << (a.index('?') ? ';' : '?') a << @category_info.query_string a.chomp!(';'); a.chomp!('?') return a end # # misc #
既存の make_anchor()メソッドの前半からクエリストリング作成部分を query_string()メソッドとして切り出し。上の c_anchor()で利用するために。
def make_anchor(label = nil) if label case mode when :year label = label.gsub(/\$1/, @year) when :month, :quarter, :half label = label.gsub(/\$2/, @month) label = label.gsub(/\$1/, @year || '*') end else label = @category.map {|c| CGI.escapeHTML(c)}.join(':') end %Q|<a href="#{@conf.index}?#{query_string}">#{label}</a>| end def query_string qs = @category.map {|c| "category=#{CGI.escape(c)}"}.join(';') qs << ";year=#{@year}" if @year qs << ";month=#{@month}" if @month qs end
Category::Infoを毎回作るのが面倒なのでインスタンス変数として保持。
@categories = @category_cache.restore_categories @category_info = Category::Info.new(@cgi, @years, @conf) if @mode == 'categoryview' @categorized = @category_cache.categorize(@category_info.category, @category_info.years) end
検索結果の表示も同じ方法でできる。スコア順に表示したりはできないけど、特定の語を含む日記を時系列順に閲覧することはできる。ワードフィルタ? IOの三段重ねでワードフィルタとカテゴリフィルタを両方適用することもできるね。
やってみた@2007-12-18。→http://vvvvvv.sakura.ne.jp/ds14050/diary/?word=windows
単語のハイライトとかナビゲーションリンクの対応とかカレンダーをどうするかとかは一切放置。本当にやってみただけ。word=WORD1というパラメータを付ければ「月別」「最新」「カテゴリ」表示のどれでもフィルタリングできるはず。
2008年の 1月頃から約 1年間、上記のリンク先が Internal Server Errorになっていたもよう。なんたる無様。それに気付くきっかけが連日のコメントスパムというのがなんともはや。
squeeze.rbによる HTMLファイルが既にあるし、ってことでただの grepの出力を HTMLに整形するだけのもの。タイトルとかサブタイトルを参照してもうちょっと見やすく整えたり、AND検索のときの出力に最後の検索語しか現れないのをなんとかしたりとか、そんなことの前に検索対象を間違えてることにいま気付いた。
td2 | html | |
---|---|---|
2006 | 127KB@12files | 981KB@130files |
2007 | 164KB@11files | 1.03MB@97files |
td2を読まないと……(__;) Namazuのインデックスのサイズだって元のデータが小さいからたかがしれてるぞ。
etDiary用のスプリッタ( 'etdiary' => /^<<(?!<)/ )を加えたのと、Wiki用のスプリッタを /^!/ から /^!(?!!)/ に変更したのと、本文のない日があるとその次の日を加えた二日分をまとめて読んで一日としてしまうことがあったので read_diaries(path) をごにょごにょと泥臭く修正した。
tDiaryにはいくつかの表示モードがあって、最新と月の表示は一貫してるもののカテゴリ表示はなんか違う。検索結果については検索に何を使うかによって全く変わってくる。
表示形式 | 表示制限 | フィルタ | ソート | |
---|---|---|---|---|
最新 | 全文 | 数件ずつ | (なし) | 日付降順 |
月 | 全文 | すべて | 年月 | 日付昇順 |
カテゴリ | サマリ* | すべて | カテゴリ + 期間(月、四半期、半期、年、全て) | 日付降順 |
検索結果 | 検索エンジン依存 |
なにかというと、カテゴリ表示を
表示形式 | 表示制限 | フィルタ | ソート | |
---|---|---|---|---|
カテゴリ | 全文 | 数件ずつ | カテゴリ | 日付降順 |
カテゴリ | 全文 | すべて | カテゴリ + 期間(年、年月) | 日付昇順 |
の選択制(選択するのは読者)にしたいな、と。最新表示と月表示にカテゴリフィルタを付けたようなかたち。違和感とかとまどいは減るんじゃないでしょか。
最近のエントリ(名前は違ってもそれに類するもの)は前後のエントリに置き換わればいいと思っている。
日記などで一番よく使うナビゲーションは「つぎの○件」。記事の独立性が高いブログなどでは、タイトルを見て中身を推測でき、読む読まないを事前に判断できる「最近のエントリ」も役に立つ。つぎの○件がないところではこれが唯一のナビゲーションだったりもする。アーカイブとかカレンダーとかは使わない。日記であれブログであれニュースサイト以外では記事が書かれた日時を気にすることがないから。
で、最近のエントリを利用して個別記事に飛んでから困るのが、次の移動先を同じ場所(最近のエントリ)から見つけられないこと。「次の記事」「前の記事」のリンクは全ての記事を読みたいわけではないので使いにくい。だから新着記事だけを閲覧したあと、(もうちょっと読みたいけど)サヨウナラということがたびたび起こる。ナビゲーションが途切れちゃってるのが問題。
最近のエントリでなく前後のエントリであれば、トップページでの表示は最近のエントリと同じものになるし、リンク先の個別の記事ではその前後の複数のエントリへのリンクを提供するので、興味のある記事だけを選んでとびとびに過去へ(あるいは未来へ)移動しつづけることができる。
個別記事へ直接とんできて最近のエントリが読みたくなった人はそのサイトのタイトル(大抵はトップページへのリンク)をクリックすればいいと思うよ。
(最初以外は tDiaryの話ではなかった)
* summaryの訳は要約なのでさわりと同じ意味。このサマリはさわりのよくある誤用と同じ誤用です。
画像が見つからないときにテキストリンクを表示する(true)か noimage画像を表示する(false)かの設定。
class="amazon"の時に画像の横に表示されるテキストと、画像の代替テキストなど(alt, title)は区別して、altや titleは常に設定されるようにして欲しい。
この辺のことです。
if with_image and @conf['amazon.hidename'] || pos != 'amazon' then label = '' elsif not label label = %Q|#{amazon_title( item )}#{author}| end
両方とも自分でなんとかしてみる。
def amazon_image( item ) image = {} begin size = case @conf['amazon.imgsize'] when 0; 'Large' when 2; 'Small' else; 'Medium' end image[:src] = item.elements.to_a( "#{size}Image/URL" )[0].text image[:height] = item.elements.to_a( "#{size}Image/Height" )[0].text image[:width] = item.elements.to_a( "#{size}Image/Width" )[0].text rescue image[:src] = image[:height] = image[:width] = nil end image end def amazon_default_image image = {} base = @conf['amazon.default_image_base'] || 'http://www.tdiary.org/images/amazondefaults/' case @conf['amazon.imgsize'] when 0 image[:src] = "#{base}large.png" image[:height] = 500 image[:width] = 380 when 2 image[:src] = "#{base}small.png" image[:height] = 75 image[:width] = 57 else image[:src] = "#{base}medium.png" image[:height] = 160 image[:width] = 122 end image end def amazon_to_html( item, with_image = true, label = nil, pos = 'amazon' ) with_image = false if @mode == 'categoryview' author = amazon_author( item ) author = "(#{author})" unless author.empty? label = %Q|#{amazon_title( item )}#{author}| unless label if with_image image = amazon_image( item ) image = amazon_default_image if not image[:src] and not @conf['amazon.nodefault'] if image[:src] then img = <<-HTML <img class="#{h pos}" src="#{h image[:src]}" height="#{h image[:height]}" width="#{h image[:width]}" alt="#{h label}" title="#{h label}"> HTML img.gsub!( /\t/, '' ) label = '' if @conf['amazon.hidename'] || pos != 'amazon' else img = '' end end url = amazon_url( item ) %Q|<a href="#{h url}">#{img}#{h label}</a>| end
動いているようだ。labelという変数に二つの役割が与えられているのが微かに気になるが。
def amazon_get( asin, with_image = true, label = nil, pos = 'amazon' ) ……(省略)…… rescue Timeout::Error asin rescue NoMethodError if item == nil then message = doc.elements.to_a( 'Items/Request/Errors/Error/Message' )[0].text "#{label ? label : asin}<!--#{h @conf.to_native( message, 'utf-8' )}-->" else "#{label ? label : asin}<!--#{h $!}\n#{h $@.join( ' / ' )}-->" end end
labelは他の場所で HTMLエスケープ前の文字列として扱われているのでエスケープするのがよい。asinだって中身が英数字に違いなかろうがなんだろうがエスケープすればいい。
コメント(<!---->)の中身は h(ERB::Util.html_escape) する必要はなく .gsub(/--+/, '-') した方が良い、とまで言うとパラノイア・原理主義者っぽい気がするのは何故? コメントに起因する不具合に遭ったことがないから?
tDiary-2.1.4からは section_enter_procと section_leave_procが用意されてるので、現在 body_leave_procを使って一日毎に行ってることを、section_leave_procでセクション毎に行うだけ。
構わないと思うのですよ、リロードの度にプラグインを評価しなくても。
--- index.rb~ 2005-06-13 14:05:11.000000000 +0900 +++ index.rb 2006-06-11 00:52:18.203125000 +0900 @@ -60,11 +60,15 @@ body = '' head['Last-Modified'] = CGI::rfc1123_date( tdiary.last_modified ) + require 'time' + ims = ENV['HTTP_IF_MODIFIED_SINCE']; ims = ims ? Time.httpdate(ims) : Time.at(0); # ENV? + diary_changed = (tdiary.last_modified - ims) > 30; # 30? + # ETag testing code #require 'md5' #head['ETag'] = MD5::md5( body ) - if /HEAD/i !~ @cgi.request_method then + if /HEAD/i !~ @cgi.request_method and diary_changed then if @cgi.mobile_agent? then body = conf.to_mobile( tdiary.eval_rhtml( 'i.' ) ) head['charset'] = conf.mobile_encoding @@ -73,15 +77,15 @@ body = tdiary.eval_rhtml head['charset'] = conf.encoding head['Content-Length'] = body.size.to_s - head['Pragma'] = 'no-cache' - head['Cache-Control'] = 'no-cache' +# head['Pragma'] = 'no-cache' +# head['Cache-Control'] = 'no-cache' end head['cookie'] = tdiary.cookies if tdiary.cookies.size > 0 print @cgi.header( head ) print body else - head['Pragma'] = 'no-cache' - head['Cache-Control'] = 'no-cache' +# head['Pragma'] = 'no-cache' +# head['Cache-Control'] = 'no-cache' print @cgi.header( head ) end rescue TDiary::ForceRedirect
以前から横にだらだら延びた段落は醜いと思っていた。
このようなページを見つけた。C O U L D:固定か可変かそれが問題だ
早速 cssに下記の一行を追加した。
body { max-width: 50em }
emってな曖昧な単位に少し不安があるが Firefox1.5と IE7.0で同じように見えるので良しとする。
<pre>の中だからってタグが書けないわけじゃなし。インライン要素なら OKのはず。
C:\Documents and Settings\ds14050\デスクトップ>diff -u hikidoc.rb~ hikidoc.rb --- hikidoc.rb~ 2005-10-06 16:42:35.000000000 +0900 +++ hikidoc.rb 2006-05-30 06:34:32.265625000 +0900 @@ -142,8 +142,9 @@ end def restore_pre( text ) - ret = unescape_meta_char( text, true ) - ret = restore_plugin_block( ret, true ) + text = inline_parser( text ) +# ret = unescape_meta_char( text, true ) +# ret = restore_plugin_block( ret, true ) end ######################################################################
''test'' ''test'' ''test&'test\''' ''test&'test\'''
<p><em>test</em></p> <pre> <em>test</em> </pre> <p><em>test&'test'</em></p> <pre> <em>test&'test'</em> </pre>
20050929p02で引用の中で <pre>を使いたい言うてましたが、どうやら最初から可能だったご様子。
hikidoc.rbからの引用。
###################################################################### # blockquote BLOCKQUOTE_RE = /^""[ \t]?/ BLOCKQUOTES_RE = /(#{BLOCKQUOTE_RE}.*\n?)+/ def parse_blockquote( text ) text.gsub( BLOCKQUOTES_RE ) do |str| str.chomp! str.gsub!( BLOCKQUOTE_RE, '' ) "\n<blockquote>\n%s\n</blockquote>\n\n" % block_parser(str) end end
###################################################################### # block parser ###################################################################### def block_parser( text ) ret = text ret = parse_plugin( ret ) ret = parse_pre( ret ) ret = parse_comment( ret ) ret = parse_header( ret ) ret = parse_hrules( ret ) ret = parse_list( ret ) ret = parse_definition( ret ) ret = parse_blockquote( ret ) ret = parse_table( ret ) ret = parse_paragraph( ret ) ret.lstrip end
block_parser()は見ての通り、整形済みテキスト(pre)、見出し、リスト、引用、テーブル、段落などを解釈する。
つまり、引用の中には (<pre>も含めて) block_parserが解釈するブロック要素が全て書ける。
何故今日まで気付かなかったのかというとコレ↓
BLOCKQUOTE_RE = /^""[ \t]?/
ドキュメントでは一般的に、「行頭に "" を付ければ引用になります」と書かれるけど、hikidoc.rbの実装では それに続く空白・タブ文字も引用を表す記号の一部として扱われている。行頭の空白は整形済みテキストを表すりっぱな Wiki記法の一部なのに。
そういうわけで
引用の中で整形済みテキストを書くときは 行頭を「"" 」(注:スペースは 2つ)で始めます。
現在のフォーマット:
'#{author} 【#{title}】 #{label.empty? ? publisher : label}'
20051108#p06のやり方では、『9S(3)』を使って「電撃文庫」を取得できるのは試したし、「ジャンプコミックス」と「角川スニーカー文庫」も多分取得できる。でもこれら一部大手を除くと、富士見ミステリーもファミ通文庫も、その他殆どのレーベルで取得できない。<ItemAttributes>拡張してよ、アマゾンさん。
最終更新: 2011-02-13T06:07+0900
引用文の表現力が上がってるのだけど、もうひとつ、引用の中で整形済みテキスト(pre)を使いたい。
すぐ上のセクション(↑)で、改行を維持するために引用の中のコード部分だけを<pre>で囲ってるのだけど、そのソースはちょっと汚い。(上から二行目と下から二行目で)直接HTMLを埋め込んでるから後々 tDiaryをXHTML化したりするのが難しくなる。HTML化はWikiパーサに全て任せたい。tdiary/hikidoc.rbを拡張して引用文中の<pre>が可能にならないものか。
""7×7の二次元配列aがあるとする。たとえば、以下のようなものである。 "" int a[7,7] ={ "" {1,1,1,1,1,1,1}, "" {1,0,0,0,0,0,1}, "" {1,0,1,1,1,0,1}, "" {1,0,0,0,1,1,1}, "" {1,0,0,1,0,0,1}, "" {1,0,0,0,1,0,1}, "" {1,1,1,1,1,1,1} "" }; ""いま、aを迷路と見立てる。a[x,y]が0の箇所は歩けて、1の箇所は歩けな
新しい Wikiスタイル(HikiDoc)を使いたいがために。
引用の中に
見出しとか
孫引用とか
- リストを挿入してみたりできる
でも多分 http://vvvvvv.sakura.ne.jp/ds14050/diary/ で日記にアクセスすると上の引用部分が下のように見えてるはず。(実際にはキャッシュが働くので新旧tDiaryのどっちが最後にキャッシュを作ったかによる)
引用の中に
!見出しとか
""孫引用とか
*リストを挿入してみたりできる
http://vvvvvv.sakura.ne.jp/ds14050/diary/ に加えた変更を http://vvvvvv.sakura.ne.jp/ds14050/diary-test/ に写し終えたら diary-test/ を diary/ にリネーム。