diff --git a/ChangeLog b/ChangeLog index 311380723e..1e74fa946c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,19 @@ +Fri Dec 7 14:22:29 2012 Eric Hodel + + * lib/rdoc/markup/to_joined_paragraph.rb: Completed documentation + * lib/rdoc/parser/c.rb: ditto + * lib/rdoc/parser/changelog.rb: ditto + * lib/rdoc/servlet.rb: ditto + * lib/rdoc/store.rb: ditto + + * lib/rdoc/store.rb: Improved HTML error page. Completed + documentation + + * lib/rdoc/parser/ruby.rb: Fixed bug attaching a comment to A::B = 42 + * test/rdoc/test_rdoc_parser_ruby.rb: Test for above + + * test/rdoc/test_rdoc_comment.rb: Removed garbage + Fri Dec 7 14:03:59 2012 Nobuyoshi Nakada * lib/timeout.rb (Timeout#timeout): since async_interrupt_timing diff --git a/lib/rdoc/markup/to_joined_paragraph.rb b/lib/rdoc/markup/to_joined_paragraph.rb index d91eb439f0..6982d86697 100644 --- a/lib/rdoc/markup/to_joined_paragraph.rb +++ b/lib/rdoc/markup/to_joined_paragraph.rb @@ -12,12 +12,15 @@ class RDoc::Markup::ToJoinedParagraph < RDoc::Markup::Formatter super nil end - def start_accepting + def start_accepting # :nodoc: end - def end_accepting + def end_accepting # :nodoc: end + ## + # Converts the parts of +paragraph+ to a single entry. + def accept_paragraph paragraph parts = [] string = false diff --git a/lib/rdoc/parser/c.rb b/lib/rdoc/parser/c.rb index 31be27169c..d371ed0e6a 100644 --- a/lib/rdoc/parser/c.rb +++ b/lib/rdoc/parser/c.rb @@ -446,6 +446,10 @@ class RDoc::Parser::C < RDoc::Parser end end + ## + # Creates classes and module that were missing were defined due to the file + # order being different than the declaration order. + def do_missing return if @missing_dependencies.empty? diff --git a/lib/rdoc/parser/changelog.rb b/lib/rdoc/parser/changelog.rb index 6a21506a93..fd7114daee 100644 --- a/lib/rdoc/parser/changelog.rb +++ b/lib/rdoc/parser/changelog.rb @@ -1,11 +1,28 @@ require 'time' +## +# A ChangeLog file parser. +# +# This parser converts a ChangeLog into an RDoc::Markup::Document. When +# viewed as HTML a ChangeLog page will have an entry for each day's entries in +# the sidebar table of contents. +# +# This parser is meant to parse the MRI ChangeLog, but can be used to parse any +# {GNU style Change +# Log}[http://www.gnu.org/prep/standards/html_node/Style-of-Change-Logs.html]. + class RDoc::Parser::ChangeLog < RDoc::Parser include RDoc::Parser::Text parse_files_matching(/(\/|\\|\A)ChangeLog[^\/\\]*\z/) + ## + # Attaches the +continuation+ of the previous line to the +entry_body+. + # + # Continued function listings are joined together as a single entry. + # Continued descriptions are joined to make a single paragraph. + def continue_entry_body entry_body, continuation return unless last = entry_body.last @@ -21,6 +38,9 @@ class RDoc::Parser::ChangeLog < RDoc::Parser end end + ## + # Creates an RDoc::Markup::Document given the +groups+ of ChangeLog entries. + def create_document groups doc = RDoc::Markup::Document.new doc.omit_headings_below = 2 @@ -39,6 +59,10 @@ class RDoc::Parser::ChangeLog < RDoc::Parser doc end + ## + # Returns a list of ChangeLog entries an RDoc::Markup nodes for the given + # +entries+. + def create_entries entries out = [] @@ -52,6 +76,10 @@ class RDoc::Parser::ChangeLog < RDoc::Parser out end + ## + # Returns an RDoc::Markup::List containing the given +items+ in the + # ChangeLog + def create_items items list = RDoc::Markup::List.new :NOTE @@ -69,12 +97,30 @@ class RDoc::Parser::ChangeLog < RDoc::Parser list end + ## + # Groups +entries+ by date. + def group_entries entries - entries.group_by do |title, body| + entries.group_by do |title, _| Time.parse(title).strftime "%Y-%m-%d" end end + ## + # Parses the entries in the ChangeLog. + # + # Returns an Array of each ChangeLog entry in order of parsing. + # + # A ChangeLog entry is an Array containing the ChangeLog title (date and + # committer) and an Array of ChangeLog items (file and function changed with + # description). + # + # An example result would be: + # + # [ 'Tue Dec 4 08:33:46 2012 Eric Hodel ', + # [ 'README.EXT: Converted to RDoc format', + # 'README.EXT.ja: ditto']] + def parse_entries entries = [] entry_name = nil @@ -122,6 +168,9 @@ class RDoc::Parser::ChangeLog < RDoc::Parser entries end + ## + # Converts the ChangeLog into an RDoc::Markup::Document + def scan entries = parse_entries grouped_entries = group_entries entries diff --git a/lib/rdoc/parser/ruby.rb b/lib/rdoc/parser/ruby.rb index fe461ff389..31e31b5ce9 100644 --- a/lib/rdoc/parser/ruby.rb +++ b/lib/rdoc/parser/ruby.rb @@ -240,7 +240,7 @@ class RDoc::Parser::Ruby < RDoc::Parser # with :: separated named) and return the ultimate name, the associated # container, and the given name (with the ::). - def get_class_or_module container + def get_class_or_module container, ignore_constants = false skip_tkspace name_t = get_tk given_name = '' @@ -259,9 +259,16 @@ class RDoc::Parser::Ruby < RDoc::Parser while TkCOLON2 === peek_tk do prev_container = container container = container.find_module_named name_t.name - container ||= prev_container.add_module RDoc::NormalModule, name_t.name + container ||= + if ignore_constants then + RDoc::Context.new + else + c = prev_container.add_module RDoc::NormalModule, name_t.name + c.ignore unless prev_container.document_children + c + end - container.ignore unless prev_container.document_children + container.record_location @top_level get_tk skip_tkspace false @@ -663,9 +670,10 @@ class RDoc::Parser::Ruby < RDoc::Parser end ## - # Parses a constant in +context+ with +comment+ + # Parses a constant in +context+ with +comment+. If +ignore_constants+ is + # true, no found constants will be added to RDoc. - def parse_constant container, tk, comment + def parse_constant container, tk, comment, ignore_constants = false offset = tk.seek line_no = tk.line_no @@ -676,6 +684,17 @@ class RDoc::Parser::Ruby < RDoc::Parser eq_tk = get_tk + if TkCOLON2 === eq_tk then + unget_tk eq_tk + unget_tk tk + + container, name_t, = get_class_or_module container, ignore_constants + + name = name_t.name + + eq_tk = get_tk + end + unless TkASSIGN === eq_tk then unget_tk eq_tk return false @@ -1334,6 +1353,26 @@ class RDoc::Parser::Ruby < RDoc::Parser end end + ## + # Parses a rescue + + def parse_rescue + skip_tkspace false + + while tk = get_tk + case tk + when TkNL, TkSEMICOLON then + break + when TkCOMMA then + skip_tkspace false + + get_tk if TkNL === peek_tk + end + + skip_tkspace false + end + end + ## # The core of the ruby parser. @@ -1407,7 +1446,7 @@ class RDoc::Parser::Ruby < RDoc::Parser parse_method container, single, tk, comment when TkCONSTANT then - unless parse_constant container, tk, comment then + unless parse_constant container, tk, comment, current_method then try_parse_comment = true end @@ -1441,6 +1480,9 @@ class RDoc::Parser::Ruby < RDoc::Parser when TkSUPER then current_method.calls_super = true if current_method + when TkRESCUE then + parse_rescue + when TkIDENTIFIER then if nest == 1 and current_method.nil? then case tk.name diff --git a/lib/rdoc/servlet.rb b/lib/rdoc/servlet.rb index 3df366b967..71b2911fc0 100644 --- a/lib/rdoc/servlet.rb +++ b/lib/rdoc/servlet.rb @@ -2,21 +2,60 @@ require 'rdoc' require 'time' require 'webrick' +## +# This is a WEBrick servlet that allows you to browse ri documentation. +# +# You can show documentation through either `ri --server` or, with RubyGems +# 2.0 or newer, `gem server`. For ri, the server runs on port 8214 by +# default. For RubyGems the server runs on port 8808 by default. +# +# You can use this servlet in your own project by mounting it on a WEBrick +# server: +# +# require 'webrick' +# +# server = WEBrick::HTTPServer.new Port: 8000 +# +# server.mount '/', RDoc::Servlet +# +# If you want to mount the servlet some other place than the root, provide the +# base path when mounting: +# +# server.mount '/rdoc', RDoc::Servlet, '/rdoc' + class RDoc::Servlet < WEBrick::HTTPServlet::AbstractServlet @server_stores = Hash.new { |hash, server| hash[server] = {} } @cache = Hash.new { |hash, store| hash[store] = {} } + ## + # Maps an asset type to its path on the filesystem + attr_reader :asset_dirs + ## + # An RDoc::Options instance used for rendering options + attr_reader :options - def self.get_instance server, *options + ## + # Creates an instance of this servlet that shares cached data between + # requests. + + def self.get_instance server, *options # :nodoc: stores = @server_stores[server] new server, stores, @cache, *options end + ## + # Creates a new WEBrick servlet. + # + # Use +mount_path+ when mounting the servlet somewhere other than /. + # + # +server+ is provided automatically by WEBrick when mounting. +stores+ and + # +cache+ are provided automatically by the servlet. + def initialize server, stores, cache, mount_path = nil super server @@ -44,6 +83,9 @@ class RDoc::Servlet < WEBrick::HTTPServlet::AbstractServlet } end + ## + # Serves the asset at the path in +req+ for +generator_name+ via +res+. + def asset generator_name, req, res asset_dir = @asset_dirs[generator_name] @@ -60,6 +102,9 @@ class RDoc::Servlet < WEBrick::HTTPServlet::AbstractServlet end end + ## + # GET request entry point. Fills in +res+ for the path, etc. in +req+. + def do_GET req, res req.path.sub!(/^#{Regexp.escape @mount_path}/o, '') if @mount_path @@ -82,6 +127,13 @@ class RDoc::Servlet < WEBrick::HTTPServlet::AbstractServlet error e, req, res end + ## + # Fills in +res+ with the class, module or page for +req+ from +store+. + # + # +path+ is relative to the mount_path and is used to determine the class, + # module or page name (/RDoc/Servlet.html becomes RDoc::Servlet). + # +generator+ is used to create the page. + def documentation_page store, generator, path, req, res name = path.sub(/.html$/, '').gsub '/', '::' @@ -94,6 +146,10 @@ class RDoc::Servlet < WEBrick::HTTPServlet::AbstractServlet end end + ## + # Creates the JSON search index on +res+ for the given +store+. +generator+ + # must respond to \#json_index to build. +req+ is ignored. + def documentation_search store, generator, req, res json_index = @cache[store].fetch :json_index do @cache[store][:json_index] = @@ -104,6 +160,10 @@ class RDoc::Servlet < WEBrick::HTTPServlet::AbstractServlet res.body = "var search_data = #{json_index}" end + ## + # Returns the RDoc::Store and path relative to +mount_path+ for + # documentation at +path+. + def documentation_source path _, source_name, path = path.split '/', 3 @@ -119,8 +179,11 @@ class RDoc::Servlet < WEBrick::HTTPServlet::AbstractServlet return store, path end - def error e, req, res - backtrace = e.backtrace.join "\n" + ## + # Generates an error page for the +exception+ while handling +req+ on +res+. + + def error exception, req, res + backtrace = exception.backtrace.join "\n" res.content_type = 'text/html' res.status = 500 @@ -130,7 +193,7 @@ class RDoc::Servlet < WEBrick::HTTPServlet::AbstractServlet -Error - #{ERB::Util.html_escape e.class} +Error - #{ERB::Util.html_escape exception.class} @@ -138,10 +201,17 @@ class RDoc::Servlet < WEBrick::HTTPServlet::AbstractServlet

Error

While processing #{ERB::Util.html_escape req.request_uri} the -RDoc server has encountered a #{ERB::Util.html_escape e.class} +RDoc (#{ERB::Util.html_escape RDoc::VERSION}) server has encountered a +#{ERB::Util.html_escape exception.class} exception: -

#{ERB::Util.html_escape e.message}
+
#{ERB::Util.html_escape exception.message}
+ +

Please report this to the +RDoc issues tracker. Please +include the RDoc version, the URI above and exception class, message and +backtrace. If you're viewing a gem's documentation, include the gem name and +version. If you're viewing Ruby's documentation, include the version of ruby.

Backtrace: @@ -152,6 +222,9 @@ exception: BODY end + ## + # Instantiates a Darkfish generator for +store+ + def generator_for store generator = RDoc::Generator::Darkfish.new store, @options generator.file_output = false @@ -168,6 +241,11 @@ exception: generator end + ## + # Handles the If-Modified-Since HTTP header on +req+ for +path+. If the + # file has not been modified a Not Modified response is returned. If the + # file has been modified a Last-Modified header is added to +res+. + def if_modified_since req, res, path = nil last_modified = File.stat(path).mtime if path @@ -183,6 +261,14 @@ exception: end end + ## + # Returns an Array of installed documentation. + # + # Each entry contains the documentation name (gem name, 'Ruby + # Documentation', etc.), the path relative to the mount point, whether the + # documentation exists, the type of documentation (See RDoc::RI::Paths#each) + # and the filesystem to the RDoc::Store for the documentation. + def installed_docs ri_paths.map do |path, type| store = RDoc::Store.new path, type @@ -202,15 +288,24 @@ exception: end end + ## + # Returns a 404 page built by +generator+ for +req+ on +res+. + def not_found generator, req, res res.body = generator.generate_servlet_not_found req.path res.status = 404 end + ## + # Enumerates the ri paths. See RDoc::RI::Paths#each + def ri_paths &block RDoc::RI::Paths.each true, true, true, :all, &block end + ## + # Generates the root page on +res+. +req+ is ignored. + def root req, res generator = RDoc::Generator::Darkfish.new nil, @options @@ -219,6 +314,9 @@ exception: res.content_type = 'text/html' end + ## + # Generates a search index for the root page on +res+. +req+ is ignored. + def root_search req, res search_index = [] info = [] @@ -259,6 +357,10 @@ exception: res.content_type = 'application/javascript' end + ## + # Displays documentation for +req+ on +res+, whether that be HTML or some + # asset. + def show_documentation req, res store, path = documentation_source req.path @@ -280,6 +382,9 @@ exception: res.content_type ||= 'text/html' end + ## + # Returns an RDoc::Store for the given +source_name+ ('ruby' or a gem name). + def store_for source_name case source_name when 'ruby' then diff --git a/lib/rdoc/store.rb b/lib/rdoc/store.rb index 00c1010802..cf9ee07625 100644 --- a/lib/rdoc/store.rb +++ b/lib/rdoc/store.rb @@ -59,7 +59,7 @@ class RDoc::Store @name = name end - def message + def message # :nodoc: "store at #{@store.path} missing file #{@file} for #{@name}" end diff --git a/test/rdoc/test_rdoc_comment.rb b/test/rdoc/test_rdoc_comment.rb index 4d0d54edca..1afc94c251 100644 --- a/test/rdoc/test_rdoc_comment.rb +++ b/test/rdoc/test_rdoc_comment.rb @@ -70,7 +70,7 @@ call-seq: comment = RDoc::Comment.new <<-COMMENT, @top_level # call-seq: # bla => true or false -#\s +# # moar comment COMMENT diff --git a/test/rdoc/test_rdoc_parser_ruby.rb b/test/rdoc/test_rdoc_parser_ruby.rb index 41b58f6615..10ccd46c3d 100644 --- a/test/rdoc/test_rdoc_parser_ruby.rb +++ b/test/rdoc/test_rdoc_parser_ruby.rb @@ -80,23 +80,53 @@ class C; end assert_equal 'A', name_t.text assert_equal 'A', given_name - cont, name_t, given_name = util_parser('A::B') .get_class_or_module ctxt + cont, name_t, given_name = util_parser('B::C') .get_class_or_module ctxt - assert_equal @store.find_module_named('A'), cont - assert_equal 'B', name_t.text - assert_equal 'A::B', given_name + b = @store.find_module_named('B') + assert_equal b, cont + assert_equal [@top_level], b.in_files + assert_equal 'C', name_t.text + assert_equal 'B::C', given_name - cont, name_t, given_name = util_parser('A:: B').get_class_or_module ctxt + cont, name_t, given_name = util_parser('D:: E').get_class_or_module ctxt - assert_equal @store.find_module_named('A'), cont - assert_equal 'B', name_t.text - assert_equal 'A::B', given_name + assert_equal @store.find_module_named('D'), cont + assert_equal 'E', name_t.text + assert_equal 'D::E', given_name assert_raises NoMethodError do util_parser("A::\nB").get_class_or_module ctxt end end + def test_get_class_or_module_document_children + ctxt = @top_level.add_class RDoc::NormalClass, 'A' + ctxt.stop_doc + + util_parser('B::C').get_class_or_module ctxt + + b = @store.find_module_named('A::B') + assert b.ignored? + + d = @top_level.add_class RDoc::NormalClass, 'A::D' + + util_parser('D::E').get_class_or_module ctxt + + refute d.ignored? + end + + def test_get_class_or_module_ignore_constants + ctxt = RDoc::Context.new + ctxt.store = @store + + util_parser('A') .get_class_or_module ctxt, true + util_parser('A::B').get_class_or_module ctxt, true + + assert_empty ctxt.constants + assert_empty @store.modules_hash.keys + assert_empty @store.classes_hash.keys + end + def test_get_class_specification assert_equal 'A', util_parser('A') .get_class_specification assert_equal 'A::B', util_parser('A::B').get_class_specification @@ -1108,6 +1138,37 @@ EOF assert_equal 'A', bar.find_module_named('A').full_name end + def test_parse_constant_in_method + klass = @top_level.add_class RDoc::NormalClass, 'Foo' + + util_parser 'A::B = v' + + tk = @parser.get_tk + + @parser.parse_constant klass, tk, @comment, true + + assert_empty klass.constants + + assert_empty @store.modules_hash.keys + assert_equal %w[Foo], @store.classes_hash.keys + end + + def test_parse_constant_rescue + klass = @top_level.add_class RDoc::NormalClass, 'Foo' + + util_parser "A => e" + + tk = @parser.get_tk + + @parser.parse_constant klass, tk, @comment + + assert_empty klass.constants + assert_empty klass.modules + + assert_empty @store.modules_hash.keys + assert_equal %w[Foo], @store.classes_hash.keys + end + def test_parse_constant_stopdoc klass = @top_level.add_class RDoc::NormalClass, 'Foo' klass.stop_doc @@ -1121,6 +1182,27 @@ EOF assert_empty klass.constants end + def test_parse_comment_nested + content = <<-CONTENT +A::B::C = 1 + CONTENT + + util_parser content + + tk = @parser.get_tk + + parsed = @parser.parse_constant @top_level, tk, 'comment' + + assert parsed + + a = @top_level.find_module_named 'A' + b = a.find_module_named 'B' + c = b.constants.first + + assert_equal 'A::B::C', c.full_name + assert_equal 'comment', c.comment + end + def test_parse_include klass = RDoc::NormalClass.new 'C' klass.parent = @top_level @@ -2585,6 +2667,61 @@ end assert_equal 'A nice girl', m.comment.text end + def test_scan_constant_in_method + content = <<-CONTENT # newline is after M is important +module M + def m + A + B::C + end +end + CONTENT + + util_parser content + + @parser.scan + + m = @top_level.modules.first + + assert_empty m.constants + + assert_empty @store.classes_hash.keys + assert_equal %w[M], @store.modules_hash.keys + end + + def test_scan_constant_in_rescue + content = <<-CONTENT # newline is after M is important +module M + def m + rescue A::B + rescue A::C => e + rescue A::D, A::E + rescue A::F, + A::G + rescue H + rescue I => e + rescue J, K + rescue L => + e + rescue M; + rescue N, + O => e + end +end + CONTENT + + util_parser content + + @parser.scan + + m = @top_level.modules.first + + assert_empty m.constants + + assert_empty @store.classes_hash.keys + assert_equal %w[M], @store.modules_hash.keys + end + def test_scan_constant_nodoc content = <<-CONTENT # newline is after M is important module M