きりかノート 3冊め

おあそびプログラミング

RubyCocoa 今日のコミット 2013-11-04

APIドキュメント(の器)を用意した。

RubyCocoaも10年を超える時を経て、だいぶコードがカオスな感じになっている。以前からコード整理したいなあと思っていたのと、合わせてリファレンスが要るよね、ということで今日は後者の作業。ドキュメント書きながら外向けのインターフェイスを把握して、リファクタリングにも役立つんじゃないかなというもくろみ。

基本方針としては、Objective-C側とRuby側のリファレンスを別々に書くようにする。両方がでてくると邪魔だし、ふつーはRuby側しか使わない。Objective-C側はテンプレートから生成するアプリケーション初期化のあたりくらいだから、自分ではあまりコード書かない。

Objective-Cのリファレンス。こっちはたぶん完成。って言ってもヘッダファイル2個しかないしね。

Rubyのリファレンス。こっちはまだ全然。とりあえずC側のモジュール/クラスやメソッドの一覧が出るようになったレベル。

Objective-Cのドキュメントをheaderdocで書く

はじめはappledocで書くつもりだったのだけど、どうにもCの関数のドキュメントを書く方法が見つからなかった(未対応?)ので、いにしえのheaderdocを使うことに。

headerdocの使いかたは

  1. コード中にドキュメントを書く
  2. headerdoc2htmlでHTMLファイルを生成する
  3. gatherheaderdocでとりまとめたTOCファイルを作る

て感じ。framework/pre-doc.rbに次のようなコードを入れて、"ruby install.rb doc"または"rake doc"でdoc/objcにドキュメントが作成される。

   # objective-c documents by "headerdoc"
   hd2html = `xcrun -f headerdoc2html`.chomp
   gatherhd = `xcrun -f gatherheaderdoc`.chomp
   if hd2html.length > 0 && file.exist?(hd2html)
       fileutils.rm_r('../doc/objc/', :force => true)
       cmd = %w(#{hd2html} -o ../doc/objc)
       cmd += %w(src/objc/rubycocoa.h src/objc/rbruntime.h src/objc/rbobject.h)
       command(cmd.join(' '))
       cmd = %w(#{gatherhd} ../doc/objc rubycocoa.html)
       command(cmd.join(' '))
   end

リファレンス見ながらてきとーに試しつつ、書いてった感じ。あんま苦労はしなかったかな。どっちかというと、appledocをあきらめるまでの試行錯誤がしんどかった。

Rubyのドキュメントをyardで書く

クリアコードのククログや雑誌なんかでも何度か見かけてたyardを使うことに。

そのままだとyardは拡張子.mのファイルをRubyスクリプトとして処理しようとしてしまうので、次のようなファイルを用意して、yardocの--loadオプションで読み込ませるようにする。

   # register .m .mm file to use CParser
   require 'yard'
   YARD::Parser::SourceParser.register_parser_type(:objc, YARD::Parser::C::CParser, %w(m mm))
   YARD::Handlers::Processor.register_handler_namespace(:objc, YARD::Handlers::C)

これでObjective-Cのコードも処理できるようになったのだけど、ちょっとした罠が。

  • モジュール|クラスの初期化関数の関数は返り値はvoidであること。
  • rb_define_module_under()やrb_define_class_under()の名前がモジュール名になっていること。

というもの。後者はrb_define_module()でのOSXモジュールの定義は1回である必要があるため、パラメータで渡したり、externしたりするんだけど、このときのコード上の変数名を見ているようで、

   _kObjcID = rb_define_class_under(mOSX, "ObjcID", rb_cObject);     //OK
   _mBundleSupport = rb_define_module_under(_mOSX, "BundleSupport"); //OK

   _kObjcPtr = rb_define_class_under (outer, "ObjcPtr", rb_cObject); //NG

というようになる。なんかそれで合ってるか不安だったので、あきらめてrdocにしようかとも思ったけどけっきょく関数を合わせる形で対応。

ちなみにyardの生成スクリプトはこんな感じ。(未コミット)

   # TODO: Ruby documents by "yard"
   yardoc = `which yardoc`.chomp
   if yardoc.length > 0 && File.exist?(yardoc)
       FileUtils.rm_r('../doc/ruby/', :force => true)
       cmd = %W(#{yardoc} -o ../doc/ruby --markup markdown
                --load ./tool/yard_objc_register.rb
                --title "RubyCocoa\ Documentation"
       )
   #        --hide-void-return)
   #    cmd += Dir.glob("src/ruby/**/*.rb")
   #    cmd -= %w(src/ruby/osx/objc/cocoa.rb
   #         src/ruby/osx/objc/oc_all.rb
   #         src/ruby/osx/objc/foundation.rb)
       cmd += %w(src/objc/cls_objcid.m
                 src/objc/cls_objcptr.m
                 src/objc/mdl_bundle_support.m
                 src/objc/mdl_objwrapper.m
                 src/objc/mdl_osxobjc.m
                 src/objc/BridgeSupport.m)
       command(cmd.join(' '))
   end

次のリリースのときにはまだ完成してなくてもrubycocoa.sourceforge.netのほうに載せるつもり。