├── lib └── rdoc │ ├── generator │ ├── html │ │ ├── common.rb │ │ ├── one_page_html.rb │ │ └── html.rb │ ├── xml.rb │ ├── xml │ │ ├── rdf.rb │ │ └── xml.rb │ ├── html.rb │ └── shadow_classes.rb │ └── template.rb └── test └── test_rdoc_generator_context.rb /lib/rdoc/generator/html/common.rb: -------------------------------------------------------------------------------- 1 | # 2 | # The templates require further refactoring. In particular, 3 | # * Some kind of HTML generation library should be used. 4 | # 5 | # Also, all of the templates require some TLC from a designer. 6 | # 7 | # Right now, this file contains some constants that are used by all 8 | # of the templates. 9 | # 10 | module RDoc::Generator::HTML::Common 11 | XHTML_STRICT_PREAMBLE = <<-EOF 12 | 14 | EOF 15 | 16 | XHTML_FRAME_PREAMBLE = <<-EOF 17 | 19 | EOF 20 | 21 | HTML_ELEMENT = <<-EOF 22 | 23 | EOF 24 | end 25 | -------------------------------------------------------------------------------- /test/test_rdoc_generator_context.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'minitest/unit' 3 | require 'rdoc/generator' 4 | require 'rdoc/stats' 5 | require 'rdoc/code_objects' 6 | require 'rdoc/parser/ruby' 7 | 8 | class TestRDocGeneratorContext < MiniTest::Unit::TestCase 9 | 10 | DATA = <<-DATA 11 | class Foo # :nodoc: 12 | end 13 | 14 | class Bar 15 | end 16 | DATA 17 | 18 | def setup 19 | RDoc::TopLevel.reset 20 | RDoc::Generator::Method.reset 21 | 22 | top_level = RDoc::TopLevel.new 'data.rb' 23 | 24 | @options = RDoc::Options.new 25 | @options.quiet = true 26 | @options.inline_source = true 27 | 28 | stats = RDoc::Stats.new 0 29 | 30 | parser = RDoc::Parser::Ruby.new top_level, 'data.rb', DATA, @options, stats 31 | 32 | @top_levels = [] 33 | @top_levels.push parser.scan 34 | end 35 | 36 | def test_class_build_indices 37 | files, classes = RDoc::Generator::Context.build_indices @top_levels, @options 38 | 39 | assert_equal 1, classes.length 40 | 41 | assert_equal 'Bar', classes.first.name 42 | 43 | # HACK complete 44 | end 45 | 46 | end 47 | 48 | -------------------------------------------------------------------------------- /lib/rdoc/template.rb: -------------------------------------------------------------------------------- 1 | require 'erb' 2 | 3 | module RDoc; end 4 | 5 | ## 6 | # An ERb wrapper that allows nesting of one ERb template inside another. 7 | # 8 | # This TemplatePage operates similarly to RDoc 1.x's TemplatePage, but uses 9 | # ERb instead of a custom template language. 10 | # 11 | # Converting from a RDoc 1.x template to an RDoc 2.x template is fairly easy. 12 | # 13 | # * %blah% becomes <%= values["blah"] %> 14 | # * !INCLUDE! becomes <%= template_include %> 15 | # * HREF:aref:name becomes <%= href values["aref"], values["name"] %> 16 | # * IF:blah becomes <% if values["blah"] then %> 17 | # * IFNOT:blah becomes <% unless values["blah"] then %> 18 | # * ENDIF:blah becomes <% end %> 19 | # * START:blah becomes <% values["blah"].each do |blah| %> 20 | # * END:blah becomes <% end %> 21 | # 22 | # To make nested loops easier to convert, start by converting START statements 23 | # to: 24 | # 25 | # <% values["blah"].each do |blah| $stderr.puts blah.keys %> 26 | # 27 | # So you can see what is being used inside which loop. 28 | 29 | class RDoc::TemplatePage 30 | 31 | ## 32 | # Create a new TemplatePage that will use +templates+. 33 | 34 | def initialize(*templates) 35 | @templates = templates 36 | end 37 | 38 | ## 39 | # Returns "#{name}" 40 | 41 | def href(ref, name) 42 | if ref then 43 | "#{name}" 44 | else 45 | name 46 | end 47 | end 48 | 49 | ## 50 | # Process the template using +values+, writing the result to +io+. 51 | 52 | def write_html_on(io, values) 53 | b = binding 54 | template_include = "" 55 | 56 | @compiled_templates ||= @templates.map do |template| 57 | ERB.new(template) 58 | end 59 | 60 | @compiled_templates.reverse_each do |template| 61 | template_include = template.result(b) 62 | end 63 | 64 | io.write template_include 65 | end 66 | 67 | end 68 | 69 | -------------------------------------------------------------------------------- /lib/rdoc/generator/xml.rb: -------------------------------------------------------------------------------- 1 | require 'rdoc/generator/html' 2 | require 'rdoc/cache' 3 | 4 | ## 5 | # Generate XML output as one big file 6 | 7 | class RDoc::Generator::XML < RDoc::Generator::HTML 8 | 9 | RDoc::RDoc.add_generator self 10 | 11 | ## 12 | # Standard generator factory 13 | 14 | def self.for(options) 15 | new(options) 16 | end 17 | 18 | def initialize(*args) 19 | super 20 | end 21 | 22 | ## 23 | # Build the initial indices and output objects 24 | # based on an array of TopLevel objects containing 25 | # the extracted information. 26 | 27 | def generate(info) 28 | @info = info 29 | @files = [] 30 | @classes = [] 31 | @hyperlinks = {} 32 | 33 | build_indices 34 | generate_xml 35 | end 36 | 37 | ## 38 | # Generate: 39 | # 40 | # * a list of File objects for each TopLevel object. 41 | # * a list of Class objects for each first level 42 | # class or module in the TopLevel objects 43 | # * a complete list of all hyperlinkable terms (file, 44 | # class, module, and method names) 45 | 46 | def build_indices 47 | template_cache = RDoc::Cache.instance 48 | 49 | @info.each do |toplevel| 50 | @files << RDoc::Generator::File.new(template_cache, toplevel, @options, 51 | RDoc::Generator::FILE_DIR) 52 | end 53 | 54 | RDoc::TopLevel.all_classes_and_modules.each do |cls| 55 | build_class_list(template_cache, cls, @files[0], RDoc::Generator::CLASS_DIR) 56 | end 57 | end 58 | 59 | def build_class_list(template_cache, from, html_file, class_dir) 60 | @classes << RDoc::Generator::Class.new(template_cache, from, html_file, 61 | class_dir, @options) 62 | from.each_classmodule do |mod| 63 | build_class_list(template_cache, mod, html_file, class_dir) 64 | end 65 | end 66 | 67 | ## 68 | # Generate all the HTML. For the one-file case, we generate 69 | # all the information in to one big hash 70 | 71 | def generate_xml 72 | values = { 73 | :charset => @options.charset, 74 | :files => gen_into(@files), 75 | :classes => gen_into(@classes) 76 | } 77 | 78 | template = RDoc::TemplatePage.new @template::ONE_PAGE 79 | 80 | if @options.op_name 81 | opfile = File.open(@options.op_name, "w") 82 | else 83 | opfile = $stdout 84 | end 85 | template.write_html_on(opfile, values) 86 | end 87 | 88 | def gen_into(list) 89 | res = [] 90 | list.each do |item| 91 | res << item.value_hash 92 | end 93 | res 94 | end 95 | 96 | def gen_file_index 97 | gen_an_index(@files, 'Files') 98 | end 99 | 100 | def gen_class_index 101 | gen_an_index(@classes, 'Classes') 102 | end 103 | 104 | def gen_method_index 105 | gen_an_index(RDoc::Generator::HtmlMethod.all_methods, 'Methods') 106 | end 107 | 108 | def gen_an_index(collection, title) 109 | res = [] 110 | collection.sort.each do |f| 111 | if f.document_self 112 | res << { :href => f.path, :name => f.index_name } 113 | end 114 | end 115 | 116 | return { 117 | :entries => res, 118 | :list_title => title, 119 | :index_url => main_url, 120 | } 121 | end 122 | 123 | end 124 | 125 | -------------------------------------------------------------------------------- /lib/rdoc/generator/html/one_page_html.rb: -------------------------------------------------------------------------------- 1 | require 'rdoc/generator/html' 2 | require 'rdoc/generator/html/common' 3 | 4 | module RDoc::Generator::HTML::ONE_PAGE_HTML 5 | 6 | include RDoc::Generator::HTML::Common 7 | 8 | CONTENTS_XML = <<-EOF 9 | <% if defined? classes and classes[:description] then %> 10 | <%= classes[:description] %> 11 | <% end %> 12 | 13 | <% if defined? files and files[:requires] then %> 14 |

Requires:

15 | 25 | <% end %> 26 | 27 | <% if defined? classes and classes[:includes] then %> 28 |

Includes

29 | 39 | <% end %> 40 | 41 | <% if defined? classes and classes[:sections] then %> 42 | <% classes[:sections].each do |sections| %> 43 | <% if sections[:attributes] then %> 44 |

Attributes

45 | 46 | <% sections[:attributes].each do |attributes| %> 47 | 48 | <% end %><%# sections[:attributes] %> 49 |
<%= attributes[:name] %><%= attributes[:rw] %><%= attributes[:a_desc] %>
50 | <% end %> 51 | 52 | <% if sections[:method_list] then %> 53 |

Methods

54 | <% sections[:method_list].each do |method_list| %> 55 | <% if method_list[:methods] then %> 56 | <% method_list[:methods].each do |methods| %> 57 |

<%= methods[:type] %> <%= methods[:category] %> method: 58 | <% if methods[:callseq] then %> 59 | <%= methods[:callseq] %> 60 | <% end %> 61 | <% unless methods[:callseq] then %> 62 | <%= methods[:name] %><%= methods[:params] %>

63 | <% end %> 64 | 65 | <% if methods[:m_desc] then %> 66 | <%= methods[:m_desc] %> 67 | <% end %> 68 | 69 | <% if methods[:sourcecode] then %> 70 |
 71 | <%= methods[:sourcecode] %>
 72 | 
73 | <% end %> 74 | <% end %><%# method_list[:methods] %> 75 | <% end %> 76 | <% end %><%# sections[:method_list] %> 77 | <% end %> 78 | <% end %><%# classes[:sections] %> 79 | <% end %> 80 | EOF 81 | 82 | ONE_PAGE = XHTML_STRICT_PREAMBLE + HTML_ELEMENT + %{ 83 | 84 | <%= values[:title] %> 85 | 86 | 87 | 88 | <% values[:files].each do |files| %> 89 |

