diff --git a/ChangeLog b/ChangeLog index 8c76752ac4..7f79bfc25e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +Tue Nov 27 13:27:46 2012 Eric Hodel + + * lib/rdoc*: Updated to RDoc 4.0 (pre-release) + * bin/rdoc: ditto + * test/rdoc*: ditto + * NEWS: Updated with RDoc 4.0 information + Tue Nov 27 12:17:11 2012 Koichi Sasada * thread.c (rb_thread_terminate_all): retry broadcast only when diff --git a/NEWS b/NEWS index 269fcbe2b6..267c52db19 100644 --- a/NEWS +++ b/NEWS @@ -272,6 +272,17 @@ with all sufficient information, see the ChangeLog file. http://rake.rubyforge.org/doc/release_notes/rake-0_9_4_rdoc.html for a list of changes in rake 0.9.3 and 0.9.4. +* rdoc + * rdoc has been updated to version 4.0 + + This version is largely backwards-compatible with previous rdoc versions. + The most notable change is an update to the ri data format (ri data must + be regenerated for gems shared across rdoc versions). Further API changes + are internal and won't affect most users. + + See https://github.com/rdoc/rdoc/blob/master/History.rdoc for a list of + changes in rdoc 4.0. + * resolv * new methods: * Resolv::DNS#timeouts= diff --git a/bin/rdoc b/bin/rdoc index 64601a819e..aaa23292df 100755 --- a/bin/rdoc +++ b/bin/rdoc @@ -5,8 +5,6 @@ # # Copyright (c) 2003 Dave Thomas # Released under the same terms as Ruby -# -# $Revision$ begin gem 'rdoc' @@ -20,6 +18,10 @@ require 'rdoc/rdoc' begin r = RDoc::RDoc.new r.document ARGV +rescue Errno::ENOSPC + $stderr.puts 'Ran out of space creating documentation' + $stderr.puts + $stderr.puts 'Please free up some space and try again' rescue SystemExit raise rescue Exception => e diff --git a/ext/nkf/nkf-utf8/utf8tbl.c b/ext/nkf/nkf-utf8/utf8tbl.c index 7a1cacaf9f..bbf5c5f109 100644 --- a/ext/nkf/nkf-utf8/utf8tbl.c +++ b/ext/nkf/nkf-utf8/utf8tbl.c @@ -1,7 +1,7 @@ /* * utf8tbl.c - Convertion Table for nkf * - * $Id: utf8tbl.c,v 1.23 2008/02/07 19:25:29 naruse Exp $ + * $Id$ */ #include "config.h" diff --git a/lib/rdoc.rb b/lib/rdoc.rb index aaa1aaa092..5c2df18a0d 100644 --- a/lib/rdoc.rb +++ b/lib/rdoc.rb @@ -1,86 +1,55 @@ $DEBUG_RDOC = nil -# :main: README.txt +# :main: README.rdoc ## -# RDoc is a Ruby documentation system which contains RDoc::RDoc for generating -# documentation, RDoc::RI for interactive documentation and RDoc::Markup for -# text markup. +# RDoc produces documentation for Ruby source files by parsing the source and +# extracting the definition for classes, modules, methods, includes and +# requires. It associates these with optional documentation contained in an +# immediately preceding comment block then renders the result using an output +# formatter. # -# RDoc::RDoc produces documentation for Ruby source files. It works similarly -# to JavaDoc, parsing the source and extracting the definition for classes, -# modules, methods, includes and requires. It associates these with optional -# documentation contained in an immediately preceding comment block then -# renders the result using an output formatter. -# -# RDoc::Markup that converts plain text into various output formats. The -# markup library is used to interpret the comment blocks that RDoc uses to -# document methods, classes, and so on. -# -# RDoc::RI implements the +ri+ command-line tool which displays on-line -# documentation for ruby classes, methods, etc. +ri+ features several output -# formats and an interactive mode (ri -i). See ri --help -# for further details. +# For a simple introduction to writing or generating documentation using RDoc +# see the README. # # == Roadmap # -# * If you want to use RDoc to create documentation for your Ruby source files, -# see RDoc::Markup and refer to rdoc --help for command line -# usage. -# * If you want to write documentation for Ruby files see RDoc::Parser::Ruby -# * If you want to write documentation for extensions written in C see -# RDoc::Parser::C -# * If you want to generate documentation using rake see RDoc::Task. -# * If you want to drive RDoc programmatically, see RDoc::RDoc. -# * If you want to use the library to format text blocks into HTML, look at -# RDoc::Markup. -# * If you want to make an RDoc plugin such as a generator or directive -# handler see RDoc::RDoc. -# * If you want to write your own output generator see RDoc::Generator. +# If you think you found a bug in RDoc see DEVELOPERS@Bugs # -# == Summary +# If you want to use RDoc to create documentation for your Ruby source files, +# see RDoc::Markup and refer to rdoc --help for command line usage. # -# Once installed, you can create documentation using the +rdoc+ command +# If you want to set the default markup format see +# RDoc::Markup@Supported+Formats # -# % rdoc [options] [names...] +# If you want to store rdoc configuration in your gem (such as the default +# markup format) see RDoc::Options@Saved+Options # -# For an up-to-date option summary, type +# If you want to write documentation for Ruby files see RDoc::Parser::Ruby # -# % rdoc --help +# If you want to write documentation for extensions written in C see +# RDoc::Parser::C # -# A typical use might be to generate documentation for a package of Ruby -# source (such as RDoc itself). +# If you want to generate documentation using rake see RDoc::Task. # -# % rdoc +# If you want to drive RDoc programmatically, see RDoc::RDoc. # -# This command generates documentation for all the Ruby and C source -# files in and below the current directory. These will be stored in a -# documentation tree starting in the subdirectory +doc+. +# If you want to use the library to format text blocks into HTML or other +# formats, look at RDoc::Markup. # -# You can make this slightly more useful for your readers by having the -# index page contain the documentation for the primary file. In our -# case, we could type +# If you want to make an RDoc plugin such as a generator or directive handler +# see RDoc::RDoc. # -# % rdoc --main README.txt +# If you want to write your own output generator see RDoc::Generator. # -# You'll find information on the various formatting tricks you can use -# in comment blocks in the documentation this generates. +# If you want an overview of how RDoc works see DEVELOPERS # -# RDoc uses file extensions to determine how to process each file. File names -# ending +.rb+ and +.rbw+ are assumed to be Ruby source. Files -# ending +.c+ are parsed as C files. All other files are assumed to -# contain just Markup-style markup (with or without leading '#' comment -# markers). If directory names are passed to RDoc, they are scanned -# recursively for C and Ruby source files only. -# -# == Other stuff +# == Credits # # RDoc is currently being maintained by Eric Hodel . # # Dave Thomas is the original author of RDoc. # -# == Credits -# # * The Ruby parser in rdoc/parse.rb is based heavily on the outstanding # work of Keiju ISHITSUKA of Nippon Rational Inc, who produced the Ruby # parser for irb and the rtags package. @@ -92,19 +61,10 @@ module RDoc class Error < RuntimeError; end - def self.const_missing const_name # :nodoc: - if const_name.to_s == 'RDocError' then - warn "RDoc::RDocError is deprecated" - return Error - end - - super - end - ## # RDoc version you are using - VERSION = '3.9.4' + VERSION = '4.0' ## # Method visibilities @@ -143,5 +103,80 @@ module RDoc METHOD_MODIFIERS = GENERAL_MODIFIERS + %w[arg args yield yields notnew not-new not_new doc] + ## + # Loads the best available YAML library. + + def self.load_yaml + begin + gem 'psych' + rescue Gem::LoadError + end + + begin + require 'psych' + rescue ::LoadError + ensure + require 'yaml' + end + end + + autoload :RDoc, 'rdoc/rdoc' + + autoload :TestCase, 'rdoc/test_case' + + autoload :CrossReference, 'rdoc/cross_reference' + autoload :ERBIO, 'rdoc/erbio' + autoload :ERBPartial, 'rdoc/erb_partial' + autoload :Encoding, 'rdoc/encoding' + autoload :Generator, 'rdoc/generator' + autoload :Options, 'rdoc/options' + autoload :Parser, 'rdoc/parser' + autoload :Servlet, 'rdoc/servlet' + autoload :RI, 'rdoc/ri' + autoload :Stats, 'rdoc/stats' + autoload :Store, 'rdoc/store' + autoload :Task, 'rdoc/task' + autoload :Text, 'rdoc/text' + + autoload :Markdown, 'rdoc/markdown' + autoload :Markup, 'rdoc/markup' + autoload :RD, 'rdoc/rd' + autoload :TomDoc, 'rdoc/tom_doc' + + autoload :KNOWN_CLASSES, 'rdoc/known_classes' + + autoload :RubyLex, 'rdoc/ruby_lex' + autoload :RubyToken, 'rdoc/ruby_token' + autoload :TokenStream, 'rdoc/token_stream' + + autoload :Comment, 'rdoc/comment' + + # code objects + # + # We represent the various high-level code constructs that appear in Ruby + # programs: classes, modules, methods, and so on. + autoload :CodeObject, 'rdoc/code_object' + + autoload :Context, 'rdoc/context' + autoload :TopLevel, 'rdoc/top_level' + + autoload :AnonClass, 'rdoc/anon_class' + autoload :ClassModule, 'rdoc/class_module' + autoload :NormalClass, 'rdoc/normal_class' + autoload :NormalModule, 'rdoc/normal_module' + autoload :SingleClass, 'rdoc/single_class' + + autoload :Alias, 'rdoc/alias' + autoload :AnyMethod, 'rdoc/any_method' + autoload :MethodAttr, 'rdoc/method_attr' + autoload :GhostMethod, 'rdoc/ghost_method' + autoload :MetaMethod, 'rdoc/meta_method' + autoload :Attr, 'rdoc/attr' + + autoload :Constant, 'rdoc/constant' + autoload :Include, 'rdoc/include' + autoload :Extend, 'rdoc/extend' + autoload :Require, 'rdoc/require' + end diff --git a/lib/rdoc/alias.rb b/lib/rdoc/alias.rb index fa433dc0a9..39d2694817 100644 --- a/lib/rdoc/alias.rb +++ b/lib/rdoc/alias.rb @@ -1,5 +1,3 @@ -require 'rdoc/code_object' - ## # Represent an alias, which is an old_name/new_name pair associated with a # particular context diff --git a/lib/rdoc/anon_class.rb b/lib/rdoc/anon_class.rb index 63c09e11f1..c23d8e5d96 100644 --- a/lib/rdoc/anon_class.rb +++ b/lib/rdoc/anon_class.rb @@ -1,5 +1,3 @@ -require 'rdoc/class_module' - ## # An anonymous class like: # diff --git a/lib/rdoc/any_method.rb b/lib/rdoc/any_method.rb index c008edfe95..c3d68e87b4 100644 --- a/lib/rdoc/any_method.rb +++ b/lib/rdoc/any_method.rb @@ -1,12 +1,16 @@ -require 'rdoc/method_attr' -require 'rdoc/token_stream' - ## # AnyMethod is the base class for objects representing methods class RDoc::AnyMethod < RDoc::MethodAttr - MARSHAL_VERSION = 1 # :nodoc: + ## + # 2:: + # RDoc 4 + # Added calls_super + # Added parent name and class + # Added section title + + MARSHAL_VERSION = 2 # :nodoc: ## # Don't rename \#initialize to \::new @@ -28,6 +32,11 @@ class RDoc::AnyMethod < RDoc::MethodAttr attr_accessor :params + ## + # If true this method uses +super+ to call a superclass version + + attr_accessor :calls_super + include RDoc::TokenStream ## @@ -39,6 +48,8 @@ class RDoc::AnyMethod < RDoc::MethodAttr @c_function = nil @dont_rename_initialize = false @token_stream = nil + @calls_super = false + @superclass_method = nil end ## @@ -97,6 +108,10 @@ class RDoc::AnyMethod < RDoc::MethodAttr aliases, @params, @file.absolute_name, + @calls_super, + @parent.name, + @parent.class, + @section.title, ] end @@ -107,34 +122,44 @@ class RDoc::AnyMethod < RDoc::MethodAttr # * #full_name # * #parent_name - def marshal_load(array) + def marshal_load array @dont_rename_initialize = nil @is_alias_for = nil @token_stream = nil @aliases = [] + @parent = nil + @parent_name = nil + @parent_class = nil + @section = nil + @file = nil - version = array[0] - @name = array[1] - @full_name = array[2] - @singleton = array[3] - @visibility = array[4] - @comment = array[5] - @call_seq = array[6] - @block_params = array[7] + version = array[0] + @name = array[1] + @full_name = array[2] + @singleton = array[3] + @visibility = array[4] + @comment = array[5] + @call_seq = array[6] + @block_params = array[7] + # 8 handled below + @params = array[9] + # 10 handled below + @calls_super = array[11] + @parent_name = array[12] + @parent_title = array[13] + @section_title = array[14] array[8].each do |new_name, comment| add_alias RDoc::Alias.new(nil, @name, new_name, comment, @singleton) end - @params = array[9] - - @parent_name = if @full_name =~ /#/ then - $` - else - name = @full_name.split('::') - name.pop - name.join '::' - end + @parent_name ||= if @full_name =~ /#/ then + $` + else + name = @full_name.split('::') + name.pop + name.join '::' + end @file = RDoc::TopLevel.new array[10] if version > 0 end @@ -169,7 +194,9 @@ class RDoc::AnyMethod < RDoc::MethodAttr return [] end - params.gsub(/\s+/, '').split ',' + params = params.gsub(/\s+/, '').split ',' + + params.map { |param| param.sub(/=.*/, '') } end ## @@ -181,10 +208,12 @@ class RDoc::AnyMethod < RDoc::MethodAttr params = @call_seq.split("\n").last params = params.sub(/[^( ]+/, '') params = params.sub(/(\|[^|]+\|)\s*\.\.\.\s*(end|\})/, '\1 \2') - else + elsif @params then params = @params.gsub(/\s*\#.*/, '') params = params.tr("\n", " ").squeeze(" ") params = "(#{params})" unless params[0] == ?( + else + params = '' end if @block_params then @@ -203,5 +232,31 @@ class RDoc::AnyMethod < RDoc::MethodAttr params end + ## + # Sets the store for this method and its referenced code objects. + + def store= store + super + + @file = @store.add_file @file.full_name if @file + end + + ## + # For methods that +super+, find the superclass method that would be called. + + def superclass_method + return unless @calls_super + return @superclass_method if @superclass_method + + parent.each_ancestor do |ancestor| + if method = ancestor.method_list.find { |m| m.name == @name } then + @superclass_method = method + break + end + end + + @superclass_method + end + end diff --git a/lib/rdoc/attr.rb b/lib/rdoc/attr.rb index 5d9bc17831..0eb1c0d79b 100644 --- a/lib/rdoc/attr.rb +++ b/lib/rdoc/attr.rb @@ -1,12 +1,16 @@ -require 'rdoc/method_attr' - ## # An attribute created by \#attr, \#attr_reader, \#attr_writer or # \#attr_accessor class RDoc::Attr < RDoc::MethodAttr - MARSHAL_VERSION = 2 # :nodoc: + ## + # 3:: + # RDoc 4 + # Added parent name and class + # Added section title + + MARSHAL_VERSION = 3 # :nodoc: ## # Is the attribute readable ('R'), writable ('W') or both ('RW')? @@ -57,6 +61,16 @@ class RDoc::Attr < RDoc::MethodAttr 'attribute' end + ## + # Attributes never call super. See RDoc::AnyMethod#calls_super + # + # An RDoc::Attr can show up in the method list in some situations (see + # Gem::ConfigFile) + + def calls_super # :nodoc: + false + end + ## # Returns attr_reader, attr_writer or attr_accessor as appropriate. @@ -93,6 +107,9 @@ class RDoc::Attr < RDoc::MethodAttr parse(@comment), singleton, @file.absolute_name, + @parent.full_name, + @parent.class, + @section.title ] end @@ -104,17 +121,28 @@ class RDoc::Attr < RDoc::MethodAttr # * #parent_name def marshal_load array - version = array[0] - @name = array[1] - @full_name = array[2] - @rw = array[3] - @visibility = array[4] - @comment = array[5] - @singleton = array[6] || false # MARSHAL_VERSION == 0 + @aliases = [] + @parent = nil + @parent_name = nil + @parent_class = nil + @section = nil + @file = nil + + version = array[0] + @name = array[1] + @full_name = array[2] + @rw = array[3] + @visibility = array[4] + @comment = array[5] + @singleton = array[6] || false # MARSHAL_VERSION == 0 + # 7 handled below + @parent_name = array[8] + @parent_class = array[9] + @section_title = array[10] @file = RDoc::TopLevel.new array[7] if version > 1 - @parent_name = @full_name + @parent_name ||= @full_name.split('#', 2).first end def pretty_print q # :nodoc: @@ -132,5 +160,14 @@ class RDoc::Attr < RDoc::MethodAttr "#{definition} #{name} in: #{parent}" end + ## + # Attributes do not have token streams. + # + # An RDoc::Attr can show up in the method list in some situations (see + # Gem::ConfigFile) + + def token_stream # :nodoc: + end + end diff --git a/lib/rdoc/class_module.rb b/lib/rdoc/class_module.rb index 27066d8bd7..04f0132b7d 100644 --- a/lib/rdoc/class_module.rb +++ b/lib/rdoc/class_module.rb @@ -1,5 +1,3 @@ -require 'rdoc/context' - ## # ClassModule is the base class for objects representing either a class or a # module. @@ -13,8 +11,17 @@ class RDoc::ClassModule < RDoc::Context # * Added file to constants # * Added file to includes # * Added file to methods + # 2:: + # RDoc 3.13 + # * Added extends + # 3:: + # RDoc 4.0 + # * Added sections + # * Added in_files + # * Added parent name + # * Complete Constant dump - MARSHAL_VERSION = 1 # :nodoc: + MARSHAL_VERSION = 3 # :nodoc: ## # Constants that are aliases for this class or module @@ -56,6 +63,7 @@ class RDoc::ClassModule < RDoc::Context klass.external_aliases.concat mod.external_aliases klass.constants.concat mod.constants klass.includes.concat mod.includes + klass.extends.concat mod.extends klass.methods_hash.update mod.methods_hash klass.constants_hash.update mod.constants_hash @@ -84,6 +92,7 @@ class RDoc::ClassModule < RDoc::Context klass.external_aliases + klass.constants + klass.includes + + klass.extends + klass.classes + klass.modules).each do |obj| obj.parent = klass @@ -115,16 +124,32 @@ class RDoc::ClassModule < RDoc::Context # across multiple runs. def add_comment comment, location - return if comment.empty? or not document_self + return unless document_self original = comment - comment = normalize_comment comment + comment = case comment + when RDoc::Comment then + comment.normalize + else + normalize_comment comment + end @comment_location << [comment, location] self.comment = original end + def add_things my_things, other_things # :nodoc: + other_things.each do |group, things| + my_things[group].each { |thing| yield false, thing } if + my_things.include? group + + things.each do |thing| + yield true, thing + end + end + end + ## # Ancestors list for this ClassModule: the list of included modules # (classes will add their superclass if any). @@ -141,6 +166,11 @@ class RDoc::ClassModule < RDoc::Context includes.map { |i| i.module }.reverse end + ## + # Ancestors of this class or module only + + alias direct_ancestors ancestors + ## # Clears the comment. Used by the ruby parser. @@ -155,9 +185,13 @@ class RDoc::ClassModule < RDoc::Context # more like +=. def comment= comment - return if comment.empty? + comment = case comment + when RDoc::Comment then + comment.normalize + else + normalize_comment comment + end - comment = normalize_comment comment comment = "#{@comment}\n---\n#{comment}" unless @comment.empty? super comment @@ -166,7 +200,7 @@ class RDoc::ClassModule < RDoc::Context ## # Prepares this ClassModule for use by a generator. # - # See RDoc::TopLevel::complete + # See RDoc::Store#complete def complete min_visibility update_aliases @@ -175,13 +209,23 @@ class RDoc::ClassModule < RDoc::Context remove_invisible min_visibility end + ## + # Does this ClassModule or any of its methods have document_self set? + + def document_self_or_methods + document_self || method_list.any?{ |m| m.document_self } + end + ## # Iterates the ancestors of this class or module for which an # RDoc::ClassModule exists. def each_ancestor # :yields: module + return enum_for __method__ unless block_given? + ancestors.each do |mod| next if String === mod + next if self == mod yield mod end end @@ -215,8 +259,8 @@ class RDoc::ClassModule < RDoc::Context # Return the fully qualified name of this class or module def full_name - @full_name ||= if RDoc::ClassModule === @parent then - "#{@parent.full_name}::#{@name}" + @full_name ||= if RDoc::ClassModule === parent then + "#{parent.full_name}::#{@name}" else @name end @@ -250,13 +294,20 @@ class RDoc::ClassModule < RDoc::Context @superclass, parse(@comment_location), attrs, - constants.map do |const| - [const.name, parse(const.comment), const.file_name] - end, + constants, includes.map do |incl| [incl.name, parse(incl.comment), incl.file_name] end, method_types, + extends.map do |ext| + [ext.name, parse(ext.comment), ext.file_name] + end, + @sections.values, + @in_files.map do |tl| + tl.absolute_name + end, + parent.full_name, + parent.class, ] end @@ -268,6 +319,8 @@ class RDoc::ClassModule < RDoc::Context @parent = nil @temporary_section = nil @visibility = nil + @classes = {} + @modules = {} @name = array[1] @full_name = array[2] @@ -291,9 +344,14 @@ class RDoc::ClassModule < RDoc::Context attr.record_location RDoc::TopLevel.new file end - array[6].each do |name, comment, file| - const = add_constant RDoc::Constant.new(name, nil, comment) - const.record_location RDoc::TopLevel.new file + array[6].each do |constant, comment, file| + case constant + when RDoc::Constant then + add_constant constant + else + constant = add_constant RDoc::Constant.new(constant, nil, comment) + constant.record_location RDoc::TopLevel.new file + end end array[7].each do |name, comment, file| @@ -313,6 +371,27 @@ class RDoc::ClassModule < RDoc::Context end end end + + array[9].each do |name, comment, file| + ext = add_extend RDoc::Extend.new(name, comment) + ext.record_location RDoc::TopLevel.new file + end if array[9] # Support Marshal version 1 + + sections = (array[10] || []).map do |section| + [section.title, section] + end + + @sections = Hash[*sections.flatten] + @current_section = add_section nil + + @in_files = [] + + (array[11] || []).each do |filename| + record_location RDoc::TopLevel.new filename + end + + @parent_name = array[12] + @parent_class = array[13] end ## @@ -321,6 +400,9 @@ class RDoc::ClassModule < RDoc::Context # The data in +class_module+ is preferred over the receiver. def merge class_module + @parent = class_module.parent + @parent_name = class_module.parent_name + other_document = parse class_module.comment_location if other_document then @@ -360,6 +442,18 @@ class RDoc::ClassModule < RDoc::Context end end + @includes.uniq! # clean up + + merge_collections extends, cm.extends, other_files do |add, ext| + if add then + add_extend ext + else + @extends.delete ext + end + end + + @extends.uniq! # clean up + merge_collections method_list, cm.method_list, other_files do |add, meth| if add then add_method meth @@ -369,6 +463,8 @@ class RDoc::ClassModule < RDoc::Context end end + merge_sections cm + self end @@ -391,22 +487,46 @@ class RDoc::ClassModule < RDoc::Context my_things = mine. group_by { |thing| thing.file } other_things = other.group_by { |thing| thing.file } - my_things.delete_if do |file, things| - next false unless other_files.include? file + remove_things my_things, other_files, &block + add_things my_things, other_things, &block + end - things.each do |thing| - yield false, thing - end + ## + # Merges the comments in this ClassModule with the comments in the other + # ClassModule +cm+. - true + def merge_sections cm # :nodoc: + my_sections = sections.group_by { |section| section.title } + other_sections = cm.sections.group_by { |section| section.title } + + other_files = cm.in_files + + remove_things my_sections, other_files do |_, section| + @sections.delete section.title end - other_things.each do |file, things| - my_things[file].each { |thing| yield false, thing } if - my_things.include?(file) + other_sections.each do |group, sections| + if my_sections.include? group + my_sections[group].each do |my_section| + other_section = cm.sections_hash[group] - things.each do |thing| - yield true, thing + my_comments = my_section.comments + other_comments = other_section.comments + + other_files = other_section.in_files + + merge_collections my_comments, other_comments, other_files do |add, comment| + if add then + my_section.add_comment comment + else + my_section.remove_comment comment + end + end + end + else + sections.each do |section| + add_section group, section.comments + end end end end @@ -438,11 +558,15 @@ class RDoc::ClassModule < RDoc::Context when Array then docs = comment_location.map do |comment, location| doc = super comment - doc.file = location.absolute_name + doc.file = location doc end RDoc::Markup::Document.new(*docs) + when RDoc::Comment then + doc = super comment_location.text, comment_location.format + doc.file = comment_location.location + doc when RDoc::Markup::Document then return comment_location else @@ -451,10 +575,10 @@ class RDoc::ClassModule < RDoc::Context end ## - # Path to this class or module + # Path to this class or module for use with HTML generator output. def path - http_url RDoc::RDoc.current.generator.class_dir + http_url @store.rdoc.generator.class_dir end ## @@ -488,21 +612,61 @@ class RDoc::ClassModule < RDoc::Context modules_hash.each_key do |name| full_name = prefix + name - modules_hash.delete name unless RDoc::TopLevel.all_modules_hash[full_name] + modules_hash.delete name unless @store.modules_hash[full_name] end classes_hash.each_key do |name| full_name = prefix + name - classes_hash.delete name unless RDoc::TopLevel.all_classes_hash[full_name] + classes_hash.delete name unless @store.classes_hash[full_name] end end + def remove_things my_things, other_files # :nodoc: + my_things.delete_if do |file, things| + next false unless other_files.include? file + + things.each do |thing| + yield false, thing + end + + true + end + end + + ## + # Search record used by RDoc::Generator::JsonIndex + + def search_record + [ + name, + full_name, + full_name, + '', + path, + '', + snippet(@comment_location), + ] + end + + ## + # Sets the store for this class or module and its contained code objects. + + def store= store + super + + @attributes .each do |attr| attr.store = store end + @constants .each do |const| const.store = store end + @includes .each do |incl| incl.store = store end + @extends .each do |ext| ext.store = store end + @method_list.each do |meth| meth.store = store end + end + ## # Get the superclass of this class. Attempts to retrieve the superclass # object, returns the name if it is not known. def superclass - RDoc::TopLevel.find_class_named(@superclass) || @superclass + @store.find_class_named(@superclass) || @superclass end ## @@ -533,7 +697,7 @@ class RDoc::ClassModule < RDoc::Context # aliases through a constant. # # The aliased module/class is replaced in the children and in - # RDoc::TopLevel::all_modules_hash or RDoc::TopLevel::all_classes_hash + # RDoc::Store#modules_hash or RDoc::Store#classes_hash # by a copy that has RDoc::ClassModule#is_alias_for set to # the aliased module/class, and this copy is added to #aliases # of the aliased module/class. @@ -548,16 +712,21 @@ class RDoc::ClassModule < RDoc::Context next unless cm = const.is_alias_for cm_alias = cm.dup cm_alias.name = const.name - cm_alias.parent = self - cm_alias.full_name = nil # force update for new parent + + # Don't move top-level aliases under Object, they look ugly there + unless RDoc::TopLevel === cm_alias.parent then + cm_alias.parent = self + cm_alias.full_name = nil # force update for new parent + end + cm_alias.aliases.clear cm_alias.is_alias_for = cm if cm.module? then - RDoc::TopLevel.all_modules_hash[cm_alias.full_name] = cm_alias + @store.modules_hash[cm_alias.full_name] = cm_alias modules_hash[const.name] = cm_alias else - RDoc::TopLevel.all_classes_hash[cm_alias.full_name] = cm_alias + @store.classes_hash[cm_alias.full_name] = cm_alias classes_hash[const.name] = cm_alias end @@ -574,8 +743,26 @@ class RDoc::ClassModule < RDoc::Context def update_includes includes.reject! do |include| mod = include.module - !(String === mod) && RDoc::TopLevel.all_modules_hash[mod.full_name].nil? + !(String === mod) && @store.modules_hash[mod.full_name].nil? end + + includes.uniq! + end + + ## + # Deletes from #extends those whose module has been removed from the + # documentation. + #-- + # FIXME: like update_includes, extends are not reliably removed + + def update_extends + extends.reject! do |ext| + mod = ext.module + + !(String === mod) && @store.modules_hash[mod.full_name].nil? + end + + extends.uniq! end end diff --git a/lib/rdoc/code_object.rb b/lib/rdoc/code_object.rb index 54826fffbd..e2d9d909da 100644 --- a/lib/rdoc/code_object.rb +++ b/lib/rdoc/code_object.rb @@ -1,6 +1,3 @@ -require 'rdoc' -require 'rdoc/text' - ## # Base class for the RDoc code tree. # @@ -78,9 +75,9 @@ class RDoc::CodeObject attr_accessor :offset ## - # Our parent CodeObject + # Sets the parent CodeObject - attr_accessor :parent + attr_writer :parent ## # Did we ever receive a +:nodoc:+ directive? @@ -88,9 +85,14 @@ class RDoc::CodeObject attr_reader :received_nodoc ## - # Which section are we in + # Set the section this CodeObject is in - attr_accessor :section + attr_writer :section + + ## + # The RDoc::Store for this object. + + attr_accessor :store ## # We are the model of the code, but we know that at some point we will be @@ -103,11 +105,16 @@ class RDoc::CodeObject # Creates a new CodeObject that will document itself and its children def initialize - @metadata = {} - @comment = '' - @parent = nil - @file = nil - @full_name = nil + @metadata = {} + @comment = '' + @parent = nil + @parent_name = nil # for loading + @parent_class = nil # for loading + @section = nil + @section_title = nil # for loading + @file = nil + @full_name = nil + @store = nil @document_children = true @document_self = true @@ -124,11 +131,11 @@ class RDoc::CodeObject @comment = case comment when NilClass then '' when RDoc::Markup::Document then comment + when RDoc::Comment then comment.normalize else if comment and not comment.empty? then normalize_comment comment else - # TODO is this sufficient? # HACK correct fix is to have #initialize create @comment # with the correct encoding if String === @comment and @@ -216,7 +223,7 @@ class RDoc::CodeObject ## # Force the documentation of this object unless documentation - # has been turned off by :endoc: + # has been turned off by :enddoc: #-- # HACK untested, was assigning to an ivar @@ -261,6 +268,29 @@ class RDoc::CodeObject @ignored end + ## + # Our parent CodeObject. The parent may be missing for classes loaded from + # legacy RI data stores. + + def parent + return @parent if @parent + return nil unless @parent_name + + if @parent_class == RDoc::TopLevel then + @parent = @store.add_file @parent_name + else + @parent = @store.find_class_or_module @parent_name + + return @parent if @parent + + begin + @parent = @store.load_class @parent_name + rescue RDoc::Store::MissingFileError + nil + end + end + end + ## # File name of our parent @@ -283,9 +313,19 @@ class RDoc::CodeObject @file = top_level end + ## + # The section this CodeObject is in. Sections allow grouping of constants, + # attributes and methods inside a class or module. + + def section + return @section if @section + + @section = parent.add_section @section_title if parent + end + ## # Enable capture of documentation unless documentation has been - # turned off by :endoc: + # turned off by :enddoc: def start_doc return if @done_documenting diff --git a/lib/rdoc/code_objects.rb b/lib/rdoc/code_objects.rb index c60dad92df..f1a626cd2e 100644 --- a/lib/rdoc/code_objects.rb +++ b/lib/rdoc/code_objects.rb @@ -1,23 +1,5 @@ -# We represent the various high-level code constructs that appear in Ruby -# programs: classes, modules, methods, and so on. +# This file was used to load all the RDoc::CodeObject subclasses at once. Now +# autoload handles this. -require 'rdoc/code_object' -require 'rdoc/context' -require 'rdoc/top_level' - -require 'rdoc/class_module' -require 'rdoc/normal_class' -require 'rdoc/normal_module' -require 'rdoc/anon_class' -require 'rdoc/single_class' - -require 'rdoc/any_method' -require 'rdoc/alias' -require 'rdoc/ghost_method' -require 'rdoc/meta_method' - -require 'rdoc/attr' -require 'rdoc/constant' -require 'rdoc/require' -require 'rdoc/include' +require 'rdoc' diff --git a/lib/rdoc/comment.rb b/lib/rdoc/comment.rb new file mode 100644 index 0000000000..25e7c966c8 --- /dev/null +++ b/lib/rdoc/comment.rb @@ -0,0 +1,232 @@ +## +# A comment holds the text comment for a RDoc::CodeObject and provides a +# unified way of cleaning it up and parsing it into an RDoc::Markup::Document. +# +# Each comment may have a different markup format set by #format=. By default +# 'rdoc' is used. The :markup: directive tells RDoc which format to use. +# +# See RDoc::Markup@Other+directives for instructions on adding an alternate +# format. + +class RDoc::Comment + + include RDoc::Text + + ## + # The format of this comment. Defaults to RDoc::Markup + + attr_reader :format + + ## + # The RDoc::TopLevel this comment was found in + + attr_accessor :location + + ## + # For duck-typing when merging classes at load time + + alias file location # :nodoc: + + ## + # The text for this comment + + attr_reader :text + + ## + # Overrides the content returned by #parse. Use when there is no #text + # source for this comment + + attr_writer :document + + ## + # Creates a new comment with +text+ that is found in the RDoc::TopLevel + # +location+. + + def initialize text = nil, location = nil + @location = location + @text = text + + @document = nil + @format = 'rdoc' + @normalized = false + end + + ## + #-- + # TODO deep copy @document + + def initialize_copy copy # :nodoc: + @text = copy.text.dup + end + + def == other # :nodoc: + self.class === other and + other.text == @text and other.location == @location + end + + ## + # Look for a 'call-seq' in the comment to override the normal parameter + # handling. The :call-seq: is indented from the baseline. All lines of the + # same indentation level and prefix are consumed. + # + # For example, all of the following will be used as the :call-seq: + # + # # :call-seq: + # # ARGF.readlines(sep=$/) -> array + # # ARGF.readlines(limit) -> array + # # ARGF.readlines(sep, limit) -> array + # # + # # ARGF.to_a(sep=$/) -> array + # # ARGF.to_a(limit) -> array + # # ARGF.to_a(sep, limit) -> array + + def extract_call_seq method + # we must handle situations like the above followed by an unindented first + # comment. The difficulty is to make sure not to match lines starting + # with ARGF at the same indent, but that are after the first description + # paragraph. + if @text =~ /^\s*:?call-seq:(.*?(?:\S).*?)^\s*$/m then + all_start, all_stop = $~.offset(0) + seq_start, seq_stop = $~.offset(1) + + # we get the following lines that start with the leading word at the + # same indent, even if they have blank lines before + if $1 =~ /(^\s*\n)+^(\s*\w+)/m then + leading = $2 # ' * ARGF' in the example above + re = %r% + \A( + (^\s*\n)+ + (^#{Regexp.escape leading}.*?\n)+ + )+ + ^\s*$ + %xm + + if @text[seq_stop..-1] =~ re then + all_stop = seq_stop + $~.offset(0).last + seq_stop = seq_stop + $~.offset(1).last + end + end + + seq = @text[seq_start..seq_stop] + seq.gsub!(/^\s*(\S|\n)/m, '\1') + @text.slice! all_start...all_stop + + method.call_seq = seq.chomp + + elsif @text.sub!(/^\s*:?call-seq:(.*?)(^\s*$|\z)/m, '') then + seq = $1 + seq.gsub!(/^\s*/, '') + method.call_seq = seq + end + #elsif @text.sub!(/\A\/\*\s*call-seq:(.*?)\*\/\Z/, '') then + # method.call_seq = $1.strip + #end + + method + end + + ## + # A comment is empty if its text String is empty. + + def empty? + @text.empty? + end + + ## + # HACK dubious + + def force_encoding encoding + @text.force_encoding encoding + end + + ## + # Sets the format of this comment and resets any parsed document + + def format= format + @format = format + @document = nil + end + + def inspect # :nodoc: + location = @location ? @location.absolute_name : '(unknown)' + + "#<%s:%x %s %p>" % [self.class, object_id, location, @text] + end + + ## + # Normalizes the text. See RDoc::Text#normalize_comment for details + + def normalize + return self unless @text + return self if @normalized # TODO eliminate duplicate normalization + + @text = normalize_comment @text + + @normalized = true + + self + end + + ## + # Was this text normalized? + + def normalized? # :nodoc: + @normalized + end + + ## + # Parses the comment into an RDoc::Markup::Document. The parsed document is + # cached until the text is changed. + + def parse + return @document if @document + + @document = super @text, @format + @document.file = @location + @document + end + + ## + # Removes private sections from this comment. Private sections are flush to + # the comment marker and start with -- and end with ++. + # For C-style comments, a private marker may not start at the opening of the + # comment. + # + # /* + # *-- + # * private + # *++ + # * public + # */ + + def remove_private + # Workaround for gsub encoding for Ruby 1.9.2 and earlier + empty = '' + empty.force_encoding @text.encoding if Object.const_defined? :Encoding + + @text = @text.gsub(%r%^\s*([#*]?)--.*?^\s*(\1)\+\+\n?%m, empty) + @text = @text.sub(%r%^\s*[#*]?--.*%m, '') + end + + ## + # Replaces this comment's text with +text+ and resets the parsed document. + # + # An error is raised if the comment contains a document but no text. + + def text= text + raise RDoc::Error, 'replacing document-only comment is not allowed' if + @text.nil? and @document + + @document = nil + @text = text + end + + ## + # Returns true if this comment is in TomDoc format. + + def tomdoc? + @format == 'tomdoc' + end + +end + diff --git a/lib/rdoc/constant.rb b/lib/rdoc/constant.rb index 056ce130be..d9fcf021ed 100644 --- a/lib/rdoc/constant.rb +++ b/lib/rdoc/constant.rb @@ -1,16 +1,14 @@ -require 'rdoc/code_object' - ## # A constant class RDoc::Constant < RDoc::CodeObject - ## - # If this constant is an alias for a module or class, - # this is the RDoc::ClassModule it is an alias for. - # +nil+ otherwise. + MARSHAL_VERSION = 0 # :nodoc: - attr_accessor :is_alias_for + ## + # Sets the module or class this is constant is an alias for. + + attr_writer :is_alias_for ## # The constant's name @@ -22,14 +20,23 @@ class RDoc::Constant < RDoc::CodeObject attr_accessor :value + ## + # The constant's visibility + + attr_accessor :visibility + ## # Creates a new constant with +name+, +value+ and +comment+ def initialize(name, value, comment) super() - @name = name + + @name = name @value = value + @is_alias_for = nil + @visibility = nil + self.comment = comment end @@ -59,6 +66,27 @@ class RDoc::Constant < RDoc::CodeObject super or is_alias_for && is_alias_for.documented? end + ## + # Full constant name including namespace + + def full_name + @full_name ||= "#{parent_name}::#{@name}" + end + + ## + # The module or class this constant is an alias for + + def is_alias_for + case @is_alias_for + when String then + found = @store.find_class_or_module @is_alias_for + @is_alias_for = found if found + @is_alias_for + else + @is_alias_for + end + end + def inspect # :nodoc: "#<%s:0x%x %s::%s>" % [ self.class, object_id, @@ -67,12 +95,76 @@ class RDoc::Constant < RDoc::CodeObject end ## - # Path to this constant + # Dumps this Constant for use by ri. See also #marshal_load + + def marshal_dump + alias_name = case found = is_alias_for + when RDoc::CodeObject then found.full_name + else found + end + + [ MARSHAL_VERSION, + @name, + full_name, + @visibility, + alias_name, + parse(@comment), + @file.absolute_name, + parent.name, + parent.class, + section.title, + ] + end + + ## + # Loads this Constant from +array+. For a loaded Constant the following + # methods will return cached values: + # + # * #full_name + # * #parent_name + + def marshal_load array + initialize array[1], nil, array[5] + + @full_name = array[2] + @visibility = array[3] + @is_alias_for = array[4] + # 5 handled above + # 6 handled below + @parent_name = array[7] + @parent_class = array[8] + @section_title = array[9] + + @file = RDoc::TopLevel.new array[6] + end + + ## + # Path to this constant for use with HTML generator output. def path "#{@parent.path}##{@name}" end + def pretty_print q # :nodoc: + q.group 2, "[#{self.class.name} #{full_name}", "]" do + unless comment.empty? then + q.breakable + q.text "comment:" + q.breakable + q.pp @comment + end + end + end + + ## + # Sets the store for this class or module and its contained code objects. + + def store= store + super + + @file = @store.add_file @file.full_name if @file + end + def to_s # :nodoc: parent_name = parent ? parent.full_name : '(unknown)' if is_alias_for diff --git a/lib/rdoc/context.rb b/lib/rdoc/context.rb index abdab2026d..6f9deae4a8 100644 --- a/lib/rdoc/context.rb +++ b/lib/rdoc/context.rb @@ -1,4 +1,4 @@ -require 'rdoc/code_object' +require 'cgi' ## # A Context is something that can hold modules, classes, methods, attributes, @@ -14,6 +14,12 @@ class RDoc::Context < RDoc::CodeObject TYPES = %w[class instance] + ## + # If a context has these titles it will be sorted in this order. + + TOMDOC_TITLES = [nil, 'Public', 'Internal', 'Deprecated'] # :nodoc: + TOMDOC_TITLES_SORT = TOMDOC_TITLES.sort_by { |title| title.to_s } # :nodoc: + ## # Class/module aliases @@ -24,6 +30,11 @@ class RDoc::Context < RDoc::CodeObject attr_reader :attributes + ## + # Block params to be used in the next MethodAttr parsed under this context + + attr_accessor :block_params + ## # Constants defined @@ -44,6 +55,11 @@ class RDoc::Context < RDoc::CodeObject attr_reader :includes + ## + # Modules this context is extended with + + attr_reader :extends + ## # Methods defined in this context @@ -72,7 +88,7 @@ class RDoc::Context < RDoc::CodeObject attr_accessor :unmatched_alias_lists ## - # Aliases that could not eventually be resolved. + # Aliases that could not be resolved. attr_reader :external_aliases @@ -87,123 +103,16 @@ class RDoc::Context < RDoc::CodeObject attr_reader :methods_hash + ## + # Params to be used in the next MethodAttr parsed under this context + + attr_accessor :params + ## # Hash of registered constants. attr_reader :constants_hash - ## - # A section of documentation like: - # - # # :section: The title - # # The body - # - # Sections can be referenced multiple times and will be collapsed into a - # single section. - - class Section - - include RDoc::Text - - ## - # Section comment - - attr_reader :comment - - ## - # Context this Section lives in - - attr_reader :parent - - ## - # Section title - - attr_reader :title - - @@sequence = "SEC00000" - - ## - # Creates a new section with +title+ and +comment+ - - def initialize parent, title, comment - @parent = parent - @title = title ? title.strip : title - - @@sequence.succ! - @sequence = @@sequence.dup - - @comment = extract_comment comment - end - - ## - # Sections are equal when they have the same #title - - def == other - self.class === other and @title == other.title - end - - ## - # Anchor reference for linking to this section - - def aref - title = @title || '[untitled]' - - CGI.escape(title).gsub('%', '-').sub(/^-/, '') - end - - ## - # Appends +comment+ to the current comment separated by a rule. - - def comment= comment - comment = extract_comment comment - - return if comment.empty? - - if @comment then - @comment += "\n# ---\n#{comment}" - else - @comment = comment - end - end - - ## - # Extracts the comment for this section from the original comment block. - # If the first line contains :section:, strip it and use the rest. - # Otherwise remove lines up to the line containing :section:, and look - # for those lines again at the end and remove them. This lets us write - # - # # :section: The title - # # The body - - def extract_comment comment - if comment =~ /^#[ \t]*:section:.*\n/ then - start = $` - rest = $' - - if start.empty? then - rest - else - rest.sub(/#{start.chomp}\Z/, '') - end - else - comment - end - end - - def inspect # :nodoc: - "#<%s:0x%x %p>" % [self.class, object_id, title] - end - - ## - # Section sequence number (deprecated) - - def sequence - warn "RDoc::Context::Section#sequence is deprecated, use #aref" - @sequence - end - - end - ## # Creates an unnamed empty context with public current visibility @@ -235,6 +144,7 @@ class RDoc::Context < RDoc::CodeObject @aliases = [] @requires = [] @includes = [] + @extends = [] @constants = [] @external_aliases = [] @@ -242,8 +152,12 @@ class RDoc::Context < RDoc::CodeObject # a method not yet encountered). @unmatched_alias_lists = {} - @methods_hash = {} + @methods_hash = {} @constants_hash = {} + + @params = nil + + @store ||= nil end ## @@ -366,12 +280,12 @@ class RDoc::Context < RDoc::CodeObject if full_name =~ /^(.+)::(\w+)$/ then name = $2 ename = $1 - enclosing = RDoc::TopLevel.classes_hash[ename] || - RDoc::TopLevel.modules_hash[ename] + enclosing = @store.classes_hash[ename] || @store.modules_hash[ename] # HACK: crashes in actionpack/lib/action_view/helpers/form_helper.rb (metaprogramming) unless enclosing then # try the given name at top level (will work for the above example) - enclosing = RDoc::TopLevel.classes_hash[given_name] || RDoc::TopLevel.modules_hash[given_name] + enclosing = @store.classes_hash[given_name] || + @store.modules_hash[given_name] return enclosing if enclosing # not found: create the parent(s) names = ename.split('::') @@ -410,7 +324,7 @@ class RDoc::Context < RDoc::CodeObject end # did we believe it was a module? - mod = RDoc::TopLevel.modules_hash.delete superclass + mod = @store.modules_hash.delete superclass upgrade_to_class mod, RDoc::NormalClass, mod.parent if mod @@ -418,7 +332,7 @@ class RDoc::Context < RDoc::CodeObject superclass = nil if superclass == full_name end - klass = RDoc::TopLevel.classes_hash[full_name] + klass = @store.classes_hash[full_name] if klass then # if TopLevel, it may not be registered in the classes: @@ -435,7 +349,7 @@ class RDoc::Context < RDoc::CodeObject end else # this is a new class - mod = RDoc::TopLevel.modules_hash.delete full_name + mod = @store.modules_hash.delete full_name if mod then klass = upgrade_to_class mod, RDoc::NormalClass, enclosing @@ -445,10 +359,12 @@ class RDoc::Context < RDoc::CodeObject klass = class_type.new name, superclass enclosing.add_class_or_module(klass, enclosing.classes_hash, - RDoc::TopLevel.classes_hash) + @store.classes_hash) end end + klass.parent = self + klass end @@ -463,6 +379,7 @@ class RDoc::Context < RDoc::CodeObject mod.section = current_section # TODO declaring context? something is # wrong here... mod.parent = self + mod.store = @store unless @done_documenting then self_hash[mod.name] = mod @@ -504,12 +421,20 @@ class RDoc::Context < RDoc::CodeObject # Adds included module +include+ which should be an RDoc::Include def add_include include - add_to @includes, include unless - @includes.map { |i| i.full_name }.include? include.full_name + add_to @includes, include include end + ## + # Adds extension module +ext+ which should be an RDoc::Extend + + def add_extend ext + add_to @extends, ext + + ext + end + ## # Adds +method+ if not already there. If it is (as method or attribute), # updates the comment if it was empty. @@ -523,6 +448,10 @@ class RDoc::Context < RDoc::CodeObject if known then known.comment = method.comment if known.comment.empty? + previously = ", previously in #{known.file}" unless + method.file == known.file + @store.rdoc.options.warn \ + "Duplicate method #{known.full_name} in #{method.file}#{previously}" else @methods_hash[key] = method method.visibility = @visibility @@ -542,9 +471,9 @@ class RDoc::Context < RDoc::CodeObject return mod if mod full_name = child_name name - mod = RDoc::TopLevel.modules_hash[full_name] || class_type.new(name) + mod = @store.modules_hash[full_name] || class_type.new(name) - add_class_or_module(mod, @modules, RDoc::TopLevel.modules_hash) + add_class_or_module mod, @modules, @store.modules_hash end ## @@ -554,31 +483,34 @@ class RDoc::Context < RDoc::CodeObject def add_module_alias from, name, file return from if @done_documenting - to_name = child_name(name) + to_name = child_name name # if we already know this name, don't register an alias: # see the metaprogramming in lib/active_support/basic_object.rb, - # where we already know BasicObject as a class when we find + # where we already know BasicObject is a class when we find # BasicObject = BlankSlate - return from if RDoc::TopLevel.find_class_or_module(to_name) + return from if @store.find_class_or_module to_name - if from.module? then - RDoc::TopLevel.modules_hash[to_name] = from - @modules[name] = from + to = from.dup + to.name = name + to.full_name = nil + + if to.module? then + @store.modules_hash[to_name] = to + @modules[name] = to else - RDoc::TopLevel.classes_hash[to_name] = from - @classes[name] = from + @store.classes_hash[to_name] = to + @classes[name] = to end - # HACK: register a constant for this alias: - # constant value and comment will be updated after, - # when the Ruby parser adds the constant - const = RDoc::Constant.new name, nil, '' + # Registers a constant for this alias. The constant value and comment + # will be updated later, when the Ruby parser adds the constant + const = RDoc::Constant.new name, nil, to.comment const.record_location file const.is_alias_for = from add_constant const - from + to end ## @@ -602,9 +534,9 @@ class RDoc::Context < RDoc::CodeObject # # See also RDoc::Context::Section - def add_section title, comment + def add_section title, comment = nil if section = @sections[title] then - section.comment = comment + section.add_comment comment if comment else section = Section.new self, title, comment @sections[title] = section @@ -616,9 +548,11 @@ class RDoc::Context < RDoc::CodeObject ## # Adds +thing+ to the collection +array+ - def add_to(array, thing) + def add_to array, thing array << thing if @document_self - thing.parent = self + + thing.parent = self + thing.store = @store if @store thing.section = current_section end @@ -628,7 +562,7 @@ class RDoc::Context < RDoc::CodeObject # This means any of: comment, aliases, methods, attributes, external # aliases, require, constant. # - # Includes are also checked unless includes == false. + # Includes and extends are also checked unless includes == false. def any_content(includes = true) @any_content ||= !( @@ -640,7 +574,7 @@ class RDoc::Context < RDoc::CodeObject @requires.empty? && @constants.empty? ) - @any_content || (includes && !@includes.empty?) + @any_content || (includes && !(@includes + @extends).empty? ) end ## @@ -723,6 +657,9 @@ class RDoc::Context < RDoc::CodeObject ## # Iterator for ancestors for duck-typing. Does nothing. See # RDoc::ClassModule#each_ancestor. + # + # This method exists to make it easy to work with Context subclasses that + # aren't part of RDoc. def each_ancestor # :nodoc: end @@ -755,11 +692,20 @@ class RDoc::Context < RDoc::CodeObject @includes.each do |i| yield i end end + ## + # Iterator for extension modules + + def each_extend # :yields: extend + @extends.each do |e| yield e end + end + ## # Iterator for methods def each_method # :yields: method - @method_list.sort.each {|m| yield m} + return enum_for __method__ unless block_given? + + @method_list.sort.each { |m| yield m } end ## @@ -773,13 +719,15 @@ class RDoc::Context < RDoc::CodeObject # NOTE: Do not edit collections yielded by this method def each_section # :yields: section, constants, attributes - constants = @constants.group_by do |constant| constant.section end - constants.default = [] + return enum_for __method__ unless block_given? + constants = @constants.group_by do |constant| constant.section end attributes = @attributes.group_by do |attribute| attribute.section end + + constants.default = [] attributes.default = [] - @sections.sort_by { |title, _| title.to_s }.each do |_, section| + sort_sections.each do |section| yield section, constants[section].sort, attributes[section].sort end end @@ -851,8 +799,8 @@ class RDoc::Context < RDoc::CodeObject ## # Finds a file with +name+ in this context - def find_file_named(name) - top_level.class.find_file_named(name) + def find_file_named name + @store.find_file_named name end ## @@ -922,21 +870,21 @@ class RDoc::Context < RDoc::CodeObject # look for a class or module 'symbol' case symbol when /^::/ then - result = RDoc::TopLevel.find_class_or_module(symbol) + result = @store.find_class_or_module symbol when /^(\w+):+(.+)$/ suffix = $2 top = $1 searched = self - loop do + while searched do mod = searched.find_module_named(top) break unless mod - result = RDoc::TopLevel.find_class_or_module(mod.full_name + '::' + suffix) + result = @store.find_class_or_module "#{mod.full_name}::#{suffix}" break if result || searched.is_a?(RDoc::TopLevel) searched = searched.parent end else searched = self - loop do + while searched do result = searched.find_module_named(symbol) break if result || searched.is_a?(RDoc::TopLevel) searched = searched.parent @@ -985,6 +933,8 @@ class RDoc::Context < RDoc::CodeObject ## # Instance methods + #-- + # TODO rename to instance_methods def instance_method_list @instance_method_list ||= method_list.reject { |a| a.singleton } @@ -1098,24 +1048,23 @@ class RDoc::Context < RDoc::CodeObject ## # Only called when min_visibility == :public or :private - def remove_invisible_in(array, min_visibility) # :nodoc: - if min_visibility == :public + def remove_invisible_in array, min_visibility # :nodoc: + if min_visibility == :public then array.reject! { |e| e.visibility != :public and not e.force_documentation } else array.reject! { |e| - e.visibility == :private and - not e.force_documentation + e.visibility == :private and not e.force_documentation } end end ## - # Tries to resolve unmatched aliases when a method - # or attribute has just been added. + # Tries to resolve unmatched aliases when a method or attribute has just + # been added. - def resolve_aliases(added) + def resolve_aliases added # resolve any pending unmatched aliases key = added.pretty_name unmatched_alias_list = @unmatched_alias_lists[key] @@ -1127,6 +1076,31 @@ class RDoc::Context < RDoc::CodeObject @unmatched_alias_lists.delete key end + ## + # Returns RDoc::Context::Section objects referenced in this context for use + # in a table of contents. + + def section_contents + used_sections = {} + + each_method do |method| + next unless method.display? + + used_sections[method.section] = true + end + + # order found sections + sections = sort_sections.select do |section| + used_sections[section] + end + + # only the default section is used + return [] if + sections.length == 1 and not sections.first.title + + sections + end + ## # Sections in this context @@ -1155,6 +1129,26 @@ class RDoc::Context < RDoc::CodeObject end end + ## + # Sorts sections alphabetically (default) or in TomDoc fashion (none, + # Public, Internal, Deprecated) + + def sort_sections + titles = @sections.map { |title, _| title } + + if titles.length > 1 and + TOMDOC_TITLES_SORT == + (titles | TOMDOC_TITLES).sort_by { |title| title.to_s } then + @sections.values_at(*TOMDOC_TITLES).compact + else + @sections.sort_by { |title, _| + title.to_s + }.map { |_, section| + section + } + end + end + def to_s # :nodoc: "#{self.class.name} #{self.full_name}" end @@ -1179,13 +1173,16 @@ class RDoc::Context < RDoc::CodeObject enclosing.modules_hash.delete mod.name klass = RDoc::ClassModule.from_module class_type, mod + klass.store = @store # if it was there, then we keep it even if done_documenting - RDoc::TopLevel.classes_hash[mod.full_name] = klass - enclosing.classes_hash[mod.name] = klass + @store.classes_hash[mod.full_name] = klass + enclosing.classes_hash[mod.name] = klass klass end + autoload :Section, 'rdoc/context/section' + end diff --git a/lib/rdoc/context/section.rb b/lib/rdoc/context/section.rb new file mode 100644 index 0000000000..580f07deff --- /dev/null +++ b/lib/rdoc/context/section.rb @@ -0,0 +1,238 @@ +## +# A section of documentation like: +# +# # :section: The title +# # The body +# +# Sections can be referenced multiple times and will be collapsed into a +# single section. + +class RDoc::Context::Section + + include RDoc::Text + + MARSHAL_VERSION = 0 # :nodoc: + + ## + # Section comment + + attr_reader :comment + + ## + # Section comments + + attr_reader :comments + + ## + # Context this Section lives in + + attr_reader :parent + + ## + # Section title + + attr_reader :title + + @@sequence = "SEC00000" + + ## + # Creates a new section with +title+ and +comment+ + + def initialize parent, title, comment + @parent = parent + @title = title ? title.strip : title + + @@sequence.succ! + @sequence = @@sequence.dup + + @comments = [] + + add_comment comment + end + + ## + # Sections are equal when they have the same #title + + def == other + self.class === other and @title == other.title + end + + ## + # Adds +comment+ to this section + + def add_comment comment + comment = extract_comment comment + + return if comment.empty? + + case comment + when RDoc::Comment then + @comments << comment + when RDoc::Markup::Document then + @comments.concat comment.parts + when Array then + @comments.concat comment + else + raise TypeError, "unknown comment type: #{comment.inspect}" + end + end + + ## + # Anchor reference for linking to this section + + def aref + title = @title || '[untitled]' + + CGI.escape(title).gsub('%', '-').sub(/^-/, '') + end + + ## + # Extracts the comment for this section from the original comment block. + # If the first line contains :section:, strip it and use the rest. + # Otherwise remove lines up to the line containing :section:, and look + # for those lines again at the end and remove them. This lets us write + # + # # :section: The title + # # The body + + def extract_comment comment + case comment + when Array then + comment.map do |c| + extract_comment c + end + when nil + RDoc::Comment.new '' + when RDoc::Comment then + if comment.text =~ /^#[ \t]*:section:.*\n/ then + start = $` + rest = $' + + comment.text = if start.empty? then + rest + else + rest.sub(/#{start.chomp}\Z/, '') + end + end + + comment + when RDoc::Markup::Document then + comment + else + raise TypeError, "unknown comment #{comment.inspect}" + end + end + + def inspect # :nodoc: + "#<%s:0x%x %p>" % [self.class, object_id, title] + end + + ## + # The files comments in this section come from + + def in_files + return [] if @comments.empty? + + case @comments + when Array then + @comments.map do |comment| + comment.file + end + when RDoc::Markup::Document then + @comment.parts.map do |document| + document.file + end + else + raise RDoc::Error, "BUG: unknown comment class #{@comments.class}" + end + end + + ## + # Serializes this Section. The title and parsed comment are saved, but not + # the section parent which must be restored manually. + + def marshal_dump + [ + MARSHAL_VERSION, + @title, + parse, + ] + end + + ## + # De-serializes this Section. The section parent must be restored manually. + + def marshal_load array + @parent = nil + + @title = array[1] + @comments = array[2] + end + + ## + # Parses +comment_location+ into an RDoc::Markup::Document composed of + # multiple RDoc::Markup::Documents with their file set. + + def parse + case @comments + when String then + super + when Array then + docs = @comments.map do |comment, location| + doc = super comment + doc.file = location if location + doc + end + + RDoc::Markup::Document.new(*docs) + when RDoc::Comment then + doc = super @comments.text, comments.format + doc.file = @comments.location + doc + when RDoc::Markup::Document then + return @comments + else + raise ArgumentError, "unknown comment class #{comments.class}" + end + end + + ## + # The section's title, or 'Top Section' if the title is nil. + # + # This is used by the table of contents template so the name is silly. + + def plain_html + @title || 'Top Section' + end + + ## + # Removes a comment from this section if it is from the same file as + # +comment+ + + def remove_comment comment + return if @comments.empty? + + case @comments + when Array then + @comments.delete_if do |my_comment| + my_comment.file == comment.file + end + when RDoc::Markup::Document then + @comments.parts.delete_if do |document| + document.file == comment.file.name + end + else + raise RDoc::Error, "BUG: unknown comment class #{@comments.class}" + end + end + + ## + # Section sequence number (deprecated) + + def sequence + warn "RDoc::Context::Section#sequence is deprecated, use #aref" + @sequence + end + +end + diff --git a/lib/rdoc/cross_reference.rb b/lib/rdoc/cross_reference.rb index adeef2661a..c6f127387c 100644 --- a/lib/rdoc/cross_reference.rb +++ b/lib/rdoc/cross_reference.rb @@ -18,7 +18,7 @@ class RDoc::CrossReference # # See CLASS_REGEXP_STR - METHOD_REGEXP_STR = '([a-z]\w*[!?=]?)(?:\([\w.+*/=<>-]*\))?' + METHOD_REGEXP_STR = '([a-z]\w*[!?=]?|%)(?:\([\w.+*/=<>-]*\))?' ## # Regular expressions matching text that should potentially have @@ -27,63 +27,79 @@ class RDoc::CrossReference # have been suppressed, since the suppression characters are removed by the # code that is triggered. - CROSSREF_REGEXP = /( - # A::B::C.meth - #{CLASS_REGEXP_STR}(?:[.#]|::)#{METHOD_REGEXP_STR} + CROSSREF_REGEXP = /(?:^|\s) + ( + (?: + # A::B::C.meth + #{CLASS_REGEXP_STR}(?:[.#]|::)#{METHOD_REGEXP_STR} - # Stand-alone method (preceded by a #) - | \\?\##{METHOD_REGEXP_STR} + # Stand-alone method (preceded by a #) + | \\?\##{METHOD_REGEXP_STR} - # Stand-alone method (preceded by ::) - | ::#{METHOD_REGEXP_STR} + # Stand-alone method (preceded by ::) + | ::#{METHOD_REGEXP_STR} - # A::B::C - # The stuff after CLASS_REGEXP_STR is a - # nasty hack. CLASS_REGEXP_STR unfortunately matches - # words like dog and cat (these are legal "class" - # names in Fortran 95). When a word is flagged as a - # potential cross-reference, limitations in the markup - # engine suppress other processing, such as typesetting. - # This is particularly noticeable for contractions. - # In order that words like "can't" not - # be flagged as potential cross-references, only - # flag potential class cross-references if the character - # after the cross-reference is a space, sentence - # punctuation, tag start character, or attribute - # marker. - | #{CLASS_REGEXP_STR}(?=[\s\)\.\?\!\,\;<\000]|\z) + # A::B::C + # The stuff after CLASS_REGEXP_STR is a + # nasty hack. CLASS_REGEXP_STR unfortunately matches + # words like dog and cat (these are legal "class" + # names in Fortran 95). When a word is flagged as a + # potential cross-reference, limitations in the markup + # engine suppress other processing, such as typesetting. + # This is particularly noticeable for contractions. + # In order that words like "can't" not + # be flagged as potential cross-references, only + # flag potential class cross-references if the character + # after the cross-reference is a space, sentence + # punctuation, tag start character, or attribute + # marker. + | #{CLASS_REGEXP_STR}(?=[@\s).?!,;<\000]|\z) - # Things that look like filenames - # The key thing is that there must be at least - # one special character (period, slash, or - # underscore). - | (?:\.\.\/)*[-\/\w]+[_\/\.][-\w\/\.]+ + # Things that look like filenames + # The key thing is that there must be at least + # one special character (period, slash, or + # underscore). + | (?:\.\.\/)*[-\/\w]+[_\/.][-\w\/.]+ - # Things that have markup suppressed - # Don't process things like '\<' in \, though. - # TODO: including < is a hack, not very satisfying. - | \\[^\s<] - )/x + # Things that have markup suppressed + # Don't process things like '\<' in \, though. + # TODO: including < is a hack, not very satisfying. + | \\[^\s<] + ) + + # labels for headings + (?:@[\w+%-]+(?:\.[\w|%-]+)?)? + )/x ## # Version of CROSSREF_REGEXP used when --hyperlink-all is specified. - ALL_CROSSREF_REGEXP = /( - # A::B::C.meth - #{CLASS_REGEXP_STR}(?:[.#]|::)#{METHOD_REGEXP_STR} + ALL_CROSSREF_REGEXP = / + (?:^|\s) + ( + (?: + # A::B::C.meth + #{CLASS_REGEXP_STR}(?:[.#]|::)#{METHOD_REGEXP_STR} - # Stand-alone method - | \\?#{METHOD_REGEXP_STR} + # Stand-alone method + | \\?#{METHOD_REGEXP_STR} - # A::B::C - | #{CLASS_REGEXP_STR}(?=[\s\)\.\?\!\,\;<\000]|\z) + # A::B::C + | #{CLASS_REGEXP_STR}(?=[@\s).?!,;<\000]|\z) - # Things that look like filenames - | (?:\.\.\/)*[-\/\w]+[_\/\.][-\w\/\.]+ + # Things that look like filenames + | (?:\.\.\/)*[-\/\w]+[_\/.][-\w\/.]+ - # Things that have markup suppressed - | \\[^\s<] - )/x + # Things that have markup suppressed + | \\[^\s<] + ) + + # labels for headings + (?:@[\w+%-]+)? + )/x + + ## + # Hash of references that have been looked-up to their replacements attr_accessor :seen @@ -93,6 +109,7 @@ class RDoc::CrossReference def initialize context @context = context + @store = context.store @seen = {} end @@ -107,16 +124,6 @@ class RDoc::CrossReference def resolve name, text return @seen[name] if @seen.include? name - # Find class, module, or method in class or module. - # - # Do not, however, use an if/elsif/else chain to do so. Instead, test - # each possible pattern until one matches. The reason for this is that a - # string like "YAML.txt" could be the txt() class method of class YAML (in - # which case it would match the first pattern, which splits the string - # into container and method components and looks up both) or a filename - # (in which case it would match the last pattern, which just checks - # whether the string as a whole is a known symbol). - if /#{CLASS_REGEXP_STR}([.#]|::)#{METHOD_REGEXP_STR}/o =~ name then type = $2 type = '' if type == '.' # will find either #method or ::method @@ -141,12 +148,15 @@ class RDoc::CrossReference ref = case name when /^\\(#{CLASS_REGEXP_STR})$/o then - ref = @context.find_symbol $1 + @context.find_symbol $1 else - ref = @context.find_symbol name + @context.find_symbol name end unless ref - ref = nil if RDoc::Alias === ref # external alias: can't link to it + # Try a page name + ref = @store.page name if not ref and name =~ /^\w+$/ + + ref = nil if RDoc::Alias === ref # external alias, can't link to it out = if name == '\\' then name diff --git a/lib/rdoc/encoding.rb b/lib/rdoc/encoding.rb index ab752ee665..0b1ec6728e 100644 --- a/lib/rdoc/encoding.rb +++ b/lib/rdoc/encoding.rb @@ -1,7 +1,5 @@ # coding: US-ASCII -require 'rdoc' - ## # This class is a wrapper around File IO and Encoding that helps RDoc load # files and convert them to the correct encoding. @@ -27,26 +25,40 @@ module RDoc::Encoding RDoc::Encoding.set_encoding content if Object.const_defined? :Encoding then - encoding ||= Encoding.default_external - orig_encoding = content.encoding + begin + encoding ||= Encoding.default_external + orig_encoding = content.encoding - if utf8 then - content.force_encoding Encoding::UTF_8 - content.encode! encoding - else - # assume the content is in our output encoding - content.force_encoding encoding - end + if utf8 then + content.force_encoding Encoding::UTF_8 + content.encode! encoding + else + # assume the content is in our output encoding + content.force_encoding encoding + end - unless content.valid_encoding? then - # revert and try to transcode - content.force_encoding orig_encoding - content.encode! encoding - end + unless content.valid_encoding? then + # revert and try to transcode + content.force_encoding orig_encoding + content.encode! encoding + end - unless content.valid_encoding? then - warn "unable to convert #{filename} to #{encoding}, skipping" - content = nil + unless content.valid_encoding? then + warn "unable to convert #{filename} to #{encoding}, skipping" + content = nil + end + rescue Encoding::InvalidByteSequenceError, + Encoding::UndefinedConversionError => e + if force_transcode then + content.force_encoding orig_encoding + content.encode!(encoding, + :invalid => :replace, :undef => :replace, + :replace => '?') + return content + else + warn "unable to convert #{e.message} for #{filename}, skipping" + return nil + end end end @@ -55,15 +67,6 @@ module RDoc::Encoding raise unless e.message =~ /unknown encoding name - (.*)/ warn "unknown encoding name \"#{$1}\" for #{filename}, skipping" nil - rescue Encoding::UndefinedConversionError => e - if force_transcode then - content.force_encoding orig_encoding - content.encode! encoding, :undef => :replace, :replace => '?' - content - else - warn "unable to convert #{e.message} for #{filename}, skipping" - nil - end rescue Errno::EISDIR, Errno::ENOENT nil end diff --git a/lib/rdoc/erb_partial.rb b/lib/rdoc/erb_partial.rb new file mode 100644 index 0000000000..910d1e0351 --- /dev/null +++ b/lib/rdoc/erb_partial.rb @@ -0,0 +1,18 @@ +## +# Allows an ERB template to be rendered in the context (binding) of an +# existing ERB template evaluation. + +class RDoc::ERBPartial < ERB + + ## + # Overrides +compiler+ startup to set the +eoutvar+ to an empty string only + # if it isn't already set. + + def set_eoutvar compiler, eoutvar = '_erbout' + super + + compiler.pre_cmd = ["#{eoutvar} ||= ''"] + end + +end + diff --git a/lib/rdoc/extend.rb b/lib/rdoc/extend.rb new file mode 100644 index 0000000000..2bccfba084 --- /dev/null +++ b/lib/rdoc/extend.rb @@ -0,0 +1,117 @@ +## +# A Module extension in a class with \#extend + +class RDoc::Extend < RDoc::CodeObject + + ## + # Name of extension module + + attr_accessor :name + + ## + # Creates a new Extend for +name+ with +comment+ + + def initialize(name, comment) + super() + @name = name + self.comment = comment + @module = nil # cache for module if found + end + + ## + # Extends are sorted by name + + def <=> other + return unless self.class === other + + name <=> other.name + end + + def == other # :nodoc: + self.class === other and @name == other.name + end + + alias eql? == + + ## + # Full name based on #module + + def full_name + m = self.module + RDoc::ClassModule === m ? m.full_name : @name + end + + def hash # :nodoc: + [@name, self.module].hash + end + + def inspect # :nodoc: + "#<%s:0x%x %s.extend %s>" % [ + self.class, + object_id, + parent_name, @name, + ] + end + + ## + # Attempts to locate the extend module object. Returns the name if not + # known. + # + # The scoping rules of Ruby to resolve the name of an extension module are: + # - first look into the children of the current context; + # - if not found, look into the children of extension modules, + # in reverse extend order; + # - if still not found, go up the hierarchy of names. + # + # This method has O(n!) behavior when the module calling + # extend is referencing nonexistent modules. Avoid calling #module until + # after all the files are parsed. This behavior is due to ruby's constant + # lookup behavior. + + def module + return @module if @module + + # search the current context + return @name unless parent + full_name = parent.child_name(@name) + @module = @store.modules_hash[full_name] + return @module if @module + return @name if @name =~ /^::/ + + # search the includes before this one, in reverse order + searched = parent.extends.take_while { |i| i != self }.reverse + searched.each do |i| + ext = i.module + next if String === ext + full_name = ext.child_name(@name) + @module = @store.modules_hash[full_name] + return @module if @module + end + + # go up the hierarchy of names + up = parent.parent + while up + full_name = up.child_name(@name) + @module = @store.modules_hash[full_name] + return @module if @module + up = up.parent + end + + @name + end + + ## + # Sets the store for this class or module and its contained code objects. + + def store= store + super + + @file = @store.add_file @file.full_name if @file + end + + def to_s # :nodoc: + "extend #@name in: #{parent}" + end + +end + diff --git a/lib/rdoc/generator.rb b/lib/rdoc/generator.rb index 2fa891f533..9051f8a658 100644 --- a/lib/rdoc/generator.rb +++ b/lib/rdoc/generator.rb @@ -1,12 +1,10 @@ -require 'rdoc' - ## # RDoc uses generators to turn parsed source code in the form of an # RDoc::CodeObject tree into some form of output. RDoc comes with the HTML # generator RDoc::Generator::Darkfish and an ri data generator # RDoc::Generator::RI. # -# = Registering a Generator +# == Registering a Generator # # Generators are registered by calling RDoc::RDoc.add_generator with the class # of the generator: @@ -15,26 +13,38 @@ require 'rdoc' # RDoc::RDoc.add_generator self # end # -# = Adding Options to +rdoc+ +# == Adding Options to +rdoc+ # # Before option processing in +rdoc+, RDoc::Options will call ::setup_options # on the generator class with an RDoc::Options instance. The generator can # use RDoc::Options#option_parser to add command-line options to the +rdoc+ -# tool. See OptionParser for details on how to add options. +# tool. See RDoc::Options@Custom+Options for an example and see OptionParser +# for details on how to add options. # # You can extend the RDoc::Options instance with additional accessors for your # generator. # -# = Generator Instantiation +# == Generator Instantiation # # After parsing, RDoc::RDoc will instantiate a generator by calling -# #initialize with an RDoc::Options instance. +# #initialize with an RDoc::Store instance and an RDoc::Options instance. # -# RDoc will then call #generate on the generator instance and pass in an Array -# of RDoc::TopLevel instances, each representing a parsed file. You can use -# the various class methods on RDoc::TopLevel and in the RDoc::CodeObject tree -# to create your desired output format. +# The RDoc::Store instance holds documentation for parsed source code. In +# RDoc 3 and earlier the RDoc::TopLevel class held this data. When upgrading +# a generator from RDoc 3 and earlier you should only need to replace +# RDoc::TopLevel with the store instance. +# +# RDoc will then call #generate on the generator instance. You can use the +# various methods on RDoc::Store and in the RDoc::CodeObject tree to create +# your desired output format. module RDoc::Generator + + autoload :Markup, 'rdoc/generator/markup' + + autoload :Darkfish, 'rdoc/generator/darkfish' + autoload :JsonIndex, 'rdoc/generator/json_index' + autoload :RI, 'rdoc/generator/ri' + end diff --git a/lib/rdoc/generator/darkfish.rb b/lib/rdoc/generator/darkfish.rb index a3ffea0ce8..bd0f617d84 100644 --- a/lib/rdoc/generator/darkfish.rb +++ b/lib/rdoc/generator/darkfish.rb @@ -1,9 +1,8 @@ # -*- mode: ruby; ruby-indent-level: 2; tab-width: 2 -*- -require 'pathname' +require 'erb' require 'fileutils' -require 'rdoc/erbio' - +require 'pathname' require 'rdoc/generator/markup' ## @@ -46,6 +45,11 @@ require 'rdoc/generator/markup' # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# == Attributions +# +# Darkfish uses the {Silk Icons}[http://www.famfamfam.com/lab/icons/silk/] set +# by Mark James. class RDoc::Generator::Darkfish @@ -53,6 +57,7 @@ class RDoc::Generator::Darkfish include ERB::Util + ## # Path to this file's parent directory. Used to find templates and other # resources. @@ -61,7 +66,7 @@ class RDoc::Generator::Darkfish ## # Release Version - VERSION = '2' + VERSION = '3' ## # Description of this generator @@ -69,25 +74,87 @@ class RDoc::Generator::Darkfish DESCRIPTION = 'HTML generator, written by Michael Granger' ## - # Initialize a few instance variables before we start + # The relative path to style sheets and javascript. By default this is set + # the same as the rel_prefix. - def initialize options - @options = options + attr_accessor :asset_rel_path - @template_dir = Pathname.new options.template_dir - @template_cache = {} + ## + # The path to generate files into, combined with --op from the + # options for a full path. - @files = nil - @classes = nil + attr_reader :base_dir - @basedir = Pathname.pwd.expand_path - end + ## + # Classes and modules to be used by this generator, not necessarily + # displayed. See also #modsort + + attr_reader :classes + + ## + # No files will be written when dry_run is true. + + attr_accessor :dry_run + + ## + # When false the generate methods return a String instead of writing to a + # file. The default is true. + + attr_accessor :file_output + + ## + # Files to be displayed by this generator + + attr_reader :files + + ## + # The JSON index generator for this Darkfish generator + + attr_reader :json_index + + ## + # Methods to be displayed by this generator + + attr_reader :methods + + ## + # Sorted list of classes and modules to be displayed by this generator + + attr_reader :modsort + + ## + # The RDoc::Store that is the source of the generated content + + attr_reader :store ## # The output directory attr_reader :outputdir + ## + # Initialize a few instance variables before we start + + def initialize store, options + @store = store + @options = options + + @asset_rel_path = '' + @base_dir = Pathname.pwd.expand_path + @dry_run = @options.dry_run + @file_output = true + @template_dir = Pathname.new options.template_dir + @template_cache = {} + + @classes = nil + @context = nil + @files = nil + @methods = nil + @modsort = nil + + @json_index = RDoc::Generator::JsonIndex.new self, options + end + ## # Output progress information if debugging is enabled @@ -126,7 +193,7 @@ class RDoc::Generator::Darkfish def write_style_sheet debug_msg "Copying static files" - options = { :verbose => $DEBUG_RDOC, :noop => @options.dry_run } + options = { :verbose => $DEBUG_RDOC, :noop => @dry_run } FileUtils.cp @template_dir + 'rdoc.css', '.', options @@ -134,7 +201,7 @@ class RDoc::Generator::Darkfish next if File.directory? path next if File.basename(path) =~ /^\./ - dst = Pathname.new(path).relative_path_from @template_dir + dst = Pathname.new(path).relative_path_from @template_dir # I suck at glob dst_dir = dst.dirname @@ -148,19 +215,17 @@ class RDoc::Generator::Darkfish # Build the initial indices and output objects based on an array of TopLevel # objects containing the extracted information. - def generate top_levels - @outputdir = Pathname.new(@options.op_dir).expand_path(@basedir) + def generate + setup - @files = top_levels.sort - @classes = RDoc::TopLevel.all_classes_and_modules.sort - @methods = @classes.map { |m| m.method_list }.flatten.sort - @modsort = get_sorted_module_list(@classes) - - # Now actually write the output write_style_sheet generate_index generate_class_files generate_file_files + generate_table_of_contents + @json_index.generate + + copy_static rescue => e debug_msg "%s: %s\n %s" % [ @@ -170,42 +235,64 @@ class RDoc::Generator::Darkfish raise end - protected + ## + # Copies static files from the static_path into the output directory + + def copy_static + return if @options.static_path.empty? + + fu_options = { :verbose => $DEBUG_RDOC, :noop => @dry_run } + + @options.static_path.each do |path| + unless File.directory? path then + FileUtils.install path, @outputdir, fu_options.merge(:mode => 0644) + next + end + + Dir.chdir path do + Dir[File.join('**', '*')].each do |entry| + dest_file = @outputdir + entry + + if File.directory? entry then + FileUtils.mkdir_p entry, fu_options + else + FileUtils.install entry, dest_file, fu_options.merge(:mode => 0644) + end + end + end + end + end ## # Return a list of the documented modules sorted by salience first, then # by name. - def get_sorted_module_list(classes) - nscounts = classes.inject({}) do |counthash, klass| - top_level = klass.full_name.gsub(/::.*/, '') - counthash[top_level] ||= 0 - counthash[top_level] += 1 - - counthash - end - - # Sort based on how often the top level namespace occurs, and then on the - # name of the module -- this works for projects that put their stuff into - # a namespace, of course, but doesn't hurt if they don't. - classes.sort_by do |klass| - top_level = klass.full_name.gsub( /::.*/, '' ) - [nscounts[top_level] * -1, klass.full_name] - end.select do |klass| + def get_sorted_module_list classes + classes.select do |klass| klass.display? - end + end.sort end ## # Generate an index page which lists all the classes which are documented. def generate_index + setup + template_file = @template_dir + 'index.rhtml' return unless template_file.exist? debug_msg "Rendering the index page..." - out_file = @basedir + @options.op_dir + 'index.html' + out_file = @base_dir + @options.op_dir + 'index.html' + rel_prefix = @outputdir.relative_path_from out_file.dirname + search_index_rel_prefix = rel_prefix + search_index_rel_prefix += @asset_rel_path if @file_output + + # suppress 1.9.3 warning + asset_rel_prefix = asset_rel_prefix = rel_prefix + @asset_rel_path + + @title = @options.title render_template template_file, out_file do |io| binding end rescue => e @@ -217,10 +304,40 @@ class RDoc::Generator::Darkfish end ## - # Generate a documentation file for each class + # Generates a class file for +klass+ + + def generate_class klass, template_file = nil + setup + + current = klass + + template_file ||= @template_dir + 'class.rhtml' + + debug_msg " working on %s (%s)" % [klass.full_name, klass.path] + out_file = @outputdir + klass.path + rel_prefix = @outputdir.relative_path_from out_file.dirname + search_index_rel_prefix = rel_prefix + search_index_rel_prefix += @asset_rel_path if @file_output + + # suppress 1.9.3 warning + asset_rel_prefix = asset_rel_prefix = rel_prefix + @asset_rel_path + svninfo = svninfo = get_svninfo(current) + + @title = "#{klass.type} #{klass.full_name} - #{@options.title}" + + debug_msg " rendering #{out_file}" + render_template template_file, out_file do |io| binding end + end + + ## + # Generate a documentation file for each class and module def generate_class_files - template_file = @template_dir + 'classpage.rhtml' + setup + + template_file = @template_dir + 'class.rhtml' + template_file = @template_dir + 'classpage.rhtml' unless + template_file.exist? return unless template_file.exist? debug_msg "Generating class documentation in #{@outputdir}" @@ -228,14 +345,8 @@ class RDoc::Generator::Darkfish @classes.each do |klass| current = klass - debug_msg " working on %s (%s)" % [klass.full_name, klass.path] - out_file = @outputdir + klass.path - # suppress 1.9.3 warning - rel_prefix = rel_prefix = @outputdir.relative_path_from(out_file.dirname) - svninfo = svninfo = self.get_svninfo(klass) - debug_msg " rendering #{out_file}" - render_template template_file, out_file do |io| binding end + generate_class klass, template_file end rescue => e error = RDoc::Error.new \ @@ -249,19 +360,56 @@ class RDoc::Generator::Darkfish # Generate a documentation file for each file def generate_file_files - template_file = @template_dir + 'filepage.rhtml' - return unless template_file.exist? + setup + + page_file = @template_dir + 'page.rhtml' + fileinfo_file = @template_dir + 'fileinfo.rhtml' + + # for legacy templates + filepage_file = @template_dir + 'filepage.rhtml' unless + page_file.exist? or fileinfo_file.exist? + + return unless + page_file.exist? or fileinfo_file.exist? or filepage_file.exist? + debug_msg "Generating file documentation in #{@outputdir}" out_file = nil + current = nil @files.each do |file| - out_file = @outputdir + file.path - debug_msg " working on %s (%s)" % [file.full_name, out_file] - # suppress 1.9.3 warning - rel_prefix = rel_prefix = @outputdir.relative_path_from(out_file.dirname) + current = file + + if file.text? and page_file.exist? then + generate_page file + next + end + + template_file = nil + out_file = @outputdir + file.path + debug_msg " working on %s (%s)" % [file.full_name, out_file] + rel_prefix = @outputdir.relative_path_from out_file.dirname + search_index_rel_prefix = rel_prefix + search_index_rel_prefix += @asset_rel_path if @file_output + + # suppress 1.9.3 warning + asset_rel_prefix = asset_rel_prefix = rel_prefix + @asset_rel_path + + unless filepage_file then + if file.text? then + next unless page_file.exist? + template_file = page_file + @title = file.page_name + else + next unless fileinfo_file.exist? + template_file = fileinfo_file + @title = "File: #{file.base_name}" + end + end + + @title += " - #{@options.title}" + template_file ||= filepage_file - debug_msg " rendering #{out_file}" render_template template_file, out_file do |io| binding end end rescue => e @@ -272,6 +420,134 @@ class RDoc::Generator::Darkfish raise error end + ## + # Generate a page file for +file+ + + def generate_page file + setup + + template_file = @template_dir + 'page.rhtml' + + out_file = @outputdir + file.path + debug_msg " working on %s (%s)" % [file.full_name, out_file] + rel_prefix = @outputdir.relative_path_from out_file.dirname + search_index_rel_prefix = rel_prefix + search_index_rel_prefix += @asset_rel_path if @file_output + + # suppress 1.9.3 warning + current = current = file + asset_rel_prefix = asset_rel_prefix = rel_prefix + @asset_rel_path + + @title = "#{file.page_name} - #{@options.title}" + + debug_msg " rendering #{out_file}" + render_template template_file, out_file do |io| binding end + end + + ## + # Generates the 404 page for the RDoc servlet + + def generate_servlet_not_found path + setup + + template_file = @template_dir + 'servlet_not_found.rhtml' + return unless template_file.exist? + + debug_msg "Rendering the servlet root page..." + + rel_prefix = rel_prefix = '' + search_index_rel_prefix = rel_prefix + search_index_rel_prefix += @asset_rel_path if @file_output + + # suppress 1.9.3 warning + asset_rel_prefix = asset_rel_prefix = '' + + @title = 'Not Found' + + render_template template_file do |io| binding end + rescue => e + error = RDoc::Error.new \ + "error generating servlet_root: #{e.message} (#{e.class})" + error.set_backtrace e.backtrace + + raise error + end + + ## + # Generates the servlet root page for the RDoc servlet + + def generate_servlet_root installed + setup + + template_file = @template_dir + 'servlet_root.rhtml' + return unless template_file.exist? + + debug_msg 'Rendering the servlet root page...' + + rel_prefix = rel_prefix = '' + search_index_rel_prefix = rel_prefix + search_index_rel_prefix += @asset_rel_path if @file_output + + # suppress 1.9.3 warning + asset_rel_prefix = asset_rel_prefix = '' + + @title = 'Local RDoc Documentation' + + render_template template_file do |io| binding end + rescue => e + error = RDoc::Error.new \ + "error generating servlet_root: #{e.message} (#{e.class})" + error.set_backtrace e.backtrace + + raise error + end + + ## + # Generate an index page which lists all the classes which are documented. + + def generate_table_of_contents + setup + + template_file = @template_dir + 'table_of_contents.rhtml' + return unless template_file.exist? + + debug_msg "Rendering the Table of Contents..." + + out_file = @outputdir + 'table_of_contents.html' + rel_prefix = @outputdir.relative_path_from out_file.dirname + search_index_rel_prefix = rel_prefix + search_index_rel_prefix += @asset_rel_path if @file_output + + # suppress 1.9.3 warning + asset_rel_prefix = asset_rel_prefix = rel_prefix + @asset_rel_path + + @title = "Table of Contents - #{@options.title}" + + render_template template_file, out_file do |io| binding end + rescue => e + error = RDoc::Error.new \ + "error generating table_of_contents.html: #{e.message} (#{e.class})" + error.set_backtrace e.backtrace + + raise error + end + + ## + # Prepares for generation of output from the current directory + + def setup + return if instance_variable_defined? :@outputdir + + @outputdir = Pathname.new(@options.op_dir).expand_path @base_dir + + return unless @store + + @classes = @store.all_classes_and_modules.sort + @files = @store.all_files.sort + @methods = @classes.map { |m| m.method_list }.flatten.sort + @modsort = get_sorted_module_list @classes + end + ## # Return a string describing the amount of time in the given number of # seconds in terms a human can understand easily. @@ -324,6 +600,46 @@ class RDoc::Generator::Darkfish } end + ## + # Creates a template from its components and the +body_file+. + # + # For backwards compatibility, if +body_file+ contains " + + + +#{head_file.read} + +#{body} + +#{footer_file.read} + TEMPLATE + end + + ## + # Renders the ERb contained in +file_name+ relative to the template + # directory and returns the result based on the current context. + + def render file_name + template_file = @template_dir + file_name + + template = template_for template_file, false, RDoc::ERBPartial + + template.filename = template_file.to_s + + template.result @context + end + ## # Load and render the erb template in the given +template_file+ and write # it out to +out_file+. @@ -332,28 +648,33 @@ class RDoc::Generator::Darkfish # # An io will be yielded which must be captured by binding in the caller. - def render_template template_file, out_file # :yield: io - template = template_for template_file + def render_template template_file, out_file = nil # :yield: io + io_output = out_file && !@dry_run && @file_output + erb_klass = io_output ? RDoc::ERBIO : ERB - unless @options.dry_run then + template = template_for template_file, true, erb_klass + + if io_output then debug_msg "Outputting to %s" % [out_file.expand_path] out_file.dirname.mkpath out_file.open 'w', 0644 do |io| io.set_encoding @options.encoding if Object.const_defined? :Encoding - context = yield io + @context = yield io - template_result template, context, template_file + template_result template, @context, template_file end else - context = yield nil + @context = yield nil - output = template_result template, context, template_file + output = template_result template, @context, template_file debug_msg " would have written %d characters to %s" % [ output.length, out_file.expand_path - ] + ] if @dry_run + + output end end @@ -374,14 +695,25 @@ class RDoc::Generator::Darkfish ## # Retrieves a cache template for +file+, if present, or fills the cache. - def template_for file + def template_for file, page = true, klass = ERB template = @template_cache[file] return template if template - klass = @options.dry_run ? ERB : RDoc::ERBIO + template = if page then + assemble_template file + else + file.read + end - template = klass.new file.read, nil, '<>' + erbout = if page then + 'io' + else + file_var = File.basename(file).sub(/\..*/, '') + "_erbout_#{file_var}" + end + + template = klass.new template, nil, '<>', erbout @template_cache[file] = template template end diff --git a/lib/rdoc/generator/json_index.rb b/lib/rdoc/generator/json_index.rb new file mode 100644 index 0000000000..c303b2effb --- /dev/null +++ b/lib/rdoc/generator/json_index.rb @@ -0,0 +1,248 @@ +require 'json' + +## +# The JsonIndex generator is designed to complement an HTML generator and +# produces a JSON search index. This generator is derived from sdoc by +# Vladimir Kolesnikov and contains verbatim code written by him. +# +# This generator is designed to be used with a regular HTML generator: +# +# class RDoc::Generator::Darkfish +# def initialize options +# # ... +# @base_dir = Pathname.pwd.expand_path +# +# @json_index = RDoc::Generator::JsonIndex.new self, options +# end +# +# def generate +# # ... +# @json_index.generate +# end +# end +# +# == Index Format +# +# The index is output as a JSON file assigned to the global variable +# +search_data+. The structure is: +# +# var search_data = { +# "index": { +# "searchIndex": +# ["a", "b", ...], +# "longSearchIndex": +# ["a", "a::b", ...], +# "info": [ +# ["A", "A", "A.html", "", ""], +# ["B", "A::B", "A::B.html", "", ""], +# ... +# ] +# } +# } +# +# The same item is described across the +searchIndex+, +longSearchIndex+ and +# +info+ fields. The +searchIndex+ field contains the item's short name, the +# +longSearchIndex+ field contains the full_name (when appropriate) and the +# +info+ field contains the item's name, full_name, path, parameters and a +# snippet of the item's comment. +# +# == LICENSE +# +# Copyright (c) 2009 Vladimir Kolesnikov +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +class RDoc::Generator::JsonIndex + + include RDoc::Text + + ## + # Where the search index lives in the generated output + + SEARCH_INDEX_FILE = File.join 'js', 'search_index.js' + + attr_reader :index # :nodoc: + + ## + # Creates a new generator. +parent_generator+ is used to determine the + # class_dir and file_dir of links in the output index. + # + # +options+ are the same options passed to the parent generator. + + def initialize parent_generator, options + @parent_generator = parent_generator + @store = parent_generator.store + @options = options + + @template_dir = File.expand_path '../template/json_index', __FILE__ + @base_dir = @parent_generator.base_dir + + @classes = nil + @files = nil + @index = nil + end + + ## + # Builds the JSON index as a Hash. + + def build_index + reset @store.all_files.sort, @store.all_classes_and_modules.sort + + index_classes + index_methods + index_pages + + { :index => @index } + end + + ## + # Output progress information if debugging is enabled + + def debug_msg *msg + return unless $DEBUG_RDOC + $stderr.puts(*msg) + end + + ## + # Writes the JSON index to disk + + def generate + debug_msg "Generating JSON index" + + debug_msg " writing search index to %s" % SEARCH_INDEX_FILE + data = build_index + + return if @options.dry_run + + out_dir = @base_dir + @options.op_dir + index_file = out_dir + SEARCH_INDEX_FILE + + FileUtils.mkdir_p index_file.dirname, :verbose => $DEBUG_RDOC + + index_file.open 'w', 0644 do |io| + io.set_encoding Encoding::UTF_8 if Object.const_defined? :Encoding + io.write 'var search_data = ' + + JSON.dump data, io, 0 + end + + Dir.chdir @template_dir do + Dir['**/*.js'].each do |source| + dest = File.join out_dir, source + + FileUtils.install source, dest, :mode => 0644, :verbose => $DEBUG_RDOC + end + end + end + + ## + # Adds classes and modules to the index + + def index_classes + debug_msg " generating class search index" + + documented = @classes.uniq.select do |klass| + klass.document_self_or_methods + end + + documented.each do |klass| + debug_msg " #{klass.full_name}" + record = klass.search_record + @index[:searchIndex] << search_string(record.shift) + @index[:longSearchIndex] << search_string(record.shift) + @index[:info] << record + end + end + + ## + # Adds methods to the index + + def index_methods + debug_msg " generating method search index" + + list = @classes.uniq.map do |klass| + klass.method_list + end.flatten.sort_by do |method| + [method.name, method.parent.full_name] + end + + list.each do |method| + debug_msg " #{method.full_name}" + record = method.search_record + @index[:searchIndex] << "#{search_string record.shift}()" + @index[:longSearchIndex] << "#{search_string record.shift}()" + @index[:info] << record + end + end + + ## + # Adds pages to the index + + def index_pages + debug_msg " generating pages search index" + + pages = @files.select do |file| + file.text? + end + + pages.each do |page| + debug_msg " #{page.page_name}" + record = page.search_record + @index[:searchIndex] << search_string(record.shift) + @index[:longSearchIndex] << '' + record.shift + @index[:info] << record + end + end + + ## + # The directory classes are written to + + def class_dir + @parent_generator.class_dir + end + + ## + # The directory files are written to + + def file_dir + @parent_generator.file_dir + end + + def reset files, classes # :nodoc: + @files = files + @classes = classes + + @index = { + :searchIndex => [], + :longSearchIndex => [], + :info => [] + } + end + + ## + # Removes whitespace and downcases +string+ + + def search_string string + string.downcase.gsub(/\s/, '') + end + +end + diff --git a/lib/rdoc/generator/markup.rb b/lib/rdoc/generator/markup.rb index c267bb1c13..3b3546690e 100644 --- a/lib/rdoc/generator/markup.rb +++ b/lib/rdoc/generator/markup.rb @@ -1,14 +1,8 @@ -# This file is loaded by generators. It allows RDoc's CodeObject tree to -# avoid loading generator code to increase startup time (for ri). - -require 'rdoc/text' -require 'rdoc/code_objects' -require 'rdoc/generator' -require 'rdoc/markup/to_html_crossref' -require 'rdoc/ruby_token' - ## # Handle common RDoc::Markup tasks for various CodeObjects +# +# This module is loaded by generators. It allows RDoc's CodeObject tree to +# avoid loading generator code to improve startup time for +ri+. module RDoc::Generator::Markup @@ -39,18 +33,18 @@ module RDoc::Generator::Markup def formatter return @formatter if defined? @formatter - show_hash = RDoc::RDoc.current.options.show_hash - hyperlink_all = RDoc::RDoc.current.options.hyperlink_all + options = @store.rdoc.options this = RDoc::Context === self ? self : @parent - @formatter = RDoc::Markup::ToHtmlCrossref.new(this.path, this, show_hash, - hyperlink_all) + @formatter = RDoc::Markup::ToHtmlCrossref.new options, this.path, this + @formatter.code_object = self + @formatter end ## # Build a webcvs URL starting for the given +url+ with +full_path+ appended # as the destination path. If +url+ contains '%s' +full_path+ will be - # sprintf'd into +url+ instead. + # will replace the %s using sprintf on the +url+. def cvs_url(url, full_path) if /%s/ =~ url then @@ -62,10 +56,14 @@ module RDoc::Generator::Markup end -class RDoc::AnyMethod +class RDoc::CodeObject include RDoc::Generator::Markup +end + +class RDoc::MethodAttr + @add_line_numbers = false class << self @@ -82,7 +80,8 @@ class RDoc::AnyMethod # # # File xxxxx, line dddd # - # If it has, line numbers are added an ', line dddd' is removed. + # If it has this comment then line numbers are added to +src+ and the , + # line dddd portion of the comment is removed. def add_line_numbers(src) return unless src.sub!(/\A(.*)(, line (\d+))/, '\1') @@ -111,32 +110,7 @@ class RDoc::AnyMethod def markup_code return '' unless @token_stream - src = "" - - @token_stream.each do |t| - next unless t - - style = case t - when RDoc::RubyToken::TkCONSTANT then 'ruby-constant' - when RDoc::RubyToken::TkKW then 'ruby-keyword' - when RDoc::RubyToken::TkIVAR then 'ruby-ivar' - when RDoc::RubyToken::TkOp then 'ruby-operator' - when RDoc::RubyToken::TkId then 'ruby-identifier' - when RDoc::RubyToken::TkNode then 'ruby-node' - when RDoc::RubyToken::TkCOMMENT then 'ruby-comment' - when RDoc::RubyToken::TkREGEXP then 'ruby-regexp' - when RDoc::RubyToken::TkSTRING then 'ruby-string' - when RDoc::RubyToken::TkVal then 'ruby-value' - end - - text = CGI.escapeHTML t.text - - if style then - src << "#{text}" - else - src << text - end - end + src = RDoc::TokenStream.to_html @token_stream # dedent the source indent = src.length @@ -151,34 +125,21 @@ class RDoc::AnyMethod end src.gsub!(/^#{' ' * indent}/, '') if indent > 0 - add_line_numbers(src) if self.class.add_line_numbers + add_line_numbers(src) if RDoc::MethodAttr.add_line_numbers src end end -class RDoc::Attr +class RDoc::ClassModule - include RDoc::Generator::Markup + ## + # Handy wrapper for marking up this class or module's comment -end - -class RDoc::Alias - - include RDoc::Generator::Markup - -end - -class RDoc::Constant - - include RDoc::Generator::Markup - -end - -class RDoc::Context - - include RDoc::Generator::Markup + def description + markup @comment_location + end end @@ -195,7 +156,7 @@ class RDoc::TopLevel # command line option to set. def cvs_url - url = RDoc::RDoc.current.options.webcvs + url = @store.rdoc.options.webcvs if /%s/ =~ url then url % @absolute_name diff --git a/lib/rdoc/generator/ri.rb b/lib/rdoc/generator/ri.rb index 939a165cfb..b9c4141a5e 100644 --- a/lib/rdoc/generator/ri.rb +++ b/lib/rdoc/generator/ri.rb @@ -1,6 +1,3 @@ -require 'rdoc/generator' -require 'rdoc/ri' - ## # Generates ri data files @@ -16,70 +13,17 @@ class RDoc::Generator::RI ## # Set up a new ri generator - def initialize options #:not-new: - @options = options - @old_siginfo = nil - @current = nil - - @store = RDoc::RI::Store.new '.' - @store.dry_run = @options.dry_run - @store.encoding = @options.encoding if @options.respond_to? :encoding + def initialize store, options #:not-new: + @options = options + @store = store + @store.path = '.' end ## - # Build the initial indices and output objects based on an array of TopLevel - # objects containing the extracted information. + # Writes the parsed data store to disk for use by ri. - def generate top_levels - install_siginfo_handler - - @store.load_cache - - RDoc::TopLevel.all_classes_and_modules.each do |klass| - @current = "#{klass.class}: #{klass.full_name}" - - @store.save_class klass - - klass.each_method do |method| - @current = "#{method.class}: #{method.full_name}" - @store.save_method klass, method - end - - klass.each_attribute do |attribute| - @store.save_method klass, attribute - end - end - - @current = 'saving cache' - - @store.save_cache - - ensure - @current = nil - - remove_siginfo_handler - end - - ## - # Installs a siginfo handler that prints the current filename. - - def install_siginfo_handler - return unless Signal.list.key? 'INFO' - - @old_siginfo = trap 'INFO' do - puts @current if @current - end - end - - ## - # Removes a siginfo handler and replaces the previous - - def remove_siginfo_handler - return unless Signal.list.key? 'INFO' - - handler = @old_siginfo || 'DEFAULT' - - trap 'INFO', handler + def generate + @store.save end end diff --git a/lib/rdoc/generator/template/darkfish/_footer.rhtml b/lib/rdoc/generator/template/darkfish/_footer.rhtml new file mode 100644 index 0000000000..0736c335ba --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/_footer.rhtml @@ -0,0 +1,5 @@ + diff --git a/lib/rdoc/generator/template/darkfish/_head.rhtml b/lib/rdoc/generator/template/darkfish/_head.rhtml new file mode 100644 index 0000000000..f3d82a37f6 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/_head.rhtml @@ -0,0 +1,16 @@ + + +<%= h @title %> + + + + + + + + + + + diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_VCS_info.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_VCS_info.rhtml new file mode 100644 index 0000000000..93d57f39f6 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/_sidebar_VCS_info.rhtml @@ -0,0 +1,18 @@ +<% if !svninfo.empty? then %> + +<% end %> diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_classes.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_classes.rhtml new file mode 100644 index 0000000000..efa202fa18 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/_sidebar_classes.rhtml @@ -0,0 +1,9 @@ + diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_extends.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_extends.rhtml new file mode 100644 index 0000000000..19273829a0 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/_sidebar_extends.rhtml @@ -0,0 +1,16 @@ +<% unless klass.extends.empty? then %> + + +<% end %> diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_in_files.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_in_files.rhtml new file mode 100644 index 0000000000..c4ae216a14 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/_sidebar_in_files.rhtml @@ -0,0 +1,8 @@ + diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_includes.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_includes.rhtml new file mode 100644 index 0000000000..5494f1f5f8 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/_sidebar_includes.rhtml @@ -0,0 +1,16 @@ +<% unless klass.includes.empty? then %> + + +<% end %> diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_installed.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_installed.rhtml new file mode 100644 index 0000000000..45a3048e84 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/_sidebar_installed.rhtml @@ -0,0 +1,14 @@ + diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_methods.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_methods.rhtml new file mode 100644 index 0000000000..88e2734819 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/_sidebar_methods.rhtml @@ -0,0 +1,12 @@ +<% unless klass.method_list.empty? then %> + + +<% end %> diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_navigation.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_navigation.rhtml new file mode 100644 index 0000000000..fdeb6aed9f --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/_sidebar_navigation.rhtml @@ -0,0 +1,7 @@ + diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_pages.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_pages.rhtml new file mode 100644 index 0000000000..2089387c51 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/_sidebar_pages.rhtml @@ -0,0 +1,12 @@ +<% simple_files = @files.select { |f| f.text? } %> +<% unless simple_files.empty? then %> + +<% end %> diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_parent.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_parent.rhtml new file mode 100644 index 0000000000..463f05a8d9 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/_sidebar_parent.rhtml @@ -0,0 +1,10 @@ +<% if klass.type == 'class' then %> + +<% end %> diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_search.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_search.rhtml new file mode 100644 index 0000000000..f3275783d0 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/_sidebar_search.rhtml @@ -0,0 +1,10 @@ + diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_sections.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_sections.rhtml new file mode 100644 index 0000000000..726423a341 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/_sidebar_sections.rhtml @@ -0,0 +1,10 @@ +<% unless klass.sections.length == 1 then %> + +<% end %> diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_table_of_contents.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_table_of_contents.rhtml new file mode 100644 index 0000000000..225f811f0e --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/_sidebar_table_of_contents.rhtml @@ -0,0 +1,13 @@ +<% table = current.parse(current.comment).table_of_contents + if table.length > 1 then %> +
+ +
+<% end %> diff --git a/lib/rdoc/generator/template/darkfish/class.rhtml b/lib/rdoc/generator/template/darkfish/class.rhtml new file mode 100644 index 0000000000..c7e52e6808 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/class.rhtml @@ -0,0 +1,179 @@ + + + +
+

