require 'rdoc/ri/ri_driver'
require 'rexml/document'
begin
  if $ihelp_full_text_search != false
    require 'rubygems'
    require 'ferret'
    require 'stringio'
    require 'fileutils'
    $ihelp_full_text_search = true
  end
rescue Exception
  $ihelp_full_text_search = false
end


# Ri bindings for interactive use from within Ruby.
# Does a bit of second-guessing (Instance method? Class method?
# Try both unless explicitly defined. Not found in this class? Try the
# ancestor classes.)
#
# The goal is that help is given for all methods that have help.
#
# Examples:
#
#   require 'ihelp'
#
#   a = "string"
#   a.help
#   a.help :reverse
#   a.help :map
#   String.help
#   String.help :new
#   String.help :reverse
#   String.help :map
#   String.instance_help :reverse
#   String.instance_help :new # => No help found.
#   a.help :new
#   ihelp "String#reverse"
#   ihelp "String.reverse"
#   a.method(:reverse).help # gets help for Method
#   ihelp "Hash#map"
#
# You can also search for terms in the documentation:
#
#   ihelp 'domain name lookup'
#   String.help 'byte order'
#
#
# Custom help renderers:
#
# The help-method calls IHelp::Renderer's method defined by
# IHelp.renderer with the RI info object. You can print help
# out the way you want by defining your own renderer method
# in IHelp::Renderer and setting IHelp.renderer to the name
# of the method.
#
# Example:
#
#   require 'ihelp'
#
#   IHelp.renderers
#   # => ["emacs", "rubydoc", "ri", "source", "html"]
#
#   class IHelp::Renderer
#     def print_name(info)
#       puts info.full_name
#     end
#   end
#
#   IHelp.renderers
#   # => ["emacs", "rubydoc", "ri", "source", "print_name", "html"]
#
#   IHelp.renderer = :print_name
#
#   [1,2,3].help:reject
#   # Array#reject
#   # => nil
#
# The current renderers are:
#
#   ri -- the default renderer
#   html -- creates a HTML document for the help and opens it
#           with the program named in IHelp.web_browser
#   rubydoc -- opens the corresponding www.ruby-doc.org class
#              documentation page with the program named in
#              IHelp.web_browser
#   emacs -- uses gnudoit and ri-emacs to display help in an Emacs buffer.
#            The Emacs commands that I got it running with were:
#               ;; make ri-emacs autoload according to its instructions
#               M-x ruby-mode
#               M-x gnuserv-start
#               M-x run-ruby
#               > IHelp.renderer = :emacs
#               > String.help:center
#   source -- uses RubyToRuby to print the source for the method
#             (experimental)
#
#
# Changelog:
#
# 0.4.5
#   Use RI environment variable to pass config to ri (thanks to Parragh Szabolcs.)
#   Bugfix for missing stringio (thanks to Marcel M. Cary.)
#   Show first thousand hits instead of a mere ten.
#
# 0.4.0
#   Full-text documentation search using Ferret.
#
# 0.3.2
#   Added support for ruby 1.8.5, added emacs renderer from
#   rubykitch <rubykitch@ruby-lang.org>, made source renderer use the
#   released version of RubyToRuby.
#
# License: Ruby's
#
# Author: Ilmari Heikkinen <kig misfiring net>
#
module IHelp
  HELP_VERSION = "0.4.5"
end


