
# # 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. 
#
# 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  = "stylesheet.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=\"index.html?a=#{ref.aref}&name=#{name}\">#{name}</a>"
            else
                name #it does not need to be a link
            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"
                url = path
            end

            if (type == "http" || type == "link") && url =~ /\.(gif|png|jpg|jpeg|bmp)$/
                "<img src=\"#{url}\">"
            elsif (type == "http" || type == "link")
                "<a href=\"#{url}\" target=\"_blank\">#{text}</a>"
            else
                "<a href=\"#\" onclick=\"jsHref('#{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


        def style_url(path, css_name=nil)
            css_name ||= CSS_NAME      
        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 # Where's the DRY in this?? Put this in the template where it belongs
       def href(link, cls, name)
         %{"<a href=\"#\" onclick=\"jsHref('#{link}');\" class=\"#{cls}\">#{name}</a>"}
       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" => meth.aref,
                    "href" => meth.path
                }
            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 = ref.path
                    res << { "name" => h_name, "href" => path }
                else
                    res << { "name" => h_name, "href" => "" }
                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["href"]        = m.path
                            row["m_seq"]       = m.seq
                            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,
                                        'href' => other.viewer.path,
                                        'aref'  => other.viewer.aref
                                    } 
                                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(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(cls.viewer.path, "link", cls.full_name) <<
                    "<br />\n" <<
                    build_class_list(level + 1, cls, section, infile)
                end
            end

            res
        end

        def document_self
            @context.document_self
        end

        def diagram_reference(diagram)
            res = diagram.gsub(/((?:src|href)=")(.*?)"/) {
                $1 + $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

        @@c_seq = "C00000000"

        attr_reader :path

        def initialize(context, html_file, prefix, options)
            super(context, options)
            @@c_seq  = @@c_seq.succ
            @c_seq   = @@c_seq
            @html_file = html_file
            @is_module = context.is_module?
            @values    = {}

            context.viewer = self

            @path = http_url(context.full_name, prefix)

            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 seq
            @c_seq
        end

        def aref
            @c_seq
        end

        def scope
            a = @context.full_name.split("::")
            if a.length > 1
                a.pop
                a.join("::")
            else
                "" 
            end
        end

        def name
            @context.full_name.gsub("#{scope}::", '')
        end

        def full_name
            @context.full_name
        end

        def parent_name
            @context.parent.full_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)

            # Convert README to html
            unless File.exist?('files/README.html')
              File.open('files/README.html', 'w') do |file|
                file << markup(File.read(File.expand_path(@options.main_page)))
              end
            end

            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
            @values["class_seq"] = seq
            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"] = 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"] = 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

        @@f_seq = "F00000000"

        attr_reader :path
        attr_reader :name

        def initialize(context, options, file_dir)
            super(context, options)
            @@f_seq  = @@f_seq.succ
            @f_seq   = @@f_seq
            @values = {}

            @path = http_url(file_dir)
            @source_file_path = File.expand_path(@context.file_relative_name).gsub("\/doc\/", "/")
            @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 seq
            @f_seq
        end

        def aref
            @f_seq
        end

        def name
            full_path = @context.file_absolute_name
            short_name = File.basename(full_path)
        end

        def full_name
            @context.file_absolute_name
        end

        def scope
            @context.file_relative_name.gsub(/\/#{name}$/, '')
        end

        def parent_name
            nil
        end

        def full_file_source
            ret_str = ""
            File.open(@source_file_path, 'r') do |f| 
                while(!f.eof?) do
                    ret_str += f.readline()
                end
            end
            ret_str
        rescue
            "file not found -#{@source_file_path}-"
            #@source_file_path
        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)
            @values["file_seq"] =  seq

            #pulling in the source for this file
            #@values["source_code"] = @context.token_stream

            @values["file_source_code"] = CGI.escapeHTML(full_file_source)

            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?


            file_context = @context

            @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::SRC_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

        @@m_seq = "M000000"

        @@all_methods = []

        def HtmlMethod::reset
            @@all_methods = []
        end

        def initialize(context, html_class, options)
            @context    = context
            @html_class = html_class
            @options    = options
            @@m_seq       = @@m_seq.succ
            @m_seq        = @@m_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 = MERBGenerator.gen_url(path, 'source.png')
                #end
            end
            AllReferences.add(name, self)
        end

        def seq
            @m_seq
        end

        def aref
            @m_seq
        end

        def scope
            @html_class.full_name
        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 name
            @context.name
        end

        def section
            @context.section
        end

        def parent_name
            if @context.parent.parent
                @context.parent.parent.full_name
            else
                nil
            end
        end

        def path
            @html_class.path
        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(name),
                    'code'      => code_body,
                    'style_url' => style_url(file_path, @options.css),
                    'charset'   => @options.charset
                }
                template.write_html_on(f, values)
            end
            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)
            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 MERBGenerator

        include MarkUp

        # Generators may need to return specific subclasses depending
        # on the options they are passed. Because of this
        # we create them using a factory

        def MERBGenerator.for(options)
            AllReferences::reset
            HtmlMethod::reset

            MERBGenerator.new(options)

        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
            write_javascript
            gen_sub_directories()
            build_indices
            generate_html
        end

        private

        ##
        # Load up the AJAX 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 AJAX 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)
            File.open(CSS_NAME, "w") do |f|
                values = { "font" => "helvetica"} #this is not used anywhere but the template function demands a hash of values
                template.write_html_on(f, values)
            end
        end
        
        def write_javascript
            #Argh... I couldn't figure out how to copy these from the template dir so they were copied into
            # the template file "ajax.rb" and processed similarlly to the style sheets. Not exactly a good thing to do with
            # external library code. Not very DRY.
                
            File.open("api_grease.js", "w") do |f|
                f << RDoc::Page::API_GREASE_JS
            end
            
            File.open("prototype.js", "w") do |f|
                f << RDoc::Page::PROTOTYPE_JS
            end
            
        rescue LoadError
            $stderr.puts "Could not find AJAX template"
            exit 99
        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.name, "scope" => f.scope, "seq_id" => f.seq }
                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|
                tStr = ""
                #File.open(main_url, 'r') do |g|
                #    tStr = markup(g)
                #end
                values = {
                    "initial_page" => tStr,
                    'title'        => CGI.escapeHTML(@options.title),
                    'charset'      => @options.charset,
                    'content'      => File.read('files/README.html')
                }
            
                values['inline_source'] = true
                template.write_html_on(f, values)
            end
        end

        # return the url of the main page
        def main_url
            "files/README.html"
        end


    end

end