<%= klass.type %> <%= klass.full_name %>

+ +
+ <%= klass.description %> +
+ + <% klass.each_section do |section, constants, attributes| %> + <% constants = constants.select { |const| const.display? } %> + <% attributes = attributes.select { |attr| attr.display? } %> +
+ <% if section.title then %> +
+

+ <%= section.title %> +

+ + ↑ top + +
+ <% end %> + + <% if section.comment then %> +
+ <%= section.description %> +
+ <% end %> + + <% unless constants.empty? then %> + +
+

Constants

+
+ <% constants.each do |const| %> +
<%= const.name %> + <% if const.comment then %> +
<%= const.description.strip %> + <% else %> +
(Not documented) + <% end %> + <% end %> +
+
+ <% end %> + + <% unless attributes.empty? then %> + +
+

Attributes

+ + <% attributes.each do |attrib| %> +
+
+ <%= h attrib.name %>[<%= attrib.rw %>] +
+ +
+ <% if attrib.comment then %> + <%= attrib.description.strip %> + <% else %> +

(Not documented) + <% end %> +

+
+ <% end %> +
+ <% end %> + + + <% klass.methods_by_type(section).each do |type, visibilities| + next if visibilities.empty? + visibilities.each do |visibility, methods| + next if methods.empty? %> +
+