File: <%= files[:short_name] %>

90 | 91 | 92 | 93 |
Path:<%= files[:full_path] %>
Modified:<%= files[:dtm_modified] %>
94 | } + CONTENTS_XML + %{ 95 | <% end %><%# values[:files] %> 96 | 97 | <% if values[:classes] then %> 98 |

Classes

99 | <% values[:classes].each do |classes| %> 100 | <% if classes[:parent] then %> 101 |

<%= classes[:classmod] %> <%= classes[:full_name] %> < <%= href classes[:par_url], classes[:parent] %>

102 | <% end %> 103 | <% unless classes[:parent] then %> 104 |

<%= classes[:classmod] %> <%= classes[:full_name] %>

105 | <% end %> 106 | 107 | <% if classes[:infiles] then %> 108 | (in files 109 | <% classes[:infiles].each do |infiles| %> 110 | <%= href infiles[:full_path_url], infiles[:full_path] %> 111 | <% end %><%# classes[:infiles] %> 112 | ) 113 | <% end %> 114 | } + CONTENTS_XML + %{ 115 | <% end %><%# values[:classes] %> 116 | <% end %> 117 | 118 | 119 | } 120 | 121 | end 122 | 123 | -------------------------------------------------------------------------------- /lib/rdoc/generator/xml/rdf.rb: -------------------------------------------------------------------------------- 1 | require 'rdoc/generator/xml' 2 | 3 | module RDoc::Generator::XML::RDF 4 | 5 | CONTENTS_RDF = <<-EOF 6 | <% if defined? classes and classes[:description] then %> 7 | 8 | <%= classes[:description] %> 9 | 10 | <% end %> 11 | 12 | <% if defined? files and files[:requires] then %> 13 | <% files[:requires].each do |requires| %> 14 | 15 | <% end # files[:requires] %> 16 | <% end %> 17 | 18 | <% if defined? classes and classes[:includes] then %> 19 | 20 | <% classes[:includes].each do |includes| %> 21 | 22 | <% end # includes[:includes] %> 23 | 24 | <% end %> 25 | 26 | <% if defined? classes and classes[:sections] then %> 27 | <% classes[:sections].each do |sections| %> 28 | <% if sections[:attributes] then %> 29 | <% sections[:attributes].each do |attributes| %> 30 | 31 | 32 | <% if attributes[:rw] then %> 33 | <%= attributes[:rw] %> 34 | <% end %> 35 | <%= attributes[:a_desc] %> 36 | 37 | 38 | <% end # sections[:attributes] %> 39 | <% end %> 40 | 41 | <% if sections[:method_list] then %> 42 | <% sections[:method_list].each do |method_list| %> 43 | <% if method_list[:methods] then %> 44 | <% method_list[:methods].each do |methods| %> 45 | 46 | 48 | <%= methods[:params] %> 49 | <% if methods[:m_desc] then %> 50 | 51 | <%= methods[:m_desc] %> 52 | 53 | <% end %> 54 | <% if methods[:sourcecode] then %> 55 | 56 | <%= methods[:sourcecode] %> 57 | 58 | <% end %> 59 | 60 | 61 | <% end # method_list[:methods] %> 62 | <% end %> 63 | <% end # sections[:method_list] %> 64 | <% end %> 65 | 66 | <% end # classes[:sections] %> 67 | <% end %> 68 | EOF 69 | 70 | ######################################################################## 71 | 72 | ONE_PAGE = %{ 73 | 76 | 77 | 78 | <% values[:files].each do |files| %> 79 | 80 | <%= files[:full_path] %> 81 | <%= files[:dtm_modified] %> 82 | } + CONTENTS_RDF + %{ 83 | 84 | <% end # values[:files] %> 85 | <% values[:classes].each do |classes| %> 86 | <<%= values[:classmod] %> rd:name="<%= classes[:full_name] %>" rd:id="<%= classes[:full_name] %>"> 87 | 88 | <% if classes[:infiles] then %> 89 | 90 | <% classes[:infiles].each do |infiles| %> 91 | 92 | 94 | rdf:about="<%= infiles[:full_path_url] %>" 95 | <% end %> 96 | /> 97 | 98 | <% end # classes[:infiles] %> 99 | 100 | <% end %> 101 | <% if classes[:parent] then %> 102 | <%= href classes[:par_url], classes[:parent] %> 103 | <% end %> 104 | 105 | } + CONTENTS_RDF + %{ 106 | > 107 | <% end # values[:classes] %> 108 | 109 | 110 | } 111 | 112 | end 113 | 114 | -------------------------------------------------------------------------------- /lib/rdoc/generator/xml/xml.rb: -------------------------------------------------------------------------------- 1 | require 'rdoc/generator/xml' 2 | 3 | module RDoc::Generator::XML::XML 4 | 5 | CONTENTS_XML = <<-EOF 6 | <% if defined? classes and classes[:description] then %> 7 | 8 | <%= classes[:description] %> 9 | 10 | <% end %> 11 | 12 | <% if defined? files and files[:requires] then %> 13 | 14 | <% files[:requires].each do |requires| %> 15 | 17 | href="<%= requires[:aref] %>" 18 | <% end %> 19 | /> 20 | <% end %><%# files[:requires] %> 21 | 22 | <% end %> 23 | <% if defined? classes and classes[:sections] then %> 24 | <% classes[:sections].each do |sections| %> 25 | <% if sections[:constants] then %> 26 | 27 | <% sections[:constants].each do |constant| %> 28 | 29 | <% if constant[:value] then %> 30 | <%= constant[:value] %> 31 | <% end %> 32 | <%= constant[:a_desc] %> 33 | 34 | <% end %><%# sections[:constants] %> 35 | 36 | <% end %> 37 | <% if sections[:attributes] then %> 38 | 39 | <% sections[:attributes].each do |attributes| %> 40 | 41 | <% if attributes[:rw] then %> 42 | <%= attributes[:rw] %> 43 | <% end %> 44 | <%= attributes[:a_desc] %> 45 | 46 | <% end %><%# sections[:attributes] %> 47 | 48 | <% end %> 49 | <% if sections[:method_list] then %> 50 | 51 | <% sections[:method_list].each do |method_list| %> 52 | <% if method_list[:methods] then %> 53 | <% method_list[:methods].each do |methods| %> 54 | 55 | <%= methods[:params] %> 56 | <% if methods[:m_desc] then %> 57 | 58 | <%= methods[:m_desc] %> 59 | 60 | <% end %> 61 | <% if methods[:sourcecode] then %> 62 | 63 | <%= methods[:sourcecode] %> 64 | 65 | <% end %> 66 | 67 | <% end %><%# method_list[:methods] %> 68 | <% end %> 69 | <% end %><%# sections[:method_list] %> 70 | 71 | <% end %> 72 | <% end %><%# classes[:sections] %> 73 | <% end %> 74 | <% if defined? classes and classes[:includes] then %> 75 | 76 | <% classes[:includes].each do |includes| %> 77 | 79 | href="<%= includes[:aref] %>" 80 | <% end %> 81 | /> 82 | <% end %><%# classes[:includes] %> 83 | 84 | <% end %> 85 | 86 | EOF 87 | 88 | ONE_PAGE = %{ 89 | 90 | 91 | <% values[:files].each do |files| %> 92 | 93 | 94 | <%= files[:full_path] %> 95 | <%= files[:dtm_modified] %> 96 | 97 | } + CONTENTS_XML + %{ 98 | 99 | <% end %><%# values[:files] %> 100 | 101 | 102 | <% values[:classes].each do |classes| %> 103 | <<%= classes[:classmod] %> name="<%= classes[:full_name] %>" id="<%= classes[:full_name] %>"> 104 | 105 | <% if classes[:infiles] then %> 106 | 107 | <% classes[:infiles].each do |infiles| %> 108 | <%= href infiles[:full_path_url], infiles[:full_path] %> 109 | <% end %><%# classes[:infiles] %> 110 | 111 | <% end %> 112 | <% if classes[:parent] then %> 113 | <%= href classes[:par_url], classes[:parent] %> 114 | <% end %> 115 | 116 | } + CONTENTS_XML + %{ 117 | > 118 | <% end %><%# values[:classes] %> 119 | 120 | 121 | } 122 | 123 | end 124 | -------------------------------------------------------------------------------- /lib/rdoc/generator/html.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | 3 | require 'rdoc/generator' 4 | require 'rdoc/markup/to_html' 5 | require 'rdoc/cache' 6 | 7 | ## 8 | # We're responsible for generating all the HTML files from the object tree 9 | # defined in code_objects.rb. We generate: 10 | # 11 | # [files] an html file for each input file given. These 12 | # input files appear as objects of class 13 | # TopLevel 14 | # 15 | # [classes] an html file for each class or module encountered. 16 | # These classes are not grouped by file: if a file 17 | # contains four classes, we'll generate an html 18 | # file for the file itself, and four html files 19 | # for the individual classes. 20 | # 21 | # [indices] we generate three indices for files, classes, 22 | # and methods. These are displayed in a browser 23 | # like window with three index panes across the 24 | # top and the selected description below 25 | # 26 | # Method descriptions appear in whatever entity (file, class, or module) that 27 | # contains them. 28 | # 29 | # We generate files in a structure below a specified subdirectory, normally 30 | # +doc+. 31 | # 32 | # opdir 33 | # | 34 | # |___ files 35 | # | |__ per file summaries 36 | # | 37 | # |___ classes 38 | # |__ per class/module descriptions 39 | # 40 | # HTML is generated using the Template class. 41 | 42 | class RDoc::Generator::HTML 43 | 44 | RDoc::RDoc.add_generator self 45 | 46 | include RDoc::Generator::MarkUp 47 | 48 | ## 49 | # Generator may need to return specific subclasses depending on the 50 | # options they are passed. Because of this we create them using a factory 51 | 52 | def self.for(options) 53 | RDoc::Generator::AllReferences.reset 54 | RDoc::Generator::Method.reset 55 | 56 | if options.all_one_file 57 | RDoc::Generator::HTMLInOne.new options 58 | else 59 | new options 60 | end 61 | end 62 | 63 | class << self 64 | protected :new 65 | end 66 | 67 | ## 68 | # Set up a new HTML generator. Basically all we do here is load up the 69 | # correct output temlate 70 | 71 | def initialize(options) #:not-new: 72 | @options = options 73 | load_html_template 74 | end 75 | 76 | ## 77 | # Build the initial indices and output objects 78 | # based on an array of TopLevel objects containing 79 | # the extracted information. 80 | 81 | def generate(toplevels) 82 | @toplevels = toplevels 83 | @files = [] 84 | @classes = [] 85 | @template_cache = RDoc::Cache.instance 86 | 87 | write_style_sheet 88 | gen_sub_directories 89 | build_indices 90 | generate_html 91 | end 92 | 93 | private 94 | 95 | ## 96 | # Load up the HTML template specified in the options. If the template name 97 | # contains a slash, use it literally. 98 | # 99 | # If the template is not a path, first look for it in rdoc's HTML template 100 | # directory. Perhaps this behavior should be reversed (first try to include 101 | # the template and, only if that fails, try to include it in the default 102 | # template directory). One danger with reversing the behavior, however, is 103 | # that if something like require 'html' could load up an unrelated file in 104 | # the standard library or in a gem. 105 | 106 | def load_html_template 107 | template = @options.template 108 | 109 | unless template =~ %r{/|\\} then 110 | generator_name = @options.generator.name.sub(/^RDoc::Generator::/, '') 111 | template = File.join('rdoc', 'generator', generator_name.downcase, 112 | template) 113 | end 114 | 115 | begin 116 | require template 117 | 118 | @template = self.class.const_get @options.template.upcase 119 | @options.template_class = @template 120 | rescue LoadError => e 121 | # The template did not exist in the default template directory, so 122 | # see if require can find the template elsewhere (in a gem, for 123 | # instance). 124 | if(e.message[template] && template != @options.template) 125 | template = @options.template 126 | retry 127 | end 128 | 129 | $stderr.puts "Could not find HTML template '#{template}': #{e.message}" 130 | exit 99 131 | end 132 | end 133 | 134 | ## 135 | # Write out the style sheet used by the main frames 136 | 137 | def write_style_sheet 138 | return unless @template.constants.include? :STYLE or 139 | @template.constants.include? 'STYLE' 140 | 141 | template = RDoc::TemplatePage.new @template::STYLE 142 | 143 | unless @options.css then 144 | open RDoc::Generator::CSS_NAME, 'w' do |f| 145 | values = {} 146 | 147 | if @template.constants.include? :FONTS or 148 | @template.constants.include? 'FONTS' then 149 | values[:fonts] = @template::FONTS 150 | end 151 | 152 | template.write_html_on(f, values) 153 | end 154 | end 155 | end 156 | 157 | ## 158 | # See the comments at the top for a description of the directory structure 159 | 160 | def gen_sub_directories 161 | FileUtils.mkdir_p RDoc::Generator::FILE_DIR 162 | FileUtils.mkdir_p RDoc::Generator::CLASS_DIR 163 | rescue 164 | $stderr.puts $!.message 165 | exit 1 166 | end 167 | 168 | def build_indices 169 | @files, @classes = RDoc::Generator::Context.build_indices(@toplevels, 170 | @options, 171 | @template_cache) 172 | end 173 | 174 | ## 175 | # Generate all the HTML 176 | 177 | def generate_html 178 | @main_url = main_url 179 | @sorted_files = @files.sort 180 | @sorted_classes = @classes.sort 181 | @sorted_methods = RDoc::Generator::Method.all_methods.sort 182 | 183 | # the individual descriptions for files and classes 184 | gen_into(@files) 185 | gen_into(@classes) 186 | 187 | # and the index files 188 | gen_file_index 189 | gen_class_index 190 | gen_method_index 191 | gen_main_index 192 | 193 | # this method is defined in the template file 194 | values = { 195 | :title_suffix => CGI.escapeHTML("[#{@options.title}]"), 196 | :charset => @options.charset, 197 | :style_url => style_url('', @options.css), 198 | } 199 | 200 | @template.write_extra_pages(values) if 201 | @template.respond_to?(:write_extra_pages) 202 | end 203 | 204 | def gen_into(list) 205 | # 206 | # The file, class, and method lists technically should be regenerated 207 | # for every output file, in order that the relative links be correct 208 | # (we are worried here about frameless templates, which need this 209 | # information for every generated page). Doing this is a bit slow, 210 | # however. For a medium-sized gem, this increased rdoc's runtime by 211 | # about 5% (using the 'time' command-line utility). While this is not 212 | # necessarily a problem, I do not want to pessimize rdoc for large 213 | # projects, however, and so we only regenerate the lists when the 214 | # directory of the output file changes, which seems like a reasonable 215 | # optimization. 216 | # 217 | file_list = {} 218 | class_list = {} 219 | method_list = {} 220 | prev_op_dir = nil 221 | 222 | # Extra optimization: sort the list based on item directories, to minimize 223 | # the number of times the directory changes. This reduces runtime by about 224 | # 8% on RDoc's own sources. 225 | sorted_list = list.sort do |a, b| 226 | File.dirname(a.path) <=> File.dirname(b.path) 227 | end 228 | 229 | sorted_list.each do |item| 230 | next unless item.document_self 231 | 232 | op_file = item.path 233 | op_dir = File.dirname(op_file) 234 | 235 | if(op_dir != prev_op_dir) 236 | file_list = index_to_links op_file, @sorted_files 237 | class_list = index_to_links op_file, @sorted_classes 238 | method_list = index_to_links op_file, @sorted_methods 239 | end 240 | prev_op_dir = op_dir 241 | 242 | FileUtils.mkdir_p op_dir 243 | 244 | open op_file, 'w' do |io| 245 | item.write_on io, file_list, class_list, method_list 246 | end 247 | end 248 | end 249 | 250 | def gen_file_index 251 | gen_an_index @files, 'Files', @template::FILE_INDEX, "fr_file_index.html" 252 | end 253 | 254 | def gen_class_index 255 | gen_an_index(@classes, 'Classes', @template::CLASS_INDEX, 256 | "fr_class_index.html") 257 | end 258 | 259 | def gen_method_index 260 | gen_an_index(RDoc::Generator::Method.all_methods, 'Methods', 261 | @template::METHOD_INDEX, "fr_method_index.html") 262 | end 263 | 264 | def gen_an_index(collection, title, template, filename) 265 | template = RDoc::TemplatePage.new @template::FR_INDEX_BODY, template 266 | res = [] 267 | collection.sort.each do |f| 268 | if f.document_self 269 | res << { :href => f.path, :name => f.index_name } 270 | end 271 | end 272 | 273 | values = { 274 | :entries => res, 275 | :title => CGI.escapeHTML("#{title} [#{@options.title}]"), 276 | :list_title => CGI.escapeHTML(title), 277 | :index_url => @main_url, 278 | :charset => @options.charset, 279 | :style_url => style_url('', @options.css), 280 | } 281 | 282 | open filename, 'w' do |f| 283 | template.write_html_on(f, values) 284 | end 285 | end 286 | 287 | ## 288 | # The main index page is mostly a template frameset, but includes the 289 | # initial page. If the --main option was given, we use this as 290 | # our main page, otherwise we use the first file specified on the command 291 | # line. 292 | 293 | def gen_main_index 294 | if @template.const_defined? :FRAMELESS then 295 | # 296 | # If we're using a template without frames, then just redirect 297 | # to it from index.html. 298 | # 299 | # One alternative to this, expanding the main page's template into 300 | # index.html, is tricky because the relative URLs will be different 301 | # (since index.html is located in at the site's root, 302 | # rather than within a files or a classes subdirectory). 303 | # 304 | open 'index.html', 'w' do |f| 305 | f.puts(%{}) 307 | f.puts(%{}) 309 | f.puts(%{}) 310 | f.puts(%{#{CGI.escapeHTML(@options.title)}}) 311 | f.puts(%{}) 312 | f.puts(%{}) 313 | f.puts(%{}) 314 | f.puts(%{}) 315 | end 316 | else 317 | main = RDoc::TemplatePage.new @template::INDEX 318 | 319 | open 'index.html', 'w' do |f| 320 | style_url = style_url '', @options.css 321 | 322 | classes = @classes.sort.map { |klass| klass.value_hash } 323 | 324 | values = { 325 | :initial_page => @main_url, 326 | :style_url => style_url('', @options.css), 327 | :title => CGI.escapeHTML(@options.title), 328 | :charset => @options.charset, 329 | :classes => classes, 330 | } 331 | 332 | values[:inline_source] = @options.inline_source 333 | 334 | main.write_html_on f, values 335 | end 336 | end 337 | end 338 | 339 | def index_to_links(output_path, sorted_collection) 340 | result = sorted_collection.map do |f| 341 | next unless f.document_self 342 | { :href => RDoc::Markup::ToHtml.gen_relative_url(output_path, f.path), 343 | :name => f.index_name } 344 | end 345 | result.compact! 346 | result 347 | end 348 | 349 | ## 350 | # Returns the url of the main page 351 | 352 | def main_url 353 | main_page = @options.main_page 354 | 355 | # 356 | # If a main page has been specified (--main), then search for it 357 | # in the AllReferences array. This allows either files or classes 358 | # to be used for the main page. 359 | # 360 | if main_page then 361 | main_page_ref = RDoc::Generator::AllReferences[main_page] 362 | 363 | if main_page_ref then 364 | return main_page_ref.path 365 | else 366 | $stderr.puts "Could not find main page #{main_page}" 367 | end 368 | end 369 | 370 | # 371 | # No main page has been specified, so just use the README. 372 | # 373 | @files.each do |file| 374 | return file.path if file.name =~ /^README/ 375 | end 376 | 377 | # 378 | # There's no README (shame! shame!). Just use the first file 379 | # that will be documented. 380 | # 381 | @files.each do |file| 382 | return file.path if file.document_self 383 | end 384 | 385 | # 386 | # There are no files to be documented... Something seems very wrong. 387 | # 388 | raise RDoc::Error, "Couldn't find anything to document (perhaps :stopdoc: has been used in all classes)!" 389 | end 390 | private :main_url 391 | 392 | end 393 | 394 | class RDoc::Generator::HTMLInOne < RDoc::Generator::HTML 395 | 396 | def initialize(*args) 397 | super 398 | end 399 | 400 | ## 401 | # Build the initial indices and output objects 402 | # based on an array of TopLevel objects containing 403 | # the extracted information. 404 | 405 | def generate(info) 406 | @toplevels = info 407 | @hyperlinks = {} 408 | 409 | build_indices 410 | generate_xml 411 | end 412 | 413 | ## 414 | # Generate: 415 | # 416 | # * a list of RDoc::Generator::File objects for each TopLevel object. 417 | # * a list of RDoc::Generator::Class objects for each first level 418 | # class or module in the TopLevel objects 419 | # * a complete list of all hyperlinkable terms (file, 420 | # class, module, and method names) 421 | 422 | def build_indices 423 | @files, @classes = RDoc::Generator::Context.build_indices(@toplevels, 424 | @options) 425 | end 426 | 427 | ## 428 | # Generate all the HTML. For the one-file case, we generate 429 | # all the information in to one big hash 430 | 431 | def generate_xml 432 | values = { 433 | :charset => @options.charset, 434 | :files => gen_into(@files), 435 | :classes => gen_into(@classes), 436 | :title => CGI.escapeHTML(@options.title), 437 | } 438 | 439 | template = RDoc::TemplatePage.new @template::ONE_PAGE 440 | 441 | opfile = if @options.op_name then 442 | open @options.op_name, 'w' 443 | else 444 | $stdout 445 | end 446 | template.write_html_on(opfile, values) 447 | end 448 | 449 | def gen_into(list) 450 | res = [] 451 | list.each do |item| 452 | res << item.value_hash 453 | end 454 | res 455 | end 456 | end 457 | -------------------------------------------------------------------------------- /lib/rdoc/generator/html/html.rb: -------------------------------------------------------------------------------- 1 | require 'rdoc/generator/html' 2 | require 'rdoc/generator/html/common' 3 | 4 | ## 5 | # = CSS2 RDoc HTML template 6 | # 7 | # This is a template for RDoc that uses XHTML 1.0 Strict and dictates a 8 | # bit more of the appearance of the output to cascading stylesheets than the 9 | # default. It was designed for clean inline code display, and uses DHTMl to 10 | # toggle the visibility of each method's source with each click on the 11 | # '[source]' link. 12 | # 13 | # This template *also* forms the basis of the frameless template. 14 | # 15 | # == Authors 16 | # 17 | # * Michael Granger 18 | # 19 | # Copyright (c) 2002, 2003 The FaerieMUD Consortium. Some rights reserved. 20 | # 21 | # This work is licensed under the Creative Commons Attribution License. To 22 | # view a copy of this license, visit 23 | # http://creativecommons.org/licenses/by/1.0/ or send a letter to Creative 24 | # Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA. 25 | 26 | module RDoc::Generator::HTML::HTML 27 | 28 | include RDoc::Generator::HTML::Common 29 | 30 | FONTS = "Verdana,Arial,Helvetica,sans-serif" 31 | 32 | STYLE = <<-EOF 33 | body { 34 | font-family: #{FONTS}; 35 | font-size: 90%; 36 | margin: 0; 37 | margin-left: 40px; 38 | padding: 0; 39 | background: white; 40 | color: black; 41 | } 42 | 43 | h1, h2, h3, h4 { 44 | margin: 0; 45 | background: transparent; 46 | } 47 | 48 | h1 { 49 | font-size: 150%; 50 | } 51 | 52 | h2,h3,h4 { 53 | margin-top: 1em; 54 | } 55 | 56 | :link, :visited { 57 | background: #eef; 58 | color: #039; 59 | text-decoration: none; 60 | } 61 | 62 | :link:hover, :visited:hover { 63 | background: #039; 64 | color: #eef; 65 | } 66 | 67 | /* Override the base stylesheet's Anchor inside a table cell */ 68 | td > :link, td > :visited { 69 | background: transparent; 70 | color: #039; 71 | text-decoration: none; 72 | } 73 | 74 | /* and inside a section title */ 75 | .section-title > :link, .section-title > :visited { 76 | background: transparent; 77 | color: #eee; 78 | text-decoration: none; 79 | } 80 | 81 | /* === Structural elements =================================== */ 82 | 83 | .index { 84 | margin: 0; 85 | margin-left: -40px; 86 | padding: 0; 87 | font-size: 90%; 88 | } 89 | 90 | .index :link, .index :visited { 91 | margin-left: 0.7em; 92 | } 93 | 94 | .index .section-bar { 95 | margin-left: 0px; 96 | padding-left: 0.7em; 97 | background: #ccc; 98 | font-size: small; 99 | } 100 | 101 | #classHeader, #fileHeader { 102 | width: auto; 103 | color: white; 104 | padding: 0.5em 1.5em 0.5em 1.5em; 105 | margin: 0; 106 | margin-left: -40px; 107 | border-bottom: 3px solid #006; 108 | } 109 | 110 | #classHeader :link, #fileHeader :link, 111 | #classHeader :visited, #fileHeader :visited { 112 | background: inherit; 113 | color: white; 114 | } 115 | 116 | #classHeader td, #fileHeader td { 117 | background: inherit; 118 | color: white; 119 | } 120 | 121 | #fileHeader { 122 | background: #057; 123 | } 124 | 125 | #classHeader { 126 | background: #048; 127 | } 128 | 129 | .class-name-in-header { 130 | font-size: 180%; 131 | font-weight: bold; 132 | } 133 | 134 | #bodyContent { 135 | padding: 0 1.5em 0 1.5em; 136 | } 137 | 138 | #description { 139 | padding: 0.5em 1.5em; 140 | background: #efefef; 141 | border: 1px dotted #999; 142 | } 143 | 144 | #description h1, #description h2, #description h3, 145 | #description h4, #description h5, #description h6 { 146 | color: #125; 147 | background: transparent; 148 | } 149 | 150 | #validator-badges { 151 | text-align: center; 152 | } 153 | 154 | #validator-badges img { 155 | border: 0; 156 | } 157 | 158 | #copyright { 159 | color: #333; 160 | background: #efefef; 161 | font: 0.75em sans-serif; 162 | margin-top: 5em; 163 | margin-bottom: 0; 164 | padding: 0.5em 2em; 165 | } 166 | 167 | /* === Classes =================================== */ 168 | 169 | table.header-table { 170 | color: white; 171 | font-size: small; 172 | } 173 | 174 | .type-note { 175 | font-size: small; 176 | color: #dedede; 177 | } 178 | 179 | .section-bar { 180 | color: #333; 181 | border-bottom: 1px solid #999; 182 | margin-left: -20px; 183 | } 184 | 185 | .section-title { 186 | background: #79a; 187 | color: #eee; 188 | padding: 3px; 189 | margin-top: 2em; 190 | margin-left: -30px; 191 | border: 1px solid #999; 192 | } 193 | 194 | .top-aligned-row { 195 | vertical-align: top 196 | } 197 | 198 | .bottom-aligned-row { 199 | vertical-align: bottom 200 | } 201 | 202 | #diagram img { 203 | border: 0; 204 | } 205 | 206 | /* --- Context section classes ----------------------- */ 207 | 208 | .context-row { } 209 | 210 | .context-item-name { 211 | font-family: monospace; 212 | font-weight: bold; 213 | color: black; 214 | } 215 | 216 | .context-item-value { 217 | font-size: small; 218 | color: #448; 219 | } 220 | 221 | .context-item-desc { 222 | color: #333; 223 | padding-left: 2em; 224 | } 225 | 226 | /* --- Method classes -------------------------- */ 227 | 228 | .method-detail { 229 | background: #efefef; 230 | padding: 0; 231 | margin-top: 0.5em; 232 | margin-bottom: 1em; 233 | border: 1px dotted #ccc; 234 | } 235 | 236 | .method-heading { 237 | color: black; 238 | background: #ccc; 239 | border-bottom: 1px solid #666; 240 | padding: 0.2em 0.5em 0 0.5em; 241 | } 242 | 243 | .method-signature { 244 | color: black; 245 | background: inherit; 246 | } 247 | 248 | .method-name { 249 | font-weight: bold; 250 | } 251 | 252 | .method-args { 253 | font-style: italic; 254 | } 255 | 256 | .method-description { 257 | padding: 0 0.5em 0 0.5em; 258 | } 259 | 260 | /* --- Source code sections -------------------- */ 261 | 262 | :link.source-toggle, :visited.source-toggle { 263 | font-size: 90%; 264 | } 265 | 266 | div.method-source-code { 267 | background: #262626; 268 | color: #ffdead; 269 | margin: 1em; 270 | padding: 0.5em; 271 | border: 1px dashed #999; 272 | overflow: auto; 273 | } 274 | 275 | div.method-source-code pre { 276 | color: #ffdead; 277 | } 278 | 279 | /* --- Ruby keyword styles --------------------- */ 280 | 281 | .standalone-code { 282 | background: #221111; 283 | color: #ffdead; 284 | overflow: auto; 285 | } 286 | 287 | .ruby-constant { 288 | color: #7fffd4; 289 | background: transparent; 290 | } 291 | 292 | .ruby-keyword { 293 | color: #00ffff; 294 | background: transparent; 295 | } 296 | 297 | .ruby-ivar { 298 | color: #eedd82; 299 | background: transparent; 300 | } 301 | 302 | .ruby-operator { 303 | color: #00ffee; 304 | background: transparent; 305 | } 306 | 307 | .ruby-identifier { 308 | color: #ffdead; 309 | background: transparent; 310 | } 311 | 312 | .ruby-node { 313 | color: #ffa07a; 314 | background: transparent; 315 | } 316 | 317 | .ruby-comment { 318 | color: #b22222; 319 | font-weight: bold; 320 | background: transparent; 321 | } 322 | 323 | .ruby-regexp { 324 | color: #ffa07a; 325 | background: transparent; 326 | } 327 | 328 | .ruby-value { 329 | color: #7fffd4; 330 | background: transparent; 331 | } 332 | EOF 333 | 334 | 335 | ##################################################################### 336 | ### H E A D E R T E M P L A T E 337 | ##################################################################### 338 | 339 | HEADER = XHTML_STRICT_PREAMBLE + HTML_ELEMENT + <<-EOF 340 | 341 | <%= values[:title] %> 342 | 343 | 344 | 345 | 376 | 377 | 378 | 379 | EOF 380 | 381 | ##################################################################### 382 | ### F O O T E R T E M P L A T E 383 | ##################################################################### 384 | 385 | FOOTER = <<-EOF 386 |
387 |

[Validate]

388 |
389 | 390 | 391 | 392 | EOF 393 | 394 | 395 | ##################################################################### 396 | ### F I L E P A G E H E A D E R T E M P L A T E 397 | ##################################################################### 398 | 399 | FILE_PAGE = <<-EOF 400 |
401 |

<%= values[:short_name] %>

402 | 403 | 404 | 405 | 410 | 411 | 412 | 413 | 414 | 415 |
Path:<%= values[:full_path] %> 406 | <% if values[:cvsurl] then %> 407 |  (CVS) 408 | <% end %> 409 |
Last Update:<%= values[:dtm_modified] %>
416 |
417 | EOF 418 | 419 | ##################################################################### 420 | ### C L A S S P A G E H E A D E R T E M P L A T E 421 | ##################################################################### 422 | 423 | CLASS_PAGE = <<-EOF 424 |
425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 447 | 448 | 449 | <% if values[:parent] then %> 450 | 451 | 452 | 461 | 462 | <% end %> 463 |
<%= values[:classmod] %><%= values[:full_name] %>
In: 433 | <% values[:infiles].each do |infiles| %> 434 | <% if infiles[:full_path_url] then %> 435 | 436 | <% end %> 437 | <%= infiles[:full_path] %> 438 | <% if infiles[:full_path_url] then %> 439 | 440 | <% end %> 441 | <% if infiles[:cvsurl] then %> 442 |  (CVS) 443 | <% end %> 444 |
445 | <% end %><%# values[:infiles] %> 446 |
Parent: 453 | <% if values[:par_url] then %> 454 | 455 | <% end %> 456 | <%= values[:parent] %> 457 | <% if values[:par_url] then %> 458 | 459 | <% end %> 460 |
464 |
465 | EOF 466 | 467 | ##################################################################### 468 | ### M E T H O D L I S T T E M P L A T E 469 | ##################################################################### 470 | 471 | METHOD_LIST = <<-EOF 472 |
473 | <% if values[:diagram] then %> 474 |
475 | <%= values[:diagram] %> 476 |
477 | <% end 478 | 479 | if values[:description] then %> 480 |
481 | <%= values[:description] %> 482 |
483 | <% end 484 | 485 | if values[:requires] then %> 486 |
487 |

Required files

488 | 489 |
490 | <% values[:requires].each do |requires| %> 491 | <%= href requires[:aref], requires[:name] %>   492 | <% end %><%# values[:requires] %> 493 |
494 |
495 | <% end 496 | 497 | if values[:toc] then %> 498 |
499 |

Contents

500 |
    501 | <% values[:toc].each do |toc| %> 502 |
  • <%= toc[:secname] %>
  • 503 | <% end %><%# values[:toc] %> 504 |
505 | <% end %> 506 |
507 | 508 | <% if values[:methods] then %> 509 |
510 |

Methods

511 | 512 |
513 | <% values[:methods].each do |methods| %> 514 | <%= href methods[:aref], methods[:name] %>   515 | <% end %><%# values[:methods] %> 516 |
517 |
518 | <% end %> 519 |
520 | 521 | 522 | <% if values[:includes] then %> 523 |
524 |

Included Modules

525 | 526 |
527 | <% values[:includes].each do |includes| %> 528 | <%= href includes[:aref], includes[:name] %> 529 | <% end %><%# values[:includes] %> 530 |
531 |
532 | <% end 533 | 534 | values[:sections].each do |sections| %> 535 |
536 | <% if sections[:sectitle] then %> 537 |

<%= sections[:sectitle] %>

538 | <% if sections[:seccomment] then %> 539 |
540 | <%= sections[:seccomment] %> 541 |
542 | <% end 543 | end 544 | 545 | if sections[:classlist] then %> 546 |
547 |

Classes and Modules

548 | 549 | <%= sections[:classlist] %> 550 |
551 | <% end 552 | 553 | if sections[:constants] then %> 554 |
555 |

Constants

556 | 557 |
558 | 559 | <% sections[:constants].each do |constants| %> 560 | 561 | 562 | 563 | 564 | <% if constants[:desc] then %> 565 | 566 | 567 | <% end %> 568 | 569 | <% end %><%# sections[:constants] %> 570 |
<%= constants[:name] %>=<%= constants[:value] %> <%= constants[:desc] %>
571 |
572 |
573 | <% end 574 | 575 | if sections[:aliases] then %> 576 |
577 |

External Aliases

578 | 579 |
580 | 581 | <% sections[:aliases].each do |aliases| %> 582 | 583 | 584 | 585 | 586 | 587 | <% if aliases[:desc] then %> 588 | 589 | 590 | 591 | 592 | <% end 593 | end %><%# sections[:aliases] %> 594 |
<%= aliases[:old_name] %>-><%= aliases[:new_name] %>
 <%= aliases[:desc] %>
595 |
596 |
597 | <% end %> 598 | 599 | <% if sections[:attributes] then %> 600 |
601 |

Attributes

602 | 603 |
604 | 605 | <% sections[:attributes].each do |attribute| %> 606 | 607 | 608 | <% if attribute[:rw] then %> 609 | 610 | <% end 611 | unless attribute[:rw] then %> 612 | 613 | <% end %> 614 | 615 | 616 | <% end %><%# sections[:attributes] %> 617 |
<%= attribute[:name] %> [<%= attribute[:rw] %>]   <%= attribute[:a_desc] %>
618 |
619 |
620 | <% end %> 621 | 622 | 623 | <% if sections[:method_list] then %> 624 |
625 | <% sections[:method_list].each do |method_list| 626 | if method_list[:methods] then %> 627 |

<%= method_list[:type] %> <%= method_list[:category] %> methods

628 | 629 | <% method_list[:methods].each do |methods| %> 630 |
631 | 632 | 633 | 654 | 655 |
656 | <% if methods[:m_desc] then %> 657 | <%= methods[:m_desc] %> 658 | <% end 659 | if methods[:sourcecode] then %> 660 |

[Source]

662 |
663 |
664 | <%= methods[:sourcecode] %>
665 | 
666 |
667 | <% end %> 668 |
669 |
670 | 671 | <% end %><%# method_list[:methods] %><% 672 | end 673 | end %><%# sections[:method_list] %> 674 | 675 |
676 | <% end %> 677 | <% end %><%# values[:sections] %> 678 | EOF 679 | 680 | ##################################################################### 681 | ### B O D Y T E M P L A T E 682 | ##################################################################### 683 | 684 | BODY = HEADER + %{ 685 | 686 | <%= template_include %> 687 | 688 |
689 | 690 | } + METHOD_LIST + %{ 691 | 692 |
693 | 694 | } + FOOTER 695 | 696 | ##################################################################### 697 | ### S O U R C E C O D E T E M P L A T E 698 | ##################################################################### 699 | 700 | SRC_PAGE = XHTML_STRICT_PREAMBLE + HTML_ELEMENT + <<-EOF 701 | 702 | <%= values[:title] %> 703 | 704 | 705 | 706 | 707 |
<%= values[:code] %>
708 | 709 | 710 | EOF 711 | 712 | 713 | ##################################################################### 714 | ### I N D E X F I L E T E M P L A T E S 715 | ##################################################################### 716 | 717 | FR_INDEX_BODY = %{<%= template_include %>} 718 | 719 | FILE_INDEX = XHTML_STRICT_PREAMBLE + HTML_ELEMENT + <<-EOF 720 | 725 | 726 | <%= values[:title] %> 727 | 728 | 729 | 730 | 731 | 732 |
733 |

<%= values[:list_title] %>

734 |
735 | <% values[:entries].each do |entries| %> 736 | <%= entries[:name] %>
737 | <% end %><%# values[:entries] %> 738 |
739 |
740 | 741 | 742 | EOF 743 | 744 | CLASS_INDEX = FILE_INDEX 745 | METHOD_INDEX = FILE_INDEX 746 | 747 | INDEX = XHTML_FRAME_PREAMBLE + HTML_ELEMENT + <<-EOF 748 | 753 | 754 | <%= values[:title] %> 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | EOF 767 | 768 | end 769 | 770 | -------------------------------------------------------------------------------- /lib/rdoc/generator/shadow_classes.rb: -------------------------------------------------------------------------------- 1 | require 'cgi' 2 | require 'rdoc/options' 3 | require 'rdoc/markup/to_html_crossref' 4 | require 'rdoc/template' 5 | require 'rdoc/cache' 6 | 7 | module RDoc::Generator 8 | 9 | ## 10 | # Name of sub-directory that holds file descriptions 11 | 12 | FILE_DIR = "files" 13 | 14 | ## 15 | # Name of sub-directory that holds class descriptions 16 | 17 | CLASS_DIR = "classes" 18 | 19 | ## 20 | # Name of the RDoc CSS file 21 | 22 | CSS_NAME = "rdoc-style.css" 23 | 24 | ## 25 | # A hash of all items that can be cross-referenced. 26 | # 27 | # This is used when we output required and included names. If the names 28 | # appear in this hash we can generate an html cross reference to the 29 | # appropriate description. 30 | # 31 | # We also use this when parsing comment blocks. Any decorated words 32 | # matching an entry in this list are hyperlinked. 33 | 34 | class AllReferences 35 | 36 | ## 37 | # Access +name+ in the reference store 38 | 39 | def self.[](name) 40 | @@refs[name] 41 | end 42 | 43 | ## 44 | # Add +name+ that references +html_class+ 45 | 46 | def self.add(name, html_class) 47 | @@refs[name] = html_class 48 | end 49 | 50 | ## 51 | # List of all known names 52 | 53 | def self.keys 54 | @@refs.keys 55 | end 56 | 57 | ## 58 | # Empties the reference store 59 | 60 | def self.reset 61 | @@refs = {} 62 | end 63 | 64 | reset 65 | 66 | end 67 | 68 | ## 69 | # Handle common markup tasks for the various Context subclasses 70 | 71 | module MarkUp 72 | 73 | ## 74 | # Convert a string in markup format into HTML. 75 | 76 | def markup(str, remove_para = false) 77 | return '' unless str 78 | 79 | # Convert leading comment markers to spaces, but only if all non-blank 80 | # lines have them 81 | if str =~ /^(?>\s*)[^\#]/ then 82 | content = str 83 | else 84 | content = str.gsub(/^\s*(#+)/) { $1.tr '#', ' ' } 85 | end 86 | 87 | res = formatter.convert content 88 | 89 | if remove_para then 90 | res.sub!(/^

/, '') 91 | res.sub!(/<\/p>$/, '') 92 | end 93 | 94 | res 95 | end 96 | 97 | ## 98 | # Qualify a stylesheet URL; if if +css_name+ does not begin with '/' or 99 | # 'http[s]://', prepend a prefix relative to +path+. Otherwise, return it 100 | # unmodified. 101 | 102 | def style_url(path, css_name=nil) 103 | # $stderr.puts "style_url( #{path.inspect}, #{css_name.inspect} )" 104 | css_name ||= CSS_NAME 105 | if %r{^(https?:/)?/} =~ css_name 106 | css_name 107 | else 108 | RDoc::Markup::ToHtml.gen_relative_url path, css_name 109 | end 110 | end 111 | 112 | ## 113 | # Build a webcvs URL with the given 'url' argument. URLs with a '%s' in them 114 | # get the file's path sprintfed into them; otherwise they're just catenated 115 | # together. 116 | 117 | def cvs_url(url, full_path) 118 | if /%s/ =~ url 119 | return sprintf( url, full_path ) 120 | else 121 | return url + full_path 122 | end 123 | end 124 | 125 | end 126 | 127 | ## 128 | # A Context is built by the parser to represent a container: contexts hold 129 | # classes, modules, methods, require lists and include lists. ClassModule 130 | # and TopLevel are the context objects we process here 131 | 132 | class Context 133 | 134 | include MarkUp 135 | 136 | attr_reader :context 137 | 138 | ## 139 | # Generate: 140 | # 141 | # * a list of RDoc::Generator::File objects for each TopLevel object 142 | # * a list of RDoc::Generator::Class objects for each first level class or 143 | # module in the TopLevel objects 144 | # * a complete list of all hyperlinkable terms (file, class, module, and 145 | # method names) 146 | 147 | def self.build_indices(toplevels, options, template_cache = nil) 148 | files = [] 149 | classes = [] 150 | template_cache ||= RDoc::Cache.instance 151 | 152 | file_dir = if defined? options.generator::FILE_DIR then 153 | options.generator::FILE_DIR 154 | else 155 | RDoc::Generator::FILE_DIR 156 | end 157 | 158 | toplevels.each do |toplevel| 159 | files << RDoc::Generator::File.new(template_cache, toplevel, options, 160 | file_dir) 161 | end 162 | 163 | class_dir = if defined? options.generator::CLASS_DIR then 164 | options.generator::CLASS_DIR 165 | else 166 | RDoc::Generator::CLASS_DIR 167 | end 168 | 169 | RDoc::TopLevel.all_classes_and_modules.each do |cls| 170 | next unless cls.document_self 171 | 172 | build_class_list(template_cache, classes, options, cls, files[0], 173 | class_dir) 174 | end 175 | 176 | return files, classes 177 | end 178 | 179 | def self.build_class_list(template_cache, classes, options, from, html_file, class_dir) 180 | classes << RDoc::Generator::Class.new(template_cache, from, html_file, class_dir, options) 181 | 182 | from.each_classmodule do |mod| 183 | build_class_list(template_cache, classes, options, mod, html_file, class_dir) 184 | end 185 | end 186 | 187 | def initialize(context, options) 188 | @context = context 189 | @options = options 190 | 191 | # HACK ugly 192 | @template = options.template_class 193 | end 194 | 195 | def formatter 196 | @formatter ||= @options.formatter || 197 | RDoc::Markup::ToHtmlCrossref.new(path, self, @options.show_hash) 198 | end 199 | 200 | ## 201 | # convenience method to build a hyperlink 202 | 203 | def href(link, cls, name) 204 | %{#{name}} #" 205 | end 206 | 207 | ## 208 | # Returns a reference to outselves to be used as an href= the form depends 209 | # on whether we're all in one file or in multiple files 210 | 211 | def as_href(from_path) 212 | if @options.all_one_file 213 | "#" + path 214 | else 215 | RDoc::Markup::ToHtml.gen_relative_url from_path, path 216 | end 217 | end 218 | 219 | ## 220 | # Create a list of Method objects for each method in the corresponding 221 | # context object. If the @options.show_all variable is set (corresponding 222 | # to the --all option, we include all methods, otherwise just the 223 | # public ones. 224 | 225 | def collect_methods 226 | list = @context.method_list 227 | 228 | unless @options.show_all then 229 | list = list.select do |m| 230 | m.visibility == :public or 231 | m.visibility == :protected or 232 | m.force_documentation 233 | end 234 | end 235 | 236 | @methods = list.collect do |m| 237 | RDoc::Generator::Method.new m, self, @options 238 | end 239 | end 240 | 241 | ## 242 | # Build a summary list of all the methods in this context 243 | 244 | def build_method_summary_list(path_prefix = "") 245 | collect_methods unless @methods 246 | 247 | @methods.sort.map do |meth| 248 | { 249 | :name => CGI.escapeHTML(meth.name), 250 | :aref => "##{meth.aref}" 251 | } 252 | end 253 | end 254 | 255 | ## 256 | # Build a list of aliases for which we couldn't find a 257 | # corresponding method 258 | 259 | def build_alias_summary_list(section) 260 | @context.aliases.map do |al| 261 | next unless al.section == section 262 | 263 | res = { 264 | :old_name => al.old_name, 265 | :new_name => al.new_name, 266 | } 267 | 268 | if al.comment and not al.comment.empty? then 269 | res[:desc] = markup al.comment, true 270 | end 271 | 272 | res 273 | end.compact 274 | end 275 | 276 | ## 277 | # Build a list of constants 278 | 279 | def build_constants_summary_list(section) 280 | @context.constants.map do |co| 281 | next unless co.section == section 282 | 283 | res = { 284 | :name => co.name, 285 | :value => CGI.escapeHTML(co.value) 286 | } 287 | 288 | if co.comment and not co.comment.empty? then 289 | res[:desc] = markup co.comment, true 290 | end 291 | 292 | res 293 | end.compact 294 | end 295 | 296 | def build_requires_list(context) 297 | potentially_referenced_list(context.requires) {|fn| [fn + ".rb"] } 298 | end 299 | 300 | def build_include_list(context) 301 | potentially_referenced_list(context.includes) 302 | end 303 | 304 | ## 305 | # Build a list from an array of Context items. Look up each in the 306 | # AllReferences hash: if we find a corresponding entry, we generate a 307 | # hyperlink to it, otherwise just output the name. However, some names 308 | # potentially need massaging. For example, you may require a Ruby file 309 | # without the .rb extension, but the file names we know about may have it. 310 | # To deal with this, we pass in a block which performs the massaging, 311 | # returning an array of alternative names to match 312 | 313 | def potentially_referenced_list(array) 314 | res = [] 315 | array.each do |i| 316 | ref = AllReferences[i.name] 317 | # if !ref 318 | # container = @context.parent 319 | # while !ref && container 320 | # name = container.name + "::" + i.name 321 | # ref = AllReferences[name] 322 | # container = container.parent 323 | # end 324 | # end 325 | 326 | ref = @context.find_symbol(i.name) 327 | ref = ref.viewer if ref 328 | 329 | if !ref && block_given? 330 | possibles = yield(i.name) 331 | while !ref and !possibles.empty? 332 | ref = AllReferences[possibles.shift] 333 | end 334 | end 335 | h_name = CGI.escapeHTML(i.name) 336 | if ref and ref.document_self 337 | path = url(ref.path) 338 | res << { :name => h_name, :aref => path } 339 | else 340 | res << { :name => h_name } 341 | end 342 | end 343 | res 344 | end 345 | 346 | ## 347 | # Build an array of arrays of method details. The outer array has up 348 | # to six entries, public, private, and protected for both class 349 | # methods, the other for instance methods. The inner arrays contain 350 | # a hash for each method 351 | 352 | def build_method_detail_list(section) 353 | outer = [] 354 | 355 | methods = @methods.sort.select do |m| 356 | m.document_self and m.section == section 357 | end 358 | 359 | for singleton in [true, false] 360 | for vis in [ :public, :protected, :private ] 361 | res = [] 362 | methods.each do |m| 363 | next unless m.visibility == vis and m.singleton == singleton 364 | 365 | row = {} 366 | 367 | if m.call_seq then 368 | row[:callseq] = m.call_seq.gsub(/->/, '→') 369 | else 370 | row[:name] = CGI.escapeHTML(m.name) 371 | row[:params] = m.params 372 | end 373 | 374 | desc = m.description.strip 375 | row[:m_desc] = desc unless desc.empty? 376 | row[:aref] = m.aref 377 | row[:visibility] = m.visibility.to_s 378 | 379 | alias_names = [] 380 | 381 | m.aliases.each do |other| 382 | if other.viewer then # won't be if the alias is private 383 | alias_names << { 384 | :name => other.name, 385 | :aref => other.viewer.as_href(path) 386 | } 387 | end 388 | end 389 | 390 | row[:aka] = alias_names unless alias_names.empty? 391 | 392 | if @options.inline_source then 393 | code = m.source_code 394 | row[:sourcecode] = code if code 395 | else 396 | code = m.src_url 397 | if code then 398 | row[:codeurl] = code 399 | row[:imgurl] = m.img_url 400 | end 401 | end 402 | 403 | res << row 404 | end 405 | 406 | if res.size > 0 then 407 | outer << { 408 | :type => vis.to_s.capitalize, 409 | :category => singleton ? "Class" : "Instance", 410 | :methods => res 411 | } 412 | end 413 | end 414 | end 415 | 416 | outer 417 | end 418 | 419 | ## 420 | # Build the structured list of classes and modules contained 421 | # in this context. 422 | 423 | def build_class_list(level, from, section, infile=nil) 424 | prefix = '  ::' * level; 425 | res = '' 426 | 427 | from.modules.sort.each do |mod| 428 | next unless mod.section == section 429 | next if infile && !mod.defined_in?(infile) 430 | if mod.document_self 431 | res << 432 | prefix << 433 | 'Module ' << 434 | href(url(mod.viewer.path), 'link', mod.full_name) << 435 | "
\n" << 436 | build_class_list(level + 1, mod, section, infile) 437 | end 438 | end 439 | 440 | from.classes.sort.each do |cls| 441 | next unless cls.section == section 442 | next if infile and not cls.defined_in?(infile) 443 | 444 | if cls.document_self 445 | res << 446 | prefix << 447 | 'Class ' << 448 | href(url(cls.viewer.path), 'link', cls.full_name) << 449 | "
\n" << 450 | build_class_list(level + 1, cls, section, infile) 451 | end 452 | end 453 | 454 | res 455 | end 456 | 457 | def url(target) 458 | RDoc::Markup::ToHtml.gen_relative_url path, target 459 | end 460 | 461 | def aref_to(target) 462 | if @options.all_one_file 463 | "#" + target 464 | else 465 | url(target) 466 | end 467 | end 468 | 469 | def document_self 470 | @context.document_self 471 | end 472 | 473 | def diagram_reference(diagram) 474 | res = diagram.gsub(/((?:src|href)=")(.*?)"/) { 475 | $1 + url($2) + '"' 476 | } 477 | res 478 | end 479 | 480 | ## 481 | # Find a symbol in ourselves or our parent 482 | 483 | def find_symbol(symbol, method=nil) 484 | res = @context.find_symbol(symbol, method) 485 | if res 486 | res = res.viewer 487 | end 488 | res 489 | end 490 | 491 | ## 492 | # create table of contents if we contain sections 493 | 494 | def add_table_of_sections 495 | toc = [] 496 | @context.sections.each do |section| 497 | if section.title then 498 | toc << { 499 | :secname => section.title, 500 | :href => section.sequence 501 | } 502 | end 503 | end 504 | 505 | @values[:toc] = toc unless toc.empty? 506 | end 507 | 508 | end 509 | 510 | ## 511 | # Wraps a ClassModule CodeObject for use by a Generator 512 | 513 | class Class < Context 514 | 515 | attr_reader :methods 516 | attr_reader :path 517 | attr_reader :values 518 | 519 | def initialize(template_cache, context, html_file, prefix, options) 520 | super context, options 521 | 522 | @template_cache = template_cache 523 | @html_file = html_file 524 | @html_class = self 525 | @is_module = context.module? 526 | @values = {} 527 | 528 | context.viewer = self 529 | 530 | if options.all_one_file 531 | @path = context.full_name 532 | else 533 | @path = http_url(context.full_name, prefix) 534 | end 535 | 536 | collect_methods 537 | 538 | AllReferences.add(name, self) 539 | end 540 | 541 | ## 542 | # Returns the relative file name to store this class in, which is also its 543 | # url 544 | 545 | def http_url(full_name, prefix) 546 | path = full_name.dup 547 | 548 | path.gsub!(/<<\s*(\w*)/, 'from-\1') if path['<<'] 549 | 550 | path = [prefix] + path.split('::') 551 | 552 | ::File.join(*path.compact) + ".html" 553 | end 554 | 555 | ## 556 | # Name of this class 557 | 558 | def name 559 | @context.full_name 560 | end 561 | 562 | ## 563 | # Name of this class' parent 564 | 565 | def parent_name 566 | @context.parent.full_name 567 | end 568 | 569 | def index_name 570 | name 571 | end 572 | 573 | ## 574 | # Writes this class to +f+ 575 | 576 | def write_on(f, file_list, class_list, method_list, overrides = {}) 577 | value_hash 578 | 579 | @values[:file_list] = file_list 580 | @values[:class_list] = class_list 581 | @values[:method_list] = method_list 582 | 583 | @values.update overrides 584 | 585 | template_page = @template_cache.cache(@template) do 586 | RDoc::TemplatePage.new(@template::BODY, 587 | @template::CLASS_PAGE, 588 | @template::METHOD_LIST) 589 | end 590 | 591 | template_page.write_html_on(f, @values) 592 | end 593 | 594 | ## 595 | # A Hash representation of this class used for filling in templates 596 | 597 | def value_hash 598 | class_attribute_values 599 | add_table_of_sections 600 | 601 | @values[:charset] = @options.charset 602 | @values[:style_url] = style_url(path, @options.css) 603 | 604 | d = markup(@context.comment) 605 | @values[:description] = d unless d.empty? 606 | 607 | ml = build_method_summary_list @path 608 | @values[:methods] = ml unless ml.empty? 609 | 610 | il = build_include_list @context 611 | @values[:includes] = il unless il.empty? 612 | 613 | @values[:sections] = @context.sections.map do |section| 614 | secdata = { 615 | :sectitle => section.title, 616 | :secsequence => section.sequence, 617 | :seccomment => markup(section.comment), 618 | } 619 | 620 | al = build_alias_summary_list section 621 | secdata[:aliases] = al unless al.empty? 622 | 623 | co = build_constants_summary_list section 624 | secdata[:constants] = co unless co.empty? 625 | 626 | al = build_attribute_list section 627 | secdata[:attributes] = al unless al.empty? 628 | 629 | cl = build_class_list 0, @context, section 630 | secdata[:classlist] = cl unless cl.empty? 631 | 632 | mdl = build_method_detail_list section 633 | secdata[:method_list] = mdl unless mdl.empty? 634 | 635 | secdata 636 | end 637 | 638 | @values 639 | end 640 | 641 | ## 642 | # Hash representation of this class' attributes that belongs to +section+ 643 | 644 | def build_attribute_list(section) 645 | @context.attributes.sort.map do |att| 646 | next unless att.section == section 647 | 648 | if att.visibility == :public or att.visibility == :protected or 649 | @options.show_all then 650 | 651 | entry = { 652 | :name => CGI.escapeHTML(att.name), 653 | :rw => att.rw, 654 | :a_desc => markup(att.comment, true) 655 | } 656 | 657 | unless att.visibility == :public or att.visibility == :protected then 658 | entry[:rw] << "-" 659 | end 660 | 661 | entry 662 | end 663 | end.compact 664 | end 665 | 666 | def class_attribute_values 667 | h_name = CGI.escapeHTML(name) 668 | 669 | @values[:href] = @path 670 | @values[:classmod] = @is_module ? "Module" : "Class" 671 | @values[:title] = "#{@values['classmod']}: #{h_name} [#{@options.title}]" 672 | 673 | c = @context 674 | c = c.parent while c and not c.diagram 675 | 676 | if c and c.diagram then 677 | @values[:diagram] = diagram_reference(c.diagram) 678 | end 679 | 680 | @values[:full_name] = h_name 681 | 682 | if not @context.module? and @context.superclass then 683 | parent_class = @context.superclass 684 | @values[:parent] = CGI.escapeHTML(parent_class) 685 | 686 | if parent_name 687 | lookup = parent_name + "::" + parent_class 688 | else 689 | lookup = parent_class 690 | end 691 | 692 | parent_url = AllReferences[lookup] || AllReferences[parent_class] 693 | 694 | if parent_url and parent_url.document_self 695 | @values[:par_url] = aref_to(parent_url.path) 696 | end 697 | end 698 | 699 | files = [] 700 | @context.in_files.each do |f| 701 | res = {} 702 | full_path = CGI.escapeHTML(f.file_absolute_name) 703 | 704 | res[:full_path] = full_path 705 | res[:full_path_url] = aref_to(f.viewer.path) if f.document_self 706 | 707 | if @options.webcvs 708 | res[:cvsurl] = cvs_url( @options.webcvs, full_path ) 709 | end 710 | 711 | files << res 712 | end 713 | 714 | @values[:infiles] = files 715 | end 716 | 717 | ## 718 | # Classes are ordered by name 719 | 720 | def <=>(other) 721 | self.name <=> other.name 722 | end 723 | 724 | def inspect # :nodoc: 725 | "#<#{self.class} name: #{name} path: #{@path}>" 726 | end 727 | 728 | def pretty_print(q) # :nodoc: 729 | q.group 1, "#<#{self.class} ", '>' do 730 | q.text 'name: ' 731 | q.pp name 732 | q.text ',' 733 | q.breakable 734 | 735 | q.text 'path: ' 736 | q.pp @path 737 | q.text ',' 738 | q.breakable 739 | 740 | q.text 'values: ' 741 | q.pp @values 742 | q.text ',' 743 | q.breakable 744 | 745 | q.text 'methods: ' 746 | q.pp @methods 747 | end 748 | end 749 | 750 | end 751 | 752 | ## 753 | # Wraps a TopLevel CodeObject for use by a Generator 754 | 755 | class File < Context 756 | 757 | ## 758 | # Path this file was found at 759 | 760 | attr_reader :path 761 | 762 | ## 763 | # Name of the file 764 | 765 | attr_reader :name 766 | 767 | attr_reader :values 768 | 769 | def initialize(template_cache, context, options, file_dir) 770 | super context, options 771 | 772 | @values = {} 773 | @template_cache = template_cache 774 | 775 | if options.all_one_file 776 | @path = filename_to_label 777 | else 778 | @path = http_url(file_dir) 779 | end 780 | 781 | @name = @context.file_relative_name 782 | 783 | collect_methods 784 | AllReferences.add(name, self) 785 | context.viewer = self 786 | end 787 | 788 | def http_url(file_dir) 789 | path = [file_dir, "#{@context.file_relative_name.tr '.', '_'}.html"] 790 | 791 | ::File.join path.compact 792 | end 793 | 794 | def filename_to_label 795 | @context.file_relative_name.gsub(/%|\/|\?|\#/) do 796 | ('%%%x' % $&[0]).unpack('C') 797 | end 798 | end 799 | 800 | def index_name 801 | name 802 | end 803 | 804 | ## 805 | # A file doesn't have a parent 806 | 807 | def parent_name 808 | nil 809 | end 810 | 811 | ## 812 | # Hash representation of this file 813 | 814 | def value_hash 815 | file_attribute_values 816 | add_table_of_sections 817 | 818 | @values[:charset] = @options.charset 819 | @values[:href] = path 820 | @values[:parser] = @context.parser 821 | @values[:style_url] = style_url(path, @options.css) 822 | 823 | if @context.comment 824 | d = markup(@context.comment) 825 | @values[:description] = d if d.size > 0 826 | end 827 | 828 | ml = build_method_summary_list 829 | @values[:methods] = ml unless ml.empty? 830 | 831 | il = build_include_list(@context) 832 | @values[:includes] = il unless il.empty? 833 | 834 | rl = build_requires_list(@context) 835 | @values[:requires] = rl unless rl.empty? 836 | 837 | file_context = @context unless @options.promiscuous 838 | 839 | @values[:sections] = @context.sections.map do |section| 840 | secdata = { 841 | :sectitle => section.title, 842 | :secsequence => section.sequence, 843 | :seccomment => markup(section.comment) 844 | } 845 | 846 | cl = build_class_list(0, @context, section, file_context) 847 | secdata[:classlist] = cl unless cl.empty? 848 | 849 | mdl = build_method_detail_list(section) 850 | secdata[:method_list] = mdl unless mdl.empty? 851 | 852 | al = build_alias_summary_list(section) 853 | secdata[:aliases] = al unless al.empty? 854 | 855 | co = build_constants_summary_list(section) 856 | secdata[:constants] = co unless co.empty? 857 | 858 | secdata 859 | end 860 | 861 | @values 862 | end 863 | 864 | ## 865 | # Writes this file to +f+ 866 | 867 | def write_on(f, file_list, class_list, method_list, overrides = {}) 868 | value_hash 869 | 870 | @values[:file_list] = file_list 871 | @values[:class_list] = class_list 872 | @values[:method_list] = method_list 873 | 874 | @values.update overrides 875 | 876 | template_page = @template_cache.cache(@template) do 877 | RDoc::TemplatePage.new(@template::BODY, 878 | @template::FILE_PAGE, 879 | @template::METHOD_LIST) 880 | end 881 | template_page.write_html_on(f, @values) 882 | end 883 | 884 | def file_attribute_values 885 | full_path = @context.file_absolute_name 886 | short_name = ::File.basename full_path 887 | 888 | @values[:title] = CGI.escapeHTML("File: #{short_name} [#{@options.title}]") 889 | 890 | if @context.diagram then 891 | @values[:diagram] = diagram_reference(@context.diagram) 892 | end 893 | 894 | @values[:short_name] = CGI.escapeHTML(short_name) 895 | @values[:full_path] = CGI.escapeHTML(full_path) 896 | @values[:dtm_modified] = @context.file_stat.mtime.to_s 897 | 898 | if @options.webcvs then 899 | @values[:cvsurl] = cvs_url @options.webcvs, @values[:full_path] 900 | end 901 | end 902 | 903 | ## 904 | # Files are ordered by name 905 | 906 | def <=>(other) 907 | self.name <=> other.name 908 | end 909 | 910 | def inspect # :nodoc: 911 | "#<#{self.class} name: #{@name} path: #{@path}>" 912 | end 913 | 914 | def pretty_print(q) # :nodoc: 915 | q.group 1, "#<#{self.class} ", '>' do 916 | q.text 'name: ' 917 | q.pp @name 918 | q.text ',' 919 | q.breakable 920 | 921 | q.text 'path: ' 922 | q.pp @path 923 | q.text ',' 924 | q.breakable 925 | 926 | q.text 'values: ' 927 | q.pp @values 928 | end 929 | end 930 | 931 | end 932 | 933 | ## 934 | # Wraps an AnyMethod CodeObject for use by a Generator 935 | 936 | class Method 937 | 938 | include MarkUp 939 | 940 | ## 941 | # CodeObject this method points to 942 | 943 | attr_reader :context 944 | attr_reader :src_url 945 | attr_reader :img_url 946 | attr_reader :source_code 947 | 948 | ## 949 | # All methods known by the generator 950 | 951 | def self.all_methods 952 | @@all_methods 953 | end 954 | 955 | ## 956 | # Resets the method cache 957 | 958 | def self.reset 959 | @@all_methods = [] 960 | @@seq = "M000000" 961 | end 962 | 963 | # Initialize the class variables. 964 | self.reset 965 | 966 | def initialize(context, html_class, options) 967 | # TODO: rethink the class hierarchy here... 968 | @context = context 969 | @html_class = html_class 970 | @options = options 971 | 972 | @@seq = @@seq.succ 973 | @seq = @@seq 974 | 975 | # HACK ugly 976 | @template = options.template_class 977 | 978 | @@all_methods << self 979 | 980 | context.viewer = self 981 | 982 | if ts = @context.token_stream then 983 | @source_code = markup_code ts 984 | 985 | unless @options.inline_source then 986 | @src_url = create_source_code_file @source_code 987 | @img_url = RDoc::Markup::ToHtml.gen_relative_url path, 'source.png' 988 | end 989 | end 990 | 991 | AllReferences.add name, self 992 | end 993 | 994 | ## 995 | # Returns a reference to outselves to be used as an href= the form depends 996 | # on whether we're all in one file or in multiple files 997 | 998 | def as_href(from_path) 999 | if @options.all_one_file 1000 | "#" + path 1001 | else 1002 | RDoc::Markup::ToHtml.gen_relative_url from_path, path 1003 | end 1004 | end 1005 | 1006 | def formatter 1007 | @formatter ||= @options.formatter || 1008 | RDoc::Markup::ToHtmlCrossref.new(path, self, @options.show_hash) 1009 | end 1010 | 1011 | def inspect # :nodoc: 1012 | alias_for = if @context.is_alias_for then 1013 | " (alias_for #{@context.is_alias_for})" 1014 | else 1015 | nil 1016 | end 1017 | 1018 | "#<%s:0x%x %s%s%s (%s)%s>" % [ 1019 | self.class, object_id, 1020 | @context.parent.name, 1021 | @context.singleton ? '::' : '#', 1022 | name, 1023 | @context.visibility, 1024 | alias_for 1025 | ] 1026 | end 1027 | 1028 | ## 1029 | # Method name 1030 | 1031 | def name 1032 | @context.name 1033 | end 1034 | 1035 | ## 1036 | # Section this method belongs to 1037 | 1038 | def section 1039 | @context.section 1040 | end 1041 | 1042 | def index_name 1043 | "#{@context.name} (#{@html_class.name})" 1044 | end 1045 | 1046 | def parent_name 1047 | if @context.parent.parent 1048 | @context.parent.parent.full_name 1049 | else 1050 | nil 1051 | end 1052 | end 1053 | 1054 | def aref 1055 | @seq 1056 | end 1057 | 1058 | def path 1059 | if @options.all_one_file 1060 | aref 1061 | else 1062 | @html_class.path + "#" + aref 1063 | end 1064 | end 1065 | 1066 | def description 1067 | markup(@context.comment) 1068 | end 1069 | 1070 | ## 1071 | # public, protected, private 1072 | 1073 | def visibility 1074 | @context.visibility 1075 | end 1076 | 1077 | ## 1078 | # Is this method a singleton method? 1079 | 1080 | def singleton 1081 | @context.singleton 1082 | end 1083 | 1084 | def call_seq 1085 | cs = @context.call_seq 1086 | if cs 1087 | cs.gsub(/\n/, "
\n") 1088 | else 1089 | nil 1090 | end 1091 | end 1092 | 1093 | def params 1094 | # params coming from a call-seq in 'C' will start with the 1095 | # method name 1096 | params = @context.params 1097 | if params !~ /^\w/ 1098 | params = @context.params.gsub(/\s*\#.*/, '') 1099 | params = params.tr("\n", " ").squeeze(" ") 1100 | params = "(" + params + ")" unless params[0] == ?( 1101 | 1102 | if (block = @context.block_params) 1103 | # If this method has explicit block parameters, remove any 1104 | # explicit &block 1105 | 1106 | params.sub!(/,?\s*&\w+/, '') 1107 | 1108 | block.gsub!(/\s*\#.*/, '') 1109 | block = block.tr("\n", " ").squeeze(" ") 1110 | if block[0] == ?( 1111 | block.sub!(/^\(/, '').sub!(/\)/, '') 1112 | end 1113 | params << " {|#{block.strip}| ...}" 1114 | end 1115 | end 1116 | CGI.escapeHTML(params) 1117 | end 1118 | 1119 | def create_source_code_file(code_body) 1120 | meth_path = @html_class.path.sub(/\.html$/, '.src') 1121 | FileUtils.mkdir_p(meth_path) 1122 | file_path = ::File.join meth_path, "#{@seq}.html" 1123 | 1124 | template = RDoc::TemplatePage.new(@template::SRC_PAGE) 1125 | 1126 | open file_path, 'w' do |f| 1127 | values = { 1128 | :title => CGI.escapeHTML(index_name), 1129 | :code => code_body, 1130 | :style_url => style_url(file_path, @options.css), 1131 | :charset => @options.charset 1132 | } 1133 | template.write_html_on(f, values) 1134 | end 1135 | 1136 | RDoc::Markup::ToHtml.gen_relative_url path, file_path 1137 | end 1138 | 1139 | ## 1140 | # Methods are sorted by name 1141 | 1142 | def <=>(other) 1143 | @context <=> other.context 1144 | end 1145 | 1146 | ## 1147 | # Given a sequence of source tokens, mark up the source code to make it 1148 | # look purty. 1149 | 1150 | def markup_code(tokens) 1151 | src = "" 1152 | tokens.each do |t| 1153 | next unless t 1154 | # style = STYLE_MAP[t.class] 1155 | style = case t 1156 | when RDoc::RubyToken::TkCONSTANT then "ruby-constant" 1157 | when RDoc::RubyToken::TkKW then "ruby-keyword kw" 1158 | when RDoc::RubyToken::TkIVAR then "ruby-ivar" 1159 | when RDoc::RubyToken::TkOp then "ruby-operator" 1160 | when RDoc::RubyToken::TkId then "ruby-identifier" 1161 | when RDoc::RubyToken::TkNode then "ruby-node" 1162 | when RDoc::RubyToken::TkCOMMENT then "ruby-comment cmt" 1163 | when RDoc::RubyToken::TkREGEXP then "ruby-regexp re" 1164 | when RDoc::RubyToken::TkSTRING then "ruby-value str" 1165 | when RDoc::RubyToken::TkVal then "ruby-value" 1166 | else 1167 | nil 1168 | end 1169 | 1170 | text = CGI.escapeHTML(t.text) 1171 | 1172 | if style 1173 | src << "#{text}" 1174 | else 1175 | src << text 1176 | end 1177 | end 1178 | 1179 | add_line_numbers(src) if @options.include_line_numbers 1180 | src 1181 | end 1182 | 1183 | ## 1184 | # We rely on the fact that the first line of a source code listing has 1185 | # # File xxxxx, line dddd 1186 | 1187 | def add_line_numbers(src) 1188 | if src =~ /\A.*, line (\d+)/ then 1189 | first = $1.to_i - 1 1190 | last = first + src.count("\n") 1191 | size = last.to_s.length 1192 | 1193 | line = first 1194 | src.gsub!(/^/) do 1195 | res = if line == first then 1196 | " " * (size + 2) 1197 | else 1198 | "%#{size}d: " % line 1199 | end 1200 | 1201 | line += 1 1202 | res 1203 | end 1204 | end 1205 | end 1206 | 1207 | ## 1208 | # Should this method be included in generated documentation? 1209 | 1210 | def document_self 1211 | @context.document_self 1212 | end 1213 | 1214 | ## 1215 | # Array of other names for this method 1216 | 1217 | def aliases 1218 | @context.aliases 1219 | end 1220 | 1221 | def find_symbol(symbol, method=nil) 1222 | res = @context.parent.find_symbol(symbol, method) 1223 | if res 1224 | res = res.viewer 1225 | end 1226 | res 1227 | end 1228 | 1229 | end 1230 | 1231 | end 1232 | 1233 | --------------------------------------------------------------------------------