yarv-diff:298
From: ko1 atdot.net
Date: 22 Feb 2006 01:41:15 -0000
Subject: [yarv-diff:298] r463 - in trunk: . ext lib lib/rdoc lib/rdoc/dot lib/rdoc/generators lib/rdoc/generators/template lib/rdoc/generators/template/chm lib/rdoc/generators/template/html lib/rdoc/generators/template/xml lib/rdoc/markup lib/rdoc/markup/sample lib/rdoc/markup/simple_markup lib/rdoc/markup/test lib/rdoc/parsers lib/rdoc/ri rb
Author: ko1
Date: 2006-02-22 10:41:14 +0900 (Wed, 22 Feb 2006)
New Revision: 463
Added:
trunk/.document
trunk/ext/.document
trunk/lib/.document
trunk/lib/ftools.rb
trunk/lib/rdoc/
trunk/lib/rdoc/README
trunk/lib/rdoc/code_objects.rb
trunk/lib/rdoc/diagram.rb
trunk/lib/rdoc/dot/
trunk/lib/rdoc/dot/dot.rb
trunk/lib/rdoc/generators/
trunk/lib/rdoc/generators/chm_generator.rb
trunk/lib/rdoc/generators/html_generator.rb
trunk/lib/rdoc/generators/ri_generator.rb
trunk/lib/rdoc/generators/template/
trunk/lib/rdoc/generators/template/chm/
trunk/lib/rdoc/generators/template/chm/chm.rb
trunk/lib/rdoc/generators/template/html/
trunk/lib/rdoc/generators/template/html/hefss.rb
trunk/lib/rdoc/generators/template/html/html.rb
trunk/lib/rdoc/generators/template/html/kilmer.rb
trunk/lib/rdoc/generators/template/html/old_html.rb
trunk/lib/rdoc/generators/template/html/one_page_html.rb
trunk/lib/rdoc/generators/template/xml/
trunk/lib/rdoc/generators/template/xml/rdf.rb
trunk/lib/rdoc/generators/template/xml/xml.rb
trunk/lib/rdoc/generators/xml_generator.rb
trunk/lib/rdoc/markup/
trunk/lib/rdoc/markup/sample/
trunk/lib/rdoc/markup/sample/rdoc2latex.rb
trunk/lib/rdoc/markup/sample/sample.rb
trunk/lib/rdoc/markup/simple_markup.rb
trunk/lib/rdoc/markup/simple_markup/
trunk/lib/rdoc/markup/simple_markup/fragments.rb
trunk/lib/rdoc/markup/simple_markup/inline.rb
trunk/lib/rdoc/markup/simple_markup/lines.rb
trunk/lib/rdoc/markup/simple_markup/preprocess.rb
trunk/lib/rdoc/markup/simple_markup/to_flow.rb
trunk/lib/rdoc/markup/simple_markup/to_html.rb
trunk/lib/rdoc/markup/simple_markup/to_latex.rb
trunk/lib/rdoc/markup/test/
trunk/lib/rdoc/markup/test/AllTests.rb
trunk/lib/rdoc/markup/test/TestInline.rb
trunk/lib/rdoc/markup/test/TestParse.rb
trunk/lib/rdoc/options.rb
trunk/lib/rdoc/parsers/
trunk/lib/rdoc/parsers/parse_c.rb
trunk/lib/rdoc/parsers/parse_f95.rb
trunk/lib/rdoc/parsers/parse_rb.rb
trunk/lib/rdoc/parsers/parse_simple.rb
trunk/lib/rdoc/parsers/parserfactory.rb
trunk/lib/rdoc/rdoc.rb
trunk/lib/rdoc/ri/
trunk/lib/rdoc/ri/ri_cache.rb
trunk/lib/rdoc/ri/ri_descriptions.rb
trunk/lib/rdoc/ri/ri_display.rb
trunk/lib/rdoc/ri/ri_driver.rb
trunk/lib/rdoc/ri/ri_formatter.rb
trunk/lib/rdoc/ri/ri_options.rb
trunk/lib/rdoc/ri/ri_paths.rb
trunk/lib/rdoc/ri/ri_reader.rb
trunk/lib/rdoc/ri/ri_util.rb
trunk/lib/rdoc/ri/ri_writer.rb
trunk/lib/rdoc/template.rb
trunk/lib/rdoc/tokenstream.rb
trunk/lib/rdoc/usage.rb
Modified:
trunk/
trunk/ChangeLog
trunk/eval_thread.c
trunk/gc.c
trunk/process.c
trunk/rb/insns2vm.rb
trunk/ruby.h
trunk/vm.c
Log:
r712@lermite: ko1 | 2006-02-22 10:37:24 +0900
* lib/.document : imported from Ruby 1.9 HEAD
* .document : ditto
* ext/.document : ditto
* lib/ftools.rb : ditto
* lib/rdoc/ : ditto
* eval_thread.c : remove unused functions
* process.c : ditto
* rb/insns2vm.rb : compare modified date of vm_opts.h and
vm_opts.h.base
* ruby.h : rename RValue to RValues
* gc.c : ditto
* vm.c : ditto
Property changes on: trunk
___________________________________________________________________
Name: svk:merge
- 81cd9672-7512-7e48-ae48-6936450e977d:/local/yarv/trunk:710
+ 81cd9672-7512-7e48-ae48-6936450e977d:/local/yarv/trunk:712
Added: trunk/.document
===================================================================
--- trunk/.document 2006-02-21 22:21:14 UTC (rev 462)
+++ trunk/.document 2006-02-22 01:41:14 UTC (rev 463)
@@ -0,0 +1,16 @@
+# This file determines which files in the
+# Ruby hierarchy will be processed by the RDoc
+# tool when it is given the top-level directory
+# as an argument
+
+# Process all the C source files
+*.c
+
+# the lib/ directory (which has its own .document file)
+
+lib
+
+
+# and some of the ext/ directory (which has its own .document file)
+
+ext
Modified: trunk/ChangeLog
===================================================================
--- trunk/ChangeLog 2006-02-21 22:21:14 UTC (rev 462)
+++ trunk/ChangeLog 2006-02-22 01:41:14 UTC (rev 463)
@@ -4,6 +4,32 @@
# from Mon, 03 May 2004 01:24:19 +0900
#
+2006-02-22(Wed) 10:33:04 +0900 Koichi Sasada <ko1 atdot.net>
+
+ * lib/.document : imported from Ruby 1.9 HEAD
+
+ * .document : ditto
+
+ * ext/.document : ditto
+
+ * lib/ftools.rb : ditto
+
+ * lib/rdoc/ : ditto
+
+ * eval_thread.c : remove unused functions
+
+ * process.c : ditto
+
+ * rb/insns2vm.rb : compare modified date of vm_opts.h and
+ vm_opts.h.base
+
+ * ruby.h : rename RValue to RValues
+
+ * gc.c : ditto
+
+ * vm.c : ditto
+
+
2006-02-22(Wed) 06:32:10 +0900 Koichi Sasada <ko1 atdot.net>
* configure.in : remove last commit
Modified: trunk/eval_thread.c
===================================================================
--- trunk/eval_thread.c 2006-02-21 22:21:14 UTC (rev 462)
+++ trunk/eval_thread.c 2006-02-22 01:41:14 UTC (rev 463)
@@ -442,60 +442,6 @@
return 0;
}
-#if defined(_THREAD_SAFE)
-
-void
-rb_thread_start_timer()
-{
-}
-
-void
-rb_thread_stop_timer()
-{
-}
-#elif defined(HAVE_SETITIMER)
-static void
-catch_timer(sig)
- int sig;
-{
-#if !defined(POSIX_SIGNAL) && !defined(BSD_SIGNAL)
- signal(sig, catch_timer);
-#endif
- if (!rb_thread_critical) {
- rb_thread_pending = 1;
- }
- /* cause EINTR */
-}
-
-void
-rb_thread_start_timer()
-{
- struct itimerval tval;
-
- if (!thread_init)
- return;
- tval.it_interval.tv_sec = 0;
- tval.it_interval.tv_usec = 10000;
- tval.it_value = tval.it_interval;
- setitimer(ITIMER_VIRTUAL, &tval, NULL);
-}
-
-void
-rb_thread_stop_timer()
-{
- struct itimerval tval;
-
- if (!thread_init)
- return;
- tval.it_interval.tv_sec = 0;
- tval.it_interval.tv_usec = 0;
- tval.it_value = tval.it_interval;
- setitimer(ITIMER_VIRTUAL, &tval, NULL);
-}
-#else /* !(_THREAD_SAFE || HAVE_SETITIMER) */
-int rb_thread_tick = THREAD_TICK;
-#endif
-
static VALUE
rb_thread_start_0(fn, arg, th)
VALUE (*fn) ();
Added: trunk/ext/.document
===================================================================
--- trunk/ext/.document 2006-02-21 22:21:14 UTC (rev 462)
+++ trunk/ext/.document 2006-02-22 01:41:14 UTC (rev 463)
@@ -0,0 +1,7 @@
+# Add files to this as they become documented
+
+iconv/iconv.c
+stringio/stringio.c
+strscan/strscan.c
+zlib/zlib.c
+win32ole
Modified: trunk/gc.c
===================================================================
--- trunk/gc.c 2006-02-21 22:21:14 UTC (rev 462)
+++ trunk/gc.c 2006-02-22 01:41:14 UTC (rev 463)
@@ -73,10 +73,101 @@
static unsigned long malloc_limit = GC_MALLOC_LIMIT;
static VALUE nomem_error;
+static int dont_gc;
+static int during_gc;
+static int need_call_final = 0;
+static st_table *finalizer_table = 0;
+
+#define MARK_STACK_MAX 1024
+static VALUE mark_stack[MARK_STACK_MAX];
+static VALUE *mark_stack_ptr;
+static int mark_stack_overflow;
+
+#undef GC_DEBUG
+
+typedef struct RVALUE {
+ union {
+ struct {
+ unsigned long flags; /* always 0 for freed obj */
+ struct RVALUE *next;
+ } free;
+ struct RBasic basic;
+ struct RObject object;
+ struct RClass klass;
+ struct RFloat flonum;
+ struct RString string;
+ struct RArray array;
+ struct RRegexp regexp;
+ struct RHash hash;
+ struct RData data;
+ struct RStruct rstruct;
+ struct RBignum bignum;
+ struct RFile file;
+ struct RNode node;
+ struct RMatch match;
+ } as;
+#ifdef GC_DEBUG
+ char *file;
+ int line;
+#endif
+} RVALUE;
+
+static RVALUE *freelist = 0;
+static RVALUE *deferred_final_list = 0;
+
+#define HEAPS_INCREMENT 10
+static struct heaps_slot {
+ RVALUE *slot;
+ int limit;
+} *heaps;
+static int heaps_length = 0;
+static int heaps_used = 0;
+
+#define HEAP_MIN_SLOTS 10000
+static int heap_slots = HEAP_MIN_SLOTS;
+
+#define FREE_MIN 4096
+
+static RVALUE *himem, *lomem;
+
+extern st_table *rb_class_tbl;
+VALUE *rb_gc_stack_start = 0;
+#ifdef __ia64
+VALUE *rb_gc_register_stack_start = 0;
+#endif
+
+int gc_stress = 0;
+
+
+#ifdef DJGPP
+/* set stack size (http://www.delorie.com/djgpp/v2faq/faq15_9.html) */
+unsigned int _stklen = 0x180000; /* 1.5 kB */
+#endif
+
+#if defined(DJGPP) || defined(_WIN32_WCE)
+static unsigned int STACK_LEVEL_MAX = 65535;
+#elif defined(__human68k__)
+unsigned int _stacksize = 262144;
+# define STACK_LEVEL_MAX (_stacksize - 4096)
+# undef HAVE_GETRLIMIT
+#elif defined(HAVE_GETRLIMIT)
+static unsigned int STACK_LEVEL_MAX = 655300;
+#else
+# define STACK_LEVEL_MAX 655300
+#endif
+
+
+
static void run_final(VALUE obj);
static int garbage_collect(void);
void
+rb_global_variable(VALUE *var)
+{
+ rb_gc_register_address(var);
+}
+
+void
rb_memerror(void)
{
static int recurse = 0;
@@ -88,8 +179,6 @@
rb_exc_raise(nomem_error);
}
-int gc_stress = 0;
-
/*
* call-seq:
* GC.stress => true or false
@@ -213,12 +302,7 @@
RUBY_CRITICAL(free(x));
}
-static int dont_gc;
-static int during_gc;
-static int need_call_final = 0;
-static st_table *finalizer_table = 0;
-
/*
* call-seq:
* GC.enable => true or false
@@ -302,59 +386,6 @@
}
}
-#undef GC_DEBUG
-
-void
-rb_global_variable(VALUE *var)
-{
- rb_gc_register_address(var);
-}
-
-typedef struct RVALUE {
- union {
- struct {
- unsigned long flags; /* always 0 for freed obj */
- struct RVALUE *next;
- } free;
- struct RBasic basic;
- struct RObject object;
- struct RClass klass;
- struct RFloat flonum;
- struct RString string;
- struct RArray array;
- struct RRegexp regexp;
- struct RHash hash;
- struct RData data;
- struct RStruct rstruct;
- struct RBignum bignum;
- struct RFile file;
- struct RNode node;
- struct RMatch match;
- } as;
-#ifdef GC_DEBUG
- char *file;
- int line;
-#endif
-} RVALUE;
-
-static RVALUE *freelist = 0;
-static RVALUE *deferred_final_list = 0;
-
-#define HEAPS_INCREMENT 10
-static struct heaps_slot {
- RVALUE *slot;
- int limit;
-} *heaps;
-static int heaps_length = 0;
-static int heaps_used = 0;
-
-#define HEAP_MIN_SLOTS 10000
-static int heap_slots = HEAP_MIN_SLOTS;
-
-#define FREE_MIN 4096
-
-static RVALUE *himem, *lomem;
-
static void
add_heap(void)
{
@@ -436,30 +467,6 @@
return (VALUE)data;
}
-extern st_table *rb_class_tbl;
-VALUE *rb_gc_stack_start = 0;
-#ifdef __ia64
-VALUE *rb_gc_register_stack_start = 0;
-#endif
-
-
-#ifdef DJGPP
-/* set stack size (http://www.delorie.com/djgpp/v2faq/faq15_9.html) */
-unsigned int _stklen = 0x180000; /* 1.5 kB */
-#endif
-
-#if defined(DJGPP) || defined(_WIN32_WCE)
-static unsigned int STACK_LEVEL_MAX = 65535;
-#elif defined(__human68k__)
-unsigned int _stacksize = 262144;
-# define STACK_LEVEL_MAX (_stacksize - 4096)
-# undef HAVE_GETRLIMIT
-#elif defined(HAVE_GETRLIMIT)
-static unsigned int STACK_LEVEL_MAX = 655300;
-#else
-# define STACK_LEVEL_MAX 655300
-#endif
-
NOINLINE(void yarv_set_stack_end(VALUE **stack_end_p));
#define YARV_SET_STACK_END yarv_set_stack_end(&th->machine_stack_end)
@@ -520,11 +527,6 @@
return ret;
}
-#define MARK_STACK_MAX 1024
-static VALUE mark_stack[MARK_STACK_MAX];
-static VALUE *mark_stack_ptr;
-static int mark_stack_overflow;
-
static void
init_mark_stack(void)
{
@@ -969,9 +971,9 @@
case T_VALUE:
{
- rb_gc_mark(RVALUE(obj)->v1);
- rb_gc_mark(RVALUE(obj)->v2);
- ptr = RVALUE(obj)->v3;
+ rb_gc_mark(RVALUES(obj)->v1);
+ rb_gc_mark(RVALUES(obj)->v2);
+ ptr = RVALUES(obj)->v3;
goto again;
}
break;
@@ -1367,11 +1369,16 @@
yarv_machine_stack_mark(yarv_thread_t *th)
{
#if STACK_GROW_DIRECTION < 0
- rb_gc_mark_locations(th->machine_stack_end, th->machine_stack_start);
+ rb_gc_mark_locations(th->machine_stack_end, th->machine_stack_start);
#elif STACK_GROW_DIRECTION > 0
- rb_gc_mark_locations(th->machin_stack_start, th->machine_stack_end);
+ rb_gc_mark_locations(th->machin_stack_start, th->machine_stack_end);
#else
-#error "TODO"
+ if (th->machin_stack_start < th->machine_stack_end) {
+ rb_gc_mark_locations(th->machin_stack_start, th->machine_stack_end);
+ }
+ else {
+ rb_gc_mark_locations(th->machine_stack_end, th->machine_stack_start);
+ }
#endif
}
Added: trunk/lib/.document
===================================================================
--- trunk/lib/.document 2006-02-21 22:21:14 UTC (rev 462)
+++ trunk/lib/.document 2006-02-22 01:41:14 UTC (rev 463)
@@ -0,0 +1,35 @@
+# We only run RDoc on the top-level files in here: we skip
+# all the helper stuff in sub-directories
+
+# Eventually, we hope to see...
+# *.rb
+
+# But for now
+
+abbrev.rb
+base64.rb
+benchmark.rb
+cgi.rb
+cgi
+complex.rb
+date.rb
+English.rb
+fileutils.rb
+find.rb
+generator.rb
+logger.rb
+matrix.rb
+net
+observer.rb
+optionparser.rb
+pathname.rb
+resolv.rb
+set.rb
+shellwords.rb
+# singleton.rb
+tempfile.rb
+test/unit.rb
+thread.rb
+thwait.rb
+time.rb
+yaml.rb
Added: trunk/lib/ftools.rb
===================================================================
--- trunk/lib/ftools.rb 2006-02-21 22:21:14 UTC (rev 462)
+++ trunk/lib/ftools.rb 2006-02-22 01:41:14 UTC (rev 463)
@@ -0,0 +1,264 @@
+#
+# = ftools.rb: Extra tools for the File class
+#
+# Author:: WATANABE, Hirofumi
+# Documentation:: Zachary Landau
+#
+# This library can be distributed under the terms of the Ruby license.
+# You can freely distribute/modify this library.
+#
+# It is included in the Ruby standard library.
+#
+# == Description
+#
+# +ftools+ adds several (class, not instance) methods to the File class, for copying, moving,
+# deleting, installing, and comparing files, as well as creating a directory path. See the
+# File class for details.
+#
+# +fileutils+ contains all or nearly all the same functionality and more, and is a recommended
+# option over +ftools+.
+#
+
+
+#
+# When you
+#
+# require 'ftools'
+#
+# then the File class aquires some utility methods for copying, moving, and deleting files, and
+# more.
+#
+# See the method descriptions below, and consider using +fileutils+ as it is more
+# comprehensive.
+#
+class File
+end
+
+class << File
+
+ BUFSIZE = 8 * 1024
+
+ #
+ # If +to+ is a valid directory, +from+ will be appended to +to+, adding
+ # and escaping backslashes as necessary. Otherwise, +to+ will be returned.
+ # Useful for appending +from+ to +to+ only if the filename was not specified
+ # in +to+.
+ #
+ def catname(from, to)
+ if directory? to
+ join to.sub(%r([/\\]$), ''), basename(from)
+ else
+ to
+ end
+ end
+
+ #
+ # Copies a file +from+ to +to+. If +to+ is a directory, copies +from+
+ # to <tt>to/from</tt>.
+ #
+ def syscopy(from, to)
+ to = catname(from, to)
+
+ fmode = stat(from).mode
+ tpath = to
+ not_exist = !exist?(tpath)
+
+ from = open(from, "rb")
+ to = open(to, "wb")
+
+ begin
+ while true
+ to.syswrite from.sysread(BUFSIZE)
+ end
+ rescue EOFError
+ ret = true
+ rescue
+ ret = false
+ ensure
+ to.close
+ from.close
+ end
+ chmod(fmode, tpath) if not_exist
+ ret
+ end
+
+ #
+ # Copies a file +from+ to +to+ using #syscopy. If +to+ is a directory,
+ # copies +from+ to <tt>to/from</tt>. If +verbose+ is true, <tt>from -> to</tt>
+ # is printed.
+ #
+ def copy(from, to, verbose = false)
+ $stderr.print from, " -> ", catname(from, to), "\n" if verbose
+ syscopy from, to
+ end
+
+ alias cp copy
+
+ #
+ # Moves a file +from+ to +to+ using #syscopy. If +to+ is a directory,
+ # copies from +from+ to <tt>to/from</tt>. If +verbose+ is true, <tt>from -> to</tt>
+ # is printed.
+ #
+ def move(from, to, verbose = false)
+ to = catname(from, to)
+ $stderr.print from, " -> ", to, "\n" if verbose
+
+ if RUBY_PLATFORM =~ /djgpp|(cyg|ms|bcc)win|mingw/ and file? to
+ unlink to
+ end
+ fstat = stat(from)
+ begin
+ rename from, to
+ rescue
+ begin
+ symlink readlink(from), to and unlink from
+ rescue
+ from_stat = stat(from)
+ syscopy from, to and unlink from
+ utime(from_stat.atime, from_stat.mtime, to)
+ begin
+ chown(fstat.uid, fstat.gid, to)
+ rescue
+ end
+ end
+ end
+ end
+
+ alias mv move
+
+ #
+ # Returns +true+ iff the contents of files +from+ and +to+ are
+ # identical. If +verbose+ is +true+, <tt>from <=> to</tt> is printed.
+ #
+ def compare(from, to, verbose = false)
+ $stderr.print from, " <=> ", to, "\n" if verbose
+
+ return false if stat(from).size != stat(to).size
+
+ from = open(from, "rb")
+ to = open(to, "rb")
+
+ ret = false
+ fr = tr = ''
+
+ begin
+ while fr == tr
+ fr = from.read(BUFSIZE)
+ if fr
+ tr = to.read(fr.size)
+ else
+ ret = to.read(BUFSIZE)
+ ret = !ret || ret.length == 0
+ break
+ end
+ end
+ rescue
+ ret = false
+ ensure
+ to.close
+ from.close
+ end
+ ret
+ end
+
+ alias cmp compare
+
+ #
+ # Removes a list of files. Each parameter should be the name of the file to
+ # delete. If the last parameter isn't a String, verbose mode will be enabled.
+ # Returns the number of files deleted.
+ #
+ def safe_unlink(*files)
+ verbose = if files[-1].is_a? String then false else files.pop end
+ files.each do |file|
+ begin
+ unlink file
+ $stderr.print "removing ", file, "\n" if verbose
+ rescue Errno::EACCES # for Windows
+ continue if symlink? file
+ begin
+ mode = stat(file).mode
+ o_chmod mode | 0200, file
+ unlink file
+ $stderr.print "removing ", file, "\n" if verbose
+ rescue
+ o_chmod mode, file rescue nil
+ end
+ rescue
+ end
+ end
+ end
+
+ alias rm_f safe_unlink
+
+ #
+ # Creates a directory and all its parent directories.
+ # For example,
+ #
+ # File.makedirs '/usr/lib/ruby'
+ #
+ # causes the following directories to be made, if they do not exist.
+ # * /usr
+ # * /usr/lib
+ # * /usr/lib/ruby
+ #
+ # You can pass several directories, each as a parameter. If the last
+ # parameter isn't a String, verbose mode will be enabled.
+ #
+ def makedirs(*dirs)
+ verbose = if dirs[-1].is_a? String then false else dirs.pop end
+ mode = 0755
+ for dir in dirs
+ parent = dirname(dir)
+ next if parent == dir or directory? dir
+ makedirs parent unless directory? parent
+ $stderr.print "mkdir ", dir, "\n" if verbose
+ if basename(dir) != ""
+ begin
+ Dir.mkdir dir, mode
+ rescue SystemCallError
+ raise unless directory? dir
+ end
+ end
+ end
+ end
+
+ alias mkpath makedirs
+
+ alias o_chmod chmod
+
+ vsave, $VERBOSE = $VERBOSE, false
+
+ #
+ # Changes permission bits on +files+ to the bit pattern represented
+ # by +mode+. If the last parameter isn't a String, verbose mode will
+ # be enabled.
+ #
+ # File.chmod 0755, 'somecommand'
+ # File.chmod 0644, 'my.rb', 'your.rb', true
+ #
+ def chmod(mode, *files)
+ verbose = if files[-1].is_a? String then false else files.pop end
+ $stderr.printf "chmod %04o %s\n", mode, files.join(" ") if verbose
+ o_chmod mode, *files
+ end
+ $VERBOSE = vsave
+
+ #
+ # If +src+ is not the same as +dest+, copies it and changes the permission
+ # mode to +mode+. If +dest+ is a directory, destination is <tt>dest/src</tt>.
+ # If +mode+ is not set, default is used. If +verbose+ is set to true, the
+ # name of each file copied will be printed.
+ #
+ def install(from, to, mode = nil, verbose = false)
+ to = catname(from, to)
+ unless exist? to and cmp from, to
+ safe_unlink to if exist? to
+ cp from, to, verbose
+ chmod mode, to, verbose if mode
+ end
+ end
+
+end
+
+# vi:set sw=2:
Added: trunk/lib/rdoc/README
===================================================================
--- trunk/lib/rdoc/README 2006-02-21 22:21:14 UTC (rev 462)
+++ trunk/lib/rdoc/README 2006-02-22 01:41:14 UTC (rev 463)
@@ -0,0 +1,492 @@
+= RDOC - Ruby Documentation System
+
+This package contains Rdoc and SimpleMarkup. Rdoc is an application
+that produces documentation for one or more Ruby source files. We work
+similarly to JavaDoc, parsing the source, and extracting the
+definition for classes, modules, and methods (along with includes and
+requires). We associate with these optional documentation contained
+in the immediately preceding comment block, and then render the result
+using a pluggable output formatter. (Currently, HTML is the only
+supported format. Markup is a library that converts plain text into
+various output formats. The Markup library is used to interpret the
+comment blocks that Rdoc uses to document methods, classes, and so on.
+
+This library contains two packages, rdoc itself and a text markup
+library, 'markup'.
+
+== Roadmap
+
+* If you want to use Rdoc to create documentation for your Ruby source
+ files, read on.
+* If you want to include extensions written in C, see rdoc/parsers/parse_c.rb.
+* For information on the various markups available in comment
+ blocks, see markup/simple_markup.rb.
+* If you want to drive Rdoc programatically, see RDoc::RDoc.
+* If you want to use the library to format text blocks into HTML,
+ have a look at SM::SimpleMarkup.
+* If you want to try writing your own HTML output template, see
+ RDoc::Page.
+
+== Summary
+
+Once installed, you can create documentation using the 'rdoc' command
+(the command is 'rdoc.bat' under Windows)
+
+ % rdoc [options] [names...]
+
+Type "rdoc --help" for an up-to-date option summary.
+
+A typical use might be to generate documentation for a package of Ruby
+source (such as rdoc itself).
+
+ % rdoc
+
+This command generates documentation for all the Ruby and C source
+files in and below the current directory. These will be stored in a
+documentation tree starting in the subdirectory 'doc'.
+
+You can make this slightly more useful for your readers by having the
+index page contain the documentation for the primary file. In our
+case, we could type
+
+ % rdoc --main rdoc/rdoc.rb
+
+You'll find information on the various formatting tricks you can use
+in comment blocks in the documentation this generates.
+
+RDoc uses file extensions to determine how to process each file. File
+names ending <tt>.rb</tt> and <tt>.rbw</tt> are assumed to be Ruby
+source. Files ending <tt>.c</tt> are parsed as C files. All other
+files are assumed to contain just SimpleMarkup-style markup (with or
+without leading '#' comment markers). If directory names are passed to
+RDoc, they are scanned recursively for C and Ruby source files only.
+
+== Credits
+
+* The Ruby parser in rdoc/parse.rb is based heavily on the outstanding
+ work of Keiju ISHITSUKA of Nippon Rational Inc, who produced the Ruby
+ parser for irb and the rtags package.
+
+* Code to diagram classes and modules was written by Sergey A Yanovitsky
+ (Jah) of Enticla.
+
+* Charset patch from MoonWolf.
+
+* Rich Kilmer wrote the kilmer.rb output template.
+
+* Dan Brickley led the design of the RDF format.
+
+== License
+
+RDoc is Copyright (c) 2001-2003 Dave Thomas, The Pragmatic Programmers. It
+is free software, and may be redistributed under the terms specified
+in the README file of the Ruby distribution.
+
+
+----
+
+= Usage
+
+RDoc is invoked from the command line using:
+
+ % rdoc <options> [name...]
+
+Files are parsed, and the information they contain collected, before
+any output is produced. This allows cross references between all files
+to be resolved. If a name is a directory, it is traversed. If no
+names are specified, all Ruby files in the current directory (and
+subdirectories) are processed.
+
+Options are:
+
+[<tt>--accessor</tt> <i>name[,name...]</i>]
+ specifies the name(s) of additional methods that should be treated
+ as if they were <tt>attr_</tt><i>xxx</i> methods. Specifying
+ "--accessor db_opt" means lines such as
+
+ db_opt :name, :age
+
+ will get parsed and displayed in the documentation. Each name may have an
+ optional "=flagtext" appended, in which case the given flagtext will appear
+ where (for example) the 'rw' appears for attr_accessor.
+
+[<tt>--all</tt>]
+ include protected and private methods in the output (by default
+ only public methods are included)
+
+[<tt>--charset</tt> _charset_]
+ Set the character set for the generated HTML.
+
+[<tt>--diagram</tt>]
+ include diagrams showing modules and classes. This is currently
+ an experimental feature, and may not be supported by all output
+ templates. You need dot V1.8.6 or later to use the --diagram
+ option correctly (http://www.research.att.com/sw/tools/graphviz/).
+
+[<tt>--exclude</tt> <i>pattern</i>]
+ exclude files and directories matching this pattern from processing
+
+[<tt>--extension</tt> <i>new=old</i>]
+ treat files ending <i>.new</i> as if they ended
+ <i>.old</i>. Saying '--extension cgi=rb' causes RDoc to treat .cgi
+ files as Ruby source.
+
+[<tt>fileboxes</tt>]
+ Classes are put in boxes which represents files, where these
+ classes reside. Classes shared between more than one file are
+ shown with list of files that sharing them. Silently discarded if
+ --diagram is not given Experimental.
+
+[<tt>--fmt</tt> _fmt_]
+ generate output in a particular format.
+
+[<tt>--help</tt>]
+ generate a usage summary.
+
+[<tt>--help-output</tt>]
+ explain the various output options.
+
+[<tt>--image-format</tt> <i>gif/png/jpg/jpeg</i>]
+ sets output image format for diagrams. Can be png, gif, jpeg,
+ jpg. If this option is omitted, png is used. Requires --diagram.
+
+[<tt>--include</tt> <i>dir,...</i>]
+ specify one or more directories to be searched when satisfying
+ :+include+: directives. Multiple <tt>--include</tt> options may be
+ given. The directory containing the file currently being processed
+ is always searched.
+
+[<tt>--inline-source</tt>]
+ By default, the source code of methods is shown in a popup. With
+ this option, it's displayed inline.
+
+[<tt>line-numbers</tt>]
+ include line numbers in the source code
+
+[<tt>--main</tt> _name_]
+ the class of module _name_ will appear on the index page. If you
+ want to set a particular file as a main page (a README, for
+ example) simply specifiy its name as the first on the command
+ line.
+
+[<tt>--merge</tt>]
+ when generating _ri_ output, if classes being processed already
+ exist in the destination directory, merge in the current details
+ rather than overwrite them.
+
+[<tt>--one-file</tt>]
+ place all the output into a single file
+
+[<tt>--op</tt> _dir_]
+ set the output directory to _dir_ (the default is the directory
+ "doc")
+
+[<tt>--op-name</tt> _name_]
+ set the name of the output. Has no effect for HTML.
+ "doc")
+
+[<tt>--opname</tt> _name_]
+ set the output name (has no effect for HTML).
+
+[<tt>--promiscuous</tt>]
+ If a module or class is defined in more than one source file, and
+ you click on a particular file's name in the top navigation pane,
+ RDoc will normally only show you the inner classes and modules of
+ that class that are defined in the particular file. Using this
+ option makes it show all classes and modules defined in the class,
+ regardless of the file they were defined in.
+
+[<tt>--quiet</tt>]
+ do not display progress messages
+
+[<tt>--ri</tt>, <tt>--ri-site</tt>, _and_ <tt>--ri-system</tt>]
+ generate output than can be read by the _ri_ command-line tool.
+ By default --ri places its output in ~/.rdoc, --ri-site in
+ $datadir/ri/<ver>/site, and --ri-system in
+ $datadir/ri/<ver>/system. All can be overridden with a subsequent
+ --op option. All default directories are in ri's default search
+ path.
+
+[<tt>--show-hash</tt>]
+ A name of the form #name in a comment is a possible hyperlink to
+ an instance method name. When displayed, the '#' is removed unless
+ this option is specified
+
+[<tt>--style</tt> <i>stylesheet url</i>]
+ specifies the URL of an external stylesheet to use (rather than
+ generating one of our own)
+
+[<tt>tab-width</tt> _n_]
+ set the width of tab characters (default 8)
+
+[<tt>--template</tt> <i>name</i>]
+ specify an alternate template to use when generating output (the
+ default is 'standard'). This template should be in a directory
+ accessible via $: as rdoc/generators/xxxx_template, where 'xxxx'
+ depends on the output formatter.
+
+[<tt>--version</tt>]
+ display RDoc's version
+
+[<tt>--webcvs</tt> _url_]
+ Specify a URL for linking to a web frontend to CVS. If the URL
+ contains a '\%s', the name of the current file will be
+ substituted; if the URL doesn't contain a '\%s', the filename will
+ be appended to it.
+
+= Example
+
+A typical small Ruby program commented using RDoc might be as follows. You
+can see the formatted result in EXAMPLE.rb and Anagram.
+
+ :include: EXAMPLE.rb
+
+= Markup
+
+Comment blocks can be written fairly naturally, either using '#' on
+successive lines of the comment, or by including the comment in
+an =begin/=end block. If you use the latter form, the =begin line
+must be flagged with an RDoc tag:
+
+ =begin rdoc
+ Documentation to
+ be processed by RDoc.
+ =end
+
+Paragraphs are lines that share the left margin. Text indented past
+this margin are formatted verbatim.
+
+1. Lists are typed as indented paragraphs with:
+ * a '*' or '-' (for bullet lists)
+ * a digit followed by a period for
+ numbered lists
+ * an upper or lower case letter followed
+ by a period for alpha lists.
+
+ For example, the input that produced the above paragraph looked like
+ 1. Lists are typed as indented
+ paragraphs with:
+ * a '*' or '-' (for bullet lists)
+ * a digit followed by a period for
+ numbered lists
+ * an upper or lower case letter followed
+ by a period for alpha lists.
+
+2. Labeled lists (sometimes called description
+ lists) are typed using square brackets for the label.
+ [cat] small domestic animal
+ [+cat+] command to copy standard input
+
+3. Labeled lists may also be produced by putting a double colon
+ after the label. This sets the result in tabular form, so the
+ descriptions all line up. This was used to create the 'author'
+ block at the bottom of this description.
+ cat:: small domestic animal
+ +cat+:: command to copy standard input
+
+ For both kinds of labeled lists, if the body text starts on the same
+ line as the label, then the start of that text determines the block
+ indent for the rest of the body. The text may also start on the line
+ following the label, indented from the start of the label. This is
+ often preferable if the label is long. Both the following are
+ valid labeled list entries:
+
+ <tt>--output</tt> <i>name [, name]</i>::
+ specify the name of one or more output files. If multiple
+ files are present, the first is used as the index.
+
+ <tt>--quiet:</tt>:: do not output the names, sizes, byte counts,
+ index areas, or bit ratios of units as
+ they are processed.
+
+4. Headings are entered using equals signs
+
+ = Level One Heading
+ == Level Two Heading
+ and so on
+
+5. Rules (horizontal lines) are entered using three or
+ more hyphens.
+
+6. Non-verbatim text can be marked up:
+
+ _italic_:: \_word_ or \<em>text</em>
+ *bold*:: \*word* or \<b>text</b>
+ +typewriter+:: \+word+ or \<tt>text</tt>
+
+ The first form only works around 'words', where a word is a
+ sequence of upper and lower case letters and underscores. Putting a
+ backslash before inline markup stops it being interpreted, which is
+ how I created the table above:
+
+ _italic_:: \_word_ or \<em>text</em>
+ *bold*:: \*word* or \<b>text</b>
+ +typewriter+:: \+word+ or \<tt>text</tt>
+
+7. Names of classes, source files, and any method names
+ containing an underscore or preceded by a hash
+ character are automatically hyperlinked from
+ comment text to their description.
+
+8. Hyperlinks to the web starting http:, mailto:, ftp:, or www. are
+ recognized. An HTTP url that references an external image file is
+ converted into an inline <IMG..>. Hyperlinks starting 'link:' are
+ assumed to refer to local files whose path is relative to the --op
+ directory.
+
+ Hyperlinks can also be of the form <tt>label</tt>[url], in which
+ case the label is used in the displayed text, and <tt>url</tt> is
+ used as the target. If <tt>label</tt> contains multiple words,
+ put it in braces: <em>{multi word label}[</em>url<em>]</em>.
+
+9. Method parameter lists are extracted and displayed with
+ the method description. If a method calls +yield+, then
+ the parameters passed to yield will also be displayed:
+
+ def fred
+ ...
+ yield line, address
+
+ This will get documented as
+
+ fred() { |line, address| ... }
+
+ You can override this using a comment containing
+ ':yields: ...' immediately after the method definition
+
+ def fred # :yields: index, position
+ ...
+ yield line, address
+
+ which will get documented as
+
+ fred() { |index, position| ... }
+
+
+10. ':yields:' is an example of a documentation modifier. These appear
+ immediately after the start of the document element they are modifying.
+ Other modifiers include
+
+ [<tt>:nodoc:</tt><i>[all]</i>]
+ don't include this element in the documentation. For classes
+ and modules, the methods, aliases, constants, and attributes
+ directly within the affected class or module will also be
+ omitted. By default, though, modules and classes within that
+ class of module _will_ be documented. This is turned off by
+ adding the +all+ modifier.
+
+ module SM #:nodoc:
+ class Input
+ end
+ end
+ module Markup #:nodoc: all
+ class Output
+ end
+ end
+
+ In the above code, only class <tt>SM::Input</tt> will be
+ documented.
+
+ [<tt>:doc:</tt>]
+ force a method or attribute to be documented even if it
+ wouldn't otherwise be. Useful if, for example, you want to
+ include documentation of a particular private method.
+
+ [<tt>:notnew:</tt>]
+ only applicable to the +initialize+ instance method. Normally
+ RDoc assumes that the documentation and parameters for
+ #initialize are actually for the ::new method, and so fakes
+ out a ::new for the class. THe :notnew: modifier stops
+ this. Remember that #initialize is protected, so you won't
+ see the documentation unless you use the -a command line
+ option.
+
+
+11. RDoc stops processing comments if it finds a comment
+ line containing '<tt>#--</tt>'. This can be used to
+ separate external from internal comments, or
+ to stop a comment being associated with a method,
+ class, or module. Commenting can be turned back on with
+ a line that starts '<tt>#++</tt>'.
+
+ # Extract the age and calculate the
+ # date-of-birth.
+ #--
+ # FIXME: fails if the birthday falls on
+ # February 29th
+ #++
+ # The DOB is returned as a Time object.
+
+ def get_dob(person)
+ ...
+
+12. Comment blocks can contain other directives:
+
+ [<tt>:section: title</tt>]
+ Starts a new section in the output. The title following
+ <tt>:section:</tt> is used as the section heading, and the
+ remainder of the comment containing the section is used as
+ introductory text. Subsequent methods, aliases, attributes,
+ and classes will be documented in this section. A :section:
+ comment block may have one or more lines before the :section:
+ directive. These will be removed, and any identical lines at
+ the end of the block are also removed. This allows you to add
+ visual cues such as
+
+ # ----------------------------------------
+ # :section: My Section
+ # This is the section that I wrote.
+ # See it glisten in the noon-day sun.
+ # ----------------------------------------
+
+ [<tt>call-seq:</tt>]
+ lines up to the next blank line in the comment are treated as
+ the method's calling sequence, overriding the
+ default parsing of method parameters and yield arguments.
+
+ [<tt>:include:</tt><i>filename</i>]
+ include the contents of the named file at this point. The
+ file will be searched for in the directories listed by
+ the <tt>--include</tt> option, or in the current
+ directory by default. The contents of the file will be
+ shifted to have the same indentation as the ':' at the
+ start of the :include: directive.
+
+ [<tt>:title:</tt><i>text</i>]
+ Sets the title for the document. Equivalent to the --title command
+ line parameter. (The command line parameter overrides any :title:
+ directive in the source).
+
+ [<tt>:enddoc:</tt>]
+ Document nothing further at the current level.
+
+ [<tt>:main:</tt><i>name</i>]
+ Equivalent to the --main command line parameter.
+
+ [<tt>:stopdoc: / :startdoc:</tt>]
+ Stop and start adding new documentation elements to the
+ current container. For example, if a class has a number of
+ constants that you don't want to document, put a
+ <tt>:stopdoc:</tt> before the first, and a
+ <tt>:startdoc:</tt> after the last. If you don't specifiy a
+ <tt>:startdoc:</tt> by the end of the container, disables
+ documentation for the entire class or module.
+
+
+---
+
+See also markup/simple_markup.rb.
+
+= Other stuff
+
+Author:: Dave Thomas <dave pragmaticprogrammer.com>
+Requires:: Ruby 1.8.1 or later
+License:: Copyright (c) 2001-2003 Dave Thomas.
+ Released under the same license as Ruby.
+
+== Warranty
+
+This software is provided "as is" and without any express or
+implied warranties, including, without limitation, the implied
+warranties of merchantibility and fitness for a particular
+purpose.
Added: trunk/lib/rdoc/code_objects.rb
===================================================================
--- trunk/lib/rdoc/code_objects.rb 2006-02-21 22:21:14 UTC (rev 462)
+++ trunk/lib/rdoc/code_objects.rb 2006-02-22 01:41:14 UTC (rev 463)
@@ -0,0 +1,765 @@
+# We represent the various high-level code constructs that appear
+# in Ruby programs: classes, modules, methods, and so on.
+
+require 'rdoc/tokenstream'
+
+module RDoc
+
+
+ # We contain the common stuff for contexts (which are containers)
+ # and other elements (methods, attributes and so on)
+ #
+ class CodeObject
+
+ attr_accessor :parent
+
+ # We are the model of the code, but we know that at some point
+ # we will be worked on by viewers. By implementing the Viewable
+ # protocol, viewers can associated themselves with these objects.
+
+ attr_accessor :viewer
+
+ # are we done documenting (ie, did we come across a :enddoc:)?
+
+ attr_accessor :done_documenting
+
+ # Which section are we in
+
+ attr_accessor :section
+
+ # do we document ourselves?
+
+ attr_reader :document_self
+
+ def document_self=(val)
+ @document_self = val
+ if !val
+ remove_methods_etc
+ end
+ end
+
+ # set and cleared by :startdoc: and :enddoc:, this is used to toggle
+ # the capturing of documentation
+ def start_doc
+ @document_self = true
+ @document_children = true
+ end
+
+ def stop_doc
+ @document_self = false
+ @document_children = false
+ end
+
+ # do we document ourselves and our children
+
+ attr_reader :document_children
+
+ def document_children=(val)
+ @document_children = val
+ if !val
+ remove_classes_and_modules
+ end
+ end
+
+ # Do we _force_ documentation, even is we wouldn't normally show the entity
+ attr_accessor :force_documentation
+
+ # Default callbacks to nothing, but this is overridden for classes
+ # and modules
+ def remove_classes_and_modules
+ end
+
+ def remove_methods_etc
+ end
+
+ def initialize
+ @document_self = true
+ @document_children = true
+ @force_documentation = false
+ @done_documenting = false
+ end
+
+ # Access the code object's comment
+ attr_reader :comment
+
+ # Update the comment, but don't overwrite a real comment
+ # with an empty one
+ def comment=(comment)
+ @comment = comment unless comment.empty?
+ end
+
+ # There's a wee trick we pull. Comment blocks can have directives that
+ # override the stuff we extract during the parse. So, we have a special
+ # class method, attr_overridable, that lets code objects list
+ # those directives. Wehn a comment is assigned, we then extract
+ # out any matching directives and update our object
+
+ def CodeObject.attr_overridable(name, *aliases)
+ @overridables ||= {}
+
+ attr_accessor name
+
+ aliases.unshift name
+ aliases.each do |directive_name|
+ @overridables[directive_name.to_s] = name
+ end
+ end
+
+ end
+
+ # A Context is something that can hold modules, classes, methods,
+ # attributes, aliases, requires, and includes. Classes, modules, and
+ # files are all Contexts.
+
+ class Context < CodeObject
+ attr_reader :name, :method_list, :attributes, :aliases, :constants
+ attr_reader :requires, :includes, :in_files, :visibility
+
+ attr_reader :sections
+
+ class Section
+ attr_reader :title, :comment, :sequence
+
+ @@sequence = "SEC00000"
+
+ def initialize(title, comment)
+ @title = title
+ sequence.succ!
+ @sequence = sequence.dup
+ set_comment(comment)
+ end
+
+ private
+
+ # Set the comment for this section from the original comment block
+ # If the first line contains :section:, strip it and use the rest. Otherwise
+ # remove lines up to the line containing :section:, and look for
+ # those lines again at the end and remove them. This lets us write
+ #
+ # # ---------------------
+ # # :SECTION: The title
+ # # The body
+ # # ---------------------
+
+ def set_comment(comment)
+ return unless comment
+
+ if comment =~ /^.*?:section:.*$/
+ start = $`
+ rest = $'
+ if start.empty?
+ @comment = rest
+ else
+ @comment = rest.sub(/#{start.chomp}\Z/, '')
+ end
+ else
+ @comment = comment
+ end
+ @comment = nil if @comment.empty?
+ end
+ end
+
+
+ def initialize
+ super()
+
+ @in_files = []
+
+ @name ||= "unknown"
+ @comment ||= ""
+ @parent = nil
+ @visibility = :public
+
+ @current_section = Section.new(nil, nil)
+ @sections = [ @current_section ]
+
+ initialize_methods_etc
+ initialize_classes_and_modules
+ end
+
+ # map the class hash to an array externally
+ def classes
+ @classes.values
+ end
+
+ # map the module hash to an array externally
+ def modules
+ @modules.values
+ end
+
+ # Change the default visibility for new methods
+ def ongoing_visibility=(vis)
+ @visibility = vis
+ end
+
+ # Given an array +methods+ of method names, set the
+ # visibility of the corresponding AnyMethod object
+
+ def set_visibility_for(methods, vis, singleton=false)
+ count = 0
+ @method_list.each do |m|
+ if methods.include?(m.name) && m.singleton == singleton
+ m.visibility = vis
+ count += 1
+ end
+ end
+
+ return if count == methods.size || singleton
+
+ # perhaps we need to look at attributes
+
+ @attributes.each do |a|
+ if methods.include?(a.name)
+ a.visibility = vis
+ count += 1
+ end
+ end
+ end
+
+ # Record the file that we happen to find it in
+ def record_location(toplevel)
+ @in_files << toplevel unless @in_files.include?(toplevel)
+ end
+
+ # Return true if at least part of this thing was defined in +file+
+ def defined_in?(file)
+ @in_files.include?(file)
+ end
+
+ def add_class(class_type, name, superclass)
+ add_class_or_module(@classes, class_type, name, superclass)
+ end
+
+ def add_module(class_type, name)
+ add_class_or_module(@modules, class_type, name, nil)
+ end
+
+ def add_method(a_method)
+ puts "Adding #@visibility method #{a_method.name} to #@name" if $DEBUG
+ a_method.visibility = @visibility
+ add_to(@method_list, a_method)
+ end
+
+ def add_attribute(an_attribute)
+ add_to(@attributes, an_attribute)
+ end
+
+ def add_alias(an_alias)
+ meth = find_instance_method_named(an_alias.old_name)
+ if meth
+ new_meth = AnyMethod.new(an_alias.text, an_alias.new_name)
+ new_meth.is_alias_for = meth
+ new_meth.singleton = meth.singleton
+ new_meth.params = meth.params
+ new_meth.comment = "Alias for \##{meth.name}"
+ meth.add_alias(new_meth)
+ add_method(new_meth)
+ else
+ add_to(@aliases, an_alias)
+ end
+ end
+
+ def add_include(an_include)
+ add_to(@includes, an_include)
+ end
+
+ def add_constant(const)
+ add_to(@constants, const)
+ end
+
+ # Requires always get added to the top-level (file) context
+ def add_require(a_require)
+ if self.kind_of? TopLevel
+ add_to(@requires, a_require)
+ else
+ parent.add_require(a_require)
+ end
+ end
+
+ def add_class_or_module(collection, class_type, name, superclass=nil)
+ cls = collection[name]
+ if cls
+ puts "Reusing class/module #{name}" if $DEBUG
+ else
+ cls = class_type.new(name, superclass)
+ puts "Adding class/module #{name} to #@name" if $DEBUG
+# collection[name] = cls if @document_self && !@done_documenting
+ collection[name] = cls if !@done_documenting
+ cls.parent = self
+ cls.section = @current_section
+ end
+ cls
+ end
+
+ def add_to(array, thing)
+ array << thing if @document_self && !@done_documenting
+ thing.parent = self
+ thing.section = @current_section
+ end
+
+ # If a class's documentation is turned off after we've started
+ # collecting methods etc., we need to remove the ones
+ # we have
+
+ def remove_methods_etc
+ initialize_methods_etc
+ end
+
+ def initialize_methods_etc
+ @method_list = []
+ @attributes = []
+ @aliases = []
+ @requires = []
+ @includes = []
+ @constants = []
+ end
+
+ # and remove classes and modules when we see a :nodoc: all
+ def remove_classes_and_modules
+ initialize_classes_and_modules
+ end
+
+ def initialize_classes_and_modules
+ @classes = {}
+ @modules = {}
+ end
+
+ # Find a named module
+ def find_module_named(name)
+ return self if self.name == name
+ res = @modules[name] || @classes[name]
+ return res if res
+ find_enclosing_module_named(name)
+ end
+
+ # find a module at a higher scope
+ def find_enclosing_module_named(name)
+ parent && parent.find_module_named(name)
+ end
+
+ # Iterate over all the classes and modules in
+ # this object
+
+ def each_classmodule
+ @modules.each_value {|m| yield m}
+ @classes.each_value {|c| yield c}
+ end
+
+ def each_method
+ @method_list.each {|m| yield m}
+ end
+
+ def each_attribute
+ @attributes.each {|a| yield a}
+ end
+
+ def each_constant
+ @constants.each {|c| yield c}
+ end
+
+ # Return the toplevel that owns us
+
+ def toplevel
+ return @toplevel if defined? @toplevel
+ @toplevel = self
+ @toplevel = @toplevel.parent until TopLevel === @toplevel
+ @toplevel
+ end
+
+ # allow us to sort modules by name
+ def <=>(other)
+ name <=> other.name
+ end
+
+ # Look up the given symbol. If method is non-nil, then
+ # we assume the symbol references a module that
+ # contains that method
+ def find_symbol(symbol, method=nil)
+ result = nil
+ case symbol
+ when /^::(.*)/
+ result = toplevel.find_symbol($1)
+ when /::/
+ modules = symbol.split(/::/)
+ unless modules.empty?
+ module_name = modules.shift
+ result = find_module_named(module_name)
+ if result
+ modules.each do |module_name|
+ result = result.find_module_named(module_name)
+ break unless result
+ end
+ end
+ end
+ else
+ # if a method is specified, then we're definitely looking for
+ # a module, otherwise it could be any symbol
+ if method
+ result = find_module_named(symbol)
+ else
+ result = find_local_symbol(symbol)
+ if result.nil?
+ if symbol =~ /^[A-Z]/
+ result = parent
+ while result && result.name != symbol
+ result = result.parent
+ end
+ end
+ end
+ end
+ end
+ if result && method
+ if !result.respond_to?(:find_local_symbol)
+ p result.name
+ p method
+ fail
+ end
+ result = result.find_local_symbol(method)
+ end
+ result
+ end
+
+ def find_local_symbol(symbol)
+ res = find_method_named(symbol) ||
+ find_constant_named(symbol) ||
+ find_attribute_named(symbol) ||
+ find_module_named(symbol)
+ end
+
+ # Handle sections
+
+ def set_current_section(title, comment)
+ @current_section = Section.new(title, comment)
+ @sections << @current_section
+ end
+
+ private
+
+ # Find a named method, or return nil
+ def find_method_named(name)
+ @method_list.find {|meth| meth.name == name}
+ end
+
+ # Find a named instance method, or return nil
+ def find_instance_method_named(name)
+ @method_list.find {|meth| meth.name == name && !meth.singleton}
+ end
+
+ # Find a named constant, or return nil
+ def find_constant_named(name)
+ @constants.find {|m| m.name == name}
+ end
+
+ # Find a named attribute, or return nil
+ def find_attribute_named(name)
+ @attributes.find {|m| m.name == name}
+ end
+
+ end
+
+
+ # A TopLevel context is a source file
+
+ class TopLevel < Context
+ attr_accessor :file_stat
+ attr_accessor :file_relative_name
+ attr_accessor :file_absolute_name
+ attr_accessor :diagram
+
+ @@all_classes = {}
+ @@all_modules = {}
+
+ def TopLevel::reset
+ @@all_classes = {}
+ @@all_modules = {}
+ end
+
+ def initialize(file_name)
+ super()
+ @name = "TopLevel"
+ @file_relative_name = file_name
+ @file_absolute_name = file_name
+ @file_stat = File.stat(file_name)
+ @diagram = nil
+ end
+
+ def full_name
+ nil
+ end
+
+ # Adding a class or module to a TopLevel is special, as we only
+ # want one copy of a particular top-level class. For example,
+ # if both file A and file B implement class C, we only want one
+ # ClassModule object for C. This code arranges to share
+ # classes and modules between files.
+
+ def add_class_or_module(collection, class_type, name, superclass)
+ cls = collection[name]
+ if cls
+ puts "Reusing class/module #{name}" if $DEBUG
+ else
+ if class_type == NormalModule
+ all = @@all_modules
+ else
+ all = @@all_classes
+ end
+ cls = all[name]
+ if !cls
+ cls = class_type.new(name, superclass)
+ all[name] = cls unless @done_documenting
+ end
+ puts "Adding class/module #{name} to #@name" if $DEBUG
+ collection[name] = cls unless @done_documenting
+ cls.parent = self
+ end
+ cls
+ end
+
+ def TopLevel.all_classes_and_modules
+ all_classes.values + all_modules.values
+ end
+
+ def TopLevel.find_class_named(name)
+ all_classes.each_value do |c|
+ res = c.find_class_named(name)
+ return res if res
+ end
+ nil
+ end
+
+ def find_local_symbol(symbol)
+ find_class_or_module_named(symbol) || super
+ end
+
+ def find_class_or_module_named(symbol)
+ all_classes.each_value {|c| return c if c.name == symbol}
+ all_modules.each_value {|m| return m if m.name == symbol}
+ nil
+ end
+
+ # Find a named module
+ def find_module_named(name)
+ find_class_or_module_named(name) || find_enclosing_module_named(name)
+ end
+
+
+ end
+
+ # ClassModule is the base class for objects representing either a
+ # class or a module.
+
+ class ClassModule < Context
+
+ attr_reader :superclass
+ attr_accessor :diagram
+
+ def initialize(name, superclass = nil)
+ @name = name
+ @diagram = nil
+ @superclass = superclass
+ @comment = ""
+ super()
+ end
+
+ # Return the fully qualified name of this class or module
+ def full_name
+ if @parent && @parent.full_name
+ @parent.full_name + "::" + @name
+ else
+ @name
+ end
+ end
+
+ def http_url(prefix)
+ path = full_name.split("::")
+ File.join(prefix, *path) + ".html"
+ end
+
+ # Return +true+ if this object represents a module
+ def is_module?
+ false
+ end
+
+ # to_s is simply for debugging
+ def to_s
+ res = self.class.name + ": " + @name
+ res << @comment.to_s
+ res << super
+ res
+ end
+
+ def find_class_named(name)
+ return self if full_name == name
+ @classes.each_value {|c| return c if c.find_class_named(name) }
+ nil
+ end
+ end
+
+ # Anonymous classes
+ class AnonClass < ClassModule
+ end
+
+ # Normal classes
+ class NormalClass < ClassModule
+ end
+
+ # Singleton classes
+ class SingleClass < ClassModule
+ end
+
+ # Module
+ class NormalModule < ClassModule
+ def is_module?
+ true
+ end
+ end
+
+
+ # AnyMethod is the base class for objects representing methods
+
+ class AnyMethod < CodeObject
+ attr_accessor :name
+ attr_accessor :visibility
+ attr_accessor :block_params
+ attr_accessor :dont_rename_initialize
+ attr_accessor :singleton
+ attr_reader :aliases # list of other names for this method
+ attr_accessor :is_alias_for # or a method we're aliasing
+
+ attr_overridable :params, :param, :parameters, :parameter
+
+ attr_accessor :call_seq
+
+
+ include TokenStream
+
+ def initialize(text, name)
+ super()
+ @text = text
+ @name = name
+ @token_stream = nil
+ @visibility = :public
+ @dont_rename_initialize = false
+ @block_params = nil
+ @aliases = []
+ @is_alias_for = nil
+ @comment = ""
+ @call_seq = nil
+ end
+
+ def <=>(other)
+ @name <=> other.name
+ end
+
+ def to_s
+ res = self.class.name + ": " + @name + " (" + @text + ")\n"
+ res << @comment.to_s
+ res
+ end
+
+ def param_seq
+ p = params.gsub(/\s*\#.*/, '')
+ p = p.tr("\n", " ").squeeze(" ")
+ p = "(" + p + ")" unless p[0] == ?(
+
+ if (block = block_params)
+ # If this method has explicit block parameters, remove any
+ # explicit &block
+$stderr.puts p
+ p.sub!(/,?\s*&\w+/)
+$stderr.puts p
+
+ block.gsub!(/\s*\#.*/, '')
+ block = block.tr("\n", " ").squeeze(" ")
+ if block[0] == ?(
+ block.sub!(/^\(/, '').sub!(/\)/, '')
+ end
+ p << " {|#{block}| ...}"
+ end
+ p
+ end
+
+ def add_alias(method)
+ @aliases << method
+ end
+ end
+
+
+ # Represent an alias, which is an old_name/ new_name pair associated
+ # with a particular context
+ class Alias < CodeObject
+ attr_accessor :text, :old_name, :new_name, :comment
+
+ def initialize(text, old_name, new_name, comment)
+ super()
+ @text = text
+ @old_name = old_name
+ @new_name = new_name
+ self.comment = comment
+ end
+
+ def to_s
+ "alias: #{self.old_name} -> #{self.new_name}\n#{self.comment}"
+ end
+ end
+
+ # Represent a constant
+ class Constant < CodeObject
+ attr_accessor :name, :value
+
+ def initialize(name, value, comment)
+ super()
+ @name = name
+ @value = value
+ self.comment = comment
+ end
+ end
+
+ # Represent attributes
+ class Attr < CodeObject
+ attr_accessor :text, :name, :rw, :visibility
+
+ def initialize(text, name, rw, comment)
+ super()
+ @text = text
+ @name = name
+ @rw = rw
+ @visibility = :public
+ self.comment = comment
+ end
+
+ def to_s
+ "attr: #{self.name} #{self.rw}\n#{self.comment}"
+ end
+
+ def <=>(other)
+ self.name <=> other.name
+ end
+ end
+
+ # a required file
+
+ class Require < CodeObject
+ attr_accessor :name
+
+ def initialize(name, comment)
+ super()
+ @name = name.gsub(/'|"/, "") #'
+ self.comment = comment
+ end
+
+ end
+
+ # an included module
+ class Include < CodeObject
+ attr_accessor :name
+
+ def initialize(name, comment)
+ super()
+ @name = name
+ self.comment = comment
+ end
+
+ end
+
+end
Added: trunk/lib/rdoc/diagram.rb
===================================================================
--- trunk/lib/rdoc/diagram.rb 2006-02-21 22:21:14 UTC (rev 462)
+++ trunk/lib/rdoc/diagram.rb 2006-02-22 01:41:14 UTC (rev 463)
@@ -0,0 +1,333 @@
+# A wonderful hack by to draw package diagrams using the dot package.
+# Originally written by Jah, team Enticla.
+#
+# You must have the V1.7 or later in your path
+# http://www.research.att.com/sw/tools/graphviz/
+
+require "rdoc/dot/dot"
+require 'rdoc/options'
+
+module RDoc
+
+ # Draw a set of diagrams representing the modules and classes in the
+ # system. We draw one diagram for each file, and one for each toplevel
+ # class or module. This means there will be overlap. However, it also
+ # means that you'll get better context for objects.
+ #
+ # To use, simply
+ #
+ # d = Diagram.new(info) # pass in collection of top level infos
+ # d.draw
+ #
+ # The results will be written to the +dot+ subdirectory. The process
+ # also sets the +diagram+ attribute in each object it graphs to
+ # the name of the file containing the image. This can be used
+ # by output generators to insert images.
+
+ class Diagram
+
+ FONT = "Arial"
+
+ DOT_PATH = "dot"
+
+ # Pass in the set of top level objects. The method also creates
+ # the subdirectory to hold the images
+
+ def initialize(info, options)
+ @info = info
+ @options = options
+ @counter = 0
+ File.makedirs(DOT_PATH)
+ end
+
+ # Draw the diagrams. We traverse the files, drawing a diagram for
+ # each. We also traverse each top-level class and module in that
+ # file drawing a diagram for these too.
+
+ def draw
+ unless @options.quiet
+ $stderr.print "Diagrams: "
+ $stderr.flush
+ end
+
+ @info.each_with_index do |i, file_count|
+ @done_modules = {}
+ @local_names = find_names(i)
+ @global_names = []
+ @global_graph = graph = DOT::DOTDigraph.new('name' => 'TopLevel',
+ 'label' => i.file_absolute_name,
+ 'fontname' => FONT,
+ 'fontsize' => '8',
+ 'bgcolor' => 'lightcyan1',
+ 'compound' => 'true')
+
+ # it's a little hack %) i'm too lazy to create a separate class
+ # for default node
+ graph << DOT::DOTNode.new('name' => 'node',
+ 'fontname' => FONT,
+ 'color' => 'black',
+ 'fontsize' => 8)
+
+ i.modules.each do |mod|
+ draw_module(mod, graph, true, i.file_relative_name)
+ end
+ add_classes(i, graph, i.file_relative_name)
+
+ i.diagram = convert_to_png("f_#{file_count}", graph, i.name)
+
+ # now go through and document each top level class and
+ # module independently
+ i.modules.each_with_index do |mod, count|
+ @done_modules = {}
+ @local_names = find_names(mod)
+ @global_names = []
+
+ @global_graph = graph = DOT::DOTDigraph.new('name' => 'TopLevel',
+ 'label' => i.full_name,
+ 'fontname' => FONT,
+ 'fontsize' => '8',
+ 'bgcolor' => 'lightcyan1',
+ 'compound' => 'true')
+
+ graph << DOT::DOTNode.new('name' => 'node',
+ 'fontname' => FONT,
+ 'color' => 'black',
+ 'fontsize' => 8)
+ draw_module(mod, graph, true)
+ mod.diagram = convert_to_png("m_#{file_count}_#{count}",
+ graph,
+ "Module: #{mod.name}")
+ end
+ end
+ $stderr.puts unless @options.quiet
+ end
+
+ #######
+ private
+ #######
+
+ def find_names(mod)
+ return [mod.full_name] + mod.classes.collect{|cl| cl.full_name} +
+ mod.modules.collect{|m| find_names(m)}.flatten
+ end
+
+ def find_full_name(name, mod)
+ full_name = name.dup
+ return full_name if @local_names.include?(full_name)
+ mod_path = mod.full_name.split('::')[0..-2]
+ unless mod_path.nil?
+ until mod_path.empty?
+ full_name = mod_path.pop + '::' + full_name
+ return full_name if @local_names.include?(full_name)
+ end
+ end
+ return name
+ end
+
+ def draw_module(mod, graph, toplevel = false, file = nil)
+ return if @done_modules[mod.full_name] and not toplevel
+
+ @counter += 1
+ url = mod.http_url("classes")
+ m = DOT::DOTSubgraph.new('name' => "cluster_#{mod.full_name.gsub( /:/,'_' )}",
+ 'label' => mod.name,
+ 'fontname' => FONT,
+ 'color' => 'blue',
+ 'style' => 'filled',
+ 'URL' => %{"#{url}"},
+ 'fillcolor' => toplevel ? 'palegreen1' : 'palegreen3')
+
+ @done_modules[mod.full_name] = m
+ add_classes(mod, m, file)
+ graph << m
+
+ unless mod.includes.empty?
+ mod.includes.each do |m|
+ m_full_name = find_full_name(m.name, mod)
+ if @local_names.include?(m_full_name)
+ @global_graph << DOT::DOTEdge.new('from' => "#{m_full_name.gsub( /:/,'_' )}",
+ 'to' => "#{mod.full_name.gsub( /:/,'_' )}",
+ 'ltail' => "cluster_#{m_full_name.gsub( /:/,'_' )}",
+ 'lhead' => "cluster_#{mod.full_name.gsub( /:/,'_' )}")
+ else
+ unless @global_names.include?(m_full_name)
+ path = m_full_name.split("::")
+ url = File.join('classes', *path) + ".html"
+ @global_graph << DOT::DOTNode.new('name' => "#{m_full_name.gsub( /:/,'_' )}",
+ 'shape' => 'box',
+ 'label' => "#{m_full_name}",
+ 'URL' => %{"#{url}"})
+ @global_names << m_full_name
+ end
+ @global_graph << DOT::DOTEdge.new('from' => "#{m_full_name.gsub( /:/,'_' )}",
+ 'to' => "#{mod.full_name.gsub( /:/,'_' )}",
+ 'lhead' => "cluster_#{mod.full_name.gsub( /:/,'_' )}")
+ end
+ end
+ end
+ end
+
+ def add_classes(container, graph, file = nil )
+
+ use_fileboxes = Options.instance.fileboxes
+
+ files = {}
+
+ # create dummy node (needed if empty and for module includes)
+ if container.full_name
+ graph << DOT::DOTNode.new('name' => "#{container.full_name.gsub( /:/,'_' )}",
+ 'label' => "",
+ 'width' => (container.classes.empty? and
+ container.modules.empty?) ?
+ '0.75' : '0.01',
+ 'height' => '0.01',
+ 'shape' => 'plaintext')
+ end
+ container.classes.each_with_index do |cl, cl_index|
+ last_file = cl.in_files[-1].file_relative_name
+
+ if use_fileboxes && !files.include?(last_file)
+ @counter += 1
+ files[last_file] =
+ DOT::DOTSubgraph.new('name' => "cluster_#{@counter}",
+ 'label' => "#{last_file}",
+ 'fontname' => FONT,
+ 'color'=>
+ last_file == file ? 'red' : 'black')
+ end
+
+ next if cl.name == 'Object' || cl.name[0,2] == "<<"
+
+ url = cl.http_url("classes")
+
+ label = cl.name.dup
+ if use_fileboxes && cl.in_files.length > 1
+ label << '\n[' +
+ cl.in_files.collect {|i|
+ i.file_relative_name
+ }.sort.join( '\n' ) +
+ ']'
+ end
+
+ attrs = {
+ 'name' => "#{cl.full_name.gsub( /:/, '_' )}",
+ 'fontcolor' => 'black',
+ 'style'=>'filled',
+ 'color'=>'palegoldenrod',
+ 'label' => label,
+ 'shape' => 'ellipse',
+ 'URL' => %{"#{url}"}
+ }
+
+ c = DOT::DOTNode.new(attrs)
+
+ if use_fileboxes
+ files[last_file].push c
+ else
+ graph << c
+ end
+ end
+
+ if use_fileboxes
+ files.each_value do |val|
+ graph << val
+ end
+ end
+
+ unless container.classes.empty?
+ container.classes.each_with_index do |cl, cl_index|
+ cl.includes.each do |m|
+ m_full_name = find_full_name(m.name, cl)
+ if @local_names.include?(m_full_name)
+ @global_graph << DOT::DOTEdge.new('from' => "#{m_full_name.gsub( /:/,'_' )}",
+ 'to' => "#{cl.full_name.gsub( /:/,'_' )}",
+ 'ltail' => "cluster_#{m_full_name.gsub( /:/,'_' )}")
+ else
+ unless @global_names.include?(m_full_name)
+ path = m_full_name.split("::")
+ url = File.join('classes', *path) + ".html"
+ @global_graph << DOT::DOTNode.new('name' => "#{m_full_name.gsub( /:/,'_' )}",
+ 'shape' => 'box',
+ 'label' => "#{m_full_name}",
+ 'URL' => %{"#{url}"})
+ @global_names << m_full_name
+ end
+ @global_graph << DOT::DOTEdge.new('from' => "#{m_full_name.gsub( /:/,'_' )}",
+ 'to' => "#{cl.full_name.gsub( /:/, '_')}")
+ end
+ end
+
+ sclass = cl.superclass
+ next if sclass.nil? || sclass == 'Object'
+ sclass_full_name = find_full_name(sclass,cl)
+ unless @local_names.include?(sclass_full_name) or @global_names.include?(sclass_full_name)
+ path = sclass_full_name.split("::")
+ url = File.join('classes', *path) + ".html"
+ @global_graph << DOT::DOTNode.new(
+ 'name' => "#{sclass_full_name.gsub( /:/, '_' )}",
+ 'label' => sclass_full_name,
+ 'URL' => %{"#{url}"})
+ @global_names << sclass_full_name
+ end
+ @global_graph << DOT::DOTEdge.new('from' => "#{sclass_full_name.gsub( /:/,'_' )}",
+ 'to' => "#{cl.full_name.gsub( /:/, '_')}")
+ end
+ end
+
+ container.modules.each do |submod|
+ draw_module(submod, graph)
+ end
+
+ end
+
+ def convert_to_png(file_base, graph, name)
+ op_type = Options.instance.image_format
+ dotfile = File.join(DOT_PATH, file_base)
+ src = dotfile + ".dot"
+ dot = dotfile + "." + op_type
+
+ unless @options.quiet
+ $stderr.print "."
+ $stderr.flush
+ end
+
+ File.open(src, 'w+' ) do |f|
+ f << graph.to_s << "\n"
+ end
+
+ system "dot", "-T#{op_type}", src, "-o", dot
+
+ # Now construct the imagemap wrapper around
+ # that png
+
+ return wrap_in_image_map(src, dot, name)
+ end
+
+ # Extract the client-side image map from dot, and use it
+ # to generate the imagemap proper. Return the whole
+ # <map>..<img> combination, suitable for inclusion on
+ # the page
+
+ def wrap_in_image_map(src, dot, name)
+ res = %{<map id="map" name="map">\n}
+ dot_map = `dot -Tismap #{src}`
+ dot_map.each do |area|
+ unless area =~ /^rectangle \((\d+),(\d+)\) \((\d+),(\d+)\) ([\/\w.]+)\s*(.*)/
+ $stderr.puts "Unexpected output from dot:\n#{area}"
+ return nil
+ end
+
+ xs, ys = [$1.to_i, $3.to_i], [$2.to_i, $4.to_i]
+ url, area_name = $5, $6
+
+ res << %{ <area shape="RECT" coords="#{xs.min},#{ys.min},#{xs.max},#{ys.max}" }
+ res << %{ href="#{url}" alt="#{area_name}">\n}
+ end
+ res << "</map>\n"
+# map_file = src.sub(/.dot/, '.map')
+# system("dot -Timap #{src} -o #{map_file}")
+ res << %{<img src="#{dot}" usemap="#map" border=0 alt="#{name}">}
+ return res
+ end
+ end
+end
Added: trunk/lib/rdoc/dot/dot.rb
===================================================================
--- trunk/lib/rdoc/dot/dot.rb 2006-02-21 22:21:14 UTC (rev 462)
+++ trunk/lib/rdoc/dot/dot.rb 2006-02-22 01:41:14 UTC (rev 463)
@@ -0,0 +1,255 @@
+module DOT
+
+ # these glogal vars are used to make nice graph source
+ $tab = ' '
+ $tab2 = $tab * 2
+
+ # if we don't like 4 spaces, we can change it any time
+ def change_tab( t )
+ $tab = t
+ $tab2 = t * 2
+ end
+
+ # options for node declaration
+ NODE_OPTS = [
+ 'bgcolor',
+ 'color',
+ 'fontcolor',
+ 'fontname',
+ 'fontsize',
+ 'height',
+ 'width',
+ 'label',
+ 'layer',
+ 'rank',
+ 'shape',
+ 'shapefile',
+ 'style',
+ 'URL',
+ ]
+
+ # options for edge declaration
+ EDGE_OPTS = [
+ 'color',
+ 'decorate',
+ 'dir',
+ 'fontcolor',
+ 'fontname',
+ 'fontsize',
+ 'id',
+ 'label',
+ 'layer',
+ 'lhead',
+ 'ltail',
+ 'minlen',
+ 'style',
+ 'weight'
+ ]
+
+ # options for graph declaration
+ GRAPH_OPTS = [
+ 'bgcolor',
+ 'center',
+ 'clusterrank',
+ 'color',
+ 'compound',
+ 'concentrate',
+ 'fillcolor',
+ 'fontcolor',
+ 'fontname',
+ 'fontsize',
+ 'label',
+ 'layerseq',
+ 'margin',
+ 'mclimit',
+ 'nodesep',
+ 'nslimit',
+ 'ordering',
+ 'orientation',
+ 'page',
+ 'rank',
+ 'rankdir',
+ 'ranksep',
+ 'ratio',
+ 'size',
+ 'style',
+ 'URL'
+ ]
+
+ # a root class for any element in dot notation
+ class DOTSimpleElement
+ attr_accessor :name
+
+ def initialize( params = {} )
+ @label = params['name'] ? params['name'] : ''
+ end
+
+ def to_s
+ @name
+ end
+ end
+
+ # an element that has options ( node, edge or graph )
+ class DOTElement < DOTSimpleElement
+ #attr_reader :parent
+ attr_accessor :name, :options
+
+ def initialize( params = {}, option_list = [] )
+ super( params )
+ @name = params['name'] ? params['name'] : nil
+ @parent = params['parent'] ? params['parent'] : nil
+ @options = {}
+ option_list.each{ |i|
+ @options[i] = params[i] if params[i]
+ }
+ @options['label'] ||= @name if @name != 'node'
+ end
+
+ def each_option
+ @options.each{ |i| yield i }
+ end
+
+ def each_option_pair
+ @options.each_pair{ |key, val| yield key, val }
+ end
+
+ #def parent=( thing )
+ # @parent.delete( self ) if defined?( @parent ) and @parent
+ # @parent = thing
+ #end
+ end
+
+
+ # this is used when we build nodes that have shape=record
+ # ports don't have options :)
+ class DOTPort < DOTSimpleElement
+ attr_accessor :label
+
+ def initialize( params = {} )
+ super( params )
+ @name = params['label'] ? params['label'] : ''
+ end
+ def to_s
+ ( @name && @name != "" ? "<#{@name}>" : "" ) + "#{@label}"
+ end
+ end
+
+ # node element
+ class DOTNode < DOTElement
+
+ def initialize( params = {}, option_list = NODE_OPTS )
+ super( params, option_list )
+ @ports = params['ports'] ? params['ports'] : []
+ end
+
+ def each_port
+ @ports.each{ |i| yield i }
+ end
+
+ def << ( thing )
+ @ports << thing
+ end
+
+ def push ( thing )
+ @ports.push( thing )
+ end
+
+ def pop
+ @ports.pop
+ end
+
+ def to_s( t = '' )
+
+ label = @options['shape'] != 'record' && @ports.length == 0 ?
+ @options['label'] ?
+ t + $tab + "label = \"#{@options['label']}\"\n" :
+ '' :
+ t + $tab + 'label = "' + " \\\n" +
+ t + $tab2 + "#{@options['label']}| \\\n" +
+ @ports.collect{ |i|
+ t + $tab2 + i.to_s
+ }.join( "| \\\n" ) + " \\\n" +
+ t + $tab + '"' + "\n"
+
+ t + "#{@name} [\n" +
+ @options.to_a.collect{ |i|
+ i[1] && i[0] != 'label' ?
+ t + $tab + "#{i[0]} = #{i[1]}" : nil
+ }.compact.join( ",\n" ) + ( label != '' ? ",\n" : "\n" ) +
+ label +
+ t + "]\n"
+ end
+ end
+
+ # subgraph element is the same to graph, but has another header in dot
+ # notation
+ class DOTSubgraph < DOTElement
+
+ def initialize( params = {}, option_list = GRAPH_OPTS )
+ super( params, option_list )
+ @nodes = params['nodes'] ? params['nodes'] : []
+ @dot_string = 'subgraph'
+ end
+
+ def each_node
+ @nodes.each{ |i| yield i }
+ end
+
+ def << ( thing )
+ @nodes << thing
+ end
+
+ def push( thing )
+ @nodes.push( thing )
+ end
+
+ def pop
+ @nodes.pop
+ end
+
+ def to_s( t = '' )
+ hdr = t + "#{@dot_string} #{@name} {\n"
+
+ options = @options.to_a.collect{ |name, val|
+ val && name != 'label' ?
+ t + $tab + "#{name} = #{val}" :
+ name ? t + $tab + "#{name} = \"#{val}\"" : nil
+ }.compact.join( "\n" ) + "\n"
+
+ nodes = @nodes.collect{ |i|
+ i.to_s( t + $tab )
+ }.join( "\n" ) + "\n"
+ hdr + options + nodes + t + "}\n"
+ end
+ end
+
+ # this is graph
+ class DOTDigraph < DOTSubgraph
+ def initialize( params = {}, option_list = GRAPH_OPTS )
+ super( params, option_list )
+ @dot_string = 'digraph'
+ end
+ end
+
+ # this is edge
+ class DOTEdge < DOTElement
+ attr_accessor :from, :to
+ def initialize( params = {}, option_list = EDGE_OPTS )
+ super( params, option_list )
+ @from = params['from'] ? params['from'] : nil
+ @to = params['to'] ? params['to'] : nil
+ end
+
+ def to_s( t = '' )
+ t + "#{@from} -> #{to} [\n" +
+ @options.to_a.collect{ |i|
+ i[1] && i[0] != 'label' ?
+ t + $tab + "#{i[0]} = #{i[1]}" :
+ i[1] ? t + $tab + "#{i[0]} = \"#{i[1]}\"" : nil
+ }.compact.join( "\n" ) + "\n" + t + "]\n"
+ end
+ end
+end
+
+
+
Added: trunk/lib/rdoc/generators/chm_generator.rb
===================================================================
--- trunk/lib/rdoc/generators/chm_generator.rb 2006-02-21 22:21:14 UTC (rev 462)
+++ trunk/lib/rdoc/generators/chm_generator.rb 2006-02-22 01:41:14 UTC (rev 463)
@@ -0,0 +1,112 @@
+require 'rdoc/generators/html_generator'
+
+module Generators
+
+ class CHMGenerator < HTMLGenerator
+
+ HHC_PATH = "c:/Program Files/HTML Help Workshop/hhc.exe"
+
+ # Standard generator factory
+ def CHMGenerator.for(options)
+ CHMGenerator.new(options)
+ end
+
+
+ def initialize(*args)
+ super
+ @op_name = @options.op_name || "rdoc"
+ check_for_html_help_workshop
+ end
+
+ def check_for_html_help_workshop
+ stat = File.stat(HHC_PATH)
+ rescue
+ $stderr <<
+ "\n.chm output generation requires that Microsoft's Html Help\n" <<
+ "Workshop is installed. RDoc looks for it in:\n\n " <<
+ HHC_PATH <<
+ "\n\nYou can download a copy for free from:\n\n" <<
+ " http://msdn.microsoft.com/library/default.asp?" <<
+ "url=/library/en-us/htmlhelp/html/hwMicrosoftHTMLHelpDownloads.asp\n\n"
+
+ exit 99
+ end
+
+ # Generate the html as normal, then wrap it
+ # in a help project
+ def generate(info)
+ super
+ @project_name = @op_name + ".hhp"
+ create_help_project
+ end
+
+ # The project contains the project file, a table of contents
+ # and an index
+ def create_help_project
+ create_project_file
+ create_contents_and_index
+ compile_project
+ end
+
+ # The project file links together all the various
+ # files that go to make up the help.
+
+ def create_project_file
+ template = TemplatePage.new(RDoc::Page::HPP_FILE)
+ values = { "title" => @options.title, "opname" => @op_name }
+ files = []
+ @files.each do |f|
+ files << { "html_file_name" => f.path }
+ end
+
+ values['all_html_files'] = files
+
+ File.open(@project_name, "w") do |f|
+ template.write_html_on(f, values)
+ end
+ end
+
+ # The contents is a list of all files and modules.
+ # For each we include as sub-entries the list
+ # of methods they contain. As we build the contents
+ # we also build an index file
+
+ def create_contents_and_index
+ contents = []
+ index = []
+
+ ( files+ classes).sort.each do |entry|
+ content_entry = { "c_name" => entry.name, "ref" => entry.path }
+ index << { "name" => entry.name, "aref" => entry.path }
+
+ internals = []
+
+ methods = entry.build_method_summary_list(entry.path)
+
+ content_entry["methods"] = methods unless methods.empty?
+ contents << content_entry
+ index.concat methods
+ end
+
+ values = { "contents" => contents }
+ template = TemplatePage.new(RDoc::Page::CONTENTS)
+ File.open("contents.hhc", "w") do |f|
+ template.write_html_on(f, values)
+ end
+
+ values = { "index" => index }
+ template = TemplatePage.new(RDoc::Page::CHM_INDEX)
+ File.open("index.hhk", "w") do |f|
+ template.write_html_on(f, values)
+ end
+ end
+
+ # Invoke the windows help compiler to compiler the project
+ def compile_project
+ system(HHC_PATH, @project_name)
+ end
+
+ end
+
+
+end
Added: trunk/lib/rdoc/generators/html_generator.rb
===================================================================
--- trunk/lib/rdoc/generators/html_generator.rb 2006-02-21 22:21:14 UTC (rev 462)
+++ trunk/lib/rdoc/generators/html_generator.rb 2006-02-22 01:41:14 UTC (rev 463)
@@ -0,0 +1,1502 @@
+# We're responsible for generating all the HTML files
+# from the object tree defined in code_objects.rb. We
+# generate:
+#
+# [files] an html file for each input file given. These
+# input files appear as objects of class
+# TopLevel
+#
+# [classes] an html file for each class or module encountered.
+# These classes are not grouped by file: if a file
+# contains four classes, we'll generate an html
+# file for the file itself, and four html files
+# for the individual classes.
+#
+# [indices] we generate three indices for files, classes,
+# and methods. These are displayed in a browser
+# like window with three index panes across the
+# top and the selected description below
+#
+# Method descriptions appear in whatever entity (file, class,
+# or module) that contains them.
+#
+# We generate files in a structure below a specified subdirectory,
+# normally +doc+.
+#
+# opdir
+# |
+# |___ files
+# | |__ per file summaries
+# |
+# |___ classes
+# |__ per class/module descriptions
+#
+# HTML is generated using the Template class.
+#
+
+require 'ftools'
+
+require 'rdoc/options'
+require 'rdoc/template'
+require 'rdoc/markup/simple_markup'
+require 'rdoc/markup/simple_markup/to_html'
+require 'cgi'
+
+module Generators
+
+ # Name of sub-direcories that hold file and class/module descriptions
+
+ FILE_DIR = "files"
+ CLASS_DIR = "classes"
+ CSS_NAME = "rdoc-style.css"
+
+
+ ##
+ # Build a hash of all items that can be cross-referenced.
+ # This is used when we output required and included names:
+ # if the names appear in this hash, we can generate
+ # an html cross reference to the appropriate description.
+ # We also use this when parsing comment blocks: any decorated
+ # words matching an entry in this list are hyperlinked.
+
+ class AllReferences
+ @@refs = {}
+
+ def AllReferences::reset
+ @@refs = {}
+ end
+
+ def AllReferences.add(name, html_class)
+ @@refs[name] = html_class
+ end
+
+ def AllReferences.[](name)
+ @@refs[name]
+ end
+
+ def AllReferences.keys
+ refs.keys
+ end
+ end
+
+
+ ##
+ # Subclass of the SM::ToHtml class that supports looking
+ # up words in the AllReferences list. Those that are
+ # found (like AllReferences in this comment) will
+ # be hyperlinked
+
+ class HyperlinkHtml < SM::ToHtml
+ # We need to record the html path of our caller so we can generate
+ # correct relative paths for any hyperlinks that we find
+ def initialize(from_path, context)
+ super()
+ @from_path = from_path
+
+ @parent_name = context.parent_name
+ @parent_name += "::" if @parent_name
+ @context = context
+ end
+
+ # We're invoked when any text matches the CROSSREF pattern
+ # (defined in MarkUp). If we fine the corresponding reference,
+ # generate a hyperlink. If the name we're looking for contains
+ # no punctuation, we look for it up the module/class chain. For
+ # example, HyperlinkHtml is found, even without the Generators::
+ # prefix, because we look for it in module Generators first.
+
+ def handle_special_CROSSREF(special)
+ name = special.text
+ if name[0,1] == '#'
+ lookup = name[1..-1]
+ name = lookup unless Options.instance.show_hash
+ else
+ lookup = name
+ end
+
+ if /([A-Z].*)[.\#](.*)/ =~ lookup
+ container = $1
+ method = $2
+ ref = @context.find_symbol(container, method)
+ else
+ ref = @context.find_symbol(lookup)
+ end
+
+ if ref and ref.document_self
+ "<a href=\"#{ref.as_href(@from_path)}\">#{name}</a>"
+ else
+ name
+ end
+ end
+
+
+ # Generate a hyperlink for url, labeled with text. Handle the
+ # special cases for img: and link: described under handle_special_HYPEDLINK
+ def gen_url(url, text)
+ if url =~ /([A-Za-z]+):(.*)/
+ type = $1
+ path = $2
+ else
+ type = "http"
+ path = url
+ url = "http://#{url}"
+ end
+
+ if type == "link"
+ if path[0,1] == '#' # is this meaningful?
+ url = path
+ else
+ url = HTMLGenerator.gen_url(@from_path, path)
+ end
+ end
+
+ if (type == "http" || type == "link") &&
+ url =~ /\.(gif|png|jpg|jpeg|bmp)$/
+
+ "<img src=\"#{url}\">"
+ else
+ "<a href=\"#{url}\">#{text.sub(%r{^#{type}:/*}, '')}</a>"
+ end
+ end
+
+ # And we're invoked with a potential external hyperlink mailto:
+ # just gets inserted. http: links are checked to see if they
+ # reference an image. If so, that image gets inserted using an
+ # <img> tag. Otherwise a conventional <a href> is used. We also
+ # support a special type of hyperlink, link:, which is a reference
+ # to a local file whose path is relative to the --op directory.
+
+ def handle_special_HYPERLINK(special)
+ url = special.text
+ gen_url(url, url)
+ end
+
+ # HEre's a hypedlink where the label is different to the URL
+ # <label>[url]
+ #
+
+ def handle_special_TIDYLINK(special)
+ text = special.text
+# unless text =~ /(\S+)\[(.*?)\]/
+ unless text =~ /\{(.*?)\}\[(.*?)\]/ or text =~ /(\S+)\[(.*?)\]/
+ return text
+ end
+ label = $1
+ url = $2
+ gen_url(url, label)
+ end
+
+ end
+
+
+
+ #####################################################################
+ #
+ # Handle common markup tasks for the various Html classes
+ #
+
+ module MarkUp
+
+ # Convert a string in markup format into HTML. We keep a cached
+ # SimpleMarkup object lying around after the first time we're
+ # called per object.
+
+ def markup(str, remove_para=false)
+ return '' unless str
+ unless defined? @markup
+ @markup = SM::SimpleMarkup.new
+
+ # class names, variable names, file names, or instance variables
+ @markup.add_special(/(
+ \b([A-Z]\w*(::\w+)*[.\#]\w+) # A::B.meth
+ | \b([A-Z]\w+(::\w+)*) # A::B..
+ | \#\w+[!?=]? # #meth_name
+ | \b\w+([_\/\.]+\w+)+[!?=]? # meth_name
+ )/x,
+ :CROSSREF)
+
+ # external hyperlinks
+ @markup.add_special(/((link:|https?:|mailto:|ftp:|www\.)\S+\w)/, :HYPERLINK)
+
+ # and links of the form <text>[<url>]
+ @markup.add_special(/(((\{.*?\})|\b\S+?)\[\S+?\.\S+?\])/, :TIDYLINK)
+# @markup.add_special(/\b(\S+?\[\S+?\.\S+?\])/, :TIDYLINK)
+
+ end
+ unless defined? @html_formatter
+ @html_formatter = HyperlinkHtml.new(self.path, self)
+ end
+
+ # Convert leading comment markers to spaces, but only
+ # if all non-blank lines have them
+
+ if str =~ /^(?>\s*)[^\#]/
+ content = str
+ else
+ content = str.gsub(/^\s*(#+)/) { $1.tr('#',' ') }
+ end
+
+ res = @markup.convert(content, @html_formatter)
+ if remove_para
+ res.sub!(/^<p>/, '')
+ res.sub!(/<\/p>$/, '')
+ end
+ res
+ end
+
+ # Qualify a stylesheet URL; if if +css_name+ does not begin with '/' or
+ # 'http[s]://', prepend a prefix relative to +path+. Otherwise, return it
+ # unmodified.
+
+ def style_url(path, css_name=nil)
+# $stderr.puts "style_url( #{path.inspect}, #{css_name.inspect} )"
+ css_name ||= CSS_NAME
+ if %r{^(https?:/)?/} =~ css_name
+ return css_name
+ else
+ return HTMLGenerator.gen_url(path, css_name)
+ end
+ end
+
+ # Build a webcvs URL with the given 'url' argument. URLs with a '%s' in them
+ # get the file's path sprintfed into them; otherwise they're just catenated
+ # together.
+
+ def cvs_url(url, full_path)
+ if /%s/ =~ url
+ return sprintf( url, full_path )
+ else
+ return url + full_path
+ end
+ end
+ end
+
+
+ #####################################################################
+ #
+ # A Context is built by the parser to represent a container: contexts
+ # hold classes, modules, methods, require lists and include lists.
+ # ClassModule and TopLevel are the context objects we process here
+ #
+ class ContextUser
+
+ include MarkUp
+
+ attr_reader :context
+
+ def initialize(context, options)
+ @context = context
+ @options = options
+ end
+
+ # convenience method to build a hyperlink
+ def href(link, cls, name)
+ %{<a href="#{link}" class="#{cls}">#{name}</a>} #"
+ end
+
+ # return a reference to outselves to be used as an href=
+ # the form depends on whether we're all in one file
+ # or in multiple files
+
+ def as_href(from_path)
+ if @options.all_one_file
+ "#" + path
+ else
+ HTMLGenerator.gen_url(from_path, path)
+ end
+ end
+
+ # Create a list of HtmlMethod objects for each method
+ # in the corresponding context object. If the @options.show_all
+ # variable is set (corresponding to the <tt>--all</tt> option,
+ # we include all methods, otherwise just the public ones.
+
+ def collect_methods
+ list = @context.method_list
+ unless @options.show_all
+ list = list.find_all {|m| m.visibility == :public || m.visibility == :protected || m.force_documentation }
+ end
+ @methods = list.collect {|m| HtmlMethod.new(m, self, @options) }
+ end
+
+ # Build a summary list of all the methods in this context
+ def build_method_summary_list(path_prefix="")
+ collect_methods unless @methods
+ meths = @methods.sort
+ res = []
+ meths.each do |meth|
+ res << {
+ "name" => CGI.escapeHTML(meth.name),
+ "aref" => "#{path_prefix}\##{meth.aref}"
+ }
+ end
+ res
+ end
+
+
+ # Build a list of aliases for which we couldn't find a
+ # corresponding method
+ def build_alias_summary_list(section)
+ values = []
+ @context.aliases.each do |al|
+ next unless al.section == section
+ res = {
+ 'old_name' => al.old_name,
+ 'new_name' => al.new_name,
+ }
+ if al.comment && !al.comment.empty?
+ res['desc'] = markup(al.comment, true)
+ end
+ values << res
+ end
+ values
+ end
+
+ # Build a list of constants
+ def build_constants_summary_list(section)
+ values = []
+ @context.constants.each do |co|
+ next unless co.section == section
+ res = {
+ 'name' => co.name,
+ 'value' => CGI.escapeHTML(co.value)
+ }
+ res['desc'] = markup(co.comment, true) if co.comment && !co.comment.empty?
+ values << res
+ end
+ values
+ end
+
+ def build_requires_list(context)
+ potentially_referenced_list(context.requires) {|fn| [fn + ".rb"] }
+ end
+
+ def build_include_list(context)
+ potentially_referenced_list(context.includes)
+ end
+
+ # Build a list from an array of <i>Htmlxxx</i> items. Look up each
+ # in the AllReferences hash: if we find a corresponding entry,
+ # we generate a hyperlink to it, otherwise just output the name.
+ # However, some names potentially need massaging. For example,
+ # you may require a Ruby file without the .rb extension,
+ # but the file names we know about may have it. To deal with
+ # this, we pass in a block which performs the massaging,
+ # returning an array of alternative names to match
+
+ def potentially_referenced_list(array)
+ res = []
+ array.each do |i|
+ ref = AllReferences[i.name]
+# if !ref
+# container = @context.parent
+# while !ref && container
+# name = container.name + "::" + i.name
+# ref = AllReferences[name]
+# container = container.parent
+# end
+# end
+
+ ref = @context.find_symbol(i.name)
+ ref = ref.viewer if ref
+
+ if !ref && block_given?
+ possibles = yield(i.name)
+ while !ref and !possibles.empty?
+ ref = AllReferences[possibles.shift]
+ end
+ end
+ h_name = CGI.escapeHTML(i.name)
+ if ref and ref.document_self
+ path = url(ref.path)
+ res << { "name" => h_name, "aref" => path }
+ else
+ res << { "name" => h_name }
+ end
+ end
+ res
+ end
+
+ # Build an array of arrays of method details. The outer array has up
+ # to six entries, public, private, and protected for both class
+ # methods, the other for instance methods. The inner arrays contain
+ # a hash for each method
+
+ def build_method_detail_list(section)
+ outer = []
+
+ methods = @methods.sort
+ for singleton in [true, false]
+ for vis in [ :public, :protected, :private ]
+ res = []
+ methods.each do |m|
+ if m.section == section and
+ m.document_self and
+ m.visibility == vis and
+ m.singleton == singleton
+ row = {}
+ if m.call_seq
+ row["callseq"] = m.call_seq.gsub(/->/, '→')
+ else
+ row["name"] = CGI.escapeHTML(m.name)
+ row["params"] = m.params
+ end
+ desc = m.description.strip
+ row["m_desc"] = desc unless desc.empty?
+ row["aref"] = m.aref
+ row["visibility"] = m.visibility.to_s
+
+ alias_names = []
+ m.aliases.each do |other|
+ if other.viewer # won't be if the alias is private
+ alias_names << {
+ 'name' => other.name,
+ 'aref' => other.viewer.as_href(path)
+ }
+ end
+ end
+ unless alias_names.empty?
+ row["aka"] = alias_names
+ end
+
+ if @options.inline_source
+ code = m.source_code
+ row["sourcecode"] = code if code
+ else
+ code = m.src_url
+ if code
+ row["codeurl"] = code
+ row["imgurl"] = m.img_url
+ end
+ end
+ res << row
+ end
+ end
+ if res.size > 0
+ outer << {
+ "type" => vis.to_s.capitalize,
+ "category" => singleton ? "Class" : "Instance",
+ "methods" => res
+ }
+ end
+ end
+ end
+ outer
+ end
+
+ # Build the structured list of classes and modules contained
+ # in this context.
+
+ def build_class_list(level, from, section, infile=nil)
+ res = ""
+ prefix = " ::" * level;
+
+ from.modules.sort.each do |mod|
+ next unless mod.section == section
+ next if infile && !mod.defined_in?(infile)
+ if mod.document_self
+ res <<
+ prefix <<
+ "Module " <<
+ href(url(mod.viewer.path), "link", mod.full_name) <<
+ "<br />\n" <<
+ build_class_list(level + 1, mod, section, infile)
+ end
+ end
+
+ from.classes.sort.each do |cls|
+ next unless cls.section == section
+ next if infile && !cls.defined_in?(infile)
+ if cls.document_self
+ res <<
+ prefix <<
+ "Class " <<
+ href(url(cls.viewer.path), "link", cls.full_name) <<
+ "<br />\n" <<
+ build_class_list(level + 1, cls, section, infile)
+ end
+ end
+
+ res
+ end
+
+ def url(target)
+ HTMLGenerator.gen_url(path, target)
+ end
+
+ def aref_to(target)
+ if @options.all_one_file
+ "#" + target
+ else
+ url(target)
+ end
+ end
+
+ def document_self
+ @context.document_self
+ end
+
+ def diagram_reference(diagram)
+ res = diagram.gsub(/((?:src|href)=")(.*?)"/) {
+ $1 + url($2) + '"'
+ }
+ res
+ end
+
+
+ # Find a symbol in ourselves or our parent
+ def find_symbol(symbol, method=nil)
+ res = @context.find_symbol(symbol, method)
+ if res
+ res = res.viewer
+ end
+ res
+ end
+
+ # create table of contents if we contain sections
+
+ def add_table_of_sections
+ toc = []
+ @context.sections.each do |section|
+ if section.title
+ toc << {
+ 'secname' => section.title,
+ 'href' => section.sequence
+ }
+ end
+ end
+
+ @values['toc'] = toc unless toc.empty?
+ end
+
+
+ end
+
+ #####################################################################
+ #
+ # Wrap a ClassModule context
+
+ class HtmlClass < ContextUser
+
+ attr_reader :path
+
+ def initialize(context, html_file, prefix, options)
+ super(context, options)
+
+ @html_file = html_file
+ @is_module = context.is_module?
+ @values = {}
+
+ context.viewer = self
+
+ if options.all_one_file
+ @path = context.full_name
+ else
+ @path = http_url(context.full_name, prefix)
+ end
+
+ collect_methods
+
+ AllReferences.add(name, self)
+ end
+
+ # return the relative file name to store this class in,
+ # which is also its url
+ def http_url(full_name, prefix)
+ path = full_name.dup
+ if path['<<']
+ path.gsub!(/<<\s*(\w*)/) { "from-#$1" }
+ end
+ File.join(prefix, path.split("::")) + ".html"
+ end
+
+
+ def name
+ @context.full_name
+ end
+
+ def parent_name
+ @context.parent.full_name
+ end
+
+ def index_name
+ name
+ end
+
+ def write_on(f)
+ value_hash
+ template = TemplatePage.new(RDoc::Page::BODY,
+ RDoc::Page::CLASS_PAGE,
+ RDoc::Page::METHOD_LIST)
+ template.write_html_on(f, @values)
+ end
+
+ def value_hash
+ class_attribute_values
+ add_table_of_sections
+
+ @values["charset"] = @options.charset
+ @values["style_url"] = style_url(path, @options.css)
+
+ d = markup( context.comment)
+ @values["description"] = d unless d.empty?
+
+ ml = build_method_summary_list
+ @values["methods"] = ml unless ml.empty?
+
+ il = build_include_list(@context)
+ @values["includes"] = il unless il.empty?
+
+ @values["sections"] = @context.sections.map do |section|
+
+ secdata = {
+ "sectitle" => section.title,
+ "secsequence" => section.sequence,
+ "seccomment" => markup(section.comment)
+ }
+
+ al = build_alias_summary_list(section)
+ secdata["aliases"] = al unless al.empty?
+
+ co = build_constants_summary_list(section)
+ secdata["constants"] = co unless co.empty?
+
+ al = build_attribute_list(section)
+ secdata["attributes"] = al unless al.empty?
+
+ cl = build_class_list(0, @context, section)
+ secdata["classlist"] = cl unless cl.empty?
+
+ mdl = build_method_detail_list(section)
+ secdata["method_list"] = mdl unless mdl.empty?
+
+ secdata
+ end
+
+ @values
+ end
+
+ def build_attribute_list(section)
+ atts = @context.attributes.sort
+ res = []
+ atts.each do |att|
+ next unless att.section == section
+ if att.visibility == :public || att.visibility == :protected || @options.show_all
+ entry = {
+ "name" => CGI.escapeHTML(att.name),
+ "rw" => att.rw,
+ "a_desc" => markup(att.comment, true)
+ }
+ unless att.visibility == :public || att.visibility == :protected
+ entry["rw"] << "-"
+ end
+ res << entry
+ end
+ end
+ res
+ end
+
+ def class_attribute_values
+ h_name = CGI.escapeHTML(name)
+
+ @values["classmod"] = @is_module ? "Module" : "Class"
+ @values["title"] = "#{@values['classmod']}: #{h_name}"
+
+ c = @context
+ c = c.parent while c and !c.diagram
+ if c && c.diagram
+ @values["diagram"] = diagram_reference(c.diagram)
+ end
+
+ @values["full_name"] = h_name
+
+ parent_class = @context.superclass
+
+ if parent_class
+ @values["parent"] = CGI.escapeHTML(parent_class)
+
+ if parent_name
+ lookup = parent_name + "::" + parent_class
+ else
+ lookup = parent_class
+ end
+
+ parent_url = AllReferences[lookup] || AllReferences[parent_class]
+
+ if parent_url and parent_url.document_self
+ @values["par_url"] = aref_to(parent_url.path)
+ end
+ end
+
+ files = []
+ @context.in_files.each do |f|
+ res = {}
+ full_path = CGI.escapeHTML(f.file_absolute_name)
+
+ res["full_path"] = full_path
+ res["full_path_url"] = aref_to(f.viewer.path) if f.document_self
+
+ if @options.webcvs
+ res["cvsurl"] = cvs_url( @options.webcvs, full_path )
+ end
+
+ files << res
+ end
+
+ @values['infiles'] = files
+ end
+
+ def <=>(other)
+ self.name <=> other.name
+ end
+
+ end
+
+ #####################################################################
+ #
+ # Handles the mapping of a file's information to HTML. In reality,
+ # a file corresponds to a +TopLevel+ object, containing modules,
+ # classes, and top-level methods. In theory it _could_ contain
+ # attributes and aliases, but we ignore these for now.
+
+ class HtmlFile < ContextUser
+
+ attr_reader :path
+ attr_reader :name
+
+ def initialize(context, options, file_dir)
+ super(context, options)
+
+ @values = {}
+
+ if options.all_one_file
+ @path = filename_to_label
+ else
+ @path = http_url(file_dir)
+ end
+
+ @name = @context.file_relative_name
+
+ collect_methods
+ AllReferences.add(name, self)
+ context.viewer = self
+ end
+
+ def http_url(file_dir)
+ File.join(file_dir, @context.file_relative_name.tr('.', '_')) +
+ ".html"
+ end
+
+ def filename_to_label
+ @context.file_relative_name.gsub(/%|\/|\?|\#/) {|s| '%' + ("%x" % s[0]) }
+ end
+
+ def index_name
+ name
+ end
+
+ def parent_name
+ nil
+ end
+
+ def value_hash
+ file_attribute_values
+ add_table_of_sections
+
+ @values["charset"] = @options.charset
+ @values["href"] = path
+ @values["style_url"] = style_url(path, @options.css)
+
+ if @context.comment
+ d = markup( context.comment)
+ @values["description"] = d if d.size > 0
+ end
+
+ ml = build_method_summary_list
+ @values["methods"] = ml unless ml.empty?
+
+ il = build_include_list(@context)
+ @values["includes"] = il unless il.empty?
+
+ rl = build_requires_list(@context)
+ @values["requires"] = rl unless rl.empty?
+
+ if @options.promiscuous
+ file_context = nil
+ else
+ file_context = @context
+ end
+
+
+ @values["sections"] = @context.sections.map do |section|
+
+ secdata = {
+ "sectitle" => section.title,
+ "secsequence" => section.sequence,
+ "seccomment" => markup(section.comment)
+ }
+
+ cl = build_class_list(0, @context, section, file_context)
+ @values["classlist"] = cl unless cl.empty?
+
+ mdl = build_method_detail_list(section)
+ secdata["method_list"] = mdl unless mdl.empty?
+
+ al = build_alias_summary_list(section)
+ secdata["aliases"] = al unless al.empty?
+
+ co = build_constants_summary_list(section)
+ @values["constants"] = co unless co.empty?
+
+ secdata
+ end
+
+ @values
+ end
+
+ def write_on(f)
+ value_hash
+ template = TemplatePage.new(RDoc::Page::BODY,
+ RDoc::Page::FILE_PAGE,
+ RDoc::Page::METHOD_LIST)
+ template.write_html_on(f, @values)
+ end
+
+ def file_attribute_values
+ full_path = @context.file_absolute_name
+ short_name = File.basename(full_path)
+
+ @values["title"] = CGI.escapeHTML("File: #{short_name}")
+
+ if @context.diagram
+ @values["diagram"] = diagram_reference( context.diagram)
+ end
+
+ @values["short_name"] = CGI.escapeHTML(short_name)
+ @values["full_path"] = CGI.escapeHTML(full_path)
+ @values["dtm_modified"] = @context.file_stat.mtime.to_s
+
+ if @options.webcvs
+ @values["cvsurl"] = cvs_url( @options.webcvs, @values["full_path"] )
+ end
+ end
+
+ def <=>(other)
+ self.name <=> other.name
+ end
+ end
+
+ #####################################################################
+
+ class HtmlMethod
+ include MarkUp
+
+ attr_reader :context
+ attr_reader :src_url
+ attr_reader :img_url
+ attr_reader :source_code
+
+ @@seq = "M000000"
+
+ @@all_methods = []
+
+ def HtmlMethod::reset
+ @@all_methods = []
+ end
+
+ def initialize(context, html_class, options)
+ @context = context
+ @html_class = html_class
+ @options = options
+ @@seq = seq.succ
+ @seq = @@seq
+ @@all_methods << self
+
+ context.viewer = self
+
+ if (ts = @context.token_stream)
+ @source_code = markup_code(ts)
+ unless @options.inline_source
+ @src_url = create_source_code_file(@source_code)
+ @img_url = HTMLGenerator.gen_url(path, 'source.png')
+ end
+ end
+
+ AllReferences.add(name, self)
+ end
+
+ # return a reference to outselves to be used as an href=
+ # the form depends on whether we're all in one file
+ # or in multiple files
+
+ def as_href(from_path)
+ if @options.all_one_file
+ "#" + path
+ else
+ HTMLGenerator.gen_url(from_path, path)
+ end
+ end
+
+ def name
+ @context.name
+ end
+
+ def section
+ @context.section
+ end
+
+ def index_name
+ "#{ context.name} (#{ html_class.name})"
+ end
+
+ def parent_name
+ if @context.parent.parent
+ @context.parent.parent.full_name
+ else
+ nil
+ end
+ end
+
+ def aref
+ @seq
+ end
+
+ def path
+ if @options.all_one_file
+ aref
+ else
+ @html_class.path + "#" + aref
+ end
+ end
+
+ def description
+ markup( context.comment)
+ end
+
+ def visibility
+ @context.visibility
+ end
+
+ def singleton
+ @context.singleton
+ end
+
+ def call_seq
+ cs = @context.call_seq
+ if cs
+ cs.gsub(/\n/, "<br />\n")
+ else
+ nil
+ end
+ end
+
+ def params
+ # params coming from a call-seq in 'C' will start with the
+ # method name
+ p = @context.params
+ if p !~ /^\w/
+ p = @context.params.gsub(/\s*\#.*/, '')
+ p = p.tr("\n", " ").squeeze(" ")
+ p = "(" + p + ")" unless p[0] == ?(
+
+ if (block = @context.block_params)
+ # If this method has explicit block parameters, remove any
+ # explicit &block
+
+ p.sub!(/,?\s*&\w+/, '')
+
+ block.gsub!(/\s*\#.*/, '')
+ block = block.tr("\n", " ").squeeze(" ")
+ if block[0] == ?(
+ block.sub!(/^\(/, '').sub!(/\)/, '')
+ end
+ p << " {|#{block.strip}| ...}"
+ end
+ end
+ CGI.escapeHTML(p)
+ end
+
+ def create_source_code_file(code_body)
+ meth_path = @html_class.path.sub(/\.html$/, '.src')
+ File.makedirs(meth_path)
+ file_path = File.join(meth_path, @seq) + ".html"
+
+ template = TemplatePage.new(RDoc::Page::SRC_PAGE)
+ File.open(file_path, "w") do |f|
+ values = {
+ 'title' => CGI.escapeHTML(index_name),
+ 'code' => code_body,
+ 'style_url' => style_url(file_path, @options.css),
+ 'charset' => @options.charset
+ }
+ template.write_html_on(f, values)
+ end
+ HTMLGenerator.gen_url(path, file_path)
+ end
+
+ def HtmlMethod.all_methods
+ @@all_methods
+ end
+
+ def <=>(other)
+ @context <=> other.context
+ end
+
+ ##
+ # Given a sequence of source tokens, mark up the source code
+ # to make it look purty.
+
+
+ def markup_code(tokens)
+ src = ""
+ tokens.each do |t|
+ next unless t
+ # p t.class
+# style = STYLE_MAP[t.class]
+ style = case t
+ when RubyToken::TkCONSTANT then "ruby-constant"
+ when RubyToken::TkKW then "ruby-keyword kw"
+ when RubyToken::TkIVAR then "ruby-ivar"
+ when RubyToken::TkOp then "ruby-operator"
+ when RubyToken::TkId then "ruby-identifier"
+ when RubyToken::TkNode then "ruby-node"
+ when RubyToken::TkCOMMENT then "ruby-comment cmt"
+ when RubyToken::TkREGEXP then "ruby-regexp re"
+ when RubyToken::TkSTRING then "ruby-value str"
+ when RubyToken::TkVal then "ruby-value"
+ else
+ nil
+ end
+
+ text = CGI.escapeHTML(t.text)
+
+ if style
+ src << "<span class=\"#{style}\">#{text}</span>"
+ else
+ src << text
+ end
+ end
+
+ add_line_numbers(src) if Options.instance.include_line_numbers
+ src
+ end
+
+ # we rely on the fact that the first line of a source code
+ # listing has
+ # # File xxxxx, line dddd
+
+ def add_line_numbers(src)
+ if src =~ /\A.*, line (\d+)/
+ first = $1.to_i - 1
+ last = first + src.count("\n")
+ size = last.to_s.length
+ real_fmt = "%#{size}d: "
+ fmt = " " * (size+2)
+ src.gsub!(/^/) do
+ res = sprintf(fmt, first)
+ first += 1
+ fmt = real_fmt
+ res
+ end
+ end
+ end
+
+ def document_self
+ @context.document_self
+ end
+
+ def aliases
+ @context.aliases
+ end
+
+ def find_symbol(symbol, method=nil)
+ res = @context.parent.find_symbol(symbol, method)
+ if res
+ res = res.viewer
+ end
+ res
+ end
+ end
+
+ #####################################################################
+
+ class HTMLGenerator
+
+ include MarkUp
+
+ ##
+ # convert a target url to one that is relative to a given
+ # path
+
+ def HTMLGenerator.gen_url(path, target)
+ from = File.dirname(path)
+ to, to_file = File.split(target)
+
+ from = from.split("/")
+ to = to.split("/")
+
+ while from.size > 0 and to.size > 0 and from[0] == to[0]
+ from.shift
+ to.shift
+ end
+
+ from.fill("..")
+ from.concat(to)
+ from << to_file
+ File.join(*from)
+ end
+
+ # Generators may need to return specific subclasses depending
+ # on the options they are passed. Because of this
+ # we create them using a factory
+
+ def HTMLGenerator.for(options)
+ AllReferences::reset
+ HtmlMethod::reset
+
+ if options.all_one_file
+ HTMLGeneratorInOne.new(options)
+ else
+ HTMLGenerator.new(options)
+ end
+ end
+
+ class <<self
+ protected :new
+ end
+
+ # Set up a new HTML generator. Basically all we do here is load
+ # up the correct output temlate
+
+ def initialize(options) #:not-new:
+ @options = options
+ load_html_template
+ end
+
+
+ ##
+ # Build the initial indices and output objects
+ # based on an array of TopLevel objects containing
+ # the extracted information.
+
+ def generate(toplevels)
+ @toplevels = toplevels
+ @files = []
+ @classes = []
+
+ write_style_sheet
+ gen_sub_directories()
+ build_indices
+ generate_html
+ end
+
+ private
+
+ ##
+ # Load up the HTML template specified in the options.
+ # If the template name contains a slash, use it literally
+ #
+ def load_html_template
+ template = @options.template
+ unless template =~ %r{/|\\}
+ template = File.join("rdoc/generators/template",
+ @options.generator.key, template)
+ end
+ require template
+ extend RDoc::Page
+ rescue LoadError
+ $stderr.puts "Could not find HTML template '#{template}'"
+ exit 99
+ end
+
+ ##
+ # Write out the style sheet used by the main frames
+ #
+
+ def write_style_sheet
+ template = TemplatePage.new(RDoc::Page::STYLE)
+ unless @options.css
+ File.open(CSS_NAME, "w") do |f|
+ values = { "fonts" => RDoc::Page::FONTS }
+ template.write_html_on(f, values)
+ end
+ end
+ end
+
+ ##
+ # See the comments at the top for a description of the
+ # directory structure
+
+ def gen_sub_directories
+ File.makedirs(FILE_DIR, CLASS_DIR)
+ rescue
+ $stderr.puts $!.message
+ exit 1
+ end
+
+ ##
+ # Generate:
+ #
+ # * a list of HtmlFile objects for each TopLevel object.
+ # * a list of HtmlClass objects for each first level
+ # class or module in the TopLevel objects
+ # * a complete list of all hyperlinkable terms (file,
+ # class, module, and method names)
+
+ def build_indices
+
+ @toplevels.each do |toplevel|
+ @files << HtmlFile.new(toplevel, @options, FILE_DIR)
+ end
+
+ RDoc::TopLevel.all_classes_and_modules.each do |cls|
+ build_class_list(cls, @files[0], CLASS_DIR)
+ end
+ end
+
+ def build_class_list(from, html_file, class_dir)
+ @classes << HtmlClass.new(from, html_file, class_dir, @options)
+ from.each_classmodule do |mod|
+ build_class_list(mod, html_file, class_dir)
+ end
+ end
+
+ ##
+ # Generate all the HTML
+ #
+ def generate_html
+ # the individual descriptions for files and classes
+ gen_into(@files)
+ gen_into(@classes)
+ # and the index files
+ gen_file_index
+ gen_class_index
+ gen_method_index
+ gen_main_index
+
+ # this method is defined in the template file
+ write_extra_pages if defined? write_extra_pages
+ end
+
+ def gen_into(list)
+ list.each do |item|
+ if item.document_self
+ op_file = item.path
+ File.makedirs(File.dirname(op_file))
+ File.open(op_file, "w") { |file| item.write_on(file) }
+ end
+ end
+
+ end
+
+ def gen_file_index
+ gen_an_index(@files, 'Files',
+ RDoc::Page::FILE_INDEX,
+ "fr_file_index.html")
+ end
+
+ def gen_class_index
+ gen_an_index(@classes, 'Classes',
+ RDoc::Page::CLASS_INDEX,
+ "fr_class_index.html")
+ end
+
+ def gen_method_index
+ gen_an_index(HtmlMethod.all_methods, 'Methods',
+ RDoc::Page::METHOD_INDEX,
+ "fr_method_index.html")
+ end
+
+
+ def gen_an_index(collection, title, template, filename)
+ template = TemplatePage.new(RDoc::Page::FR_INDEX_BODY, template)
+ res = []
+ collection.sort.each do |f|
+ if f.document_self
+ res << { "href" => f.path, "name" => f.index_name }
+ end
+ end
+
+ values = {
+ "entries" => res,
+ 'list_title' => CGI.escapeHTML(title),
+ 'index_url' => main_url,
+ 'charset' => @options.charset,
+ 'style_url' => style_url('', @options.css),
+ }
+
+ File.open(filename, "w") do |f|
+ template.write_html_on(f, values)
+ end
+ end
+
+ # The main index page is mostly a template frameset, but includes
+ # the initial page. If the <tt>--main</tt> option was given,
+ # we use this as our main page, otherwise we use the
+ # first file specified on the command line.
+
+ def gen_main_index
+ template = TemplatePage.new(RDoc::Page::INDEX)
+ File.open("index.html", "w") do |f|
+ values = {
+ "initial_page" => main_url,
+ 'title' => CGI.escapeHTML( options.title),
+ 'charset' => @options.charset
+ }
+ if @options.inline_source
+ values['inline_source'] = true
+ end
+ template.write_html_on(f, values)
+ end
+ end
+
+ # return the url of the main page
+ def main_url
+ main_page = @options.main_page
+ ref = nil
+ if main_page
+ ref = AllReferences[main_page]
+ if ref
+ ref = ref.path
+ else
+ $stderr.puts "Could not find main page #{main_page}"
+ end
+ end
+
+ unless ref
+ for file in @files
+ if file.document_self
+ ref = file.path
+ break
+ end
+ end
+ end
+
+ unless ref
+ $stderr.puts "Couldn't find anything to document"
+ $stderr.puts "Perhaps you've used :stopdoc: in all classes"
+ exit(1)
+ end
+
+ ref
+ end
+
+
+ end
+
+
+ ######################################################################
+
+
+ class HTMLGeneratorInOne < HTMLGenerator
+
+ def initialize(*args)
+ super
+ end
+
+ ##
+ # Build the initial indices and output objects
+ # based on an array of TopLevel objects containing
+ # the extracted information.
+
+ def generate(info)
+ @toplevels = info
+ @files = []
+ @classes = []
+ @hyperlinks = {}
+
+ build_indices
+ generate_xml
+ end
+
+
+ ##
+ # Generate:
+ #
+ # * a list of HtmlFile objects for each TopLevel object.
+ # * a list of HtmlClass objects for each first level
+ # class or module in the TopLevel objects
+ # * a complete list of all hyperlinkable terms (file,
+ # class, module, and method names)
+
+ def build_indices
+
+ @toplevels.each do |toplevel|
+ @files << HtmlFile.new(toplevel, @options, FILE_DIR)
+ end
+
+ RDoc::TopLevel.all_classes_and_modules.each do |cls|
+ build_class_list(cls, @files[0], CLASS_DIR)
+ end
+ end
+
+ def build_class_list(from, html_file, class_dir)
+ @classes << HtmlClass.new(from, html_file, class_dir, @options)
+ from.each_classmodule do |mod|
+ build_class_list(mod, html_file, class_dir)
+ end
+ end
+
+ ##
+ # Generate all the HTML. For the one-file case, we generate
+ # all the information in to one big hash
+ #
+ def generate_xml
+ values = {
+ 'charset' => @options.charset,
+ 'files' => gen_into(@files),
+ 'classes' => gen_into(@classes),
+ 'title' => CGI.escapeHTML( options.title),
+ }
+
+ # this method is defined in the template file
+ write_extra_pages if defined? write_extra_pages
+
+ template = TemplatePage.new(RDoc::Page::ONE_PAGE)
+
+ if @options.op_name
+ opfile = File.open( options.op_name, "w")
+ else
+ opfile = $stdout
+ end
+ template.write_html_on(opfile, values)
+ end
+
+ def gen_into(list)
+ res = []
+ list.each do |item|
+ res << item.value_hash
+ end
+ res
+ end
+
+ def gen_file_index
+ gen_an_index(@files, 'Files')
+ end
+
+ def gen_class_index
+ gen_an_index(@classes, 'Classes')
+ end
+
+ def gen_method_index
+ gen_an_index(HtmlMethod.all_methods, 'Methods')
+ end
+
+
+ def gen_an_index(collection, title)
+ res = []
+ collection.sort.each do |f|
+ if f.document_self
+ res << { "href" => f.path, "name" => f.index_name }
+ end
+ end
+
+ return {
+ "entries" => res,
+ 'list_title' => title,
+ 'index_url' => main_url,
+ }
+ end
+
+ end
+end
Added: trunk/lib/rdoc/generators/ri_generator.rb
===================================================================
--- trunk/lib/rdoc/generators/ri_generator.rb 2006-02-21 22:21:14 UTC (rev 462)
+++ trunk/lib/rdoc/generators/ri_generator.rb 2006-02-22 01:41:14 UTC (rev 463)
@@ -0,0 +1,268 @@
+# We're responsible for generating all the HTML files
+# from the object tree defined in code_objects.rb. We
+# generate:
+#
+# [files] an html file for each input file given. These
+# input files appear as objects of class
+# TopLevel
+#
+# [classes] an html file for each class or module encountered.
+# These classes are not grouped by file: if a file
+# contains four classes, we'll generate an html
+# file for the file itself, and four html files
+# for the individual classes.
+#
+# [indices] we generate three indices for files, classes,
+# and methods. These are displayed in a browser
+# like window with three index panes across the
+# top and the selected description below
+#
+# Method descriptions appear in whatever entity (file, class,
+# or module) that contains them.
+#
+# We generate files in a structure below a specified subdirectory,
+# normally +doc+.
+#
+# opdir
+# |
+# |___ files
+# | |__ per file summaries
+# |
+# |___ classes
+# |__ per class/module descriptions
+#
+# HTML is generated using the Template class.
+#
+
+require 'ftools'
+
+require 'rdoc/options'
+require 'rdoc/template'
+require 'rdoc/markup/simple_markup'
+require 'rdoc/markup/simple_markup