<%= visibility.to_s.capitalize %> <%= type.capitalize %> Methods

+ + <% methods.each do |method| %> +
"> + <% if method.call_seq then %> + <% method.call_seq.strip.split("\n").each_with_index do |call_seq, i| %> +
+ + <%= h(call_seq.strip. + gsub( /^\w+\./m, '')). + gsub(/(.*)[-=]>/, '\1→') %> + + <% if i == 0 and method.token_stream then %> + click to toggle source + <% end %> +
+ <% end %> + <% else %> +
+ <%= h method.name %><%= method.param_seq %> + <% if method.token_stream then %> + click to toggle source + <% end %> +
+ <% end %> + +
+ <% if method.comment then %> + <%= method.description.strip %> + <% else %> +

(Not documented) + <% end %> + <% if method.calls_super then %> +

+ Calls superclass method + <%= + method.superclass_method ? + method.formatter.link(method.superclass_method.full_name, method.superclass_method.full_name) : nil + %> +
+ <% end %> + + <% if method.token_stream then %> +
+
<%= method.markup_code %>
+
+ <% end %> +
+ + <% unless method.aliases.empty? then %> +
+ Also aliased as: <%= method.aliases.map do |aka| + if aka.parent then # HACK lib/rexml/encodings + %{#{h aka.name}} + else + h aka.name + end + end.join ", " %> +
+ <% end %> + + <% if method.is_alias_for then %> + + <% end %> +
+ + <% end %> +
+ <% end + end %> +
+<% end %> + +
diff --git a/lib/rdoc/generator/template/darkfish/classpage.rhtml b/lib/rdoc/generator/template/darkfish/classpage.rhtml deleted file mode 100644 index 9c74cacf0f..0000000000 --- a/lib/rdoc/generator/template/darkfish/classpage.rhtml +++ /dev/null @@ -1,321 +0,0 @@ -"?> - - - - - - <%= klass.type.capitalize %>: <%= klass.full_name %> - - - - - - - - - - - -
-
-
-

