diff --git a/lib/rdoc/parser/c.rb b/lib/rdoc/parser/c.rb index 6d1691360e..b13e7cad8a 100644 --- a/lib/rdoc/parser/c.rb +++ b/lib/rdoc/parser/c.rb @@ -683,6 +683,7 @@ def find_class(raw_name, name, base_name = nil) container.name = base_name if base_name container.record_location @top_level + @top_level.add_to_classes_or_modules container @classes[raw_name] = container end @classes[raw_name] @@ -898,6 +899,7 @@ def handle_class_module(var_name, type, class_name, parent, in_module) end cm.record_location enclosure.top_level + enclosure.top_level.add_to_classes_or_modules cm find_class_comment cm.full_name, cm diff --git a/lib/rdoc/server.rb b/lib/rdoc/server.rb index 3d08d18248..9dbb342908 100644 --- a/lib/rdoc/server.rb +++ b/lib/rdoc/server.rb @@ -67,6 +67,8 @@ def initialize(rdoc, port) @store = rdoc.store @port = port + # Silence stats output — the server prints its own timing. + @rdoc.stats.verbosity = 0 @generator = create_generator @template_dir = File.expand_path(@generator.template_dir) @page_cache = {} @@ -102,6 +104,12 @@ def start private + def measure + start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) + yield + ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round(1) + end + def create_generator gen = RDoc::Generator::Aliki.new(@store, @options) gen.file_output = false @@ -138,7 +146,14 @@ def handle_client(client) return write_response(client, 405, 'text/plain', 'Method Not Allowed') end - status, content_type, body = route(path) + if path.start_with?('/__') || %r{\A/(?:css|js)/}.match?(path) + status, content_type, body = route(path) + else + duration_ms = measure do + status, content_type, body = route(path) + end + $stderr.puts "#{status} #{path} (#{duration_ms}ms)" + end write_response(client, status, content_type, body) rescue => e write_response(client, 500, 'text/html', <<~HTML) @@ -185,6 +200,8 @@ def write_response(client, status, content_type, body) client.write(header) client.write(body_bytes) client.flush + rescue Errno::EPIPE + # Client disconnected before we finished writing — harmless. end ## @@ -347,19 +364,23 @@ def reparse_and_refresh(changed_files, removed_files) end unless changed_files.empty? - $stderr.puts "Re-parsing: #{changed_files.join(', ')}" - changed_files.each do |f| - begin + changed_file_names = [] + duration_ms = measure do + changed_files.each do |f| relative = @rdoc.relative_path_for(f) - @store.clear_file_contributions(relative, keep_position: true) - @rdoc.parse_file(f) - @file_mtimes[f] = File.mtime(f) rescue nil - rescue => e - $stderr.puts "Error parsing #{f}: #{e.message}" + changed_file_names << relative + begin + @store.clear_file_contributions(relative, keep_position: true) + @rdoc.parse_file(f) + @file_mtimes[f] = File.mtime(f) rescue nil + rescue => e + $stderr.puts "Error parsing #{f}: #{e.message}" + end end - end - @store.cleanup_stale_contributions + @store.cleanup_stale_contributions + end + $stderr.puts "Re-parsed #{changed_file_names.join(', ')} (#{duration_ms}ms)" end @store.complete(@options.visibility) diff --git a/lib/rdoc/stats.rb b/lib/rdoc/stats.rb index d5e9d358ac..683fe4a19a 100644 --- a/lib/rdoc/stats.rb +++ b/lib/rdoc/stats.rb @@ -39,10 +39,17 @@ def initialize(store, num_files, verbosity = 1) @start = Time.now @undoc_params = 0 + self.verbosity = verbosity + end + + ## + # Sets the verbosity level, rebuilding the display outputter. + + def verbosity=(verbosity) @display = case verbosity - when 0 then Quiet.new num_files - when 1 then Normal.new num_files - else Verbose.new num_files + when 0 then Quiet.new @num_files + when 1 then Normal.new @num_files + else Verbose.new @num_files end end diff --git a/test/rdoc/parser/c_test.rb b/test/rdoc/parser/c_test.rb index 4ddb4b6caa..17237c2400 100644 --- a/test/rdoc/parser/c_test.rb +++ b/test/rdoc/parser/c_test.rb @@ -2215,6 +2215,83 @@ def test_markup_format_override assert_equal("markdown", klass.attributes.find {|a| a.name == "default_format"}.comment.format) end + def test_clear_file_contributions_removes_c_methods + content = <<~C + /* Document-class: Foo */ + VALUE cFoo = rb_define_class("Foo", rb_cObject); + + /* call-seq: bar -> nil */ + VALUE foo_bar(VALUE self) { return Qnil; } + + void Init_Foo(void) { + cFoo = rb_define_class("Foo", rb_cObject); + rb_define_method(cFoo, "bar", foo_bar, 0); + } + C + + util_get_class content, 'cFoo' + + klass = @store.find_class_named 'Foo' + assert_equal 1, klass.method_list.size + + @store.clear_file_contributions @top_level.relative_name + assert_equal 0, klass.method_list.size + end + + def test_reparse_c_file_no_duplicates + content = <<~C + /* Document-class: Foo + * Original comment + */ + VALUE cFoo = rb_define_class("Foo", rb_cObject); + + /* call-seq: bar -> nil */ + VALUE foo_bar(VALUE self) { return Qnil; } + + void Init_Foo(void) { + cFoo = rb_define_class("Foo", rb_cObject); + rb_define_method(cFoo, "bar", foo_bar, 0); + } + C + + # First parse + util_get_class content, 'cFoo' + + klass = @store.find_class_named 'Foo' + assert_equal 1, klass.method_list.size + + # Simulate server mode re-parse: clear then parse again + @store.clear_file_contributions @top_level.relative_name + @top_level.classes_or_modules.clear + + updated_content = <<~C + /* Document-class: Foo + * Updated comment + */ + VALUE cFoo = rb_define_class("Foo", rb_cObject); + + /* call-seq: bar -> nil */ + VALUE foo_bar(VALUE self) { return Qnil; } + + /* call-seq: baz -> nil */ + VALUE foo_baz(VALUE self) { return Qnil; } + + void Init_Foo(void) { + cFoo = rb_define_class("Foo", rb_cObject); + rb_define_method(cFoo, "bar", foo_bar, 0); + rb_define_method(cFoo, "baz", foo_baz, 0); + } + C + + util_get_class updated_content, 'cFoo' + + klass = @store.find_class_named 'Foo' + method_names = klass.method_list.map(&:name) + assert_equal 2, method_names.size + assert_include method_names, 'bar' + assert_include method_names, 'baz' + end + def util_get_class(content, name = nil) @parser = util_parser content @parser.scan