[前][次][番号順一覧][スレッド一覧][生データ]

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(/->/, '&rarr;')
+              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 = "&nbsp;&nbsp;::" * 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