- Home - Classes - Methods -

-
-
- -
-
-

In Files

-
- -
-
- - <% if !svninfo.empty? then %> -
-

Subversion Info

-
-
-
Rev
-
<%= svninfo[:rev] %>
- -
Last Checked In
-
<%= svninfo[:commitdate].strftime('%Y-%m-%d %H:%M:%S') %> - (<%= svninfo[:commitdelta] %> ago)
- -
Checked in by
-
<%= svninfo[:committer] %>
-
-
-
- <% end %> -
- -
- <% if klass.type == 'class' then %> - -
-

Parent

- <% if klass.superclass and not String === klass.superclass then %> - - <% else %> - - <% end %> -
- <% end %> - - <% unless klass.sections.length == 1 then %> - -
-

Sections

- -
- <% end %> - - <% unless klass.classes_and_modules.empty? then %> - -
-

Namespace

- -
- <% end %> - - <% unless klass.method_list.empty? then %> - -
-

Methods

- -
- <% end %> - - <% unless klass.includes.empty? then %> - -
-

Included Modules

- -
- <% end %> -
- -
- <% simple_files = @files.select {|tl| tl.parser == RDoc::Parser::Simple } %> - <% unless simple_files.empty? then %> -
-

Files

- -
- <% end %> - -
-

Class/Module Index - [+]

