/ 最近 .rdf 追記 設定 本棚

脳log[tDiary: 2012-01-15~]



2012年01月15日 (日)

最終更新: 2012-01-17T01:44+0900

[tDiary] SourceForge.net: tDiary: TDiary::Plugin の多重初期化を減らしたい

自分の稼働中の tdiary.rbを見てみると該当部分はこうなっていた。

def load_plugins
	calendar
	@lazy_diaries.update( @diaries )
	if @plugin
		@plugin.diaries = @lazy_diaries
		@plugin.date = @date
		@plugin.last_modified = last_modified
		@plugin.comment = @comment
		return @plugin
	end
	@plugin = Plugin::new(
		'conf' => @conf,
		'mode' => mode,
		'diaries' => @lazy_diaries,
		'cgi' => @cgi,
		'years' => @years,
		'cache_path' => cache_path,
		'date' => @date,
		'comment' => @comment,
		'last_modified' => last_modified,
		'logger' => @logger
	)
end

if @plugin ... end部分はたしか kazuhikoさんのコードだったような……。

そうそうこれこれ*。>>脳log[2009-08-06-p01] tdiary.rbと plugin/navi_user.rbにパッチあて(plugin/recent_list.rbの分は使用してないのでスルー) + セクションごとの最終更新日時に一票 >>[tDiary-devel] all_filtersとかload_pluginsが呼ばれ過ぎで遅い件


@plugin.diariesと @plugin.dateと @plugin.comment、それとたぶん @plugin.last_modifiedに更新が必要なのはすでにわかってた話なんじゃないの、ってことなんだけど、このコードは tDiary-3.0.1になったときに失われてる。svn logは「Merge branch 'master' into svn」なので経緯は不明。


>>Re: [tDiary-devel] #194 blog-category.rbが動作しない

* 「@plugin」で引っかからなかったので「捨ておけぬ」でサイト検索して見つけた。


2011年10月04日 (火) [COSMOS] HDT722525DLA380に不良セクタ発生。2006年3月から、5年と半年の寿命であった。うち 4年はバックアップディスクとして月イチ稼働だったけど。

最終更新: 2011-10-28T14:10+0900

[tDiary] リファラ置換リスト(対google)晒し。

^http://www\.google\.(?:[^/]+)/url\?.*?[&;]cd=(1\d)\b.*?[&;]url=[^&;]*?(\d{6}[\dpct]*).*?[&;]q=([^&;]+).*$ google検索 \1th of「\3」→ \2
^http://www\.google\.(?:[^/]+)/url\?.*?[&;]cd=(\d*1)\b.*?[&;]url=[^&;]*?(\d{6}[\dpct]*).*?[&;]q=([^&;]+).*$ google検索 \1st of「\3」→ \2
^http://www\.google\.(?:[^/]+)/url\?.*?[&;]cd=(\d*2)\b.*?[&;]url=[^&;]*?(\d{6}[\dpct]*).*?[&;]q=([^&;]+).*$ google検索 \1nd of「\3」→ \2
^http://www\.google\.(?:[^/]+)/url\?.*?[&;]cd=(\d*3)\b.*?[&;]url=[^&;]*?(\d{6}[\dpct]*).*?[&;]q=([^&;]+).*$ google検索 \1rd of「\3」→ \2
^http://www\.google\.(?:[^/]+)/url\?.*?[&;]cd=(\d+).*?[&;]url=[^&;]*?(\d{6}[\dpct]*).*?[&;]q=([^&;]+).*$ google検索 \1th of「\3」→ \2
  • 適用する順番が大事。
  • Google、検索リファラを変更、検索ランキングデータが取得可能に::SEM R
  • 高機能なリファラプラグインであれば対応してるのかも。(使ってないので知らない)
  • (愚痴) リファラをクリックして自分のサイトが出ても嬉しくないんだよね。検索画面が出れば高確率で自分が関心を持つであろう他のサイトを見つけられるし、自分のサイトが何番なのか画面外なのかもどうせわかるのに。
  • 気付かないふりをしたかったけど、cd, url, qパラメータがこの順番で出現しないと置換に失敗する。数は少ないものの失敗するリファラがこの日記にも届いてる。Ruby 1.8では名前付きキャプチャ、使えないのかなー。
  • これらの正規表現って URLを相手にしてるもんだとばっかり思ってたけど、tdiary.rbの disp_refererメソッドを見るに、%HHをデコードしたあとのどうしようもない文字列が相手のような……。(理解が正しければ)

パラメータの順番を仮定しないために、結局こうなった。名前付きキャプチャは関係なくて、繰り返し付きキャプチャに最終的に入るものを把握することが大事。

^http://www\.google\.(?:[^/]+)/url(?:[?&;](?:url=[^&;]*?(\d{6}[\dpct]*)[^&;]*|q=([^&;]+)|cd=(1\d)|(?!cd=)[^&;]*))*$ google検索 \3th of「\2」→ \1
^http://www\.google\.(?:[^/]+)/url(?:[?&;](?:url=[^&;]*?(\d{6}[\dpct]*)[^&;]*|q=([^&;]+)|cd=(\d*1)|(?!cd=)[^&;]*))*$ google検索 \3st of「\2」→ \1
^http://www\.google\.(?:[^/]+)/url(?:[?&;](?:url=[^&;]*?(\d{6}[\dpct]*)[^&;]*|q=([^&;]+)|cd=(\d*2)|(?!cd=)[^&;]*))*$ google検索 \3nd of「\2」→ \1
^http://www\.google\.(?:[^/]+)/url(?:[?&;](?:url=[^&;]*?(\d{6}[\dpct]*)[^&;]*|q=([^&;]+)|cd=(\d*3)|(?!cd=)[^&;]*))*$ google検索 \3rd of「\2」→ \1
^http://www\.google\.(?:[^/]+)/url(?:[?&;](?:url=[^&;]*?(\d{6}[\dpct]*)[^&;]*|q=([^&;]+)|cd=(\d+)|(?!cd=)[^&;]*))*$ google検索 \3th of「\2」→ \1

 自己言及

リファラ周りを覗いたついでに、セルフリンクの記録を解禁した。my-sequel.rbプラグインの機能を代替できるかな、と。

tdiary.confのベースとした tdiary.conf.sampleに最初からセルフリンクよけがあるのでこれを削除。dayモードからのリンクでないものを除外するように(=dayモードからのリンクだけを記録するように)ブラウザで設定した。(index.rbの有無とか日付を ?date=形式で渡してるかどうかとかサイトに合わせた調整が必要)

リンク元記録除外リスト
^http://vvvvvv\.sakura\.ne\.jp(?!/ds14050/diary/\d{8})

最新の日記にだけ表示される「以前の日記へのリンク元」からのリンクは自己言及とは言えずノイズでしかないので 05referer.rbの referer_save_currentに次のコードを挿入した。

# reject self-reference from (maybe) volatile list
return if referer.start_with?(@conf.base_url) and (date = referer.scan(/\b(\d{4})(\d\d)(\d\d)\b/).last) and latest_day?(Struct.new(:date).new(Time.local(*date)))

