Wiki Graphviz macro プラグイン
例によって無保証です。
説明はその2の方に。

今回はハマったことというか知ったこととか。
以下、redmine-0.8.0RC1前提で。


redmineのwikiマクロに関して

  • ・{{macro_list}}マクロがデフォルトで入っていて、使えるマクロの一覧を表示できる。知らんかった。。
    • マクロ名と登録時に「desc」で指定した内容が表示される
  • {{include(Foo)}}マクロがデフォルトで入っていて、別ページをincludeできる。知らんかった。。
  • wikiマクロの登録は、Redmine::WikiFormatting::Macros.registerにブロックを渡す。
    • descにはwiki記法が使えるが、今にして思えばtextile以外のフォーマッタを使っている場合にマズイか・・・。
  Redmine::WikiFormatting::Macros.register do
    desc <<'EOF'
Render graph image from the wiki page which is specified by macro-args.

  !{{graphviz(Foo)}}
  !{{graphviz(option=value...,Foo)}}

* options are:
** format={png|jpg}
** layout={dot|neato|fdp|twopi|circo}
** target={_blank|any}
** with_source
EOF
    macro :graphviz do |wiki_content_obj, args|
(略)
  • マクロ本体はmacroにシンボルとブロックを指定する
    • ブロックの引数はWikiContentモデルとマクロの引数に指定した文字列を「,」で分割したArray
      • {{マクロ(引数)}}
  • wiki中の{{マクロ}}の都度ブロックが呼び出される
    • ブロックが返した文字列がマクロ展開後のHTMLとして出力される。
      nilを返すと元のマクロ記述がそのまま表示される
    • プレビュー時、ブロックが呼び出されたときに「プレビュー中のテキスト」を得られない
      • graphviz_meマクロで編集/プレビューしながら図を確認できたら便利だよなぁと思ったら渡ってくるのは保存済みのWikiContentモデルだった。
      • 新しいwikiページをプレビューするとobjにはnilが渡ってくる
    • ブロックはActionView::Baseのインスタンスから呼び出される
      • ブロック内でself.to_sしたら"#<ActionView::Base:0xxxxxxxxx>"だった
      • なのでself.controllerからキャッシュメソッドを呼び出したり出来る
      • graphvizマクロの場合、マクロ展開時(Viewのコンテキスト)、画像表示時(コントローラのコンテキスト)のコードをできるだけ共有したかったので都合がよかった
  • ブロックから例外を飛ばすとwikiビュー内にエラー表示できる
    Error executing the マクロ名 macro (メッセージ)

redmineプラグインに関して

  • プラグインROOTのinit.rbで登録すると利用可能になる
    Redmine::Plugin.register :wiki_graphviz_plugin do
  • プラグイン固有のコントローラのrouteはプラグインROOTのroutes.rbで追加定義できる
    connect 'wiki/:id/:page/graphviz', :controller => 'wiki_graphviz', :action => 'graphviz'

プラグイン設定関係

  • 管理→プラグイン→(プラグインごとの)設定で表示されるプラグイン設定画面
  • Redmine::Plugin.register時に、settingsで:partialに指定した設定画面を表示できる。
    settings :default => {'cache_seconds' => '0'}, :partial => 'wiki_graphviz/settings'
  • テンプレートはvendor/plugins/プラグイン/app/views/に置く
    • 簡単で便利だけど、:partialの指定を他のプラグインと同じにしてしまうと別のプラグインのテンプレートが適用されてしまうことがある。
    • extra/sample_pluginをセットアップした状態で:partialに同じ値を設定しておいたらsample_pluginの設定ビューが出てきてかなり悩んだ。 
    • わざと存在しないテンプレート名を指定しておくとテンプレートの検索パスがログに出る。これを見る限り、プラグインの検索パスが連結された状態になっているようだ。
      Missing template wiki_graphviz/_settingsi.html.erb in view path 
      ・・・・/redmine-0.8.0_RC1/vendor/plugins/wiki_graphviz_plugin/app/views:
      ・・・・/redmine-0.8.0_RC1/vendor/plugins/sample_plugin/app/views:
      ・・・・/redmine-0.8.0_RC1/app/views 
    • stylesheet_link_tagみたいに:pluginを指定してなんとかしてくれんだろうか
  • プラグインの名前が長すぎると、設定値を保存できるが再表示した際に値が正しく表示されない(デフォルト値が表示される)
mysql> desc settings;
+------------+-------------+------+-----+---------+----------------+
| Field      | Type        | Null | Key | Default | Extra          |
+------------+-------------+------+-----+---------+----------------+
| id         | int(11)     | NO   | PRI | NULL    | auto_increment |
| name       | varchar(30) | NO   |     |         |                |
| value      | text        | YES  |     | NULL    |                |
| updated_on | datetime    | YES  |     | NULL    |                |
+------------+-------------+------+-----+---------+----------------+
  • 以前は「redmine_wiki_graphviz_plugin」という名前にしていた。redmine内部で「plugin_」が前置されて35文字になり、切れているのが原因っぽいが深追いしてない。プラグイン名を短くした。
mysql> select * from settings;
(中略)
| 24 | plugin_redmine_wiki_graphviz_p | --- !map:HashWithIndifferentAccess
cache_seconds: "2300"
| 25 | plugin_wiki_graphviz_plugin    | --- !map:HashWithIndifferentAccess
cache_seconds: "3600"
(後略)

Railsに関して
※redmine-0.8.0RC1同梱のもの。2.1?

キャッシュ関係
  • config/environments/環境.rbに設定することで有効になる
    config.action_controller.perform_caching             = true
    config.action_controller.cache_store = :mem_cache_store, "localhost"
  • cache_storeの挙動が微妙に違う。
    • デフォルトのActiveSupport::Cache::MemoryStoreはrubyオブジェクトを格納/取り出しできる。当然といえば当然。
    • ActiveSupport::Cache::FileStoreは文字列前提
    • ActiveSupport::Cache::MemCacheStoreは、(内部で使っているMemCacheの挙動で)値の格納/取り出し時にマーシャリングが行われるので結果的にrubyオブジェクトを(大体)格納できる。
      • :rawオプションでマーシャリングをナシにできる。
  • Catalyst::Plugin::CacheのイメージでいたのでFileStoreからHashが復元されず「おっかしいな~?」てことになった。結局Graphvizマクロプラグインではmemcachedだけ対応とした。

  • ・viewからHEADに何か差し込みたい場合はcontent_forが使える。これは便利な仕組みだな~。
    <% content_for :header_tags do %>
       <%= stylesheet_link_tag "wiki_graphviz.css",
           :plugin => "wiki_graphviz_plugin", :media => :all %>
    <% end %>
  • 画像を出力するときは:text&:content_type指定かつlayoutナシのrender。
    render :text => img, :layout => false, :content_type => "image/png"
  • しかし、charsetをつけられてしまう。。。
    ActionController::Baseのassign_default_content_type_and_charsetを見るにContent-Transfer-Encodingがbinaryに設定されていないとデフォルトのcharsetがついてしまうようだ・・。