-
-
- Quicksearch - -
-
- - - -
- - <% if $DEBUG_RDOC then %> -
toggle debugging
- <% end %> -
-
- -
-

<%= klass.full_name %>

- -
- <%= klass.description %> -
- - <% klass.each_section do |section, constants, attributes| %> - <% constants = constants.select { |const| const.display? } %> - <% attributes = attributes.select { |attr| attr.display? } %> -
- <% if section.title then %> -

- <%= section.title %> - ↑ top -

- <% end %> - - <% if section.comment then %> -
- <%= section.description %> -
- <% end %> - - <% unless constants.empty? then %> - -
-

Constants

-
- <% constants.each do |const| %> -
<%= const.name %>
- <% if const.comment then %> -
<%= const.description.strip %>
- <% else %> -
(Not documented)
- <% end %> - <% end %> -
-
- <% end %> - - <% unless attributes.empty? then %> - -
-

Attributes

- - <% attributes.each do |attrib| %> -
- - <% if attrib.rw =~ /w/i then %> - - <% end %> -
- <%= h attrib.name %>[<%= attrib.rw %>] -
- -
- <% if attrib.comment then %> - <%= attrib.description.strip %> - <% else %> -

(Not documented)

- <% end %> -
-
- <% end %> -
- <% end %> - - - <% klass.methods_by_type(section).each do |type, visibilities| - next if visibilities.empty? - visibilities.each do |visibility, methods| - next if methods.empty? %> -
-