module IHelp

  # Returns list of available renderers.
  def self.renderers
    Renderer.instance_methods(false)
  end

  # Contains the help renderer methods to be used by IHelp#help.
  # The help-method creates a new instance of Renderer and calls
  # the method defined by IHelp.renderer with the RI info object.
  #
  class Renderer

    # Default renderer method, opens the help using the IHelpDriver
    # gotten from IHelp.ri_driver.
    #
    def ri(info)
      IHelp.ri_driver.display_info(info)
    end

    # Opens the class documentation page on www.ruby-doc.org using
    # the program defined in IHelp::Renderer.web_browser.
    #
    def rubydoc(info)
      require 'uri'
      class_name = parse_ruby_doc_url(info.full_name)
      puts "Opening help for: #{class_name.gsub(/\//,"::")}"
      system(IHelp.web_browser, "http://www.ruby-doc.org/core/classes/#{class_name}.html")
    end

    # Show sources -renderer using RubyToRuby.
    #
    # http://seattlerb.rubyforge.org/
    #
    # sudo gem install ruby2ruby
    #
    def source(info)
      require 'ruby2ruby'
      class_name = info.full_name.split(/[#\.]/).first
      klass = class_name.split("::").inject(Object){|o,i| o.const_get(i)}
      args = [klass]
      args << info.name if info.is_a? RI::MethodDescription
      puts RubyToRuby.translate(*args)
    end

    # XEmacs renderer.
    # Uses ri-emacs to show the ri output in Emacs.
    #
    # http://rubyforge.org/projects/ri-emacs/
    # http://www.rubyist.net/~rubikitch/computer/irbsh/index.en.html
    #
    def emacs(info)
      system "gnudoit", %Q[(progn (ri "#{info.full_name}") "#{info.full_name}")]
    end

    def html(info)
      puts "Opening help for: #{info.full_name}"
      doc = REXML::Document.new
      root = doc.add_element("html")
      head = root.add_element("head")
      title = head.add_element("title")
      title.add_text("#{info.full_name} - RI Documentation")
      body = root.add_element("body")
      body.add_element(info.to_html.root)
      tmp = Tempfile.new("#{info.full_name.gsub(/\W/,"_")}_doc.html")
      tmp.write( doc.to_s(2) )
      tmp.flush
      pid = fork{
        system(IHelp.web_browser, "file://#{tmp.path}")
        tmp.close!
      }
      Process.detach(pid)
      pid
    end

  private
    def parse_ruby_doc_url(item_name)
      item_name.split(/\.|#/,2).first.gsub(/::/,"/")
    end

  end


  # Print out help for self.
  #
  #   If method_name is given, prints help for that method.
  #   If instance is true, tries to find help only for the instance method.
  #   If instance is false, tries to find help for the object's method only.
  #   If instance is nil, checks object's method first, then instance method.
  #
  # Uses help_description(method_name, instance).
  #
  def help(method_name=nil, instance=nil)
    if $ihelp_full_text_search and (
        method_name and method_name.class == String and
        self.class == Object and to_s == 'main' and
        not method_name =~ /::|\.|#/ and
        not method_name =~ /\A[A-Z][a-z0-9A-Z]*\Z/
      ) # calling for main
      IHelp.search method_name
      return
    end
    if $ihelp_full_text_search and method_name and method_name.to_s.include? " " # phrase search
      puts "Searching from docs..."
      help_search method_name
      return
    end
    info = help_description(method_name, instance)
    if not info
      if $ihelp_full_text_search and method_name
        puts "No help found for method of that name, searching from docs..."
        help_search method_name
      else
        puts "No help found."
      end
      return
    end
    IHelp.render(info)
  end

  alias_method :ihelp, :help

  # Print out help for instance method method_name.
  # If no method_name given, behaves like #help.
  #
  def instance_help(method_name = nil)
    help(method_name, true)
  end

  # Returns help string in YAML for self.
  #
  #   If method_name is given, prints help for that method.
  #   If instance is true, tries to find help only for the instance method.
  #   If instance is false, tries to find help for the object's method only.
  #   If instance is nil, checks object's method first, then instance method.
  #   Returns nil if there is no help to be found.
  #
  def help_yaml(method_name=nil, instance=nil)
    info = help_description(method_name, instance)
    info.to_yaml if info
  end

  # Returns help string as a HTML REXML::Document with a DIV element as the root.
  #
  #   If method_name is given, prints help for that method.
  #   If instance is true, tries to find help only for the instance method.
  #   If instance is false, tries to find help for the object's method only.
  #   If instance is nil, checks object's method first, then instance method.
  #   Returns nil if there is no help to be found.
  #
  def help_html(method_name=nil, instance=nil)
    info = help_description(method_name, instance)
    info.to_html if info
  end

  # Return RI::ClassDescription / RI::MethodDescription for self
  # or its method meth, or its instance method meth if instance == true.
  #
  def help_description(method_name=nil, instance=nil)
    IHelp.generate_help_description(self, method_name, instance)
  end

  # Searches for str in the documentation of this object.
  #
  def help_search(str)
    raise "No search capability, do you have ferret installed? (gem install ferret)" unless $ihelp_full_text_search
    ms = if is_a? Module
      instance_methods.map{|im| instance_method im } +
      methods.map{|m| method m }
    else
      methods.map{|m| method m }
    end
    phrases = ms.map do |m|
      mod, met = m.inspect.split(" ",2)[1][0..-2].split(/#|\./)
      rmod = mod.scan(/\(([^\)]+)\)/).flatten[0]
      rmod ||= mod
      rmod.gsub(/[^a-z0-9]/i){|c| "\\"+c }
    end.uniq
    phrases.delete ""
    name_query = phrases.join(" OR ")
    query = "(name:(#{name_query})) AND (*:#{IHelp.intersect_search_query str})"
    IHelp.search query, false
  end


  class << self

    # Help renderer to use.
    attr_accessor :renderer

    # Web browser to use for html and rubydoc renderers.
    attr_accessor :web_browser

    # Don't use colors for highlighting search results.
    attr_accessor :no_colors

    IHelp.renderer ||= :ri
    IHelp.web_browser ||= 'firefox'

    # Searches for str from available documentation and prints out the
    # results.
    #
    # Creates a search index if you don't already have one. Creating the index
    # may take a couple of minutes.
    #
    # See IHelpIndex.
    #
    def search(str, escape_query=true)
      raise "No search capability, do you have ferret installed? (gem install ferret)" unless $ihelp_full_text_search
      if escape_query
        help_index.search(intersect_search_query(str.to_s))
      else
        help_index.search(str)
      end
      nil
    end

    def intersect_search_query(str)
      a = IHelpAnalyzer.new
      t = a.token_stream :content, str.to_s
      c = []
      n = nil
      c << n.text while n = t.next
      "(#{c.join(" AND ")})"
    end

    attr_accessor :help_index
    def help_index
      @help_index ||= IHelpIndex.new
    end

    # Render the RI info object a renderer method in IHelp::Renderer.
    # The name of the renderer method to use is returned by IHelp.renderer,
    # and can be set with IHelp.renderer=.
    #
    def render(info)
      IHelp::Renderer.new.send(renderer, info)
    end

    def ri_driver
      @ri_driver ||= IHelpDriver.new
    end

    # Return RI::ClassDescription / RI::MethodDescription for klass
    # or its method meth, or its instance method meth if instance == true.
    #
    def generate_help_description(klass, meth=nil, instance=nil)
      meth_str = nil
      double_colon = false
      if meth
        meth_str = meth.to_s
        if /::|\.|#/ === meth_str # called with e.g."Array#str","String.new"
          meth_str, klass_name, instance_help, double_colon =
            get_help_klass_info_for_name(meth_str)
            klass_ancs = find_ancestors(klass_name, instance)
        elsif (/\A[A-Z][a-zA-Z0-9_]*\Z/ === meth_str and # called with e.g. "Array"
               not (klass.methods+Kernel.methods).include? meth_str)
          meth_str, klass_name, instance_help, double_colon =
            get_help_klass_info_for_name(meth_str)
            klass_ancs = find_ancestors(klass_name, instance)
        else
          klass_name, klass_ancs, instance_help =
            get_help_klass_info(klass, instance)
        end
      else
        klass_name, klass_ancs, instance_help =
          get_help_klass_info(klass, instance)
      end
      info = get_help_info(meth_str, klass_name, klass_ancs, instance_help,
                           instance)
      # Retry with method as class if double_colon-splitted and no info
      if info.nil? and double_colon
        klass_name = [klass_name, meth_str].join("::")
        meth_str = nil
        klass_ancs = find_ancestors(klass_name, instance)
        info = get_help_info(
                 meth_str, klass_name, klass_ancs, instance_help, instance)
      end
      info
    end

  private
    def get_help_klass_info(klass,instance)
        if klass.is_a? Class or klass.is_a? Module
          klass_ancs = klass.ancestors + klass.class.ancestors
          klass_ancs -= [klass, klass.class]
          instance = false if instance.nil?
        # If we are an instance, set klass to our class
        #
        else
          klass = klass.class
          klass_ancs = klass.ancestors - [klass]
          instance = true if instance.nil?
        end
        klass_name = klass.name
        [klass_name, klass_ancs, instance]
    end

    def get_help_klass_info_for_name(meth_str)
      double_colon = false
      # Maybe we are being called with something like "Array#slice"
      if /#/ === meth_str
        klass_name, meth_str = meth_str.split(/#/, 2)
        instance = true

      # Or maybe the requested item is "Ri::RiDriver.new"
      elsif /\./ === meth_str
        klass_name, meth_str = meth_str.reverse.split(/\./, 2).
                                        reverse.map{|i| i.reverse}
        instance = false

      # And the problematic case of "Test::Unit" (is Unit a class name or
      # a method name? Why does Ri even care?)
      else
        klass_name, meth_str = meth_str.reverse.split(/::/, 2).
                                        reverse.map{|i| i.reverse}
        double_colon = true
        instance = false
      end
      [meth_str, klass_name, instance, double_colon]
    end

    # Find ancestors for klass_name (works if the class has been loaded)
    def find_ancestors(klass_name, instance)
      similarily_named_class = nil
      ObjectSpace.each_object(Class){|k|
        similarily_named_class = k if k.name == klass_name
        break if similarily_named_class
      }
      if similarily_named_class
        klass_ancs = similarily_named_class.ancestors
        klass_ancs += similarily_named_class.class.ancestors unless instance
      else
        klass_ancs = []
      end
      klass_ancs
    end

    def get_help_info(meth_str, klass_name, klass_ancs, instance_help, instance)
      info = get_help_info_str(meth_str, klass_name, klass_ancs, instance_help)
      # If instance is undefined, try both the class methods and instance
      # methods.
      if info.nil? and instance.nil?
        info = get_help_info_str(
                 meth_str, klass_name, klass_ancs, (not instance_help))
      end
      info
    end

    def get_help_info_str(meth_str, klass_name, klass_ancs, instance)
        info_str = ri_driver.get_info_str(klass_name, meth_str, instance)
        if not info_str
          # Walk through class hierarchy to find an inherited method
          ancest = klass_ancs.find{|anc|
            info_str = ri_driver.get_info_str(anc.name, meth_str, instance)
          }
          # Avoid returning Object in case of no help.
          if ((ancest == Object and meth_str.nil? and klass_name != 'Object') or 
              (ancest == IHelp and meth_str.nil? and klass_name != 'IHelp'))
            info_str = nil
          end
        end
        info_str
    end
  end


  # Version of RiDriver that takes its options
  # as parameter to #initialize.
  #
  class IHelpDriver < RiDriver

    attr_accessor :ri_reader, :display

    # Create new IHelpDriver, with the given args
    # passed to @options, which is a RI::Options.instance
    #
    def initialize(args = (ENV["RI"] || "").split)
      @options = RI::Options.instance
      @options.parse(args)

      paths =  (if RUBY_VERSION > "1.8.4"
                  @options.doc_dir
                else
                  @options.paths
                end) || RI::Paths::PATH
      if paths.empty?
        report_missing_documentation(paths)
      end
      @ri_reader = RI::RiReader.new(RI::RiCache.new(paths))
      @display   = @options.displayer
    end

    # Get info string from ri database for klass_name [method_name]
    #
    def get_info_str(klass_name, method_name = nil, instance = false)
      is_class_method = (not instance)
      top_level_namespace = @ri_reader.top_level_namespace
      namespaces = klass_name.split(/::/).inject(top_level_namespace){
        |ns, current_name|
        @ri_reader.lookup_namespace_in(current_name, ns)
      }
      return nil if namespaces.empty?
      if method_name.nil?
        get_class_info_str(namespaces, klass_name)
      else
        methods = @ri_reader.find_methods(
                    method_name, is_class_method, namespaces)
        return nil if methods.empty?
        get_method_info_str(method_name, methods)
      end
    end

    # Display the info based on if it's
    # for a class or a method. Using ri's pager.
    #
    def display_info(info)
      case [info.class] # only info.class doesn't work
      when [RI::ClassDescription]
        @display.display_class_info(info, @ri_reader)
      when [RI::MethodDescription]
        @display.display_method_info(info)
      end
    end

    # Get info for the class in the given namespaces.
    #
    def get_class_info_str(namespaces, klass_name)
      return nil if namespaces.empty?
      klass_name_last = klass_name.split("::").last
      klass = nil
      namespaces.find{|ns|
        begin
          ns.name == klass_name_last and
          klass = @ri_reader.get_class(ns)
        rescue TypeError
          nil
        end
      }
      klass
    end

    # Get info for the method in the given methods.
    #
    def get_method_info_str(requested_method_name, methods)
      entries = methods.find_all {|m| m.name == requested_method_name}
      return nil if entries.empty?
      method = nil
      entries.find{|entry| method = @ri_reader.get_method(entry)}
      method
    end

  end


  # IHelpIndex uses Ferret to index all available RI documentation
  # and lets you do full text searches over the index.
  #
  # E.g. IHelpIndex.new.search("domain name lookup")
  #
  # See Ferret::QueryParser for query string format.
  #
  class IHelpIndex

    class << self
      attr_accessor :reindex_when_needed
    end

    # The search index, is a Ferret::Index::Index
    attr_accessor :index

    attr_accessor :log_level

    # Default place to save and load the index from.
    GLOBAL_INDEX_PATH = File.join(File.dirname(__FILE__), "ihelp.index")
    LOCAL_INDEX_PATH = File.join(ENV["HOME"], ".ihelp.index")
    DEFAULT_LOG_LEVEL = 2

    def initialize(use_global_index = (ENV["USER"] == "root"))
      @log_level = DEFAULT_LOG_LEVEL
      if use_global_index
        @index_path = GLOBAL_INDEX_PATH
      else
        @index_path = LOCAL_INDEX_PATH
        if not File.exist?(@index_path) and File.exist?(GLOBAL_INDEX_PATH)
          FileUtils.cp_r(GLOBAL_INDEX_PATH, @index_path) rescue nil
        elsif not File.exist?(@index_path)
          self.class.reindex_when_needed = true
        end
      end
      analyzer = IHelpAnalyzer.new
      have_index = File.exist? @index_path
      if have_index
        log "Loading existing index from #{@index_path}", 1
        @index = Ferret::I.new(
          :key => :full_name,
          :path => @index_path,
          :analyzer => analyzer
        )
      else
        log "Creating new index in #{@index_path}", 2
        field_infos = Ferret::Index::FieldInfos.new(:term_vector => :no)
        field_infos.add_field(:name,
                          :store => :yes,
                          :index => :yes,
                          :boost => 10.0)
        field_infos.add_field(:full_name,
                          :store => :yes,
                          :index => :untokenized,
                          :boost => 0.0)
        field_infos.add_field(:content,
                          :boost => 1.0,
                          :store => :yes,
                          :index => :yes,
                          :term_vector => :with_positions_offsets)
        @index = Ferret::I.new(
          :key => :full_name,
          :field_infos => field_infos,
          :analyzer => analyzer
        )
      end
      if self.class.reindex_when_needed and need_reindexing?
        reindex!
      end
      unless have_index
        @index.persist(@index_path)
        log "\nSaved index.", 2
      end
    end

    def log str, level = 1
      STDERR.puts str if level >= @log_level
    end

    def path
      @index_path
    end

    def time
      t = Time.now
      rv = yield
      log "Took #{Time.now - t}"
      rv
    end

    # Returns true if there already is an object named full_name
    #
    def already_indexed? full_name
      @index[full_name]
    end

    def format_time(t)
      sec = t % 60
      min = (t / 60) % 60
      hour = (t / 3600)
      str = sec.to_i.to_s.rjust(2,'0')
      str = min.to_i.to_s.rjust(2,'0') + ":" + str
      str = hour.to_i.to_s.rjust(2,'0') + ":" + str if hour >= 1
      str
    end

    def all_names
      IHelp.ri_driver.ri_reader.all_names.uniq
    end

    def need_reindexing?
      all_names.size > @index.size
    end

    # Reindexes any non-indexed help documents.
    #
    def reindex!
      start_size = @index.size
      names = all_names
      log "Indexing...", 2
      nsz = names.size.to_s
      i = 0
      names.each{|n|
        i += 1
        next if (start_size > 0) and (already_indexed?(n))
        if @log_level == DEFAULT_LOG_LEVEL
          amt_done = (i.to_f / names.size)
          pct = ("%.1f" % [100*amt_done]).rjust(5)
          STDERR.write "\r #{pct}% (#{i.to_s.rjust(nsz.size)}/#{nsz}) #{n}".ljust(80)[0,80]
          STDERR.flush
        end
        hd = if n.include? "#"
          IHelp.ri_driver.get_info_str(*(n.split("#") + [true]))
        elsif n.include? "::"
          IHelp.ri_driver.get_info_str(n) or
          IHelp.ri_driver.get_info_str(*n.reverse.split("::",2).reverse.map{|r| r.reverse })
        else
          IHelp.ri_driver.get_info_str n
        end
        if hd.nil?
          log "skipping #{n} (not found)"
          next
        else
          log "indexing #{n}"
        end
        @index << {
          :name => hd.full_name,
          :full_name => n,
          :content => wrap_str(hd.to_text)
        }
      }
      if @log_level == DEFAULT_LOG_LEVEL
        amt_done = (i.to_f / names.size)
        pct = ("%.1f" % [100*amt_done]).rjust(5)
        STDERR.write "\r #{pct}% (#{i.to_s.rjust(nsz.size)}/#{nsz}) #{names.last}".ljust(80)[0,80]
        STDERR.flush
        STDERR.puts
      end
      if start_size != @index.size
        log "Optimizing index... (old size #{start_size}, current size #{@index.size})"
        @index.optimize
      end
    end

    def display
      return @display if @display
      @display = IHelp.ri_driver.display
      if ENV["PAGER"].to_s.size > 0
        unless ENV["PAGER"] =~ /(^|\/)less\b.* -[a-zA-Z]*[rR]/
          IHelp.no_colors = true
        end
      else
        ENV["PAGER"] = "less -R"
      end
      class << @display
        public :page
        attr_accessor :formatter
      end
      @display
    end

    def page(*a,&b)
      display.page(*a,&b)
    end

    def wrap(str)
      display.formatter.wrap(str)
    end

    def wrap_str(str)
      $stdout = StringIO.new
      wrap(str)
      $stdout.rewind
      s = $stdout.read
      $stdout = STDOUT
      s
    end

    def init_display
      display
    end

    # Searches for the terms in query and prints out first ten hits
    #
    # See Ferret::QueryParser for query string format.
    #
    def search(query)
      result = @index.search(query, :limit => 1000)
      init_display
      if IHelp.no_colors or `which less`.strip.empty?
        pre_tag = "<<"
        post_tag = ">>"
      else
        name_start = "\033[32m"
        name_end = "\033[0m"
        pre_tag = "\033[36m"
        post_tag = "\033[0m"
      end
      page do
        puts
        puts "=== #{result.total_hits} hits#{", showing first 1000" if result.total_hits > 1000} ".ljust(72,"=")
        puts
        result.hits.each_with_index do |hit, i|
          id, score = hit.doc, hit.score
          #puts hit.score
          puts "#{name_start}#{@index[id][:full_name]}#{name_end}"
          puts
          puts @index.highlight(query, id, :field => :content,
                                :pre_tag => pre_tag,
                                :post_tag => post_tag,
                                :ellipsis => "...\n").to_s
          puts
          display.formatter.draw_line
          puts
        end
        puts "#{result.total_hits} hits"
      end
      p result.hits.map{|hit| @index[hit.doc][:full_name] }
      result
    end

  end


  if defined? Ferret
    class IHelpAnalyzer < Ferret::Analysis::StandardAnalyzer
      def token_stream(*a)
        Ferret::Analysis::StemFilter.new(super)
      end
    end
  end


end


module RI

  class MethodDescription

    # Creates HTML element from the MethodDescription.
    # Uses container_tag as the root node name and header_tag
    # as the tag for the header element that contains the method's name.
    #
    # Returns a REXML document with container_tag as the root element name.
    #
    def to_html(container_tag="div", header_tag="h1")
      doc = REXML::Document.new
      root = doc.add_element(container_tag)
      header = root.add_element(header_tag)
      header.add_text(full_name)
      comment.each{|c|
        root.add_element( c.to_html.root )
      }
      doc
    end

    def to_text
      name + " -- " + params.to_s + block_params.to_s + "\n" +
      (comment || []).map{|c| c.to_text }.join("\n")
    end

  end


  class ClassDescription

    # Creates HTML element from the ClassDescription.
    # Uses container_tag as the root node name and header_tag
    # as the tag for the header element that contains the classes name.
    # Uses methods_header_tag as the tag for the "Class/Instance Methods"
    # method list headers.
    # Uses methods_tag as the tag for the method lists.
    #
    # Returns a REXML document with container_tag as the root element name.
    #
    def to_html(container_tag="div", header_tag="h1",
                methods_header_tag="h2", methods_tag="p")
      doc = REXML::Document.new
      root = doc.add_element(container_tag)
      header = root.add_element(header_tag)
      header.add_text(full_name)
      comment.each{|c|
        root.add_element( c.to_html.root )
      }
      root.add_element(methods_header_tag).add_text("Class Methods")
      cmethods = root.add_element(methods_tag)
      class_methods[0...-1].each{|m|
        cmethods.add(m.to_html.root)
        cmethods.add_text(", ")
      }
      cmethods.add(class_methods.last.to_html.root)
      root.add_element(methods_header_tag).add_text("Instance Methods")
      imethods = root.add_element(methods_tag)
      instance_methods[0...-1].each{|m|
        imethods.add(m.to_html.root)
        imethods.add_text(", ")
      }
      imethods.add(instance_methods.last.to_html.root)
      doc
    end

    def to_text
      segs = [full_name]
      segs += (comment || []).map{|c| c.to_text }
      segs << "" << "Class methods: " << (class_methods || []).map{|m| m.to_text }.join(", ")
      segs << "" << "Instance methods: " << (instance_methods || []).map{|m| m.to_text }.join(", ")
      segs << "" << "Constants: " << (constants || []).map{|m| m.to_text }.join("\n")
      segs << "" << "Attributes: " << (attributes || []).map{|m| m.to_text }.join("\n")
      segs.join("\n")
    end

  end


  class Attribute

    def to_text
      "#{name} [#{rw}] #{(comment || []).map{|c| c.to_text }.join("\n")}"
    end

  end


  class Constant

    def to_text
      "#{name} = #{value} #{(comment || []).map{|c| c.to_text }.join("\n")}"
    end

  end


  class MethodSummary

    # Creates HTML element from the ClassDescription.
    # Puts the method's name inside the tag named in
    # container_tag.
    #
    # Returns a REXML document with container_tag as the root element name.
    #
    def to_html(container_tag="em")
      doc = REXML::Document.new
      doc.add_element(container_tag).add_text(name)
      doc
    end

    def to_text
      name
    end

  end


end


module SM::Flow
  constants.each{|c|
    const_get(c).__send__(:include, self)
  }

  def to_text
    RI::TextFormatter.allocate.conv_html(
      if respond_to? :body
        body
      else
        ""
      end +
      if respond_to? :contents
        contents.map{|c| c.to_text }.join("\n\n")
      else
        ""
      end
    ).gsub(/<\/?..>/, '')
  end

  def to_html
    tag = self.class.to_s.split("::").last
    tag = "PRE" if tag == "VERB"
    xmlstr = %Q(<#{tag}>
      #{body if respond_to? :body}
      #{contents.map{|c|c.to_html} if respond_to? :content}
    </#{tag}>)
    REXML::Document.new(xmlstr)
  end

end


class Object
  include IHelp
  extend IHelp
end


if __FILE__ == $0
  require 'test/unit'

  # to get around rdoc documenting NoHelp
  eval("module NoHelp; end")

  class HelpTest < Test::Unit::TestCase

    def no_warn
      old_w = $-w
      $-w = nil
      yield
      $-w = old_w
    end

    def setup
      no_warn{
        Object.const_set("ARGV", ["--readline", "--prompt-mode", "simple"])
      }
      IHelp.instance_variable_set(
        :@ri_driver,
        IHelp::IHelpDriver.new([]))
    end

    def test_simple_help
      assert("string".help_yaml)
    end

    def test_method_help
      assert("string".help_yaml(:reverse))
    end

    def test_inherited_method_help
      assert("string".help_yaml(:map))
    end

    def test_class_help
      assert(String.help_yaml)
    end

    def test_class_method_help
      assert(String.help_yaml(:new))
    end

    def test_class_inherited_method_help
      assert(String.help_yaml(:map))
    end

    def test_method_equalities
      assert(String.help_yaml(:new) ==
             "string".help_yaml(:new))
      assert(String.help_yaml(:reverse) ==
             "string".help_yaml(:reverse))
    end

    def test_method_constraints
      assert((not "string".help_yaml(:new,true)))
      assert((not "string".help_yaml(:reverse,false)))
      assert((not String.help_yaml(:new,true)))
      assert((not String.help_yaml(:reverse,false)))
    end

    def test_help_yamlings
      assert("string".help_yaml(:reverse) ==
             help_yaml("String#reverse"))
      assert(String.help_yaml(:new) ==
             help_yaml("String::new"))
    end

    def test_multipart_namespaces
      assert(Test::Unit.help_yaml)
      assert(help_yaml("Test::Unit"))
      assert(Test::Unit.help_yaml("run?"))
      assert(help_yaml("Test::Unit.run?"))
      assert(help_yaml("Test::Unit::run?"))
      assert(help_yaml("Test::Unit#run?"))
    end

    def test_not_found
      assert((NoHelp.help_yaml == nil))
      assert((String.help_yaml(:nonexistent) == nil))
    end

  end


end

