Over the last couple of Ruby releases I’ve made some improvements (with Eric Hodel’s help and blessing) to RDoc for C extensions that I thought I would share with you. If you write C extensions with Ruby then keep reading. If you don’t do C and/or don’t care that much about RDoc, this post may not be that interesting for you. :)
First, and most significantly, you no longer need to use the “Document-class” directive for source files that contain multiple classes and/or classes that don’t match the ‘xxx’ portion of ‘Init_xxx’. Prior to 1.8.6, for example, you might have something like this:
/*
* Document-class: Top
* This is the Top namespace.
*/
/*
* Document-class: Bar
* This is the Bar class
*/
/*
* Document-class: Baz
* This is the Baz class.
*/
void Init_foo(){
VALUE mTop, cBar, cBaz;
mTop = rb_define_module("Top");
cBar = rb_define_class_under(mTop, "Bar", rb_cObject);
cBaz = rb_define_class_under(mTop, "Baz", rb_cObject);
}
I thought that having to explicitly document classes and modules outside of the Init_xxx function using special directives like that was ugly, so I dug into the rdoc source (scary!) and figured out to get this working. The short of it is that you can document your classes and modules in a manner that is much more in line with the way rdoc works for other C elements:
void Init_foo(){
VALUE mTop, cBar, cBaz;
/* This is the top namespace */
mTop = rb_define_module("Top");
/* This is the Bar class */
cBar = rb_define_class_under(mTop, "Bar", rb_cObject);
/* This is the Baz class */
cBaz = rb_define_class_under(mTop, "Baz", rb_cObject);
}
This is a much nicer DWIM approach in my opinion. No special directives required.
The second improvement was a minor one. In pure Ruby you can have “personal comments” in methods that won’t be picked up in the rdoc by using “–” to delineate them:
# This is the foo method. There are many like it but this one is mine. #-- # This was a major pain to implement for MS Windows. def foo "hello" end
In the above example the comment “This was a major pain to implement for MS Windows” is not picked up for the final rdoc output. Prior to Ruby 1.8.5 there was no similar mechanism for C extensions. Now, however, you can use the same approach:
/*
* This is the foo method. There are many like it but this one is mine.
*--
* This was a major pain to implement for MS Windows.
*/
The last thing I’ll mention is an improvement in the way constants are documented. Prior to 1.8.6 the constant definitions were parsed literally because rdoc has no way of knowing what a constant C value is. For example, if you had something like this:
#define FOO_VERSION "1.2.0"
void Init_foo(){
VALUE cFoo = rb_define_class("Foo", rb_cObject);
/* The version of this package */
rb_define_const(cFoo, "VERSION", rb_str_new2(FOO_VERSION));
}
The end result would be “VERSION = rb_str_new2(FOO_VERSION)”. Not what we want. Now, however, you can specify the literal value yourself by using the “value: comment” syntax:
#define FOO_VERSION "1.2.0"
void Init_foo(){
VALUE cFoo = rb_define_class("Foo", rb_cObject);
/* 1.2.0: The version of this package */
rb_define_const(cFoo, "VERSION", rb_str_new2(FOO_VERSION));
}
Enjoy!

I'm curious about that last example, it seems the repetition of "1.2.0" in the source and then again in the docstring is less than ideal. My C is very rusty, but don't C #defines work through textual replacement during a preprocessing stage? Does that replacement include inside comments? Is that stage performed before the RDoc is gleaned out? If all three are true, you could do something like:
/* FOO_VERSION: The version of this package */
Alas, I fear that even if my memory on the first two is correct, the third probably is not true.
Not necessarily a change request, but something to think about...
Jacob, RDoc just reads over the source file and parses it - there's no compilation involved if that's what you mean. However, in theory I could parse the files, look for rb_define_const, find the matching #define value, and set it that way.
But, that approach has several problems. First and foremost, there's no guarantee that the the #define is in the same file as the main source file. If not, I would have to scan *all* of the included header files looking for the proper value - how would I know where those are located? Second, dealing with anything except strings and numbers would be problematic. I would have to setup rules for the conversion functions, e.g. rb_str_new2 means it's a string, INT2FIX means it's a number, etc. Thirdly, it wouldn't work for anything but simple macro definitions, e.g. #define VERSION foo(x(y)). While that may not be an issue in practice, it's something to consider. Then there's the issue of conditional constants, which in fact, isn't handled currently, and can't be really, without resorting to a compile step of some sort.
It's not that it isn't possible to do what you want, it's just that it's way, WAY more effort than it's worth in my opinion.
Daniel,
Welcome to the ORA blog, and cool post. Nice Hardcore Stuff :)