<%= visibility.to_s.capitalize %> <%= type.capitalize %> Methods

- - <% methods.each do |method| %> -
"> - - - <% if method.call_seq then %> - <% method.call_seq.strip.split("\n").each_with_index do |call_seq, i| %> -
- <%= call_seq.strip.gsub(/->/, '→').gsub( /^\w+\./m, '') %> - <% if i == 0 then %> - click to toggle source - <% end %> -
- <% end %> - <% else %> -
- <%= h method.name %><%= method.params %> - click to toggle source -
- <% end %> - -
- <% if method.comment then %> - <%= method.description.strip %> - <% else %> -

(Not documented)

- <% end %> - - <% if method.token_stream then %> -
-
-<%= method.markup_code %>
-
-
- <% end %> -
- - <% unless method.aliases.empty? then %> -
- Also aliased as: <%= method.aliases.map do |aka| - if aka.parent then # HACK lib/rexml/encodings - %{#{h aka.name}} - else - h aka.name - end - end.join ", " %> -
- <% end %> - - <% if method.is_alias_for then %> - - <% end %> -
- - <% end %> -
- <% end - end %> -
- <% end %> - -
- -
-

[Validate]

-

Generated with the Darkfish - Rdoc Generator <%= RDoc::Generator::Darkfish::VERSION %>.