期待通り動いてるか動いてないかはまだ観察できてない。

「以前の日記へのリンク元」へセルフリンクを記録しない方法をとらなかったのは、そこで過去の日記の動きを観察したかったから。でもリストに記録された日からどこへ移動したかはわからないね。リファラを集約するときに「どこへ(のリンクか)」という情報を蔑ろにしたせいだ。余計なお世話だと思ってた googleの新しい形式(といっても2009年)のリファラがその情報を与えてくれてたりする。

 除外リスト

tdiary.conf.beginnerには FeedReaderからのリンクを記録しない設定が予め用意されている。俺も除外したいと思った。でも tdiary.conf.sampleには用意されていない。beginnerの定義、.beginnerと .sampleを分ける法が知りたい。

 2011-10-28

どうやら volatile referer listからのリンクの除外に失敗してる。原理的にリファラを表示したときとリンクを踏んだときの間に日記の投稿があった場合には除外できないんだけど、それにしては数が多い。volatile referer listは特別に内部リンクを表示しないことにする。

plugin/05referer.rb: referer_of_today_long
@referer_volatile.each_referer( limit ) do |count,ref|
	next if ref.start_with?(@conf.base_url) # この一行。
	result << %Q[<li>#{count} <a rel="nofollow" href="#{h ref}">#{h disp_referer( @referer_table, ref )}</a></li>\n]
end

2011年08月03日 (水) はてブで『耳刈ネルリ』の文字をみかけた。売れるといいね。次作を待ってるんだけどファミ通文庫は『荒野の恋』や『暴風ガールズファイト』『カーリー』を最後まで出せなかった根性なしだからなあ。『ヒツギでSOSO!』の人も続刊がないし、森橋ビンゴの『ラビオリ・ウェスタン』も単発で終わっちゃった。売れる物を売るだけじゃ先細りするばかりなのに。もっと、俺好みのを。

最終更新: 2011-08-04T03:29+0900

[tDiary] PageDownキーによるスクロール量をごにょごにょするプラグイン。

日と日の境が行き過ぎてしまわないようにスクロール量を制限する。

うっとうしいかな? スムーズスクロールも効かないしな。でも AutoPagerizeの機能の一部としてあってもいいような気もする。

動作確認済み> Firefox 5.0, Internet Explorer 9, Safari 5.0.2, Opera 11.50

* この日記(tDiary-2.3.3.20091124)にはまだ js_urlメソッドとか enable_jsメソッドとか無いのです。


2011年05月24日 (火) パンストの続編見たいな。

最終更新: 2017-09-15T10:23+0900

[tDiary] 同カテゴリのセクションへのリンクを表示するプラグイン(sections_of_a_category.rb)。

なんというか……ばっちい(だから非公開(書き直すつもりがないので観念した))。だらだら書き下すとこうなるのね、っていう。申し訳のように抜き出したカスタマイズポイントもうまく分離できなかった記憶がある。たんに切り取って別の場所に貼り付けただけだろう、と。svn logを見てみたら 2009年3月に書いたものらしい。最初はセクションごとに表示する仕様で、それだと日記を分断して邪魔なので一日の最後に表示するオプションを付けたとか。……logによれば。とりあえずこの日記(tDiary-2.3.3.20091124, Ruby-1.8.7-p248)では動いてるみたい。

「カテゴリ[……]の他の日記」リンクの URLはこの日記に特有の、最新表示とカテゴリ表示をくっつけたもののだから他の日記では使えないね(それも非公開の理由)。

2007年や 2009年の日記のタイトルが 2011年のこの日記から参照できてるのは「プラグインが自由に日記データを取得できる手段を提供した」恩恵を my-exプラグインが受けているからじゃないかと推測してる。

 @2017-09 書き直した(リンクはそのまま)。


2011年02月12日 (土) テイラー展開とかマクローリン展開。言葉は出てこなかったけど積分の演習問題として高校で出てきてた(ことを後に知った)。コンピュータ向けの計算だよね。展開して多くの項を計算すればするだけ精度のいいπが求まるとか。昨日の Q57から連想したんだけど、平方根の計算はまた別っぽい。手計算で平方根を求める方法は俺の時代の教科書からは消えてたよ。消えてたといえば行列を使った回転も消えてたよ。高校数学は新しく習う概念がバラバラのままなんだよ。統合してよ。///「冪剰余 - Wikipedia」っていう言葉があるのね。(ギブアップした Q250関連) 「剰余」から「n-進展開 (Wikipedia(ja):剰余)」→「p進数 - Wikipedia」とんでもないところに飛んでしまった。

最終更新: 2012-12-12T02:17+0900

[tDiary] MathMLが使いたかったので。>「ひらくの工房 - tDiary XHTML化キット

  • HTMLモードなんていらないから拡張子は xhtmlでなく htmlで、とか
  • \times より × って書きたい、とか
  • <option selected="selected"> <input checked="checked"> みたいに属性名を省略してない HTMLもあるのよ、置換しすぎないで、とか

適当にカスタマイズして設置した。

XHTML化キットの存在は投稿されたアナウンスを読んで知ってたんだけど、XHTML好きにもかかわらず今まで導入してなかったのは、日記に数式を書く機会がなかったのと、出力段階での文字列置換による XHTML化が乱暴に思えたから。結局、他に方法がないので目をつぶったが。

閲覧不可能になった日記がないか月別表示で全日記を表示してみたら 10か所くらい見つかった。パターンは以下の通り。

  • プラグインが埋め込む JavaScriptに含まれる & 記号
  • etDiaryスタイルがエスケープしてくれなかった本文中の & 記号
  • プラグイン記法( {{~}} )で直接埋め込んだ HTML片に含まれる & 記号

 JavaScriptの &

<script>タグの中であっても & は &amp; と書かなければいけない。不等号も。でも if(a &amp;&amp; b) {} とか置換してしまったらアホだ。CDATAセクションを使う。

scriptタグが解釈できないブラウザなんて PCを手に入れた当時から持ってなかったので

<script><!--
--></script>

なんて最初から書かなかったし、XHTMLを知ってからは下のように書くことにしてる。

<script>/*<![CDATA[*/
/*]]>*/</script>

コメントで囲うのは JavaScriptとして正しくあるため。より字数の少ない行コメントにしないのは、なにかでエラーになったから。MSがリリースしてた JavaScriptを暗号化(難読化)するツールに <script>タグを含んだ HTMLファイルを通したときだったか……。

 etDiaryの &

但し書きを付けても無駄。HTMLと日本語は混ぜるな危険。中途半端な HTMLの隠蔽は悪。tDiaryスタイルと etDiaryスタイルより Wikiスタイル。

 手書き HTMLの &

自業自得。さすがに、タグに挟まれた部分に & や <, > を放置してたわけではない。

<a href="ttp://hoge/script.cgi?a=b&c=d">
<img src="ttp://hoge/script.cgi?a=b&c=d">

すべてこの形の & がエラーになっていた。HTMLを出力するスクリプトでは、属性値は機械的に必ず HTMLエスケープするようにしてるんだけど、手書きだと上のような (X)HTMLは正しく見えてしまうのか、忘れられてた。こんなことがあるとやっぱりセミコロンで区切りたいねえ。

それにしても、ちらちら目に入る古い日記が恥ずかしくて死ねる。読む人間も、そもそも読める内容もないのであえて気づかぬふりで放置するけども。


2010年04月16日 (金) ここまで読んだ。「【コラム】3Dグラフィックス・マニアックス (53) 水面の表現(2)~動的なさざ波 | パソコン | マイコミジャーナル」今や隔世の感があるバーチャファイターや FF7からの道のりを辿れる。

最終更新: 2010-05-01T17:13+0900

[tDiary] Hatenaスタイル

リストをネストしたときだけ生のテキストに<p>タグが付くのがオリジナルのはてなと違うんだそう。ついでに空の<p>タグが生成されるのも気になるけどそれは別。

最初に。<li>はブロック要素もインライン要素も包含可能らしいけど両方を並べて含んでいいのかはわからなかった。読めない(block or inlineの 0以上の繰り返し、だという気はする)。Another HTML-lintが文句を言わなかったのでいいものとしよう。

次に、CodeReposの hatena_style.rbの中を見てみる。Hatena::Blockがなにやら特別な位置を占めてるけど、一応 Hatena::Inlineと同じように振る舞うみたい。ところで、Hatena::Blockはブロック要素の連なり、Hatena::Inlineはインライン要素の連なりを表してるけど、その両方の連なり(<li>の中身とか)を表すクラスがない。

てっとりばやく済ませるために、Hatena::BlockAndorInlineというクラスを用意して Hatena::Blockが行っていた仕事をごっそり移動するそれだけでなく。Hatena::BlockAndorInlineにはインライン要素を含んでいいのかを示すフラグを initializeを通して与え、それにより何が何でも<p>タグで囲むのかそのままにするのかという動作を変えることにした。現状、求められてるのがその違いだけだから。

Hatenaスタイルは使ってないからあからさまなバグがあっても気付かない可能性が大きいです。

suppress_ptag_in_nested_list.patch (7.1KiB, 2010-04-16, r37217との差分)古くなりました。


順序付きリストや定義リストの対応を忘れてる。

<dt>の中はインライン要素の 0以上の繰り返し、<dd>の中はインライン要素とブロック要素の 0以上の繰り返しっぽい。Hatenaスタイルでは定義リストにはインライン要素しか認めていないので対応は必要なし。順序付きリストだけ。

suppress_ptag_in_nested_list.rev2.patch (8.2KiB, 2010-04-16)

svn diffで -x -p (-x <ARG>: diffに渡すオプション, -p: Cの関数名を表示)を初めて付けてみたけど、Rubyスクリプトであってもクラス名は拾ってくれるみたい。


 @2010-04-18 MLに書いた件

次々空いてくる穴をふさぐのはどちらかといえば好きだけど……。

suppress_ptag_in_nested_list.rev3.patch (8.4KiB, 2010-04-18)
r37218に対して。rev2パッチ + (無限ループになっていた例での)段落の分割を防ぐ。
suppress_ptag_in_nested_list.rev3-2.patch (2.9KiB, 2010-04-18)
r37218に対して。(無限ループになっていた例での)段落の分割を防ぐ、だけのパッチ。

 @2010-04-20 「stack level too deep (SystemStackError)」になるらしい。

それはそれとして、

  • Hatena::Block#title_is_dummy?のロジックが反対。(誤:==/正:!=)
  • r37240はリストが必要以上に深くなる。
  • Hatena::Section#bodyは期待通りうごかないかも。(目的がデバッグ出力だったなら OKだけど)

      def body
        @tree.body.to_s
      end

    @treeは以前は Hatena::Blockか Hatena::Inlineだった、俺の変更で Hatena::BlockAndorInlineか Hatena::Inlineになった。カスタマイズされた to_sメソッドを持っているのは Hatena::Blockだけ。

  • 空のサブタイトルでエラー。Hatena::Block#initializeでの配慮が足りなかった。

肝心の深すぎる再帰は再現できない。自分自身を子要素に持ってるなんてことはないはずだから convertメソッドが自分自身を呼び出すしかない。その可能性があったのは Hatena::Block#convertのみ。そのときは if @body == selfでチェックして @body(Hatena::Block).convertを回避していたし、今は @bodyに Hatena::Inlineか Hatena::BlockAndorInlineしか入っていないから自分自身を呼ぶことは考えられない。わからん。キャッシュのせいだったら拍子抜けだなあ。<<<以前は @bodyに Hatena::Blockが入っていたんだから、古いデータと新しいコードの組み合わせで無限再帰は十分ありうる話。.parserキャッシュにはクラス名やインスタンス変数も入ってるし。

 とりあえず
  • fix_c37239.patch (623B, c37239の修正)

    サブタイトルの有無の判断が間違って反対になっていた。

    サブタイトルのみのときにエラーになっていた。

  • fix_c37240.patch (955B, c37240の修正)

    順序付き、順序なしリストの階層が勝手に深くなっていた。

    セクションの分割に失敗していた。


 「<p>はなくなったが空行が……」

たぶん HTMLソースレベルの空行なんだと思う。Hatena::BlockAndorInline#convertでインライン要素の後だけは改行を付加しないようにすればなくせるが、<p>の有無と違って(white-space:preとかしない限り)見た目に関与しないのでこだわらない。(<p>の後ろと</p>の前にも改行が挿入される、だとか考え始めるときりがない)

スレ主は hatena_style.rbを使うことにこだわってはいないという。こちらは楽しんでやってるんだけど、いつまでもデバッグに付き合ってもらうわけにもいかないだろう。はてなはウクレレ記法ができてこそのはてなだと思ってるので、それができない hatena_style.rbから乗り換えるのも悪くないと思う。

「ウクレレ記法」で検索したら主犯が見つかった。最初は Hikiに実装したって書いてるよ。(始めて一か月ほどだったとも書かれていて)つくづくプログラミングは何に使うかが大事だと思う。(英語なんかと一緒でツール)


 @2010-04-25

http://coderepos.org/share/ に書かれている通りにメールを送ってみたが音沙汰がない。スパム判定されたか。comitterってそのページに書かれてるので全てだよね。行動力のある人間なら(というか人並みの人でも) Twitterなり IRCなり blogなりでアクションを起こすんだろうが……。(だから手続きが自動化されてなさそうなのが嫌だったんだ。漏れるし遅れるし。それにサービス自体個人の奉仕に依存してる)

気になる。自分のバグも他人のバグも気になる。


 @2010-05-01: c37240のバグは c37332で本人の手によって修正されたみたい。

Hatena::Section#initializeの部分の修正は、既に行頭の *で分割された文字列が与えられるから問題ないとして先のファイル(fix_c37240.patch)に含めてなかったけど、まあ subが gsubになったって減るもんじゃなし。

それだけでなく 実は一行だけ buffer = '' を buffer = lines.shiftに書き換えた。これがないと無限ループに陥るケース(「>>>」という本文。3文字目は他の文字でもいい)があるみたいなので。


2010年04月15日 (木) 「新快速 ○○行き HH:MM △分遅延」結局何時に着くのだろう? 5分後の HH:MMなのか 5+△分後なのか。電光掲示で知りたい(またそれだけのスペースしかない)のは次の電車がいつ来るかなので遅延も織りこみ済みだろうと思いかけたが、すぐに、正確さが売りの日本の電車のこと、あれはいつ着くはずであったのかを示す時刻だろうと考え直した。(その通りだった。それに発着時刻は電車のIDでもあるし(あっち行き/こっち行きだけで済む駅ばかりではないだろう))

最終更新: 2010-05-07T12:01+0900

[tDiary] 非同期ファイルアップロード

設定画面に jQueryが仕込まれたのと、JavaScriptの置き場所が用意されたので、ファイルのアップロードを非同期に行う準備が整ったのではないか。独自のサブミットボタンを編集画面に貼り付けるプラグインが全部非同期になれば、プレビュー画面にアップロードフォームを表示しても(編集中の本文が消えたと)怒られないだろう。

ジャンクだけど Firefox3.6と Internet Explorer8と Google Chrome4.1で動くものをこの日記(最新じゃないので jQuery対応ではない)に仕込んでみた。

  1. アップロードフォームの targetをこっそり作成した iframeにすることで編集画面が遷移するのを防ぐ。
  2. iframe.{onload|onreadystatechange}をつかまえて、レスポンス(新しい編集画面)から目当ての要素(アップロード済みのファイル一覧とか)を取り出す。(iframe.{contentDocument|contentWindow.document}.getElementById("image_file_list"))
  3. replaceChildは問題が起きるかも?と思ったので .innerHTML = .innerHTMLでざっくりページの一部を更新。
  4. ファイル一覧の数が 0個と 1個を行き来するときは画面が痙攣したようになるのでアニメーションにできると良さげ。

これで本文を書きながらプレビューもできるしファイルのアップロードもできる。


クリップボードアクセスのために Flashを使うよりはましだけど、<iframe>に頼らなければいけないというのもわりと屈辱的。<frameset><frame>以上に、これまで存在を認めてこなかったのに。


何となくできないものと思っていたけど <iframe>の中から外のドキュメントにアクセスすることができるみたい。それができたら編集画面が無制限に入れ子になる(編集画面の<iframe>の中の編集画面の<iframe>の中の……)ことを今より簡単に防止できて、スクリプトももうちょっと使いやすく書き換えられる。


 Firefoxが(3.6になっても)テキストエリアのスクロール位置をリセットしてしまう件。

Karetta|[JavaScript] Firefoxでtextareaのカーソル位置に文字列を挿入した後にスクロールが先頭に戻ってしまう問題(karetta.jp)」で紹介されている通りで直った。といってもできるだけ余計なことをしないように条件を付けたが。

var scrollTop = textarea.scrollTop;
/*
  ここで、テキストエリアに文字列を挿入する。
*/
if(0 == textarea.scrollTop && 0 < scrollTop) {
  // スクロール位置がリセットされていたので復元する。
  textarea.scrollTop = scrollTop;
}

ところで、Internet Explorer 8だけはテキストエリアのキャレット位置を保存しようなんてことを考えなかったみたいで、いったんテキストエリアがフォーカスを失うとキャレットが必ず末尾に移動する。スクロール位置がどうこうどころではないわな。


 @2010-05-07

「何となくできないものと思っていたけど <iframe>の中から外のドキュメントにアクセスすること」 < HTML5 sanbbox属性


2010年03月08日 (月) obsoleteって形容詞だと思うんだけど推測辞書に obsoletedや obsoletingが含まれてるんだよね(辞書には載っていないが)。obsoleted - Wiktionaryによれば nonstandardだけど一部の分野で使われている、ということらしい。

最終更新: 2014-12-05T17:23+0900

[tDiary] spam絨毯爆撃対策

送信元が限られてるのかワンパターンだったので、これまでは </a> を NGワードにしてほとんどの spamを防いでいたんだけど、今日は手ひどくやられた。

フィルタはインストール方法がよくわからない。Comment-key Filterを tdiary/filter/ に設置したがエラーが出るので、とりあえず最新の 2.3.3.20091124にアップデートした。そうすると plugin/60sf.rbが有効になっているはずなので misc/filter/ にフィルタスクリプトを、misc/filter/plugin/ にフィルタに関連する設定などを表示するプラグインスクリプトを、misc/filter/plugin/ja/ などにプラグインの言語リソースをインストールし、設定画面でフィルタを有効にするといずれのスクリプトも読み込まれるようになる。

 スパムフィルタ1: Comment-key Filter & Plugin version 0.5.0

コメントフォームにキー文字列を埋め込み、コメントが HTMLフォームを通して投稿されたことを確認する。キーは日記設置者の決めた文字列と日記の日付によって一意に決められるので、ある日のキー文字列を一度取得すればその日には何件でも機械的に投稿することができる。GETして HTMLを切り刻む手間をかければ最初から機械的に投稿することもできる。そんなのは防げない。

tDiary-2.3.3.20091124にインストールするのなら key.rbを misc/filter/key.rbへ、comment_key.rbを misc/filter/plugin/key.rbへ、ja/comment_key.rbを misc/filter/plugin/ja/key.rbへコピーすれば良い。key.rbというファイル名と KeyFilterというクラス名は対応しているので、key.rbから comment_key.rbへの改名はやめたほうがいい。コピー後にフィルタを有効にし、キー文字列を設定する。

 スパムフィルタ2: limit_freq.rb

連投対策。同じ IPアドレスからの連続する投稿をはじく。30分につき 3件までしか許可しない、など。

# coding: utf-8
#
# limit_freq.rb: 
#
#  a spam-filtering plugin of tDiary
#  rejects frequent comments posted from an IP address.

module TDiary::Filter
	class LimitFreqFilter < Filter

		def comment_filter( diary, comment )
			now = Time.now.to_i
			comment_i = Comment_t.new( now, @cgi.remote_addr )

			# 数値形式の日時と文字列形式の IPアドレスの二要素
			# 配列の配列を、日時をもとに昇順にソートしたもの。
			# log = [[123456, "1.2.3.4"], [234567, "5.6.7.8"],...]
			log = []

			require 'pstore'
			ps = PStore.new( cache_path )
			ps.transaction( false ) { |db|
				log = db.fetch( DBRoot, log )

				# 新しいコメントを logに追加する。
				log.insert( ArrayExtension.lower_bound(log){|pair| pair.first - comment_i.time }, [comment_i.time, comment_i.ipaddr] )

				# 古い logを捨てる。
				oldest = now - time_span
				log.slice!( 0 ... ArrayExtension.lower_bound(log){|pair| pair.first - oldest } )

				db[DBRoot] = log
				db.commit
			}

			# 残った logに投稿者の IPアドレスがいくつ含まれるか数え、
			# その数が閾値を超えていないか確かめる。
			count = 0
			if log.all?{|pair|
				count += 1 if pair.last == comment_i.ipaddr
				count < threshold
			} then
				return this_is_not_a_spam( comment )
			else
				debug("limit_freq.rb: spam: the # of comments posted from #{comment_i.ipaddr} exceeds threshold:#{threshold} within the last #{time_span.to_i}seconds.")
				return this_is_a_spam( comment )
			end
		rescue
			debug("limit_freq.rb: BUG: #{$!}")
			return this_is_not_a_spam( comment )
		end

	private
		DBRoot = 'LimitFreqLog'
		Comment_t = Struct.new(:time, :ipaddr)

		def cache_path
			return File.join( (@conf.cache_path || "#{@conf.data_path}cache"), 'limit_freq.data' )
		end

		# どれだけの期間(秒単位)、コメントの投稿頻度の判定に
		# 利用するログ(日時とIPアドレスのペア)を保存するか。 
		def time_span
			30 * 60
		end

		# time_span当たり、何件目の投稿からを拒否するか。
		def threshold
			4
		end

		module ArrayExtension
			# ソート済みだという前提を活かしていない!
			def lower_bound( arr, &block )
				index = 0
				arr.length.times{
					return index if 0 <= yield( arr[index] )
					index += 1
				}
				return index
			end
			module_function :lower_bound
		end
	end
end

やめればいいのに本体もちょろちょろと変更。

  • 設定画面における [セキュリティ]>[spamフィルタ]>「spamの扱い spamと判定されたツッコミを{非表示にする|捨てる}」という項目は tdiary/filter/spam.rbに固有の設定らしいのだが、そんなこととは夢にも思わないので、最初は自作のフィルタでスパム判定したコメントが捨てられることに困惑した。設定を本体に取り込んで他のフィルタからも利用しやすくした。
  • limit_freq.rbや limit-freq.rbや limit_--_freq.rb というフィルタに対しては LimitFreqFilterという名前のクラスを要求するようにした。(これらのファイルが複数存在するときは困るなあ。読み込み順で最終定義が変わりそう)
  • どういうわけか無名クラスの下に定義された TDiary::Filter定数を参照してエラーになるので ::TDiary::Filterとした。
Index: core/tdiary.rb
===================================================================
--- core/tdiary.rb	(リビジョン 44436)
+++ core/tdiary.rb	(作業コピー)
@@ -342,6 +342,22 @@
 
 				@logger.info("#{@cgi.remote_addr}->#{(@cgi.params['date'][0] || 'no date').dump}: #{msg}")
 			end
+
+		private
+			# config: hide or drop spam comment
+			def hide_spam?
+				return @conf.options.include?('spamfilter.filter_mode') && @conf.options['spamfilter.filter_mode']
+			end
+
+			def this_is_a_spam( comment )
+				comment.show = false
+				return hide_spam?
+			rescue
+				return false # comment could be a String(@cgi.referer).
+			end
+			def this_is_not_a_spam( comment )
+				return true
+			end
 		end
 	end
 
@@ -1303,7 +1319,7 @@
 			filter_path = @conf.filter_path || "#{PATH}/tdiary/filter"
 			Dir::glob( "#{filter_path}/*.rb" ).sort.each do |file|
 				require file.untaint
-				@filters << TDiary::Filter::const_get( "#{File::basename( file, '.rb' ).capitalize}Filter" )::new( @cgi, @conf, @logger )
+				@filters << TDiary::Filter::const_get( "#{File::basename( file, '.rb' ).split(/[-_]+/).map{|x|x.capitalize}.join('')}Filter" )::new( @cgi, @conf, @logger )
 			end
 		end
 
Index: core/plugin/60sf.rb
===================================================================
--- core/plugin/60sf.rb	(リビジョン 44436)
+++ core/plugin/60sf.rb	(作業コピー)
@@ -4,10 +4,9 @@
 # Modified by KURODA Hiraku.
 
 SF_PREFIX = 'sf'
-@sf_path = ( @conf["#{SF_PREFIX}.path"] || "#{::TDiary::PATH}/misc/filter" ).to_a
-@sf_path = @sf_path.collect do |path|
-	/\/$/ =~ path ? path.chop : path
-end
+@sf_path = ( @conf["#{SF_PREFIX}.path"] || "#{::TDiary::PATH}/misc/filter" ).to_a.map{|path|
+	path.chomp('/')
+}
 
 # get plugin option
 def sf_option( key )
@@ -128,7 +127,7 @@
 			if File.readable?( path ) then
 				begin
 					require path
-					@sf_filters << TDiary::Filter::const_get("#{File::basename(filename, ".rb").capitalize}Filter")::new(@cgi, @conf, @logger)
+					@sf_filters << ::TDiary::Filter::const_get("#{File::basename(filename, ".rb").split(/[-_]+/).map{|x|x.capitalize}.join('')}Filter")::new(@cgi, @conf, @logger)
 					plugin_path = "#{dir}/plugin/#{filename}"
 					load_plugin(plugin_path) if File.readable?(plugin_path)
 				rescue Exception

 @2010-03-09: IPアドレスブロック

一件二件すり抜けるぐらいはいいかと思っていたがここ数日は毎日なのでいいかげん腹が立つ。なによりアクセスログを見たらヒット数、転送量トップが spammerだというのが決定的に許せない。節度を知れ。iptablesはいじれないので .htaccessで Apacheに拒否してもらう。


 @2010-03-11: 対応が早いね

spamコメントがくる月っていうのは、いつもの決まった 2、3か国からのアクセスではなく 10近い国から少数ずつアクセスがあるもんだけど、日単位では固定の IPアドレスから数十件の spamがくるのが常だった。でも今日は一件一件みごとに IPアドレスが異なっている。IPアドレスブロックで次のステージに進んでしまったのか。

spamコメントの目的が特定の URLへの誘導であるかぎりは、コメントに含まれる URLのドメインが白か黒かを判定する方法が有効でしょうね。外部に問い合わせるのは避けたかったんだけど、spamコメントの内容が URLも含めてワンパターンだから実際の問い合わせはごくごく限られた回数にできそうだし、悪くないかな。

 CodeRepos

ここで上記の comment-keyフィルタも含めてスパムフィルタの最新版が管理されていた。 >http://coderepos.org/share/browser/platform/tdiary/filter


 こぼれ話: rel="true"

plugin/00default.rbに含まれるメソッド navi_itemを自分でも使っていたのだけど、tDiaryをアップデートしたら三番目の引数が真偽値からリンクの rel属性文字列へと変更されているせいで rel="true" なるリンクができていた。こうする。

def navi_item( link, label, rel = nil )
	rel = "nofollow" if rel == true # backward compatibility

 @2010-03-09: 日記の編集もキャッシュベース?

スパムコメント一掃のために YYYYMM.tdcを削除したけど日記の編集画面からコメントが消えない。YYYYMM.parserを開くとコメントが含まれていたし、これを削除すると編集画面からもコメントが消えた。原因はデータファイルを直接削除するというイレギュラーな操作だけど、日記の編集はマスターデータに対して行いたい気もする。


2009年12月06日 (日) [C++]「ヘッダファイルの依存を減らす」<端緒。パフォーマンスの本といえば『Efficient C++』だけかと思っていた(『Boost』にそのように書かれていた)ら、目当ての本の横に『C++パフォーマンス戦略』という本が。これまでのところ大垣書店はすごくよくやっている。Amazonで注文する前にとりあえずのぞいてみて、一度も期待を裏切られていない。

最終更新: 2013-05-21T00:39+0900

[tDiary] つれづれに書き連ねるとリンクしにくいらしいので……(誰の話でしょうね。それにあそこははてな)

<h4>と <h5>にもアンカー(<a name="..."></a>)を与える。href属性や中のテキスト(&nbsp;)は URLをコピーしやすくするためのおまけ。そもそも <h4>や <h5>に id属性を付加するのでなく <a>を利用しているところからが利便性目的以外のなにものでもない。<h4>と <h5>の中に <a>を置くことには、highlight.rbを修正しなくてもハイライトが機能するというメリットもある。デメリットは見出し語の先頭に余分な空白が挿入されている、ということ。セクションタイトル右端の編集用リンク「✍」は、<h3>に含まれないように気をつかっているのですよ。

 core/tdiary/wiki_style.rb, TDiary::WikiSection#do_html4

def do_html4( date, idx, opt )
	strdump = lambda{|s| s.dump.gsub( /%/, '\\\\045' ) }

	r = @html.lstrip
	r.sub!( %r!<h3>(.+?)</h3>!m ) do
		"<h3><%= subtitle_proc( Time::at( #{date.to_i} ), #{strdump.call $1} ) %></h3>"
	end or r.sub!( %r!^<p>(.+?)</p>$!m ) do
		"<p><%= subtitle_proc( Time::at( #{date.to_i} ), #{strdump.call $1} ) %></p>"
	end

	serial = [nil, nil, nil, '%02d'%idx, '00', '00']
	dumped_param = strdump.call "#{date.strftime( '%Y%m%d' )}#p#{'%02d' % idx}"
	header_t = Struct.new(:opentag, :level)
	bqlevel = 0
	r.gsub!( %r!(<blockquote\b)|(</blockquote>)|<h(4|5)[^>]*>!i ) do
		bqlevel += 1 if $1
		bqlevel -= 1 if $2
		next $& if bqlevel != 0 or $3.nil?

		h = header_t.new($&, $3.to_i)
		serial[h.level, serial.length-h.level] = Array.new(serial.length-h.level){|i| i==0 ? serial[h.level].succ : '00' }
		frag = 'p' + serial[3 .. h.level].join('.')
		%!#{h.tag}<a name="#{frag}" href="<%=anchor(#{dumped_param}).gsub(/#p#{'%02d'%idx}/, #{strdump.call('#'+frag)}) %>">&nbsp;</a>!
	end

	r.gsub( /<(\/)?tdiary-section>/, '<\\1p>' )
end

2段階に評価されるせいで、すんごく読みにくい。

ハッシュテーブルのキーがシンボルか文字列か気にしたくないのと、括弧や引用符やコロンがじゃまくさいので、. ひとつで済む Structに登場してもらった。

lambdaの .call() も嫌いだなあ。[] で () を代用するおぞましさよりはマシだが。

試してないけど、「end or r.sub!(...」を複数の行に分けるとシンタックスエラーになりそうなのも嫌。

破壊的メソッドが返す nilを利用するコードを初めて書いた!

 @2013-05-21 URLが変わる大きな修正

H4のシリアルナンバーが増加したときに H5のシリアルをリセットするように変更した。以前はこういう連番だった。

  • p01.01
    • p01.01.01
  • p01.02
    • p01.02.02

最後が p01.02.01になるように。


 @2009-12-09

URLフォーマットが変わると影響範囲が広くて困る。自分の日記へのリンクを検出する正規表現をあちこちで書き直した。

Index: core/tdiary/wiki_style.rb
===================================================================
--- core/tdiary/wiki_style.rb	(リビジョン 43193)
+++ core/tdiary/wiki_style.rb	(作業コピー)
@@ -149,7 +149,7 @@
 			hikihtml = HikiDoc::HTMLOutput.new
 			html.gsub!( %r!<a href="(.*?)">(.*?)</a>! ) do
 				k, u = hikihtml.unescape_html($2), hikihtml.unescape_html($1)
-				if /^(\d{4}|\d{6}|\d{8}|\d{8}-\d+)[^\d]*?#?([pct]\d+)?$/ =~ u then
+				if /^(\d{4}|\d{6}|\d{8}|\d{8}-\d+)\D*([pct]\d+(?:\.\d+)*)?$/ =~ u then
 					%Q[<%=my '#{$1}#{$2}', '#{escape_quote CGI.escapeHTML k}' %>]
 				elsif /:/ =~ u
 					scheme, path = u.split( /:/, 2 )
Index: core/plugin/00default.rb
===================================================================
--- core/plugin/00default.rb	(リビジョン 43193)
+++ core/plugin/00default.rb	(作業コピー)
@@ -480,7 +480,7 @@
 # make anchor string
 #
 def anchor( s )
-	if /^([\-\d]+)#?([pct]\d*)?$/ =~ s then
+	if /^([\-\d]+)#?([pct][\d\.])?$/ =~ s then
 		if $2 then
 			"?date=#$1##$2"
 		else
@@ -540,7 +540,7 @@
 # make anchor tag in my diary
 #
 def my( a, str, title = nil )
-	date, frag = a.scan( /^(\d{8}-\d+|\d{8}|\d{6}|\d{4}|)\D*([pct]\d+)?$/ )[0]
+	date, frag = a.scan( /^(\d{8}-\d+|\d{8}|\d{6}|\d{4})\D*([pct]\d+(?:\.\d+)*)?$/ )[0]
 	anc = frag ? "#{date}#{frag}" : date
 	index = /^https?:/ =~ @index ? '' : @conf.base_url
 	index += @index.sub(%r|^\./|, '')
Index: plugin/html_anchor.rb
===================================================================
--- plugin/html_anchor.rb	(リビジョン 43193)
+++ plugin/html_anchor.rb	(作業コピー)
@@ -14,7 +14,7 @@
 if @conf.index.empty? or /\/$/ =~ @conf.index
 
 def anchor( s )
-	if /^([\-\d]+)#?([pct]\d*)?$/ =~ s then
+	if /^([\-\d]+)#?([pct][\d\.]*)?$/ =~ s then
 		if $2 then
 			"#$1.html##$2"
 		else
Index: plugin/my-sequel.rb
===================================================================
--- plugin/my-sequel.rb	(リビジョン 43193)
+++ plugin/my-sequel.rb	(作業コピー)
@@ -487,7 +487,7 @@
 	alias :my_sequel_orig_my :my unless defined?(my_sequel_orig_my)
 	def my(*args)
 		if @my_sequel_active and @my_sequel_date and @my_sequel_anchor and @mode != 'preview' then
-			dst_date, frag = args[0].scan(/^(\d{8})\D*(?:p(\d+))?$/)[0]
+			dst_date, frag = args[0].scan(/^(\d{8})\D*(?:p(\d+)(?:\.\d+)*)?$/)[0]
 			if dst_date and dst_date < @my_sequel_date then
 				dst_anchor = "#{dst_date}#{frag ? "#p%02d" % frag.to_i : ''}"
 				@my_sequel.add(@my_sequel_anchor, dst_anchor)
Index: plugin/my-ex.rb
===================================================================
--- plugin/my-ex.rb	(リビジョン 43193)
+++ plugin/my-ex.rb	(作業コピー)
@@ -13,9 +13,10 @@
 
 unless @conf.mobile_agent?
 
+alias :my_overwritten_by_my_ex :my
 def my( a, str, title = nil )
 	m = /^(\d{8})\D*(?:([pct])(\d+))?$/.match( a )
-	return '' unless m
+	return my_overwritten_by_my_ex( a, str, title ) unless m
 	_, date, place, frag = *m
 
 	if title.nil? and date and frag and @diaries[date] then

 @2010-03-08: 「ツッコミを入れる」リンクが無効になってしまっていた。

misc/plugin/html_anchor.rbと plugin/00default.rbプラグインを再び修正。

元々if /^([\-\d]+)#?([pct]\d*)?$/ =~ s then
修正ミスif /^([\-\d]+)#?([pct]\d+(?:\.\d+)*)?$/ =~ s then
OKif /^([\-\d]+)#?([pct][\d\.]*)?$/ =~ s then

「ツッコミを入れる」リンクは YYYYMMDD#c へのリンクなので、cの後ろに数字が続いていない。これをうっかり除外してしまっていた。


2009年11月05日 (木) [Vista] まただ。カーソル移動がもっさりすると思ったら。

最終更新: 2014-12-05T17:24+0900

[tDiary] フィードが空になっている。

理由は makerss.cacheが hiddenな spamコメントで埋め尽くされたから。問題です。

本日のツッコミ(全25件) ツッコミを入れる

Before...

ななしデバッグモードでコンパイルすると、4883行で、 error C2440: 'int' から 'CLayoutInt..

ななしrev8です。 あいうえお 1かきくけこ 0123456789 「い」の右側から、3行全部を0幅選択してスペース..

ななしrev8.1でOKな気がします。 いい感じ (^^)

anonymouse「1文字のTAB/SPACE入力が必ずインデント扱いになってしまっていて変」 (2009年11月10日 (火) 10..

ななし念のため。 上記の、 ・タブキーとスペースキーに割り付けられた機能を削除すると、  複数行選択したときのタブやスペ..

ds14050ええと、具体的にどういう動作が不満で、どういう結果になるのがよいのでしょうか。rev.9版のバイナリは http:/..

ななし行末を超えた位置にも通常文字は入る。 SPACEキーにインデントを割り付けて*いない*ときは、 行末を超えた位置には..

ds14050この部分ですね。 ---- if( nDataLen == 1 && IsIndentChar( pData[0] ..

ななしnDataLen == 1 && IsIndentChar( pData[0] ) なんていう条件だと、 クリッ..

ななし> 選択範囲が一行だけだったとき、Command_INDENT_TAB()は Command_INDENTの代わりに..

ds14050>1字のSPACE/TABという条件は、コードをはじめて見た >瞬間におかしいと思ったし、誰でも同じことが想像できる..

ds14050どうしようもないな。「元の挙動を残し」た。<<<大嘘

ds14050なんかもうぐだぐだだけど気づいてしまってので訂正。 >知らないのは事実です。最初のコメントに書いたとおり。 最初のコ..


2009年11月04日 (水)

最終更新: 2009-11-05T05:03+0900

[tDiary] リンク>「tDiary用目次プラグイン - にっき (2009-11-04)

いやらしいプラグインだなあ(笑)

しかしこの変哲のないただリンクに見えるもののどこに着目して、googleは小見出しを作成してるんだ。


2009年08月28日 (金) はてなの引用は元ページのタイトルを自動取得できるのねん。「404 Not Found」からの引用になってる日記(マジックリン!)を見て気付いた。日記ブログの書かれた当時は当然 404ではなかったはずで、どういうタイミングでページ取得を行ってるんだろう。■■■@2013-09-14「Hatena Pagetitle Agent/1.0」っていうのがそうかな?

最終更新: 2013-09-14T00:17+0900

[tDiary] 一日のタイトルを HikiDocフォーマットで HTML化。

使えないのを知っていて以前から HikiDocフォーマットでタイトルを書いていた。>>20090823 >>20090403

これを HTML化するのは意外と簡単。プラグインでできる。

add_title_proc {|date, title|
  if title.index('<')
    title.sub(/<span class="title">([^<>]+)<\/span>/){
      %/<div class="title">#{WikiSection.new(CGI.unescapeHTML $1).body_to_html}<\/div>/
    }
  else
    WikiSection.new(CGI.unescapeHTML title).body_to_html
  end
}

今日のタイトルに含まれる「==日記==ブログ」という部分があまりにわかりにくかったので、HTML化してみた次第。URL自動リンクも有効になって、うまうま。


勘違い発覚。Headingがブロック要素を包含できる気がしていたが、Heading自身がブロック要素だということの記憶違い。<div>を含めちゃだめだ。

修正。

add_title_proc {|date, title|
  inline_or_nil = lambda{|src|
    lines = src.split(/\r?\n/)
    return nil if 1 < lines.length
    html = WikiSection.new(lines.first).body_to_html
    return nil if html[0,3] != '<p>' or html[-4,4] != '</p>'
    return html
  }
  if title.index('<')
    title.sub(/<span class="title">([^<>]+)<\/span>/){|_0|
      html = inline_or_nil.call(CGI.unescapeHTML $1)
      html ? %/<span class="title">#{html}<\/span>/ : _0
    }
  else
    inline_or_nil.call(CGI.unescapeHTML title) or title
  end rescue title
}

それなりにチェックはしてるけど、ブロック要素を返すプラグインを呼んだりしたら(HTMLの文法的に)即アウト。


2009年08月16日 (日) ネトゲ界隈で蔓延しているようだが一抹の抵抗を試みる。課金をするのはサービスの提供者。~を課す、っていうじゃない、あなたは支払い義務を課されてるんだよ、と書こうとして、あれ、課する? 課(おお)す? なにそれ。

最終更新: 2009-08-28T23:41+0900

[tDiary] セクションごとに最終更新日時を。(セクション単位の編集機能とからんでカオス)

表示方法はこう。

# coding: utf-8
require 'date'
add_section_leave_proc{|date, index|
  diary = @diaries[date.strftime('%Y%m%d')]
  next unless diary # in case @mode == 'preview'
  section, sidx = nil, 0
  diary.each_section{|sec| sidx+=1
    if sidx == index
      section = sec
      break
    end
  }
  lm = section.last_modified rescue next
  next unless lm
  lm = DateTime.new(*(lm.utc.to_a.values_at(5,4,3,2,1,0))).new_offset(Rational(135,360)) # 日本時間
  lm.strftime %<<p class="lastmodified">最終更新: %Y-%m-%dT%H:%M%Z</p>> # 色分けテストとして、あえてタグと同じアングルブラケットで囲ってみた。
}

DateTimeのオフセットの単位がわからんかった。ブラウザのブックマークが「Rubyリファレンスマニュアル - 20051129」だからなあ。るりまには載ってるかも。とりあえず「fraction of a day」(date.rb documented by William Webber)とのこと。慣れない Rationalを使ったもんだから Rational(135,360)と書くべき所に Rational(135/360)と書いてしまい、オフセット 0の結果にしばし首をひねった。Rational()の呼び出しより引数の評価の方が先だからやむをえないことだけど、分数を表現するのにはやっぱり / を使いたい(使ってしまった)。Fixnumと Bignumのシームレスな移行のように、 Rationalへも融通無碍に切り替わって欲しい。利用者には Numericだけを利用しているように思わせる、ということ。必要なときに整数化(小数化)メソッドを呼ぶし、変数に整数(や分母を 1に約分できる分数)が入ってることを利用者が知っていれば、そのまま整数を前提としたメソッドを呼んだりできるといい。変わるのは、整数型の演算結果が整数型であることを前提にした(旧来の言語の呪縛に過ぎない)切り捨て除算がなくなる以外にあるだろうか。それも Pythonみたいに // を割り当てれば、無駄な有理数化、再整数化を避けられる。実感に基づいて、既に通った(んじゃなかったっけな?)議論を蒸し返してみました。俺は整数と小数の垣根を取っ払った JavaScriptを、最初は驚いたけど、評価している。JavaScriptのシンプルさが好きだ。セミコロンインサーションも、Cより怠けることを許していながら、Rubyの改行をターミネータにするやり方よりフォーマットが自由で、最高にバランスがいい(はまるのは returnだけだ)。require 'rational'; 10.to_r/2 とか不格好すぎるでしょ。ハードウェアと型から離れて本質に戻りましょう。算数で割り算と分数は同じものだったはずだ。<追記@2009-08-22>require 'mathn' がつまり Fixnumと Bignumと Rational(と Complex)をより親密にするおまじないでした(いまさら何を)。mathnって、読めない(マスエヌ?)のと名前から中身が想像できないのとで意識せず無視してたけど、Rationalとセットで見かけることが多かった気がする。それはそうと、Rationalや Complexの細々した議論に埋もれて全体の方向性が見えない。Ruby 2.0あたりでは require 'mathn' が不要になっているんだろうか……。</追記>

るりまにはスタンドアロンサーバー版と chm版より、スタティック HTML版を用意して欲しいなあ。chmだと閲覧が IEベースになってしまって、文字の大きさやスクロール量、進む戻るが自由にならなくて使いにくい。マニュアルサーバーを起動しておくのは嫌ですよ。view.cgiで CGIしようとしたらリンクがルートからの絶対アドレスなせいで Not Found。Apacheは既に動いてるから、名前ベースのバーチャルホストや待ち受けポートの追加で、るりまに一つのホスト(or ポート)を与えることはできると思うけど……大仰なのでやらない。base_urlのオプションが用意されてるから view.cgiの設定を間違えてるだけの気もするけど……わからない。

組み込みの Timeが UTCと localtimeしか扱わないのがもったいない。任意のオフセットに基づいた日時を出力したいだけだから、DateTimeは牛刀な印象がある。<追記@2009-08-21>よーくかんがえよー(命令形)、・・・・・・・・・ー。なんてことはない。オフセット分だけ未来(過去)の UTC時刻が即ちローカルタイムだよ。当たり前すぎて俺が何を言いたいのかわからないでしょう。先のスクリプト片の最後は DateTimeを使わずにこう書ける、ということです。

  lm = section.last_modified rescue next
  next unless lm
  offset = 9 * 60 * 60 # 秒
  lm_local = (lm + offset).utc # UTCと見せかけて lmの地方時。
  %<<p class="lastmodified">最終更新: %d-%02d-%02dT%02d:%02d%s%+03d%02d</p>> %
    [lm_local.year, lm_local.month, lm_local.day, lm_local.hour, lm_local.min, offset/60/60, offset/60%60]
}

……てなことを、makerss.rbの中の TDiary::RDFSection#time_stringが

  g = @time.dup.gmtime
  l = Time::local( g.year, g.month, g.day, g.hour, g.min, g.sec )

gmtimeに基づく年月日時分秒からローカルタイムを作っている部分を見ていて(遅まきながら)気付いた。gmtimeも localtimeも皮をむけば UNIX epochからの経過秒に過ぎないんだから、どういう意味を持たせるかはこちらの自由だった。まあ、比較はできなくなりますが……(lm.to_i ≠ lm_local.to_i はその意味(同じ瞬間の別表現であること)を考えると望ましくない結果)。</追記>

脱線終了。表示するまでの仕込みがこんな感じ。

add_last_modified_to_every_section@defaultio.rb (2.4KiB)
日記の一日分のデータの一部として、その日のセクションの更新日時をまとめて記録している。WikiStyleのフォーマットの一部として更新日時や著者名を埋め込む方法があれば、そちらの方が利用者の自由度は高い。
新設したヘッダ(Sections-Last-Modified)と日記の本文とが連動しているので tDiary以外から *.td2をいじりにくくなっている。
add_last_modified_to_every_section@wiki_style.rb (1.7KiB)
都合が良いように変更。
add_last_modified_to_every_section@tdiary.rb (10.6KiB)
ぐっちゃり。もちろん*今の*自分にとってはそうではない。
tdiary_style.rbを変更してないから TDiarySection#last_modifiedは未定義。tDiaryスタイルでも日記を書いてみて見つかったエラーは rescueした。

主に plugin/makerss.rbからの要請で更新日時を記録したいので、ちょっとした修正では最終更新日時は更新されない。とかいいながら、この日記には変更のあったセクションを見つける別の方法が入っている(20090705p01)ので makerss.rbが最終更新日時を利用するようにはしていない。


最初にポストされた時刻も有用だろうか? 日記だから最初にポストされた日はほとんど確定してるし、時刻まで知りたいとも思わないけど。


WikiSectionに last_modifiedプロパティをくっつけたけど、WikiSection自体はこれを管理してなくて、外部から操作されるだけだってのがいけてない。やっぱり to_src()と initialize()が DUMP & LOADを担う(last_modifiedや authorその他の情報をフォーマットに含める)か、メソッド群を整備してこちらの望む操作を WikiDiary、WikiSection自身にやってもらう(自分でやるから付帯情報のコピー漏れも発生しない道理)のがいい。


 2009-08-22

「編集」でセクションを追加したとき、更新日時のコピー処理でのぬるぽを修正。(if old_section and...を追加した)