-
- - - - diff --git a/lib/rdoc/generator/template/darkfish/filepage.rhtml b/lib/rdoc/generator/template/darkfish/filepage.rhtml deleted file mode 100644 index b230a456a3..0000000000 --- a/lib/rdoc/generator/template/darkfish/filepage.rhtml +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - File: <%= file.base_name %> [<%= @options.title %>] - - - - - - - - - -<% if file.parser == RDoc::Parser::Simple %> - -
-
-
-

- Home - Classes - Methods -

-
-
- -
- <% simple_files = @files.select { |f| f.parser == RDoc::Parser::Simple } %> - <% unless simple_files.empty? then %> -
-

Files

- -
- <% end %> - -
-

Class Index - [+]

-
-
- Quicksearch - -
-
- - - -
- - <% if $DEBUG_RDOC %> -
toggle debugging
- <% end %> -
-
- -
- <%= file.description %> -
- -
-

[Validate]

-

Generated with the Darkfish - Rdoc Generator <%= RDoc::Generator::Darkfish::VERSION %>.

-
- -<% else %> - -
-
-
Last Modified
-
<%= file.last_modified %>
- - <% if file.requires %> -
Requires
-
-
    - <% file.requires.each do |require| %> -
  • <%= require.name %>
  • - <% end %> -
-
- <% end %> - - <% if @options.webcvs %> -
Trac URL
-
<%= file.cvs_url %>
- <% end %> -
-
- -
- <% if file.comment %> -
-

Description

- <%= file.description %> -
- <% end %> -
- -<% end %> - - diff --git a/lib/rdoc/generator/template/darkfish/images/add.png b/lib/rdoc/generator/template/darkfish/images/add.png new file mode 100755 index 0000000000..6332fefea4 Binary files /dev/null and b/lib/rdoc/generator/template/darkfish/images/add.png differ diff --git a/lib/rdoc/generator/template/darkfish/images/arrow_up.png b/lib/rdoc/generator/template/darkfish/images/arrow_up.png new file mode 100755 index 0000000000..1ebb193243 Binary files /dev/null and b/lib/rdoc/generator/template/darkfish/images/arrow_up.png differ diff --git a/lib/rdoc/generator/template/darkfish/images/delete.png b/lib/rdoc/generator/template/darkfish/images/delete.png new file mode 100755 index 0000000000..08f249365a Binary files /dev/null and b/lib/rdoc/generator/template/darkfish/images/delete.png differ diff --git a/lib/rdoc/generator/template/darkfish/images/tag_blue.png b/lib/rdoc/generator/template/darkfish/images/tag_blue.png new file mode 100755 index 0000000000..3f02b5f8f8 Binary files /dev/null and b/lib/rdoc/generator/template/darkfish/images/tag_blue.png differ diff --git a/lib/rdoc/generator/template/darkfish/images/transparent.png b/lib/rdoc/generator/template/darkfish/images/transparent.png new file mode 100644 index 0000000000..d665e179ef Binary files /dev/null and b/lib/rdoc/generator/template/darkfish/images/transparent.png differ diff --git a/lib/rdoc/generator/template/darkfish/index.rhtml b/lib/rdoc/generator/template/darkfish/index.rhtml index 3198246f8a..d076c2a252 100644 --- a/lib/rdoc/generator/template/darkfish/index.rhtml +++ b/lib/rdoc/generator/template/darkfish/index.rhtml @@ -1,64 +1,19 @@ - - + + - <% simple_files = @files.select {|tl| tl.parser == RDoc::Parser::Simple } %> - <% unless simple_files.empty? then %> -

Files

- - <% end %> - -

Classes/Modules

- - -

Methods

- - -
-

[Validate]

-

Generated with the Darkfish - Rdoc Generator <%= RDoc::Generator::Darkfish::VERSION %>.

-
- - +
+<% if @options.main_page && main_page = @files.find { |f| f.full_name == @options.main_page } then %> +<%= main_page.description %> +<% else %> +

This is the API documentation for <%= @title %>. +<% end %> +

diff --git a/lib/rdoc/generator/template/darkfish/js/darkfish.js b/lib/rdoc/generator/template/darkfish/js/darkfish.js index 84565c1e2d..4be722fac3 100644 --- a/lib/rdoc/generator/template/darkfish/js/darkfish.js +++ b/lib/rdoc/generator/template/darkfish/js/darkfish.js @@ -9,12 +9,12 @@ /* Provide console simulation for firebug-less environments */ if (!("console" in window) || !("firebug" in console)) { - var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml", + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"]; - window.console = {}; - for (var i = 0; i < names.length; ++i) - window.console[names[i]] = function() {}; + window.console = {}; + for (var i = 0; i < names.length; ++i) + window.console[names[i]] = function() {}; }; @@ -23,94 +23,131 @@ if (!("console" in window) || !("firebug" in console)) { */ $.fn.unwrap = function( expr ) { return this.each( function() { - $(this).parents( expr ).eq( 0 ).after( this ).remove(); + $(this).parents( expr ).eq( 0 ).after( this ).remove(); }); }; function showSource( e ) { - var target = e.target; - var codeSections = $(target). - parents('.method-detail'). - find('.method-source-code'); + var target = e.target; + var codeSections = $(target). + parents('.method-detail'). + find('.method-source-code'); - $(target). - parents('.method-detail'). - find('.method-source-code'). - slideToggle(); + $(target). + parents('.method-detail'). + find('.method-source-code'). + slideToggle(); }; function hookSourceViews() { - $('.method-description,.method-heading').click( showSource ); + $('.method-heading').click( showSource ); }; function toggleDebuggingSection() { - $('.debugging-section').slideToggle(); + $('.debugging-section').slideToggle(); }; function hookDebuggingToggle() { - $('#debugging-toggle img').click( toggleDebuggingSection ); + $('#debugging-toggle img').click( toggleDebuggingSection ); }; -function hookQuickSearch() { - $('.quicksearch-field').each( function() { - var searchElems = $(this).parents('.section').find( 'li' ); - var toggle = $(this).parents('.section').find('h3 .search-toggle'); - // console.debug( "Toggle is: %o", toggle ); - var qsbox = $(this).parents('form').get( 0 ); +function hookTableOfContentsToggle() { + $('.indexpage li .toc-toggle').each( function() { + $(this).click( function() { + $(this).toggleClass('open'); + }); - $(this).quicksearch( this, searchElems, { - noSearchResultsIndicator: 'no-class-search-results', - focusOnLoad: false - }); - $(toggle).click( function() { - // console.debug( "Toggling qsbox: %o", qsbox ); - $(qsbox).toggle(); - }); - }); + var section = $(this).next(); + + $(this).click( function() { + section.slideToggle(); + }); + }); +} + +function hookSearch() { + var input = $('#search-field').eq(0); + var result = $('#search-results').eq(0); + $(result).show(); + + var search_section = $('#search-section').get(0); + $(search_section).show(); + + var search = new Search(search_data, input, result); + + search.renderItem = function(result) { + var li = document.createElement('li'); + var html = ''; + + // TODO add relative path to