From f4563440cf0b4b3b3f97f8f436216133aaca88a7 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sat, 29 Aug 2026 15:01:35 +0100 Subject: [PATCH 1/2] Enforce nested class and module declarations Assisted-By: devx/0d67a386-6fea-4c9d-96ef-064c1cc8810c --- .rubocop.yml | 4 + doc/rdoc/example.rb | 188 +- lib/rdoc/code_object.rb | 608 ++-- lib/rdoc/code_object/alias.rb | 140 +- lib/rdoc/code_object/any_method.rb | 608 ++-- lib/rdoc/code_object/attr.rb | 298 +- lib/rdoc/code_object/class_module.rb | 1597 +++++------ lib/rdoc/code_object/constant.rb | 348 +-- lib/rdoc/code_object/context.rb | 1862 ++++++------- lib/rdoc/code_object/context/section.rb | 280 +- lib/rdoc/code_object/extend.rb | 12 +- lib/rdoc/code_object/include.rb | 12 +- lib/rdoc/code_object/method_attr.rb | 650 ++--- lib/rdoc/code_object/mixin.rb | 192 +- lib/rdoc/code_object/normal_class.rb | 152 +- lib/rdoc/code_object/normal_module.rb | 120 +- lib/rdoc/code_object/require.rb | 74 +- lib/rdoc/code_object/single_class.rb | 40 +- lib/rdoc/code_object/top_level.rb | 440 +-- lib/rdoc/comment.rb | 548 ++-- lib/rdoc/cross_reference.rb | 382 +-- lib/rdoc/encoding.rb | 208 +- lib/rdoc/erb_partial.rb | 24 +- lib/rdoc/erbio.rb | 56 +- lib/rdoc/generator.rb | 94 +- lib/rdoc/generator/aliki.rb | 318 +-- lib/rdoc/generator/darkfish.rb | 1280 ++++----- lib/rdoc/generator/json_index.rb | 462 +-- lib/rdoc/generator/markup.rb | 310 +- lib/rdoc/generator/pot.rb | 166 +- lib/rdoc/generator/pot/message_extractor.rb | 108 +- lib/rdoc/generator/pot/po.rb | 98 +- lib/rdoc/generator/pot/po_entry.rb | 270 +- lib/rdoc/generator/ri.rb | 42 +- .../generator/template/aliki/_footer.rhtml | 2 +- lib/rdoc/generator/template/aliki/_head.rhtml | 20 +- .../generator/template/darkfish/_footer.rhtml | 2 +- lib/rdoc/i18n.rb | 12 +- lib/rdoc/i18n/locale.rb | 194 +- lib/rdoc/i18n/text.rb | 214 +- lib/rdoc/markdown/byte_runtime.rb | 124 +- lib/rdoc/markup.rb | 394 +-- lib/rdoc/markup/block_quote.rb | 20 +- lib/rdoc/markup/document.rb | 250 +- lib/rdoc/markup/formatter.rb | 434 +-- lib/rdoc/markup/include.rb | 62 +- lib/rdoc/markup/indented_paragraph.rb | 70 +- lib/rdoc/markup/inline_parser.rb | 558 ++-- lib/rdoc/markup/paragraph.rb | 42 +- lib/rdoc/markup/parser.rb | 998 +++---- lib/rdoc/markup/pre_process.rb | 562 ++-- lib/rdoc/markup/rule.rb | 28 +- lib/rdoc/markup/to_ansi.rb | 282 +- lib/rdoc/markup/to_bs.rb | 140 +- lib/rdoc/markup/to_html.rb | 1134 ++++---- lib/rdoc/markup/to_html_crossref.rb | 464 +-- lib/rdoc/markup/to_html_snippet.rb | 456 +-- lib/rdoc/markup/to_joined_paragraph.rb | 68 +- lib/rdoc/markup/to_label.rb | 122 +- lib/rdoc/markup/to_markdown.rb | 420 +-- lib/rdoc/markup/to_rdoc.rb | 668 ++--- lib/rdoc/markup/to_table_of_contents.rb | 128 +- lib/rdoc/markup/to_test.rb | 116 +- lib/rdoc/markup/to_tt_only.rb | 164 +- lib/rdoc/markup/verbatim.rb | 120 +- lib/rdoc/options.rb | 2124 +++++++------- lib/rdoc/parser.rb | 454 +-- lib/rdoc/parser/c.rb | 2072 +++++++------- lib/rdoc/parser/changelog.rb | 634 ++--- lib/rdoc/parser/markdown.rb | 30 +- lib/rdoc/parser/rbs.rb | 474 ++-- lib/rdoc/parser/rd.rb | 30 +- lib/rdoc/parser/ruby.rb | 2482 +++++++++-------- lib/rdoc/parser/ruby_colorizer.rb | 436 +-- lib/rdoc/parser/simple.rb | 58 +- lib/rdoc/parser/text.rb | 20 +- lib/rdoc/rd.rb | 178 +- lib/rdoc/rd/inline.rb | 110 +- lib/rdoc/rdoc.rb | 998 +++---- lib/rdoc/ri.rb | 26 +- lib/rdoc/ri/driver.rb | 2272 +++++++-------- lib/rdoc/ri/formatter.rb | 10 +- lib/rdoc/ri/paths.rb | 276 +- lib/rdoc/ri/servlet.rb | 704 ++--- lib/rdoc/ri/store.rb | 6 +- lib/rdoc/ri/task.rb | 108 +- lib/rdoc/rubygems_hook.rb | 364 +-- lib/rdoc/server.rb | 696 ++--- lib/rdoc/stats.rb | 728 ++--- lib/rdoc/stats/normal.rb | 88 +- lib/rdoc/stats/quiet.rb | 74 +- lib/rdoc/stats/verbose.rb | 66 +- lib/rdoc/store.rb | 1790 ++++++------ lib/rdoc/task.rb | 522 ++-- lib/rdoc/text.rb | 268 +- lib/rdoc/token_stream.rb | 198 +- lib/rdoc/tom_doc.rb | 404 +-- test/rdoc/markup/pre_process_test.rb | 618 ++-- test/rdoc/support/formatter_test_case.rb | 1048 +++---- test/rdoc/support/test_case.rb | 290 +- test/rdoc/support/text_formatter_test_case.rb | 242 +- 101 files changed, 20742 insertions(+), 20415 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 3a8fb7f36f..ff214deae1 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -29,6 +29,10 @@ Layout/SpaceAfterComma: Lint/UnreachableCode: Enabled: true +Style/ClassAndModuleChildren: + Enabled: true + EnforcedStyle: nested + Style/MethodDefParentheses: Enabled: true diff --git a/doc/rdoc/example.rb b/doc/rdoc/example.rb index 20b543cbec..e5245bb3ee 100644 --- a/doc/rdoc/example.rb +++ b/doc/rdoc/example.rb @@ -2,101 +2,103 @@ require 'rdoc' -## -# RDoc::Example provides example Ruby code objects for demonstrating -# RDoc's documentation capabilities. -# -# This class is used by: -# - bin/console for interactive exploration of RDoc objects -# - Testing cross-reference link generation -# - Demonstrating Ruby-specific directives like :call-seq:, :args:, :yields: -# -# For comprehensive RDoc markup documentation, see doc/markup_reference/rdoc.rdoc. - -class RDoc::Example - - # Example class for demonstrating cross-reference links. - class ExampleClass; end - - # Example module for demonstrating cross-reference links. - module ExampleModule; end - - # Example singleton method. - def self.singleton_method_example(foo, bar); end - - # Example instance method. - # - # This method demonstrates how RDoc documents instance methods, - # including arguments and yield parameters. - def instance_method_example(foo, bar) - yield 'baz' - end - - alias aliased_method instance_method_example - - # Example attribute. - attr_accessor :example_attribute - - alias aliased_attribute example_attribute - - # Example constant. - EXAMPLE_CONSTANT = '' - - # :call-seq: - # call_seq_example(foo, bar) - # Can be anything -> bar - # Also anything more -> baz or bat - # - # The :call-seq: directive overrides the actual calling sequence - # found in the Ruby code. - # - # - It can specify anything at all. - # - It can have multiple calling sequences. +module RDoc + ## + # RDoc::Example provides example Ruby code objects for demonstrating + # RDoc's documentation capabilities. # - # Note that the "arrow" is two characters, hyphen and right angle-bracket, - # which is made into a single character in the HTML. + # This class is used by: + # - bin/console for interactive exploration of RDoc objects + # - Testing cross-reference link generation + # - Demonstrating Ruby-specific directives like :call-seq:, :args:, :yields: # - # Here is the :call-seq: directive given for this method: - # - # \:call-seq: - # call_seq_example(foo, bar) - # Can be anything -> bar - # Also anything more -> baz or bat - # - def call_seq_example - nil - end - - # The :args: directive overrides the actual arguments - # found in the Ruby code. - # - # The actual signature is +args_example(foo, bar)+, but the directive - # makes it appear as +args_example(baz)+. - # - def args_example(foo, bar) # :args: baz - nil - end + # For comprehensive RDoc markup documentation, see doc/markup_reference/rdoc.rdoc. + + class Example + + # Example class for demonstrating cross-reference links. + class ExampleClass; end + + # Example module for demonstrating cross-reference links. + module ExampleModule; end + + # Example singleton method. + def self.singleton_method_example(foo, bar); end + + # Example instance method. + # + # This method demonstrates how RDoc documents instance methods, + # including arguments and yield parameters. + def instance_method_example(foo, bar) + yield 'baz' + end + + alias aliased_method instance_method_example + + # Example attribute. + attr_accessor :example_attribute + + alias aliased_attribute example_attribute + + # Example constant. + EXAMPLE_CONSTANT = '' + + # :call-seq: + # call_seq_example(foo, bar) + # Can be anything -> bar + # Also anything more -> baz or bat + # + # The :call-seq: directive overrides the actual calling sequence + # found in the Ruby code. + # + # - It can specify anything at all. + # - It can have multiple calling sequences. + # + # Note that the "arrow" is two characters, hyphen and right angle-bracket, + # which is made into a single character in the HTML. + # + # Here is the :call-seq: directive given for this method: + # + # \:call-seq: + # call_seq_example(foo, bar) + # Can be anything -> bar + # Also anything more -> baz or bat + # + def call_seq_example + nil + end + + # The :args: directive overrides the actual arguments + # found in the Ruby code. + # + # The actual signature is +args_example(foo, bar)+, but the directive + # makes it appear as +args_example(baz)+. + # + def args_example(foo, bar) # :args: baz + nil + end + + # The :yields: directive overrides the actual yield + # found in the Ruby code. + # + # The actual yield is +'baz'+, but the directive makes it appear + # as +'bat'+. + # + def yields_example(foo, bar) # :yields: 'bat' + yield 'baz' + end + + # This method is documented only by RDoc's derived documentation, + # except for these comments. + # + # RDoc automatically extracts: + # - Method name + # - Arguments + # - Yielded values + # + def derived_docs_example(foo, bar) + yield 'baz' + end - # The :yields: directive overrides the actual yield - # found in the Ruby code. - # - # The actual yield is +'baz'+, but the directive makes it appear - # as +'bat'+. - # - def yields_example(foo, bar) # :yields: 'bat' - yield 'baz' end - - # This method is documented only by RDoc's derived documentation, - # except for these comments. - # - # RDoc automatically extracts: - # - Method name - # - Arguments - # - Yielded values - # - def derived_docs_example(foo, bar) - yield 'baz' - end - end diff --git a/lib/rdoc/code_object.rb b/lib/rdoc/code_object.rb index eacede9fe1..fba667145c 100644 --- a/lib/rdoc/code_object.rb +++ b/lib/rdoc/code_object.rb @@ -1,393 +1,395 @@ # frozen_string_literal: true -## -# Base class for the RDoc code tree. -# -# We contain the common stuff for contexts (which are containers) and other -# elements (methods, attributes and so on) -# -# Here's the tree of the CodeObject subclasses: -# -# * RDoc::Context -# * RDoc::TopLevel -# * RDoc::ClassModule -# * RDoc::NormalClass -# * RDoc::NormalModule -# * RDoc::SingleClass -# * RDoc::MethodAttr -# * RDoc::Attr -# * RDoc::AnyMethod -# * RDoc::Alias -# * RDoc::Constant -# * RDoc::Require -# * RDoc::Mixin -# * RDoc::Include -# * RDoc::Extend - -class RDoc::CodeObject - - include RDoc::Text - +module RDoc ## - # Our comment + # Base class for the RDoc code tree. + # + # We contain the common stuff for contexts (which are containers) and other + # elements (methods, attributes and so on) + # + # Here's the tree of the CodeObject subclasses: + # + # * RDoc::Context + # * RDoc::TopLevel + # * RDoc::ClassModule + # * RDoc::NormalClass + # * RDoc::NormalModule + # * RDoc::SingleClass + # * RDoc::MethodAttr + # * RDoc::Attr + # * RDoc::AnyMethod + # * RDoc::Alias + # * RDoc::Constant + # * RDoc::Require + # * RDoc::Mixin + # * RDoc::Include + # * RDoc::Extend - attr_reader :comment + class CodeObject - ## - # Do we document our children? + include ::RDoc::Text - attr_reader :document_children + ## + # Our comment - ## - # Do we document ourselves? + attr_reader :comment - attr_reader :document_self + ## + # Do we document our children? - ## - # Are we done documenting (ie, did we come across a :enddoc:)? + attr_reader :document_children - attr_reader :done_documenting + ## + # Do we document ourselves? - ## - # Which file this code object was defined in + attr_reader :document_self - attr_reader :file + ## + # Are we done documenting (ie, did we come across a :enddoc:)? - ## - # Force documentation of this CodeObject + attr_reader :done_documenting - attr_reader :force_documentation + ## + # Which file this code object was defined in - ## - # Line in #file where this CodeObject was defined + attr_reader :file - attr_accessor :line + ## + # Force documentation of this CodeObject - ## - # Hash of arbitrary metadata for this CodeObject + attr_reader :force_documentation - attr_reader :metadata + ## + # Line in #file where this CodeObject was defined - ## - # Sets the parent CodeObject + attr_accessor :line - attr_writer :parent + ## + # Hash of arbitrary metadata for this CodeObject - ## - # Did we ever receive a +:nodoc:+ directive? + attr_reader :metadata - attr_reader :received_nodoc + ## + # Sets the parent CodeObject - ## - # Set the section this CodeObject is in + attr_writer :parent - attr_writer :section + ## + # Did we ever receive a +:nodoc:+ directive? - ## - # The RDoc::Store for this object. + attr_reader :received_nodoc - attr_reader :store + ## + # Set the section this CodeObject is in - ## - # When mixed-in to a class, this points to the Context in which it was originally defined. + attr_writer :section - attr_accessor :mixin_from + ## + # The RDoc::Store for this object. - ## - # Creates a new CodeObject that will document itself and its children - - def initialize - @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 - @track_visibility = true - @mixin_from = nil - - initialize_visibility - end + attr_reader :store - ## - # Initializes state for visibility of this CodeObject and its children. - - def initialize_visibility # :nodoc: - @document_children = true - @document_self = true - @done_documenting = false - @force_documentation = false - @received_nodoc = false - @ignored = false - @suppressed = false - @track_visibility = true - end + ## + # When mixed-in to a class, this points to the Context in which it was originally defined. - ## - # Replaces our comment with +comment+, unless it is empty. - - def comment=(comment) - @comment = case comment - when NilClass then '' - when RDoc::Comment then comment.normalize - else - if comment and not comment.empty? - normalize_comment comment - else - # HACK correct fix is to have #initialize create @comment - # with the correct encoding - if String === @comment and @comment.empty? - @comment = RDoc::Encoding.change_encoding @comment, comment.encoding - end - @comment - end - end - end + attr_accessor :mixin_from - ## - # Should this CodeObject be displayed in output? - # - # A code object should be displayed if: - # - # * The item didn't have a nodoc or wasn't in a container that had nodoc - # * The item wasn't ignored - # * The item has documentation and was not suppressed + ## + # Creates a new CodeObject that will document itself and its children - def display? - @document_self and not @ignored and - (documented? or not @suppressed) - end + def initialize + @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 + @track_visibility = true + @mixin_from = nil - ## - # Enables or disables documentation of this CodeObject's children unless it - # has been turned off by :enddoc: + initialize_visibility + end - def document_children=(document_children) - return unless @track_visibility + ## + # Initializes state for visibility of this CodeObject and its children. + + def initialize_visibility # :nodoc: + @document_children = true + @document_self = true + @done_documenting = false + @force_documentation = false + @received_nodoc = false + @ignored = false + @suppressed = false + @track_visibility = true + end - @document_children = document_children unless @done_documenting - end + ## + # Replaces our comment with +comment+, unless it is empty. - ## - # Enables or disables documentation of this CodeObject unless it has been - # turned off by :enddoc:. If the argument is +nil+ it means the - # documentation is turned off by +:nodoc:+. + def comment=(comment) + @comment = case comment + when NilClass then '' + when ::RDoc::Comment then comment.normalize + else + if comment and not comment.empty? + normalize_comment comment + else + # HACK correct fix is to have #initialize create @comment + # with the correct encoding + if String === @comment and @comment.empty? + @comment = ::RDoc::Encoding.change_encoding @comment, comment.encoding + end + @comment + end + end + end - def document_self=(document_self) - return unless @track_visibility - return if @done_documenting + ## + # Should this CodeObject be displayed in output? + # + # A code object should be displayed if: + # + # * The item didn't have a nodoc or wasn't in a container that had nodoc + # * The item wasn't ignored + # * The item has documentation and was not suppressed + + def display? + @document_self and not @ignored and + (documented? or not @suppressed) + end - @document_self = document_self - @received_nodoc = true if document_self.nil? - end + ## + # Enables or disables documentation of this CodeObject's children unless it + # has been turned off by :enddoc: - ## - # Does this object have a comment with content or is #received_nodoc true? + def document_children=(document_children) + return unless @track_visibility - def documented? - @received_nodoc or !@comment.empty? - end + @document_children = document_children unless @done_documenting + end - ## - # Turns documentation on/off, and turns on/off #document_self - # and #document_children. - # - # Once documentation has been turned off (by +:enddoc:+), - # the object will refuse to turn #document_self or - # #document_children on, so +:doc:+ and +:start_doc:+ directives - # will have no effect in the current file. - - def done_documenting=(value) - return unless @track_visibility - @done_documenting = value - @document_self = !value - @document_children = @document_self - end + ## + # Enables or disables documentation of this CodeObject unless it has been + # turned off by :enddoc:. If the argument is +nil+ it means the + # documentation is turned off by +:nodoc:+. - ## - # File name where this CodeObject was found. - # - # See also RDoc::Context#in_files + def document_self=(document_self) + return unless @track_visibility + return if @done_documenting - def file_name - return unless @file + @document_self = document_self + @received_nodoc = true if document_self.nil? + end - @file.absolute_name - end + ## + # Does this object have a comment with content or is #received_nodoc true? - ## - # Force the documentation of this object unless documentation - # has been turned off by :enddoc: - #-- - # HACK untested, was assigning to an ivar + def documented? + @received_nodoc or !@comment.empty? + end - def force_documentation=(value) - @force_documentation = value unless @done_documenting - end + ## + # Turns documentation on/off, and turns on/off #document_self + # and #document_children. + # + # Once documentation has been turned off (by +:enddoc:+), + # the object will refuse to turn #document_self or + # #document_children on, so +:doc:+ and +:start_doc:+ directives + # will have no effect in the current file. + + def done_documenting=(value) + return unless @track_visibility + @done_documenting = value + @document_self = !value + @document_children = @document_self + end - ## - # Sets the full_name overriding any computed full name. - # - # Set to +nil+ to clear RDoc's cached value + ## + # File name where this CodeObject was found. + # + # See also RDoc::Context#in_files - def full_name=(full_name) - @full_name = full_name - end + def file_name + return unless @file - ## - # Use this to ignore a CodeObject and all its children until found again - # (#record_location is called). An ignored item will not be displayed in - # documentation. - # - # See github issue #55 - # - # The ignored status is temporary in order to allow implementation details - # to be hidden. At the end of processing a file RDoc allows all classes - # and modules to add new documentation to previously created classes. - # - # If a class was ignored (via stopdoc) then reopened later with additional - # documentation it should be displayed. If a class was ignored and never - # reopened it should not be displayed. The ignore flag allows this to - # occur. + @file.absolute_name + end - def ignore - return unless @track_visibility + ## + # Force the documentation of this object unless documentation + # has been turned off by :enddoc: + #-- + # HACK untested, was assigning to an ivar - @ignored = true + def force_documentation=(value) + @force_documentation = value unless @done_documenting + end - stop_doc - end + ## + # Sets the full_name overriding any computed full name. + # + # Set to +nil+ to clear RDoc's cached value - ## - # Has this class been ignored? - # - # See also #ignore + def full_name=(full_name) + @full_name = full_name + end - def ignored? - @ignored - end + ## + # Use this to ignore a CodeObject and all its children until found again + # (#record_location is called). An ignored item will not be displayed in + # documentation. + # + # See github issue #55 + # + # The ignored status is temporary in order to allow implementation details + # to be hidden. At the end of processing a file RDoc allows all classes + # and modules to add new documentation to previously created classes. + # + # If a class was ignored (via stopdoc) then reopened later with additional + # documentation it should be displayed. If a class was ignored and never + # reopened it should not be displayed. The ignore flag allows this to + # occur. + + def ignore + return unless @track_visibility + + @ignored = true + + stop_doc + end - ## - # The options instance from the store this CodeObject is attached to, or a - # default options instance if the CodeObject is not attached. - # - # Used by: store= (visibility check), ClassModule#path, TopLevel#path, - # ClassModule#embed_mixins + ## + # Has this class been ignored? + # + # See also #ignore - def options - @store&.options || RDoc::Options.new - end + def ignored? + @ignored + end - ## - # Our parent CodeObject. The parent may be missing for classes loaded from - # legacy RI data stores. + ## + # The options instance from the store this CodeObject is attached to, or a + # default options instance if the CodeObject is not attached. + # + # Used by: store= (visibility check), ClassModule#path, TopLevel#path, + # ClassModule#embed_mixins - def parent - return @parent if @parent - return nil unless @parent_name + def options + @store&.options || ::RDoc::Options.new + end - if @parent_class == RDoc::TopLevel - @parent = @store.add_file @parent_name - else - @parent = @store.find_class_or_module @parent_name + ## + # 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 + @parent = @store.add_file @parent_name + else + @parent = @store.find_class_or_module @parent_name - begin - @parent = @store.load_class @parent_name - rescue RDoc::Store::MissingFileError - nil + return @parent if @parent + + begin + @parent = @store.load_class @parent_name + rescue ::RDoc::Store::MissingFileError + nil + end end end - end - ## - # Name of our parent + ## + # Name of our parent - def parent_name - @parent ? @parent.full_name : '(unknown)' - end + def parent_name + @parent ? @parent.full_name : '(unknown)' + end - ## - # Records the RDoc::TopLevel (file) where this code object was defined + ## + # Records the RDoc::TopLevel (file) where this code object was defined - def record_location(top_level) - @ignored = false - @suppressed = false - @file = top_level - end + def record_location(top_level) + @ignored = false + @suppressed = false + @file = top_level + end - ## - # The section this CodeObject is in. Sections allow grouping of constants, - # attributes and methods inside a class or module. + ## + # 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 + def section + return @section if @section - @section = parent.add_section @section_title if parent - end + @section = parent.add_section @section_title if parent + end - ## - # Enable capture of documentation unless documentation has been - # turned off by :enddoc: + ## + # Enable capture of documentation unless documentation has been + # turned off by :enddoc: - def start_doc - return if @done_documenting + def start_doc + return if @done_documenting - @document_self = true - @document_children = true - @ignored = false - @suppressed = false - end + @document_self = true + @document_children = true + @ignored = false + @suppressed = false + end - ## - # Disable capture of documentation + ## + # Disable capture of documentation - def stop_doc - return unless @track_visibility + def stop_doc + return unless @track_visibility - @document_self = false - @document_children = false - end + @document_self = false + @document_children = false + end - ## - # Sets the +store+ that contains this CodeObject + ## + # Sets the +store+ that contains this CodeObject - def store=(store) - @store = store + def store=(store) + @store = store - return unless @track_visibility + return unless @track_visibility - if :nodoc == options.visibility - initialize_visibility - @track_visibility = false + if :nodoc == options.visibility + initialize_visibility + @track_visibility = false + end end - end - ## - # Use this to suppress a CodeObject and all its children until the next file - # it is seen in or documentation is discovered. A suppressed item with - # documentation will be displayed while an ignored item with documentation - # may not be displayed. + ## + # Use this to suppress a CodeObject and all its children until the next file + # it is seen in or documentation is discovered. A suppressed item with + # documentation will be displayed while an ignored item with documentation + # may not be displayed. - def suppress - return unless @track_visibility + def suppress + return unless @track_visibility - @suppressed = true + @suppressed = true - stop_doc - end + stop_doc + end - ## - # Has this class been suppressed? - # - # See also #suppress + ## + # Has this class been suppressed? + # + # See also #suppress - def suppressed? - @suppressed - end + def suppressed? + @suppressed + end + end end diff --git a/lib/rdoc/code_object/alias.rb b/lib/rdoc/code_object/alias.rb index 3a86777e4f..fdf75ef758 100644 --- a/lib/rdoc/code_object/alias.rb +++ b/lib/rdoc/code_object/alias.rb @@ -1,98 +1,100 @@ # frozen_string_literal: true -## -# Represent an alias, which is an old_name/new_name pair associated with a -# particular context -#-- -# TODO implement Alias as a proxy to a method/attribute, inheriting from -# MethodAttr +module RDoc + ## + # Represent an alias, which is an old_name/new_name pair associated with a + # particular context + #-- + # TODO implement Alias as a proxy to a method/attribute, inheriting from + # MethodAttr -class RDoc::Alias < RDoc::CodeObject + class Alias < ::RDoc::CodeObject - ## - # Aliased method's name + ## + # Aliased method's name - attr_reader :new_name + attr_reader :new_name - alias name new_name + alias name new_name - ## - # Aliasee method's name + ## + # Aliasee method's name - attr_reader :old_name + attr_reader :old_name - ## - # Is this an alias declared in a singleton context? + ## + # Is this an alias declared in a singleton context? - attr_reader :singleton + attr_reader :singleton - ## - # Creates a new Alias that aliases +old_name+ - # to +new_name+, has +comment+ and is a +singleton+ context. + ## + # Creates a new Alias that aliases +old_name+ + # to +new_name+, has +comment+ and is a +singleton+ context. - def initialize(old_name, new_name, comment, singleton: false) - super() + def initialize(old_name, new_name, comment, singleton: false) + super() - @singleton = singleton - @old_name = old_name - @new_name = new_name - self.comment = comment - end + @singleton = singleton + @old_name = old_name + @new_name = new_name + self.comment = comment + end - ## - # Order by #singleton then #new_name + ## + # Order by #singleton then #new_name - def <=>(other) - [@singleton ? 0 : 1, new_name] <=> [other.singleton ? 0 : 1, other.new_name] - end + def <=>(other) + [@singleton ? 0 : 1, new_name] <=> [other.singleton ? 0 : 1, other.new_name] + end - ## - # HTML fragment reference for this alias + ## + # HTML fragment reference for this alias - def aref - type = singleton ? 'c' : 'i' - "#alias-#{type}-#{html_name}" - end + def aref + type = singleton ? 'c' : 'i' + "#alias-#{type}-#{html_name}" + end - ## - # HTML id-friendly version of +#new_name+. + ## + # HTML id-friendly version of +#new_name+. - def html_name - CGI.escape(@new_name.gsub('-', '-2D')).gsub('%', '-').sub(/^-/, '') - end + def html_name + CGI.escape(@new_name.gsub('-', '-2D')).gsub('%', '-').sub(/^-/, '') + end - def inspect # :nodoc: - parent_name = parent ? parent.name : '(unknown)' - "#<%s:0x%x %s.alias_method %s, %s>" % [ - self.class, object_id, - parent_name, @old_name, @new_name, - ] - end + def inspect # :nodoc: + parent_name = parent ? parent.name : '(unknown)' + "#<%s:0x%x %s.alias_method %s, %s>" % [ + self.class, object_id, + parent_name, @old_name, @new_name, + ] + end - ## - # '::' for the alias of a singleton method/attribute, '#' for instance-level. + ## + # '::' for the alias of a singleton method/attribute, '#' for instance-level. - def name_prefix - singleton ? '::' : '#' - end + def name_prefix + singleton ? '::' : '#' + end - ## - # Old name with prefix '::' or '#'. + ## + # Old name with prefix '::' or '#'. - def pretty_old_name - "#{singleton ? '::' : '#'}#{@old_name}" - end + def pretty_old_name + "#{singleton ? '::' : '#'}#{@old_name}" + end - ## - # New name with prefix '::' or '#'. + ## + # New name with prefix '::' or '#'. - def pretty_new_name - "#{singleton ? '::' : '#'}#{@new_name}" - end + def pretty_new_name + "#{singleton ? '::' : '#'}#{@new_name}" + end - alias pretty_name pretty_new_name + alias pretty_name pretty_new_name - def to_s # :nodoc: - "alias: #{self.new_name} -> #{self.pretty_old_name} in: #{parent}" - end + def to_s # :nodoc: + "alias: #{self.new_name} -> #{self.pretty_old_name} in: #{parent}" + end + end end diff --git a/lib/rdoc/code_object/any_method.rb b/lib/rdoc/code_object/any_method.rb index c7e0e8f08e..bf21f546f8 100644 --- a/lib/rdoc/code_object/any_method.rb +++ b/lib/rdoc/code_object/any_method.rb @@ -1,387 +1,389 @@ # frozen_string_literal: true -## -# AnyMethod is the base class for objects representing methods +module RDoc + ## + # AnyMethod is the base class for objects representing methods -class RDoc::AnyMethod < RDoc::MethodAttr + class AnyMethod < ::RDoc::MethodAttr - ## - # 2:: - # RDoc 4 - # Added calls_super - # Added parent name and class - # Added section title - # 3:: - # RDoc 4.1 - # Added is_alias_for - # 4:: - # Added type_signature_lines (serialized as joined string) - - MARSHAL_VERSION = 4 # :nodoc: + ## + # 2:: + # RDoc 4 + # Added calls_super + # Added parent name and class + # Added section title + # 3:: + # RDoc 4.1 + # Added is_alias_for + # 4:: + # Added type_signature_lines (serialized as joined string) - ## - # Don't rename \#initialize to \::new + MARSHAL_VERSION = 4 # :nodoc: - attr_accessor :dont_rename_initialize + ## + # Don't rename \#initialize to \::new - ## - # The C function that implements this method (if it was defined in a C file) + attr_accessor :dont_rename_initialize - attr_accessor :c_function + ## + # The C function that implements this method (if it was defined in a C file) - # The section title of the method (if defined in a C file via +:category:+) - attr_accessor :section_title + attr_accessor :c_function - ## - # If true this method uses +super+ to call a superclass version + # The section title of the method (if defined in a C file via +:category:+) + attr_accessor :section_title - attr_accessor :calls_super + ## + # If true this method uses +super+ to call a superclass version - include RDoc::TokenStream + attr_accessor :calls_super - ## - # Creates a new AnyMethod with name +name+ + include ::RDoc::TokenStream - def initialize(name, singleton: false) - super(name, singleton: singleton) + ## + # Creates a new AnyMethod with name +name+ - @c_function = nil - @dont_rename_initialize = false - @token_stream = nil - @calls_super = false - @superclass_method = nil - end + def initialize(name, singleton: false) + super(name, singleton: singleton) - ## - # Adds +an_alias+ as an alias for this method in +context+. - - def add_alias(an_alias, context = nil) - method = self.class.new an_alias.new_name, singleton: singleton - - method.record_location an_alias.file - method.params = self.params - method.visibility = self.visibility - method.comment = an_alias.comment - method.is_alias_for = self - method.type_signature_lines = self.type_signature_lines - @aliases << method - context.add_method method if context - method - end + @c_function = nil + @dont_rename_initialize = false + @token_stream = nil + @calls_super = false + @superclass_method = nil + end - ## - # Prefix for +aref+ is 'method'. + ## + # Adds +an_alias+ as an alias for this method in +context+. + + def add_alias(an_alias, context = nil) + method = self.class.new an_alias.new_name, singleton: singleton + + method.record_location an_alias.file + method.params = self.params + method.visibility = self.visibility + method.comment = an_alias.comment + method.is_alias_for = self + method.type_signature_lines = self.type_signature_lines + @aliases << method + context.add_method method if context + method + end - def aref_prefix - 'method' - end + ## + # Prefix for +aref+ is 'method'. - ## - # The call_seq or the param_seq with method name, if there is no call_seq. - # - # Use this for displaying a method's argument lists. - - def arglists - if @call_seq - @call_seq - elsif @params - "#{name}#{param_seq}" + def aref_prefix + 'method' end - end - ## - # Different ways to call this method + ## + # The call_seq or the param_seq with method name, if there is no call_seq. + # + # Use this for displaying a method's argument lists. - def call_seq - unless call_seq = _call_seq - call_seq = is_alias_for._call_seq if is_alias_for + def arglists + if @call_seq + @call_seq + elsif @params + "#{name}#{param_seq}" + end end - return unless call_seq + ## + # Different ways to call this method - deduplicate_call_seq(call_seq) - end + def call_seq + unless call_seq = _call_seq + call_seq = is_alias_for._call_seq if is_alias_for + end - ## - # Sets the different ways you can call this method. If an empty +call_seq+ - # is given nil is assumed. - # - # See also #param_seq + return unless call_seq - def call_seq=(call_seq) - return if call_seq.nil? || call_seq.empty? + deduplicate_call_seq(call_seq) + end - @call_seq = call_seq - end + ## + # Sets the different ways you can call this method. If an empty +call_seq+ + # is given nil is assumed. + # + # See also #param_seq - ## - # Whether the method has a call-seq. + def call_seq=(call_seq) + return if call_seq.nil? || call_seq.empty? - def has_call_seq? - !!(@call_seq || is_alias_for&._call_seq) - end + @call_seq = call_seq + end - ## - # Loads is_alias_for from the internal name. Returns nil if the alias - # cannot be found. + ## + # Whether the method has a call-seq. - def is_alias_for # :nodoc: - case @is_alias_for - when RDoc::MethodAttr - @is_alias_for - when Array - return nil unless @store + def has_call_seq? + !!(@call_seq || is_alias_for&._call_seq) + end - klass_name, singleton, method_name = @is_alias_for + ## + # Loads is_alias_for from the internal name. Returns nil if the alias + # cannot be found. - return nil unless klass = @store.find_class_or_module(klass_name) + def is_alias_for # :nodoc: + case @is_alias_for + when ::RDoc::MethodAttr + @is_alias_for + when Array + return nil unless @store - @is_alias_for = klass.find_method method_name, singleton - end - end + klass_name, singleton, method_name = @is_alias_for - ## - # Dumps this AnyMethod for use by ri. See also #marshal_load + return nil unless klass = @store.find_class_or_module(klass_name) - def marshal_dump - aliases = @aliases.map do |a| - [a.name, parse(a.comment)] + @is_alias_for = klass.find_method method_name, singleton + end end - is_alias_for = [ - @is_alias_for.parent.full_name, - @is_alias_for.singleton, - @is_alias_for.name - ] if @is_alias_for - - [ MARSHAL_VERSION, - @name, - full_name, - @singleton, - @visibility, - parse(@comment), - @call_seq, - @block_params, - aliases, - @params, - @file.relative_name, - @calls_super, - @parent.name, - @parent.class, - @section.title, - is_alias_for, - @type_signature_lines&.join("\n"), - ] - end + ## + # Dumps this AnyMethod for use by ri. See also #marshal_load - ## - # Loads this AnyMethod from +array+. For a loaded AnyMethod the following - # methods will return cached values: - # - # * #full_name - # * #parent_name - - def marshal_load(array) - initialize_visibility - - @dont_rename_initialize = 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] || :public - @comment = RDoc::Comment.from_document 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] - @is_alias_for = array[15] - @type_signature_lines = array[16]&.split("\n") - - array[8].each do |new_name, document| - add_alias RDoc::Alias.new(@name, new_name, RDoc::Comment.from_document(document), singleton: @singleton) + def marshal_dump + aliases = @aliases.map do |a| + [a.name, parse(a.comment)] + end + + is_alias_for = [ + @is_alias_for.parent.full_name, + @is_alias_for.singleton, + @is_alias_for.name + ] if @is_alias_for + + [ MARSHAL_VERSION, + @name, + full_name, + @singleton, + @visibility, + parse(@comment), + @call_seq, + @block_params, + aliases, + @params, + @file.relative_name, + @calls_super, + @parent.name, + @parent.class, + @section.title, + is_alias_for, + @type_signature_lines&.join("\n"), + ] end - @parent_name ||= if @full_name =~ /#/ - $` - else - name = @full_name.split('::') - name.pop - name.join '::' - end + ## + # Loads this AnyMethod from +array+. For a loaded AnyMethod the following + # methods will return cached values: + # + # * #full_name + # * #parent_name + + def marshal_load(array) + initialize_visibility + + @dont_rename_initialize = 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] || :public + @comment = ::RDoc::Comment.from_document 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] + @is_alias_for = array[15] + @type_signature_lines = array[16]&.split("\n") + + array[8].each do |new_name, document| + add_alias ::RDoc::Alias.new(@name, new_name, ::RDoc::Comment.from_document(document), singleton: @singleton) + end - @file = RDoc::TopLevel.new array[10] if version > 0 - end + @parent_name ||= if @full_name =~ /#/ + $` + else + name = @full_name.split('::') + name.pop + name.join '::' + end - ## - # Method name - # - # If the method has no assigned name, it extracts it from #call_seq. + @file = ::RDoc::TopLevel.new array[10] if version > 0 + end - def name - return @name if @name + ## + # Method name + # + # If the method has no assigned name, it extracts it from #call_seq. - @name = - @call_seq[/^.*?\.(\w+)/, 1] || - @call_seq[/^.*?(\w+)/, 1] || - @call_seq if @call_seq - end + def name + return @name if @name - ## - # A list of this method's method and yield parameters. +call-seq+ params - # are preferred over parsed method and block params. - - def param_list - if @call_seq - params = @call_seq.split("\n").last - params = params.sub(/.*?\((.*)\)/, '\1') - params = params.sub(/(\{|do)\s*\|([^|]*)\|.*/, ',\2') - elsif @params - params = @params.sub(/\((.*)\)/, '\1') - - params << ",#{@block_params}" if @block_params - elsif @block_params - params = @block_params - else - return [] + @name = + @call_seq[/^.*?\.(\w+)/, 1] || + @call_seq[/^.*?(\w+)/, 1] || + @call_seq if @call_seq end - if @block_params - # If this method has explicit block parameters, remove any explicit - # &block - params = params.sub(/,?\s*&\w+/, '') - else - params = params.sub(/\&(\w+)/, '\1') - end + ## + # A list of this method's method and yield parameters. +call-seq+ params + # are preferred over parsed method and block params. + + def param_list + if @call_seq + params = @call_seq.split("\n").last + params = params.sub(/.*?\((.*)\)/, '\1') + params = params.sub(/(\{|do)\s*\|([^|]*)\|.*/, ',\2') + elsif @params + params = @params.sub(/\((.*)\)/, '\1') + + params << ",#{@block_params}" if @block_params + elsif @block_params + params = @block_params + else + return [] + end - params = params.gsub(/\s+/, '').split(',').reject(&:empty?) + if @block_params + # If this method has explicit block parameters, remove any explicit + # &block + params = params.sub(/,?\s*&\w+/, '') + else + params = params.sub(/\&(\w+)/, '\1') + end - params.map { |param| param.sub(/=.*/, '') } - end + params = params.gsub(/\s+/, '').split(',').reject(&:empty?) - ## - # Pretty parameter list for this method. If the method's parameters were - # given by +call-seq+ it is preferred over the parsed values. - - def param_seq - if @call_seq - params = @call_seq.split("\n").last - params = params.sub(/[^( ]+/, '') - params = params.sub(/(\|[^|]+\|)\s*\.\.\.\s*(end|\})/, '\1 \2') - elsif @params - params = @params.gsub(/\s*\#.*/, '') - params = params.tr_s("\n ", " ") - params = "(#{params})" unless params[0] == ?( - else - params = '' + params.map { |param| param.sub(/=.*/, '') } end - if @block_params - # If this method has explicit block parameters, remove any explicit - # &block - params = params.sub(/,?\s*&\w+/, '') + ## + # Pretty parameter list for this method. If the method's parameters were + # given by +call-seq+ it is preferred over the parsed values. + + def param_seq + if @call_seq + params = @call_seq.split("\n").last + params = params.sub(/[^( ]+/, '') + params = params.sub(/(\|[^|]+\|)\s*\.\.\.\s*(end|\})/, '\1 \2') + elsif @params + params = @params.gsub(/\s*\#.*/, '') + params = params.tr_s("\n ", " ") + params = "(#{params})" unless params[0] == ?( + else + params = '' + end - block = @block_params.tr_s("\n ", " ") - if block[0] == ?( - block = block.sub(/^\(/, '').sub(/\)/, '') + if @block_params + # If this method has explicit block parameters, remove any explicit + # &block + params = params.sub(/,?\s*&\w+/, '') + + block = @block_params.tr_s("\n ", " ") + if block[0] == ?( + block = block.sub(/^\(/, '').sub(/\)/, '') + end + params << " { |#{block}| ... }" end - params << " { |#{block}| ... }" - end - params - end + params + end - ## - # Whether to skip the method description, true for methods that have - # aliases with a call-seq that doesn't include the method name. + ## + # Whether to skip the method description, true for methods that have + # aliases with a call-seq that doesn't include the method name. - def skip_description? - has_call_seq? && call_seq.nil? && !!(is_alias_for || !aliases.empty?) - end + def skip_description? + has_call_seq? && call_seq.nil? && !!(is_alias_for || !aliases.empty?) + end - ## - # Sets the store for this method and its referenced code objects. + ## + # Sets the store for this method and its referenced code objects. - def store=(store) - super + def store=(store) + super - @file = @store.add_file @file.full_name if @file - end + @file = @store.add_file @file.full_name if @file + end - ## - # For methods that +super+, find the superclass method that would be called. + ## + # 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 + 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 } - @superclass_method = method - break + parent.each_ancestor do |ancestor| + if method = ancestor.method_list.find { |m| m.name == @name } + @superclass_method = method + break + end end - end - @superclass_method - end + @superclass_method + end protected - ## - # call_seq without deduplication and alias lookup. + ## + # call_seq without deduplication and alias lookup. - def _call_seq - @call_seq if defined?(@call_seq) && @call_seq - end + def _call_seq + @call_seq if defined?(@call_seq) && @call_seq + end private - ## - # call_seq with alias examples information removed, if this - # method is an alias method. + ## + # call_seq with alias examples information removed, if this + # method is an alias method. - def deduplicate_call_seq(call_seq) - return call_seq unless is_alias_for || !aliases.empty? + def deduplicate_call_seq(call_seq) + return call_seq unless is_alias_for || !aliases.empty? - method_name = self.name + method_name = self.name - entries = call_seq.split "\n" + entries = call_seq.split "\n" - ignore = aliases.map(&:name) - if is_alias_for - ignore << is_alias_for.name - ignore.concat is_alias_for.aliases.map(&:name) - end + ignore = aliases.map(&:name) + if is_alias_for + ignore << is_alias_for.name + ignore.concat is_alias_for.aliases.map(&:name) + end - ignore.delete(method_name) - ignore_bracket_methods, ignore_other_methods = ignore.partition {|i| i.start_with?('[') } + ignore.delete(method_name) + ignore_bracket_methods, ignore_other_methods = ignore.partition {|i| i.start_with?('[') } - if ignore_other_methods.any? - ignore_union = Regexp.union(ignore_other_methods) - entries.reject! do |entry| - /\A(?:\w*\.)?#{ignore_union}(?:[(\s]|\z)/.match?(entry) || - /\s#{ignore_union}\s/.match?(entry) + if ignore_other_methods.any? + ignore_union = Regexp.union(ignore_other_methods) + entries.reject! do |entry| + /\A(?:\w*\.)?#{ignore_union}(?:[(\s]|\z)/.match?(entry) || + /\s#{ignore_union}\s/.match?(entry) + end end - end - if ignore_bracket_methods.any? - entries.reject! do |entry| - # Ignore `receiver[arg] -> return_type` `[](arg)` `[]` - /\A\w*\[.*?\](?:[(\s]|\z)/.match?(entry) + if ignore_bracket_methods.any? + entries.reject! do |entry| + # Ignore `receiver[arg] -> return_type` `[](arg)` `[]` + /\A\w*\[.*?\](?:[(\s]|\z)/.match?(entry) + end end - end - entries.empty? ? nil : entries.join("\n") + entries.empty? ? nil : entries.join("\n") + end end end diff --git a/lib/rdoc/code_object/attr.rb b/lib/rdoc/code_object/attr.rb index 027e602509..3d18075c77 100644 --- a/lib/rdoc/code_object/attr.rb +++ b/lib/rdoc/code_object/attr.rb @@ -1,178 +1,180 @@ # frozen_string_literal: true -## -# An attribute created by \#attr, \#attr_reader, \#attr_writer or -# \#attr_accessor +module RDoc + ## + # An attribute created by \#attr, \#attr_reader, \#attr_writer or + # \#attr_accessor -class RDoc::Attr < RDoc::MethodAttr + class Attr < ::RDoc::MethodAttr - ## - # 3:: - # RDoc 4 - # Added parent name and class - # Added section title - # 4:: - # Added type_signature_lines (serialized as joined string) + ## + # 3:: + # RDoc 4 + # Added parent name and class + # Added section title + # 4:: + # Added type_signature_lines (serialized as joined string) - MARSHAL_VERSION = 4 # :nodoc: + MARSHAL_VERSION = 4 # :nodoc: - ## - # Is the attribute readable ('R'), writable ('W') or both ('RW')? + ## + # Is the attribute readable ('R'), writable ('W') or both ('RW')? - attr_accessor :rw + attr_accessor :rw - ## - # Creates a new Attr with +name+, read/write status +rw+ and - # +comment+. +singleton+ marks this as a class attribute. + ## + # Creates a new Attr with +name+, read/write status +rw+ and + # +comment+. +singleton+ marks this as a class attribute. - def initialize(name, rw, comment, singleton: false) - super(name, singleton: singleton) + def initialize(name, rw, comment, singleton: false) + super(name, singleton: singleton) - @rw = rw - self.comment = comment - end + @rw = rw + self.comment = comment + end - ## - # Attributes are equal when their names, singleton and rw are identical + ## + # Attributes are equal when their names, singleton and rw are identical - def ==(other) - self.class == other.class and - self.name == other.name and - self.rw == other.rw and - self.singleton == other.singleton - end + def ==(other) + self.class == other.class and + self.name == other.name and + self.rw == other.rw and + self.singleton == other.singleton + end - ## - # Add +an_alias+ as an attribute in +context+. - - def add_alias(an_alias, context) - access_type = an_alias.new_name.end_with?('=') ? 'W' : 'R' - new_attr = self.class.new(an_alias.new_name, access_type, comment, singleton: singleton) - new_attr.record_location an_alias.file - new_attr.visibility = self.visibility - new_attr.is_alias_for = self - new_attr.type_signature_lines = self.type_signature_lines - @aliases << new_attr - context.add_attribute new_attr - new_attr - end + ## + # Add +an_alias+ as an attribute in +context+. + + def add_alias(an_alias, context) + access_type = an_alias.new_name.end_with?('=') ? 'W' : 'R' + new_attr = self.class.new(an_alias.new_name, access_type, comment, singleton: singleton) + new_attr.record_location an_alias.file + new_attr.visibility = self.visibility + new_attr.is_alias_for = self + new_attr.type_signature_lines = self.type_signature_lines + @aliases << new_attr + context.add_attribute new_attr + new_attr + end - ## - # The #aref prefix for attributes + ## + # The #aref prefix for attributes - def aref_prefix - 'attribute' - end + def aref_prefix + '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) + ## + # 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 + def calls_super # :nodoc: + false + end - ## - # Returns attr_reader, attr_writer or attr_accessor as appropriate. + ## + # Returns attr_reader, attr_writer or attr_accessor as appropriate. - def definition - case @rw - when 'RW' then 'attr_accessor' - when 'R' then 'attr_reader' - when 'W' then 'attr_writer' + def definition + case @rw + when 'RW' then 'attr_accessor' + when 'R' then 'attr_reader' + when 'W' then 'attr_writer' + end end - end - def inspect # :nodoc: - alias_for = @is_alias_for ? " (alias for #{@is_alias_for.name})" : nil - visibility = self.visibility - visibility = "forced #{visibility}" if force_documentation - "#<%s:0x%x %s %s (%s)%s>" % [ - self.class, object_id, - full_name, - rw, - visibility, - alias_for, - ] - end + def inspect # :nodoc: + alias_for = @is_alias_for ? " (alias for #{@is_alias_for.name})" : nil + visibility = self.visibility + visibility = "forced #{visibility}" if force_documentation + "#<%s:0x%x %s %s (%s)%s>" % [ + self.class, object_id, + full_name, + rw, + visibility, + alias_for, + ] + end - ## - # Dumps this Attr for use by ri. See also #marshal_load - - def marshal_dump - [ MARSHAL_VERSION, - @name, - full_name, - @rw, - @visibility, - parse(@comment), - singleton, - @file.relative_name, - @parent.full_name, - @parent.class, - @section.title, - @type_signature_lines&.join("\n"), - ] - end + ## + # Dumps this Attr for use by ri. See also #marshal_load + + def marshal_dump + [ MARSHAL_VERSION, + @name, + full_name, + @rw, + @visibility, + parse(@comment), + singleton, + @file.relative_name, + @parent.full_name, + @parent.class, + @section.title, + @type_signature_lines&.join("\n"), + ] + end - ## - # Loads this Attr from +array+. For a loaded Attr the following - # methods will return cached values: - # - # * #full_name - # * #parent_name - - def marshal_load(array) - initialize_visibility - - @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] || :public - @comment = RDoc::Comment.from_document array[5] - @singleton = array[6] || false # MARSHAL_VERSION == 0 - # 7 handled below - @parent_name = array[8] - @parent_class = array[9] - @section_title = array[10] - @type_signature_lines = array[11]&.split("\n") - - @file = RDoc::TopLevel.new array[7] if version > 1 - - @parent_name ||= @full_name.split('#', 2).first - end + ## + # Loads this Attr from +array+. For a loaded Attr the following + # methods will return cached values: + # + # * #full_name + # * #parent_name + + def marshal_load(array) + initialize_visibility + + @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] || :public + @comment = ::RDoc::Comment.from_document array[5] + @singleton = array[6] || false # MARSHAL_VERSION == 0 + # 7 handled below + @parent_name = array[8] + @parent_class = array[9] + @section_title = array[10] + @type_signature_lines = array[11]&.split("\n") + + @file = ::RDoc::TopLevel.new array[7] if version > 1 + + @parent_name ||= @full_name.split('#', 2).first + end - def pretty_print(q) # :nodoc: - q.group 2, "[#{self.class.name} #{full_name} #{rw} #{visibility}", "]" do - unless comment.empty? - q.breakable - q.text "comment:" - q.breakable - q.pp @comment + def pretty_print(q) # :nodoc: + q.group 2, "[#{self.class.name} #{full_name} #{rw} #{visibility}", "]" do + unless comment.empty? + q.breakable + q.text "comment:" + q.breakable + q.pp @comment + end end end - end - def to_s # :nodoc: - "#{definition} #{name} in: #{parent}" - end + def to_s # :nodoc: + "#{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) + ## + # 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 + def token_stream # :nodoc: + end + end end diff --git a/lib/rdoc/code_object/class_module.rb b/lib/rdoc/code_object/class_module.rb index 96f84bf998..c3bf7be545 100644 --- a/lib/rdoc/code_object/class_module.rb +++ b/lib/rdoc/code_object/class_module.rb @@ -1,974 +1,975 @@ # frozen_string_literal: true -## -# ClassModule is the base class for objects representing either a class or a -# module. - -class RDoc::ClassModule < RDoc::Context - - ## - # 1:: - # RDoc 3.7 - # * Added visibility, singleton and file to attributes - # * 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 = 3 # :nodoc: - - ## - # Constants that are aliases for this class or module - - attr_accessor :constant_aliases +module RDoc + ## + # ClassModule is the base class for objects representing either a class or a + # module. + + class ClassModule < ::RDoc::Context + + ## + # 1:: + # RDoc 3.7 + # * Added visibility, singleton and file to attributes + # * 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 = 3 # :nodoc: + + ## + # Constants that are aliases for this class or module + + attr_accessor :constant_aliases + + ## + # A hash of { location => [comments] } documenting this class/module. + # Use #add_comment to add comments. + # + # Ruby hashes maintain insertion order, so comments render in the order + # they were first added. Each location maps to an array of comments, + # allowing a class reopened in the same file to accumulate multiple comments. + # + # Before marshalling: + # - +location+ is an RDoc::TopLevel + # - +comments+ are Strings + # + # After unmarshalling: + # - +location+ is a filename String + # - +comments+ are RDoc::Markup::Documents + + attr_accessor :comment_location + + ## + # Class or module this constant is an alias for + + attr_accessor :is_alias_for + + ## + # Return a RDoc::ClassModule of class +class_type+ that is a copy + # of module +module+. Used to promote modules to classes. + #-- + # TODO move to RDoc::NormalClass (I think) + + def self.from_module(class_type, mod) + klass = class_type.new mod.name + + mod.comment_location.each do |location, comments| + comments.each { |comment| klass.add_comment comment, location } + end - ## - # A hash of { location => [comments] } documenting this class/module. - # Use #add_comment to add comments. - # - # Ruby hashes maintain insertion order, so comments render in the order - # they were first added. Each location maps to an array of comments, - # allowing a class reopened in the same file to accumulate multiple comments. - # - # Before marshalling: - # - +location+ is an RDoc::TopLevel - # - +comments+ are Strings - # - # After unmarshalling: - # - +location+ is a filename String - # - +comments+ are RDoc::Markup::Documents - - attr_accessor :comment_location + klass.parent = mod.parent + klass.section = mod.section + + klass.attributes.concat mod.attributes + klass.method_list.concat mod.method_list + klass.aliases.concat mod.aliases + 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 + + klass.current_section = mod.current_section + klass.in_files.concat mod.in_files + klass.sections.concat mod.sections + klass.unmatched_alias_lists = mod.unmatched_alias_lists + klass.current_section = mod.current_section + klass.classes_hash.update mod.classes_hash + klass.modules_hash.update mod.modules_hash + klass.metadata.update mod.metadata + + klass.document_self = mod.received_nodoc ? nil : mod.document_self + klass.document_children = mod.document_children + klass.force_documentation = mod.force_documentation + klass.done_documenting = mod.done_documenting + + # update the parent of all children + + (klass.attributes + + klass.method_list + + klass.aliases + + klass.external_aliases + + klass.constants + + klass.includes + + klass.extends + + klass.classes + + klass.modules).each do |obj| + obj.parent = klass + obj.full_name = nil + end - ## - # Class or module this constant is an alias for + klass + end - attr_accessor :is_alias_for + ## + # Creates a new ClassModule with +name+ with optional +superclass+ + # + # This is a constructor for subclasses, and must never be called directly. - ## - # Return a RDoc::ClassModule of class +class_type+ that is a copy - # of module +module+. Used to promote modules to classes. - #-- - # TODO move to RDoc::NormalClass (I think) - - def self.from_module(class_type, mod) - klass = class_type.new mod.name - - mod.comment_location.each do |location, comments| - comments.each { |comment| klass.add_comment comment, location } - end - - klass.parent = mod.parent - klass.section = mod.section - - klass.attributes.concat mod.attributes - klass.method_list.concat mod.method_list - klass.aliases.concat mod.aliases - 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 - - klass.current_section = mod.current_section - klass.in_files.concat mod.in_files - klass.sections.concat mod.sections - klass.unmatched_alias_lists = mod.unmatched_alias_lists - klass.current_section = mod.current_section - - klass.classes_hash.update mod.classes_hash - klass.modules_hash.update mod.modules_hash - klass.metadata.update mod.metadata - - klass.document_self = mod.received_nodoc ? nil : mod.document_self - klass.document_children = mod.document_children - klass.force_documentation = mod.force_documentation - klass.done_documenting = mod.done_documenting - - # update the parent of all children - - (klass.attributes + - klass.method_list + - klass.aliases + - klass.external_aliases + - klass.constants + - klass.includes + - klass.extends + - klass.classes + - klass.modules).each do |obj| - obj.parent = klass - obj.full_name = nil - end - - klass - end + def initialize(name, superclass = nil) + @constant_aliases = [] + @is_alias_for = nil + @name = name + @superclass = superclass + @comment_location = {} # Hash of { location => [comments] } - ## - # Creates a new ClassModule with +name+ with optional +superclass+ - # - # This is a constructor for subclasses, and must never be called directly. - - def initialize(name, superclass = nil) - @constant_aliases = [] - @is_alias_for = nil - @name = name - @superclass = superclass - @comment_location = {} # Hash of { location => [comments] } - - super() - end + super() + end - ## - # Adds +comment+ to this ClassModule's list of comments at +location+. This - # method is preferred over #comment= since it allows ri data to be updated - # across multiple runs. + ## + # Adds +comment+ to this ClassModule's list of comments at +location+. This + # method is preferred over #comment= since it allows ri data to be updated + # across multiple runs. - def add_comment(comment, location) - return unless document_self + def add_comment(comment, location) + return unless document_self - original = comment + original = comment - comment = case comment - when RDoc::Comment - comment.normalize - else - normalize_comment comment - end + comment = case comment + when ::RDoc::Comment + comment.normalize + else + normalize_comment comment + end - (@comment_location[location] ||= []) << comment + (@comment_location[location] ||= []) << comment - self.comment = original - end + 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 + 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 + things.each do |thing| + yield true, thing + end end end - end - ## - # Ancestors list for this ClassModule: the list of included modules - # (classes will add their superclass if any). - # - # Returns the included classes or modules, not the includes - # themselves. The returned values are either String or - # RDoc::NormalModule instances (see RDoc::Include#module). - # - # The values are returned in reverse order of their inclusion, - # which is the order suitable for searching methods/attributes - # in the ancestors. The superclass, if any, comes last. - - def ancestors - included_ancestors - end + ## + # Ancestors list for this ClassModule: the list of included modules + # (classes will add their superclass if any). + # + # Returns the included classes or modules, not the includes + # themselves. The returned values are either String or + # RDoc::NormalModule instances (see RDoc::Include#module). + # + # The values are returned in reverse order of their inclusion, + # which is the order suitable for searching methods/attributes + # in the ancestors. The superclass, if any, comes last. - def included_ancestors # :nodoc: - includes.map { |i| i.module }.reverse - end + def ancestors + included_ancestors + end - def aref_prefix # :nodoc: - raise NotImplementedError, "missing aref_prefix for #{self.class}" - end + def included_ancestors # :nodoc: + includes.map { |i| i.module }.reverse + end - ## - # HTML fragment reference for this module or class using GitHub-style - # anchor format (lowercase, :: replaced with -). - # - # Examples: - # Foo -> class-foo - # Foo::Bar -> class-foo-bar - - def aref - "#{aref_prefix}-#{full_name.downcase.gsub('::', '-')}" - end + def aref_prefix # :nodoc: + raise NotImplementedError, "missing aref_prefix for #{self.class}" + end - ## - # Legacy HTML fragment reference for backward compatibility. - # Returns the old RDoc-style anchor format. - # - # Examples: - # Foo -> class-Foo - # Foo::Bar -> class-Foo::Bar - - def legacy_aref - "#{aref_prefix}-#{full_name}" - end + ## + # HTML fragment reference for this module or class using GitHub-style + # anchor format (lowercase, :: replaced with -). + # + # Examples: + # Foo -> class-foo + # Foo::Bar -> class-foo-bar - ## - # Ancestors of this class or module only + def aref + "#{aref_prefix}-#{full_name.downcase.gsub('::', '-')}" + end - alias direct_ancestors ancestors + ## + # Legacy HTML fragment reference for backward compatibility. + # Returns the old RDoc-style anchor format. + # + # Examples: + # Foo -> class-Foo + # Foo::Bar -> class-Foo::Bar - ## - # Clears the comment. Used by the Ruby parser. + def legacy_aref + "#{aref_prefix}-#{full_name}" + end - def clear_comment - @comment = '' - end + ## + # Ancestors of this class or module only - ## - # This method is deprecated, use #add_comment instead. - # - # Appends +comment+ to the current comment, but separated by a rule. Works - # more like +=. - - def comment=(comment) # :nodoc: - comment = case comment - when RDoc::Comment - comment.normalize - else - normalize_comment comment - end + alias direct_ancestors ancestors - comment = "#{@comment.to_s}\n---\n#{comment.to_s}" unless @comment.empty? + ## + # Clears the comment. Used by the Ruby parser. - super comment - end + def clear_comment + @comment = '' + end - ## - # Prepares this ClassModule for use by a generator. - # - # See RDoc::Store#complete - - def complete(min_visibility) - update_aliases - remove_nodoc_children - embed_mixins - update_includes - update_extends - remove_invisible min_visibility - end + ## + # This method is deprecated, use #add_comment instead. + # + # Appends +comment+ to the current comment, but separated by a rule. Works + # more like +=. - ## - # Does this ClassModule or any of its methods have document_self set? + def comment=(comment) # :nodoc: + comment = case comment + when ::RDoc::Comment + comment.normalize + else + normalize_comment comment + end - def document_self_or_methods - document_self || method_list.any?{ |m| m.document_self } - end + comment = "#{@comment.to_s}\n---\n#{comment.to_s}" unless @comment.empty? - ## - # Does this class or module have a comment with content or is - # #received_nodoc true? + super comment + end - def documented? - return true if @received_nodoc - return false if @comment_location.empty? - @comment_location.each_value.any? { |comments| comments.any? { |c| not c.empty? } } - end + ## + # Prepares this ClassModule for use by a generator. + # + # See RDoc::Store#complete - ## - # Iterates the ancestors of this class or module for which an - # RDoc::ClassModule exists. + def complete(min_visibility) + update_aliases + remove_nodoc_children + embed_mixins + update_includes + update_extends + remove_invisible min_visibility + end - def each_ancestor # :yields: module - return enum_for __method__ unless block_given? + ## + # Does this ClassModule or any of its methods have document_self set? - ancestors.each do |mod| - next if String === mod - next if self == mod - yield mod + def document_self_or_methods + document_self || method_list.any?{ |m| m.document_self } end - end - ## - # Looks for a symbol in the #ancestors. See Context#find_local_symbol. + ## + # Does this class or module have a comment with content or is + # #received_nodoc true? - def find_ancestor_local_symbol(symbol) - each_ancestor do |m| - res = m.find_local_symbol(symbol) - return res if res + def documented? + return true if @received_nodoc + return false if @comment_location.empty? + @comment_location.each_value.any? { |comments| comments.any? { |c| not c.empty? } } end - nil - end - - ## - # Finds a class or module with +name+ in this namespace or its descendants + ## + # Iterates the ancestors of this class or module for which an + # RDoc::ClassModule exists. - def find_class_named(name) - return self if full_name == name - return self if @name == name + def each_ancestor # :yields: module + return enum_for __method__ unless block_given? - @classes.values.find do |klass| - next if klass == self - klass.find_class_named name + ancestors.each do |mod| + next if String === mod + next if self == mod + yield mod + end end - end - - ## - # Return the fully qualified name of this class or module - - def full_name - @full_name ||= if RDoc::ClassModule === parent - "#{parent.full_name}::#{@name}" - else - @name - end - end - ## - # Return array of full_name splitted by +::+. + ## + # Looks for a symbol in the #ancestors. See Context#find_local_symbol. - def nesting_namespaces - @namespaces ||= full_name.split("::").reject(&:empty?) - end - - ## - # Return array of fully qualified nesting namespaces. - # - # For example, if full_name is +A::B::C+, this method returns ["A", "A::B", "A::B::C"] + def find_ancestor_local_symbol(symbol) + each_ancestor do |m| + res = m.find_local_symbol(symbol) + return res if res + end - def fully_qualified_nesting_namespaces - return nesting_namespaces if nesting_namespaces.length < 2 - @fqns ||= nesting_namespaces.inject([]) do |list, n| - list << (list.empty? ? n : "#{list.last}::#{n}") + nil end - end - ## - # TODO: filter included items by #display? + ## + # Finds a class or module with +name+ in this namespace or its descendants - def marshal_dump # :nodoc: - attrs = attributes.sort.map do |attr| - next unless attr.display? - [ attr.name, attr.rw, - attr.visibility, attr.singleton, attr.file_name, - ] - end.compact - - method_types = methods_by_type.map do |type, visibilities| - visibilities = visibilities.map do |visibility, methods| - method_names = methods.map do |method| - next unless method.display? - [method.name, method.file_name] - end.compact - - [visibility, method_names.uniq] - end - - [type, visibilities] - end - - [ MARSHAL_VERSION, - @name, - full_name, - @superclass, - parse(@comment_location), - attrs, - constants.select { |constant| constant.display? }, - includes.map do |incl| - next unless incl.display? - [incl.name, parse(incl.comment), incl.file_name] - end.compact, - method_types, - extends.map do |ext| - next unless ext.display? - [ext.name, parse(ext.comment), ext.file_name] - end.compact, - @sections.values, - @in_files.map do |tl| - tl.relative_name - end, - parent.full_name, - parent.class, - ] - end + def find_class_named(name) + return self if full_name == name + return self if @name == name - def marshal_load(array) # :nodoc: - initialize_visibility - initialize_methods_etc - @current_section = nil - @document_self = true - @done_documenting = false - @parent = nil - @temporary_section = nil - @classes = {} - @modules = {} - - @name = array[1] - @full_name = array[2] - @superclass = array[3] - document = array[4] - - @comment = RDoc::Comment.from_document document - - @comment_location = if document.parts.first.is_a?(RDoc::Markup::Document) - document.parts.group_by(&:file) - else - { document.file => [document] } - end - - array[5].each do |name, rw, visibility, singleton, file| - singleton ||= false - visibility ||= :public - - attr = RDoc::Attr.new name, rw, nil, singleton: singleton - - add_attribute attr - attr.visibility = visibility - attr.record_location RDoc::TopLevel.new file - end - - array[6].each do |constant, document, file| - case constant - when RDoc::Constant - add_constant constant - else - constant = add_constant RDoc::Constant.new(constant, nil, RDoc::Comment.from_document(document)) - constant.record_location RDoc::TopLevel.new file + @classes.values.find do |klass| + next if klass == self + klass.find_class_named name end end - array[7].each do |name, document, file| - incl = add_include RDoc::Include.new(name, RDoc::Comment.from_document(document)) - incl.record_location RDoc::TopLevel.new file - end + ## + # Return the fully qualified name of this class or module - array[8].each do |type, visibilities| - visibilities.each do |visibility, methods| - methods.each do |name, file| - method = RDoc::AnyMethod.new name, singleton: type == 'class' - method.record_location RDoc::TopLevel.new file - method.visibility = visibility - add_method method - end - end + def full_name + @full_name ||= if ::RDoc::ClassModule === parent + "#{parent.full_name}::#{@name}" + else + @name + end end - array[9].each do |name, document, file| - ext = add_extend RDoc::Extend.new(name, RDoc::Comment.from_document(document)) - ext.record_location RDoc::TopLevel.new file - end if array[9] # Support Marshal version 1 + ## + # Return array of full_name splitted by +::+. - sections = (array[10] || []).map do |section| - [section.title, section] + def nesting_namespaces + @namespaces ||= full_name.split("::").reject(&:empty?) end - @sections = Hash[*sections.flatten] - @current_section = add_section nil + ## + # Return array of fully qualified nesting namespaces. + # + # For example, if full_name is +A::B::C+, this method returns ["A", "A::B", "A::B::C"] - @in_files = [] - - (array[11] || []).each do |filename| - record_location RDoc::TopLevel.new filename + def fully_qualified_nesting_namespaces + return nesting_namespaces if nesting_namespaces.length < 2 + @fqns ||= nesting_namespaces.inject([]) do |list, n| + list << (list.empty? ? n : "#{list.last}::#{n}") + end end - @parent_name = array[12] - @parent_class = array[13] - end + ## + # TODO: filter included items by #display? - ## - # Merges +class_module+ into this ClassModule. - # - # The data in +class_module+ is preferred over the receiver. + def marshal_dump # :nodoc: + attrs = attributes.sort.map do |attr| + next unless attr.display? + [ attr.name, attr.rw, + attr.visibility, attr.singleton, attr.file_name, + ] + end.compact + + method_types = methods_by_type.map do |type, visibilities| + visibilities = visibilities.map do |visibility, methods| + method_names = methods.map do |method| + next unless method.display? + [method.name, method.file_name] + end.compact - def merge(class_module) - @parent = class_module.parent - @parent_name = class_module.parent_name + [visibility, method_names.uniq] + end - other_document = parse class_module.comment_location + [type, visibilities] + end + + [ MARSHAL_VERSION, + @name, + full_name, + @superclass, + parse(@comment_location), + attrs, + constants.select { |constant| constant.display? }, + includes.map do |incl| + next unless incl.display? + [incl.name, parse(incl.comment), incl.file_name] + end.compact, + method_types, + extends.map do |ext| + next unless ext.display? + [ext.name, parse(ext.comment), ext.file_name] + end.compact, + @sections.values, + @in_files.map do |tl| + tl.relative_name + end, + parent.full_name, + parent.class, + ] + end - if other_document - document = parse @comment_location + def marshal_load(array) # :nodoc: + initialize_visibility + initialize_methods_etc + @current_section = nil + @document_self = true + @done_documenting = false + @parent = nil + @temporary_section = nil + @classes = {} + @modules = {} - document = document.merge other_document + @name = array[1] + @full_name = array[2] + @superclass = array[3] + document = array[4] - @comment = RDoc::Comment.from_document(document) + @comment = ::RDoc::Comment.from_document document - @comment_location = if document.parts.first.is_a?(RDoc::Markup::Document) + @comment_location = if document.parts.first.is_a?(::RDoc::Markup::Document) document.parts.group_by(&:file) else { document.file => [document] } end - end - cm = class_module - other_files = cm.in_files + array[5].each do |name, rw, visibility, singleton, file| + singleton ||= false + visibility ||= :public + + attr = ::RDoc::Attr.new name, rw, nil, singleton: singleton - merge_collections attributes, cm.attributes, other_files do |add, attr| - if add add_attribute attr - else - @attributes.delete attr - @methods_hash.delete attr.pretty_name + attr.visibility = visibility + attr.record_location ::RDoc::TopLevel.new file end - end - merge_collections constants, cm.constants, other_files do |add, const| - if add - add_constant const - else - @constants.delete const - @constants_hash.delete const.name + array[6].each do |constant, document, file| + case constant + when ::RDoc::Constant + add_constant constant + else + constant = add_constant ::RDoc::Constant.new(constant, nil, ::RDoc::Comment.from_document(document)) + constant.record_location ::RDoc::TopLevel.new file + end end - end - merge_collections includes, cm.includes, other_files do |add, incl| - if add - add_include incl - else - @includes.delete incl + array[7].each do |name, document, file| + incl = add_include ::RDoc::Include.new(name, ::RDoc::Comment.from_document(document)) + incl.record_location ::RDoc::TopLevel.new file end - end - @includes.uniq! # clean up + array[8].each do |type, visibilities| + visibilities.each do |visibility, methods| + methods.each do |name, file| + method = ::RDoc::AnyMethod.new name, singleton: type == 'class' + method.record_location ::RDoc::TopLevel.new file + method.visibility = visibility + add_method method + end + end + end - merge_collections extends, cm.extends, other_files do |add, ext| - if add - add_extend ext - else - @extends.delete ext + array[9].each do |name, document, file| + ext = add_extend ::RDoc::Extend.new(name, ::RDoc::Comment.from_document(document)) + 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 - end - @extends.uniq! # clean up + @sections = Hash[*sections.flatten] + @current_section = add_section nil - merge_collections method_list, cm.method_list, other_files do |add, meth| - if add - add_method meth - else - @method_list.delete meth - @methods_hash.delete meth.pretty_name + @in_files = [] + + (array[11] || []).each do |filename| + record_location ::RDoc::TopLevel.new filename end + + @parent_name = array[12] + @parent_class = array[13] end - merge_sections cm + ## + # Merges +class_module+ into this ClassModule. + # + # The data in +class_module+ is preferred over the receiver. - self - end + def merge(class_module) + @parent = class_module.parent + @parent_name = class_module.parent_name - ## - # Merges collection +mine+ with +other+ preferring other. +other_files+ is - # used to help determine which items should be deleted. - # - # Yields whether the item should be added or removed (true or false) and the - # item to be added or removed. - # - # merge_collections things, other.things, other.in_files do |add, thing| - # if add - # # add the thing - # else - # # remove the thing - # end - # end - - def merge_collections(mine, other, other_files, &block) # :nodoc: - my_things = mine. group_by { |thing| thing.file } - other_things = other.group_by { |thing| thing.file } - - remove_things my_things, other_files, &block - add_things my_things, other_things, &block - end + other_document = parse class_module.comment_location - ## - # Merges the comments in this ClassModule with the comments in the other - # ClassModule +cm+. + if other_document + document = parse @comment_location - def merge_sections(cm) # :nodoc: - my_sections = sections.group_by { |section| section.title } - other_sections = cm.sections.group_by { |section| section.title } + document = document.merge other_document - other_files = cm.in_files + @comment = ::RDoc::Comment.from_document(document) - remove_things my_sections, other_files do |_, section| - @sections.delete section.title - end + @comment_location = if document.parts.first.is_a?(::RDoc::Markup::Document) + document.parts.group_by(&:file) + else + { document.file => [document] } + end + end - other_sections.each do |group, sections| - if my_sections.include? group - my_sections[group].each do |my_section| - other_section = cm.sections_hash[group] + cm = class_module + other_files = cm.in_files - my_comments = my_section.comments - other_comments = other_section.comments + merge_collections attributes, cm.attributes, other_files do |add, attr| + if add + add_attribute attr + else + @attributes.delete attr + @methods_hash.delete attr.pretty_name + end + end - other_files = other_section.in_files + merge_collections constants, cm.constants, other_files do |add, const| + if add + add_constant const + else + @constants.delete const + @constants_hash.delete const.name + end + end - merge_collections my_comments, other_comments, other_files do |add, comment| - if add - my_section.add_comment comment - else - my_section.remove_comment comment - end - end + merge_collections includes, cm.includes, other_files do |add, incl| + if add + add_include incl + else + @includes.delete incl end - else - sections.each do |section| - add_section group, section.comments + end + + @includes.uniq! # clean up + + merge_collections extends, cm.extends, other_files do |add, ext| + if add + add_extend ext + else + @extends.delete ext end end - end - end - ## - # Does this object represent a module? + @extends.uniq! # clean up - def module? - false - end + merge_collections method_list, cm.method_list, other_files do |add, meth| + if add + add_method meth + else + @method_list.delete meth + @methods_hash.delete meth.pretty_name + end + end - ## - # Allows overriding the initial name. - # - # Used for modules and classes that are constant aliases. + merge_sections cm - def name=(new_name) - @name = new_name - end + self + end - ## - # Parses +comment_location+ into an RDoc::Markup::Document composed of - # multiple RDoc::Markup::Documents with their file set. + ## + # Merges collection +mine+ with +other+ preferring other. +other_files+ is + # used to help determine which items should be deleted. + # + # Yields whether the item should be added or removed (true or false) and the + # item to be added or removed. + # + # merge_collections things, other.things, other.in_files do |add, thing| + # if add + # # add the thing + # else + # # remove the thing + # end + # end - def parse(comment_location) - case comment_location - when String - super - when Hash - docs = comment_location.flat_map do |location, comments| - comments.map do |comment| - doc = super comment - doc.file = location - doc - end - end + def merge_collections(mine, other, other_files, &block) # :nodoc: + my_things = mine. group_by { |thing| thing.file } + other_things = other.group_by { |thing| thing.file } - RDoc::Markup::Document.new(*docs) - when RDoc::Comment - doc = super comment_location.text, comment_location.format - doc.file = comment_location.location - doc - when RDoc::Markup::Document - return comment_location - else - raise ArgumentError, "unknown comment class #{comment_location.class}" + remove_things my_things, other_files, &block + add_things my_things, other_things, &block end - end - ## - # Path to this class or module for use with HTML generator output. + ## + # Merges the comments in this ClassModule with the comments in the other + # ClassModule +cm+. - def path - prefix = options.class_module_path_prefix - return http_url unless prefix - File.join(prefix, http_url) - end + def merge_sections(cm) # :nodoc: + my_sections = sections.group_by { |section| section.title } + other_sections = cm.sections.group_by { |section| section.title } - ## - # Name to use to generate the url: - # modules and classes that are aliases for another - # module or class return the name of the latter. + other_files = cm.in_files - def name_for_path - is_alias_for ? is_alias_for.name_for_path : full_name - end + remove_things my_sections, other_files do |_, section| + @sections.delete section.title + end - ## - # Returns the classes and modules that are not constants - # aliasing another class or module. For use by formatters - # only (caches its result). + other_sections.each do |group, sections| + if my_sections.include? group + my_sections[group].each do |my_section| + other_section = cm.sections_hash[group] - def non_aliases - @non_aliases ||= classes_and_modules.reject { |cm| cm.is_alias_for } - end + my_comments = my_section.comments + other_comments = other_section.comments - ## - # Updates the child modules or classes of class/module +parent+ by - # deleting the ones that have been removed from the documentation. - # - # +parent_hash+ is either parent.modules_hash or - # parent.classes_hash and +all_hash+ is ::all_modules_hash or - # ::all_classes_hash. + other_files = other_section.in_files - def remove_nodoc_children - prefix = self.full_name + '::' + merge_collections my_comments, other_comments, other_files do |add, comment| + if add + 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 - modules_hash.each_key do |name| - full_name = prefix + name - modules_hash.delete name unless @store.modules_hash[full_name] + ## + # Does this object represent a module? + + def module? + false end - classes_hash.each_key do |name| - full_name = prefix + name - classes_hash.delete name unless @store.classes_hash[full_name] + ## + # Allows overriding the initial name. + # + # Used for modules and classes that are constant aliases. + + def name=(new_name) + @name = new_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 + ## + # Parses +comment_location+ into an RDoc::Markup::Document composed of + # multiple RDoc::Markup::Documents with their file set. - things.each do |thing| - yield false, thing + def parse(comment_location) + case comment_location + when String + super + when Hash + docs = comment_location.flat_map do |location, comments| + comments.map do |comment| + doc = super comment + doc.file = location + doc + end + end + + ::RDoc::Markup::Document.new(*docs) + when ::RDoc::Comment + doc = super comment_location.text, comment_location.format + doc.file = comment_location.location + doc + when ::RDoc::Markup::Document + return comment_location + else + raise ArgumentError, "unknown comment class #{comment_location.class}" end + end + + ## + # Path to this class or module for use with HTML generator output. - true + def path + prefix = options.class_module_path_prefix + return http_url unless prefix + File.join(prefix, http_url) end - end - ## - # Search record used by RDoc::Generator::JsonIndex - # - # TODO: Remove this method after dropping the darkfish theme and JsonIndex generator. - # Use #search_snippet instead for getting documentation snippets. - - def search_record - [ - name, - full_name, - full_name, - '', - path, - '', - snippet(@comment_location), - ] - end + ## + # Name to use to generate the url: + # modules and classes that are aliases for another + # module or class return the name of the latter. - ## - # Returns an HTML snippet of the first comment for search results. + def name_for_path + is_alias_for ? is_alias_for.name_for_path : full_name + end - def search_snippet - first_comment = @comment_location.each_value.first&.first - return '' unless first_comment && !first_comment.empty? + ## + # Returns the classes and modules that are not constants + # aliasing another class or module. For use by formatters + # only (caches its result). - snippet(first_comment) - end + def non_aliases + @non_aliases ||= classes_and_modules.reject { |cm| cm.is_alias_for } + end - ## - # Rebuilds +@comment+ from the current +@comment_location+ entries, - # skipping any empty placeholders. - - def rebuild_comment_from_location - texts = @comment_location.each_value.flat_map { |comments| - comments.filter_map { |c| c.to_s unless c.empty? } - } - merged = texts.join("\n---\n") - @comment = merged.empty? ? '' : RDoc::Comment.new(merged) - end + ## + # Updates the child modules or classes of class/module +parent+ by + # deleting the ones that have been removed from the documentation. + # + # +parent_hash+ is either parent.modules_hash or + # parent.classes_hash and +all_hash+ is ::all_modules_hash or + # ::all_classes_hash. - ## - # Sets the store for this class or module and its contained code objects. + def remove_nodoc_children + prefix = self.full_name + '::' - def store=(store) - super + modules_hash.each_key do |name| + full_name = prefix + name + modules_hash.delete name unless @store.modules_hash[full_name] + end - @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 + classes_hash.each_key do |name| + full_name = prefix + name + classes_hash.delete name unless @store.classes_hash[full_name] + end + end - ## - # Get the superclass of this class. Attempts to retrieve the superclass - # object, returns the name if it is not known. + def remove_things(my_things, other_files) # :nodoc: + my_things.delete_if do |file, things| + next false unless other_files.include? file - def superclass - @store.find_class_named(@superclass) || @superclass - end + things.each do |thing| + yield false, thing + end - ## - # Set the superclass of this class to +superclass+ - # - # where +superclass+ is one of: - # - # - +nil+ - # - a String containing the full name of the superclass - # - the RDoc::ClassModule representing the superclass - - def superclass=(superclass) - raise NoMethodError, "#{full_name} is a module" if module? - case superclass - when RDoc::ClassModule - @superclass = superclass.full_name - when nil, String - @superclass = superclass - else - raise TypeError, "superclass must be a String or RDoc::ClassModule, not #{superclass.class}" + true + end end - end - ## - # Get all super classes of this class in an array. The last element might be - # a string if the name is unknown. - - def super_classes - result = [] - # Degenerate input can produce a cyclic superclass chain - visited = [full_name] - parent = self - while parent = parent.superclass - if parent.is_a?(String) - result << parent - break - end - break if visited.include?(parent.full_name) - visited << parent.full_name - result << parent + ## + # Search record used by RDoc::Generator::JsonIndex + # + # TODO: Remove this method after dropping the darkfish theme and JsonIndex generator. + # Use #search_snippet instead for getting documentation snippets. + + def search_record + [ + name, + full_name, + full_name, + '', + path, + '', + snippet(@comment_location), + ] end - result - end - def to_s # :nodoc: - if is_alias_for - "#{self.class.name} #{self.full_name} -> #{is_alias_for}" - else - super + ## + # Returns an HTML snippet of the first comment for search results. + + def search_snippet + first_comment = @comment_location.each_value.first&.first + return '' unless first_comment && !first_comment.empty? + + snippet(first_comment) end - end - ## - # 'module' or 'class' + ## + # Rebuilds +@comment+ from the current +@comment_location+ entries, + # skipping any empty placeholders. - def type - module? ? 'module' : 'class' - end + def rebuild_comment_from_location + texts = @comment_location.each_value.flat_map { |comments| + comments.filter_map { |c| c.to_s unless c.empty? } + } + merged = texts.join("\n---\n") + @comment = merged.empty? ? '' : ::RDoc::Comment.new(merged) + end - ## - # Updates the child modules & classes by replacing the ones that are - # aliases through a constant. - # - # The aliased module/class is replaced in the children and in - # 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. - # - # Formatters can use the #non_aliases method to retrieve children that - # are not aliases, for instance to list the namespace content, since - # the aliased modules are included in the constants of the class/module, - # that are listed separately. - - def update_aliases - constants.each do |const| - cm = const.is_alias_for - cm ||= const.resolved_alias_target if const.is_a?(RDoc::Constant) - next unless cm - - # Resolve chained aliases (A = B = C) to the real class/module. - cm = @store.find_class_or_module(cm.full_name) || cm - while (target = cm.is_alias_for) - cm = target - end - - cm_alias = cm.dup - cm_alias.name = const.name - - if full_name == 'Object' - # Don't move top-level aliases under Object, they look ugly there - cm_alias.parent = top_level + ## + # 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 + @store.find_class_named(@superclass) || @superclass + end + + ## + # Set the superclass of this class to +superclass+ + # + # where +superclass+ is one of: + # + # - +nil+ + # - a String containing the full name of the superclass + # - the RDoc::ClassModule representing the superclass + + def superclass=(superclass) + raise NoMethodError, "#{full_name} is a module" if module? + case superclass + when ::RDoc::ClassModule + @superclass = superclass.full_name + when nil, String + @superclass = superclass else - cm_alias.parent = self + raise TypeError, "superclass must be a String or RDoc::ClassModule, not #{superclass.class}" end - cm_alias.full_name = nil # force update for new parent - - # Don't clobber a real (non-alias) class/module already living at this - # name. Mirrors the BasicObject = BlankSlate guard in - # Context#add_module_alias. Existing alias copies (set by - # add_module_alias or a previous update_aliases pass) carry is_alias_for, - # so they're still overwritable here. - existing = @store.find_class_or_module(cm_alias.full_name) - next if existing && !existing.is_alias_for + end - # Persist a lazy-resolved target so Stats#report_constants and - # Constant#marshal_dump observe the alias relationship. Skipped - # aliases (above) intentionally leave the constant unmarked. - const.is_alias_for ||= cm + ## + # Get all super classes of this class in an array. The last element might be + # a string if the name is unknown. - cm_alias.aliases.clear - cm_alias.is_alias_for = cm + def super_classes + result = [] + # Degenerate input can produce a cyclic superclass chain + visited = [full_name] + parent = self + while parent = parent.superclass + if parent.is_a?(String) + result << parent + break + end + break if visited.include?(parent.full_name) + visited << parent.full_name + result << parent + end + result + end - if cm.module? - @store.modules_hash[cm_alias.full_name] = cm_alias - modules_hash[const.name] = cm_alias + def to_s # :nodoc: + if is_alias_for + "#{self.class.name} #{self.full_name} -> #{is_alias_for}" else - @store.classes_hash[cm_alias.full_name] = cm_alias - classes_hash[const.name] = cm_alias + super end - - cm.aliases << cm_alias end - end - ## - # Deletes from #includes those whose module has been removed from the - # documentation. - #-- - # FIXME: includes are not reliably removed, see _possible_bug test case + ## + # 'module' or 'class' + + def type + module? ? 'module' : 'class' + end + + ## + # Updates the child modules & classes by replacing the ones that are + # aliases through a constant. + # + # The aliased module/class is replaced in the children and in + # 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. + # + # Formatters can use the #non_aliases method to retrieve children that + # are not aliases, for instance to list the namespace content, since + # the aliased modules are included in the constants of the class/module, + # that are listed separately. + + def update_aliases + constants.each do |const| + cm = const.is_alias_for + cm ||= const.resolved_alias_target if const.is_a?(::RDoc::Constant) + next unless cm + + # Resolve chained aliases (A = B = C) to the real class/module. + cm = @store.find_class_or_module(cm.full_name) || cm + while (target = cm.is_alias_for) + cm = target + end - def update_includes - includes.reject! do |include| - mod = include.module - !(String === mod) && @store.modules_hash[mod.full_name].nil? - end + cm_alias = cm.dup + cm_alias.name = const.name - includes.uniq! - end + if full_name == 'Object' + # Don't move top-level aliases under Object, they look ugly there + cm_alias.parent = top_level + else + cm_alias.parent = self + end + cm_alias.full_name = nil # force update for new parent + + # Don't clobber a real (non-alias) class/module already living at this + # name. Mirrors the BasicObject = BlankSlate guard in + # Context#add_module_alias. Existing alias copies (set by + # add_module_alias or a previous update_aliases pass) carry is_alias_for, + # so they're still overwritable here. + existing = @store.find_class_or_module(cm_alias.full_name) + next if existing && !existing.is_alias_for + + # Persist a lazy-resolved target so Stats#report_constants and + # Constant#marshal_dump observe the alias relationship. Skipped + # aliases (above) intentionally leave the constant unmarked. + const.is_alias_for ||= cm + + cm_alias.aliases.clear + cm_alias.is_alias_for = cm + + if cm.module? + @store.modules_hash[cm_alias.full_name] = cm_alias + modules_hash[const.name] = cm_alias + else + @store.classes_hash[cm_alias.full_name] = cm_alias + classes_hash[const.name] = cm_alias + end - ## - # Deletes from #extends those whose module has been removed from the - # documentation. - #-- - # FIXME: like update_includes, extends are not reliably removed + cm.aliases << cm_alias + end + end + + ## + # Deletes from #includes those whose module has been removed from the + # documentation. + #-- + # FIXME: includes are not reliably removed, see _possible_bug test case - def update_extends - extends.reject! do |ext| - mod = ext.module + def update_includes + includes.reject! do |include| + mod = include.module + !(String === mod) && @store.modules_hash[mod.full_name].nil? + end - !(String === mod) && @store.modules_hash[mod.full_name].nil? + includes.uniq! end - extends.uniq! - end + ## + # Deletes from #extends those whose module has been removed from the + # documentation. + #-- + # FIXME: like update_includes, extends are not reliably removed - def embed_mixins - return unless options.embed_mixins + def update_extends + extends.reject! do |ext| + mod = ext.module - includes.each do |include| - next if String === include.module - include.module.method_list.each do |code_object| - add_method(prepare_to_embed(code_object)) - end - include.module.constants.each do |code_object| - add_constant(prepare_to_embed(code_object)) - end - include.module.attributes.each do |code_object| - add_attribute(prepare_to_embed(code_object)) + !(String === mod) && @store.modules_hash[mod.full_name].nil? end + + extends.uniq! end - extends.each do |ext| - next if String === ext.module - ext.module.method_list.each do |code_object| - add_method(prepare_to_embed(code_object, true)) + def embed_mixins + return unless options.embed_mixins + + includes.each do |include| + next if String === include.module + include.module.method_list.each do |code_object| + add_method(prepare_to_embed(code_object)) + end + include.module.constants.each do |code_object| + add_constant(prepare_to_embed(code_object)) + end + include.module.attributes.each do |code_object| + add_attribute(prepare_to_embed(code_object)) + end end - ext.module.attributes.each do |code_object| - add_attribute(prepare_to_embed(code_object, true)) + + extends.each do |ext| + next if String === ext.module + ext.module.method_list.each do |code_object| + add_method(prepare_to_embed(code_object, true)) + end + ext.module.attributes.each do |code_object| + add_attribute(prepare_to_embed(code_object, true)) + end end end - end private - def prepare_to_embed(code_object, singleton=false) - code_object = code_object.dup - code_object.mixin_from = code_object.parent - code_object.singleton = true if singleton - set_current_section(code_object.section.title, code_object.section.comment) - code_object + def prepare_to_embed(code_object, singleton=false) + code_object = code_object.dup + code_object.mixin_from = code_object.parent + code_object.singleton = true if singleton + set_current_section(code_object.section.title, code_object.section.comment) + code_object + end end end diff --git a/lib/rdoc/code_object/constant.rb b/lib/rdoc/code_object/constant.rb index 7fe34f6d0d..0c418eefe7 100644 --- a/lib/rdoc/code_object/constant.rb +++ b/lib/rdoc/code_object/constant.rb @@ -1,221 +1,223 @@ # frozen_string_literal: true -## -# A constant +module RDoc + ## + # A constant -class RDoc::Constant < RDoc::CodeObject + class Constant < ::RDoc::CodeObject - MARSHAL_VERSION = 0 # :nodoc: + MARSHAL_VERSION = 0 # :nodoc: - ## - # Sets the module or class this is constant is an alias for. + ## + # Sets the module or class this is constant is an alias for. - attr_writer :is_alias_for + attr_writer :is_alias_for - ## - # The constant's name + ## + # The constant's name - attr_accessor :name + attr_accessor :name - ## - # The constant's value + ## + # The constant's value - attr_accessor :value + attr_accessor :value - ## - # The constant's visibility + ## + # The constant's visibility - attr_accessor :visibility + attr_accessor :visibility - ## - # The constant path on the RHS when the RHS is a bare constant reference - # (+Foo = Bar+ or +Foo = Bar::Baz+). Captured at parse time so - # #resolved_alias_target doesn't have to re-derive it from the textual - # #value. nil for other RHS shapes. + ## + # The constant path on the RHS when the RHS is a bare constant reference + # (+Foo = Bar+ or +Foo = Bar::Baz+). Captured at parse time so + # #resolved_alias_target doesn't have to re-derive it from the textual + # #value. nil for other RHS shapes. - attr_accessor :is_alias_for_path + attr_accessor :is_alias_for_path - ## - # Creates a new constant with +name+, +value+ and +comment+ + ## + # Creates a new constant with +name+, +value+ and +comment+ - def initialize(name, value, comment) - super() + def initialize(name, value, comment) + super() - @name = name - @value = value + @name = name + @value = value - @is_alias_for = nil - @is_alias_for_path = nil - @visibility = :public + @is_alias_for = nil + @is_alias_for_path = nil + @visibility = :public - self.comment = comment - end + self.comment = comment + end - ## - # Constants are ordered by name + ## + # Constants are ordered by name - def <=>(other) - return unless self.class === other + def <=>(other) + return unless self.class === other - [parent_name, name] <=> [other.parent_name, other.name] - end + [parent_name, name] <=> [other.parent_name, other.name] + end - ## - # Constants are equal when their #parent and #name is the same + ## + # Constants are equal when their #parent and #name is the same - def ==(other) - self.class == other.class and - @parent == other.parent and - @name == other.name - end + def ==(other) + self.class == other.class and + @parent == other.parent and + @name == other.name + end - ## - # A constant is documented if it has a comment, or is an alias - # for a documented class or module. - - def documented? - return true if super - return false unless @is_alias_for - case @is_alias_for - when String - found = @store.find_class_or_module @is_alias_for - return false unless found - @is_alias_for = found + ## + # A constant is documented if it has a comment, or is an alias + # for a documented class or module. + + def documented? + return true if super + return false unless @is_alias_for + case @is_alias_for + when String + found = @store.find_class_or_module @is_alias_for + return false unless found + @is_alias_for = found + end + @is_alias_for.documented? end - @is_alias_for.documented? - end - ## - # Full constant name including namespace + ## + # Full constant name including namespace - def full_name - @full_name ||= "#{parent_name}::#{@name}" - end + def full_name + @full_name ||= "#{parent_name}::#{@name}" + end - ## - # The module or class this constant is an alias for, when one was recorded - # explicitly (by RDoc::Context#add_module_alias, RDoc::ClassModule#update_aliases, - # or ri marshal load). Pure accessor; see #resolved_alias_target for the - # opportunistic lookup path. - - def is_alias_for - case @is_alias_for - when String - found = @store.find_class_or_module @is_alias_for - @is_alias_for = found if found - @is_alias_for - else - @is_alias_for + ## + # The module or class this constant is an alias for, when one was recorded + # explicitly (by RDoc::Context#add_module_alias, RDoc::ClassModule#update_aliases, + # or ri marshal load). Pure accessor; see #resolved_alias_target for the + # opportunistic lookup path. + + def is_alias_for + case @is_alias_for + when String + found = @store.find_class_or_module @is_alias_for + @is_alias_for = found if found + @is_alias_for + else + @is_alias_for + end end - end - ## - # Returns the class/module this constant *would* alias if #is_alias_for_path - # was set by the parser and that path resolves to a known class/module, or - # nil. Used to support `Const = RHS` parsed before `class RHS;end` is defined - # in another file. Pure lookup; does not mutate state. Honors :nodoc: - # (returns nil if document_self is false). Note that module nesting - # information is lost, so constant lookup is inaccurate. - - def resolved_alias_target - return nil unless document_self - return nil unless @is_alias_for_path - parent.find_module_named(@is_alias_for_path) - end + ## + # Returns the class/module this constant *would* alias if #is_alias_for_path + # was set by the parser and that path resolves to a known class/module, or + # nil. Used to support `Const = RHS` parsed before `class RHS;end` is defined + # in another file. Pure lookup; does not mutate state. Honors :nodoc: + # (returns nil if document_self is false). Note that module nesting + # information is lost, so constant lookup is inaccurate. + + def resolved_alias_target + return nil unless document_self + return nil unless @is_alias_for_path + parent.find_module_named(@is_alias_for_path) + end - def inspect # :nodoc: - "#<%s:0x%x %s::%s>" % [ - self.class, object_id, - parent_name, @name, - ] - end + def inspect # :nodoc: + "#<%s:0x%x %s::%s>" % [ + self.class, object_id, + parent_name, @name, + ] + end - ## - # 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.relative_name, - parent.name, - parent.class, - section.title, - ] - end + ## + # 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.relative_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, RDoc::Comment.from_document(array[5]) - - @full_name = array[2] - @visibility = array[3] || :public - @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 + ## + # 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, ::RDoc::Comment.from_document(array[5]) + + @full_name = array[2] + @visibility = array[3] || :public + @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. + ## + # Path to this constant for use with HTML generator output. - def path - "#{@parent.path}##{@name}" - end + def path + "#{@parent.path}##{@name}" + end - ## - # Returns an HTML snippet of the comment for search results. + ## + # Returns an HTML snippet of the comment for search results. - def search_snippet - return '' if comment.empty? + def search_snippet + return '' if comment.empty? - snippet(comment) - end + snippet(comment) + end - def pretty_print(q) # :nodoc: - q.group 2, "[#{self.class.name} #{full_name}", "]" do - unless comment.empty? - q.breakable - q.text "comment:" - q.breakable - q.pp @comment + def pretty_print(q) # :nodoc: + q.group 2, "[#{self.class.name} #{full_name}", "]" do + unless comment.empty? + q.breakable + q.text "comment:" + q.breakable + q.pp @comment + end end end - end - ## - # Sets the store for this class or module and its contained code objects. + ## + # Sets the store for this class or module and its contained code objects. - def store=(store) - super + def store=(store) + super - @file = @store.add_file @file.full_name if @file - end + @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 - "constant #{parent_name}::#@name -> #{is_alias_for}" - else - "constant #{parent_name}::#@name" + def to_s # :nodoc: + parent_name = parent ? parent.full_name : '(unknown)' + if is_alias_for + "constant #{parent_name}::#@name -> #{is_alias_for}" + else + "constant #{parent_name}::#@name" + end end - end + end end diff --git a/lib/rdoc/code_object/context.rb b/lib/rdoc/code_object/context.rb index ccc9203141..592c47af32 100644 --- a/lib/rdoc/code_object/context.rb +++ b/lib/rdoc/code_object/context.rb @@ -1,1206 +1,1206 @@ # frozen_string_literal: true -## -# A Context is something that can hold modules, classes, methods, attributes, -# aliases, requires, and includes. Classes, modules, and files are all -# Contexts. - -class RDoc::Context < RDoc::CodeObject - - include Comparable - +module RDoc ## - # Types of methods + # A Context is something that can hold modules, classes, methods, attributes, + # aliases, requires, and includes. Classes, modules, and files are all + # Contexts. - TYPES = %w[class instance] + class Context < ::RDoc::CodeObject - ## - # If a context has these titles it will be sorted in this order. + include Comparable - TOMDOC_TITLES = [nil, 'Public', 'Internal', 'Deprecated'] # :nodoc: - TOMDOC_TITLES_SORT = TOMDOC_TITLES.sort_by { |title| title.to_s } # :nodoc: + ## + # Types of methods - ## - # Class/module aliases + TYPES = %w[class instance] - attr_reader :aliases + ## + # If a context has these titles it will be sorted in this order. - ## - # All attr* methods + TOMDOC_TITLES = [nil, 'Public', 'Internal', 'Deprecated'] # :nodoc: + TOMDOC_TITLES_SORT = TOMDOC_TITLES.sort_by { |title| title.to_s } # :nodoc: - attr_reader :attributes + ## + # Class/module aliases - ## - # Block params to be used in the next MethodAttr parsed under this context + attr_reader :aliases - attr_accessor :block_params + ## + # All attr* methods - ## - # Constants defined + attr_reader :attributes - attr_reader :constants + ## + # Block params to be used in the next MethodAttr parsed under this context - ## - # Sets the current documentation section of documentation + attr_accessor :block_params - attr_writer :current_section + ## + # Constants defined - ## - # Files this context is found in + attr_reader :constants - attr_reader :in_files + ## + # Sets the current documentation section of documentation - ## - # Modules this context includes + attr_writer :current_section - attr_reader :includes + ## + # Files this context is found in - ## - # Modules this context is extended with + attr_reader :in_files - attr_reader :extends + ## + # Modules this context includes - ## - # Methods defined in this context + attr_reader :includes - attr_reader :method_list + ## + # Modules this context is extended with - ## - # Name of this class excluding namespace. See also full_name + attr_reader :extends - attr_reader :name + ## + # Methods defined in this context - ## - # Files this context requires + attr_reader :method_list - attr_reader :requires + ## + # Name of this class excluding namespace. See also full_name - ## - # Use this section for the next method, attribute or constant added. + attr_reader :name - attr_accessor :temporary_section + ## + # Files this context requires - ## - # Hash old_name => [aliases], for aliases - # that haven't (yet) been resolved to a method/attribute. - # (Not to be confused with the aliases of the context.) + attr_reader :requires - attr_accessor :unmatched_alias_lists + ## + # Use this section for the next method, attribute or constant added. - ## - # Aliases that could not be resolved. + attr_accessor :temporary_section - attr_reader :external_aliases + ## + # Hash old_name => [aliases], for aliases + # that haven't (yet) been resolved to a method/attribute. + # (Not to be confused with the aliases of the context.) - ## - # Hash of registered methods. Attributes are also registered here, - # twice if they are RW. + attr_accessor :unmatched_alias_lists - attr_reader :methods_hash + ## + # Aliases that could not be resolved. - ## - # Params to be used in the next MethodAttr parsed under this context + attr_reader :external_aliases - attr_accessor :params + ## + # Hash of registered methods. Attributes are also registered here, + # twice if they are RW. - ## - # Hash of registered constants. + attr_reader :methods_hash - attr_reader :constants_hash + ## + # Params to be used in the next MethodAttr parsed under this context - ## - # Creates an unnamed empty context + attr_accessor :params - def initialize - super + ## + # Hash of registered constants. - @in_files = [] + attr_reader :constants_hash - @name ||= "unknown" - @parent = nil + ## + # Creates an unnamed empty context - @current_section = Section.new self, nil, nil - @sections = { nil => @current_section } - @temporary_section = nil + def initialize + super - @classes = {} - @modules = {} + @in_files = [] - initialize_methods_etc - end + @name ||= "unknown" + @parent = nil + @current_section = Section.new self, nil, nil + @sections = { nil => @current_section } + @temporary_section = nil - ## - # Sets the defaults for methods and so-forth + @classes = {} + @modules = {} - def initialize_methods_etc - @method_list = [] - @attributes = [] - @aliases = [] - @requires = [] - @includes = [] - @extends = [] - @constants = [] - @external_aliases = [] + initialize_methods_etc + end - # This Hash maps a method name to a list of unmatched aliases (aliases of - # a method not yet encountered). - @unmatched_alias_lists = {} + ## + # Sets the defaults for methods and so-forth - @methods_hash = {} - @constants_hash = {} + def initialize_methods_etc + @method_list = [] + @attributes = [] + @aliases = [] + @requires = [] + @includes = [] + @extends = [] + @constants = [] + @external_aliases = [] + # This Hash maps a method name to a list of unmatched aliases (aliases of + # a method not yet encountered). + @unmatched_alias_lists = {} - @params = nil + @methods_hash = {} + @constants_hash = {} - @store ||= nil - end + @params = nil - ## - # Contexts are sorted by full_name + @store ||= nil + end - def <=>(other) - return nil unless RDoc::CodeObject === other + ## + # Contexts are sorted by full_name - full_name <=> other.full_name - end + def <=>(other) + return nil unless ::RDoc::CodeObject === other - ## - # Adds an item of type +klass+ with the given +name+ and +comment+ to the - # context. - # - # Currently only RDoc::Extend and RDoc::Include are supported. - - def add(klass, name, comment) - if RDoc::Extend == klass - ext = RDoc::Extend.new name, comment - add_extend ext - elsif RDoc::Include == klass - incl = RDoc::Include.new name, comment - add_include incl - else - raise NotImplementedError, "adding a #{klass} is not implemented" + full_name <=> other.full_name end - end - ## - # Adds +an_alias+ that is automatically resolved + ## + # Adds an item of type +klass+ with the given +name+ and +comment+ to the + # context. + # + # Currently only RDoc::Extend and RDoc::Include are supported. + + def add(klass, name, comment) + if ::RDoc::Extend == klass + ext = ::RDoc::Extend.new name, comment + add_extend ext + elsif ::RDoc::Include == klass + incl = ::RDoc::Include.new name, comment + add_include incl + else + raise NotImplementedError, "adding a #{klass} is not implemented" + end + end - def add_alias(an_alias) - return an_alias unless @document_self + ## + # Adds +an_alias+ that is automatically resolved - method_attr = find_method(an_alias.old_name, an_alias.singleton) || - find_attribute(an_alias.old_name, an_alias.singleton) + def add_alias(an_alias) + return an_alias unless @document_self - if method_attr - method_attr.add_alias an_alias, self - else - add_to @external_aliases, an_alias - unmatched_alias_list = - @unmatched_alias_lists[an_alias.pretty_old_name] ||= [] - unmatched_alias_list.push an_alias - end + method_attr = find_method(an_alias.old_name, an_alias.singleton) || + find_attribute(an_alias.old_name, an_alias.singleton) - an_alias - end + if method_attr + method_attr.add_alias an_alias, self + else + add_to @external_aliases, an_alias + unmatched_alias_list = + @unmatched_alias_lists[an_alias.pretty_old_name] ||= [] + unmatched_alias_list.push an_alias + end - ## - # Adds +attribute+ if not already there. If it is (as method(s) or attribute), - # updates the comment if it was empty. - # - # The attribute is registered only if it defines a new method. - # For instance, attr_reader :foo will not be registered - # if method +foo+ exists, but attr_accessor :foo will be registered - # if method +foo+ exists, but foo= does not. + an_alias + end - def add_attribute(attribute) - return attribute unless @document_self + ## + # Adds +attribute+ if not already there. If it is (as method(s) or attribute), + # updates the comment if it was empty. + # + # The attribute is registered only if it defines a new method. + # For instance, attr_reader :foo will not be registered + # if method +foo+ exists, but attr_accessor :foo will be registered + # if method +foo+ exists, but foo= does not. - # mainly to check for redefinition of an attribute as a method - # TODO find a policy for 'attr_reader :foo' + 'def foo=()' - register = false + def add_attribute(attribute) + return attribute unless @document_self - key = nil + # mainly to check for redefinition of an attribute as a method + # TODO find a policy for 'attr_reader :foo' + 'def foo=()' + register = false - if attribute.rw.index 'R' - key = attribute.pretty_name - known = @methods_hash[key] + key = nil - if known - known.comment = attribute.comment if known.comment.empty? - elsif registered = @methods_hash[attribute.pretty_name + '='] and - RDoc::Attr === registered - registered.rw = 'RW' - else - @methods_hash[key] = attribute - register = true + if attribute.rw.index 'R' + key = attribute.pretty_name + known = @methods_hash[key] + + if known + known.comment = attribute.comment if known.comment.empty? + elsif registered = @methods_hash[attribute.pretty_name + '='] and + ::RDoc::Attr === registered + registered.rw = 'RW' + else + @methods_hash[key] = attribute + register = true + end end - end - if attribute.rw.index 'W' - key = attribute.pretty_name + '=' - known = @methods_hash[key] + if attribute.rw.index 'W' + key = attribute.pretty_name + '=' + known = @methods_hash[key] - if known - known.comment = attribute.comment if known.comment.empty? - elsif registered = @methods_hash[attribute.pretty_name] and - RDoc::Attr === registered - registered.rw = 'RW' - else - @methods_hash[key] = attribute - register = true + if known + known.comment = attribute.comment if known.comment.empty? + elsif registered = @methods_hash[attribute.pretty_name] and + ::RDoc::Attr === registered + registered.rw = 'RW' + else + @methods_hash[key] = attribute + register = true + end end - end - if register - add_to @attributes, attribute - resolve_aliases attribute - end + if register + add_to @attributes, attribute + resolve_aliases attribute + end - attribute - end + attribute + end - ## - # Adds a class named +given_name+ with +superclass+. - # - # Both +given_name+ and +superclass+ may contain '::', and are - # interpreted relative to the +self+ context. This allows handling correctly - # examples like these: - # class RDoc::Gauntlet < Gauntlet - # module Mod - # class Object # implies < ::Object - # class SubObject < Object # this is _not_ ::Object - # - # Given class Container::Item RDoc assumes +Container+ is a module - # unless it later sees class Container. +add_class+ automatically - # upgrades +given_name+ to a class in this case. - - def add_class(class_type, given_name, superclass = '::Object') - # superclass +nil+ is passed by the C parser in the following cases: - # - registering Object in 1.8 (correct) - # - registering BasicObject in 1.9 (correct) - # - registering RubyVM in 1.9 in iseq.c (incorrect: < Object in vm.c) + ## + # Adds a class named +given_name+ with +superclass+. # - # If we later find a superclass for a registered class with a nil - # superclass, we must honor it. - - # find the name & enclosing context - if given_name =~ /^:+(\w+)$/ - full_name = $1 - enclosing = top_level - name = full_name.split(/:+/).last - else - full_name = child_name given_name - - if full_name =~ /^(.+)::(\w+)$/ - name = $2 - ename = $1 - enclosing = @store.classes_hash[ename] || @store.modules_hash[ename] - # HACK: crashes in actionpack/lib/action_view/helpers/form_helper.rb (metaprogramming) - unless enclosing - # try the given name at top level (will work for the above example) - enclosing = @store.classes_hash[given_name] || - @store.modules_hash[given_name] - return enclosing if enclosing - # not found: create the parent(s) - enclosing = find_or_create_namespace_path ename - end + # Both +given_name+ and +superclass+ may contain '::', and are + # interpreted relative to the +self+ context. This allows handling correctly + # examples like these: + # class RDoc::Gauntlet < Gauntlet + # module Mod + # class Object # implies < ::Object + # class SubObject < Object # this is _not_ ::Object + # + # Given class Container::Item RDoc assumes +Container+ is a module + # unless it later sees class Container. +add_class+ automatically + # upgrades +given_name+ to a class in this case. + + def add_class(class_type, given_name, superclass = '::Object') + # superclass +nil+ is passed by the C parser in the following cases: + # - registering Object in 1.8 (correct) + # - registering BasicObject in 1.9 (correct) + # - registering RubyVM in 1.9 in iseq.c (incorrect: < Object in vm.c) + # + # If we later find a superclass for a registered class with a nil + # superclass, we must honor it. + + # find the name & enclosing context + if given_name =~ /^:+(\w+)$/ + full_name = $1 + enclosing = top_level + name = full_name.split(/:+/).last else - name = full_name - enclosing = self + full_name = child_name given_name + + if full_name =~ /^(.+)::(\w+)$/ + name = $2 + ename = $1 + enclosing = @store.classes_hash[ename] || @store.modules_hash[ename] + # HACK: crashes in actionpack/lib/action_view/helpers/form_helper.rb (metaprogramming) + unless enclosing + # try the given name at top level (will work for the above example) + enclosing = @store.classes_hash[given_name] || + @store.modules_hash[given_name] + return enclosing if enclosing + # not found: create the parent(s) + enclosing = find_or_create_namespace_path ename + end + else + name = full_name + enclosing = self + end end - end - # fix up superclass - if full_name == 'BasicObject' - superclass = nil - elsif full_name == 'Object' - superclass = '::BasicObject' - end + # fix up superclass + if full_name == 'BasicObject' + superclass = nil + elsif full_name == 'Object' + superclass = '::BasicObject' + end - # find the superclass full name - if superclass - if superclass =~ /^:+/ - superclass = $' #' - else - if superclass =~ /^(\w+):+(.+)$/ - suffix = $2 - mod = find_module_named($1) - superclass = mod.full_name + '::' + suffix if mod + # find the superclass full name + if superclass + if superclass =~ /^:+/ + superclass = $' #' else - mod = find_module_named(superclass) - superclass = mod.full_name if mod + if superclass =~ /^(\w+):+(.+)$/ + suffix = $2 + mod = find_module_named($1) + superclass = mod.full_name + '::' + suffix if mod + else + mod = find_module_named(superclass) + superclass = mod.full_name if mod + end end + + # did we believe it was a module? + mod = @store.modules_hash.delete superclass + + upgrade_to_class mod, ::RDoc::NormalClass, mod.parent if mod + + # e.g., Object < Object + superclass = nil if superclass == full_name end - # did we believe it was a module? - mod = @store.modules_hash.delete superclass + klass = @store.classes_hash[full_name] - upgrade_to_class mod, RDoc::NormalClass, mod.parent if mod + if klass + # if TopLevel, it may not be registered in the classes: + enclosing.classes_hash[name] = klass - # e.g., Object < Object - superclass = nil if superclass == full_name - end + # update the superclass if needed + if superclass + existing = klass.superclass + existing = existing.full_name unless existing.is_a?(String) if existing + if existing.nil? || + (existing == 'Object' && superclass != 'Object') + klass.superclass = superclass + end + end + else + # this is a new class + mod = @store.modules_hash.delete full_name - klass = @store.classes_hash[full_name] + if mod + klass = upgrade_to_class mod, ::RDoc::NormalClass, enclosing - if klass - # if TopLevel, it may not be registered in the classes: - enclosing.classes_hash[name] = klass + klass.superclass = superclass unless superclass.nil? + else + klass = class_type.new name, superclass - # update the superclass if needed - if superclass - existing = klass.superclass - existing = existing.full_name unless existing.is_a?(String) if existing - if existing.nil? || - (existing == 'Object' && superclass != 'Object') - klass.superclass = superclass + enclosing.add_class_or_module(klass, enclosing.classes_hash, + @store.classes_hash) end end - else - # this is a new class - mod = @store.modules_hash.delete full_name - if mod - klass = upgrade_to_class mod, RDoc::NormalClass, enclosing + klass.parent = self - klass.superclass = superclass unless superclass.nil? - else - klass = class_type.new name, superclass + klass + end - enclosing.add_class_or_module(klass, enclosing.classes_hash, - @store.classes_hash) + ## + # Adds the class or module +mod+ to the modules or + # classes Hash +self_hash+, and to +all_hash+ (either + # TopLevel::modules_hash or TopLevel::classes_hash), + # unless #done_documenting is +true+. Sets the #parent of +mod+ + # to +self+, and its #section to #current_section. Returns +mod+. + + def add_class_or_module(mod, self_hash, all_hash) + mod.section = current_section # TODO declaring context? something is + # wrong here... + mod.parent = self + mod.full_name = nil + mod.store = @store + + unless @done_documenting + self_hash[mod.name] = mod + # this must be done AFTER adding mod to its parent, so that the full + # name is correct: + all_hash[mod.full_name] = mod end + + mod end - klass.parent = self + ## + # Adds +constant+ if not already there. If it is, updates the comment, + # value and/or is_alias_for of the known constant if they were empty/nil. - klass - end + def add_constant(constant) + return constant unless @document_self - ## - # Adds the class or module +mod+ to the modules or - # classes Hash +self_hash+, and to +all_hash+ (either - # TopLevel::modules_hash or TopLevel::classes_hash), - # unless #done_documenting is +true+. Sets the #parent of +mod+ - # to +self+, and its #section to #current_section. Returns +mod+. - - def add_class_or_module(mod, self_hash, all_hash) - mod.section = current_section # TODO declaring context? something is - # wrong here... - mod.parent = self - mod.full_name = nil - mod.store = @store - - unless @done_documenting - self_hash[mod.name] = mod - # this must be done AFTER adding mod to its parent, so that the full - # name is correct: - all_hash[mod.full_name] = mod - end - - mod - end + # HACK: avoid duplicate 'PI' & 'E' in math.c (1.8.7 source code) + # (this is a #ifdef: should be handled by the C parser) + known = @constants_hash[constant.name] - ## - # Adds +constant+ if not already there. If it is, updates the comment, - # value and/or is_alias_for of the known constant if they were empty/nil. + if known + known.comment = constant.comment if known.comment.empty? - def add_constant(constant) - return constant unless @document_self + known.value = constant.value if + known.value.nil? or known.value.strip.empty? - # HACK: avoid duplicate 'PI' & 'E' in math.c (1.8.7 source code) - # (this is a #ifdef: should be handled by the C parser) - known = @constants_hash[constant.name] + constant.parent = self + known.is_alias_for ||= constant.is_alias_for + else + @constants_hash[constant.name] = constant + add_to @constants, constant + end - if known - known.comment = constant.comment if known.comment.empty? + constant + end - known.value = constant.value if - known.value.nil? or known.value.strip.empty? + ## + # Adds included module +include+ which should be an RDoc::Include - constant.parent = self - known.is_alias_for ||= constant.is_alias_for - else - @constants_hash[constant.name] = constant - add_to @constants, constant - end + def add_include(include) + add_to @includes, include - constant - end + include + end - ## - # Adds included module +include+ which should be an RDoc::Include + ## + # Adds extension module +ext+ which should be an RDoc::Extend - def add_include(include) - add_to @includes, include + def add_extend(ext) + add_to @extends, ext - include - end + ext + end - ## - # Adds extension module +ext+ which should be an RDoc::Extend + ## + # Adds +method+ if not already there. If it is (as method or attribute), + # updates the comment if it was empty. - def add_extend(ext) - add_to @extends, ext + def add_method(method) + return method unless @document_self - ext - end + # HACK: avoid duplicate 'new' in io.c & struct.c (1.8.7 source code) + key = method.pretty_name + known = @methods_hash[key] - ## - # Adds +method+ if not already there. If it is (as method or attribute), - # updates the comment if it was empty. - - def add_method(method) - return method unless @document_self - - # HACK: avoid duplicate 'new' in io.c & struct.c (1.8.7 source code) - key = method.pretty_name - known = @methods_hash[key] - - if known - if @store # otherwise we are loading - known.comment = method.comment if known.comment.empty? - previously = ", previously in #{known.file}" unless - method.file == known.file - @store.options.warn \ - "Duplicate method #{known.full_name} in #{method.file}#{previously}" + if known + if @store # otherwise we are loading + known.comment = method.comment if known.comment.empty? + previously = ", previously in #{known.file}" unless + method.file == known.file + @store.options.warn \ + "Duplicate method #{known.full_name} in #{method.file}#{previously}" + end + else + @methods_hash[key] = method + add_to @method_list, method + resolve_aliases method end - else - @methods_hash[key] = method - add_to @method_list, method - resolve_aliases method - end - method - end + method + end - ## - # Returns the owner context and local name for +constant_path+, creating - # missing namespace modules. A leading +::+ resolves from the top-level. - # This only resolves explicit context-tree paths; RDoc::Parser::Ruby has - # parser-local lexical helpers for Ruby's nesting-dependent lookup. + ## + # Returns the owner context and local name for +constant_path+, creating + # missing namespace modules. A leading +::+ resolves from the top-level. + # This only resolves explicit context-tree paths; RDoc::Parser::Ruby has + # parser-local lexical helpers for Ruby's nesting-dependent lookup. - def find_or_create_constant_owner_for_path(constant_path) # :nodoc: - constant_path = constant_path.to_s - owner = constant_path.start_with?('::') ? top_level : self - constant_path = constant_path.delete_prefix('::') + def find_or_create_constant_owner_for_path(constant_path) # :nodoc: + constant_path = constant_path.to_s + owner = constant_path.start_with?('::') ? top_level : self + constant_path = constant_path.delete_prefix('::') - owner_path, separator, name = constant_path.rpartition('::') - owner = owner.find_or_create_namespace_path owner_path unless separator.empty? + owner_path, separator, name = constant_path.rpartition('::') + owner = owner.find_or_create_namespace_path owner_path unless separator.empty? - [owner, name] - end + [owner, name] + end - ## - # Finds or creates the module namespace path under this context. + ## + # Finds or creates the module namespace path under this context. - def find_or_create_namespace_path(path) # :nodoc: - path.to_s.split('::').inject(self) do |owner, name| - owner.classes_hash[name] || - owner.modules_hash[name] || - owner.add_module(RDoc::NormalModule, name) + def find_or_create_namespace_path(path) # :nodoc: + path.to_s.split('::').inject(self) do |owner, name| + owner.classes_hash[name] || + owner.modules_hash[name] || + owner.add_module(::RDoc::NormalModule, name) + end end - end - ## - # Adds a module named +name+. If RDoc already knows +name+ is a class then - # that class is returned instead. See also #add_class. + ## + # Adds a module named +name+. If RDoc already knows +name+ is a class then + # that class is returned instead. See also #add_class. - def add_module(class_type, name) - if name.to_s.include?('::') - owner, name = find_or_create_constant_owner_for_path name - return owner.add_module class_type, name unless owner == self - end + def add_module(class_type, name) + if name.to_s.include?('::') + owner, name = find_or_create_constant_owner_for_path name + return owner.add_module class_type, name unless owner == self + end - mod = @classes[name] || @modules[name] - return mod if mod + mod = @classes[name] || @modules[name] + return mod if mod - full_name = child_name name - mod = @store.modules_hash[full_name] || class_type.new(name) + full_name = child_name name + mod = @store.modules_hash[full_name] || class_type.new(name) - add_class_or_module mod, @modules, @store.modules_hash - end + add_class_or_module mod, @modules, @store.modules_hash + end - ## - # Adds a module by +RDoc::NormalModule+ instance. See also #add_module. + ## + # Adds a module by +RDoc::NormalModule+ instance. See also #add_module. - def add_module_by_normal_module(mod) - add_class_or_module mod, @modules, @store.modules_hash - end + def add_module_by_normal_module(mod) + add_class_or_module mod, @modules, @store.modules_hash + end - ## - # Adds an alias from +from+ (a class or module) to the constant +to+ which - # was defined in +file+. + ## + # Adds an alias from +from+ (a class or module) to the constant +to+ which + # was defined in +file+. + + def add_module_alias(from, to, file) + return from if @done_documenting - def add_module_alias(from, to, file) - return from if @done_documenting + to_full_name = child_name to.name - to_full_name = child_name to.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 is a class when we find + # BasicObject = BlankSlate + return from if @store.find_class_or_module to_full_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 is a class when we find - # BasicObject = BlankSlate - return from if @store.find_class_or_module to_full_name + new_to = from.dup + new_to.name = to.name + new_to.full_name = nil + new_to.is_alias_for = from + + if new_to.module? + @store.modules_hash[to_full_name] = new_to + @modules[to.name] = new_to + else + @store.classes_hash[to_full_name] = new_to + @classes[to.name] = new_to + end - new_to = from.dup - new_to.name = to.name - new_to.full_name = nil - new_to.is_alias_for = from + # 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 to.name, nil, new_to.comment + const.record_location file + const.is_alias_for = from + add_constant const - if new_to.module? - @store.modules_hash[to_full_name] = new_to - @modules[to.name] = new_to - else - @store.classes_hash[to_full_name] = new_to - @classes[to.name] = new_to + new_to end - # 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 to.name, nil, new_to.comment - const.record_location file - const.is_alias_for = from - add_constant const + ## + # Adds +require+ to this context's top level - new_to - end + def add_require(require) + return require unless @document_self - ## - # Adds +require+ to this context's top level + if ::RDoc::TopLevel === self + add_to @requires, require + else + parent.add_require require + end + end - def add_require(require) - return require unless @document_self + ## + # Returns a section with +title+, creating it if it doesn't already exist. + # +comment+ will be appended to the section's comment. + # + # A section with a +title+ of +nil+ will return the default section. + # + # See also RDoc::Context::Section + + def add_section(title, comment = nil) + if section = @sections[title] + section.add_comment comment if comment + else + section = Section.new self, title, comment, @store + @sections[title] = section + end - if RDoc::TopLevel === self - add_to @requires, require - else - parent.add_require require + section end - end - ## - # Returns a section with +title+, creating it if it doesn't already exist. - # +comment+ will be appended to the section's comment. - # - # A section with a +title+ of +nil+ will return the default section. - # - # See also RDoc::Context::Section - - def add_section(title, comment = nil) - if section = @sections[title] - section.add_comment comment if comment - else - section = Section.new self, title, comment, @store - @sections[title] = section - end - - section - end + ## + # Adds +thing+ to the collection +array+ - ## - # Adds +thing+ to the collection +array+ + def add_to(array, thing) + array << thing if @document_self - def add_to(array, thing) - array << thing if @document_self + thing.parent = self + thing.store = @store if @store + thing.section = current_section + end - thing.parent = self - thing.store = @store if @store - thing.section = current_section - end + ## + # Is there any content? + # + # This means any of: comment, aliases, methods, attributes, external + # aliases, require, constant. + # + # Includes and extends are also checked unless includes == false. + + def any_content(includes = true) + @any_content ||= !( + @comment.empty? && + @method_list.empty? && + @attributes.empty? && + @aliases.empty? && + @external_aliases.empty? && + @requires.empty? && + @constants.empty? + ) + @any_content || (includes && !(@includes + @extends).empty? ) + end - ## - # Is there any content? - # - # This means any of: comment, aliases, methods, attributes, external - # aliases, require, constant. - # - # Includes and extends are also checked unless includes == false. - - def any_content(includes = true) - @any_content ||= !( - @comment.empty? && - @method_list.empty? && - @attributes.empty? && - @aliases.empty? && - @external_aliases.empty? && - @requires.empty? && - @constants.empty? - ) - @any_content || (includes && !(@includes + @extends).empty? ) - end + ## + # Creates the full name for a child with +name+ - ## - # Creates the full name for a child with +name+ + def child_name(name) + if name =~ /^:+/ + $' #' + elsif ::RDoc::TopLevel === self + name + else + "#{self.full_name}::#{name}" + end + end - def child_name(name) - if name =~ /^:+/ - $' #' - elsif RDoc::TopLevel === self - name - else - "#{self.full_name}::#{name}" + ## + # Class methods + + def class_method_list + method_list.select { |a| a.singleton } end - end - ## - # Class methods + ## + # Array of classes in this context - def class_method_list - method_list.select { |a| a.singleton } - end + def classes + @classes.values + end - ## - # Array of classes in this context + ## + # All classes and modules in this namespace - def classes - @classes.values - end + def classes_and_modules + classes + modules + end - ## - # All classes and modules in this namespace + ## + # Hash of classes keyed by class name - def classes_and_modules - classes + modules - end + def classes_hash + @classes + end - ## - # Hash of classes keyed by class name + ## + # The current documentation section that new items will be added to. If + # temporary_section is available it will be used. - def classes_hash - @classes - end + def current_section + if section = @temporary_section + @temporary_section = nil + else + section = @current_section + end - ## - # The current documentation section that new items will be added to. If - # temporary_section is available it will be used. + section + end - def current_section - if section = @temporary_section - @temporary_section = nil - else - section = @current_section + def display(method_attr) # :nodoc: + if method_attr.is_a? ::RDoc::Attr + "#{method_attr.definition} #{method_attr.pretty_name}" + else + "method #{method_attr.pretty_name}" + end end - section - end + ## + # 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 display(method_attr) # :nodoc: - if method_attr.is_a? RDoc::Attr - "#{method_attr.definition} #{method_attr.pretty_name}" - else - "method #{method_attr.pretty_name}" + def each_ancestor(&_) # :nodoc: end - end - ## - # 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. + ## + # Iterator for classes and modules - def each_ancestor(&_) # :nodoc: - end + def each_classmodule(&block) # :yields: module + classes_and_modules.sort.each(&block) + end - ## - # Iterator for classes and modules + ## + # Iterator for methods - def each_classmodule(&block) # :yields: module - classes_and_modules.sort.each(&block) - end + def each_method # :yields: method + return enum_for __method__ unless block_given? - ## - # Iterator for methods + @method_list.sort.each { |m| yield m } + end - def each_method # :yields: method - return enum_for __method__ unless block_given? + ## + # Iterator for each section's contents sorted by title. The +section+, the + # section's +constants+ and the sections +attributes+ are yielded. The + # +constants+ and +attributes+ collections are sorted. + # + # To retrieve methods in a section use #methods_by_type with the optional + # +section+ parameter. + # + # NOTE: Do not edit collections yielded by this method - @method_list.sort.each { |m| yield m } - end + def each_section # :yields: section, constants, attributes + return enum_for __method__ unless block_given? - ## - # Iterator for each section's contents sorted by title. The +section+, the - # section's +constants+ and the sections +attributes+ are yielded. The - # +constants+ and +attributes+ collections are sorted. - # - # To retrieve methods in a section use #methods_by_type with the optional - # +section+ parameter. - # - # NOTE: Do not edit collections yielded by this method + constants = @constants.group_by do |constant| constant.section end + attributes = @attributes.group_by do |attribute| attribute.section end - def each_section # :yields: section, constants, attributes - return enum_for __method__ unless block_given? + constants.default = [] + attributes.default = [] - constants = @constants.group_by do |constant| constant.section end - attributes = @attributes.group_by do |attribute| attribute.section end + sort_sections.each do |section| + yield section, constants[section].select(&:display?).sort, attributes[section].select(&:display?).sort + end + end - constants.default = [] - attributes.default = [] + ## + # Finds an attribute +name+ with singleton value +singleton+. - sort_sections.each do |section| - yield section, constants[section].select(&:display?).sort, attributes[section].select(&:display?).sort + def find_attribute(name, singleton) + name = $1 if name =~ /^(.*)=$/ + @attributes.find { |a| a.name == name && a.singleton == singleton } end - end - ## - # Finds an attribute +name+ with singleton value +singleton+. + ## + # Finds an attribute with +name+ in this context - def find_attribute(name, singleton) - name = $1 if name =~ /^(.*)=$/ - @attributes.find { |a| a.name == name && a.singleton == singleton } - end + def find_attribute_named(name) + case name + when /\A#/ + find_attribute name[1..-1], false + when /\A::/ + find_attribute name[2..-1], true + else + @attributes.find { |a| a.name == name } + end + end - ## - # Finds an attribute with +name+ in this context + ## + # Finds a class method with +name+ in this context - def find_attribute_named(name) - case name - when /\A#/ - find_attribute name[1..-1], false - when /\A::/ - find_attribute name[2..-1], true - else - @attributes.find { |a| a.name == name } + def find_class_method_named(name) + @method_list.find { |meth| meth.singleton && meth.name == name } end - end - ## - # Finds a class method with +name+ in this context + ## + # Finds a constant with +name+ in this context - def find_class_method_named(name) - @method_list.find { |meth| meth.singleton && meth.name == name } - end + def find_constant_named(name) + @constants.find do |m| + m.name == name || m.full_name == name + end + end - ## - # Finds a constant with +name+ in this context + ## + # Tries to find a module at a higher scope. + # But parent is not always a higher module nesting scope, so the result is not correct. + # Parent chain can only represent last-opened nesting, and may be broken in some cases. + # The Ruby parser does not represent module nesting with the parent chain. - def find_constant_named(name) - @constants.find do |m| - m.name == name || m.full_name == name + def find_enclosing_module_named(name) + parent && parent.find_module_named(name) end - end - ## - # Tries to find a module at a higher scope. - # But parent is not always a higher module nesting scope, so the result is not correct. - # Parent chain can only represent last-opened nesting, and may be broken in some cases. - # The Ruby parser does not represent module nesting with the parent chain. + ## + # Finds an external alias +name+ with singleton value +singleton+. - def find_enclosing_module_named(name) - parent && parent.find_module_named(name) - end + def find_external_alias(name, singleton) + @external_aliases.find { |m| m.name == name && m.singleton == singleton } + end - ## - # Finds an external alias +name+ with singleton value +singleton+. + ## + # Finds an external alias with +name+ in this context - def find_external_alias(name, singleton) - @external_aliases.find { |m| m.name == name && m.singleton == singleton } - end + def find_external_alias_named(name) + case name + when /\A#/ + find_external_alias name[1..-1], false + when /\A::/ + find_external_alias name[2..-1], true + else + @external_aliases.find { |a| a.name == name } + end + end - ## - # Finds an external alias with +name+ in this context + ## + # Finds an instance method with +name+ in this context - def find_external_alias_named(name) - case name - when /\A#/ - find_external_alias name[1..-1], false - when /\A::/ - find_external_alias name[2..-1], true - else - @external_aliases.find { |a| a.name == name } + def find_instance_method_named(name) + @method_list.find { |meth| !meth.singleton && meth.name == name } end - end - ## - # Finds an instance method with +name+ in this context + ## + # Finds a method, constant, attribute, external alias, module or file + # named +symbol+ in this context. + + def find_local_symbol(symbol) + find_method_named(symbol) or + find_constant_named(symbol) or + find_attribute_named(symbol) or + find_external_alias_named(symbol) or + find_module_named(symbol) or + @store.find_file_named(symbol) + end - def find_instance_method_named(name) - @method_list.find { |meth| !meth.singleton && meth.name == name } - end + ## + # Finds a method named +name+ with singleton value +singleton+. - ## - # Finds a method, constant, attribute, external alias, module or file - # named +symbol+ in this context. - - def find_local_symbol(symbol) - find_method_named(symbol) or - find_constant_named(symbol) or - find_attribute_named(symbol) or - find_external_alias_named(symbol) or - find_module_named(symbol) or - @store.find_file_named(symbol) - end + def find_method(name, singleton) + @method_list.find { |m| + if m.singleton + m.name == name && m.singleton == singleton + else + m.name == name && !m.singleton && !singleton + end + } + end - ## - # Finds a method named +name+ with singleton value +singleton+. + ## + # Finds a instance or module method with +name+ in this context - def find_method(name, singleton) - @method_list.find { |m| - if m.singleton - m.name == name && m.singleton == singleton + def find_method_named(name) + case name + when /\A#/ + find_method name[1..-1], false + when /\A::/ + find_method name[2..-1], true else - m.name == name && !m.singleton && !singleton + @method_list.find { |meth| meth.name == name } end - } - end + end - ## - # Finds a instance or module method with +name+ in this context + ## + # Find a module with +name+ trying to using ruby's scoping rules. + # find_enclosing_module_named cannot use ruby's scoping so the result is not correct. - def find_method_named(name) - case name - when /\A#/ - find_method name[1..-1], false - when /\A::/ - find_method name[2..-1], true - else - @method_list.find { |meth| meth.name == name } + def find_module_named(name) + res = get_module_named(name) + return res if res + return self if self.name == name + find_enclosing_module_named name end - end - ## - # Find a module with +name+ trying to using ruby's scoping rules. - # find_enclosing_module_named cannot use ruby's scoping so the result is not correct. - - def find_module_named(name) - res = get_module_named(name) - return res if res - return self if self.name == name - find_enclosing_module_named name - end + # Get a module named +name+ in this context + # Don't look up for higher module nesting scopes. RDoc::Context doesn't have that information. - # Get a module named +name+ in this context - # Don't look up for higher module nesting scopes. RDoc::Context doesn't have that information. - - def get_module_named(name) - @modules[name] || @classes[name] - end + def get_module_named(name) + @modules[name] || @classes[name] + end - ## - # Look up +symbol+, first as a module, then as a local symbol. + ## + # Look up +symbol+, first as a module, then as a local symbol. - def find_symbol(symbol) - find_symbol_module(symbol) || find_local_symbol(symbol) - end + def find_symbol(symbol) + find_symbol_module(symbol) || find_local_symbol(symbol) + end - ## - # Look up a module named +symbol+. - - def find_symbol_module(symbol) - result = nil - - # look for a class or module 'symbol' - case symbol - when /^::/ - result = @store.find_class_or_module symbol - when /^(\w+):+(.+)$/ - suffix = $2 - top = $1 - searched = self - while searched do - mod = searched.find_module_named(top) - break unless mod - 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 - while searched do - result = searched.find_module_named(symbol) - break if result || searched.is_a?(RDoc::TopLevel) - searched = searched.parent + ## + # Look up a module named +symbol+. + + def find_symbol_module(symbol) + result = nil + + # look for a class or module 'symbol' + case symbol + when /^::/ + result = @store.find_class_or_module symbol + when /^(\w+):+(.+)$/ + suffix = $2 + top = $1 + searched = self + while searched do + mod = searched.find_module_named(top) + break unless mod + 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 + while searched do + result = searched.find_module_named(symbol) + break if result || searched.is_a?(::RDoc::TopLevel) + searched = searched.parent + end end + + result end - result - end + ## + # The full name for this context. This method is overridden by subclasses. - ## - # The full name for this context. This method is overridden by subclasses. + def full_name + '(unknown)' + end - def full_name - '(unknown)' - end + ## + # Does this context and its methods and constants all have documentation? + # + # (Yes, fully documented doesn't mean everything.) - ## - # Does this context and its methods and constants all have documentation? - # - # (Yes, fully documented doesn't mean everything.) - - def fully_documented? - documented? and - attributes.all? { |a| a.documented? } and - method_list.all? { |m| m.documented? } and - constants.all? { |c| c.documented? } - end + def fully_documented? + documented? and + attributes.all? { |a| a.documented? } and + method_list.all? { |m| m.documented? } and + constants.all? { |c| c.documented? } + end - ## - # URL for this with a +prefix+ + ## + # URL for this with a +prefix+ - def http_url - path = name_for_path - path = path.gsub(/<<\s*(\w*)/, 'from-\1') if path =~ /<'class' or - # 'instance') and visibility (+:public+, +:protected+, +:private+). - # - # If +section+ is provided only methods in that RDoc::Context::Section will - # be returned. - - def methods_by_type(section = nil) - methods = {} - - TYPES.each do |type| - visibilities = {} - RDoc::VISIBILITIES.each do |vis| - visibilities[vis] = [] + ## + # Breaks method_list into a nested hash by type ('class' or + # 'instance') and visibility (+:public+, +:protected+, +:private+). + # + # If +section+ is provided only methods in that RDoc::Context::Section will + # be returned. + + def methods_by_type(section = nil) + methods = {} + + TYPES.each do |type| + visibilities = {} + ::RDoc::VISIBILITIES.each do |vis| + visibilities[vis] = [] + end + + methods[type] = visibilities end - methods[type] = visibilities - end + each_method do |method| + next if section and not method.section == section + methods[method.type][method.visibility] << method + end - each_method do |method| - next if section and not method.section == section - methods[method.type][method.visibility] << method + methods end - methods - end + ## + # Yields AnyMethod and Attr entries matching the list of names in +methods+. - ## - # Yields AnyMethod and Attr entries matching the list of names in +methods+. + def methods_matching(methods, singleton = false, &block) + (@method_list + @attributes).each do |m| + yield m if methods.include?(m.name) and m.singleton == singleton + end - def methods_matching(methods, singleton = false, &block) - (@method_list + @attributes).each do |m| - yield m if methods.include?(m.name) and m.singleton == singleton + each_ancestor do |parent| + parent.methods_matching(methods, singleton, &block) + end end - each_ancestor do |parent| - parent.methods_matching(methods, singleton, &block) + ## + # Array of modules in this context + + def modules + @modules.values end - end - ## - # Array of modules in this context + ## + # Hash of modules keyed by module name - def modules - @modules.values - end + def modules_hash + @modules + end - ## - # Hash of modules keyed by module name + ## + # Name to use to generate the url. + # #full_name by default. - def modules_hash - @modules - end + def name_for_path + full_name + end - ## - # Name to use to generate the url. - # #full_name by default. + ## + # Record +top_level+ as a file +self+ is in. - def name_for_path - full_name - end + def record_location(top_level) + @in_files << top_level unless @in_files.include?(top_level) + end - ## - # Record +top_level+ as a file +self+ is in. + ## + # Should we remove this context from the documentation? + # + # The answer is yes if: + # * #received_nodoc is +true+ + # * #any_content is +false+ (not counting includes) + # * All #includes are modules (not a string), and their module has + # #remove_from_documentation? == true + # * All classes and modules have #remove_from_documentation? == true + + def remove_from_documentation? + # Contexts that are still ignored here were created inside a :stopdoc: + # region and never received documentable contents afterwards + @remove_from_documentation ||= + (@received_nodoc || @ignored) && + !any_content(false) && + @includes.all? { |i| !i.module.is_a?(String) && i.module.remove_from_documentation? } && + classes_and_modules.all? { |cm| cm.remove_from_documentation? } + end - def record_location(top_level) - @in_files << top_level unless @in_files.include?(top_level) - end + ## + # Removes methods and attributes with a visibility less than +min_visibility+. + #-- + # TODO mark the visibility of attributes in the template (if not public?) - ## - # Should we remove this context from the documentation? - # - # The answer is yes if: - # * #received_nodoc is +true+ - # * #any_content is +false+ (not counting includes) - # * All #includes are modules (not a string), and their module has - # #remove_from_documentation? == true - # * All classes and modules have #remove_from_documentation? == true - - def remove_from_documentation? - # Contexts that are still ignored here were created inside a :stopdoc: - # region and never received documentable contents afterwards - @remove_from_documentation ||= - (@received_nodoc || @ignored) && - !any_content(false) && - @includes.all? { |i| !i.module.is_a?(String) && i.module.remove_from_documentation? } && - classes_and_modules.all? { |cm| cm.remove_from_documentation? } - end + def remove_invisible(min_visibility) + return if [:private, :nodoc].include? min_visibility + remove_invisible_in @method_list, min_visibility + remove_invisible_in @attributes, min_visibility + remove_invisible_in @constants, min_visibility + end - ## - # Removes methods and attributes with a visibility less than +min_visibility+. - #-- - # TODO mark the visibility of attributes in the template (if not public?) - - def remove_invisible(min_visibility) - return if [:private, :nodoc].include? min_visibility - remove_invisible_in @method_list, min_visibility - remove_invisible_in @attributes, min_visibility - remove_invisible_in @constants, min_visibility - end + ## + # Only called when min_visibility == :public or :private - ## - # Only called when min_visibility == :public or :private + def remove_invisible_in(array, min_visibility) # :nodoc: + if min_visibility == :public + array.reject! { |e| + e.visibility != :public and not e.force_documentation + } + else + array.reject! { |e| + e.visibility == :private and not e.force_documentation + } + end + end - def remove_invisible_in(array, min_visibility) # :nodoc: - if min_visibility == :public - array.reject! { |e| - e.visibility != :public and not e.force_documentation - } - else - array.reject! { |e| - e.visibility == :private and not e.force_documentation - } + ## + # Tries to resolve unmatched aliases when a method or attribute has just + # been added. + + def resolve_aliases(added) + # resolve any pending unmatched aliases + key = added.pretty_name + unmatched_alias_list = @unmatched_alias_lists[key] + return unless unmatched_alias_list + unmatched_alias_list.each do |unmatched_alias| + added.add_alias unmatched_alias, self + @external_aliases.delete unmatched_alias + end + @unmatched_alias_lists.delete key end - end - ## - # Tries to resolve unmatched aliases when a method or attribute has just - # been added. - - def resolve_aliases(added) - # resolve any pending unmatched aliases - key = added.pretty_name - unmatched_alias_list = @unmatched_alias_lists[key] - return unless unmatched_alias_list - unmatched_alias_list.each do |unmatched_alias| - added.add_alias unmatched_alias, self - @external_aliases.delete unmatched_alias - end - @unmatched_alias_lists.delete key - end + ## + # Returns RDoc::Context::Section objects referenced in this context for use + # in a table of contents. - ## - # Returns RDoc::Context::Section objects referenced in this context for use - # in a table of contents. + def section_contents + used_sections = {} - def section_contents - used_sections = {} + each_method do |method| + next unless method.display? - each_method do |method| - next unless method.display? + used_sections[method.section] = true + end - used_sections[method.section] = true - end + # order found sections + sections = sort_sections.select do |section| + used_sections[section] + end - # order found sections - sections = sort_sections.select do |section| - used_sections[section] + # only the default section is used + return [] if + sections.length == 1 and not sections.first.title + + sections end - # only the default section is used - return [] if - sections.length == 1 and not sections.first.title + ## + # Sections in this context - sections - end + def sections + @sections.values + end - ## - # Sections in this context + def sections_hash # :nodoc: + @sections + end - def sections - @sections.values - end + ## + # Sets the current section to a section with +title+. See also #add_section - def sections_hash # :nodoc: - @sections - end + def set_current_section(title, comment) + @current_section = add_section title, comment + end - ## - # Sets the current section to a section with +title+. See also #add_section + ## + # Given an array +methods+ of method names, set the visibility of each to + # +visibility+ - def set_current_section(title, comment) - @current_section = add_section title, comment - end + def set_visibility_for(methods, visibility, singleton = false) + methods_matching methods, singleton do |m| + m.visibility = visibility + end + end - ## - # Given an array +methods+ of method names, set the visibility of each to - # +visibility+ + ## + # Given an array +names+ of constants, set the visibility of each constant to + # +visibility+ - def set_visibility_for(methods, visibility, singleton = false) - methods_matching methods, singleton do |m| - m.visibility = visibility + def set_constant_visibility_for(names, visibility) + names.each do |name| + constant = @constants_hash[name] or next + constant.visibility = visibility + end end - end - ## - # Given an array +names+ of constants, set the visibility of each constant to - # +visibility+ + ## + # Sorts sections alphabetically (default) or in TomDoc fashion (none, + # Public, Internal, Deprecated) - def set_constant_visibility_for(names, visibility) - names.each do |name| - constant = @constants_hash[name] or next - constant.visibility = visibility - end - end + def sort_sections + titles = @sections.map { |title, _| title } - ## - # 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 } - @sections.values_at(*TOMDOC_TITLES).compact - else - @sections.sort_by { |title, _| - title.to_s - }.map { |_, section| - section - } + if titles.length > 1 and + TOMDOC_TITLES_SORT == + (titles | TOMDOC_TITLES).sort_by { |title| title.to_s } + @sections.values_at(*TOMDOC_TITLES).compact + else + @sections.sort_by { |title, _| + title.to_s + }.map { |_, section| + section + } + end end - end - def to_s # :nodoc: - "#{self.class.name} #{self.full_name}" - end + def to_s # :nodoc: + "#{self.class.name} #{self.full_name}" + end - ## - # Return the TopLevel that owns us - #-- - # FIXME we can be 'owned' by several TopLevel (see #record_location & - # #in_files) - - def top_level - return @top_level if defined? @top_level - @top_level = self - @top_level = @top_level.parent until RDoc::TopLevel === @top_level - @top_level - end + ## + # Return the TopLevel that owns us + #-- + # FIXME we can be 'owned' by several TopLevel (see #record_location & + # #in_files) + + def top_level + return @top_level if defined? @top_level + @top_level = self + @top_level = @top_level.parent until ::RDoc::TopLevel === @top_level + @top_level + end - ## - # Upgrades NormalModule +mod+ in +enclosing+ to a +class_type+ + ## + # Upgrades NormalModule +mod+ in +enclosing+ to a +class_type+ - def upgrade_to_class(mod, class_type, enclosing) - enclosing.modules_hash.delete mod.name + def upgrade_to_class(mod, class_type, enclosing) + enclosing.modules_hash.delete mod.name - klass = RDoc::ClassModule.from_module class_type, mod - klass.store = @store + klass = ::RDoc::ClassModule.from_module class_type, mod + klass.store = @store - # if it was there, then we keep it even if done_documenting - @store.classes_hash[mod.full_name] = klass - enclosing.classes_hash[mod.name] = klass + # if it was there, then we keep it even if done_documenting + @store.classes_hash[mod.full_name] = klass + enclosing.classes_hash[mod.name] = klass - klass - end + klass + end - autoload :Section, "#{__dir__}/context/section" + autoload :Section, "#{__dir__}/context/section" + end end diff --git a/lib/rdoc/code_object/context/section.rb b/lib/rdoc/code_object/context/section.rb index 2d2b78e465..51cd08b482 100644 --- a/lib/rdoc/code_object/context/section.rb +++ b/lib/rdoc/code_object/context/section.rb @@ -2,181 +2,185 @@ require 'cgi/escape' require 'cgi/util' unless defined?(CGI::EscapeExt) -## -# A section of documentation like: -# -# # :section: The title -# # The body -# -# Sections can be referenced multiple times and will be collapsed into a -# single section. +module RDoc + class Context + ## + # 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 + class Section - include RDoc::Text + include ::RDoc::Text - MARSHAL_VERSION = 0 # :nodoc: + MARSHAL_VERSION = 0 # :nodoc: - ## - # Section comments + ## + # Section comments - attr_reader :comments + attr_reader :comments - ## - # Context this Section lives in + ## + # Context this Section lives in - attr_reader :parent + attr_reader :parent - ## - # Section title + ## + # Section title - attr_reader :title + attr_reader :title - ## - # The RDoc::Store for this object. + ## + # The RDoc::Store for this object. - attr_reader :store + attr_reader :store - ## - # Creates a new section with +title+ and +comment+ + ## + # Creates a new section with +title+ and +comment+ - def initialize(parent, title, comment, store = nil) - @parent = parent - @title = title ? title.strip : title - @store = store + def initialize(parent, title, comment, store = nil) + @parent = parent + @title = title ? title.strip : title + @store = store - @comments = [] + @comments = [] - add_comment comment - end + add_comment comment + end - ## - # Sections are equal when they have the same #title + ## + # Sections are equal when they have the same #title - def ==(other) - self.class === other and @title == other.title - end + def ==(other) + self.class === other and @title == other.title + end - alias eql? == + alias eql? == - ## - # Adds +comment+ to this section + ## + # Adds +comment+ to this section - def add_comment(comment) - Array(comment).each do |c| - next if c.nil? - raise TypeError, "unknown comment #{c.inspect}" unless RDoc::Comment === c - @comments << c unless c.empty? - end - end + def add_comment(comment) + Array(comment).each do |c| + next if c.nil? + raise TypeError, "unknown comment #{c.inspect}" unless ::RDoc::Comment === c + @comments << c unless c.empty? + end + end - ## - # Anchor reference for linking to this section using GitHub-style format. - # - # Examples: - # "Section" -> "section" - # "One Two" -> "one-two" - # "[untitled]" -> "untitled" + ## + # Anchor reference for linking to this section using GitHub-style format. + # + # Examples: + # "Section" -> "section" + # "One Two" -> "one-two" + # "[untitled]" -> "untitled" - def aref - title = @title || '[untitled]' + def aref + title = @title || '[untitled]' - RDoc::Text.to_anchor(title) - end + ::RDoc::Text.to_anchor(title) + end - ## - # Legacy anchor reference for backward compatibility. - # - # Examples: - # "Section" -> "section" - # "One Two" -> "one+two" - # "[untitled]" -> "5Buntitled-5D" + ## + # Legacy anchor reference for backward compatibility. + # + # Examples: + # "Section" -> "section" + # "One Two" -> "one+two" + # "[untitled]" -> "5Buntitled-5D" - def legacy_aref - title = @title || '[untitled]' + def legacy_aref + title = @title || '[untitled]' - CGI.escape(title).gsub('%', '-').sub(/^-/, '') - end + CGI.escape(title).gsub('%', '-').sub(/^-/, '') + end - def inspect # :nodoc: - "#<%s:0x%x %p>" % [self.class, object_id, title] - end + def inspect # :nodoc: + "#<%s:0x%x %p>" % [self.class, object_id, title] + end - def hash # :nodoc: - @title.hash - end - - ## - # The files comments in this section come from + def hash # :nodoc: + @title.hash + end - def in_files - @comments.map(&:file) - end + ## + # The files comments in this section come from - ## - # Serializes this Section. The title and parsed comment are saved, but not - # the section parent which must be restored manually. + def in_files + @comments.map(&:file) + end - def marshal_dump - [ - MARSHAL_VERSION, - @title, - to_document, - ] - end + ## + # Serializes this Section. The title and parsed comment are saved, but not + # the section parent which must be restored manually. - ## - # De-serializes this Section. The section parent must be restored manually. + def marshal_dump + [ + MARSHAL_VERSION, + @title, + to_document, + ] + end - def marshal_load(array) - @parent = nil + ## + # De-serializes this Section. The section parent must be restored manually. - @title = array[1] - @comments = array[2].parts.map { |doc| RDoc::Comment.from_document(doc) } - end + def marshal_load(array) + @parent = nil - ## - # Parses +comment_location+ into an RDoc::Markup::Document composed of - # multiple RDoc::Markup::Documents with their file set. + @title = array[1] + @comments = array[2].parts.map { |doc| ::RDoc::Comment.from_document(doc) } + end - def to_document - RDoc::Markup::Document.new(*@comments.map(&:parse)) - end + ## + # Parses +comment_location+ into an RDoc::Markup::Document composed of + # multiple RDoc::Markup::Documents with their file set. + + def to_document + ::RDoc::Markup::Document.new(*@comments.map(&:parse)) + 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 + + ## + # Section comment + + def comment + return nil if @comments.empty? + ::RDoc::Comment.from_document(to_document) + end + + def description + return '' if @comments.empty? + markup comment + end + + def language + @comments.first&.language + end + + ## + # Removes a comment from this section if it is from the same file as + # +comment+ + + def remove_comment(target_comment) + @comments.delete_if do |stored_comment| + stored_comment.file == target_comment.file + 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 - - ## - # Section comment - - def comment - return nil if @comments.empty? - RDoc::Comment.from_document(to_document) - end - - def description - return '' if @comments.empty? - markup comment - end - - def language - @comments.first&.language - end - - ## - # Removes a comment from this section if it is from the same file as - # +comment+ - - def remove_comment(target_comment) - @comments.delete_if do |stored_comment| - stored_comment.file == target_comment.file end end - end diff --git a/lib/rdoc/code_object/extend.rb b/lib/rdoc/code_object/extend.rb index 7d57433de6..bde2f6f5a9 100644 --- a/lib/rdoc/code_object/extend.rb +++ b/lib/rdoc/code_object/extend.rb @@ -1,9 +1,11 @@ # frozen_string_literal: true -## -# A Module extension to a class with \#extend -# -# RDoc::Extend.new 'Enumerable', 'comment ...' +module RDoc + ## + # A Module extension to a class with \#extend + # + # RDoc::Extend.new 'Enumerable', 'comment ...' -class RDoc::Extend < RDoc::Mixin + class Extend < ::RDoc::Mixin + end end diff --git a/lib/rdoc/code_object/include.rb b/lib/rdoc/code_object/include.rb index c3e0d45e47..bc49d7f4af 100644 --- a/lib/rdoc/code_object/include.rb +++ b/lib/rdoc/code_object/include.rb @@ -1,9 +1,11 @@ # frozen_string_literal: true -## -# A Module included in a class with \#include -# -# RDoc::Include.new 'Enumerable', 'comment ...' +module RDoc + ## + # A Module included in a class with \#include + # + # RDoc::Include.new 'Enumerable', 'comment ...' -class RDoc::Include < RDoc::Mixin + class Include < ::RDoc::Mixin + end end diff --git a/lib/rdoc/code_object/method_attr.rb b/lib/rdoc/code_object/method_attr.rb index b7b53a0bac..396df01f6a 100644 --- a/lib/rdoc/code_object/method_attr.rb +++ b/lib/rdoc/code_object/method_attr.rb @@ -1,422 +1,424 @@ # frozen_string_literal: true -## -# Abstract class representing either a method or an attribute. - -class RDoc::MethodAttr < RDoc::CodeObject - - include Comparable - +module RDoc ## - # Name of this method/attribute. + # Abstract class representing either a method or an attribute. - attr_accessor :name + class MethodAttr < ::RDoc::CodeObject - ## - # public, protected, private + include Comparable - attr_accessor :visibility + ## + # Name of this method/attribute. - ## - # Is this a singleton method/attribute? + attr_accessor :name - attr_accessor :singleton + ## + # public, protected, private - ## - # Array of other names for this method/attribute + attr_accessor :visibility - attr_reader :aliases + ## + # Is this a singleton method/attribute? - ## - # The method/attribute we're aliasing + attr_accessor :singleton - attr_accessor :is_alias_for + ## + # Array of other names for this method/attribute - #-- - # The attributes below are for AnyMethod only. - # They are left here for the time being to - # allow ri to operate. - # TODO modify ri to avoid calling these on attributes. - #++ + attr_reader :aliases - ## - # Parameters yielded by the called block + ## + # The method/attribute we're aliasing - attr_reader :block_params + attr_accessor :is_alias_for - ## - # Parameters for this method + #-- + # The attributes below are for AnyMethod only. + # They are left here for the time being to + # allow ri to operate. + # TODO modify ri to avoid calling these on attributes. + #++ - attr_accessor :params + ## + # Parameters yielded by the called block - ## - # Different ways to call this method + attr_reader :block_params - attr_accessor :call_seq + ## + # Parameters for this method - ## - # RBS type signature lines from inline annotations or loaded .rbs files. - # Each entry is one overload or type expression. + attr_accessor :params - attr_accessor :type_signature_lines + ## + # Different ways to call this method - ## - # The call_seq or the param_seq with method name, if there is no call_seq. + attr_accessor :call_seq - attr_reader :arglists + ## + # RBS type signature lines from inline annotations or loaded .rbs files. + # Each entry is one overload or type expression. - ## - # Creates a new MethodAttr with method or attribute - # name +name+. - # - # Usually this is called by super from a subclass. - - def initialize(name, singleton: false) - super() - - @name = name - - @aliases = [] - @is_alias_for = nil - @parent_name = nil - @singleton = singleton - @visibility = :public - @see = false - - @arglists = nil - @block_params = nil - @call_seq = nil - @params = nil - @type_signature_lines = nil - end + attr_accessor :type_signature_lines - ## - # Resets cached data for the object so it can be rebuilt by accessor methods + ## + # The call_seq or the param_seq with method name, if there is no call_seq. - def initialize_copy(other) # :nodoc: - @full_name = nil - end + attr_reader :arglists - def initialize_visibility # :nodoc: - super - @see = nil - end + ## + # Creates a new MethodAttr with method or attribute + # name +name+. + # + # Usually this is called by super from a subclass. - ## - # Order by #singleton then #name + def initialize(name, singleton: false) + super() - def <=>(other) - return unless other.respond_to?(:singleton) && - other.respond_to?(:name) + @name = name - [@singleton ? 0 : 1, name_ord_range, name] <=> - [other.singleton ? 0 : 1, other.name_ord_range, other.name] - end + @aliases = [] + @is_alias_for = nil + @parent_name = nil + @singleton = singleton + @visibility = :public + @see = false - def ==(other) # :nodoc: - equal?(other) or self.class == other.class and full_name == other.full_name - end + @arglists = nil + @block_params = nil + @call_seq = nil + @params = nil + @type_signature_lines = nil + end - ## - # A method/attribute is documented if any of the following is true: - # - it was marked with :nodoc:; - # - it has a comment; - # - it is an alias for a documented method; - # - it has a +#see+ method that is documented. - - def documented? - super or - (is_alias_for and is_alias_for.documented?) or - (see and see.documented?) - end + ## + # Resets cached data for the object so it can be rebuilt by accessor methods - ## - # A method/attribute to look at, - # in particular if this method/attribute has no documentation. - # - # It can be a method/attribute of the superclass or of an included module, - # including the Kernel module, which is always appended to the included - # modules. - # - # Returns +nil+ if there is no such method/attribute. - # The +#is_alias_for+ method/attribute, if any, is not included. - # - # Templates may generate a "see also ..." if this method/attribute - # has documentation, and "see ..." if it does not. - - def see - @see = find_see if @see == false - @see - end + def initialize_copy(other) # :nodoc: + @full_name = nil + end - ## - # Sets the store for this class or module and its contained code objects. + def initialize_visibility # :nodoc: + super + @see = nil + end - def store=(store) - super + ## + # Order by #singleton then #name - @file = @store.add_file @file.full_name if @file - end + def <=>(other) + return unless other.respond_to?(:singleton) && + other.respond_to?(:name) - def find_see # :nodoc: - return nil if singleton || is_alias_for + [@singleton ? 0 : 1, name_ord_range, name] <=> + [other.singleton ? 0 : 1, other.name_ord_range, other.name] + end - # look for the method - other = find_method_or_attribute name - return other if other + def ==(other) # :nodoc: + equal?(other) or self.class == other.class and full_name == other.full_name + end - # if it is a setter, look for a getter - return nil unless name =~ /[a-z_]=$/i # avoid == or === - return find_method_or_attribute name[0..-2] - end + ## + # A method/attribute is documented if any of the following is true: + # - it was marked with :nodoc:; + # - it has a comment; + # - it is an alias for a documented method; + # - it has a +#see+ method that is documented. + + def documented? + super or + (is_alias_for and is_alias_for.documented?) or + (see and see.documented?) + end - def find_method_or_attribute(name) # :nodoc: - return nil unless parent.respond_to? :ancestors + ## + # A method/attribute to look at, + # in particular if this method/attribute has no documentation. + # + # It can be a method/attribute of the superclass or of an included module, + # including the Kernel module, which is always appended to the included + # modules. + # + # Returns +nil+ if there is no such method/attribute. + # The +#is_alias_for+ method/attribute, if any, is not included. + # + # Templates may generate a "see also ..." if this method/attribute + # has documentation, and "see ..." if it does not. + + def see + @see = find_see if @see == false + @see + end - searched = parent.ancestors - kernel = @store.modules_hash['Kernel'] + ## + # Sets the store for this class or module and its contained code objects. - searched << kernel if kernel && - parent != kernel && !searched.include?(kernel) + def store=(store) + super - searched.each do |ancestor| - next if String === ancestor - next if parent == ancestor + @file = @store.add_file @file.full_name if @file + end - other = ancestor.find_method_named('#' + name) || - ancestor.find_attribute_named(name) + def find_see # :nodoc: + return nil if singleton || is_alias_for + # look for the method + other = find_method_or_attribute name return other if other + + # if it is a setter, look for a getter + return nil unless name =~ /[a-z_]=$/i # avoid == or === + return find_method_or_attribute name[0..-2] end - nil - end + def find_method_or_attribute(name) # :nodoc: + return nil unless parent.respond_to? :ancestors - ## - # Abstract method. Contexts in their building phase call this - # to register a new alias for this known method/attribute. - # - # - creates a new AnyMethod/Attribute named an_alias.new_name; - # - adds +self+ as an alias for the new method or attribute - # - adds the method or attribute to #aliases - # - adds the method or attribute to +context+. - - def add_alias(an_alias, context) - raise NotImplementedError - end + searched = parent.ancestors + kernel = @store.modules_hash['Kernel'] - ## - # HTML fragment reference for this method + searched << kernel if kernel && + parent != kernel && !searched.include?(kernel) - def aref - type = singleton ? 'c' : 'i' - # % characters are not allowed in html names => dash instead - "#{aref_prefix}-#{type}-#{html_name}" - end + searched.each do |ancestor| + next if String === ancestor + next if parent == ancestor - ## - # Prefix for +aref+, defined by subclasses. + other = ancestor.find_method_named('#' + name) || + ancestor.find_attribute_named(name) - def aref_prefix - raise NotImplementedError - end + return other if other + end - ## - # Attempts to sanitize the content passed by the Ruby parser: - # remove outer parentheses, etc. + nil + end + + ## + # Abstract method. Contexts in their building phase call this + # to register a new alias for this known method/attribute. + # + # - creates a new AnyMethod/Attribute named an_alias.new_name; + # - adds +self+ as an alias for the new method or attribute + # - adds the method or attribute to #aliases + # - adds the method or attribute to +context+. + + def add_alias(an_alias, context) + raise NotImplementedError + end - def block_params=(value) - # 'yield.to_s' or 'assert yield, msg' - return @block_params = '' if value =~ /^[\.,]/ + ## + # HTML fragment reference for this method - # remove trailing 'if/unless ...' - return @block_params = '' if value =~ /^(if|unless)\s/ + def aref + type = singleton ? 'c' : 'i' + # % characters are not allowed in html names => dash instead + "#{aref_prefix}-#{type}-#{html_name}" + end - value = $1.strip if value =~ /^(.+)\s(if|unless)\s/ + ## + # Prefix for +aref+, defined by subclasses. - # outer parentheses - value = $1 if value =~ /^\s*\((.*)\)\s*$/ - value = value.strip + def aref_prefix + raise NotImplementedError + end - # proc/lambda - return @block_params = $1 if value =~ /^(proc|lambda)(\s*\{|\sdo)/ + ## + # Attempts to sanitize the content passed by the Ruby parser: + # remove outer parentheses, etc. - # surrounding +...+ or [...] - value = $1.strip if value =~ /^\+(.*)\+$/ - value = $1.strip if value =~ /^\[(.*)\]$/ + def block_params=(value) + # 'yield.to_s' or 'assert yield, msg' + return @block_params = '' if value =~ /^[\.,]/ - return @block_params = '' if value.empty? + # remove trailing 'if/unless ...' + return @block_params = '' if value =~ /^(if|unless)\s/ - # global variable - return @block_params = 'str' if value =~ /^\$[&0-9]$/ + value = $1.strip if value =~ /^(.+)\s(if|unless)\s/ - # wipe out array/hash indices - value.gsub!(/(\w)\[[^\[]+\]/, '\1') + # outer parentheses + value = $1 if value =~ /^\s*\((.*)\)\s*$/ + value = value.strip - # remove @ from class/instance variables - value.gsub!(/@@?([a-z0-9_]+)/, '\1') + # proc/lambda + return @block_params = $1 if value =~ /^(proc|lambda)(\s*\{|\sdo)/ - # method calls => method name - value.gsub!(/([A-Z:a-z0-9_]+)\.([a-z0-9_]+)(\s*\(\s*[a-z0-9_.,\s]*\s*\)\s*)?/) do - case $2 - when 'to_s' then $1 - when 'const_get' then 'const' - when 'new' - $1.split('::').last. # ClassName => class_name - gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2'). - gsub(/([a-z\d])([A-Z])/, '\1_\2'). - downcase - else - $2 - end - end + # surrounding +...+ or [...] + value = $1.strip if value =~ /^\+(.*)\+$/ + value = $1.strip if value =~ /^\[(.*)\]$/ - # class prefixes - value.gsub!(/[A-Za-z0-9_:]+::/, '') + return @block_params = '' if value.empty? - # simple expressions - value = $1 if value =~ /^([a-z0-9_]+)\s*[-*+\/]/ + # global variable + return @block_params = 'str' if value =~ /^\$[&0-9]$/ - @block_params = value.strip - end + # wipe out array/hash indices + value.gsub!(/(\w)\[[^\[]+\]/, '\1') - ## - # HTML id-friendly method/attribute name + # remove @ from class/instance variables + value.gsub!(/@@?([a-z0-9_]+)/, '\1') - def html_name - require 'cgi/escape' - require 'cgi/util' unless defined?(CGI::EscapeExt) + # method calls => method name + value.gsub!(/([A-Z:a-z0-9_]+)\.([a-z0-9_]+)(\s*\(\s*[a-z0-9_.,\s]*\s*\)\s*)?/) do + case $2 + when 'to_s' then $1 + when 'const_get' then 'const' + when 'new' + $1.split('::').last. # ClassName => class_name + gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2'). + gsub(/([a-z\d])([A-Z])/, '\1_\2'). + downcase + else + $2 + end + end - CGI.escape(@name.gsub('-', '-2D')).gsub('%', '-').sub(/^-/, '') - end + # class prefixes + value.gsub!(/[A-Za-z0-9_:]+::/, '') - ## - # Full method/attribute name including namespace + # simple expressions + value = $1 if value =~ /^([a-z0-9_]+)\s*[-*+\/]/ - def full_name - @full_name ||= "#{parent_name}#{pretty_name}" - end + @block_params = value.strip + end - def inspect # :nodoc: - alias_for = - if @is_alias_for.respond_to? :name - " (alias for #{@is_alias_for.name})" - elsif Array === @is_alias_for - " (alias for #{@is_alias_for.last})" - end - visibility = self.visibility - visibility = "forced #{visibility}" if force_documentation - "#<%s:0x%x %s (%s)%s>" % [ - self.class, object_id, - full_name, - visibility, - alias_for, - ] - end + ## + # HTML id-friendly method/attribute name - ## - # '::' for a class method/attribute, '#' for an instance method. + def html_name + require 'cgi/escape' + require 'cgi/util' unless defined?(CGI::EscapeExt) - def name_prefix - @singleton ? '::' : '#' - end + CGI.escape(@name.gsub('-', '-2D')).gsub('%', '-').sub(/^-/, '') + end - ## - # Method/attribute name with class/instance indicator + ## + # Full method/attribute name including namespace - def pretty_name - "#{name_prefix}#{@name}" - end + def full_name + @full_name ||= "#{parent_name}#{pretty_name}" + end - ## - # Type of method/attribute (class or instance) + def inspect # :nodoc: + alias_for = + if @is_alias_for.respond_to? :name + " (alias for #{@is_alias_for.name})" + elsif Array === @is_alias_for + " (alias for #{@is_alias_for.last})" + end + visibility = self.visibility + visibility = "forced #{visibility}" if force_documentation + "#<%s:0x%x %s (%s)%s>" % [ + self.class, object_id, + full_name, + visibility, + alias_for, + ] + end - def type - singleton ? 'class' : 'instance' - end + ## + # '::' for a class method/attribute, '#' for an instance method. - ## - # Path to this method for use with HTML generator output. + def name_prefix + @singleton ? '::' : '#' + end - def path - "#{@parent.path}##{aref}" - end + ## + # Method/attribute name with class/instance indicator - ## - # Name of our parent with special handling for un-marshaled methods + def pretty_name + "#{name_prefix}#{@name}" + end - def parent_name - @parent_name || super - end + ## + # Type of method/attribute (class or instance) - def pretty_print(q) # :nodoc: - alias_for = - if @is_alias_for.respond_to? :name - "alias for #{@is_alias_for.name}" - elsif Array === @is_alias_for - "alias for #{@is_alias_for.last}" - end + def type + singleton ? 'class' : 'instance' + end - q.group 2, "[#{self.class.name} #{full_name} #{visibility}", "]" do - if alias_for - q.breakable - q.text alias_for - end + ## + # Path to this method for use with HTML generator output. + + def path + "#{@parent.path}##{aref}" + end + + ## + # Name of our parent with special handling for un-marshaled methods + + def parent_name + @parent_name || super + end - unless comment.empty? - q.breakable - q.text "comment:" - q.breakable - q.pp @comment + def pretty_print(q) # :nodoc: + alias_for = + if @is_alias_for.respond_to? :name + "alias for #{@is_alias_for.name}" + elsif Array === @is_alias_for + "alias for #{@is_alias_for.last}" + end + + q.group 2, "[#{self.class.name} #{full_name} #{visibility}", "]" do + if alias_for + q.breakable + q.text alias_for + end + + unless comment.empty? + q.breakable + q.text "comment:" + q.breakable + q.pp @comment + end end end - end - ## - # Used by RDoc::Generator::JsonIndex to create a record for the search - # engine. - # - # TODO: Remove this method after dropping the darkfish theme and JsonIndex generator. - # Use #search_snippet instead for getting documentation snippets. - - def search_record - [ - @name, - full_name, - @name, - @parent.full_name, - path, - params, - search_snippet, - ] - end + ## + # Used by RDoc::Generator::JsonIndex to create a record for the search + # engine. + # + # TODO: Remove this method after dropping the darkfish theme and JsonIndex generator. + # Use #search_snippet instead for getting documentation snippets. + + def search_record + [ + @name, + full_name, + @name, + @parent.full_name, + path, + params, + search_snippet, + ] + end - ## - # Returns an HTML snippet of the comment for search results. + ## + # Returns an HTML snippet of the comment for search results. - def search_snippet - return '' if @comment.empty? + def search_snippet + return '' if @comment.empty? - snippet(@comment) - end + snippet(@comment) + end - def to_s # :nodoc: - if @is_alias_for - "#{self.class.name}: #{full_name} -> #{is_alias_for}" - else - "#{self.class.name}: #{full_name}" + def to_s # :nodoc: + if @is_alias_for + "#{self.class.name}: #{full_name} -> #{is_alias_for}" + else + "#{self.class.name}: #{full_name}" + end end - end - def name_ord_range # :nodoc: - case name.ord - when 0..64 # anything below "A" - 1 - when 91..96 # the symbols between "Z" and "a" - 2 - when 123..126 # 7-bit symbols above "z": "{", "|", "}", "~" - 3 - else # everythig else can be sorted as normal - 4 + def name_ord_range # :nodoc: + case name.ord + when 0..64 # anything below "A" + 1 + when 91..96 # the symbols between "Z" and "a" + 2 + when 123..126 # 7-bit symbols above "z": "{", "|", "}", "~" + 3 + else # everythig else can be sorted as normal + 4 + end end end end diff --git a/lib/rdoc/code_object/mixin.rb b/lib/rdoc/code_object/mixin.rb index 39c8311b2f..df2588b438 100644 --- a/lib/rdoc/code_object/mixin.rb +++ b/lib/rdoc/code_object/mixin.rb @@ -1,123 +1,125 @@ # frozen_string_literal: true -## -# A Mixin adds features from a module into another context. RDoc::Include and -# RDoc::Extend are both mixins. +module RDoc + ## + # A Mixin adds features from a module into another context. RDoc::Include and + # RDoc::Extend are both mixins. -class RDoc::Mixin < RDoc::CodeObject + class Mixin < ::RDoc::CodeObject - ## - # Name of included module + ## + # Name of included module - attr_accessor :name + attr_accessor :name - ## - # Creates a new Mixin for +name+ with +comment+ + ## + # Creates a new Mixin for +name+ with +comment+ - def initialize(name, comment) - super() - @name = name - self.comment = comment - @module = nil # cache for module if found - end + def initialize(name, comment) + super() + @name = name + self.comment = comment + @module = nil # cache for module if found + end - ## - # Mixins are sorted by name + ## + # Mixins are sorted by name - def <=>(other) - return unless self.class === other + def <=>(other) + return unless self.class === other - name <=> other.name - end + name <=> other.name + end - def ==(other) # :nodoc: - self.class === other and @name == other.name - end + def ==(other) # :nodoc: + self.class === other and @name == other.name + end - alias eql? == # :nodoc: + alias eql? == # :nodoc: - ## - # Full name based on #module + ## + # Full name based on #module - def full_name - m = self.module - RDoc::ClassModule === m ? m.full_name : @name - end + def full_name + m = self.module + ::RDoc::ClassModule === m ? m.full_name : @name + end - def hash # :nodoc: - [@name, self.module].hash - end + def hash # :nodoc: + [@name, self.module].hash + end - def inspect # :nodoc: - "#<%s:0x%x %s.%s %s>" % [ - self.class, - object_id, - parent_name, self.class.name.downcase, @name, - ] - end + def inspect # :nodoc: + "#<%s:0x%x %s.%s %s>" % [ + self.class, + object_id, + parent_name, self.class.name.downcase, @name, + ] + end - ## - # Attempts to locate the included module object. Returns the name if not - # known. - # - # The scoping rules of Ruby to resolve the name of an included module are: - # - first look into the children of the current context; - # - if not found, look into the children of included modules, - # in reverse inclusion order; - # - if still not found, go up the hierarchy of names. - # - # This method has O(n!) behavior when the module calling - # include is referencing nonexistent modules. Avoid calling #module until - # after all the files are parsed. This behavior is due to ruby's constant - # lookup behavior. - # - # As of the beginning of October, 2011, no gem includes nonexistent modules. - # - # The Ruby parser passes an already-resolved full-path +name+, so most of this - # logic only runs for the C parser, which passes the unresolved local name. - - 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.includes.take_while { |i| i != self }.reverse - searched.each do |i| - inc = i.module - next if String === inc - full_name = inc.child_name(@name) - @module = @store.modules_hash[full_name] + ## + # Attempts to locate the included module object. Returns the name if not + # known. + # + # The scoping rules of Ruby to resolve the name of an included module are: + # - first look into the children of the current context; + # - if not found, look into the children of included modules, + # in reverse inclusion order; + # - if still not found, go up the hierarchy of names. + # + # This method has O(n!) behavior when the module calling + # include is referencing nonexistent modules. Avoid calling #module until + # after all the files are parsed. This behavior is due to ruby's constant + # lookup behavior. + # + # As of the beginning of October, 2011, no gem includes nonexistent modules. + # + # The Ruby parser passes an already-resolved full-path +name+, so most of this + # logic only runs for the C parser, which passes the unresolved local name. + + def module return @module if @module - end - # go up the hierarchy of names - up = parent.parent - while up - full_name = up.child_name(@name) + # search the current context + return @name unless parent + full_name = parent.child_name(@name) @module = @store.modules_hash[full_name] return @module if @module - up = up.parent + return @name if @name =~ /^::/ + + # search the includes before this one, in reverse order + searched = parent.includes.take_while { |i| i != self }.reverse + searched.each do |i| + inc = i.module + next if String === inc + full_name = inc.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 - @name - end + ## + # Sets the store for this class or module and its contained code objects. - ## - # Sets the store for this class or module and its contained code objects. + def store=(store) + super - def store=(store) - super + @file = @store.add_file @file.full_name if @file + end - @file = @store.add_file @file.full_name if @file - end + def to_s # :nodoc: + "#{self.class.name.downcase} #@name in: #{parent}" + end - def to_s # :nodoc: - "#{self.class.name.downcase} #@name in: #{parent}" end - end diff --git a/lib/rdoc/code_object/normal_class.rb b/lib/rdoc/code_object/normal_class.rb index 6f648ea339..527a812fab 100644 --- a/lib/rdoc/code_object/normal_class.rb +++ b/lib/rdoc/code_object/normal_class.rb @@ -1,89 +1,91 @@ # frozen_string_literal: true -## -# A normal class, neither singleton nor anonymous - -class RDoc::NormalClass < RDoc::ClassModule - +module RDoc ## - # The ancestors of this class including modules. Unlike Module#ancestors, - # this class is not included in the result. The result will contain both - # RDoc::ClassModules and Strings. - - def ancestors - ancestors = included_ancestors - super_classes.each do |sclass| - ancestors << sclass - ancestors.concat sclass.included_ancestors unless String === sclass + # A normal class, neither singleton nor anonymous + + class NormalClass < ::RDoc::ClassModule + + ## + # The ancestors of this class including modules. Unlike Module#ancestors, + # this class is not included in the result. The result will contain both + # RDoc::ClassModules and Strings. + + def ancestors + ancestors = included_ancestors + super_classes.each do |sclass| + ancestors << sclass + ancestors.concat sclass.included_ancestors unless String === sclass + end + ancestors end - ancestors - end - def aref_prefix # :nodoc: - 'class' - end + def aref_prefix # :nodoc: + 'class' + end - ## - # The definition of this class, class MyClassName + ## + # The definition of this class, class MyClassName - def definition - "class #{full_name}" - end + def definition + "class #{full_name}" + end - def direct_ancestors - superclass ? super + [superclass] : super - end + def direct_ancestors + superclass ? super + [superclass] : super + end - def inspect # :nodoc: - superclass = @superclass ? " < #{@superclass}" : nil - "<%s:0x%x class %s%s includes: %p extends: %p attributes: %p methods: %p aliases: %p>" % [ - self.class, object_id, - full_name, superclass, @includes, @extends, @attributes, @method_list, @aliases - ] - end + def inspect # :nodoc: + superclass = @superclass ? " < #{@superclass}" : nil + "<%s:0x%x class %s%s includes: %p extends: %p attributes: %p methods: %p aliases: %p>" % [ + self.class, object_id, + full_name, superclass, @includes, @extends, @attributes, @method_list, @aliases + ] + end - def to_s # :nodoc: - display = "#{self.class.name} #{self.full_name}" - if superclass - display += ' < ' + (superclass.is_a?(String) ? superclass : superclass.full_name) + def to_s # :nodoc: + display = "#{self.class.name} #{self.full_name}" + if superclass + display += ' < ' + (superclass.is_a?(String) ? superclass : superclass.full_name) + end + display += ' -> ' + is_alias_for.to_s if is_alias_for + display end - display += ' -> ' + is_alias_for.to_s if is_alias_for - display - end - def pretty_print(q) # :nodoc: - superclass = @superclass ? " < #{@superclass}" : nil - - q.group 2, "[class #{full_name}#{superclass}", "]" do - q.breakable - q.text "includes:" - q.breakable - q.seplist @includes do |inc| q.pp inc end - - q.breakable - q.text "constants:" - q.breakable - q.seplist @constants do |const| q.pp const end - - q.breakable - q.text "attributes:" - q.breakable - q.seplist @attributes do |attr| q.pp attr end - - q.breakable - q.text "methods:" - q.breakable - q.seplist @method_list do |meth| q.pp meth end - - q.breakable - q.text "aliases:" - q.breakable - q.seplist @aliases do |aliaz| q.pp aliaz end - - q.breakable - q.text "comment:" - q.breakable - q.pp comment + def pretty_print(q) # :nodoc: + superclass = @superclass ? " < #{@superclass}" : nil + + q.group 2, "[class #{full_name}#{superclass}", "]" do + q.breakable + q.text "includes:" + q.breakable + q.seplist @includes do |inc| q.pp inc end + + q.breakable + q.text "constants:" + q.breakable + q.seplist @constants do |const| q.pp const end + + q.breakable + q.text "attributes:" + q.breakable + q.seplist @attributes do |attr| q.pp attr end + + q.breakable + q.text "methods:" + q.breakable + q.seplist @method_list do |meth| q.pp meth end + + q.breakable + q.text "aliases:" + q.breakable + q.seplist @aliases do |aliaz| q.pp aliaz end + + q.breakable + q.text "comment:" + q.breakable + q.pp comment + end end - end + end end diff --git a/lib/rdoc/code_object/normal_module.rb b/lib/rdoc/code_object/normal_module.rb index 677a9dc3bd..ade8a0ffaf 100644 --- a/lib/rdoc/code_object/normal_module.rb +++ b/lib/rdoc/code_object/normal_module.rb @@ -1,73 +1,75 @@ # frozen_string_literal: true -## -# A normal module, like NormalClass +module RDoc + ## + # A normal module, like NormalClass -class RDoc::NormalModule < RDoc::ClassModule + class NormalModule < ::RDoc::ClassModule - def aref_prefix # :nodoc: - 'module' - end + def aref_prefix # :nodoc: + 'module' + end - def inspect # :nodoc: - "#<%s:0x%x module %s includes: %p extends: %p attributes: %p methods: %p aliases: %p>" % [ - self.class, object_id, - full_name, @includes, @extends, @attributes, @method_list, @aliases - ] - end + def inspect # :nodoc: + "#<%s:0x%x module %s includes: %p extends: %p attributes: %p methods: %p aliases: %p>" % [ + self.class, object_id, + full_name, @includes, @extends, @attributes, @method_list, @aliases + ] + end - ## - # The definition of this module, module MyModuleName + ## + # The definition of this module, module MyModuleName - def definition - "module #{full_name}" - end + def definition + "module #{full_name}" + end - ## - # This is a module, returns true + ## + # This is a module, returns true - def module? - true - end + def module? + true + end - def pretty_print(q) # :nodoc: - q.group 2, "[module #{full_name}:", "]" do - q.breakable - q.text "includes:" - q.breakable - q.seplist @includes do |inc| q.pp inc end - q.breakable - - q.breakable - q.text "constants:" - q.breakable - q.seplist @constants do |const| q.pp const end - - q.text "attributes:" - q.breakable - q.seplist @attributes do |attr| q.pp attr end - q.breakable - - q.text "methods:" - q.breakable - q.seplist @method_list do |meth| q.pp meth end - q.breakable - - q.text "aliases:" - q.breakable - q.seplist @aliases do |aliaz| q.pp aliaz end - q.breakable - - q.text "comment:" - q.breakable - q.pp comment + def pretty_print(q) # :nodoc: + q.group 2, "[module #{full_name}:", "]" do + q.breakable + q.text "includes:" + q.breakable + q.seplist @includes do |inc| q.pp inc end + q.breakable + + q.breakable + q.text "constants:" + q.breakable + q.seplist @constants do |const| q.pp const end + + q.text "attributes:" + q.breakable + q.seplist @attributes do |attr| q.pp attr end + q.breakable + + q.text "methods:" + q.breakable + q.seplist @method_list do |meth| q.pp meth end + q.breakable + + q.text "aliases:" + q.breakable + q.seplist @aliases do |aliaz| q.pp aliaz end + q.breakable + + q.text "comment:" + q.breakable + q.pp comment + end end - end - ## - # Modules don't have one, raises NoMethodError + ## + # Modules don't have one, raises NoMethodError - def superclass - raise NoMethodError, "#{full_name} is a module" - end + def superclass + raise NoMethodError, "#{full_name} is a module" + end + end end diff --git a/lib/rdoc/code_object/require.rb b/lib/rdoc/code_object/require.rb index d65cd961ba..9d0e9c23c5 100644 --- a/lib/rdoc/code_object/require.rb +++ b/lib/rdoc/code_object/require.rb @@ -1,51 +1,53 @@ # frozen_string_literal: true -## -# A file loaded by \#require +module RDoc + ## + # A file loaded by \#require -class RDoc::Require < RDoc::CodeObject + class Require < ::RDoc::CodeObject - ## - # Name of the required file + ## + # Name of the required file - attr_accessor :name + attr_accessor :name - ## - # Creates a new Require that loads +name+ with +comment+ + ## + # Creates a new Require that loads +name+ with +comment+ - def initialize(name, comment) - super() - @name = name.gsub(/'|"/, "") #' - @top_level = nil - self.comment = comment - end + def initialize(name, comment) + super() + @name = name.gsub(/'|"/, "") #' + @top_level = nil + self.comment = comment + end - def inspect # :nodoc: - "#<%s:0x%x require '%s' in %s>" % [ - self.class, - object_id, - @name, - @parent ? @parent.base_name : '(unknown)' - ] - end + def inspect # :nodoc: + "#<%s:0x%x require '%s' in %s>" % [ + self.class, + object_id, + @name, + @parent ? @parent.base_name : '(unknown)' + ] + end - def to_s # :nodoc: - "require #{name} in: #{parent}" - end + def to_s # :nodoc: + "require #{name} in: #{parent}" + end - ## - # The RDoc::TopLevel corresponding to this require, or +nil+ if not found. + ## + # The RDoc::TopLevel corresponding to this require, or +nil+ if not found. - def top_level - @top_level ||= begin - tl = RDoc::TopLevel.all_files_hash[name + '.rb'] + def top_level + @top_level ||= begin + tl = ::RDoc::TopLevel.all_files_hash[name + '.rb'] - if tl.nil? and RDoc::TopLevel.all_files.first.full_name =~ %r(^lib/) - # second chance - tl = RDoc::TopLevel.all_files_hash['lib/' + name + '.rb'] - end + if tl.nil? and ::RDoc::TopLevel.all_files.first.full_name =~ %r(^lib/) + # second chance + tl = ::RDoc::TopLevel.all_files_hash['lib/' + name + '.rb'] + end - tl + tl + end end - end + end end diff --git a/lib/rdoc/code_object/single_class.rb b/lib/rdoc/code_object/single_class.rb index 88a93a0c5f..6cef965bab 100644 --- a/lib/rdoc/code_object/single_class.rb +++ b/lib/rdoc/code_object/single_class.rb @@ -1,30 +1,32 @@ # frozen_string_literal: true -## -# A singleton class +module RDoc + ## + # A singleton class -class RDoc::SingleClass < RDoc::ClassModule + class SingleClass < ::RDoc::ClassModule - ## - # Adds the superclass to the included modules. + ## + # Adds the superclass to the included modules. - def ancestors - superclass ? super + [superclass] : super - end + def ancestors + superclass ? super + [superclass] : super + end - def aref_prefix # :nodoc: - 'sclass' - end + def aref_prefix # :nodoc: + 'sclass' + end - ## - # The definition of this singleton class, class << MyClassName + ## + # The definition of this singleton class, class << MyClassName - def definition - "class << #{full_name}" - end + def definition + "class << #{full_name}" + end - def pretty_print(q) # :nodoc: - q.group 2, "[class << #{full_name}", "]" do - next + def pretty_print(q) # :nodoc: + q.group 2, "[class << #{full_name}", "]" do + next + end end end end diff --git a/lib/rdoc/code_object/top_level.rb b/lib/rdoc/code_object/top_level.rb index 0cc98c0469..5a2ea84d29 100644 --- a/lib/rdoc/code_object/top_level.rb +++ b/lib/rdoc/code_object/top_level.rb @@ -1,286 +1,288 @@ # frozen_string_literal: true -## -# A TopLevel context is a representation of the contents of a single file +module RDoc + ## + # A TopLevel context is a representation of the contents of a single file -class RDoc::TopLevel < RDoc::Context + class TopLevel < ::RDoc::Context - MARSHAL_VERSION = 0 # :nodoc: + MARSHAL_VERSION = 0 # :nodoc: - ## - # Relative name of this file + ## + # Relative name of this file - attr_accessor :relative_name + attr_accessor :relative_name - ## - # Absolute name of this file + ## + # Absolute name of this file - attr_accessor :absolute_name + attr_accessor :absolute_name - ## - # Base name of this file + ## + # Base name of this file - attr_reader :base_name + attr_reader :base_name - ## - # Base name of this file without the extension + ## + # Base name of this file without the extension - attr_reader :page_name + attr_reader :page_name - ## - # All the classes or modules that were declared in - # this file. These are assigned to either +#classes_hash+ - # or +#modules_hash+ once we know what they really are. + ## + # All the classes or modules that were declared in + # this file. These are assigned to either +#classes_hash+ + # or +#modules_hash+ once we know what they really are. - attr_reader :classes_or_modules + attr_reader :classes_or_modules - ## - # The parser class that processed this file + ## + # The parser class that processed this file - attr_reader :parser + attr_reader :parser - ## - # Creates a new TopLevel for the file at +absolute_name+. If documentation - # is being generated outside the source dir +relative_name+ is relative to - # the source directory. - - def initialize(absolute_name, relative_name = absolute_name) - super() - @name = nil - @absolute_name = absolute_name - @relative_name = relative_name - @parser = nil - - if relative_name - @base_name = File.basename(relative_name) - @page_name = @base_name.sub(/\.(rb|rdoc|txt|md)\z/i, '') - else - @base_name = nil - @page_name = nil + ## + # Creates a new TopLevel for the file at +absolute_name+. If documentation + # is being generated outside the source dir +relative_name+ is relative to + # the source directory. + + def initialize(absolute_name, relative_name = absolute_name) + super() + @name = nil + @absolute_name = absolute_name + @relative_name = relative_name + @parser = nil + + if relative_name + @base_name = File.basename(relative_name) + @page_name = @base_name.sub(/\.(rb|rdoc|txt|md)\z/i, '') + else + @base_name = nil + @page_name = nil + end + + @classes_or_modules = [] end - @classes_or_modules = [] - end + ## + # Sets the parser for this toplevel context, also the store. - ## - # Sets the parser for this toplevel context, also the store. + def parser=(val) + @parser = val + @store&.cache_text_file(relative_name) + @parser + end - def parser=(val) - @parser = val - @store&.cache_text_file(relative_name) - @parser - end + ## + # An RDoc::TopLevel is equal to another with the same relative_name - ## - # An RDoc::TopLevel is equal to another with the same relative_name + def ==(other) + self.class === other and @relative_name == other.relative_name + end - def ==(other) - self.class === other and @relative_name == other.relative_name - end + alias eql? == - alias eql? == + ## + # Adds +an_alias+ to +Object+ instead of +self+. - ## - # Adds +an_alias+ to +Object+ instead of +self+. + def add_alias(an_alias) + object_class.record_location self + return an_alias unless @document_self + object_class.add_alias an_alias + end - def add_alias(an_alias) - object_class.record_location self - return an_alias unless @document_self - object_class.add_alias an_alias - end + ## + # Adds +constant+ to +Object+ instead of +self+. - ## - # Adds +constant+ to +Object+ instead of +self+. + def add_constant(constant) + object_class.record_location self + return constant unless @document_self + object_class.add_constant constant + end - def add_constant(constant) - object_class.record_location self - return constant unless @document_self - object_class.add_constant constant - end + ## + # Adds +include+ to +Object+ instead of +self+. - ## - # Adds +include+ to +Object+ instead of +self+. + def add_include(include) + object_class.record_location self + return include unless @document_self + object_class.add_include include + end - def add_include(include) - object_class.record_location self - return include unless @document_self - object_class.add_include include - end + ## + # Adds +method+ to +Object+ instead of +self+. - ## - # Adds +method+ to +Object+ instead of +self+. + def add_method(method) + object_class.record_location self + return method unless @document_self + object_class.add_method method + end - def add_method(method) - object_class.record_location self - return method unless @document_self - object_class.add_method method - end + ## + # Adds class or module +mod+. Used in the building phase + # by the Ruby parser. - ## - # Adds class or module +mod+. Used in the building phase - # by the Ruby parser. + def add_to_classes_or_modules(mod) + @classes_or_modules << mod + end - def add_to_classes_or_modules(mod) - @classes_or_modules << mod - end + alias name base_name - alias name base_name + ## + # See RDoc::TopLevel::find_class_or_module + #-- + # TODO Why do we search through all classes/modules found, not just the + # ones of this instance? - ## - # See RDoc::TopLevel::find_class_or_module - #-- - # TODO Why do we search through all classes/modules found, not just the - # ones of this instance? + def find_class_or_module(name) + @store.find_class_or_module name + end - def find_class_or_module(name) - @store.find_class_or_module name - end + ## + # Finds a class or module named +symbol+ - ## - # Finds a class or module named +symbol+ + def find_local_symbol(symbol) + find_class_or_module(symbol) || super + end - def find_local_symbol(symbol) - find_class_or_module(symbol) || super - end + ## + # Finds a module or class with +name+ - ## - # Finds a module or class with +name+ + def find_module_named(name) + find_class_or_module(name) + end - def find_module_named(name) - find_class_or_module(name) - end + alias get_module_named find_module_named - alias get_module_named find_module_named + ## + # Returns the relative name of this file - ## - # Returns the relative name of this file + def full_name + @relative_name + end - def full_name - @relative_name - end + ## + # An RDoc::TopLevel has the same hash as another with the same + # relative_name - ## - # An RDoc::TopLevel has the same hash as another with the same - # relative_name + def hash + @relative_name.hash + end - def hash - @relative_name.hash - end + ## + # URL for this with a +prefix+ - ## - # URL for this with a +prefix+ + def http_url + @relative_name.tr('.', '_') + '.html' + end - def http_url - @relative_name.tr('.', '_') + '.html' - end + def inspect # :nodoc: + "#<%s:0x%x %p modules: %p classes: %p>" % [ + self.class, object_id, + base_name, + @modules.map { |n, m| m }, + @classes.map { |n, c| c } + ] + end - def inspect # :nodoc: - "#<%s:0x%x %p modules: %p classes: %p>" % [ - self.class, object_id, - base_name, - @modules.map { |n, m| m }, - @classes.map { |n, c| c } - ] - end + ## + # Dumps this TopLevel for use by ri. See also #marshal_load - ## - # Dumps this TopLevel for use by ri. See also #marshal_load - - def marshal_dump - [ - MARSHAL_VERSION, - @relative_name, - @parser, - parse(@comment), - ] - end + def marshal_dump + [ + MARSHAL_VERSION, + @relative_name, + @parser, + parse(@comment), + ] + end - ## - # Loads this TopLevel from +array+. + ## + # Loads this TopLevel from +array+. - def marshal_load(array) # :nodoc: - initialize array[1] + def marshal_load(array) # :nodoc: + initialize array[1] - @parser = array[2] - @comment = RDoc::Comment.from_document array[3] - end + @parser = array[2] + @comment = ::RDoc::Comment.from_document array[3] + end - ## - # Returns the NormalClass "Object", creating it if not found. - # - # Records +self+ as a location in "Object". - - def object_class - @object_class ||= begin - oc = @store.find_class_named('Object') || add_class(RDoc::NormalClass, 'Object') - oc.record_location self - oc + ## + # Returns the NormalClass "Object", creating it if not found. + # + # Records +self+ as a location in "Object". + + def object_class + @object_class ||= begin + oc = @store.find_class_named('Object') || add_class(::RDoc::NormalClass, 'Object') + oc.record_location self + oc + end end - end - ## - # Path to this file for use with HTML generator output. - - def path - base = if options.main_page == full_name - 'index.html' - else - http_url - end - - prefix = options.file_path_prefix - return base unless prefix - File.join(prefix, base) - end + ## + # Path to this file for use with HTML generator output. - def pretty_print(q) # :nodoc: - q.group 2, "[#{self.class}: ", "]" do - q.text "base name: #{base_name.inspect}" - q.breakable + def path + base = if options.main_page == full_name + 'index.html' + else + http_url + end - items = @modules.map { |n, m| m } - items.concat @modules.map { |n, c| c } - q.seplist items do |mod| q.pp mod end + prefix = options.file_path_prefix + return base unless prefix + File.join(prefix, base) end - end - ## - # Search record used by RDoc::Generator::JsonIndex - # - # TODO: Remove this method after dropping the darkfish theme and JsonIndex generator. - # Use #search_snippet instead for getting documentation snippets. - - def search_record - return unless @parser < RDoc::Parser::Text - - [ - page_name, - '', - page_name, - '', - path, - '', - search_snippet, - ] - end + def pretty_print(q) # :nodoc: + q.group 2, "[#{self.class}: ", "]" do + q.text "base name: #{base_name.inspect}" + q.breakable - ## - # Returns an HTML snippet of the comment for search results. + items = @modules.map { |n, m| m } + items.concat @modules.map { |n, c| c } + q.seplist items do |mod| q.pp mod end + end + end - def search_snippet - return '' if @comment.empty? + ## + # Search record used by RDoc::Generator::JsonIndex + # + # TODO: Remove this method after dropping the darkfish theme and JsonIndex generator. + # Use #search_snippet instead for getting documentation snippets. + + def search_record + return unless @parser < ::RDoc::Parser::Text + + [ + page_name, + '', + page_name, + '', + path, + '', + search_snippet, + ] + end - snippet(@comment) - end + ## + # Returns an HTML snippet of the comment for search results. - ## - # Is this TopLevel from a text file instead of a source code file? + def search_snippet + return '' if @comment.empty? - def text? - @parser and @parser.include? RDoc::Parser::Text - end + snippet(@comment) + end - def to_s # :nodoc: - "file #{full_name}" - end + ## + # Is this TopLevel from a text file instead of a source code file? + def text? + @parser and @parser.include? ::RDoc::Parser::Text + end + + def to_s # :nodoc: + "file #{full_name}" + end + + end end diff --git a/lib/rdoc/comment.rb b/lib/rdoc/comment.rb index b50193d56c..af17c62dc8 100644 --- a/lib/rdoc/comment.rb +++ b/lib/rdoc/comment.rb @@ -1,352 +1,354 @@ # frozen_string_literal: true -## -# 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 Reference}[rdoc-ref:doc/markup_reference/rdoc.rdoc@Directive+for+Specifying+RDoc+Source+Format]. +module RDoc + ## + # 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 Reference}[rdoc-ref:doc/markup_reference/rdoc.rdoc@Directive+for+Specifying+RDoc+Source+Format]. -class RDoc::Comment + class Comment - include RDoc::Text + include ::RDoc::Text - ## - # The format of this comment. Defaults to RDoc::Markup + ## + # The format of this comment. Defaults to RDoc::Markup - attr_reader :format + attr_reader :format - ## - # The RDoc::TopLevel this comment was found in + ## + # The RDoc::TopLevel this comment was found in - attr_accessor :location + attr_accessor :location - ## - # Line where this Comment was written + ## + # Line where this Comment was written - attr_accessor :line + attr_accessor :line - ## - # For duck-typing when merging classes at load time + ## + # For duck-typing when merging classes at load time - alias file location # :nodoc: + alias file location # :nodoc: - ## - # The text for this comment + ## + # The text for this comment - attr_reader :text + attr_reader :text - ## - # Alias for text + ## + # Alias for text - alias to_s text + alias to_s text - ## - # Overrides the content returned by #parse. Use when there is no #text - # source for this comment + ## + # Overrides the content returned by #parse. Use when there is no #text + # source for this comment - attr_writer :document + attr_writer :document - ## - # Creates a new comment with +text+ that is found in the RDoc::TopLevel - # +location+. - - def initialize(text = nil, location = nil, language = nil) - @location = location - @text = text.nil? ? nil : text.dup - @language = language + ## + # Creates a new comment with +text+ that is found in the RDoc::TopLevel + # +location+. - @document = nil - @format = 'rdoc' - @normalized = false - end + def initialize(text = nil, location = nil, language = nil) + @location = location + @text = text.nil? ? nil : text.dup + @language = language - ## - #-- - # TODO deep copy @document + @document = nil + @format = 'rdoc' + @normalized = false + end - def initialize_copy(copy) # :nodoc: - @text = copy.text.dup - end + ## + #-- + # TODO deep copy @document - def ==(other) # :nodoc: - self.class === other and - other.text == @text and other.location == @location - end + def initialize_copy(copy) # :nodoc: + @text = copy.text.dup + end - ## - # A comment is empty if its text String is empty. + def ==(other) # :nodoc: + self.class === other and + other.text == @text and other.location == @location + end - def empty? - @text.empty? && (@document.nil? || @document.empty?) - end + ## + # A comment is empty if its text String is empty. - ## - # HACK dubious + def empty? + @text.empty? && (@document.nil? || @document.empty?) + end - def encode!(encoding) - @text = String.new @text, encoding: encoding - self - end + ## + # HACK dubious - ## - # Sets the format of this comment and resets any parsed document + def encode!(encoding) + @text = String.new @text, encoding: encoding + self + end - def format=(format) - @format = format - @document = nil - end + ## + # Sets the format of this comment and resets any parsed document - def inspect # :nodoc: - location = @location ? @location.relative_name : '(unknown)' + def format=(format) + @format = format + @document = nil + end - "#<%s:%x %s %p>" % [self.class, object_id, location, @text] - end + def inspect # :nodoc: + location = @location ? @location.relative_name : '(unknown)' - ## - # Normalizes the text. See RDoc::Text#normalize_comment for details + "#<%s:%x %s %p>" % [self.class, object_id, location, @text] + end - def normalize - return self unless @text - return self if @normalized # TODO eliminate duplicate normalization + ## + # Normalizes the text. See RDoc::Text#normalize_comment for details - @text = normalize_comment @text + def normalize + return self unless @text + return self if @normalized # TODO eliminate duplicate normalization - @normalized = true + @text = normalize_comment @text - self - end + @normalized = true - # Change normalized, when creating already normalized comment. + self + end - def normalized=(value) - @normalized = value - end + # Change normalized, when creating already normalized comment. - ## - # Was this text normalized? + def normalized=(value) + @normalized = value + end - def normalized? # :nodoc: - @normalized - end + ## + # Was this text normalized? - ## - # Parses the comment into an RDoc::Markup::Document. The parsed document is - # cached until the text is changed. + def normalized? # :nodoc: + @normalized + end - def parse - return @document if @document + ## + # Parses the comment into an RDoc::Markup::Document. The parsed document is + # cached until the text is changed. - @document = super @text, @format - @document.file = @location - @document - end + def parse + return @document if @document - ## - # 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. + @document = super @text, @format + @document.file = @location + @document + end - def text=(text) - raise RDoc::Error, 'replacing document-only comment is not allowed' if - @text.nil? and @document + ## + # 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. - @document = nil - @text = text.nil? ? nil : text.dup - end + def text=(text) + raise ::RDoc::Error, 'replacing document-only comment is not allowed' if + @text.nil? and @document - ## - # Returns true if this comment is in TomDoc format. + @document = nil + @text = text.nil? ? nil : text.dup + end - def tomdoc? - @format == 'tomdoc' - end + ## + # Returns true if this comment is in TomDoc format. - MULTILINE_DIRECTIVES = %w[call-seq].freeze # :nodoc: + def tomdoc? + @format == 'tomdoc' + end - # There are more, but already handled by RDoc::Parser::C - COLON_LESS_DIRECTIVES = %w[call-seq Document-method].freeze # :nodoc: + MULTILINE_DIRECTIVES = %w[call-seq].freeze # :nodoc: - DIRECTIVE_OR_ESCAPED_DIRECTIV_REGEXP = /\A(?\\?:|:?)(?[\w-]+):(?.*)/ + # There are more, but already handled by RDoc::Parser::C + COLON_LESS_DIRECTIVES = %w[call-seq Document-method].freeze # :nodoc: - private_constant :MULTILINE_DIRECTIVES, :COLON_LESS_DIRECTIVES, :DIRECTIVE_OR_ESCAPED_DIRECTIV_REGEXP + DIRECTIVE_OR_ESCAPED_DIRECTIV_REGEXP = /\A(?\\?:|:?)(?[\w-]+):(?.*)/ - class << self + private_constant :MULTILINE_DIRECTIVES, :COLON_LESS_DIRECTIVES, :DIRECTIVE_OR_ESCAPED_DIRECTIV_REGEXP - ## - # Create a new parsed comment from a document + class << self - def from_document(document) # :nodoc: - comment = RDoc::Comment.new('') - comment.document = document - comment.location = RDoc::TopLevel.new(document.file) if document.file - comment - end + ## + # Create a new parsed comment from a document - # Parse comment, collect directives as an attribute and return [normalized_comment_text, directives_hash] - # This method expands include and removes everything not needed in the document text, such as - # private section, directive line, comment characters `# /* * */` and indent spaces. - # - # RDoc comment consists of include, directive, multiline directive, private section and comment text. - # - # Include - # # :include: filename - # - # Directive - # # :directive-without-value: - # # :directive-with-value: value - # - # Multiline directive (only :call-seq:) - # # :multiline-directive: - # # value1 - # # value2 - # - # Private section - # #-- - # # private comment - # #++ - - def parse(text, filename, line_no, type, &include_callback) - case type - when :ruby - text = text.gsub(/^#+/, '') if text.start_with?('#') - private_start_regexp = /^-{2,}$/ - private_end_regexp = /^\+{2}$/ - indent_regexp = /^\s*/ - when :c - private_start_regexp = /^(\s*\*)?-{2,}$/ - private_end_regexp = /^(\s*\*)?\+{2}$/ - indent_regexp = /^\s*(\/\*+|\*)?\s*/ - text = text.gsub(/\s*\*+\/\s*\z/, '') - when :simple - # Unlike other types, this implementation only looks for two dashes at - # the beginning of the line. Three or more dashes are considered to be - # a rule and ignored. - private_start_regexp = /^-{2}$/ - private_end_regexp = /^\+{2}$/ - indent_regexp = /^\s*/ + def from_document(document) # :nodoc: + comment = ::RDoc::Comment.new('') + comment.document = document + comment.location = ::RDoc::TopLevel.new(document.file) if document.file + comment end - directives = {} - lines = text.split("\n") - in_private = false - comment_lines = [] - until lines.empty? - line = lines.shift - read_lines = 1 - if in_private - # If `++` appears in a private section that starts with `--`, private section ends. - in_private = false if line.match?(private_end_regexp) - line_no += read_lines - next - elsif line.match?(private_start_regexp) - # If `--` appears in a line, private section starts. - in_private = true - line_no += read_lines - next + # Parse comment, collect directives as an attribute and return [normalized_comment_text, directives_hash] + # This method expands include and removes everything not needed in the document text, such as + # private section, directive line, comment characters `# /* * */` and indent spaces. + # + # RDoc comment consists of include, directive, multiline directive, private section and comment text. + # + # Include + # # :include: filename + # + # Directive + # # :directive-without-value: + # # :directive-with-value: value + # + # Multiline directive (only :call-seq:) + # # :multiline-directive: + # # value1 + # # value2 + # + # Private section + # #-- + # # private comment + # #++ + + def parse(text, filename, line_no, type, &include_callback) + case type + when :ruby + text = text.gsub(/^#+/, '') if text.start_with?('#') + private_start_regexp = /^-{2,}$/ + private_end_regexp = /^\+{2}$/ + indent_regexp = /^\s*/ + when :c + private_start_regexp = /^(\s*\*)?-{2,}$/ + private_end_regexp = /^(\s*\*)?\+{2}$/ + indent_regexp = /^\s*(\/\*+|\*)?\s*/ + text = text.gsub(/\s*\*+\/\s*\z/, '') + when :simple + # Unlike other types, this implementation only looks for two dashes at + # the beginning of the line. Three or more dashes are considered to be + # a rule and ignored. + private_start_regexp = /^-{2}$/ + private_end_regexp = /^\+{2}$/ + indent_regexp = /^\s*/ end - prefix = line[indent_regexp] - prefix_indent = ' ' * prefix.size - line = line.byteslice(prefix.bytesize..) + directives = {} + lines = text.split("\n") + in_private = false + comment_lines = [] + until lines.empty? + line = lines.shift + read_lines = 1 + if in_private + # If `++` appears in a private section that starts with `--`, private section ends. + in_private = false if line.match?(private_end_regexp) + line_no += read_lines + next + elsif line.match?(private_start_regexp) + # If `--` appears in a line, private section starts. + in_private = true + line_no += read_lines + next + end - if (directive_match = DIRECTIVE_OR_ESCAPED_DIRECTIV_REGEXP.match(line)) - colon = directive_match[:colon] - directive = directive_match[:directive] - raw_param = directive_match[:param] - param = raw_param.strip - else - colon = directive = raw_param = param = nil - end + prefix = line[indent_regexp] + prefix_indent = ' ' * prefix.size + line = line.byteslice(prefix.bytesize..) + + if (directive_match = DIRECTIVE_OR_ESCAPED_DIRECTIV_REGEXP.match(line)) + colon = directive_match[:colon] + directive = directive_match[:directive] + raw_param = directive_match[:param] + param = raw_param.strip + else + colon = directive = raw_param = param = nil + end - if !directive - comment_lines << prefix_indent + line - elsif colon == '\\:' - # If directive is escaped, unescape it - comment_lines << prefix_indent + line.sub('\\:', ':') - elsif raw_param.start_with?(':') || (colon.empty? && !COLON_LESS_DIRECTIVES.include?(directive)) - # Something like `:toto::` is not a directive - # Only few directives allows to start without a colon - comment_lines << prefix_indent + line - elsif directive == 'include' - filename_to_include = param - include_callback.call(filename_to_include, prefix_indent).lines.each { |l| comment_lines << l.chomp } - elsif MULTILINE_DIRECTIVES.include?(directive) - value_lines = take_multiline_directive_value_lines(directive, filename, line_no, lines, prefix_indent.size, indent_regexp, !param.empty?) - read_lines += value_lines.size - lines.shift(value_lines.size) - unless param.empty? - # Accept `:call-seq: first-line\n second-line` for now - value_lines.unshift(param) + if !directive + comment_lines << prefix_indent + line + elsif colon == '\\:' + # If directive is escaped, unescape it + comment_lines << prefix_indent + line.sub('\\:', ':') + elsif raw_param.start_with?(':') || (colon.empty? && !COLON_LESS_DIRECTIVES.include?(directive)) + # Something like `:toto::` is not a directive + # Only few directives allows to start without a colon + comment_lines << prefix_indent + line + elsif directive == 'include' + filename_to_include = param + include_callback.call(filename_to_include, prefix_indent).lines.each { |l| comment_lines << l.chomp } + elsif MULTILINE_DIRECTIVES.include?(directive) + value_lines = take_multiline_directive_value_lines(directive, filename, line_no, lines, prefix_indent.size, indent_regexp, !param.empty?) + read_lines += value_lines.size + lines.shift(value_lines.size) + unless param.empty? + # Accept `:call-seq: first-line\n second-line` for now + value_lines.unshift(param) + end + value = value_lines.join("\n") + directives[directive] = [value.empty? ? nil : value, line_no] + else + directives[directive] = [param.empty? ? nil : param, line_no] end - value = value_lines.join("\n") - directives[directive] = [value.empty? ? nil : value, line_no] - else - directives[directive] = [param.empty? ? nil : param, line_no] + line_no += read_lines end - line_no += read_lines + + normalized_comment = String.new(encoding: text.encoding) << normalize_comment_lines(comment_lines).join("\n") + [normalized_comment, directives] end - normalized_comment = String.new(encoding: text.encoding) << normalize_comment_lines(comment_lines).join("\n") - [normalized_comment, directives] - end + # Remove preceding indent spaces and blank lines from the comment lines + + private def normalize_comment_lines(lines) + blank_line_regexp = /\A\s*\z/ + lines = lines.dup + lines.shift while lines.first&.match?(blank_line_regexp) + lines.pop while lines.last&.match?(blank_line_regexp) - # Remove preceding indent spaces and blank lines from the comment lines - - private def normalize_comment_lines(lines) - blank_line_regexp = /\A\s*\z/ - lines = lines.dup - lines.shift while lines.first&.match?(blank_line_regexp) - lines.pop while lines.last&.match?(blank_line_regexp) - - min_spaces = lines.map do |l| - l.match(/\A *(?=\S)/)&.end(0) - end.compact.min - if min_spaces && min_spaces > 0 - lines.map { |l| l[min_spaces..] || '' } - else - lines + min_spaces = lines.map do |l| + l.match(/\A *(?=\S)/)&.end(0) + end.compact.min + if min_spaces && min_spaces > 0 + lines.map { |l| l[min_spaces..] || '' } + else + lines + end end - end - # Take value lines of multiline directive + # Take value lines of multiline directive - private def take_multiline_directive_value_lines(directive, filename, line_no, lines, base_indent_size, indent_regexp, has_param) - return [] if lines.empty? + private def take_multiline_directive_value_lines(directive, filename, line_no, lines, base_indent_size, indent_regexp, has_param) + return [] if lines.empty? - first_indent_size = lines.first.match(indent_regexp).end(0) + first_indent_size = lines.first.match(indent_regexp).end(0) - # Blank line or unindented line is not part of multiline-directive value - return [] if first_indent_size <= base_indent_size + # Blank line or unindented line is not part of multiline-directive value + return [] if first_indent_size <= base_indent_size - if has_param - # :multiline-directive: line1 - # line2 - # line3 - # - value_lines = lines.take_while do |l| - l.rstrip.match(indent_regexp).end(0) > base_indent_size - end - min_indent = value_lines.map { |l| l.match(indent_regexp).end(0) }.min - value_lines.map { |l| l[min_indent..] } - else - # Take indented lines accepting blank lines between them - value_lines = lines.take_while do |l| - l = l.rstrip - indent = l[indent_regexp] - if indent == l || indent.size >= first_indent_size - true + if has_param + # :multiline-directive: line1 + # line2 + # line3 + # + value_lines = lines.take_while do |l| + l.rstrip.match(indent_regexp).end(0) > base_indent_size end - end - value_lines.map! { |l| (l[first_indent_size..] || '').chomp } + min_indent = value_lines.map { |l| l.match(indent_regexp).end(0) }.min + value_lines.map { |l| l[min_indent..] } + else + # Take indented lines accepting blank lines between them + value_lines = lines.take_while do |l| + l = l.rstrip + indent = l[indent_regexp] + if indent == l || indent.size >= first_indent_size + true + end + end + value_lines.map! { |l| (l[first_indent_size..] || '').chomp } - if value_lines.size != lines.size && !value_lines.last.empty? - warn "#{filename}:#{line_no} Multiline directive :#{directive}: should end with a blank line." + if value_lines.size != lines.size && !value_lines.last.empty? + warn "#{filename}:#{line_no} Multiline directive :#{directive}: should end with a blank line." + end + value_lines.pop while value_lines.last&.empty? + value_lines end - value_lines.pop while value_lines.last&.empty? - value_lines end end end diff --git a/lib/rdoc/cross_reference.rb b/lib/rdoc/cross_reference.rb index 1858877f0f..e85327f7a1 100644 --- a/lib/rdoc/cross_reference.rb +++ b/lib/rdoc/cross_reference.rb @@ -1,215 +1,217 @@ # frozen_string_literal: true -## -# RDoc::CrossReference is a reusable way to create cross references for names. - -class RDoc::CrossReference - - ## - # Regular expression to match class references - # - # 1. There can be a '\\' in front of text to suppress the cross-reference - # 2. There can be a '::' in front of class names to reference from the - # top-level namespace. - # 3. The method can be followed by parenthesis (not recommended) - - CLASS_REGEXP_STR = '\\\\?((?:\:{2})?[A-Z]\w*(?:\:\:\w+)*)' - - ## - # Regular expression to match a single method argument. - - METHOD_ARG_REGEXP_STR = '[\w.+*/=<>-]+' - - ## - # Regular expression to match method arguments. - - METHOD_ARGS_REGEXP_STR = /(?:\((?:#{METHOD_ARG_REGEXP_STR}(?:,\s*#{METHOD_ARG_REGEXP_STR})*)?\))?/.source - - ## - # Regular expression to match method references. - # - # See CLASS_REGEXP_STR - - METHOD_REGEXP_STR = /( - (?!\d)[\w]+[!?=]?| - %|=(?:==?|~)|![=~]|\[\]=?|<(?:<|=>?)?|>[>=]?|[-+!]@?|\*\*?|[\/%\`|&^~] - )#{METHOD_ARGS_REGEXP_STR}/.source.delete("\n ").freeze - - ## - # Regular expressions matching text that should potentially have - # cross-reference links generated are passed to add_regexp_handling. Note - # that these expressions are meant to pick up text for which cross-references - # have been suppressed, since the suppression characters are removed by the - # code that is triggered. - - CROSSREF_REGEXP = /(?:^|[\s()]) - ( - (?: - # A::B::C.meth - #{CLASS_REGEXP_STR}(?:[.#]|::)#{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) - - # Stand-alone method (preceded by a #) - | \\?\##{METHOD_REGEXP_STR} - - # Stand-alone method (preceded by ::) - | ::#{METHOD_REGEXP_STR} - - # 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<] - ) - - # labels for headings - (?:@[\w+%-]+(?:\.[\w|%-]+)?)? - )/x - - ## - # Version of CROSSREF_REGEXP used when --hyperlink-all is specified. - - ALL_CROSSREF_REGEXP = / - (?:^|[\s()]) - ( - (?: - # A::B::C.meth - #{CLASS_REGEXP_STR}(?:[.#]|::)#{METHOD_REGEXP_STR} - - # A::B::C - | #{CLASS_REGEXP_STR}(?=[@\s).?!,;<\000]|\z) - - # Stand-alone method - | \\?#{METHOD_REGEXP_STR} - - # Things that look like filenames - | (?:\.\.\/)*[-\/\w]+[_\/.][-\w\/.]+ - - # Things that have markup suppressed - | \\[^\s<] - ) - - # labels for headings - (?:@[\w+%-]+)? - )/x - +module RDoc ## - # Hash of references that have been looked-up to their replacements - - attr_accessor :seen - - ## - # Allows cross-references to be created based on the given +context+ - # (RDoc::Context). - - def initialize(context) - @context = context - @store = context.store - - @seen = {} - end + # RDoc::CrossReference is a reusable way to create cross references for names. + + class CrossReference + + ## + # Regular expression to match class references + # + # 1. There can be a '\\' in front of text to suppress the cross-reference + # 2. There can be a '::' in front of class names to reference from the + # top-level namespace. + # 3. The method can be followed by parenthesis (not recommended) + + CLASS_REGEXP_STR = '\\\\?((?:\:{2})?[A-Z]\w*(?:\:\:\w+)*)' + + ## + # Regular expression to match a single method argument. + + METHOD_ARG_REGEXP_STR = '[\w.+*/=<>-]+' + + ## + # Regular expression to match method arguments. + + METHOD_ARGS_REGEXP_STR = /(?:\((?:#{METHOD_ARG_REGEXP_STR}(?:,\s*#{METHOD_ARG_REGEXP_STR})*)?\))?/.source + + ## + # Regular expression to match method references. + # + # See CLASS_REGEXP_STR + + METHOD_REGEXP_STR = /( + (?!\d)[\w]+[!?=]?| + %|=(?:==?|~)|![=~]|\[\]=?|<(?:<|=>?)?|>[>=]?|[-+!]@?|\*\*?|[\/%\`|&^~] + )#{METHOD_ARGS_REGEXP_STR}/.source.delete("\n ").freeze + + ## + # Regular expressions matching text that should potentially have + # cross-reference links generated are passed to add_regexp_handling. Note + # that these expressions are meant to pick up text for which cross-references + # have been suppressed, since the suppression characters are removed by the + # code that is triggered. + + CROSSREF_REGEXP = /(?:^|[\s()]) + ( + (?: + # A::B::C.meth + #{CLASS_REGEXP_STR}(?:[.#]|::)#{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) + + # Stand-alone method (preceded by a #) + | \\?\##{METHOD_REGEXP_STR} + + # Stand-alone method (preceded by ::) + | ::#{METHOD_REGEXP_STR} + + # 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<] + ) + + # labels for headings + (?:@[\w+%-]+(?:\.[\w|%-]+)?)? + )/x + + ## + # Version of CROSSREF_REGEXP used when --hyperlink-all is specified. + + ALL_CROSSREF_REGEXP = / + (?:^|[\s()]) + ( + (?: + # A::B::C.meth + #{CLASS_REGEXP_STR}(?:[.#]|::)#{METHOD_REGEXP_STR} + + # A::B::C + | #{CLASS_REGEXP_STR}(?=[@\s).?!,;<\000]|\z) + + # Stand-alone method + | \\?#{METHOD_REGEXP_STR} + + # Things that look like filenames + | (?:\.\.\/)*[-\/\w]+[_\/.][-\w\/.]+ + + # 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 + + ## + # Allows cross-references to be created based on the given +context+ + # (RDoc::Context). + + def initialize(context) + @context = context + @store = context.store + + @seen = {} + end - ## - # Returns a method, attribute or constant reference to +name+ - # if it exists in the containing context object. It returns - # nil otherwise. - # - # For example, this method would decompose name = 'A::CONSTANT' into a - # container object A and a symbol 'CONSTANT', and it would try to find - # 'CONSTANT' in A. - - def resolve_local_symbol(name) - ref = nil - type = nil - container = nil - - case name - when /#{CLASS_REGEXP_STR}::([A-Z]\w*)\z/o - symbol = $2 - container = @context.find_symbol_module($1) - when /#{CLASS_REGEXP_STR}([.#]|::)#{METHOD_REGEXP_STR}/o - type = $2 - if '.' == type # will find either #method or ::method - symbol = $3 - else - symbol = "#{type}#{$3}" - end - container = @context.find_symbol_module($1) - when /^([.#]|::)#{METHOD_REGEXP_STR}/o - type = $1 - if '.' == type + ## + # Returns a method, attribute or constant reference to +name+ + # if it exists in the containing context object. It returns + # nil otherwise. + # + # For example, this method would decompose name = 'A::CONSTANT' into a + # container object A and a symbol 'CONSTANT', and it would try to find + # 'CONSTANT' in A. + + def resolve_local_symbol(name) + ref = nil + type = nil + container = nil + + case name + when /#{CLASS_REGEXP_STR}::([A-Z]\w*)\z/o symbol = $2 - else - symbol = "#{type}#{$2}" + container = @context.find_symbol_module($1) + when /#{CLASS_REGEXP_STR}([.#]|::)#{METHOD_REGEXP_STR}/o + type = $2 + if '.' == type # will find either #method or ::method + symbol = $3 + else + symbol = "#{type}#{$3}" + end + container = @context.find_symbol_module($1) + when /^([.#]|::)#{METHOD_REGEXP_STR}/o + type = $1 + if '.' == type + symbol = $2 + else + symbol = "#{type}#{$2}" + end + container = @context end - container = @context - end - if container - unless RDoc::TopLevel === container - if '.' == type - if 'new' == symbol # AnyClassName.new will be class method + if container + unless ::RDoc::TopLevel === container + if '.' == type + if 'new' == symbol # AnyClassName.new will be class method + ref = container.find_local_symbol symbol + ref = container.find_ancestor_local_symbol symbol unless ref + else + ref = container.find_local_symbol "::#{symbol}" + ref = container.find_ancestor_local_symbol "::#{symbol}" unless ref + ref = container.find_local_symbol "##{symbol}" unless ref + ref = container.find_ancestor_local_symbol "##{symbol}" unless ref + end + else ref = container.find_local_symbol symbol ref = container.find_ancestor_local_symbol symbol unless ref - else - ref = container.find_local_symbol "::#{symbol}" - ref = container.find_ancestor_local_symbol "::#{symbol}" unless ref - ref = container.find_local_symbol "##{symbol}" unless ref - ref = container.find_ancestor_local_symbol "##{symbol}" unless ref end - else - ref = container.find_local_symbol symbol - ref = container.find_ancestor_local_symbol symbol unless ref end end + + ref end - ref - end + ## + # Returns a reference to +name+. + # + # If the reference is found and +name+ is not documented +nil+ will be + # returned. If +name+ is not found +nil+ is returned. - ## - # Returns a reference to +name+. - # - # If the reference is found and +name+ is not documented +nil+ will be - # returned. If +name+ is not found +nil+ is returned. + def resolve(name) + return @seen[name] if @seen.include? name - def resolve(name) - return @seen[name] if @seen.include? name + ref = @context.find_symbol name - ref = @context.find_symbol name + ref = resolve_local_symbol name unless ref - ref = resolve_local_symbol name unless ref + # Try a page name + ref = @store.page name if not ref and name =~ /^[\w.\/]+$/ - # 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 - ref = nil if RDoc::Alias === ref # external alias, can't link to it + ref = nil unless ref&.display? - ref = nil unless ref&.display? + @seen[name] = ref - @seen[name] = ref + ref + end - ref end - end diff --git a/lib/rdoc/encoding.rb b/lib/rdoc/encoding.rb index 4441c66dbb..1f1c479f2f 100644 --- a/lib/rdoc/encoding.rb +++ b/lib/rdoc/encoding.rb @@ -1,120 +1,122 @@ # coding: US-ASCII # frozen_string_literal: true -## -# This class is a wrapper around File IO and Encoding that helps RDoc load -# files and convert them to the correct encoding. - -module RDoc::Encoding - - HEADER_REGEXP = /\A - (?: - \#!.*\n - | - ^\#\s+frozen[-_]string[-_]literal[=:].+\n - | - ^\#\s*(?:-\*-\s*(?:[^;\n]*;\s*)*)?(?:en)?coding[=:]\s*(?[^:\s;]+).*\n - | - <\?xml[^?]*encoding=(?["'])(?.*?)\k.*\n - )+ - /xi # :nodoc: - +module RDoc ## - # Reads the contents of +filename+ and handles any encoding directives in - # the file. - # - # The content will be converted to the +encoding+. If the file cannot be - # converted a warning will be printed and nil will be returned. - # - # If +force_transcode+ is true the document will be transcoded and any - # unknown character in the target encoding will be replaced with '?' - - def self.read_file(filename, encoding, force_transcode = false) - content = File.open filename, "rb" do |f| f.read end - content.gsub!("\r\n", "\n") if RUBY_PLATFORM =~ /mswin|mingw/ - - utf8 = content.sub!(/\A\xef\xbb\xbf/, '') - - enc = RDoc::Encoding.detect_encoding content - content = RDoc::Encoding.change_encoding content, enc if enc - - begin - encoding ||= Encoding.default_external - orig_encoding = content.encoding - - if not orig_encoding.ascii_compatible? - content = content.encode encoding - elsif utf8 - content = RDoc::Encoding.change_encoding content, Encoding::UTF_8 - content = content.encode encoding - else - # assume the content is in our output encoding - content = RDoc::Encoding.change_encoding content, encoding - end - - unless content.valid_encoding? - # revert and try to transcode - content = RDoc::Encoding.change_encoding content, orig_encoding - content = content.encode encoding + # This class is a wrapper around File IO and Encoding that helps RDoc load + # files and convert them to the correct encoding. + + module Encoding + + HEADER_REGEXP = /\A + (?: + \#!.*\n + | + ^\#\s+frozen[-_]string[-_]literal[=:].+\n + | + ^\#\s*(?:-\*-\s*(?:[^;\n]*;\s*)*)?(?:en)?coding[=:]\s*(?[^:\s;]+).*\n + | + <\?xml[^?]*encoding=(?["'])(?.*?)\k.*\n + )+ + /xi # :nodoc: + + ## + # Reads the contents of +filename+ and handles any encoding directives in + # the file. + # + # The content will be converted to the +encoding+. If the file cannot be + # converted a warning will be printed and nil will be returned. + # + # If +force_transcode+ is true the document will be transcoded and any + # unknown character in the target encoding will be replaced with '?' + + def self.read_file(filename, encoding, force_transcode = false) + content = File.open filename, "rb" do |f| f.read end + content.gsub!("\r\n", "\n") if RUBY_PLATFORM =~ /mswin|mingw/ + + utf8 = content.sub!(/\A\xef\xbb\xbf/, '') + + enc = ::RDoc::Encoding.detect_encoding content + content = ::RDoc::Encoding.change_encoding content, enc if enc + + begin + encoding ||= ::Encoding.default_external + orig_encoding = content.encoding + + if not orig_encoding.ascii_compatible? + content = content.encode encoding + elsif utf8 + content = ::RDoc::Encoding.change_encoding content, ::Encoding::UTF_8 + content = content.encode encoding + else + # assume the content is in our output encoding + content = ::RDoc::Encoding.change_encoding content, encoding + end + + unless content.valid_encoding? + # revert and try to transcode + content = ::RDoc::Encoding.change_encoding content, orig_encoding + content = content.encode encoding + end + + unless content.valid_encoding? + warn "unable to convert #{filename} to #{encoding}, skipping" + content = nil + end + rescue ::Encoding::InvalidByteSequenceError, + ::Encoding::UndefinedConversionError => e + if force_transcode + content = ::RDoc::Encoding.change_encoding content, orig_encoding + content = content.encode(encoding, + :invalid => :replace, + :undef => :replace, + :replace => '?') + return content + else + warn "unable to convert #{e.message} for #{filename}, skipping" + return nil + end end - unless content.valid_encoding? - warn "unable to convert #{filename} to #{encoding}, skipping" - content = nil - end - rescue Encoding::InvalidByteSequenceError, - Encoding::UndefinedConversionError => e - if force_transcode - content = RDoc::Encoding.change_encoding content, orig_encoding - content = content.encode(encoding, - :invalid => :replace, - :undef => :replace, - :replace => '?') - return content - else - warn "unable to convert #{e.message} for #{filename}, skipping" - return nil - end + content + rescue ArgumentError => e + raise unless e.message =~ /unknown encoding name - (.*)/ + warn "unknown encoding name \"#{$1}\" for #{filename}, skipping" + nil + rescue Errno::EISDIR, Errno::ENOENT + nil end - content - rescue ArgumentError => e - raise unless e.message =~ /unknown encoding name - (.*)/ - warn "unknown encoding name \"#{$1}\" for #{filename}, skipping" - nil - rescue Errno::EISDIR, Errno::ENOENT - nil - end + ## + # Detects the encoding of +string+ based on the magic comment - ## - # Detects the encoding of +string+ based on the magic comment + def self.detect_encoding(string) + result = HEADER_REGEXP.match string + name = result && result[:name] - def self.detect_encoding(string) - result = HEADER_REGEXP.match string - name = result && result[:name] - - name ? Encoding.find(name) : nil - end + name ? ::Encoding.find(name) : nil + end - ## - # Removes magic comments and shebang + ## + # Removes magic comments and shebang - def self.remove_magic_comment(string) - string.sub HEADER_REGEXP do |s| - s.gsub(/[^\n]/, '') + def self.remove_magic_comment(string) + string.sub HEADER_REGEXP do |s| + s.gsub(/[^\n]/, '') + end end - end - ## - # Changes encoding based on +encoding+ without converting and returns new - # string - - def self.change_encoding(text, encoding) - if text.kind_of? RDoc::Comment - text.encode! encoding - else - String.new text, encoding: encoding + ## + # Changes encoding based on +encoding+ without converting and returns new + # string + + def self.change_encoding(text, encoding) + if text.kind_of? ::RDoc::Comment + text.encode! encoding + else + String.new text, encoding: encoding + end end - end + end end diff --git a/lib/rdoc/erb_partial.rb b/lib/rdoc/erb_partial.rb index bad02ea706..22695ba759 100644 --- a/lib/rdoc/erb_partial.rb +++ b/lib/rdoc/erb_partial.rb @@ -1,18 +1,20 @@ # frozen_string_literal: true -## -# Allows an ERB template to be rendered in the context (binding) of an -# existing ERB template evaluation. +module RDoc + ## + # Allows an ERB template to be rendered in the context (binding) of an + # existing ERB template evaluation. -class RDoc::ERBPartial < ERB + class ERBPartial < ERB - ## - # Overrides +compiler+ startup to set the +eoutvar+ to an empty string only - # if it isn't already set. + ## + # 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 + def set_eoutvar(compiler, eoutvar = '_erbout') + super - compiler.pre_cmd = ["#{eoutvar} ||= +''"] - end + compiler.pre_cmd = ["#{eoutvar} ||= +''"] + end + end end diff --git a/lib/rdoc/erbio.rb b/lib/rdoc/erbio.rb index e955eed811..a9a558a65e 100644 --- a/lib/rdoc/erbio.rb +++ b/lib/rdoc/erbio.rb @@ -1,37 +1,39 @@ # frozen_string_literal: true require 'erb' -## -# A subclass of ERB that writes directly to an IO. Credit to Aaron Patterson -# and Masatoshi SEKI. -# -# To use: -# -# erbio = RDoc::ERBIO.new '<%= "hello world" %>', nil, nil -# -# File.open 'hello.txt', 'w' do |io| -# erbio.result binding -# end -# -# Note that binding must enclose the io you wish to output on. +module RDoc + ## + # A subclass of ERB that writes directly to an IO. Credit to Aaron Patterson + # and Masatoshi SEKI. + # + # To use: + # + # erbio = RDoc::ERBIO.new '<%= "hello world" %>', nil, nil + # + # File.open 'hello.txt', 'w' do |io| + # erbio.result binding + # end + # + # Note that binding must enclose the io you wish to output on. -class RDoc::ERBIO < ERB + class ERBIO < ERB - ## - # Defaults +eoutvar+ to 'io', otherwise is identical to ERB's initialize + ## + # Defaults +eoutvar+ to 'io', otherwise is identical to ERB's initialize - def initialize(str, trim_mode: nil, eoutvar: 'io') - super(str, trim_mode: trim_mode, eoutvar: eoutvar) - end + def initialize(str, trim_mode: nil, eoutvar: 'io') + super(str, trim_mode: trim_mode, eoutvar: eoutvar) + end - ## - # Instructs +compiler+ how to write to +io_variable+ + ## + # Instructs +compiler+ how to write to +io_variable+ - def set_eoutvar(compiler, io_variable) - compiler.put_cmd = "#{io_variable}.write" - compiler.insert_cmd = "#{io_variable}.write" - compiler.pre_cmd = [] - compiler.post_cmd = [] - end + def set_eoutvar(compiler, io_variable) + compiler.put_cmd = "#{io_variable}.write" + compiler.insert_cmd = "#{io_variable}.write" + compiler.pre_cmd = [] + compiler.post_cmd = [] + end + end end diff --git a/lib/rdoc/generator.rb b/lib/rdoc/generator.rb index e8a14d4a66..edc5f34dc5 100644 --- a/lib/rdoc/generator.rb +++ b/lib/rdoc/generator.rb @@ -1,52 +1,54 @@ # frozen_string_literal: true -## -# 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 -# -# Generators are registered by calling RDoc::RDoc.add_generator with the class -# of the generator: -# -# class My::Awesome::Generator -# RDoc::RDoc.add_generator self -# end -# -# == 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 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 -# -# After parsing, RDoc::RDoc will instantiate a generator by calling -# #initialize with an RDoc::Store instance and an RDoc::Options instance. -# -# 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 + ## + # 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 + # + # Generators are registered by calling RDoc::RDoc.add_generator with the class + # of the generator: + # + # class My::Awesome::Generator + # RDoc::RDoc.add_generator self + # end + # + # == 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 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 + # + # After parsing, RDoc::RDoc will instantiate a generator by calling + # #initialize with an RDoc::Store instance and an RDoc::Options instance. + # + # 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 + module Generator - autoload :Markup, "#{__dir__}/generator/markup" + autoload :Markup, "#{__dir__}/generator/markup" - autoload :Aliki, "#{__dir__}/generator/aliki" - autoload :Darkfish, "#{__dir__}/generator/darkfish" - autoload :JsonIndex, "#{__dir__}/generator/json_index" - autoload :RI, "#{__dir__}/generator/ri" - autoload :POT, "#{__dir__}/generator/pot" + autoload :Aliki, "#{__dir__}/generator/aliki" + autoload :Darkfish, "#{__dir__}/generator/darkfish" + autoload :JsonIndex, "#{__dir__}/generator/json_index" + autoload :RI, "#{__dir__}/generator/ri" + autoload :POT, "#{__dir__}/generator/pot" + end end diff --git a/lib/rdoc/generator/aliki.rb b/lib/rdoc/generator/aliki.rb index bb8628c56a..fea23b6cfb 100644 --- a/lib/rdoc/generator/aliki.rb +++ b/lib/rdoc/generator/aliki.rb @@ -2,203 +2,207 @@ require 'uri' -## -# Aliki theme for RDoc documentation -# -# Author: Stan Lo -# +module RDoc + module Generator + ## + # Aliki theme for RDoc documentation + # + # Author: Stan Lo + # + + class Aliki < ::RDoc::Generator::Darkfish + DESCRIPTION = 'HTML generator, written by Stan Lo' + + ::RDoc::RDoc.add_generator self + + def initialize(store, options) + super + aliki_template_dir = File.expand_path(File.join(__dir__, 'template', 'aliki')) + @template_dir = Pathname.new(aliki_template_dir) + end -class RDoc::Generator::Aliki < RDoc::Generator::Darkfish - DESCRIPTION = 'HTML generator, written by Stan Lo' + ## + # Generate documentation. Overrides Darkfish to use Aliki's own search index + # instead of the JsonIndex generator. - RDoc::RDoc.add_generator self + def generate + setup - def initialize(store, options) - super - aliki_template_dir = File.expand_path(File.join(__dir__, 'template', 'aliki')) - @template_dir = Pathname.new(aliki_template_dir) - end + write_style_sheet + generate_index + generate_class_files + generate_file_files + generate_table_of_contents + write_search_index - ## - # Generate documentation. Overrides Darkfish to use Aliki's own search index - # instead of the JsonIndex generator. + copy_static - def generate - setup + rescue => e + debug_msg "%s: %s\n %s" % [ + e.class.name, e.message, e.backtrace.join("\n ") + ] - write_style_sheet - generate_index - generate_class_files - generate_file_files - generate_table_of_contents - write_search_index + raise + end - copy_static + ## + # Copy only the static assets required by the Aliki theme. Unlike Darkfish we + # don't ship embedded fonts or image sprites, so limit the asset list to keep + # generated documentation lightweight. - rescue => e - debug_msg "%s: %s\n %s" % [ - e.class.name, e.message, e.backtrace.join("\n ") - ] + def write_style_sheet + debug_msg "Copying Aliki static files" + options = { verbose: $DEBUG_RDOC, noop: @dry_run } - raise - end + install_rdoc_static_file @template_dir + 'css/rdoc.css', "./css/rdoc.css", options - ## - # Copy only the static assets required by the Aliki theme. Unlike Darkfish we - # don't ship embedded fonts or image sprites, so limit the asset list to keep - # generated documentation lightweight. + unless @options.template_stylesheets.empty? + FileUtils.cp @options.template_stylesheets, '.', **options + end - def write_style_sheet - debug_msg "Copying Aliki static files" - options = { verbose: $DEBUG_RDOC, noop: @dry_run } + Dir[(@template_dir + 'js/**/*').to_s].each do |path| + next if File.directory?(path) + next if File.basename(path).start_with?('.') - install_rdoc_static_file @template_dir + 'css/rdoc.css', "./css/rdoc.css", options + dst = Pathname.new(path).relative_path_from(@template_dir) - unless @options.template_stylesheets.empty? - FileUtils.cp @options.template_stylesheets, '.', **options - end + install_rdoc_static_file @template_dir + path, dst, options + end + end - Dir[(@template_dir + 'js/**/*').to_s].each do |path| - next if File.directory?(path) - next if File.basename(path).start_with?('.') + ## + # Build a search index array for Aliki's searcher. - dst = Pathname.new(path).relative_path_from(@template_dir) + def build_search_index + setup - install_rdoc_static_file @template_dir + path, dst, options - end - end + index = [] - ## - # Build a search index array for Aliki's searcher. + @classes.each do |klass| + next unless klass.display? - def build_search_index - setup + index << build_class_module_entry(klass) - index = [] + klass.constants.each do |const| + next unless const.display? - @classes.each do |klass| - next unless klass.display? + index << build_constant_entry(const, klass) + end + end - index << build_class_module_entry(klass) + @methods.each do |method| + next unless method.display? - klass.constants.each do |const| - next unless const.display? + index << build_method_entry(method) + end - index << build_constant_entry(const, klass) + index end - end - - @methods.each do |method| - next unless method.display? - - index << build_method_entry(method) - end - - index - end - - ## - # Write the search index as a JavaScript file - # Format: var search_data = { index: [...] } - # - # We still write to a .js instead of a .json because loading a JSON file triggers CORS check in browsers. - # And if we simply inspect the generated pages using file://, which is often the case due to lack of the server mode, - # the JSON file will be blocked by the browser. - def write_search_index - debug_msg "Writing Aliki search index" + ## + # Write the search index as a JavaScript file + # Format: var search_data = { index: [...] } + # + # We still write to a .js instead of a .json because loading a JSON file triggers CORS check in browsers. + # And if we simply inspect the generated pages using file://, which is often the case due to lack of the server mode, + # the JSON file will be blocked by the browser. - index = build_search_index + def write_search_index + debug_msg "Writing Aliki search index" - FileUtils.mkdir_p 'js' unless @dry_run + index = build_search_index - search_index_path = 'js/search_data.js' - return if @dry_run + FileUtils.mkdir_p 'js' unless @dry_run - data = { index: index } - File.write search_index_path, "var search_data = #{JSON.generate(data)};" - end + search_index_path = 'js/search_data.js' + return if @dry_run - ## - # Returns the type signature of +method_attr+ as HTML with linked type names. - # Returns nil if no type signature is present. + data = { index: index } + File.write search_index_path, "var search_data = #{JSON.generate(data)};" + end - def type_signature_html(method_attr, from_path) - lines = method_attr.type_signature_lines || @store.rbs_signature_for(method_attr) - return unless lines + ## + # Returns the type signature of +method_attr+ as HTML with linked type names. + # Returns nil if no type signature is present. - RDoc::RbsHelper.signature_to_html( - lines, - lookup: @store.type_name_lookup, - from_path: from_path - ) - end + def type_signature_html(method_attr, from_path) + lines = method_attr.type_signature_lines || @store.rbs_signature_for(method_attr) + return unless lines - ## - # Resolves a URL for use in templates. Absolute URLs are returned unchanged. - # Relative URLs are prefixed with rel_prefix to ensure they resolve correctly from any page. + ::RDoc::RbsHelper.signature_to_html( + lines, + lookup: @store.type_name_lookup, + from_path: from_path + ) + end - def resolve_url(rel_prefix, url) - uri = URI.parse(url) - if uri.absolute? - url - else - "#{rel_prefix}/#{url}" - end - rescue URI::InvalidURIError - "#{rel_prefix}/#{url}" - end + ## + # Resolves a URL for use in templates. Absolute URLs are returned unchanged. + # Relative URLs are prefixed with rel_prefix to ensure they resolve correctly from any page. + + def resolve_url(rel_prefix, url) + uri = URI.parse(url) + if uri.absolute? + url + else + "#{rel_prefix}/#{url}" + end + rescue URI::InvalidURIError + "#{rel_prefix}/#{url}" + end - private + private - def template_encoding - Encoding::UTF_8 - end + def template_encoding + ::Encoding::UTF_8 + end - def build_class_module_entry(klass) - type = case klass - when RDoc::NormalClass then 'class' - when RDoc::NormalModule then 'module' - else 'class' - end - - entry = { - name: klass.name, - full_name: klass.full_name, - type: type, - path: klass.path - } - - snippet = klass.search_snippet - entry[:snippet] = snippet unless snippet.empty? - entry - end + def build_class_module_entry(klass) + type = case klass + when ::RDoc::NormalClass then 'class' + when ::RDoc::NormalModule then 'module' + else 'class' + end + + entry = { + name: klass.name, + full_name: klass.full_name, + type: type, + path: klass.path + } + + snippet = klass.search_snippet + entry[:snippet] = snippet unless snippet.empty? + entry + end - def build_method_entry(method) - type = method.singleton ? 'class_method' : 'instance_method' + def build_method_entry(method) + type = method.singleton ? 'class_method' : 'instance_method' - entry = { - name: method.name, - full_name: method.full_name, - type: type, - path: method.path - } + entry = { + name: method.name, + full_name: method.full_name, + type: type, + path: method.path + } - snippet = method.search_snippet - entry[:snippet] = snippet unless snippet.empty? - entry - end + snippet = method.search_snippet + entry[:snippet] = snippet unless snippet.empty? + entry + end - def build_constant_entry(const, parent) - entry = { - name: const.name, - full_name: "#{parent.full_name}::#{const.name}", - type: 'constant', - path: parent.path - } - - snippet = const.search_snippet - entry[:snippet] = snippet unless snippet.empty? - entry + def build_constant_entry(const, parent) + entry = { + name: const.name, + full_name: "#{parent.full_name}::#{const.name}", + type: 'constant', + path: parent.path + } + + snippet = const.search_snippet + entry[:snippet] = snippet unless snippet.empty? + entry + end + end end end diff --git a/lib/rdoc/generator/darkfish.rb b/lib/rdoc/generator/darkfish.rb index b728a3f973..3766533476 100644 --- a/lib/rdoc/generator/darkfish.rb +++ b/lib/rdoc/generator/darkfish.rb @@ -6,605 +6,607 @@ require 'pathname' require_relative 'markup' -## -# Darkfish RDoc HTML Generator -# -# $Id: darkfish.rb 52 2009-01-07 02:08:11Z deveiant $ -# -# == Author/s -# * Michael Granger (ged@FaerieMUD.org) -# -# == Contributors -# * Mahlon E. Smith (mahlon@martini.nu) -# * Eric Hodel (drbrain@segment7.net) -# -# == License -# -# Copyright (c) 2007, 2008, Michael Granger. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of the author/s, nor the names of the project's -# contributors may be used to endorse or promote products derived from this -# software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# 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 - - RDoc::RDoc.add_generator self - - include ERB::Util - - ## - # Stylesheets, fonts, etc. that are included in RDoc. - - BUILTIN_STYLE_ITEMS = # :nodoc: - %w[ - css/fonts.css - fonts/Lato-Light.ttf - fonts/Lato-LightItalic.ttf - fonts/Lato-Regular.ttf - fonts/Lato-RegularItalic.ttf - fonts/SourceCodePro-Bold.ttf - fonts/SourceCodePro-Regular.ttf - css/rdoc.css - ] - - ## - # Description of this generator - - DESCRIPTION = 'HTML generator, written by Michael Granger' - - ## - # The relative path to style sheets and javascript. By default this is set - # the same as the rel_prefix. - - attr_accessor :asset_rel_path - - ## - # The path to generate files into, combined with --op from the - # options for a full path. - - attr_reader :base_dir - - ## - # 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 +module RDoc + module Generator + ## + # Darkfish RDoc HTML Generator + # + # $Id: darkfish.rb 52 2009-01-07 02:08:11Z deveiant $ + # + # == Author/s + # * Michael Granger (ged@FaerieMUD.org) + # + # == Contributors + # * Mahlon E. Smith (mahlon@martini.nu) + # * Eric Hodel (drbrain@segment7.net) + # + # == License + # + # Copyright (c) 2007, 2008, Michael Granger. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions are met: + # + # * Redistributions of source code must retain the above copyright notice, + # this list of conditions and the following disclaimer. + # + # * Redistributions in binary form must reproduce the above copyright notice, + # this list of conditions and the following disclaimer in the documentation + # and/or other materials provided with the distribution. + # + # * Neither the name of the author/s, nor the names of the project's + # contributors may be used to endorse or promote products derived from this + # software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + # 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 Darkfish + + ::RDoc::RDoc.add_generator self + + include ERB::Util + + ## + # Stylesheets, fonts, etc. that are included in RDoc. + + BUILTIN_STYLE_ITEMS = # :nodoc: + %w[ + css/fonts.css + fonts/Lato-Light.ttf + fonts/Lato-LightItalic.ttf + fonts/Lato-Regular.ttf + fonts/Lato-RegularItalic.ttf + fonts/SourceCodePro-Bold.ttf + fonts/SourceCodePro-Regular.ttf + css/rdoc.css + ] + + ## + # Description of this generator + + DESCRIPTION = 'HTML generator, written by Michael Granger' + + ## + # The relative path to style sheets and javascript. By default this is set + # the same as the rel_prefix. + + attr_accessor :asset_rel_path + + ## + # The path to generate files into, combined with --op from the + # options for a full path. + + attr_reader :base_dir + + ## + # 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 + ## + # Methods to be displayed by this generator - attr_reader :methods + attr_reader :methods - ## - # Sorted list of classes and modules to be displayed by this generator + ## + # Sorted list of classes and modules to be displayed by this generator - attr_reader :modsort + attr_reader :modsort - ## - # The RDoc::Store that is the source of the generated content + ## + # The RDoc::Store that is the source of the generated content - attr_reader :store + attr_reader :store - ## - # The directory where the template files live + ## + # The directory where the template files live - attr_reader :template_dir # :nodoc: + attr_reader :template_dir # :nodoc: - ## - # The output directory + ## + # The output directory - attr_reader :outputdir + attr_reader :outputdir - ## - # Initialize a few instance variables before we start + ## + # Initialize a few instance variables before we start - def initialize(store, options) - @store = store - @options = options + 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 = {} + @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 + @classes = nil + @context = nil + @files = nil + @methods = nil + @modsort = nil - @json_index = RDoc::Generator::JsonIndex.new self, options - end + @json_index = ::RDoc::Generator::JsonIndex.new self, options + end - ## - # Output progress information if debugging is enabled + ## + # Output progress information if debugging is enabled - def debug_msg(*msg) - return unless $DEBUG_RDOC - $stderr.puts(*msg) - end + def debug_msg(*msg) + return unless $DEBUG_RDOC + $stderr.puts(*msg) + end - ## - # Create the directories the generated docs will live in if they don't - # already exist. + ## + # Create the directories the generated docs will live in if they don't + # already exist. - def gen_sub_directories - @outputdir.mkpath - end + def gen_sub_directories + @outputdir.mkpath + end - ## - # Copy over the stylesheet into the appropriate place in the output - # directory. + ## + # Copy over the stylesheet into the appropriate place in the output + # directory. - def write_style_sheet - debug_msg "Copying static files" - options = { :verbose => $DEBUG_RDOC, :noop => @dry_run } + def write_style_sheet + debug_msg "Copying static files" + options = { :verbose => $DEBUG_RDOC, :noop => @dry_run } - BUILTIN_STYLE_ITEMS.each do |item| - install_rdoc_static_file @template_dir + item, "./#{item}", options - end + BUILTIN_STYLE_ITEMS.each do |item| + install_rdoc_static_file @template_dir + item, "./#{item}", options + end - unless @options.template_stylesheets.empty? - FileUtils.cp @options.template_stylesheets, '.', **options - end + unless @options.template_stylesheets.empty? + FileUtils.cp @options.template_stylesheets, '.', **options + end - Dir[(@template_dir + "{js,images}/**/*").to_s].each do |path| - next if File.directory? path - next if File.basename(path) =~ /^\./ + Dir[(@template_dir + "{js,images}/**/*").to_s].each do |path| + 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 - install_rdoc_static_file @template_dir + path, dst, options - end - end + install_rdoc_static_file @template_dir + path, dst, options + end + end - ## - # Build the initial indices and output objects based on an array of TopLevel - # objects containing the extracted information. + ## + # Build the initial indices and output objects based on an array of TopLevel + # objects containing the extracted information. - def generate - setup + def generate + setup - write_style_sheet - generate_index - generate_class_files - generate_file_files - generate_table_of_contents - @json_index.generate - @json_index.generate_gzipped + write_style_sheet + generate_index + generate_class_files + generate_file_files + generate_table_of_contents + @json_index.generate + @json_index.generate_gzipped - copy_static + copy_static - rescue => e - debug_msg "%s: %s\n %s" % [ - e.class.name, e.message, e.backtrace.join("\n ") - ] + rescue => e + debug_msg "%s: %s\n %s" % [ + e.class.name, e.message, e.backtrace.join("\n ") + ] - raise - end + raise + end - ## - # Copies static files from the static_path into the output directory + ## + # Copies static files from the static_path into the output directory - def copy_static - return if @options.static_path.empty? + def copy_static + return if @options.static_path.empty? - fu_options = { :verbose => $DEBUG_RDOC, :noop => @dry_run } + fu_options = { :verbose => $DEBUG_RDOC, :noop => @dry_run } - @options.static_path.each do |path| - unless File.directory? path - FileUtils.install path, @outputdir, **fu_options.merge(:mode => 0644) - next - end + @options.static_path.each do |path| + unless File.directory? path + 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 + Dir.chdir path do + Dir[File.join('**', '*')].each do |entry| + dest_file = @outputdir + entry - if File.directory? entry - FileUtils.mkdir_p entry, **fu_options - else - FileUtils.install entry, dest_file, **fu_options.merge(:mode => 0644) + if File.directory? entry + FileUtils.mkdir_p entry, **fu_options + else + FileUtils.install entry, dest_file, **fu_options.merge(:mode => 0644) + end + end end end end - end - end - ## - # Return a list of the documented modules sorted by salience first, then - # by name. + ## + # Return a list of the documented modules sorted by salience first, then + # by name. - def get_sorted_module_list(classes) - classes.select do |klass| - klass.display? - end.sort - end + def get_sorted_module_list(classes) + classes.select do |klass| + klass.display? + end.sort + end - ## - # Generate an index page which lists all the classes which are documented. + ## + # Generate an index page which lists all the classes which are documented. - def generate_index - template_file = @template_dir + 'index.rhtml' - return unless template_file.exist? + def generate_index + template_file = @template_dir + 'index.rhtml' + return unless template_file.exist? - debug_msg "Rendering the index page..." + debug_msg "Rendering the index page..." - 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 + 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 - asset_rel_prefix = rel_prefix + @asset_rel_path + asset_rel_prefix = rel_prefix + @asset_rel_path - @title = @options.title - @main_page = @files.find { |f| f.full_name == @options.main_page } + @title = @options.title + @main_page = @files.find { |f| f.full_name == @options.main_page } - render_template template_file, out_file do |io| - here = binding - # suppress 1.9.3 warning - here.local_variable_set(:asset_rel_prefix, asset_rel_prefix) - # some partials rely on the presence of current variable to render - here.local_variable_set(:current, @main_page) if @main_page - here - end - rescue => e - error = RDoc::Error.new \ - "error generating index.html: #{e.message} (#{e.class})" - error.set_backtrace e.backtrace + render_template template_file, out_file do |io| + here = binding + # suppress 1.9.3 warning + here.local_variable_set(:asset_rel_prefix, asset_rel_prefix) + # some partials rely on the presence of current variable to render + here.local_variable_set(:current, @main_page) if @main_page + here + end + rescue => e + error = ::RDoc::Error.new \ + "error generating index.html: #{e.message} (#{e.class})" + error.set_backtrace e.backtrace - raise error - end + raise error + end - ## - # Generates a class file for +klass+ + ## + # Generates a class file for +klass+ - def generate_class(klass, template_file = nil) - # This is used to auto-collapse Pages section on class/module pages - @inside_class_file = true - current = klass + def generate_class(klass, template_file = nil) + # This is used to auto-collapse Pages section on class/module pages + @inside_class_file = true + current = klass - template_file ||= @template_dir + 'class.rhtml' + 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 + 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 - asset_rel_prefix = rel_prefix + @asset_rel_path + asset_rel_prefix = rel_prefix + @asset_rel_path - breadcrumb = # used in templates - breadcrumb = generate_nesting_namespaces_breadcrumb(current, rel_prefix) + breadcrumb = # used in templates + breadcrumb = generate_nesting_namespaces_breadcrumb(current, rel_prefix) - @title = "#{klass.type} #{klass.full_name} - #{@options.title}" + @title = "#{klass.type} #{klass.full_name} - #{@options.title}" - debug_msg " rendering #{out_file}" - render_template template_file, out_file do |io| - here = binding - # suppress 1.9.3 warning - here.local_variable_set(:asset_rel_prefix, asset_rel_prefix) - here - end - ensure - @inside_class_file = false - end + debug_msg " rendering #{out_file}" + render_template template_file, out_file do |io| + here = binding + # suppress 1.9.3 warning + here.local_variable_set(:asset_rel_prefix, asset_rel_prefix) + here + end + ensure + @inside_class_file = false + end - ## - # Generate a documentation file for each class and module + ## + # Generate a documentation file for each class and module - def generate_class_files - 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}" + def generate_class_files + 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}" - current = nil + current = nil - # Document files are generated only for non-alias classes/modules - @classes.reject(&:is_alias_for).each do |klass| + # Document files are generated only for non-alias classes/modules + @classes.reject(&:is_alias_for).each do |klass| - current = klass + current = klass - generate_class klass, template_file - end - rescue => e - error = RDoc::Error.new \ - "error generating #{current.path}: #{e.message} (#{e.class})" - error.set_backtrace e.backtrace + generate_class klass, template_file + end + rescue => e + error = ::RDoc::Error.new \ + "error generating #{current.path}: #{e.message} (#{e.class})" + error.set_backtrace e.backtrace - raise error - end + raise error + end - ## - # Generate a documentation file for each file + ## + # Generate a documentation file for each file - def generate_file_files - page_file = @template_dir + 'page.rhtml' - fileinfo_file = @template_dir + 'fileinfo.rhtml' + def generate_file_files + 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? + # 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? + return unless + page_file.exist? or fileinfo_file.exist? or filepage_file.exist? - debug_msg "Generating file documentation in #{@outputdir}" + debug_msg "Generating file documentation in #{@outputdir}" - out_file = nil - current = nil + out_file = nil + current = nil - @files.each do |file| - current = file + @files.each do |file| + current = file - next if file.text? && file.full_name == @options.main_page + next if file.text? && file.full_name == @options.main_page - if file.text? and page_file.exist? - generate_page file - next - end + if file.text? and page_file.exist? + 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 + 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 + + asset_rel_prefix = rel_prefix + @asset_rel_path + + unless filepage_file + if file.text? + 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 - asset_rel_prefix = rel_prefix + @asset_rel_path + @title += " - #{@options.title}" + template_file ||= filepage_file - unless filepage_file - if file.text? - 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}" + render_template template_file, out_file do |io| + here = binding + # suppress 1.9.3 warning + here.local_variable_set(:asset_rel_prefix, asset_rel_prefix) + here.local_variable_set(:current, current) + here + end end - end + rescue => e + error = + ::RDoc::Error.new "error generating #{out_file}: #{e.message} (#{e.class})" + error.set_backtrace e.backtrace - @title += " - #{@options.title}" - template_file ||= filepage_file - - render_template template_file, out_file do |io| - here = binding - # suppress 1.9.3 warning - here.local_variable_set(:asset_rel_prefix, asset_rel_prefix) - here.local_variable_set(:current, current) - here + raise error end - end - rescue => e - error = - RDoc::Error.new "error generating #{out_file}: #{e.message} (#{e.class})" - error.set_backtrace e.backtrace - raise error - end - - ## - # Generate a page file for +file+ + ## + # Generate a page file for +file+ - def generate_page(file) - template_file = @template_dir + 'page.rhtml' + def generate_page(file) + 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 + 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 - current = file - asset_rel_prefix = rel_prefix + @asset_rel_path + current = file + asset_rel_prefix = rel_prefix + @asset_rel_path - @title = "#{file.page_name} - #{@options.title}" + @title = "#{file.page_name} - #{@options.title}" - debug_msg " rendering #{out_file}" - render_template template_file, out_file do |io| - here = binding - # suppress 1.9.3 warning - here.local_variable_set(:current, current) - here.local_variable_set(:asset_rel_prefix, asset_rel_prefix) - here - end - end + debug_msg " rendering #{out_file}" + render_template template_file, out_file do |io| + here = binding + # suppress 1.9.3 warning + here.local_variable_set(:current, current) + here.local_variable_set(:asset_rel_prefix, asset_rel_prefix) + here + end + end - ## - # Generates the 404 page for the RDoc servlet + ## + # Generates the 404 page for the RDoc servlet - def generate_servlet_not_found(message) - template_file = @template_dir + 'servlet_not_found.rhtml' - return unless template_file.exist? + def generate_servlet_not_found(message) + template_file = @template_dir + 'servlet_not_found.rhtml' + return unless template_file.exist? - debug_msg "Rendering the servlet 404 Not Found page..." + debug_msg "Rendering the servlet 404 Not Found page..." - rel_prefix = rel_prefix = '' - search_index_rel_prefix = rel_prefix - search_index_rel_prefix += @asset_rel_path if @file_output + rel_prefix = rel_prefix = '' + search_index_rel_prefix = rel_prefix + search_index_rel_prefix += @asset_rel_path if @file_output - asset_rel_prefix = '' + asset_rel_prefix = '' - @title = 'Not Found' + @title = 'Not Found' - render_template template_file do |io| - here = binding - # suppress 1.9.3 warning - here.local_variable_set(:asset_rel_prefix, asset_rel_prefix) - here - end - rescue => e - error = RDoc::Error.new \ - "error generating servlet_not_found: #{e.message} (#{e.class})" - error.set_backtrace e.backtrace + render_template template_file do |io| + here = binding + # suppress 1.9.3 warning + here.local_variable_set(:asset_rel_prefix, asset_rel_prefix) + here + end + rescue => e + error = ::RDoc::Error.new \ + "error generating servlet_not_found: #{e.message} (#{e.class})" + error.set_backtrace e.backtrace - raise error - end + raise error + end - ## - # Generates the servlet root page for the RDoc servlet + ## + # Generates the servlet root page for the RDoc servlet - def generate_servlet_root(installed) - template_file = @template_dir + 'servlet_root.rhtml' - return unless template_file.exist? + def generate_servlet_root(installed) + template_file = @template_dir + 'servlet_root.rhtml' + return unless template_file.exist? - debug_msg 'Rendering the servlet root page...' + debug_msg 'Rendering the servlet root page...' - rel_prefix = '.' - asset_rel_prefix = rel_prefix - search_index_rel_prefix = asset_rel_prefix - search_index_rel_prefix += @asset_rel_path if @file_output + rel_prefix = '.' + asset_rel_prefix = rel_prefix + search_index_rel_prefix = asset_rel_prefix + search_index_rel_prefix += @asset_rel_path if @file_output - @title = 'Local RDoc Documentation' + @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 + 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 + raise error + end - ## - # Generate an index page which lists all the classes which are documented. + ## + # Generate an index page which lists all the classes which are documented. - def generate_table_of_contents - template_file = @template_dir + 'table_of_contents.rhtml' - return unless template_file.exist? + def generate_table_of_contents + template_file = @template_dir + 'table_of_contents.rhtml' + return unless template_file.exist? - debug_msg "Rendering the Table of Contents..." + 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 + 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 - asset_rel_prefix = rel_prefix + @asset_rel_path + asset_rel_prefix = rel_prefix + @asset_rel_path - @title = "Table of Contents - #{@options.title}" + @title = "Table of Contents - #{@options.title}" - render_template template_file, out_file do |io| - here = binding - # suppress 1.9.3 warning - here.local_variable_set(:asset_rel_prefix, asset_rel_prefix) - here - end - rescue => e - error = RDoc::Error.new \ - "error generating table_of_contents.html: #{e.message} (#{e.class})" - error.set_backtrace e.backtrace + render_template template_file, out_file do |io| + here = binding + # suppress 1.9.3 warning + here.local_variable_set(:asset_rel_prefix, asset_rel_prefix) + here + 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 + raise error + end - def install_rdoc_static_file(source, destination, options) # :nodoc: - return unless source.exist? + def install_rdoc_static_file(source, destination, options) # :nodoc: + return unless source.exist? - begin - FileUtils.mkdir_p File.dirname(destination), **options + begin + FileUtils.mkdir_p File.dirname(destination), **options - begin - FileUtils.ln source, destination, **options - rescue Errno::EEXIST - FileUtils.rm destination - retry + begin + FileUtils.ln source, destination, **options + rescue Errno::EEXIST + FileUtils.rm destination + retry + end + rescue + FileUtils.cp source, destination, **options + end end - rescue - FileUtils.cp source, destination, **options - end - end - ## - # Prepares for generation of output from the current directory + ## + # Prepares for generation of output from the current directory - def setup - return if instance_variable_defined? :@outputdir + def setup + return if instance_variable_defined? :@outputdir - @outputdir = Pathname.new(@options.op_dir).expand_path @base_dir + @outputdir = Pathname.new(@options.op_dir).expand_path @base_dir - return unless @store + return unless @store - refresh_store_data - end + refresh_store_data + end - ## - # Refreshes the generator's data from the store. Called by #setup and - # can be called again after the store has been updated (e.g. in server - # mode after re-parsing changed files). + ## + # Refreshes the generator's data from the store. Called by #setup and + # can be called again after the store has been updated (e.g. in server + # mode after re-parsing changed files). - def refresh_store_data - @classes = @store.all_classes_and_modules.sort - @files = @store.all_files.sort - @methods = @classes.flat_map { |m| m.method_list }.sort - @modsort = get_sorted_module_list @classes - end + def refresh_store_data + @classes = @store.all_classes_and_modules.sort + @files = @store.all_files.sort + @methods = @classes.flat_map { |m| m.method_list }.sort + @modsort = get_sorted_module_list @classes + end - ## - # Creates a template from its components and the +body_file+. - # - # For backwards compatibility, if +body_file+ contains " @@ -613,216 +615,218 @@ def assemble_template(body_file) #{body} TEMPLATE - end + end - ## - # Renders the ERb contained in +file_name+ relative to the template - # directory and returns the result based on the current context. + ## + # 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 + def render(file_name) + template_file = @template_dir + file_name - template = template_for template_file, false, RDoc::ERBPartial + template = template_for template_file, false, ::RDoc::ERBPartial - template.filename = template_file.to_s + template.filename = template_file.to_s - template.result @context - end + template.result @context + end - ## - # Load and render the erb template in the given +template_file+ and write - # it out to +out_file+. - # - # Both +template_file+ and +out_file+ should be Pathname-like objects. - # - # An io will be yielded which must be captured by binding in the caller. + ## + # Load and render the erb template in the given +template_file+ and write + # it out to +out_file+. + # + # Both +template_file+ and +out_file+ should be Pathname-like objects. + # + # An io will be yielded which must be captured by binding in the caller. - 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 + 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 - template = template_for template_file, true, erb_klass + template = template_for template_file, true, erb_klass - if io_output - debug_msg "Outputting to %s" % [out_file.expand_path] + if io_output + 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 + out_file.dirname.mkpath + out_file.open 'w', 0644 do |io| + io.set_encoding @options.encoding - @context = yield io + @context = yield io - template_result template, @context, template_file - end - else - @context = yield nil + template_result template, @context, template_file + end + else + @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 + debug_msg " would have written %d characters to %s" % [ + output.length, out_file.expand_path + ] if @dry_run - output - end - end + output + end + end - ## - # Creates the result for +template+ with +context+. If an error is raised a - # Pathname +template_file+ will indicate the file where the error occurred. - - def template_result(template, context, template_file) - template.filename = template_file.to_s - template.result context - rescue NoMethodError => e - raise RDoc::Error, "Error while evaluating %s: %s" % [ - template_file.expand_path, - e.message, - ], e.backtrace - end + ## + # Creates the result for +template+ with +context+. If an error is raised a + # Pathname +template_file+ will indicate the file where the error occurred. + + def template_result(template, context, template_file) + template.filename = template_file.to_s + template.result context + rescue NoMethodError => e + raise ::RDoc::Error, "Error while evaluating %s: %s" % [ + template_file.expand_path, + e.message, + ], e.backtrace + end - ## - # Retrieves a cache template for +file+, if present, or fills the cache. + ## + # Retrieves a cache template for +file+, if present, or fills the cache. - def template_for(file, page = true, klass = ERB) - template = @template_cache[file] + def template_for(file, page = true, klass = ERB) + template = @template_cache[file] - return template if template + return template if template - if page - template = assemble_template file - erbout = 'io' - else - template = file.read(encoding: template_encoding) - template = template.encode @options.encoding + if page + template = assemble_template file + erbout = 'io' + else + template = file.read(encoding: template_encoding) + template = template.encode @options.encoding - file_var = File.basename(file).sub(/\..*/, '') + file_var = File.basename(file).sub(/\..*/, '') - erbout = "_erbout_#{file_var}" - end + erbout = "_erbout_#{file_var}" + end - template = klass.new template, trim_mode: '-', eoutvar: erbout - @template_cache[file] = template - template - end + template = klass.new template, trim_mode: '-', eoutvar: erbout + @template_cache[file] = template + template + end - # :stopdoc: - ParagraphExcerptRegexpOther = %r[\b\w[^./:]++\.] - # use \p/\P{letter} instead of \w/\W in Unicode - ParagraphExcerptRegexpUnicode = %r[\b\p{letter}[^./:]++\.] - # :startdoc: - - # Returns an excerpt of the comment for usage in meta description tags - def excerpt(comment) - text = case comment - when RDoc::Comment - comment.text - else - comment - end + # :stopdoc: + ParagraphExcerptRegexpOther = %r[\b\w[^./:]++\.] + # use \p/\P{letter} instead of \w/\W in Unicode + ParagraphExcerptRegexpUnicode = %r[\b\p{letter}[^./:]++\.] + # :startdoc: + + # Returns an excerpt of the comment for usage in meta description tags + def excerpt(comment) + text = case comment + when ::RDoc::Comment + comment.text + else + comment + end - # Match from a capital letter to the first period, discarding any links, so - # that we don't end up matching badges in the README - pattern = ParagraphExcerptRegexpUnicode - begin - first_paragraph_match = text.match(pattern) - rescue Encoding::CompatibilityError - # The doc is non-ASCII text and encoded in other than Unicode base encodings. - raise if pattern == ParagraphExcerptRegexpOther - pattern = ParagraphExcerptRegexpOther - retry - end - return text[0...150].tr_s("\n", " ").squeeze(" ") unless first_paragraph_match + # Match from a capital letter to the first period, discarding any links, so + # that we don't end up matching badges in the README + pattern = ParagraphExcerptRegexpUnicode + begin + first_paragraph_match = text.match(pattern) + rescue ::Encoding::CompatibilityError + # The doc is non-ASCII text and encoded in other than Unicode base encodings. + raise if pattern == ParagraphExcerptRegexpOther + pattern = ParagraphExcerptRegexpOther + retry + end + return text[0...150].tr_s("\n", " ").squeeze(" ") unless first_paragraph_match - extracted_text = first_paragraph_match[0] - second_paragraph = text.match(pattern, first_paragraph_match.end(0)) - extracted_text << " " << second_paragraph[0] if second_paragraph + extracted_text = first_paragraph_match[0] + second_paragraph = text.match(pattern, first_paragraph_match.end(0)) + extracted_text << " " << second_paragraph[0] if second_paragraph - extracted_text[0...150].tr_s("\n", " ").squeeze(" ") - end + extracted_text[0...150].tr_s("\n", " ").squeeze(" ") + end - def generate_ancestor_list(ancestors, klass) - return '' if ancestors.empty? + def generate_ancestor_list(ancestors, klass) + return '' if ancestors.empty? - ancestor = ancestors.shift - content = +'
  • ' + ancestor = ancestors.shift + content = +'
    • ' - if ancestor.is_a?(RDoc::NormalClass) - content << "#{ancestor.full_name}" - else - content << ancestor.to_s - end + if ancestor.is_a?(::RDoc::NormalClass) + content << "#{ancestor.full_name}" + else + content << ancestor.to_s + end - # Recursively call the method for the remaining ancestors - content << generate_ancestor_list(ancestors, klass) + # Recursively call the method for the remaining ancestors + content << generate_ancestor_list(ancestors, klass) - content << '
    ' - end + content << '
' + end - def generate_class_link(klass, rel_prefix) - if klass.display? - %(#{klass.name}) - else - %(#{klass.name}) - end - end + def generate_class_link(klass, rel_prefix) + if klass.display? + %(#{klass.name}) + else + %(#{klass.name}) + end + end - def generate_class_index_content(classes, rel_prefix) - grouped_classes = group_classes_by_namespace_for_sidebar(classes) - return '' unless top = grouped_classes[nil] + def generate_class_index_content(classes, rel_prefix) + grouped_classes = group_classes_by_namespace_for_sidebar(classes) + return '' unless top = grouped_classes[nil] - solo = top.one? { |klass| klass.display? } - traverse_classes(top, grouped_classes, rel_prefix, solo) - end + solo = top.one? { |klass| klass.display? } + traverse_classes(top, grouped_classes, rel_prefix, solo) + end - def traverse_classes(klasses, grouped_classes, rel_prefix, solo = false) - content = +'" - end - def group_classes_by_namespace_for_sidebar(classes) - grouped_classes = classes.group_by do |klass| - klass.full_name[/\A[^:]++(?:::[^:]++(?=::))*+(?=::[^:]*+\z)/] - end.select do |_, klasses| - klasses.any?(&:display?) - end + def group_classes_by_namespace_for_sidebar(classes) + grouped_classes = classes.group_by do |klass| + klass.full_name[/\A[^:]++(?:::[^:]++(?=::))*+(?=::[^:]*+\z)/] + end.select do |_, klasses| + klasses.any?(&:display?) + end - grouped_classes.values.each(&:uniq!) - grouped_classes - end + grouped_classes.values.each(&:uniq!) + grouped_classes + end - private + private - def template_encoding - nil - end + def template_encoding + nil + end - def nesting_namespaces_to_class_modules(klass) - tree = {} + def nesting_namespaces_to_class_modules(klass) + tree = {} - klass.nesting_namespaces.zip(klass.fully_qualified_nesting_namespaces) do |ns, fqns| - tree[ns] = @store.classes_hash[fqns] || @store.modules_hash[fqns] - end + klass.nesting_namespaces.zip(klass.fully_qualified_nesting_namespaces) do |ns, fqns| + tree[ns] = @store.classes_hash[fqns] || @store.modules_hash[fqns] + end - tree - end + tree + end - def generate_nesting_namespaces_breadcrumb(klass, rel_prefix) - nesting_namespaces_to_class_modules(klass).map do |namespace, class_module| - path = class_module ? (rel_prefix + class_module.path).to_s : "" - { name: namespace, path: path, self: klass.full_name == class_module&.full_name } + def generate_nesting_namespaces_breadcrumb(klass, rel_prefix) + nesting_namespaces_to_class_modules(klass).map do |namespace, class_module| + path = class_module ? (rel_prefix + class_module.path).to_s : "" + { name: namespace, path: path, self: klass.full_name == class_module&.full_name } + end + end end end end diff --git a/lib/rdoc/generator/json_index.rb b/lib/rdoc/generator/json_index.rb index 402634baf0..e21ba66bc2 100644 --- a/lib/rdoc/generator/json_index.rb +++ b/lib/rdoc/generator/json_index.rb @@ -5,280 +5,284 @@ rescue LoadError end -## -# 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. - # +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 +module RDoc + module Generator + ## + # 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 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. + # +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. + ## + # Builds the JSON index as a Hash. - def build_index - reset @store.all_files.sort, @store.all_classes_and_modules.sort + def build_index + reset @store.all_files.sort, @store.all_classes_and_modules.sort - index_classes - index_methods - index_pages + index_classes + index_methods + index_pages - { :index => @index } - end + { :index => @index } + end - ## - # Output progress information if debugging is enabled + ## + # Output progress information if debugging is enabled - def debug_msg(*msg) - return unless $DEBUG_RDOC - $stderr.puts(*msg) - end + def debug_msg(*msg) + return unless $DEBUG_RDOC + $stderr.puts(*msg) + end - ## - # Writes the JSON index to disk + ## + # Writes the JSON index to disk - def generate - debug_msg "Generating JSON index" + def generate + debug_msg "Generating JSON index" - debug_msg " writing search index to %s" % SEARCH_INDEX_FILE - data = build_index + debug_msg " writing search index to %s" % SEARCH_INDEX_FILE + data = build_index - return if @options.dry_run + return if @options.dry_run - out_dir = @base_dir + @options.op_dir - index_file = out_dir + SEARCH_INDEX_FILE + out_dir = @base_dir + @options.op_dir + index_file = out_dir + SEARCH_INDEX_FILE - FileUtils.mkdir_p index_file.dirname, :verbose => $DEBUG_RDOC + FileUtils.mkdir_p index_file.dirname, :verbose => $DEBUG_RDOC - index_file.open 'w', 0644 do |io| - io.set_encoding Encoding::UTF_8 - io.write 'var search_data = ' + index_file.open 'w', 0644 do |io| + io.set_encoding ::Encoding::UTF_8 + io.write 'var search_data = ' - JSON.dump data, io - end - unless ENV['SOURCE_DATE_EPOCH'].nil? - index_file.utime index_file.atime, Time.at(ENV['SOURCE_DATE_EPOCH'].to_i).gmtime - end + JSON.dump data, io + end + unless ENV['SOURCE_DATE_EPOCH'].nil? + index_file.utime index_file.atime, Time.at(ENV['SOURCE_DATE_EPOCH'].to_i).gmtime + end - Dir.chdir @template_dir do - Dir['**/*.js'].each do |source| - dest = File.join out_dir, source + Dir.chdir @template_dir do + Dir['**/*.js'].each do |source| + dest = File.join out_dir, source - FileUtils.install source, dest, :mode => 0644, :preserve => true, :verbose => $DEBUG_RDOC + FileUtils.install source, dest, :mode => 0644, :preserve => true, :verbose => $DEBUG_RDOC + end + end end - end - end - ## - # Compress the search_index.js file using gzip + ## + # Compress the search_index.js file using gzip - def generate_gzipped - return if @options.dry_run or not defined?(Zlib) + def generate_gzipped + return if @options.dry_run or not defined?(Zlib) - debug_msg "Compressing generated JSON index" - out_dir = @base_dir + @options.op_dir + debug_msg "Compressing generated JSON index" + out_dir = @base_dir + @options.op_dir - search_index_file = out_dir + SEARCH_INDEX_FILE - outfile = out_dir + "#{search_index_file}.gz" + search_index_file = out_dir + SEARCH_INDEX_FILE + outfile = out_dir + "#{search_index_file}.gz" - debug_msg "Reading the JSON index file from %s" % search_index_file - search_index = search_index_file.read(mode: 'r:utf-8') + debug_msg "Reading the JSON index file from %s" % search_index_file + search_index = search_index_file.read(mode: 'r:utf-8') - debug_msg "Writing gzipped search index to %s" % outfile + debug_msg "Writing gzipped search index to %s" % outfile - Zlib::GzipWriter.open(outfile) do |gz| - gz.mtime = File.mtime(search_index_file) - gz.orig_name = search_index_file.basename.to_s - gz.write search_index - gz.close - end + Zlib::GzipWriter.open(outfile) do |gz| + gz.mtime = File.mtime(search_index_file) + gz.orig_name = search_index_file.basename.to_s + gz.write search_index + gz.close + end - # GZip the rest of the js files - Dir.chdir @template_dir do - Dir['**/*.js'].each do |source| - dest = out_dir + source - outfile = out_dir + "#{dest}.gz" + # GZip the rest of the js files + Dir.chdir @template_dir do + Dir['**/*.js'].each do |source| + dest = out_dir + source + outfile = out_dir + "#{dest}.gz" - debug_msg "Reading the original js file from %s" % dest - data = dest.read + debug_msg "Reading the original js file from %s" % dest + data = dest.read - debug_msg "Writing gzipped file to %s" % outfile + debug_msg "Writing gzipped file to %s" % outfile - Zlib::GzipWriter.open(outfile) do |gz| - gz.mtime = File.mtime(dest) - gz.orig_name = dest.basename.to_s - gz.write data - gz.close + Zlib::GzipWriter.open(outfile) do |gz| + gz.mtime = File.mtime(dest) + gz.orig_name = dest.basename.to_s + gz.write data + gz.close + end + end end end - end - end - ## - # Adds classes and modules to the index + ## + # Adds classes and modules to the index - def index_classes - debug_msg " generating class search index" + def index_classes + debug_msg " generating class search index" - documented = @classes.uniq.select do |klass| - klass.document_self_or_methods - end + 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 + 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 + ## + # Adds methods to the index - def index_methods - debug_msg " generating method search index" + def index_methods + debug_msg " generating method search index" - list = @classes.uniq.flat_map do |klass| - klass.method_list - end.sort_by do |method| - [method.name, method.parent.full_name] - end + list = @classes.uniq.flat_map do |klass| + klass.method_list + end.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 + 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 + ## + # Adds pages to the index - def index_pages - debug_msg " generating pages search index" + def index_pages + debug_msg " generating pages search index" - pages = @files.select do |file| - file.text? && file.full_name != @options.main_page - end + pages = @files.select do |file| + file.text? && file.full_name != @options.main_page + 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 + 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 - def reset(files, classes) # :nodoc: - @files = files - @classes = classes + def reset(files, classes) # :nodoc: + @files = files + @classes = classes - @index = { - :searchIndex => [], - :longSearchIndex => [], - :info => [] - } - end + @index = { + :searchIndex => [], + :longSearchIndex => [], + :info => [] + } + end - ## - # Removes whitespace and downcases +string+ + ## + # Removes whitespace and downcases +string+ - def search_string(string) - string.downcase.gsub(/\s/, '') - end + def search_string(string) + string.downcase.gsub(/\s/, '') + end + end + end end diff --git a/lib/rdoc/generator/markup.rb b/lib/rdoc/generator/markup.rb index 38e3251932..d617a5820f 100644 --- a/lib/rdoc/generator/markup.rb +++ b/lib/rdoc/generator/markup.rb @@ -1,196 +1,214 @@ # frozen_string_literal: true -## -# 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 + module Generator + ## + # 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 Markup + + ## + # Generates a relative URL from this object's path to +target_path+ + + def aref_to(target_path) + ::RDoc::Markup::ToHtml.gen_relative_url path, target_path + end + + ## + # Generates a relative URL from +from_path+ to this object's path + + def as_href(from_path) + ::RDoc::Markup::ToHtml.gen_relative_url from_path, path + end + + ## + # Handy wrapper for marking up this object's comment + + def description + markup @comment + end + + ## + # Creates an RDoc::Markup::ToHtmlCrossref formatter + + def formatter + return @formatter if defined? @formatter + + options = @store.options + this = ::RDoc::Context === self ? self : @parent + + @formatter = ::RDoc::Markup::ToHtmlCrossref.new( + this.path, this, + pipe: options.pipe, + output_decoration: options.output_decoration, + hyperlink_all: options.hyperlink_all, + show_hash: options.show_hash, + autolink_excluded_words: options.autolink_excluded_words || [], + warn_missing_rdoc_ref: options.warn_missing_rdoc_ref + ) + @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 + # will replace the %s using sprintf on the +url+. + + def cvs_url(url, full_path) + if /%s/ =~ url + sprintf url, full_path + else + url + full_path + end + end + + ## + # The preferred URL for this object. + + def canonical_url + options = @store.options + if path + File.join(options.canonical_root, path.to_s) + else + options.canonical_root + end + end -module RDoc::Generator::Markup - - ## - # Generates a relative URL from this object's path to +target_path+ - - def aref_to(target_path) - RDoc::Markup::ToHtml.gen_relative_url path, target_path - end - - ## - # Generates a relative URL from +from_path+ to this object's path - - def as_href(from_path) - RDoc::Markup::ToHtml.gen_relative_url from_path, path - end - - ## - # Handy wrapper for marking up this object's comment - - def description - markup @comment - end - - ## - # Creates an RDoc::Markup::ToHtmlCrossref formatter - - def formatter - return @formatter if defined? @formatter - - options = @store.options - this = RDoc::Context === self ? self : @parent - - @formatter = RDoc::Markup::ToHtmlCrossref.new( - this.path, this, - pipe: options.pipe, - output_decoration: options.output_decoration, - hyperlink_all: options.hyperlink_all, - show_hash: options.show_hash, - autolink_excluded_words: options.autolink_excluded_words || [], - warn_missing_rdoc_ref: options.warn_missing_rdoc_ref - ) - @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 - # will replace the %s using sprintf on the +url+. - - def cvs_url(url, full_path) - if /%s/ =~ url - sprintf url, full_path - else - url + full_path - end - end - - ## - # The preferred URL for this object. - - def canonical_url - options = @store.options - if path - File.join(options.canonical_root, path.to_s) - else - options.canonical_root end end - end -class RDoc::CodeObject +module RDoc + class CodeObject - include RDoc::Generator::Markup + include ::RDoc::Generator::Markup + end end -class RDoc::AnyMethod +module RDoc + class AnyMethod - ## - # Creates an HTML link to the superclass method called by this method. + ## + # Creates an HTML link to the superclass method called by this method. - def superclass_method_link - target = superclass_method - return unless target + def superclass_method_link + target = superclass_method + return unless target - html_formatter = formatter - name = target.full_name + html_formatter = formatter + name = target.full_name - html_formatter.link name, html_formatter.convert_string(name) - end + html_formatter.link name, html_formatter.convert_string(name) + end + end end -class RDoc::MethodAttr +module RDoc + class MethodAttr - ## - # Prepend +src+ with line numbers. + ## + # Prepend +src+ with line numbers. - def add_line_numbers(src) - return if src.empty? || !line - start_line = line - end_line = start_line + src.count("\n") - number_digits = end_line.to_s.length + def add_line_numbers(src) + return if src.empty? || !line + start_line = line + end_line = start_line + src.count("\n") + number_digits = end_line.to_s.length - current_line = start_line - src.gsub!(/^/) do - res = "#{current_line.to_s.rjust(number_digits)} " + current_line = start_line + src.gsub!(/^/) do + res = "#{current_line.to_s.rjust(number_digits)} " - current_line += 1 - res + current_line += 1 + res + end end - end - ## - # Prepend +src+ with a comment that declares its location in the source. + ## + # Prepend +src+ with a comment that declares its location in the source. - def add_location_comment(src) - path = CGI.escapeHTML(file.relative_name) - if options.line_numbers && !src.empty? - src.prepend("# File #{path}\n") - else - src.prepend("# File #{path}, line #{line}\n") + def add_location_comment(src) + path = CGI.escapeHTML(file.relative_name) + if options.line_numbers && !src.empty? + src.prepend("# File #{path}\n") + else + src.prepend("# File #{path}, line #{line}\n") + end end - end - ## - # Turns the method's token stream into HTML. - # - # Prepends line numbers if +options.line_numbers+ is true. + ## + # Turns the method's token stream into HTML. + # + # Prepends line numbers if +options.line_numbers+ is true. - def markup_code - return '' if !@token_stream + def markup_code + return '' if !@token_stream - src = RDoc::TokenStream.to_html @token_stream + src = ::RDoc::TokenStream.to_html @token_stream - # dedent the source - common_indent = src.length - src.scan(/^ *(?=\S)/) do |whitespace| - common_indent = whitespace.length if whitespace.length < common_indent - break if common_indent == 0 - end - src.gsub!(/^#{' ' * common_indent}/, '') if common_indent > 0 + # dedent the source + common_indent = src.length + src.scan(/^ *(?=\S)/) do |whitespace| + common_indent = whitespace.length if whitespace.length < common_indent + break if common_indent == 0 + end + src.gsub!(/^#{' ' * common_indent}/, '') if common_indent > 0 + + if source_language == 'ruby' + add_line_numbers(src) if options.line_numbers + add_location_comment(src) + end - if source_language == 'ruby' - add_line_numbers(src) if options.line_numbers - add_location_comment(src) + src end - src end - end -class RDoc::ClassModule +module RDoc + class ClassModule - ## - # Handy wrapper for marking up this class or module's comment + ## + # Handy wrapper for marking up this class or module's comment - def description - markup @comment_location - end + def description + markup @comment_location + end + end end -class RDoc::Context::Section +module RDoc + class Context + class Section - include RDoc::Generator::Markup + include ::RDoc::Generator::Markup + end + end end -class RDoc::TopLevel +module RDoc + class TopLevel - ## - # Returns a URL for this source file on some web repository. Use the -W - # command line option to set. + ## + # Returns a URL for this source file on some web repository. Use the -W + # command line option to set. - def cvs_url - url = @store.options.webcvs + def cvs_url + url = @store.options.webcvs - if /%s/ =~ url - url % @relative_name - else - url + @relative_name + if /%s/ =~ url + url % @relative_name + else + url + @relative_name + end end - end + end end diff --git a/lib/rdoc/generator/pot.rb b/lib/rdoc/generator/pot.rb index a20fde077b..f6f9d0c73f 100644 --- a/lib/rdoc/generator/pot.rb +++ b/lib/rdoc/generator/pot.rb @@ -1,94 +1,98 @@ # frozen_string_literal: true -## -# Generates a POT file. -# -# Here is a translator work flow with the generator. -# -# == Create .pot -# -# You create .pot file by pot formatter: -# -# % rdoc --format pot -# -# It generates doc/rdoc.pot. -# -# == Create .po -# -# You create .po file from doc/rdoc.pot. This operation is needed only -# the first time. This work flow assumes that you are a translator -# for Japanese. -# -# You create locale/ja/rdoc.po from doc/rdoc.pot. You can use msginit -# provided by GNU gettext or rmsginit provided by gettext gem. This -# work flow uses gettext gem because it is more portable than GNU -# gettext for Rubyists. Gettext gem is implemented by pure Ruby. -# -# % gem install gettext -# % mkdir -p locale/ja -# % rmsginit --input doc/rdoc.pot --output locale/ja/rdoc.po --locale ja -# -# Translate messages in .po -# -# You translate messages in .po by a PO file editor. po-mode.el exists -# for Emacs users. There are some GUI tools such as GTranslator. -# There are some Web services such as POEditor and Tansifex. You can -# edit by your favorite text editor because .po is a text file. -# Generate localized documentation -# -# You can generate localized documentation with locale/ja/rdoc.po: -# -# % rdoc --locale ja -# -# You can find documentation in Japanese in doc/. Yay! -# -# == Update translation -# -# You need to update translation when your application is added or -# modified messages. -# -# You can update .po by the following command lines: -# -# % rdoc --format pot -# % rmsgmerge --update locale/ja/rdoc.po doc/rdoc.pot -# -# You edit locale/ja/rdoc.po to translate new messages. +module RDoc + module Generator + ## + # Generates a POT file. + # + # Here is a translator work flow with the generator. + # + # == Create .pot + # + # You create .pot file by pot formatter: + # + # % rdoc --format pot + # + # It generates doc/rdoc.pot. + # + # == Create .po + # + # You create .po file from doc/rdoc.pot. This operation is needed only + # the first time. This work flow assumes that you are a translator + # for Japanese. + # + # You create locale/ja/rdoc.po from doc/rdoc.pot. You can use msginit + # provided by GNU gettext or rmsginit provided by gettext gem. This + # work flow uses gettext gem because it is more portable than GNU + # gettext for Rubyists. Gettext gem is implemented by pure Ruby. + # + # % gem install gettext + # % mkdir -p locale/ja + # % rmsginit --input doc/rdoc.pot --output locale/ja/rdoc.po --locale ja + # + # Translate messages in .po + # + # You translate messages in .po by a PO file editor. po-mode.el exists + # for Emacs users. There are some GUI tools such as GTranslator. + # There are some Web services such as POEditor and Tansifex. You can + # edit by your favorite text editor because .po is a text file. + # Generate localized documentation + # + # You can generate localized documentation with locale/ja/rdoc.po: + # + # % rdoc --locale ja + # + # You can find documentation in Japanese in doc/. Yay! + # + # == Update translation + # + # You need to update translation when your application is added or + # modified messages. + # + # You can update .po by the following command lines: + # + # % rdoc --format pot + # % rmsgmerge --update locale/ja/rdoc.po doc/rdoc.pot + # + # You edit locale/ja/rdoc.po to translate new messages. -class RDoc::Generator::POT + class POT - RDoc::RDoc.add_generator self + ::RDoc::RDoc.add_generator self - ## - # Description of this generator + ## + # Description of this generator - DESCRIPTION = 'creates .pot file' + DESCRIPTION = 'creates .pot file' - ## - # Set up a new .pot generator + ## + # Set up a new .pot generator - def initialize(store, options) #:not-new: - @options = options - @store = store - end + def initialize(store, options) #:not-new: + @options = options + @store = store + end - ## - # Writes .pot to disk. + ## + # Writes .pot to disk. - def generate - po = extract_messages - pot_path = 'rdoc.pot' - File.open(pot_path, "w") do |pot| - pot.print(po.to_s) - end - end + def generate + po = extract_messages + pot_path = 'rdoc.pot' + File.open(pot_path, "w") do |pot| + pot.print(po.to_s) + end + end - private - def extract_messages - extractor = MessageExtractor.new(@store) - extractor.extract - end + private + def extract_messages + extractor = MessageExtractor.new(@store) + extractor.extract + end - require_relative 'pot/message_extractor' - require_relative 'pot/po' - require_relative 'pot/po_entry' + require_relative 'pot/message_extractor' + require_relative 'pot/po' + require_relative 'pot/po_entry' + end + end end diff --git a/lib/rdoc/generator/pot/message_extractor.rb b/lib/rdoc/generator/pot/message_extractor.rb index ee6d847bd6..68328d06a7 100644 --- a/lib/rdoc/generator/pot/message_extractor.rb +++ b/lib/rdoc/generator/pot/message_extractor.rb @@ -1,68 +1,74 @@ # frozen_string_literal: true -## -# Extracts message from RDoc::Store +module RDoc + module Generator + class POT + ## + # Extracts message from RDoc::Store -class RDoc::Generator::POT::MessageExtractor + class MessageExtractor - ## - # Creates a message extractor for +store+. + ## + # Creates a message extractor for +store+. - def initialize(store) - @store = store - @po = RDoc::Generator::POT::PO.new - end + def initialize(store) + @store = store + @po = ::RDoc::Generator::POT::PO.new + end - ## - # Extracts messages from +store+, stores them into - # RDoc::Generator::POT::PO and returns it. + ## + # Extracts messages from +store+, stores them into + # RDoc::Generator::POT::PO and returns it. - def extract - @store.all_classes_and_modules.each do |klass| - extract_from_klass(klass) - end - @po - end + def extract + @store.all_classes_and_modules.each do |klass| + extract_from_klass(klass) + end + @po + end - private + private - def extract_from_klass(klass) - extract_text(klass.comment_location, klass.full_name) + def extract_from_klass(klass) + extract_text(klass.comment_location, klass.full_name) - klass.each_section do |section, constants, attributes| - extract_text(section.title, "#{klass.full_name}: section title") - section.comments.each do |comment| - extract_text(comment, "#{klass.full_name}: #{section.title}") - end - end + klass.each_section do |section, constants, attributes| + extract_text(section.title, "#{klass.full_name}: section title") + section.comments.each do |comment| + extract_text(comment, "#{klass.full_name}: #{section.title}") + end + end - klass.constants.each do |constant| - extract_text(constant.comment, constant.full_name) - end + klass.constants.each do |constant| + extract_text(constant.comment, constant.full_name) + end - klass.attributes.each do |attribute| - extract_text(attribute.comment, attribute.full_name) - end + klass.attributes.each do |attribute| + extract_text(attribute.comment, attribute.full_name) + end - klass.each_method do |method| - extract_text(method.comment, method.full_name) - end - end + klass.each_method do |method| + extract_text(method.comment, method.full_name) + end + end - def extract_text(text, comment, location = nil) - return if text.nil? + def extract_text(text, comment, location = nil) + return if text.nil? - options = { - :extracted_comment => comment, - :references => [location].compact, - } - i18n_text = RDoc::I18n::Text.new(text) - i18n_text.extract_messages do |part| - @po.add(entry(part[:paragraph], options)) - end - end + options = { + :extracted_comment => comment, + :references => [location].compact, + } + i18n_text = ::RDoc::I18n::Text.new(text) + i18n_text.extract_messages do |part| + @po.add(entry(part[:paragraph], options)) + end + end - def entry(msgid, options) - RDoc::Generator::POT::POEntry.new(msgid, options) - end + def entry(msgid, options) + ::RDoc::Generator::POT::POEntry.new(msgid, options) + end + end + end + end end diff --git a/lib/rdoc/generator/pot/po.rb b/lib/rdoc/generator/pot/po.rb index 401025f6e8..95be17a0b5 100644 --- a/lib/rdoc/generator/pot/po.rb +++ b/lib/rdoc/generator/pot/po.rb @@ -1,50 +1,53 @@ # frozen_string_literal: true -## -# Generates a PO format text +module RDoc + module Generator + class POT + ## + # Generates a PO format text -class RDoc::Generator::POT::PO + class PO - ## - # Creates an object that represents PO format. + ## + # Creates an object that represents PO format. - def initialize - @entries = {} - add_header - end + def initialize + @entries = {} + add_header + end - ## - # Adds a PO entry to the PO. + ## + # Adds a PO entry to the PO. - def add(entry) - existing_entry = @entries[entry.msgid] - if existing_entry - entry = existing_entry.merge(entry) - end - @entries[entry.msgid] = entry - end + def add(entry) + existing_entry = @entries[entry.msgid] + if existing_entry + entry = existing_entry.merge(entry) + end + @entries[entry.msgid] = entry + end - ## - # Returns PO format text for the PO. + ## + # Returns PO format text for the PO. - def to_s - sort_entries.map(&:to_s).join("\n") - end + def to_s + sort_entries.map(&:to_s).join("\n") + end - private + private - def add_header - add(header_entry) - end + def add_header + add(header_entry) + end - def header_entry - comment = <<-COMMENT + def header_entry + comment = <<-COMMENT SOME DESCRIPTIVE TITLE. Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER This file is distributed under the same license as the PACKAGE package. FIRST AUTHOR , YEAR. COMMENT - content = <<-CONTENT + content = <<-CONTENT Project-Id-Version: PACKAGE VERSEION Report-Msgid-Bugs-To: PO-Revision-Date: YEAR-MO_DA HO:MI+ZONE @@ -57,23 +60,26 @@ def header_entry Plural-Forms: nplurals=INTEGER; plural=EXPRESSION; CONTENT - options = { - :msgstr => content, - :translator_comment => comment, - :flags => ['fuzzy'], - } - RDoc::Generator::POT::POEntry.new('', options) - end + options = { + :msgstr => content, + :translator_comment => comment, + :flags => ['fuzzy'], + } + ::RDoc::Generator::POT::POEntry.new('', options) + end - def sort_entries - headers, messages = @entries.values.partition do |entry| - entry.msgid.empty? - end - # TODO: sort by location - sorted_messages = messages.sort_by do |entry| - entry.msgid + def sort_entries + headers, messages = @entries.values.partition do |entry| + entry.msgid.empty? + end + # TODO: sort by location + sorted_messages = messages.sort_by do |entry| + entry.msgid + end + headers + sorted_messages + end + + end end - headers + sorted_messages end - end diff --git a/lib/rdoc/generator/pot/po_entry.rb b/lib/rdoc/generator/pot/po_entry.rb index 8de260eef1..e873b57926 100644 --- a/lib/rdoc/generator/pot/po_entry.rb +++ b/lib/rdoc/generator/pot/po_entry.rb @@ -1,141 +1,147 @@ # frozen_string_literal: true -## -# A PO entry in PO - -class RDoc::Generator::POT::POEntry - - # The msgid content - attr_reader :msgid - - # The msgstr content - attr_reader :msgstr - - # The comment content created by translator (PO editor) - attr_reader :translator_comment - - # The comment content extracted from source file - attr_reader :extracted_comment - - # The locations where the PO entry is extracted - attr_reader :references - - # The flags of the PO entry - attr_reader :flags - - ## - # Creates a PO entry for +msgid+. Other values can be specified by - # +options+. - - def initialize(msgid, options = {}) - @msgid = msgid - @msgstr = options[:msgstr] || "" - @translator_comment = options[:translator_comment] - @extracted_comment = options[:extracted_comment] - @references = options[:references] || [] - @flags = options[:flags] || [] - end - - ## - # Returns the PO entry in PO format. - - def to_s - entry = '' - entry += format_translator_comment - entry += format_extracted_comment - entry += format_references - entry += format_flags - entry += <<-ENTRY +module RDoc + module Generator + class POT + ## + # A PO entry in PO + + class POEntry + + # The msgid content + attr_reader :msgid + + # The msgstr content + attr_reader :msgstr + + # The comment content created by translator (PO editor) + attr_reader :translator_comment + + # The comment content extracted from source file + attr_reader :extracted_comment + + # The locations where the PO entry is extracted + attr_reader :references + + # The flags of the PO entry + attr_reader :flags + + ## + # Creates a PO entry for +msgid+. Other values can be specified by + # +options+. + + def initialize(msgid, options = {}) + @msgid = msgid + @msgstr = options[:msgstr] || "" + @translator_comment = options[:translator_comment] + @extracted_comment = options[:extracted_comment] + @references = options[:references] || [] + @flags = options[:flags] || [] + end + + ## + # Returns the PO entry in PO format. + + def to_s + entry = '' + entry += format_translator_comment + entry += format_extracted_comment + entry += format_references + entry += format_flags + entry += <<-ENTRY msgid #{format_message(@msgid)} msgstr #{format_message(@msgstr)} ENTRY - end - - ## - # Merges the PO entry with +other_entry+. - - def merge(other_entry) - options = { - :extracted_comment => merge_string(@extracted_comment, - other_entry.extracted_comment), - :translator_comment => merge_string(@translator_comment, - other_entry.translator_comment), - :references => merge_array(@references, - other_entry.references), - :flags => merge_array(@flags, - other_entry.flags), - } - self.class.new(@msgid, options) - end - - private - - def format_comment(mark, comment) - return '' unless comment - return '' if comment.empty? - - formatted_comment = '' - comment.each_line do |line| - formatted_comment += "#{mark} #{line}" - end - formatted_comment += "\n" unless formatted_comment.end_with?("\n") - formatted_comment - end + end + + ## + # Merges the PO entry with +other_entry+. + + def merge(other_entry) + options = { + :extracted_comment => merge_string(@extracted_comment, + other_entry.extracted_comment), + :translator_comment => merge_string(@translator_comment, + other_entry.translator_comment), + :references => merge_array(@references, + other_entry.references), + :flags => merge_array(@flags, + other_entry.flags), + } + self.class.new(@msgid, options) + end + + private + + def format_comment(mark, comment) + return '' unless comment + return '' if comment.empty? + + formatted_comment = '' + comment.each_line do |line| + formatted_comment += "#{mark} #{line}" + end + formatted_comment += "\n" unless formatted_comment.end_with?("\n") + formatted_comment + end + + def format_translator_comment + format_comment('#', @translator_comment) + end + + def format_extracted_comment + format_comment('#.', @extracted_comment) + end + + def format_references + return '' if @references.empty? + + formatted_references = '' + @references.sort.each do |file, line| + formatted_references += "\#: #{file}:#{line}\n" + end + formatted_references + end + + def format_flags + return '' if @flags.empty? + + formatted_flags = flags.join(",") + "\#, #{formatted_flags}\n" + end + + def format_message(message) + return "\"#{escape(message)}\"" unless message.include?("\n") + + formatted_message = '""' + message.each_line do |line| + formatted_message += "\n" + formatted_message += "\"#{escape(line)}\"" + end + formatted_message + end + + def escape(string) + string.gsub(/["\\\t\n]/) do |special_character| + case special_character + when "\t" + "\\t" + when "\n" + "\\n" + else + "\\#{special_character}" + end + end + end + + def merge_string(string1, string2) + [string1, string2].compact.join("\n") + end + + def merge_array(array1, array2) + (array1 + array2).uniq + end - def format_translator_comment - format_comment('#', @translator_comment) - end - - def format_extracted_comment - format_comment('#.', @extracted_comment) - end - - def format_references - return '' if @references.empty? - - formatted_references = '' - @references.sort.each do |file, line| - formatted_references += "\#: #{file}:#{line}\n" - end - formatted_references - end - - def format_flags - return '' if @flags.empty? - - formatted_flags = flags.join(",") - "\#, #{formatted_flags}\n" - end - - def format_message(message) - return "\"#{escape(message)}\"" unless message.include?("\n") - - formatted_message = '""' - message.each_line do |line| - formatted_message += "\n" - formatted_message += "\"#{escape(line)}\"" - end - formatted_message - end - - def escape(string) - string.gsub(/["\\\t\n]/) do |special_character| - case special_character - when "\t" - "\\t" - when "\n" - "\\n" - else - "\\#{special_character}" end end end - - def merge_string(string1, string2) - [string1, string2].compact.join("\n") - end - - def merge_array(array1, array2) - (array1 + array2).uniq - end - end diff --git a/lib/rdoc/generator/ri.rb b/lib/rdoc/generator/ri.rb index 32f518ac71..a6d2b00d30 100644 --- a/lib/rdoc/generator/ri.rb +++ b/lib/rdoc/generator/ri.rb @@ -1,30 +1,34 @@ # frozen_string_literal: true -## -# Generates ri data files +module RDoc + module Generator + ## + # Generates ri data files -class RDoc::Generator::RI + class RI - RDoc::RDoc.add_generator self + ::RDoc::RDoc.add_generator self - ## - # Description of this generator + ## + # Description of this generator - DESCRIPTION = 'creates ri data files' + DESCRIPTION = 'creates ri data files' - ## - # Set up a new ri generator + ## + # Set up a new ri generator - def initialize(store, options) #:not-new: - @options = options - @store = store - @store.path = '.' - end + def initialize(store, options) #:not-new: + @options = options + @store = store + @store.path = '.' + end - ## - # Writes the parsed data store to disk for use by ri. + ## + # Writes the parsed data store to disk for use by ri. - def generate - @store.save - end + def generate + @store.save + end + end + end end diff --git a/lib/rdoc/generator/template/aliki/_footer.rhtml b/lib/rdoc/generator/template/aliki/_footer.rhtml index 525d853dec..d64c3797d3 100644 --- a/lib/rdoc/generator/template/aliki/_footer.rhtml +++ b/lib/rdoc/generator/template/aliki/_footer.rhtml @@ -16,7 +16,7 @@ diff --git a/lib/rdoc/generator/template/aliki/_head.rhtml b/lib/rdoc/generator/template/aliki/_head.rhtml index c6c238d26c..4412f40531 100644 --- a/lib/rdoc/generator/template/aliki/_head.rhtml +++ b/lib/rdoc/generator/template/aliki/_head.rhtml @@ -112,52 +112,52 @@ <%- @options.template_stylesheets.each do |stylesheet| %> <%- end %> diff --git a/lib/rdoc/generator/template/darkfish/_footer.rhtml b/lib/rdoc/generator/template/darkfish/_footer.rhtml index 620cf01484..db483866ba 100644 --- a/lib/rdoc/generator/template/darkfish/_footer.rhtml +++ b/lib/rdoc/generator/template/darkfish/_footer.rhtml @@ -1,5 +1,5 @@ diff --git a/lib/rdoc/i18n.rb b/lib/rdoc/i18n.rb index f209a9a6f6..2491045f6d 100644 --- a/lib/rdoc/i18n.rb +++ b/lib/rdoc/i18n.rb @@ -1,10 +1,12 @@ # frozen_string_literal: true -## -# This module provides i18n related features. +module RDoc + ## + # This module provides i18n related features. -module RDoc::I18n + module I18n - autoload :Locale, "#{__dir__}/i18n/locale" - require_relative 'i18n/text' + autoload :Locale, "#{__dir__}/i18n/locale" + require_relative 'i18n/text' + end end diff --git a/lib/rdoc/i18n/locale.rb b/lib/rdoc/i18n/locale.rb index 6a70d6c986..3d42ff1ca2 100644 --- a/lib/rdoc/i18n/locale.rb +++ b/lib/rdoc/i18n/locale.rb @@ -1,102 +1,106 @@ # frozen_string_literal: true -## -# A message container for a locale. -# -# This object provides the following two features: -# -# * Loads translated messages from .po file. -# * Translates a message into the locale. - -class RDoc::I18n::Locale - - @@locales = {} # :nodoc: - - class << self - +module RDoc + module I18n ## - # Returns the locale object for +locale_name+. - - def [](locale_name) - @@locales[locale_name] ||= new(locale_name) - end - - ## - # Sets the locale object for +locale_name+. + # A message container for a locale. # - # Normally, this method is not used. This method is useful for - # testing. - - def []=(locale_name, locale) - @@locales[locale_name] = locale - end - - end - - ## - # The name of the locale. It uses IETF language tag format - # +[language[_territory][.codeset][@modifier]]+. - # - # See also {BCP 47 - Tags for Identifying - # Languages}[http://tools.ietf.org/rfc/bcp/bcp47.txt]. - - attr_reader :name - - ## - # Creates a new locale object for +name+ locale. +name+ must - # follow IETF language tag format. - - def initialize(name) - @name = name - @messages = {} - end + # This object provides the following two features: + # + # * Loads translated messages from .po file. + # * Translates a message into the locale. + + class Locale + + @@locales = {} # :nodoc: + + class << self + + ## + # Returns the locale object for +locale_name+. + + def [](locale_name) + @@locales[locale_name] ||= new(locale_name) + end + + ## + # Sets the locale object for +locale_name+. + # + # Normally, this method is not used. This method is useful for + # testing. + + def []=(locale_name, locale) + @@locales[locale_name] = locale + end + + end + + ## + # The name of the locale. It uses IETF language tag format + # +[language[_territory][.codeset][@modifier]]+. + # + # See also {BCP 47 - Tags for Identifying + # Languages}[http://tools.ietf.org/rfc/bcp/bcp47.txt]. + + attr_reader :name + + ## + # Creates a new locale object for +name+ locale. +name+ must + # follow IETF language tag format. + + def initialize(name) + @name = name + @messages = {} + end + + ## + # Loads translation messages from +locale_directory+/+@name+/rdoc.po + # or +locale_directory+/+@name+.po. The former has high priority. + # + # This method requires gettext gem for parsing .po file. If you + # don't have gettext gem, this method doesn't load .po file. This + # method warns and returns +false+. + # + # Returns +true+ if succeeded, +false+ otherwise. + + def load(locale_directory) + return false if @name.nil? + + po_file_candidates = [ + File.join(locale_directory, @name, 'rdoc.po'), + File.join(locale_directory, "#{@name}.po"), + ] + po_file = po_file_candidates.find do |po_file_candidate| + File.exist?(po_file_candidate) + end + return false unless po_file + + begin + require 'gettext/po_parser' + require 'gettext/mo' + rescue LoadError + warn('Need gettext gem for i18n feature:') + warn(' gem install gettext') + return false + end + + po_parser = GetText::POParser.new + messages = GetText::MO.new + po_parser.report_warning = false + po_parser.parse_file(po_file, messages) + + @messages.merge!(messages) + + true + end + + ## + # Translates the +message+ into locale. If there is no translation + # messages for +message+ in locale, +message+ itself is returned. + + def translate(message) + @messages[message] || message + end - ## - # Loads translation messages from +locale_directory+/+@name+/rdoc.po - # or +locale_directory+/+@name+.po. The former has high priority. - # - # This method requires gettext gem for parsing .po file. If you - # don't have gettext gem, this method doesn't load .po file. This - # method warns and returns +false+. - # - # Returns +true+ if succeeded, +false+ otherwise. - - def load(locale_directory) - return false if @name.nil? - - po_file_candidates = [ - File.join(locale_directory, @name, 'rdoc.po'), - File.join(locale_directory, "#{@name}.po"), - ] - po_file = po_file_candidates.find do |po_file_candidate| - File.exist?(po_file_candidate) - end - return false unless po_file - - begin - require 'gettext/po_parser' - require 'gettext/mo' - rescue LoadError - warn('Need gettext gem for i18n feature:') - warn(' gem install gettext') - return false end - - po_parser = GetText::POParser.new - messages = GetText::MO.new - po_parser.report_warning = false - po_parser.parse_file(po_file, messages) - - @messages.merge!(messages) - - true - end - - ## - # Translates the +message+ into locale. If there is no translation - # messages for +message+ in locale, +message+ itself is returned. - - def translate(message) - @messages[message] || message end - end diff --git a/lib/rdoc/i18n/text.rb b/lib/rdoc/i18n/text.rb index d3008e33e3..5a68a78126 100644 --- a/lib/rdoc/i18n/text.rb +++ b/lib/rdoc/i18n/text.rb @@ -1,126 +1,130 @@ # frozen_string_literal: true -## -# An i18n supported text. -# -# This object provides the following two features: -# -# * Extracts translation messages from wrapped raw text. -# * Translates wrapped raw text in specified locale. -# -# Wrapped raw text is one of String, RDoc::Comment or Array of them. +module RDoc + module I18n + ## + # An i18n supported text. + # + # This object provides the following two features: + # + # * Extracts translation messages from wrapped raw text. + # * Translates wrapped raw text in specified locale. + # + # Wrapped raw text is one of String, RDoc::Comment or Array of them. -class RDoc::I18n::Text + class Text - ## - # Creates a new i18n supported text for +raw+ text. + ## + # Creates a new i18n supported text for +raw+ text. - def initialize(raw) - @raw = raw - end + def initialize(raw) + @raw = raw + end - ## - # Extracts translation target messages and yields each message. - # - # Each yielded message is a Hash. It consists of the followings: - # - # :type :: :paragraph - # :paragraph :: String (The translation target message itself.) - # :line_no :: Integer (The line number of the :paragraph is started.) - # - # The above content may be added in the future. + ## + # Extracts translation target messages and yields each message. + # + # Each yielded message is a Hash. It consists of the followings: + # + # :type :: :paragraph + # :paragraph :: String (The translation target message itself.) + # :line_no :: Integer (The line number of the :paragraph is started.) + # + # The above content may be added in the future. - def extract_messages - parse do |part| - case part[:type] - when :empty_line - # ignore - when :paragraph - yield(part) + def extract_messages + parse do |part| + case part[:type] + when :empty_line + # ignore + when :paragraph + yield(part) + end + end end - end - end - # Translates raw text into +locale+. - def translate(locale) - translated_text = '' - parse do |part| - case part[:type] - when :paragraph - translated_text += locale.translate(part[:paragraph]) - when :empty_line - translated_text += part[:line] - else - raise "should not reach here: unexpected type: #{type}" + # Translates raw text into +locale+. + def translate(locale) + translated_text = '' + parse do |part| + case part[:type] + when :paragraph + translated_text += locale.translate(part[:paragraph]) + when :empty_line + translated_text += part[:line] + else + raise "should not reach here: unexpected type: #{type}" + end + end + translated_text end - end - translated_text - end - private - def parse(&block) - paragraph = '' - paragraph_start_line = 0 - line_no = 0 + private + def parse(&block) + paragraph = '' + paragraph_start_line = 0 + line_no = 0 - each_line(@raw) do |line| - line_no += 1 - case line - when /\A\s*\z/ - if paragraph.empty? - emit_empty_line_event(line, line_no, &block) - else - paragraph += line - emit_paragraph_event(paragraph, paragraph_start_line, line_no, - &block) - paragraph = '' + each_line(@raw) do |line| + line_no += 1 + case line + when /\A\s*\z/ + if paragraph.empty? + emit_empty_line_event(line, line_no, &block) + else + paragraph += line + emit_paragraph_event(paragraph, paragraph_start_line, line_no, + &block) + paragraph = '' + end + else + paragraph_start_line = line_no if paragraph.empty? + paragraph += line + end + end + + unless paragraph.empty? + emit_paragraph_event(paragraph, paragraph_start_line, line_no, &block) end - else - paragraph_start_line = line_no if paragraph.empty? - paragraph += line end - end - unless paragraph.empty? - emit_paragraph_event(paragraph, paragraph_start_line, line_no, &block) - end - end + def each_line(raw, &block) + case raw + when ::RDoc::Comment + raw.text.each_line(&block) + when Hash + raw.each_value do |comments| + comments.each { |comment| each_line(comment, &block) } + end + else + raw.each_line(&block) + end + end - def each_line(raw, &block) - case raw - when RDoc::Comment - raw.text.each_line(&block) - when Hash - raw.each_value do |comments| - comments.each { |comment| each_line(comment, &block) } + def emit_empty_line_event(line, line_no) + part = { + :type => :empty_line, + :line => line, + :line_no => line_no, + } + yield(part) end - else - raw.each_line(&block) - end - end - def emit_empty_line_event(line, line_no) - part = { - :type => :empty_line, - :line => line, - :line_no => line_no, - } - yield(part) - end + def emit_paragraph_event(paragraph, paragraph_start_line, line_no, &block) + paragraph_part = { + :type => :paragraph, + :line_no => paragraph_start_line, + } + match_data = /(\s*)\z/.match(paragraph) + if match_data + paragraph_part[:paragraph] = match_data.pre_match + yield(paragraph_part) + emit_empty_line_event(match_data[1], line_no, &block) + else + paragraph_part[:paragraph] = paragraph + yield(paragraph_part) + end + end - def emit_paragraph_event(paragraph, paragraph_start_line, line_no, &block) - paragraph_part = { - :type => :paragraph, - :line_no => paragraph_start_line, - } - match_data = /(\s*)\z/.match(paragraph) - if match_data - paragraph_part[:paragraph] = match_data.pre_match - yield(paragraph_part) - emit_empty_line_event(match_data[1], line_no, &block) - else - paragraph_part[:paragraph] = paragraph - yield(paragraph_part) end end - end diff --git a/lib/rdoc/markdown/byte_runtime.rb b/lib/rdoc/markdown/byte_runtime.rb index 86c09fca3c..ae03e9cdc1 100644 --- a/lib/rdoc/markdown/byte_runtime.rb +++ b/lib/rdoc/markdown/byte_runtime.rb @@ -2,77 +2,79 @@ require 'strscan' -class RDoc::Markdown +module RDoc + class Markdown - ## - # Byte-offset replacements for the position helpers of the kpeg-generated - # parser runtime. - # - # The generated runtime addresses +@string+ by character index, which makes - # every position lookup scan the string from its beginning when the input - # contains non-ASCII characters, so parse time becomes quadratic in the - # input size. Generated rule bodies only save and restore +pos+ without - # inspecting it, so replacing these helpers is enough to switch the whole - # parser to byte offsets. - # - # get_byte (the grammar's `.`) consumes one character and returns its - # codepoint, exactly like the character-index runtime, so positions always - # stay on character boundaries and the only observable difference is the - # position values themselves. The input must be validly encoded in an - # ASCII-compatible encoding. - # - # The error-reporting helpers of the generated runtime (+current_line+, - # +current_column+, ...) are left as-is and would misreport locations when - # given byte offsets. They are unreachable: markdown is deliberately - # designed to parse any input somehow rather than fail (the root rule - # `Doc = BOM? Block*` cannot fail), so a parse failure means a bug in the - # grammar itself, and nothing in RDoc invokes +raise_error+ or - # +show_error+. Make these helpers byte-aware before using them for - # anything. + ## + # Byte-offset replacements for the position helpers of the kpeg-generated + # parser runtime. + # + # The generated runtime addresses +@string+ by character index, which makes + # every position lookup scan the string from its beginning when the input + # contains non-ASCII characters, so parse time becomes quadratic in the + # input size. Generated rule bodies only save and restore +pos+ without + # inspecting it, so replacing these helpers is enough to switch the whole + # parser to byte offsets. + # + # get_byte (the grammar's `.`) consumes one character and returns its + # codepoint, exactly like the character-index runtime, so positions always + # stay on character boundaries and the only observable difference is the + # position values themselves. The input must be validly encoded in an + # ASCII-compatible encoding. + # + # The error-reporting helpers of the generated runtime (+current_line+, + # +current_column+, ...) are left as-is and would misreport locations when + # given byte offsets. They are unreachable: markdown is deliberately + # designed to parse any input somehow rather than fail (the root rule + # `Doc = BOM? Block*` cannot fail), so a parse failure means a bug in the + # grammar itself, and nothing in RDoc invokes +raise_error+ or + # +show_error+. Make these helpers byte-aware before using them for + # anything. - module ByteRuntime - def set_string(string, pos) - @string = string - @string_size = string ? string.bytesize : 0 - @pos = pos - @position_line_offsets = nil - @scanner = string ? StringScanner.new(string) : nil - end + module ByteRuntime + def set_string(string, pos) + @string = string + @string_size = string ? string.bytesize : 0 + @pos = pos + @position_line_offsets = nil + @scanner = string ? StringScanner.new(string) : nil + end - def scan(reg) - @scanner.pos = @pos - if @scanner.skip(reg) - @pos = @scanner.pos - true + def scan(reg) + @scanner.pos = @pos + if @scanner.skip(reg) + @pos = @scanner.pos + true + end end - end - def match_string(str) - len = str.bytesize - if @string.byteslice(@pos, len) == str - @pos += len - str + def match_string(str) + len = str.bytesize + if @string.byteslice(@pos, len) == str + @pos += len + str + end end - end - def get_byte - byte = @string.getbyte(@pos) - return nil unless byte + def get_byte + byte = @string.getbyte(@pos) + return nil unless byte - if byte < 0x80 - @pos += 1 - byte - else - @scanner.pos = @pos - # /./ interprets the character in the string's own encoding - char = @scanner.scan(/./m) - @pos = @scanner.pos - char.ord + if byte < 0x80 + @pos += 1 + byte + else + @scanner.pos = @pos + # /./ interprets the character in the string's own encoding + char = @scanner.scan(/./m) + @pos = @scanner.pos + char.ord + end end - end - def get_text(start) - @string.byteslice(start, @pos - start) + def get_text(start) + @string.byteslice(start, @pos - start) + end end end end diff --git a/lib/rdoc/markup.rb b/lib/rdoc/markup.rb index 4348e29f27..dcea269806 100644 --- a/lib/rdoc/markup.rb +++ b/lib/rdoc/markup.rb @@ -1,127 +1,128 @@ # frozen_string_literal: true -## -# RDoc::Markup parses plain text documents and attempts to decompose them into -# their constituent parts. Some of these parts are high-level: paragraphs, -# chunks of verbatim text, list entries and the like. Other parts happen at -# the character level: a piece of bold text, a word in code font. This markup -# is similar in spirit to that used on WikiWiki webs, where folks create web -# pages using a simple set of formatting rules. -# -# RDoc::Markup and other markup formats do no output formatting, this is -# handled by the RDoc::Markup::Formatter subclasses. -# -# = Markup Formats -# -# +RDoc+ supports these markup formats: -# -# - +rdoc+: -# the +RDoc+ markup format; -# see {RDoc Markup Reference}[rdoc-ref:doc/markup_reference/rdoc.rdoc] -# - +markdown+: -# The +markdown+ markup format as described in -# the {Markdown Guide}[https://www.markdownguide.org]; -# see RDoc::Markdown. -# - +rd+: -# the +rd+ markup format format; -# see RDoc::RD. -# - +tomdoc+: -# the TomDoc format as described in -# {TomDoc for Ruby}[http://tomdoc.org]; -# see RDoc::TomDoc. -# -# You can choose a markup format using the following methods: -# -# per project:: -# If you build your documentation with rake use RDoc::Task#markup. -# -# If you build your documentation by hand run: -# -# rdoc --markup your_favorite_format --write-options -# -# and commit .rdoc_options and ship it with your packaged gem. -# per file:: -# At the top of the file use the :markup: directive to set the -# default format for the rest of the file. -# per comment:: -# Use the :markup: directive at the top of a comment you want -# to write in a different format. -# -# = RDoc::Markup -# -# RDoc::Markup is extensible at runtime: you can add \new markup elements to -# be recognized in the documents that RDoc::Markup parses. -# -# RDoc::Markup is intended to be the basis for a family of tools which share -# the common requirement that simple, plain-text should be rendered in a -# variety of different output formats and media. It is envisaged that -# RDoc::Markup could be the basis for formatting RDoc style comment blocks, -# Wiki entries, and online FAQs. -# -# == Synopsis -# -# This code converts +input_string+ to HTML. The conversion takes place in -# the +convert+ method, so you can use the same RDoc::Markup converter to -# convert multiple input strings. -# -# require 'rdoc' -# -# h = RDoc::Markup::ToHtml.new(RDoc::Options.new) -# -# puts h.convert(input_string) -# -# You can extend the RDoc::Markup parser to recognize new markup -# sequences, and to add regexp handling. Here we make WikiWords significant to -# the parser, and also make the sequences {word} and \text... signify -# strike-through text. We then subclass the HTML output class to deal -# with these: -# -# require 'rdoc' -# -# class WikiHtml < RDoc::Markup::ToHtml -# def handle_regexp_WIKIWORD(target) -# "" + target + "" -# end -# end -# -# markup = RDoc::Markup.new -# markup.add_word_pair("{", "}", :STRIKE) -# markup.add_html("no", :STRIKE) -# -# markup.add_regexp_handling(/\b([A-Z][a-z]+[A-Z]\w+)/, :WIKIWORD) -# -# wh = WikiHtml.new RDoc::Options.new, markup -# wh.add_tag(:STRIKE, "", "") -# -# puts "#{wh.convert ARGF.read}" -# -# == Encoding -# -# Where Encoding support is available, RDoc will automatically convert all -# documents to the same output encoding. The output encoding can be set via -# RDoc::Options#encoding and defaults to Encoding.default_external. -# -# = \RDoc Markup Reference -# -# See {RDoc Markup Reference}[rdoc-ref:doc/markup_reference/rdoc.rdoc] -# -#-- -# Original Author:: Dave Thomas, dave@pragmaticprogrammer.com -# License:: Ruby license - -class RDoc::Markup - - # Array of regexp handling pattern and its name. A regexp handling - # sequence is something like a WikiWord - - attr_reader :regexp_handlings - +module RDoc ## - # Parses +str+ into an RDoc::Markup::Document. + # RDoc::Markup parses plain text documents and attempts to decompose them into + # their constituent parts. Some of these parts are high-level: paragraphs, + # chunks of verbatim text, list entries and the like. Other parts happen at + # the character level: a piece of bold text, a word in code font. This markup + # is similar in spirit to that used on WikiWiki webs, where folks create web + # pages using a simple set of formatting rules. + # + # RDoc::Markup and other markup formats do no output formatting, this is + # handled by the RDoc::Markup::Formatter subclasses. + # + # = Markup Formats + # + # +RDoc+ supports these markup formats: + # + # - +rdoc+: + # the +RDoc+ markup format; + # see {RDoc Markup Reference}[rdoc-ref:doc/markup_reference/rdoc.rdoc] + # - +markdown+: + # The +markdown+ markup format as described in + # the {Markdown Guide}[https://www.markdownguide.org]; + # see RDoc::Markdown. + # - +rd+: + # the +rd+ markup format format; + # see RDoc::RD. + # - +tomdoc+: + # the TomDoc format as described in + # {TomDoc for Ruby}[http://tomdoc.org]; + # see RDoc::TomDoc. + # + # You can choose a markup format using the following methods: + # + # per project:: + # If you build your documentation with rake use RDoc::Task#markup. + # + # If you build your documentation by hand run: + # + # rdoc --markup your_favorite_format --write-options + # + # and commit .rdoc_options and ship it with your packaged gem. + # per file:: + # At the top of the file use the :markup: directive to set the + # default format for the rest of the file. + # per comment:: + # Use the :markup: directive at the top of a comment you want + # to write in a different format. + # + # = RDoc::Markup + # + # RDoc::Markup is extensible at runtime: you can add \new markup elements to + # be recognized in the documents that RDoc::Markup parses. + # + # RDoc::Markup is intended to be the basis for a family of tools which share + # the common requirement that simple, plain-text should be rendered in a + # variety of different output formats and media. It is envisaged that + # RDoc::Markup could be the basis for formatting RDoc style comment blocks, + # Wiki entries, and online FAQs. + # + # == Synopsis + # + # This code converts +input_string+ to HTML. The conversion takes place in + # the +convert+ method, so you can use the same RDoc::Markup converter to + # convert multiple input strings. + # + # require 'rdoc' + # + # h = RDoc::Markup::ToHtml.new(RDoc::Options.new) + # + # puts h.convert(input_string) + # + # You can extend the RDoc::Markup parser to recognize new markup + # sequences, and to add regexp handling. Here we make WikiWords significant to + # the parser, and also make the sequences {word} and \text... signify + # strike-through text. We then subclass the HTML output class to deal + # with these: + # + # require 'rdoc' + # + # class WikiHtml < RDoc::Markup::ToHtml + # def handle_regexp_WIKIWORD(target) + # "" + target + "" + # end + # end + # + # markup = RDoc::Markup.new + # markup.add_word_pair("{", "}", :STRIKE) + # markup.add_html("no", :STRIKE) + # + # markup.add_regexp_handling(/\b([A-Z][a-z]+[A-Z]\w+)/, :WIKIWORD) + # + # wh = WikiHtml.new RDoc::Options.new, markup + # wh.add_tag(:STRIKE, "", "") + # + # puts "#{wh.convert ARGF.read}" + # + # == Encoding + # + # Where Encoding support is available, RDoc will automatically convert all + # documents to the same output encoding. The output encoding can be set via + # RDoc::Options#encoding and defaults to Encoding.default_external. + # + # = \RDoc Markup Reference + # + # See {RDoc Markup Reference}[rdoc-ref:doc/markup_reference/rdoc.rdoc] + # + #-- + # Original Author:: Dave Thomas, dave@pragmaticprogrammer.com + # License:: Ruby license - def self.parse(str) - RDoc::Markup::Parser.parse str - rescue RDoc::Markup::Parser::Error => e - $stderr.puts <<-EOF + class Markup + + # Array of regexp handling pattern and its name. A regexp handling + # sequence is something like a WikiWord + + attr_reader :regexp_handlings + + ## + # Parses +str+ into an RDoc::Markup::Document. + + def self.parse(str) + ::RDoc::Markup::Parser.parse str + rescue ::RDoc::Markup::Parser::Error => e + $stderr.puts <<-EOF While parsing markup, RDoc encountered a #{e.class}: #{e} @@ -131,7 +132,7 @@ def self.parse(str) #{text} ---8<--- -RDoc #{RDoc::VERSION} +RDoc #{::RDoc::VERSION} Ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL} #{RUBY_RELEASE_DATE} @@ -140,80 +141,81 @@ def self.parse(str) https://github.com/ruby/rdoc/issues EOF - raise - end - - ## - # Take a block of text and use various heuristics to determine its - # structure (paragraphs, lists, and so on). Invoke an event handler as we - # identify significant chunks. - - def initialize - @regexp_handlings = [] - @output = nil - end - - ## - # Add to other inline sequences. For example, we could add WikiWords using - # something like: - # - # parser.add_regexp_handling(/\b([A-Z][a-z]+[A-Z]\w+)/, :WIKIWORD) - # - # Each wiki word will be presented to the output formatter. - - def add_regexp_handling(pattern, name) - @regexp_handlings << [pattern, name] - end + raise + end + + ## + # Take a block of text and use various heuristics to determine its + # structure (paragraphs, lists, and so on). Invoke an event handler as we + # identify significant chunks. + + def initialize + @regexp_handlings = [] + @output = nil + end + + ## + # Add to other inline sequences. For example, we could add WikiWords using + # something like: + # + # parser.add_regexp_handling(/\b([A-Z][a-z]+[A-Z]\w+)/, :WIKIWORD) + # + # Each wiki word will be presented to the output formatter. + + def add_regexp_handling(pattern, name) + @regexp_handlings << [pattern, name] + end + + ## + # We take +input+, parse it if necessary, then invoke the output +formatter+ + # using a Visitor to render the result. + + def convert(input, formatter) + document = case input + when ::RDoc::Markup::Document + input + else + ::RDoc::Markup::Parser.parse input + end + + document.accept formatter + end + + autoload :Parser, "#{__dir__}/markup/parser" + autoload :InlineParser, "#{__dir__}/markup/inline_parser" + autoload :PreProcess, "#{__dir__}/markup/pre_process" + + # RDoc::Markup AST + autoload :BlankLine, "#{__dir__}/markup/blank_line" + autoload :BlockQuote, "#{__dir__}/markup/block_quote" + autoload :Document, "#{__dir__}/markup/document" + autoload :Element, "#{__dir__}/markup/element" + autoload :HardBreak, "#{__dir__}/markup/hard_break" + autoload :Heading, "#{__dir__}/markup/heading" + autoload :Include, "#{__dir__}/markup/include" + autoload :IndentedParagraph, "#{__dir__}/markup/indented_paragraph" + autoload :List, "#{__dir__}/markup/list" + autoload :ListItem, "#{__dir__}/markup/list_item" + autoload :Paragraph, "#{__dir__}/markup/paragraph" + autoload :Table, "#{__dir__}/markup/table" + autoload :Raw, "#{__dir__}/markup/raw" + autoload :Rule, "#{__dir__}/markup/rule" + autoload :Verbatim, "#{__dir__}/markup/verbatim" + + # Formatters + autoload :Formatter, "#{__dir__}/markup/formatter" + + autoload :ToAnsi, "#{__dir__}/markup/to_ansi" + autoload :ToBs, "#{__dir__}/markup/to_bs" + autoload :ToHtml, "#{__dir__}/markup/to_html" + autoload :ToHtmlCrossref, "#{__dir__}/markup/to_html_crossref" + autoload :ToHtmlSnippet, "#{__dir__}/markup/to_html_snippet" + autoload :ToLabel, "#{__dir__}/markup/to_label" + autoload :ToMarkdown, "#{__dir__}/markup/to_markdown" + autoload :ToRdoc, "#{__dir__}/markup/to_rdoc" + autoload :ToTableOfContents, "#{__dir__}/markup/to_table_of_contents" + autoload :ToTest, "#{__dir__}/markup/to_test" + autoload :ToTtOnly, "#{__dir__}/markup/to_tt_only" - ## - # We take +input+, parse it if necessary, then invoke the output +formatter+ - # using a Visitor to render the result. - - def convert(input, formatter) - document = case input - when RDoc::Markup::Document - input - else - RDoc::Markup::Parser.parse input - end - - document.accept formatter end - - autoload :Parser, "#{__dir__}/markup/parser" - autoload :InlineParser, "#{__dir__}/markup/inline_parser" - autoload :PreProcess, "#{__dir__}/markup/pre_process" - - # RDoc::Markup AST - autoload :BlankLine, "#{__dir__}/markup/blank_line" - autoload :BlockQuote, "#{__dir__}/markup/block_quote" - autoload :Document, "#{__dir__}/markup/document" - autoload :Element, "#{__dir__}/markup/element" - autoload :HardBreak, "#{__dir__}/markup/hard_break" - autoload :Heading, "#{__dir__}/markup/heading" - autoload :Include, "#{__dir__}/markup/include" - autoload :IndentedParagraph, "#{__dir__}/markup/indented_paragraph" - autoload :List, "#{__dir__}/markup/list" - autoload :ListItem, "#{__dir__}/markup/list_item" - autoload :Paragraph, "#{__dir__}/markup/paragraph" - autoload :Table, "#{__dir__}/markup/table" - autoload :Raw, "#{__dir__}/markup/raw" - autoload :Rule, "#{__dir__}/markup/rule" - autoload :Verbatim, "#{__dir__}/markup/verbatim" - - # Formatters - autoload :Formatter, "#{__dir__}/markup/formatter" - - autoload :ToAnsi, "#{__dir__}/markup/to_ansi" - autoload :ToBs, "#{__dir__}/markup/to_bs" - autoload :ToHtml, "#{__dir__}/markup/to_html" - autoload :ToHtmlCrossref, "#{__dir__}/markup/to_html_crossref" - autoload :ToHtmlSnippet, "#{__dir__}/markup/to_html_snippet" - autoload :ToLabel, "#{__dir__}/markup/to_label" - autoload :ToMarkdown, "#{__dir__}/markup/to_markdown" - autoload :ToRdoc, "#{__dir__}/markup/to_rdoc" - autoload :ToTableOfContents, "#{__dir__}/markup/to_table_of_contents" - autoload :ToTest, "#{__dir__}/markup/to_test" - autoload :ToTtOnly, "#{__dir__}/markup/to_tt_only" - end diff --git a/lib/rdoc/markup/block_quote.rb b/lib/rdoc/markup/block_quote.rb index d2f13220f6..ee3b3566e9 100644 --- a/lib/rdoc/markup/block_quote.rb +++ b/lib/rdoc/markup/block_quote.rb @@ -1,14 +1,18 @@ # frozen_string_literal: true -## -# A quoted section which contains markup items. +module RDoc + class Markup + ## + # A quoted section which contains markup items. -class RDoc::Markup::BlockQuote < RDoc::Markup::Raw + class BlockQuote < ::RDoc::Markup::Raw - ## - # Calls #accept_block_quote on +visitor+ + ## + # Calls #accept_block_quote on +visitor+ - def accept(visitor) - visitor.accept_block_quote self - end + def accept(visitor) + visitor.accept_block_quote self + end + end + end end diff --git a/lib/rdoc/markup/document.rb b/lib/rdoc/markup/document.rb index 20f431bdf0..50489e31e7 100644 --- a/lib/rdoc/markup/document.rb +++ b/lib/rdoc/markup/document.rb @@ -1,164 +1,168 @@ # frozen_string_literal: true -## -# A Document containing lists, headings, paragraphs, etc. +module RDoc + class Markup + ## + # A Document containing lists, headings, paragraphs, etc. -class RDoc::Markup::Document + class Document - include Enumerable + include Enumerable - ## - # The file this document was created from. See also - # RDoc::ClassModule#add_comment + ## + # The file this document was created from. See also + # RDoc::ClassModule#add_comment - attr_reader :file + attr_reader :file - ## - # If a heading is below the given level it will be omitted from the - # table_of_contents + ## + # If a heading is below the given level it will be omitted from the + # table_of_contents - attr_accessor :omit_headings_below + attr_accessor :omit_headings_below - ## - # The parts of the Document + ## + # The parts of the Document - attr_reader :parts + attr_reader :parts - ## - # Creates a new Document with +parts+ + ## + # Creates a new Document with +parts+ - def initialize(*parts) - @parts = [] - @parts.concat parts + def initialize(*parts) + @parts = [] + @parts.concat parts - @file = nil - @omit_headings_from_table_of_contents_below = nil - end - - ## - # Appends +part+ to the document + @file = nil + @omit_headings_from_table_of_contents_below = nil + end - def <<(part) - case part - when RDoc::Markup::Document - unless part.empty? - parts.concat part.parts - parts << RDoc::Markup::BlankLine.new + ## + # Appends +part+ to the document + + def <<(part) + case part + when ::RDoc::Markup::Document + unless part.empty? + parts.concat part.parts + parts << ::RDoc::Markup::BlankLine.new + end + when String + raise ArgumentError, + "expected RDoc::Markup::Document and friends, got String" unless + part.empty? + else + parts << part + end end - when String - raise ArgumentError, - "expected RDoc::Markup::Document and friends, got String" unless - part.empty? - else - parts << part - end - end - def ==(other) # :nodoc: - self.class == other.class and - @file == other.file and - @parts == other.parts - end + def ==(other) # :nodoc: + self.class == other.class and + @file == other.file and + @parts == other.parts + end - ## - # Runs this document and all its #items through +visitor+ + ## + # Runs this document and all its #items through +visitor+ - def accept(visitor) - visitor.start_accepting + def accept(visitor) + visitor.start_accepting - visitor.accept_document self + visitor.accept_document self - visitor.end_accepting - end + visitor.end_accepting + end - ## - # Concatenates the given +parts+ onto the document + ## + # Concatenates the given +parts+ onto the document - def concat(parts) - self.parts.concat parts - end + def concat(parts) + self.parts.concat parts + end - ## - # Enumerator for the parts of this document + ## + # Enumerator for the parts of this document - def each(&block) - @parts.each(&block) - end + def each(&block) + @parts.each(&block) + end - ## - # Does this document have no parts? + ## + # Does this document have no parts? - def empty? - @parts.empty? or (@parts.length == 1 and merged? and @parts.first.empty?) - end + def empty? + @parts.empty? or (@parts.length == 1 and merged? and @parts.first.empty?) + end - ## - # The file this Document was created from. + ## + # The file this Document was created from. - def file=(location) - @file = case location - when RDoc::TopLevel - location.relative_name - else - location - end - end - - ## - # When this is a collection of documents (#file is not set and this document - # contains only other documents as its direct children) #merge replaces - # documents in this class with documents from +other+ when the file matches - # and adds documents from +other+ when the files do not. - # - # The information in +other+ is preferred over the receiver - - def merge(other) - if empty? - @parts = other.parts - return self - end + def file=(location) + @file = case location + when ::RDoc::TopLevel + location.relative_name + else + location + end + end - other.parts.each do |other_part| - self.parts.delete_if do |self_part| - self_part.file and self_part.file == other_part.file + ## + # When this is a collection of documents (#file is not set and this document + # contains only other documents as its direct children) #merge replaces + # documents in this class with documents from +other+ when the file matches + # and adds documents from +other+ when the files do not. + # + # The information in +other+ is preferred over the receiver + + def merge(other) + if empty? + @parts = other.parts + return self + end + + other.parts.each do |other_part| + self.parts.delete_if do |self_part| + self_part.file and self_part.file == other_part.file + end + + self.parts << other_part + end + + self end - self.parts << other_part - end + ## + # Does this Document contain other Documents? - self - end + def merged? + ::RDoc::Markup::Document === @parts.first + end - ## - # Does this Document contain other Documents? + def pretty_print(q) # :nodoc: + start = @file ? "[doc (#{@file}): " : '[doc: ' - def merged? - RDoc::Markup::Document === @parts.first - end + q.group 2, start, ']' do + q.seplist @parts do |part| + q.pp part + end + end + end - def pretty_print(q) # :nodoc: - start = @file ? "[doc (#{@file}): " : '[doc: ' + ## + # Appends +parts+ to the document - q.group 2, start, ']' do - q.seplist @parts do |part| - q.pp part + def push(*parts) + self.parts.concat parts end - end - end - ## - # Appends +parts+ to the document - - def push(*parts) - self.parts.concat parts - end + ## + # Returns an Array of headings in the document. + # + # Require 'rdoc/markup/formatter' before calling this method. - ## - # Returns an Array of headings in the document. - # - # Require 'rdoc/markup/formatter' before calling this method. + def table_of_contents + accept ::RDoc::Markup::ToTableOfContents.to_toc + end - def table_of_contents - accept RDoc::Markup::ToTableOfContents.to_toc + end end - end diff --git a/lib/rdoc/markup/formatter.rb b/lib/rdoc/markup/formatter.rb index 58b8ba9755..e0bcd393c1 100644 --- a/lib/rdoc/markup/formatter.rb +++ b/lib/rdoc/markup/formatter.rb @@ -12,274 +12,278 @@ require 'rdoc/markup/inline_parser' -class RDoc::Markup::Formatter +module RDoc + class Markup + class Formatter - ## - # Converts a target url to one that is relative to a given path + ## + # Converts a target url to one that is relative to a given path - def self.gen_relative_url(path, target) - from = File.dirname path - to, to_file = File.split target + def self.gen_relative_url(path, target) + from = File.dirname path + to, to_file = File.split target - from = from.split "/" - to = to.split "/" + from = from.split "/" + to = to.split "/" - from.delete '.' - to.delete '.' + from.delete '.' + to.delete '.' - while from.size > 0 and to.size > 0 and from[0] == to[0] do - from.shift - to.shift - end - - from.fill ".." - from.concat to - from << to_file - File.join(*from) - end - - ## - # Creates a new Formatter + while from.size > 0 and to.size > 0 and from[0] == to[0] do + from.shift + to.shift + end - def initialize - @markup = RDoc::Markup.new + from.fill ".." + from.concat to + from << to_file + File.join(*from) + end - @from_path = '.' - end + ## + # Creates a new Formatter - ## - # Adds +document+ to the output + def initialize + @markup = ::RDoc::Markup.new - def accept_document(document) - document.parts.each do |item| - case item - when RDoc::Markup::Document # HACK - accept_document item - else - item.accept self + @from_path = '.' end - end - end - ## - # Adds a regexp handling for links of the form rdoc-...: + ## + # Adds +document+ to the output + + def accept_document(document) + document.parts.each do |item| + case item + when ::RDoc::Markup::Document # HACK + accept_document item + else + item.accept self + end + end + end - def add_regexp_handling_RDOCLINK - @markup.add_regexp_handling(/rdoc-[a-z]+:[^\s\]]+/, :RDOCLINK) - end + ## + # Adds a regexp handling for links of the form rdoc-...: - ## - # Allows +tag+ to be decorated with additional information. + def add_regexp_handling_RDOCLINK + @markup.add_regexp_handling(/rdoc-[a-z]+:[^\s\]]+/, :RDOCLINK) + end - def annotate(tag) - tag - end + ## + # Allows +tag+ to be decorated with additional information. - ## - # Marks up +content+ + def annotate(tag) + tag + end - def convert(content) - @markup.convert content, self - end + ## + # Marks up +content+ - # Applies regexp handling to +text+ and returns an array of [text, converted?] pairs. + def convert(content) + @markup.convert content, self + end - def apply_regexp_handling(text) - matched = [] - @markup.regexp_handlings.each_with_index do |(pattern, name), priority| - text.scan(pattern) do - m = Regexp.last_match - idx = m[1] ? 1 : 0 - matched << [m.begin(idx), m.end(idx), m[idx], name, priority] + # Applies regexp handling to +text+ and returns an array of [text, converted?] pairs. + + def apply_regexp_handling(text) + matched = [] + @markup.regexp_handlings.each_with_index do |(pattern, name), priority| + text.scan(pattern) do + m = Regexp.last_match + idx = m[1] ? 1 : 0 + matched << [m.begin(idx), m.end(idx), m[idx], name, priority] + end + end + # If the start positions are the same, prefer the earlier-registered one + # (lower numeric priority from each_with_index). + matched.sort_by! {|beg_pos, _, _, _, priority| [beg_pos, priority] } + + pos = 0 + output = [] + matched.each do |beg_pos, end_pos, s, name| + next if beg_pos < pos + + output << [text[pos...beg_pos], false] if beg_pos != pos + handled = public_send(:"handle_regexp_#{name}", s) + output << [handled, true] + pos = end_pos + end + + output << [text[pos..], false] if pos < text.size + output end - end - # If the start positions are the same, prefer the earlier-registered one - # (lower numeric priority from each_with_index). - matched.sort_by! {|beg_pos, _, _, _, priority| [beg_pos, priority] } - - pos = 0 - output = [] - matched.each do |beg_pos, end_pos, s, name| - next if beg_pos < pos - - output << [text[pos...beg_pos], false] if beg_pos != pos - handled = public_send(:"handle_regexp_#{name}", s) - output << [handled, true] - pos = end_pos - end - output << [text[pos..], false] if pos < text.size - output - end + # Called when processing plain text while traversing inline nodes from handle_inline. + # +text+ may need proper escaping. - # Called when processing plain text while traversing inline nodes from handle_inline. - # +text+ may need proper escaping. + def handle_PLAIN_TEXT(text) + end - def handle_PLAIN_TEXT(text) - end + # Called when processing regexp-handling-processed text while traversing inline nodes from handle_inline. + # +text+ may contain markup tags. - # Called when processing regexp-handling-processed text while traversing inline nodes from handle_inline. - # +text+ may contain markup tags. + def handle_REGEXP_HANDLING_TEXT(text) + end - def handle_REGEXP_HANDLING_TEXT(text) - end + # Called when processing text node while traversing inline nodes from handle_inline. + # Apply regexp handling and dispatch to the appropriate handler: handle_REGEXP_HANDLING_TEXT or handle_PLAIN_TEXT. + + def handle_TEXT(text) + apply_regexp_handling(text).each do |part, converted| + if converted + handle_REGEXP_HANDLING_TEXT(part) + else + handle_PLAIN_TEXT(part) + end + end + end - # Called when processing text node while traversing inline nodes from handle_inline. - # Apply regexp handling and dispatch to the appropriate handler: handle_REGEXP_HANDLING_TEXT or handle_PLAIN_TEXT. + # Called when processing a hard break while traversing inline nodes from handle_inline. - def handle_TEXT(text) - apply_regexp_handling(text).each do |part, converted| - if converted - handle_REGEXP_HANDLING_TEXT(part) - else - handle_PLAIN_TEXT(part) + def handle_HARD_BREAK end - end - end - # Called when processing a hard break while traversing inline nodes from handle_inline. + # Called when processing bold nodes while traversing inline nodes from handle_inline. + # Traverse the children nodes and dispatch to the appropriate handlers. - def handle_HARD_BREAK - end + def handle_BOLD(nodes) + traverse_inline_nodes(nodes) + end - # Called when processing bold nodes while traversing inline nodes from handle_inline. - # Traverse the children nodes and dispatch to the appropriate handlers. + # Called when processing emphasis nodes while traversing inline nodes from handle_inline. + # Traverse the children nodes and dispatch to the appropriate handlers. - def handle_BOLD(nodes) - traverse_inline_nodes(nodes) - end + def handle_EM(nodes) + traverse_inline_nodes(nodes) + end - # Called when processing emphasis nodes while traversing inline nodes from handle_inline. - # Traverse the children nodes and dispatch to the appropriate handlers. + # Called when processing bold word nodes while traversing inline nodes from handle_inline. + # +word+ may need proper escaping. - def handle_EM(nodes) - traverse_inline_nodes(nodes) - end + def handle_BOLD_WORD(word) + handle_PLAIN_TEXT(word) + end - # Called when processing bold word nodes while traversing inline nodes from handle_inline. - # +word+ may need proper escaping. + # Called when processing emphasis word nodes while traversing inline nodes from handle_inline. + # +word+ may need proper escaping. - def handle_BOLD_WORD(word) - handle_PLAIN_TEXT(word) - end + def handle_EM_WORD(word) + handle_PLAIN_TEXT(word) + end - # Called when processing emphasis word nodes while traversing inline nodes from handle_inline. - # +word+ may need proper escaping. + # Called when processing tt nodes while traversing inline nodes from handle_inline. + # +code+ may need proper escaping. - def handle_EM_WORD(word) - handle_PLAIN_TEXT(word) - end + def handle_TT(code) + handle_PLAIN_TEXT(code) + end - # Called when processing tt nodes while traversing inline nodes from handle_inline. - # +code+ may need proper escaping. + # Called when processing strike nodes while traversing inline nodes from handle_inline. + # Traverse the children nodes and dispatch to the appropriate handlers. - def handle_TT(code) - handle_PLAIN_TEXT(code) - end + def handle_STRIKE(nodes) + traverse_inline_nodes(nodes) + end - # Called when processing strike nodes while traversing inline nodes from handle_inline. - # Traverse the children nodes and dispatch to the appropriate handlers. + # Called when processing tidylink nodes while traversing inline nodes from handle_inline. + # +label_part+ is an array of strings or nodes representing the link label. + # +url+ is the link URL. + # Traverse the label_part nodes and dispatch to the appropriate handlers. - def handle_STRIKE(nodes) - traverse_inline_nodes(nodes) - end + def handle_TIDYLINK(label_part, url) + traverse_inline_nodes(label_part) + end - # Called when processing tidylink nodes while traversing inline nodes from handle_inline. - # +label_part+ is an array of strings or nodes representing the link label. - # +url+ is the link URL. - # Traverse the label_part nodes and dispatch to the appropriate handlers. + # Parses inline +text+, traverse the resulting nodes, and calls the appropriate handler methods. - def handle_TIDYLINK(label_part, url) - traverse_inline_nodes(label_part) - end + def handle_inline(text) + nodes = ::RDoc::Markup::InlineParser.new(text).parse + traverse_inline_nodes(nodes) + end - # Parses inline +text+, traverse the resulting nodes, and calls the appropriate handler methods. + # Traverses +nodes+ and calls the appropriate handler methods + # Nodes formats are described in RDoc::Markup::InlineParser#parse + + def traverse_inline_nodes(nodes) + nodes.each do |node| + next handle_TEXT(node) if String === node + case node[:type] + when :TIDYLINK + handle_TIDYLINK(node[:children], node[:url]) + when :HARD_BREAK + handle_HARD_BREAK + when :BOLD + handle_BOLD(node[:children]) + when :BOLD_WORD + handle_BOLD_WORD(node[:children][0] || '') + when :EM + handle_EM(node[:children]) + when :EM_WORD + handle_EM_WORD(node[:children][0] || '') + when :TT + handle_TT(node[:children][0] || '') + when :STRIKE + handle_STRIKE(node[:children]) + end + end + end - def handle_inline(text) - nodes = RDoc::Markup::InlineParser.new(text).parse - traverse_inline_nodes(nodes) - end + ## + # Converts a string to be fancier if desired - # Traverses +nodes+ and calls the appropriate handler methods - # Nodes formats are described in RDoc::Markup::InlineParser#parse - - def traverse_inline_nodes(nodes) - nodes.each do |node| - next handle_TEXT(node) if String === node - case node[:type] - when :TIDYLINK - handle_TIDYLINK(node[:children], node[:url]) - when :HARD_BREAK - handle_HARD_BREAK - when :BOLD - handle_BOLD(node[:children]) - when :BOLD_WORD - handle_BOLD_WORD(node[:children][0] || '') - when :EM - handle_EM(node[:children]) - when :EM_WORD - handle_EM_WORD(node[:children][0] || '') - when :TT - handle_TT(node[:children][0] || '') - when :STRIKE - handle_STRIKE(node[:children]) + def convert_string(string) + string end - end - end - ## - # Converts a string to be fancier if desired + ## + # Use ignore in your subclass to ignore the content of a node. + # + # ## + # # We don't support raw nodes in ToNoRaw + # + # alias accept_raw ignore - def convert_string(string) - string - end + def ignore(*node) + end - ## - # Use ignore in your subclass to ignore the content of a node. - # - # ## - # # We don't support raw nodes in ToNoRaw - # - # alias accept_raw ignore + ## + # Extracts and a scheme, url and an anchor id from +url+ and returns them. + + def parse_url(url) + case url + when /^rdoc-label:([^:]*)(?::(.*))?/ + scheme = 'link' + path = "##{$1}" + id = " id=\"#{$2}\"" if $2 + when /([A-Za-z]+):(.*)/ + scheme = $1.downcase + path = $2 + when /^#/ + else + scheme = 'http' + path = url + url = url + end + + if scheme == 'link' + url = if path[0, 1] == '#' # is this meaningful? + path + else + self.class.gen_relative_url @from_path, path + end + end + + [scheme, url, id] + end - def ignore(*node) - end + ## + # Is +tag+ a tt tag? - ## - # Extracts and a scheme, url and an anchor id from +url+ and returns them. - - def parse_url(url) - case url - when /^rdoc-label:([^:]*)(?::(.*))?/ - scheme = 'link' - path = "##{$1}" - id = " id=\"#{$2}\"" if $2 - when /([A-Za-z]+):(.*)/ - scheme = $1.downcase - path = $2 - when /^#/ - else - scheme = 'http' - path = url - url = url - end + def tt?(tag) + tag.bit == @tt_bit + end - if scheme == 'link' - url = if path[0, 1] == '#' # is this meaningful? - path - else - self.class.gen_relative_url @from_path, path - end end - - [scheme, url, id] end - - ## - # Is +tag+ a tt tag? - - def tt?(tag) - tag.bit == @tt_bit - end - end diff --git a/lib/rdoc/markup/include.rb b/lib/rdoc/markup/include.rb index 56775b25b6..345c5fbfa3 100644 --- a/lib/rdoc/markup/include.rb +++ b/lib/rdoc/markup/include.rb @@ -1,42 +1,46 @@ # frozen_string_literal: true -## -# A file included at generation time. Objects of this class are created by -# RDoc::RD for an extension-less include. -# -# This implementation in incomplete. +module RDoc + class Markup + ## + # A file included at generation time. Objects of this class are created by + # RDoc::RD for an extension-less include. + # + # This implementation in incomplete. -class RDoc::Markup::Include + class Include - ## - # The filename to be included, without extension + ## + # The filename to be included, without extension - attr_reader :file + attr_reader :file - ## - # Directories to search for #file + ## + # Directories to search for #file - attr_reader :include_path + attr_reader :include_path - ## - # Creates a new include that will import +file+ from +include_path+ + ## + # Creates a new include that will import +file+ from +include_path+ - def initialize(file, include_path) - @file = file - @include_path = include_path - end + def initialize(file, include_path) + @file = file + @include_path = include_path + end - def ==(other) # :nodoc: - self.class === other and - @file == other.file and @include_path == other.include_path - end + def ==(other) # :nodoc: + self.class === other and + @file == other.file and @include_path == other.include_path + end + + def pretty_print(q) # :nodoc: + q.group 2, '[incl ', ']' do + q.text file + q.breakable + q.text 'from ' + q.pp include_path + end + end - def pretty_print(q) # :nodoc: - q.group 2, '[incl ', ']' do - q.text file - q.breakable - q.text 'from ' - q.pp include_path end end - end diff --git a/lib/rdoc/markup/indented_paragraph.rb b/lib/rdoc/markup/indented_paragraph.rb index c28f6f605f..6ab581b008 100644 --- a/lib/rdoc/markup/indented_paragraph.rb +++ b/lib/rdoc/markup/indented_paragraph.rb @@ -1,47 +1,51 @@ # frozen_string_literal: true -## -# An Indented Paragraph of text +module RDoc + class Markup + ## + # An Indented Paragraph of text -class RDoc::Markup::IndentedParagraph < RDoc::Markup::Raw + class IndentedParagraph < ::RDoc::Markup::Raw - ## - # The indent in number of spaces + ## + # The indent in number of spaces - attr_reader :indent + attr_reader :indent - ## - # Creates a new IndentedParagraph containing +parts+ indented with +indent+ - # spaces + ## + # Creates a new IndentedParagraph containing +parts+ indented with +indent+ + # spaces - def initialize(indent, *parts) - @indent = indent + def initialize(indent, *parts) + @indent = indent - super(*parts) - end + super(*parts) + end - def ==(other) # :nodoc: - super and indent == other.indent - end + def ==(other) # :nodoc: + super and indent == other.indent + end - ## - # Calls #accept_indented_paragraph on +visitor+ + ## + # Calls #accept_indented_paragraph on +visitor+ - def accept(visitor) - visitor.accept_indented_paragraph self - end - - ## - # Joins the raw paragraph text and converts inline HardBreaks to the - # +hard_break+ text followed by the indent. + def accept(visitor) + visitor.accept_indented_paragraph self + end - def text(hard_break = nil) - @parts.map do |part| - if RDoc::Markup::HardBreak === part - '%1$s%3$*2$s' % [hard_break, @indent, ' '] if hard_break - else - part + ## + # Joins the raw paragraph text and converts inline HardBreaks to the + # +hard_break+ text followed by the indent. + + def text(hard_break = nil) + @parts.map do |part| + if ::RDoc::Markup::HardBreak === part + '%1$s%3$*2$s' % [hard_break, @indent, ' '] if hard_break + else + part + end + end.join end - end.join - end + end + end end diff --git a/lib/rdoc/markup/inline_parser.rb b/lib/rdoc/markup/inline_parser.rb index 4e2b86c630..a5eaba4cb4 100644 --- a/lib/rdoc/markup/inline_parser.rb +++ b/lib/rdoc/markup/inline_parser.rb @@ -3,310 +3,314 @@ require 'set' require 'strscan' -# Parses inline markup in RDoc text. -# This parser handles em, bold, strike, tt, hard break, and tidylink. -# Block-level constructs are handled in RDoc::Markup::Parser. - -class RDoc::Markup::InlineParser - - # TT, BOLD_WORD, EM_WORD: regexp-handling(example: crossref) is disabled - WORD_PAIRS = { - '*' => :BOLD_WORD, - '**' => :BOLD_WORD, - '_' => :EM_WORD, - '__' => :EM_WORD, - '+' => :TT, - '++' => :TT, - '`' => :TT, - '``' => :TT - } # :nodoc: - - # Other types: regexp-handling(example: crossref) is enabled - TAGS = { - 'em' => :EM, - 'i' => :EM, - 'b' => :BOLD, - 's' => :STRIKE, - 'del' => :STRIKE, - } # :nodoc: - - STANDALONE_TAGS = { 'br' => :HARD_BREAK } # :nodoc: - - CODEBLOCK_TAGS = %w[tt code] # :nodoc: - - TOKENS = { - **WORD_PAIRS.transform_values { [:word_pair, nil] }, - **TAGS.keys.to_h {|tag| ["<#{tag}>", [:open_tag, tag]] }, - **TAGS.keys.to_h {|tag| ["", [:close_tag, tag]] }, - **CODEBLOCK_TAGS.to_h {|tag| ["<#{tag}>", [:code_start, tag]] }, - **STANDALONE_TAGS.keys.to_h {|tag| ["<#{tag}>", [:standalone_tag, tag]] }, - '{' => [:tidylink_start, nil], - '}' => [:tidylink_mid, nil], - '\\' => [:escape, nil], - '[' => nil # To make `label[url]` scan as separate tokens - } # :nodoc: - - multi_char_tokens_regexp = Regexp.union(TOKENS.keys.select {|s| s.size > 1 }).source - token_starts_regexp = TOKENS.keys.map {|s| s[0] }.uniq.map {|s| Regexp.escape(s) }.join - - SCANNER_REGEXP = - /(?: - #{multi_char_tokens_regexp} - |[^#{token_starts_regexp}\sa-zA-Z0-9\.]+ # chunk of normal text - |\s+|[a-zA-Z0-9\.]+|. - )/x # :nodoc: - - # Characters that can be escaped with backslash. - ESCAPING_CHARS = '\\*_+`{}[]<>' # :nodoc: - - # Pattern to match code block content until
or . - CODEBLOCK_REGEXPS = CODEBLOCK_TAGS.to_h {|name| [name, /((?:\\.|[^\\])*?)<\/#{name}>/] } # :nodoc: - - # Word contains alphanumeric and _./:[]- characters. - # Word may start with # and may end with any non-space character. (e.g. #eql?). - # Underscore delimiter have special rules. - WORD_REGEXPS = { - # Words including _, longest match. - # Example: `_::A_` `_-42_` `_A::B::C.foo_bar[baz]_` `_kwarg:_` - # Content must not include _ followed by non-alphanumeric character - # Example: `_host_:_port_` will be `_host_` + `:` + `_port_` - '_' => /#?([a-zA-Z0-9.\/:\[\]-]|_+[a-zA-Z0-9])+[^\s]?_(?=[^a-zA-Z0-9_]|\z)/, - # Words allowing _ but not allowing __ - '__' => /#?[a-zA-Z0-9.\/:\[\]-]*(_[a-zA-Z0-9.\/:\[\]-]+)*[^\s]?__(?=[^a-zA-Z0-9]|\z)/, - **%w[* ** + ++ ` ``].to_h do |s| - # normal words that can be used within +word+ or *word* - [s, /#?[a-zA-Z0-9_.\/:\[\]-]+[^\s]?#{Regexp.escape(s)}(?=[^a-zA-Z0-9]|\z)/] - end - } # :nodoc: - - def initialize(string) - @scanner = StringScanner.new(string) - @last_match = nil - @scanner_negative_cache = Set.new - @stack = [] - @delimiters = {} - end - - # Return the current parsing node on @stack. - - def current - @stack.last - end - - # Parse and return an array of nodes. - # Node format: - # { - # type: :EM | :BOLD | :BOLD_WORD | :EM_WORD | :TT | :STRIKE | :HARD_BREAK | :TIDYLINK, - # url: string # only for :TIDYLINK - # children: [string_or_node, ...] - # } - - def parse - stack_push(:root, nil) - while true - type, token, value = scan_token - close = nil - tidylink_url = nil - case type - when :node - current[:children] << value - invalidate_open_tidylinks if value[:type] == :TIDYLINK - when :eof - close = :root - when :tidylink_open - stack_push(:tidylink, token) - when :tidylink_close - close = :tidylink - if value - tidylink_url = value - else - # Tidylink closing brace without URL part. Treat opening and closing braces as normal text - # `{labelnodes}...` case. - current[:children] << token - end - when :invalidated_tidylink_close - # `{...{label}[url]...}` case. Nested tidylink invalidates outer one. The last `}` closes the invalidated tidylink. - current[:children] << token - close = :invalidated_tidylink - when :text - current[:children] << token - when :open - stack_push(value, token) - when :close - if @delimiters[value] - close = value - else - # closing tag without matching opening tag. Treat as normal text. - current[:children] << token +module RDoc + class Markup + # Parses inline markup in RDoc text. + # This parser handles em, bold, strike, tt, hard break, and tidylink. + # Block-level constructs are handled in RDoc::Markup::Parser. + + class InlineParser + + # TT, BOLD_WORD, EM_WORD: regexp-handling(example: crossref) is disabled + WORD_PAIRS = { + '*' => :BOLD_WORD, + '**' => :BOLD_WORD, + '_' => :EM_WORD, + '__' => :EM_WORD, + '+' => :TT, + '++' => :TT, + '`' => :TT, + '``' => :TT + } # :nodoc: + + # Other types: regexp-handling(example: crossref) is enabled + TAGS = { + 'em' => :EM, + 'i' => :EM, + 'b' => :BOLD, + 's' => :STRIKE, + 'del' => :STRIKE, + } # :nodoc: + + STANDALONE_TAGS = { 'br' => :HARD_BREAK } # :nodoc: + + CODEBLOCK_TAGS = %w[tt code] # :nodoc: + + TOKENS = { + **WORD_PAIRS.transform_values { [:word_pair, nil] }, + **TAGS.keys.to_h {|tag| ["<#{tag}>", [:open_tag, tag]] }, + **TAGS.keys.to_h {|tag| ["", [:close_tag, tag]] }, + **CODEBLOCK_TAGS.to_h {|tag| ["<#{tag}>", [:code_start, tag]] }, + **STANDALONE_TAGS.keys.to_h {|tag| ["<#{tag}>", [:standalone_tag, tag]] }, + '{' => [:tidylink_start, nil], + '}' => [:tidylink_mid, nil], + '\\' => [:escape, nil], + '[' => nil # To make `label[url]` scan as separate tokens + } # :nodoc: + + multi_char_tokens_regexp = Regexp.union(TOKENS.keys.select {|s| s.size > 1 }).source + token_starts_regexp = TOKENS.keys.map {|s| s[0] }.uniq.map {|s| Regexp.escape(s) }.join + + SCANNER_REGEXP = + /(?: + #{multi_char_tokens_regexp} + |[^#{token_starts_regexp}\sa-zA-Z0-9\.]+ # chunk of normal text + |\s+|[a-zA-Z0-9\.]+|. + )/x # :nodoc: + + # Characters that can be escaped with backslash. + ESCAPING_CHARS = '\\*_+`{}[]<>' # :nodoc: + + # Pattern to match code block content until
or . + CODEBLOCK_REGEXPS = CODEBLOCK_TAGS.to_h {|name| [name, /((?:\\.|[^\\])*?)<\/#{name}>/] } # :nodoc: + + # Word contains alphanumeric and _./:[]- characters. + # Word may start with # and may end with any non-space character. (e.g. #eql?). + # Underscore delimiter have special rules. + WORD_REGEXPS = { + # Words including _, longest match. + # Example: `_::A_` `_-42_` `_A::B::C.foo_bar[baz]_` `_kwarg:_` + # Content must not include _ followed by non-alphanumeric character + # Example: `_host_:_port_` will be `_host_` + `:` + `_port_` + '_' => /#?([a-zA-Z0-9.\/:\[\]-]|_+[a-zA-Z0-9])+[^\s]?_(?=[^a-zA-Z0-9_]|\z)/, + # Words allowing _ but not allowing __ + '__' => /#?[a-zA-Z0-9.\/:\[\]-]*(_[a-zA-Z0-9.\/:\[\]-]+)*[^\s]?__(?=[^a-zA-Z0-9]|\z)/, + **%w[* ** + ++ ` ``].to_h do |s| + # normal words that can be used within +word+ or *word* + [s, /#?[a-zA-Z0-9_.\/:\[\]-]+[^\s]?#{Regexp.escape(s)}(?=[^a-zA-Z0-9]|\z)/] end + } # :nodoc: + + def initialize(string) + @scanner = StringScanner.new(string) + @last_match = nil + @scanner_negative_cache = Set.new + @stack = [] + @delimiters = {} end - next unless close + # Return the current parsing node on @stack. - while current[:delimiter] != close - children = current[:children] - open_token = current[:token] - stack_pop - current[:children] << open_token if open_token - current[:children].concat(children) + def current + @stack.last end - token = current[:token] - children = compact_string(current[:children]) - stack_pop - - return children if close == :root - - if close == :tidylink || close == :invalidated_tidylink - if tidylink_url - current[:children] << { type: :TIDYLINK, children: children, url: tidylink_url } - invalidate_open_tidylinks - else - current[:children] << token - current[:children].concat(children) + # Parse and return an array of nodes. + # Node format: + # { + # type: :EM | :BOLD | :BOLD_WORD | :EM_WORD | :TT | :STRIKE | :HARD_BREAK | :TIDYLINK, + # url: string # only for :TIDYLINK + # children: [string_or_node, ...] + # } + + def parse + stack_push(:root, nil) + while true + type, token, value = scan_token + close = nil + tidylink_url = nil + case type + when :node + current[:children] << value + invalidate_open_tidylinks if value[:type] == :TIDYLINK + when :eof + close = :root + when :tidylink_open + stack_push(:tidylink, token) + when :tidylink_close + close = :tidylink + if value + tidylink_url = value + else + # Tidylink closing brace without URL part. Treat opening and closing braces as normal text + # `{labelnodes}...` case. + current[:children] << token + end + when :invalidated_tidylink_close + # `{...{label}[url]...}` case. Nested tidylink invalidates outer one. The last `}` closes the invalidated tidylink. + current[:children] << token + close = :invalidated_tidylink + when :text + current[:children] << token + when :open + stack_push(value, token) + when :close + if @delimiters[value] + close = value + else + # closing tag without matching opening tag. Treat as normal text. + current[:children] << token + end + end + + next unless close + + while current[:delimiter] != close + children = current[:children] + open_token = current[:token] + stack_pop + current[:children] << open_token if open_token + current[:children].concat(children) + end + + token = current[:token] + children = compact_string(current[:children]) + stack_pop + + return children if close == :root + + if close == :tidylink || close == :invalidated_tidylink + if tidylink_url + current[:children] << { type: :TIDYLINK, children: children, url: tidylink_url } + invalidate_open_tidylinks + else + current[:children] << token + current[:children].concat(children) + end + else + current[:children] << { type: TAGS[close], children: children } + end end - else - current[:children] << { type: TAGS[close], children: children } end - end - end private - # When a valid tidylink node is encountered, invalidate all nested tidylinks. + # When a valid tidylink node is encountered, invalidate all nested tidylinks. - def invalidate_open_tidylinks - return unless @delimiters[:tidylink] + def invalidate_open_tidylinks + return unless @delimiters[:tidylink] - @delimiters[:invalidated_tidylink] ||= [] - @delimiters[:tidylink].each do |idx| - @delimiters[:invalidated_tidylink] << idx - @stack[idx][:delimiter] = :invalidated_tidylink - end - @delimiters.delete(:tidylink) - end - - # Pop the top node off the stack when node is closed by a closing delimiter or an error. + @delimiters[:invalidated_tidylink] ||= [] + @delimiters[:tidylink].each do |idx| + @delimiters[:invalidated_tidylink] << idx + @stack[idx][:delimiter] = :invalidated_tidylink + end + @delimiters.delete(:tidylink) + end - def stack_pop - delimiter = current[:delimiter] - @delimiters[delimiter].pop - @delimiters.delete(delimiter) if @delimiters[delimiter].empty? - @stack.pop - end + # Pop the top node off the stack when node is closed by a closing delimiter or an error. - # Push a new node onto the stack when encountering an opening delimiter. + def stack_pop + delimiter = current[:delimiter] + @delimiters[delimiter].pop + @delimiters.delete(delimiter) if @delimiters[delimiter].empty? + @stack.pop + end - def stack_push(delimiter, token) - node = { delimiter: delimiter, token: token, children: [] } - (@delimiters[delimiter] ||= []) << @stack.size - @stack << node - end + # Push a new node onto the stack when encountering an opening delimiter. - # Compacts adjacent strings in +nodes+ into a single string. + def stack_push(delimiter, token) + node = { delimiter: delimiter, token: token, children: [] } + (@delimiters[delimiter] ||= []) << @stack.size + @stack << node + end - def compact_string(nodes) - nodes.chunk {|e| String === e }.flat_map do |is_str, elems| - is_str ? elems.join : elems - end - end + # Compacts adjacent strings in +nodes+ into a single string. - # Scan from StringScanner with +pattern+ - # If +negative_cache+ is true, caches scan failure result. scan(pattern, negative_cache: true) return nil when it is called again after a failure. - # Be careful to use +negative_cache+ with a pattern and position that does not match after previous failure. + def compact_string(nodes) + nodes.chunk {|e| String === e }.flat_map do |is_str, elems| + is_str ? elems.join : elems + end + end - def strscan(pattern, negative_cache: false) - return if negative_cache && @scanner_negative_cache.include?(pattern) + # Scan from StringScanner with +pattern+ + # If +negative_cache+ is true, caches scan failure result. scan(pattern, negative_cache: true) return nil when it is called again after a failure. + # Be careful to use +negative_cache+ with a pattern and position that does not match after previous failure. - string = @scanner.scan(pattern) - @last_match = string if string - @scanner_negative_cache << pattern if !string && negative_cache - string - end + def strscan(pattern, negative_cache: false) + return if negative_cache && @scanner_negative_cache.include?(pattern) - # Scan and return the next token for parsing. - # Returns [token_type, token_string_or_nil, extra_info] - - def scan_token - last_match = @last_match - token = strscan(SCANNER_REGEXP) - type, name = TOKENS[token] - - case type - when :word_pair - # If the character before word pair delimiter is alphanumeric, do not treat as word pair. - word_pair = strscan(WORD_REGEXPS[token]) unless /[a-zA-Z0-9]\z/.match?(last_match) - - if word_pair.nil? - [:text, token, nil] - elsif token == '__' && word_pair.match?(/\A[a-zA-Z]+__\z/) - # Special exception: __FILE__, __LINE__, __send__ should be treated as normal text. - [:text, "#{token}#{word_pair}", nil] - else - [:node, nil, { type: WORD_PAIRS[token], children: [word_pair.delete_suffix(token)] }] - end - when :open_tag - [:open, token, name] - when :close_tag - [:close, token, name] - when :code_start - if (codeblock = strscan(CODEBLOCK_REGEXPS[name], negative_cache: true)) - # Need to unescape `\\` and `\<`. - # RDoc also unescapes backslash + word separators, but this is not really necessary. - content = codeblock.delete_suffix("").gsub(/\\(.)/) { '\\<*+_`'.include?($1) ? $1 : $& } - [:node, nil, { type: :TT, children: content.empty? ? [] : [content] }] - else - [:text, token, nil] + string = @scanner.scan(pattern) + @last_match = string if string + @scanner_negative_cache << pattern if !string && negative_cache + string end - when :standalone_tag - [:node, nil, { type: STANDALONE_TAGS[name], children: [] }] - when :tidylink_start - [:tidylink_open, token, nil] - when :tidylink_mid - if @delimiters[:tidylink] - if (url = read_tidylink_url) - [:tidylink_close, nil, url] + + # Scan and return the next token for parsing. + # Returns [token_type, token_string_or_nil, extra_info] + + def scan_token + last_match = @last_match + token = strscan(SCANNER_REGEXP) + type, name = TOKENS[token] + + case type + when :word_pair + # If the character before word pair delimiter is alphanumeric, do not treat as word pair. + word_pair = strscan(WORD_REGEXPS[token]) unless /[a-zA-Z0-9]\z/.match?(last_match) + + if word_pair.nil? + [:text, token, nil] + elsif token == '__' && word_pair.match?(/\A[a-zA-Z]+__\z/) + # Special exception: __FILE__, __LINE__, __send__ should be treated as normal text. + [:text, "#{token}#{word_pair}", nil] + else + [:node, nil, { type: WORD_PAIRS[token], children: [word_pair.delete_suffix(token)] }] + end + when :open_tag + [:open, token, name] + when :close_tag + [:close, token, name] + when :code_start + if (codeblock = strscan(CODEBLOCK_REGEXPS[name], negative_cache: true)) + # Need to unescape `\\` and `\<`. + # RDoc also unescapes backslash + word separators, but this is not really necessary. + content = codeblock.delete_suffix("").gsub(/\\(.)/) { '\\<*+_`'.include?($1) ? $1 : $& } + [:node, nil, { type: :TT, children: content.empty? ? [] : [content] }] + else + [:text, token, nil] + end + when :standalone_tag + [:node, nil, { type: STANDALONE_TAGS[name], children: [] }] + when :tidylink_start + [:tidylink_open, token, nil] + when :tidylink_mid + if @delimiters[:tidylink] + if (url = read_tidylink_url) + [:tidylink_close, nil, url] + else + [:tidylink_close, token, nil] + end + elsif @delimiters[:invalidated_tidylink] + [:invalidated_tidylink_close, token, nil] + else + [:text, token, nil] + end + when :escape + next_char = strscan(/./) + if next_char.nil? + # backslash at end of string + [:text, '\\', nil] + elsif next_char && ESCAPING_CHARS.include?(next_char) + # escaped character + [:text, next_char, nil] + else + # If next_char not an escaping character, it is treated as text token with backslash + next_char + # For example, backslash of `\Ruby` (suppressed crossref) remains. + [:text, "\\#{next_char}", nil] + end else - [:tidylink_close, token, nil] + if token.nil? + [:eof, nil, nil] + elsif token.match?(/\A[A-Za-z0-9]*\z/) && (url = read_tidylink_url) + # Simplified tidylink: label[url] + [:node, nil, { type: :TIDYLINK, children: [token], url: url }] + else + [:text, token, nil] + end end - elsif @delimiters[:invalidated_tidylink] - [:invalidated_tidylink_close, token, nil] - else - [:text, token, nil] - end - when :escape - next_char = strscan(/./) - if next_char.nil? - # backslash at end of string - [:text, '\\', nil] - elsif next_char && ESCAPING_CHARS.include?(next_char) - # escaped character - [:text, next_char, nil] - else - # If next_char not an escaping character, it is treated as text token with backslash + next_char - # For example, backslash of `\Ruby` (suppressed crossref) remains. - [:text, "\\#{next_char}", nil] - end - else - if token.nil? - [:eof, nil, nil] - elsif token.match?(/\A[A-Za-z0-9]*\z/) && (url = read_tidylink_url) - # Simplified tidylink: label[url] - [:node, nil, { type: :TIDYLINK, children: [token], url: url }] - else - [:text, token, nil] end - end - end - # Read the URL part of a tidylink from the current position. - # Returns nil if no valid URL part is found. - # URL part is enclosed in square brackets and may contain escaped brackets. - # Example: [http://example.com/?q=\[\]] represents http://example.com/?q=[]. - # If we're accepting rdoc-style links in markdown, url may include *+<_ with backslash escape. + # Read the URL part of a tidylink from the current position. + # Returns nil if no valid URL part is found. + # URL part is enclosed in square brackets and may contain escaped brackets. + # Example: [http://example.com/?q=\[\]] represents http://example.com/?q=[]. + # If we're accepting rdoc-style links in markdown, url may include *+<_ with backslash escape. - def read_tidylink_url - bracketed_url = strscan(/\[([^\s\[\]\\]|\\[\[\]\\*+<_])+\]/) - bracketed_url[1...-1].gsub(/\\(.)/, '\1') if bracketed_url + def read_tidylink_url + bracketed_url = strscan(/\[([^\s\[\]\\]|\\[\[\]\\*+<_])+\]/) + bracketed_url[1...-1].gsub(/\\(.)/, '\1') if bracketed_url + end + end end end diff --git a/lib/rdoc/markup/paragraph.rb b/lib/rdoc/markup/paragraph.rb index 2db7d8721c..7fb0f03164 100644 --- a/lib/rdoc/markup/paragraph.rb +++ b/lib/rdoc/markup/paragraph.rb @@ -1,28 +1,32 @@ # frozen_string_literal: true -## -# A Paragraph of text +module RDoc + class Markup + ## + # A Paragraph of text -class RDoc::Markup::Paragraph < RDoc::Markup::Raw + class Paragraph < ::RDoc::Markup::Raw - ## - # Calls #accept_paragraph on +visitor+ + ## + # Calls #accept_paragraph on +visitor+ - def accept(visitor) - visitor.accept_paragraph self - end + def accept(visitor) + visitor.accept_paragraph self + end - ## - # Joins the raw paragraph text and converts inline HardBreaks to the - # +hard_break+ text. + ## + # Joins the raw paragraph text and converts inline HardBreaks to the + # +hard_break+ text. - def text(hard_break = '') - @parts.map do |part| - if RDoc::Markup::HardBreak === part - hard_break - else - part + def text(hard_break = '') + @parts.map do |part| + if ::RDoc::Markup::HardBreak === part + hard_break + else + part + end + end.join end - end.join - end + end + end end diff --git a/lib/rdoc/markup/parser.rb b/lib/rdoc/markup/parser.rb index 9b2e0aaa74..b91e369378 100644 --- a/lib/rdoc/markup/parser.rb +++ b/lib/rdoc/markup/parser.rb @@ -1,585 +1,589 @@ # frozen_string_literal: true require 'strscan' -## -# A recursive-descent parser for RDoc markup. -# -# The parser tokenizes an input string then parses the tokens into a Document. -# Documents can be converted into output formats by writing a visitor like -# RDoc::Markup::ToHTML. -# -# The parser only handles the block-level constructs Paragraph, List, -# ListItem, Heading, Verbatim, BlankLine, Rule and BlockQuote. -# Inline markup such as \+blah\+ is handled separately by -# RDoc::Markup::InlineParser. -# -# To see what markup the Parser implements read RDoc. To see how to use -# RDoc markup to format text in your program read RDoc::Markup. - -class RDoc::Markup::Parser - - include RDoc::Text - - ## - # List token types - - LIST_TOKENS = [ - :BULLET, - :LABEL, - :LALPHA, - :NOTE, - :NUMBER, - :UALPHA, - ] - - ## - # Parser error subclass - - class Error < RuntimeError; end - - ## - # Raised when the parser is unable to handle the given markup - - class ParseError < Error; end - - ## - # Enables display of debugging information - - attr_accessor :debug - - ## - # Token accessor - - attr_reader :tokens - - ## - # Parses +str+ into a Document. - # - # Use RDoc::Markup#parse instead of this method. - - def self.parse(str) - parser = new - parser.tokenize str - doc = RDoc::Markup::Document.new - parser.parse doc - end - - ## - # Returns a token stream for +str+, for testing - - def self.tokenize(str) - parser = new - parser.tokenize str - parser.tokens - end - - ## - # Creates a new Parser. See also ::parse +module RDoc + class Markup + ## + # A recursive-descent parser for RDoc markup. + # + # The parser tokenizes an input string then parses the tokens into a Document. + # Documents can be converted into output formats by writing a visitor like + # RDoc::Markup::ToHTML. + # + # The parser only handles the block-level constructs Paragraph, List, + # ListItem, Heading, Verbatim, BlankLine, Rule and BlockQuote. + # Inline markup such as \+blah\+ is handled separately by + # RDoc::Markup::InlineParser. + # + # To see what markup the Parser implements read RDoc. To see how to use + # RDoc markup to format text in your program read RDoc::Markup. + + class Parser + + include ::RDoc::Text + + ## + # List token types + + LIST_TOKENS = [ + :BULLET, + :LABEL, + :LALPHA, + :NOTE, + :NUMBER, + :UALPHA, + ] + + ## + # Parser error subclass + + class Error < RuntimeError; end + + ## + # Raised when the parser is unable to handle the given markup + + class ParseError < Error; end + + ## + # Enables display of debugging information + + attr_accessor :debug + + ## + # Token accessor + + attr_reader :tokens + + ## + # Parses +str+ into a Document. + # + # Use RDoc::Markup#parse instead of this method. + + def self.parse(str) + parser = new + parser.tokenize str + doc = ::RDoc::Markup::Document.new + parser.parse doc + end - def initialize - @binary_input = nil - @current_token = nil - @debug = false - @s = nil - @tokens = [] - end + ## + # Returns a token stream for +str+, for testing - ## - # Builds a Heading of +level+ + def self.tokenize(str) + parser = new + parser.tokenize str + parser.tokens + end - def build_heading(level) - type, text, = get + ## + # Creates a new Parser. See also ::parse - text = case type - when :TEXT - skip :NEWLINE - text - else - unget - '' - end + def initialize + @binary_input = nil + @current_token = nil + @debug = false + @s = nil + @tokens = [] + end - RDoc::Markup::Heading.new level, text - end + ## + # Builds a Heading of +level+ - ## - # Builds a List flush to +margin+ + def build_heading(level) + type, text, = get - def build_list(margin) - p :list_start => margin if @debug + text = case type + when :TEXT + skip :NEWLINE + text + else + unget + '' + end - list = RDoc::Markup::List.new - label = nil + ::RDoc::Markup::Heading.new level, text + end - until @tokens.empty? do - type, data, column, = get + ## + # Builds a List flush to +margin+ - case type - when *LIST_TOKENS - if column < margin || (list.type && list.type != type) - unget - break - end + def build_list(margin) + p :list_start => margin if @debug - list.type = type - peek_type, _, column, = peek_token + list = ::RDoc::Markup::List.new + label = nil - case type - when :NOTE, :LABEL - label = [] unless label + until @tokens.empty? do + type, data, column, = get - if peek_type == :NEWLINE - # description not on the same line as LABEL/NOTE - # skip the trailing newline & any blank lines below - while peek_type == :NEWLINE - get - peek_type, _, column, = peek_token + case type + when *LIST_TOKENS + if column < margin || (list.type && list.type != type) + unget + break end - # we may be: - # - at end of stream - # - at a column < margin: - # [text] - # blah blah blah - # - at the same column, but with a different type of list item - # [text] - # * blah blah - # - at the same column, with the same type of list item - # [one] - # [two] - # In all cases, we have an empty description. - # In the last case only, we continue. - if peek_type.nil? || column < margin - empty = true - elsif column == margin - case peek_type - when type - empty = :continue - when *LIST_TOKENS - empty = true - else - empty = false + list.type = type + peek_type, _, column, = peek_token + + case type + when :NOTE, :LABEL + label = [] unless label + + if peek_type == :NEWLINE + # description not on the same line as LABEL/NOTE + # skip the trailing newline & any blank lines below + while peek_type == :NEWLINE + get + peek_type, _, column, = peek_token + end + + # we may be: + # - at end of stream + # - at a column < margin: + # [text] + # blah blah blah + # - at the same column, but with a different type of list item + # [text] + # * blah blah + # - at the same column, with the same type of list item + # [one] + # [two] + # In all cases, we have an empty description. + # In the last case only, we continue. + if peek_type.nil? || column < margin + empty = true + elsif column == margin + case peek_type + when type + empty = :continue + when *LIST_TOKENS + empty = true + else + empty = false + end + else + empty = false + end + + if empty + label << data + next if empty == :continue + break + end end else - empty = false + data = nil end - if empty - label << data - next if empty == :continue - break + if label + data = label << data + label = nil end + + list_item = ::RDoc::Markup::ListItem.new data + parse list_item, column + list << list_item + + else + unget + break end - else - data = nil end - if label - data = label << data - label = nil - end + p :list_end => margin if @debug - list_item = RDoc::Markup::ListItem.new data - parse list_item, column - list << list_item + if list.empty? + return nil unless label + return nil unless [:LABEL, :NOTE].include? list.type - else - unget - break - end - end + list_item = ::RDoc::Markup::ListItem.new label, ::RDoc::Markup::BlankLine.new + list << list_item + end - p :list_end => margin if @debug + list + end - if list.empty? - return nil unless label - return nil unless [:LABEL, :NOTE].include? list.type + ## + # Builds a Paragraph that is flush to +margin+ - list_item = RDoc::Markup::ListItem.new label, RDoc::Markup::BlankLine.new - list << list_item - end + def build_paragraph(margin) + p :paragraph_start => margin if @debug - list - end + paragraph = ::RDoc::Markup::Paragraph.new - ## - # Builds a Paragraph that is flush to +margin+ + until @tokens.empty? do + type, data, column, = get - def build_paragraph(margin) - p :paragraph_start => margin if @debug + if type == :TEXT and column == margin + paragraph << data - paragraph = RDoc::Markup::Paragraph.new + break if peek_token.first == :BREAK - until @tokens.empty? do - type, data, column, = get + data << ' ' if skip :NEWLINE and /#{SPACE_SEPARATED_LETTER_CLASS}\z/o.match?(data) + else + unget + break + end + end - if type == :TEXT and column == margin - paragraph << data + paragraph.parts.last.sub!(/ \z/, '') # cleanup - break if peek_token.first == :BREAK + p :paragraph_end => margin if @debug - data << ' ' if skip :NEWLINE and /#{SPACE_SEPARATED_LETTER_CLASS}\z/o.match?(data) - else - unget - break + paragraph end - end - paragraph.parts.last.sub!(/ \z/, '') # cleanup + ## + # Builds a Verbatim that is indented from +margin+. + # + # The verbatim block is shifted left (the least indented lines start in + # column 0). Each part of the verbatim is one line of text, always + # terminated by a newline. Blank lines always consist of a single newline + # character, and there is never a single newline at the end of the verbatim. - p :paragraph_end => margin if @debug - - paragraph - end + def build_verbatim(margin) + p :verbatim_begin => margin if @debug + verbatim = ::RDoc::Markup::Verbatim.new - ## - # Builds a Verbatim that is indented from +margin+. - # - # The verbatim block is shifted left (the least indented lines start in - # column 0). Each part of the verbatim is one line of text, always - # terminated by a newline. Blank lines always consist of a single newline - # character, and there is never a single newline at the end of the verbatim. - - def build_verbatim(margin) - p :verbatim_begin => margin if @debug - verbatim = RDoc::Markup::Verbatim.new + min_indent = nil + generate_leading_spaces = true + line = ''.dup - min_indent = nil - generate_leading_spaces = true - line = ''.dup + until @tokens.empty? do + type, data, column, = get - until @tokens.empty? do - type, data, column, = get + if type == :NEWLINE + line << data + verbatim << line + line = ''.dup + generate_leading_spaces = true + next + end - if type == :NEWLINE - line << data - verbatim << line - line = ''.dup - generate_leading_spaces = true - next - end + if column <= margin + unget + break + end - if column <= margin - unget - break - end + if generate_leading_spaces + indent = column - margin + line << ' ' * indent + min_indent = indent if min_indent.nil? || indent < min_indent + generate_leading_spaces = false + end - if generate_leading_spaces - indent = column - margin - line << ' ' * indent - min_indent = indent if min_indent.nil? || indent < min_indent - generate_leading_spaces = false - end + case type + when :HEADER + line << '=' * data + _, _, peek_column, = peek_token + peek_column ||= column + data + indent = peek_column - column - data + line << ' ' * indent + when :RULE + width = 2 + data + line << '-' * width + _, _, peek_column, = peek_token + peek_column ||= column + width + indent = peek_column - column - width + line << ' ' * indent + when :BREAK, :TEXT + line << data + when :BLOCKQUOTE + line << '>>>' + peek_type, _, peek_column = peek_token + if peek_type != :NEWLINE and peek_column + line << ' ' * (peek_column - column - 3) + end + else # *LIST_TOKENS + list_marker = case type + when :BULLET then data + when :LABEL then "[#{data}]" + when :NOTE then "#{data}::" + else # :LALPHA, :NUMBER, :UALPHA + "#{data}." + end + line << list_marker + peek_type, _, peek_column = peek_token + unless peek_type == :NEWLINE + peek_column ||= column + list_marker.length + indent = peek_column - column - list_marker.length + line << ' ' * indent + end + end - case type - when :HEADER - line << '=' * data - _, _, peek_column, = peek_token - peek_column ||= column + data - indent = peek_column - column - data - line << ' ' * indent - when :RULE - width = 2 + data - line << '-' * width - _, _, peek_column, = peek_token - peek_column ||= column + width - indent = peek_column - column - width - line << ' ' * indent - when :BREAK, :TEXT - line << data - when :BLOCKQUOTE - line << '>>>' - peek_type, _, peek_column = peek_token - if peek_type != :NEWLINE and peek_column - line << ' ' * (peek_column - column - 3) end - else # *LIST_TOKENS - list_marker = case type - when :BULLET then data - when :LABEL then "[#{data}]" - when :NOTE then "#{data}::" - else # :LALPHA, :NUMBER, :UALPHA - "#{data}." - end - line << list_marker - peek_type, _, peek_column = peek_token - unless peek_type == :NEWLINE - peek_column ||= column + list_marker.length - indent = peek_column - column - list_marker.length - line << ' ' * indent - end - end - end + verbatim << line << "\n" unless line.empty? + verbatim.parts.each { |p| p.slice!(0, min_indent) unless p == "\n" } if min_indent > 0 + verbatim.normalize - verbatim << line << "\n" unless line.empty? - verbatim.parts.each { |p| p.slice!(0, min_indent) unless p == "\n" } if min_indent > 0 - verbatim.normalize + p :verbatim_end => margin if @debug - p :verbatim_end => margin if @debug + verbatim + end - verbatim - end + ## + # Pulls the next token from the stream. - ## - # Pulls the next token from the stream. + def get + @current_token = @tokens.shift + p :get => @current_token if @debug + @current_token + end - def get - @current_token = @tokens.shift - p :get => @current_token if @debug - @current_token - end + ## + # Parses the tokens into an array of RDoc::Markup::XXX objects, + # and appends them to the passed +parent+ RDoc::Markup::YYY object. + # + # Exits at the end of the token stream, or when it encounters a token + # in a column less than +indent+ (unless it is a NEWLINE). + # + # Returns +parent+. + + def parse(parent, indent = 0) + p :parse_start => indent if @debug + + until @tokens.empty? do + type, data, column, = get + + case type + when :BREAK + parent << ::RDoc::Markup::BlankLine.new + skip :NEWLINE, false + next + when :NEWLINE + # trailing newlines are skipped below, so this is a blank line + parent << ::RDoc::Markup::BlankLine.new + skip :NEWLINE, false + next + end - ## - # Parses the tokens into an array of RDoc::Markup::XXX objects, - # and appends them to the passed +parent+ RDoc::Markup::YYY object. - # - # Exits at the end of the token stream, or when it encounters a token - # in a column less than +indent+ (unless it is a NEWLINE). - # - # Returns +parent+. - - def parse(parent, indent = 0) - p :parse_start => indent if @debug - - until @tokens.empty? do - type, data, column, = get - - case type - when :BREAK - parent << RDoc::Markup::BlankLine.new - skip :NEWLINE, false - next - when :NEWLINE - # trailing newlines are skipped below, so this is a blank line - parent << RDoc::Markup::BlankLine.new - skip :NEWLINE, false - next - end + # indentation change: break or verbatim + if column < indent + unget + break + elsif column > indent + unget + parent << build_verbatim(indent) + next + end - # indentation change: break or verbatim - if column < indent - unget - break - elsif column > indent - unget - parent << build_verbatim(indent) - next - end + # indentation is the same + case type + when :HEADER + parent << build_heading(data) + when :RULE + parent << ::RDoc::Markup::Rule.new(data) + skip :NEWLINE + when :TEXT + unget + parse_text parent, indent + when :BLOCKQUOTE + nil while (type, = get; type) and type != :NEWLINE + _, _, column, = peek_token + bq = ::RDoc::Markup::BlockQuote.new + p :blockquote_start => [data, column] if @debug + parse bq, column + p :blockquote_end => indent if @debug + parent << bq + when *LIST_TOKENS + unget + parent << build_list(indent) + else + type, data, column, line = @current_token + raise ParseError, "Unhandled token #{type} (#{data.inspect}) at #{line}:#{column}" + end + end - # indentation is the same - case type - when :HEADER - parent << build_heading(data) - when :RULE - parent << RDoc::Markup::Rule.new(data) - skip :NEWLINE - when :TEXT - unget - parse_text parent, indent - when :BLOCKQUOTE - nil while (type, = get; type) and type != :NEWLINE - _, _, column, = peek_token - bq = RDoc::Markup::BlockQuote.new - p :blockquote_start => [data, column] if @debug - parse bq, column - p :blockquote_end => indent if @debug - parent << bq - when *LIST_TOKENS - unget - parent << build_list(indent) - else - type, data, column, line = @current_token - raise ParseError, "Unhandled token #{type} (#{data.inspect}) at #{line}:#{column}" - end - end + p :parse_end => indent if @debug - p :parse_end => indent if @debug + parent - parent + end - end + ## + # Small hook that is overridden by RDoc::TomDoc - ## - # Small hook that is overridden by RDoc::TomDoc + def parse_text(parent, indent) # :nodoc: + parent << build_paragraph(indent) + end - def parse_text(parent, indent) # :nodoc: - parent << build_paragraph(indent) - end + ## + # Returns the next token on the stream without modifying the stream - ## - # Returns the next token on the stream without modifying the stream + def peek_token + token = @tokens.first || [] + p :peek => token if @debug + token + end - def peek_token - token = @tokens.first || [] - p :peek => token if @debug - token - end + ## + # A simple wrapper of StringScanner that is aware of the current column and lineno - ## - # A simple wrapper of StringScanner that is aware of the current column and lineno + class MyStringScanner + # :stopdoc: - class MyStringScanner - # :stopdoc: + def initialize(input) + @line = @column = 0 + @s = StringScanner.new input + end - def initialize(input) - @line = @column = 0 - @s = StringScanner.new input - end + def scan(re) + ret = @s.scan(re) + @column += ret.length if ret + ret + end - def scan(re) - ret = @s.scan(re) - @column += ret.length if ret - ret - end + def unscan(s) + @s.pos -= s.bytesize + @column -= s.length + end - def unscan(s) - @s.pos -= s.bytesize - @column -= s.length - end + def pos + [@column, @line] + end - def pos - [@column, @line] - end + def newline! + @column = 0 + @line += 1 + end - def newline! - @column = 0 - @line += 1 - end + def eos? + @s.eos? + end - def eos? - @s.eos? - end + def matched + @s.matched + end - def matched - @s.matched - end + def [](i) + @s[i] + end - def [](i) - @s[i] - end + #:startdoc: + end - #:startdoc: - end + ## + # Creates the StringScanner - ## - # Creates the StringScanner + def setup_scanner(input) + @s = MyStringScanner.new input + end - def setup_scanner(input) - @s = MyStringScanner.new input - end + ## + # Skips the next token if its type is +token_type+. + # + # Optionally raises an error if the next token is not of the expected type. - ## - # Skips the next token if its type is +token_type+. - # - # Optionally raises an error if the next token is not of the expected type. - - def skip(token_type, error = true) - type, = get - return unless type # end of stream - return @current_token if token_type == type - unget - raise ParseError, "expected #{token_type} got #{@current_token.inspect}" if error - end + def skip(token_type, error = true) + type, = get + return unless type # end of stream + return @current_token if token_type == type + unget + raise ParseError, "expected #{token_type} got #{@current_token.inspect}" if error + end - ## - # Turns text +input+ into a stream of tokens - - def tokenize(input) - setup_scanner input - - until @s.eos? do - pos = @s.pos - - # leading spaces will be reflected by the column of the next token - # the only thing we loose are trailing spaces at the end of the file - next if @s.scan(/ +/) - - # note: after BULLET, LABEL, etc., - # indent will be the column of the next non-newline token - - @tokens << case - # [CR]LF => :NEWLINE - when @s.scan(/\r?\n/) - token = [:NEWLINE, @s.matched, *pos] - @s.newline! - token - # === text => :HEADER then :TEXT - when @s.scan(/(=+)(\s*)/) - level = @s[1].length - header = [:HEADER, level, *pos] - - if @s[2] =~ /^\r?\n/ - @s.unscan(@s[2]) - header - else - pos = @s.pos - @s.scan(/.*/) - @tokens << header - [:TEXT, @s.matched.sub(/\r$/, ''), *pos] - end - # --- (at least 3) and nothing else on the line => :RULE - when @s.scan(/(-{3,}) *\r?$/) - [:RULE, @s[1].length - 2, *pos] - # * or - followed by white space and text => :BULLET - when @s.scan(/([*-]) +(\S)/) - @s.unscan(@s[2]) - [:BULLET, @s[1], *pos] - # A. text, a. text, 12. text => :UALPHA, :LALPHA, :NUMBER - when @s.scan(/([a-z]|\d+)\. +(\S)/i) - # FIXME if tab(s), the column will be wrong - # either support tabs everywhere by first expanding them to - # spaces, or assume that they will have been replaced - # before (and provide a check for that at least in debug - # mode) - list_label = @s[1] - @s.unscan(@s[2]) - list_type = - case list_label - when /[a-z]/ then :LALPHA - when /[A-Z]/ then :UALPHA - when /\d/ then :NUMBER + ## + # Turns text +input+ into a stream of tokens + + def tokenize(input) + setup_scanner input + + until @s.eos? do + pos = @s.pos + + # leading spaces will be reflected by the column of the next token + # the only thing we loose are trailing spaces at the end of the file + next if @s.scan(/ +/) + + # note: after BULLET, LABEL, etc., + # indent will be the column of the next non-newline token + + @tokens << case + # [CR]LF => :NEWLINE + when @s.scan(/\r?\n/) + token = [:NEWLINE, @s.matched, *pos] + @s.newline! + token + # === text => :HEADER then :TEXT + when @s.scan(/(=+)(\s*)/) + level = @s[1].length + header = [:HEADER, level, *pos] + + if @s[2] =~ /^\r?\n/ + @s.unscan(@s[2]) + header + else + pos = @s.pos + @s.scan(/.*/) + @tokens << header + [:TEXT, @s.matched.sub(/\r$/, ''), *pos] + end + # --- (at least 3) and nothing else on the line => :RULE + when @s.scan(/(-{3,}) *\r?$/) + [:RULE, @s[1].length - 2, *pos] + # * or - followed by white space and text => :BULLET + when @s.scan(/([*-]) +(\S)/) + @s.unscan(@s[2]) + [:BULLET, @s[1], *pos] + # A. text, a. text, 12. text => :UALPHA, :LALPHA, :NUMBER + when @s.scan(/([a-z]|\d+)\. +(\S)/i) + # FIXME if tab(s), the column will be wrong + # either support tabs everywhere by first expanding them to + # spaces, or assume that they will have been replaced + # before (and provide a check for that at least in debug + # mode) + list_label = @s[1] + @s.unscan(@s[2]) + list_type = + case list_label + when /[a-z]/ then :LALPHA + when /[A-Z]/ then :UALPHA + when /\d/ then :NUMBER + else + raise ParseError, "BUG token #{list_label}" + end + [list_type, list_label, *pos] + # [text] followed by spaces or end of line => :LABEL + when @s.scan(/\[(.*?)\]( +|\r?$)/) + [:LABEL, @s[1], *pos] + # text:: followed by spaces or end of line => :NOTE + when @s.scan(/(.*?)::( +|\r?$)/) + [:NOTE, @s[1], *pos] + # >>> followed by end of line => :BLOCKQUOTE + when @s.scan(/>>> *(\w+)?$/) + if word = @s[1] + @s.unscan(word) + end + [:BLOCKQUOTE, word, *pos] + # anything else: :TEXT else - raise ParseError, "BUG token #{list_label}" + @s.scan(/(.*?)( )?\r?$/) + token = [:TEXT, @s[1], *pos] + + if @s[2] + @tokens << token + [:BREAK, @s[2], pos[0] + @s[1].length, pos[1]] + else + token + end end - [list_type, list_label, *pos] - # [text] followed by spaces or end of line => :LABEL - when @s.scan(/\[(.*?)\]( +|\r?$)/) - [:LABEL, @s[1], *pos] - # text:: followed by spaces or end of line => :NOTE - when @s.scan(/(.*?)::( +|\r?$)/) - [:NOTE, @s[1], *pos] - # >>> followed by end of line => :BLOCKQUOTE - when @s.scan(/>>> *(\w+)?$/) - if word = @s[1] - @s.unscan(word) - end - [:BLOCKQUOTE, word, *pos] - # anything else: :TEXT - else - @s.scan(/(.*?)( )?\r?$/) - token = [:TEXT, @s[1], *pos] - - if @s[2] - @tokens << token - [:BREAK, @s[2], pos[0] + @s[1].length, pos[1]] - else - token - end - end - end + end - self - end + self + end - ## - # Returns the current token to the token stream + ## + # Returns the current token to the token stream - def unget - token = @current_token - p :unget => token if @debug - raise Error, 'too many #ungets' if token == @tokens.first - @tokens.unshift token if token - end + def unget + token = @current_token + p :unget => token if @debug + raise Error, 'too many #ungets' if token == @tokens.first + @tokens.unshift token if token + end + end + end end diff --git a/lib/rdoc/markup/pre_process.rb b/lib/rdoc/markup/pre_process.rb index e30b5da9bb..c385a7778c 100644 --- a/lib/rdoc/markup/pre_process.rb +++ b/lib/rdoc/markup/pre_process.rb @@ -1,317 +1,321 @@ # frozen_string_literal: true -## -# Handle common directives that can occur in a block of text: -# -# \:include: filename -# -# Directives can be escaped by preceding them with a backslash. -# -# RDoc plugin authors can register additional directives to be handled by -# using RDoc::Markup::PreProcess::register. -# -# Any directive that is not built-in to RDoc (including those registered via -# plugins) will be stored in the metadata hash on the CodeObject the comment -# is attached to. See RDoc::Markup@Directives for the list of built-in -# directives. - -class RDoc::Markup::PreProcess - - ## - # An RDoc::Options instance that will be filled in with overrides from - # directives - - attr_accessor :options - - ## - # Adds a post-process handler for directives. The handler will be called - # with the result RDoc::Comment (or text String) and the code object for the - # comment (if any). - - def self.post_process(&block) - @post_processors << block - end - - ## - # Registered post-processors - - def self.post_processors - @post_processors - end - - ## - # Registers +directive+ as one handled by RDoc. If a block is given the - # directive will be replaced by the result of the block, otherwise the - # directive will be removed from the processed text. - # - # The block will be called with the directive name and the directive - # parameter: - # - # RDoc::Markup::PreProcess.register 'my-directive' do |directive, param| - # # replace text, etc. - # end - - def self.register(directive, &block) - @registered[directive] = block - end - - ## - # Registered directives - - def self.registered - @registered - end - - ## - # Clears all registered directives and post-processors - - def self.reset - @post_processors = [] - @registered = {} - end - - reset - - ## - # Creates a new pre-processor for +input_file_name+ that will look for - # included files in +include_path+ - - def initialize(input_file_name, include_path) - @input_file_name = input_file_name - @include_path = include_path - @options = nil - end - - ## - # Look for directives in the given +text+. - # - # Options that we don't handle are yielded. If the block returns false the - # directive is restored to the text. If the block returns nil or no block - # was given the directive is handled according to the registered directives. - # If a String was returned the directive is replaced with the string. - # - # If no matching directive was registered the directive is restored to the - # text. - # - # If +code_object+ is given and the directive is unknown then the - # directive's parameter is set as metadata on the +code_object+. See - # RDoc::CodeObject#metadata for details. - - def handle(text, code_object = nil, &block) - if RDoc::Comment === text - comment = text - text = text.text - end - - # regexp helper (square brackets for optional) - # $1 $2 $3 $4 $5 - # [prefix][\]:directive:[spaces][param]newline - text = text.gsub(/^([ \t]*(?:#|\/?\*)?[ \t]*)(\\?):([\w-]+):([ \t]*)(.+)?(\r?\n|$)/) do - # skip something like ':toto::' - next $& if $4.empty? and $5 and $5[0, 1] == ':' - - # skip if escaped - next "#$1:#$3:#$4#$5\n" unless $2.empty? - - # This is not in handle_directive because I didn't want to pass another - # argument into it - if comment and $3 == 'markup' - next "#{$1.strip}\n" unless $5 - comment.format = $5.downcase - next "#{$1.strip}\n" +module RDoc + class Markup + ## + # Handle common directives that can occur in a block of text: + # + # \:include: filename + # + # Directives can be escaped by preceding them with a backslash. + # + # RDoc plugin authors can register additional directives to be handled by + # using RDoc::Markup::PreProcess::register. + # + # Any directive that is not built-in to RDoc (including those registered via + # plugins) will be stored in the metadata hash on the CodeObject the comment + # is attached to. See RDoc::Markup@Directives for the list of built-in + # directives. + + class PreProcess + + ## + # An RDoc::Options instance that will be filled in with overrides from + # directives + + attr_accessor :options + + ## + # Adds a post-process handler for directives. The handler will be called + # with the result RDoc::Comment (or text String) and the code object for the + # comment (if any). + + def self.post_process(&block) + @post_processors << block end - handle_directive $1, $3, $5, code_object, text.encoding, &block - end - if comment - comment.text = text - else - comment = text - end + ## + # Registered post-processors - run_post_processes(comment, code_object) + def self.post_processors + @post_processors + end - text - end + ## + # Registers +directive+ as one handled by RDoc. If a block is given the + # directive will be replaced by the result of the block, otherwise the + # directive will be removed from the processed text. + # + # The block will be called with the directive name and the directive + # parameter: + # + # RDoc::Markup::PreProcess.register 'my-directive' do |directive, param| + # # replace text, etc. + # end + + def self.register(directive, &block) + @registered[directive] = block + end - # Apply directives to a code object + ## + # Registered directives - def run_pre_processes(comment_text, code_object, start_line_no, type) - comment_text, directives = parse_comment(comment_text, start_line_no, type) - directives.each do |directive, (param, line_no)| - handle_directive('', directive, param, code_object) - end - if code_object.is_a?(RDoc::AnyMethod) && (call_seq, = directives['call-seq']) && call_seq - code_object.call_seq = call_seq.lines.map(&:chomp).reject(&:empty?).join("\n") - end - format, = directives['markup'] - [comment_text, format] - end + def self.registered + @registered + end - # Perform post preocesses to a code object + ## + # Clears all registered directives and post-processors - def run_post_processes(comment, code_object) - self.class.post_processors.each do |handler| - handler.call comment, code_object - end - end + def self.reset + @post_processors = [] + @registered = {} + end - # Parse comment and return [normalized_comment_text, directives_hash] + reset - def parse_comment(text, line_no, type) - RDoc::Comment.parse(text, @input_file_name, line_no, type) do |filename, prefix_indent| - include_file(filename, prefix_indent, text.encoding) - end - end + ## + # Creates a new pre-processor for +input_file_name+ that will look for + # included files in +include_path+ - ## - # Performs the actions described by +directive+ and its parameter +param+. - # - # +code_object+ is used for directives that operate on a class or module. - # +prefix+ is used to ensure the replacement for handled directives is - # correct. +encoding+ is used for the include directive. - # - # For a list of directives in RDoc see RDoc::Markup. - #-- - # When 1.8.7 support is ditched prefix can be defaulted to '' - - def handle_directive(prefix, directive, param, code_object = nil, - encoding = nil) - blankline = "#{prefix.strip}\n" - directive = directive.downcase - - case directive - when 'arg', 'args' - return "#{prefix}:#{directive}: #{param}\n" unless code_object && code_object.kind_of?(RDoc::AnyMethod) - - code_object.params = param - - blankline - when 'category' - if RDoc::Context === code_object - section = code_object.add_section param - code_object.temporary_section = section - elsif RDoc::AnyMethod === code_object - code_object.section_title = param + def initialize(input_file_name, include_path) + @input_file_name = input_file_name + @include_path = include_path + @options = nil end - blankline # ignore category if we're not on an RDoc::Context - when 'doc' - return blankline unless code_object - code_object.document_self = true - code_object.force_documentation = true - - blankline - when 'enddoc' - return blankline unless code_object - code_object.done_documenting = true - - blankline - when 'include' - filename = param.split(' ', 2).first - include_file filename, prefix, encoding - when 'nodoc' - return blankline unless code_object - code_object.document_self = nil # notify nodoc - code_object.document_children = param !~ /all/i + ## + # Look for directives in the given +text+. + # + # Options that we don't handle are yielded. If the block returns false the + # directive is restored to the text. If the block returns nil or no block + # was given the directive is handled according to the registered directives. + # If a String was returned the directive is replaced with the string. + # + # If no matching directive was registered the directive is restored to the + # text. + # + # If +code_object+ is given and the directive is unknown then the + # directive's parameter is set as metadata on the +code_object+. See + # RDoc::CodeObject#metadata for details. + + def handle(text, code_object = nil, &block) + if ::RDoc::Comment === text + comment = text + text = text.text + end - blankline - when 'notnew', 'not_new', 'not-new' - return blankline unless RDoc::AnyMethod === code_object + # regexp helper (square brackets for optional) + # $1 $2 $3 $4 $5 + # [prefix][\]:directive:[spaces][param]newline + text = text.gsub(/^([ \t]*(?:#|\/?\*)?[ \t]*)(\\?):([\w-]+):([ \t]*)(.+)?(\r?\n|$)/) do + # skip something like ':toto::' + next $& if $4.empty? and $5 and $5[0, 1] == ':' + + # skip if escaped + next "#$1:#$3:#$4#$5\n" unless $2.empty? + + # This is not in handle_directive because I didn't want to pass another + # argument into it + if comment and $3 == 'markup' + next "#{$1.strip}\n" unless $5 + comment.format = $5.downcase + next "#{$1.strip}\n" + end + handle_directive $1, $3, $5, code_object, text.encoding, &block + end - code_object.dont_rename_initialize = true + if comment + comment.text = text + else + comment = text + end - blankline - when 'startdoc' - return blankline unless code_object + run_post_processes(comment, code_object) - code_object.start_doc - code_object.force_documentation = true + text + end - blankline - when 'stopdoc' - return blankline unless code_object + # Apply directives to a code object - code_object.stop_doc + def run_pre_processes(comment_text, code_object, start_line_no, type) + comment_text, directives = parse_comment(comment_text, start_line_no, type) + directives.each do |directive, (param, line_no)| + handle_directive('', directive, param, code_object) + end + if code_object.is_a?(::RDoc::AnyMethod) && (call_seq, = directives['call-seq']) && call_seq + code_object.call_seq = call_seq.lines.map(&:chomp).reject(&:empty?).join("\n") + end + format, = directives['markup'] + [comment_text, format] + end - blankline - when 'yield', 'yields' - return blankline unless code_object - # remove parameter &block - code_object.params = code_object.params.sub(/,?\s*&\w+/, '') if code_object.params + # Perform post preocesses to a code object - code_object.block_params = param || '' + def run_post_processes(comment, code_object) + self.class.post_processors.each do |handler| + handler.call comment, code_object + end + end - blankline - else - result = yield directive, param if block_given? + # Parse comment and return [normalized_comment_text, directives_hash] - case result - when nil - code_object.metadata[directive] = param if code_object + def parse_comment(text, line_no, type) + ::RDoc::Comment.parse(text, @input_file_name, line_no, type) do |filename, prefix_indent| + include_file(filename, prefix_indent, text.encoding) + end + end - if RDoc::Markup::PreProcess.registered.include? directive - handler = RDoc::Markup::PreProcess.registered[directive] - result = handler.call directive, param if handler + ## + # Performs the actions described by +directive+ and its parameter +param+. + # + # +code_object+ is used for directives that operate on a class or module. + # +prefix+ is used to ensure the replacement for handled directives is + # correct. +encoding+ is used for the include directive. + # + # For a list of directives in RDoc see RDoc::Markup. + #-- + # When 1.8.7 support is ditched prefix can be defaulted to '' + + def handle_directive(prefix, directive, param, code_object = nil, + encoding = nil) + blankline = "#{prefix.strip}\n" + directive = directive.downcase + + case directive + when 'arg', 'args' + return "#{prefix}:#{directive}: #{param}\n" unless code_object && code_object.kind_of?(::RDoc::AnyMethod) + + code_object.params = param + + blankline + when 'category' + if ::RDoc::Context === code_object + section = code_object.add_section param + code_object.temporary_section = section + elsif ::RDoc::AnyMethod === code_object + code_object.section_title = param + end + + blankline # ignore category if we're not on an RDoc::Context + when 'doc' + return blankline unless code_object + code_object.document_self = true + code_object.force_documentation = true + + blankline + when 'enddoc' + return blankline unless code_object + code_object.done_documenting = true + + blankline + when 'include' + filename = param.split(' ', 2).first + include_file filename, prefix, encoding + when 'nodoc' + return blankline unless code_object + code_object.document_self = nil # notify nodoc + code_object.document_children = param !~ /all/i + + blankline + when 'notnew', 'not_new', 'not-new' + return blankline unless ::RDoc::AnyMethod === code_object + + code_object.dont_rename_initialize = true + + blankline + when 'startdoc' + return blankline unless code_object + + code_object.start_doc + code_object.force_documentation = true + + blankline + when 'stopdoc' + return blankline unless code_object + + code_object.stop_doc + + blankline + when 'yield', 'yields' + return blankline unless code_object + # remove parameter &block + code_object.params = code_object.params.sub(/,?\s*&\w+/, '') if code_object.params + + code_object.block_params = param || '' + + blankline else - result = "#{prefix}:#{directive}: #{param}\n" + result = yield directive, param if block_given? + + case result + when nil + code_object.metadata[directive] = param if code_object + + if ::RDoc::Markup::PreProcess.registered.include? directive + handler = ::RDoc::Markup::PreProcess.registered[directive] + result = handler.call directive, param if handler + else + result = "#{prefix}:#{directive}: #{param}\n" + end + when false + result = "#{prefix}:#{directive}: #{param}\n" + end + + result end - when false - result = "#{prefix}:#{directive}: #{param}\n" end - result - end - end + ## + # Handles the :include: _filename_ directive. + # + # If the first line of the included file starts with '#', and contains + # an encoding information in the form 'coding:' or 'coding=', it is + # removed. + # + # If all lines in the included file start with a '#', this leading '#' + # is removed before inclusion. The included content is indented like + # the :include: directive. + #-- + # so all content will be verbatim because of the likely space after '#'? + # TODO shift left the whole file content in that case + # TODO comment stop/start #-- and #++ in included file must be processed here + + def include_file(name, indent, encoding) + full_name = find_include_file name + + unless full_name + warn "Couldn't find file to include '#{name}' from #{@input_file_name}" + return '' + end - ## - # Handles the :include: _filename_ directive. - # - # If the first line of the included file starts with '#', and contains - # an encoding information in the form 'coding:' or 'coding=', it is - # removed. - # - # If all lines in the included file start with a '#', this leading '#' - # is removed before inclusion. The included content is indented like - # the :include: directive. - #-- - # so all content will be verbatim because of the likely space after '#'? - # TODO shift left the whole file content in that case - # TODO comment stop/start #-- and #++ in included file must be processed here - - def include_file(name, indent, encoding) - full_name = find_include_file name - - unless full_name - warn "Couldn't find file to include '#{name}' from #{@input_file_name}" - return '' - end + content = ::RDoc::Encoding.read_file full_name, encoding, true + content = ::RDoc::Encoding.remove_magic_comment content - content = RDoc::Encoding.read_file full_name, encoding, true - content = RDoc::Encoding.remove_magic_comment content + # strip magic comment + content = content.sub(/\A# .*coding[=:].*$/, '').lstrip - # strip magic comment - content = content.sub(/\A# .*coding[=:].*$/, '').lstrip + # strip leading '#'s, but only if all lines start with them + if content =~ /^[^#]/ + content.gsub(/^/, indent) + else + content.gsub(/^#?/, indent) + end + end - # strip leading '#'s, but only if all lines start with them - if content =~ /^[^#]/ - content.gsub(/^/, indent) - else - content.gsub(/^#?/, indent) - end - end + ## + # Look for the given file in the directory containing the current file, + # and then in each of the directories specified in the RDOC_INCLUDE path - ## - # Look for the given file in the directory containing the current file, - # and then in each of the directories specified in the RDOC_INCLUDE path + def find_include_file(name) + to_search = [File.dirname(@input_file_name)].concat @include_path + to_search.each do |dir| + full_name = File.join(dir, name) + stat = File.stat(full_name) rescue next + return full_name if stat.readable? + end + nil + end - def find_include_file(name) - to_search = [File.dirname(@input_file_name)].concat @include_path - to_search.each do |dir| - full_name = File.join(dir, name) - stat = File.stat(full_name) rescue next - return full_name if stat.readable? end - nil end - end diff --git a/lib/rdoc/markup/rule.rb b/lib/rdoc/markup/rule.rb index 5ff7475321..0df63c6a9d 100644 --- a/lib/rdoc/markup/rule.rb +++ b/lib/rdoc/markup/rule.rb @@ -1,20 +1,24 @@ # frozen_string_literal: true -## -# A horizontal rule with a weight +module RDoc + class Markup + ## + # A horizontal rule with a weight -class RDoc::Markup::Rule < Struct.new :weight + class Rule < Struct.new :weight - ## - # Calls #accept_rule on +visitor+ + ## + # Calls #accept_rule on +visitor+ - def accept(visitor) - visitor.accept_rule self - end + def accept(visitor) + visitor.accept_rule self + end + + def pretty_print(q) # :nodoc: + q.group 2, '[rule:', ']' do + q.pp weight + end + end - def pretty_print(q) # :nodoc: - q.group 2, '[rule:', ']' do - q.pp weight end end - end diff --git a/lib/rdoc/markup/to_ansi.rb b/lib/rdoc/markup/to_ansi.rb index 3f17bfd2d8..115781c6ea 100644 --- a/lib/rdoc/markup/to_ansi.rb +++ b/lib/rdoc/markup/to_ansi.rb @@ -1,144 +1,148 @@ # frozen_string_literal: true -## -# Outputs RDoc markup with vibrant ANSI color! +module RDoc + class Markup + ## + # Outputs RDoc markup with vibrant ANSI color! + + class ToAnsi < ::RDoc::Markup::ToRdoc + + ## + # Creates a new ToAnsi visitor that is ready to output vibrant ANSI color! + + def initialize + super + + @headings.clear + @headings[1] = ["\e[1;32m", "\e[m"] # bold + @headings[2] = ["\e[4;32m", "\e[m"] # underline + @headings[3] = ["\e[32m", "\e[m"] # just green + end + + ## + # Maps attributes to ANSI sequences + + ANSI_STYLE_CODES_ON = { + BOLD: 1, + TT: 7, + EM: 4, + STRIKE: 9 + } + + ANSI_STYLE_CODES_OFF = { + BOLD: 22, + TT: 27, + EM: 24, + STRIKE: 29 + } + + # Apply the given attributes by emitting ANSI sequences. + # Emitting attribute changes are deferred until new text is added and applied in batch. + # This method computes the necessary ANSI codes to transition from the + # current set of applied attributes to the new set of +attributes+. + + def apply_attributes(attributes) + before = @applied_attributes + after = attributes.sort + return if before == after + + if after.empty? + emit_inline("\e[m") + elsif !before.empty? && before.size > (before & after).size + 1 + codes = after.map {|attr| ANSI_STYLE_CODES_ON[attr] }.compact + emit_inline("\e[#{[0, *codes].join(';')}m") + else + off_codes = (before - after).map {|attr| ANSI_STYLE_CODES_OFF[attr] }.compact + on_codes = (after - before).map {|attr| ANSI_STYLE_CODES_ON[attr] }.compact + emit_inline("\e[#{(off_codes + on_codes).join(';')}m") + end + @applied_attributes = attributes + end + + def add_text(text) + attrs = @attributes.keys + if @applied_attributes != attrs + apply_attributes(attrs) + end + emit_inline(text) + end + + def handle_inline(text) + @applied_attributes = [] + res = super + res << "\e[m" unless @applied_attributes.empty? + @applied_attributes = [] + res + end + + ## + # Overrides indent width to ensure output lines up correctly. + + def accept_list_item_end(list_item) + width = case @list_type.last + when :BULLET + 2 + when :NOTE, :LABEL + if @prefix + @res << @prefix.strip + @prefix = nil + end + + @res << "\n" unless res.length == 1 + 2 + else + bullet = @list_index.last.to_s + @list_index[-1] = @list_index.last.succ + bullet.length + 2 + end + + @indent -= width + end + + ## + # Adds coloring to note and label list items + + def accept_list_item_start(list_item) + bullet = case @list_type.last + when :BULLET + '*' + when :NOTE, :LABEL + labels = Array(list_item.label).map do |label| + attributes(label).strip + end.join "\n" + + labels << ":\n" unless labels.empty? + + labels + else + @list_index.last.to_s + '.' + end + + case @list_type.last + when :NOTE, :LABEL + @indent += 2 + @prefix = bullet + (' ' * @indent) + else + @prefix = (' ' * @indent) + bullet.ljust(bullet.length + 1) + + width = bullet.gsub(/\e\[[\d;]*m/, '').length + 1 + + @indent += width + end + end + + def calculate_text_width(text) + text.gsub(/\e\[[\d;]*m/, '').size + end + + ## + # Starts accepting with a reset screen + + def start_accepting + super + + @res = ["\e[0m"] + end -class RDoc::Markup::ToAnsi < RDoc::Markup::ToRdoc - - ## - # Creates a new ToAnsi visitor that is ready to output vibrant ANSI color! - - def initialize - super - - @headings.clear - @headings[1] = ["\e[1;32m", "\e[m"] # bold - @headings[2] = ["\e[4;32m", "\e[m"] # underline - @headings[3] = ["\e[32m", "\e[m"] # just green - end - - ## - # Maps attributes to ANSI sequences - - ANSI_STYLE_CODES_ON = { - BOLD: 1, - TT: 7, - EM: 4, - STRIKE: 9 - } - - ANSI_STYLE_CODES_OFF = { - BOLD: 22, - TT: 27, - EM: 24, - STRIKE: 29 - } - - # Apply the given attributes by emitting ANSI sequences. - # Emitting attribute changes are deferred until new text is added and applied in batch. - # This method computes the necessary ANSI codes to transition from the - # current set of applied attributes to the new set of +attributes+. - - def apply_attributes(attributes) - before = @applied_attributes - after = attributes.sort - return if before == after - - if after.empty? - emit_inline("\e[m") - elsif !before.empty? && before.size > (before & after).size + 1 - codes = after.map {|attr| ANSI_STYLE_CODES_ON[attr] }.compact - emit_inline("\e[#{[0, *codes].join(';')}m") - else - off_codes = (before - after).map {|attr| ANSI_STYLE_CODES_OFF[attr] }.compact - on_codes = (after - before).map {|attr| ANSI_STYLE_CODES_ON[attr] }.compact - emit_inline("\e[#{(off_codes + on_codes).join(';')}m") - end - @applied_attributes = attributes - end - - def add_text(text) - attrs = @attributes.keys - if @applied_attributes != attrs - apply_attributes(attrs) - end - emit_inline(text) - end - - def handle_inline(text) - @applied_attributes = [] - res = super - res << "\e[m" unless @applied_attributes.empty? - @applied_attributes = [] - res - end - - ## - # Overrides indent width to ensure output lines up correctly. - - def accept_list_item_end(list_item) - width = case @list_type.last - when :BULLET - 2 - when :NOTE, :LABEL - if @prefix - @res << @prefix.strip - @prefix = nil - end - - @res << "\n" unless res.length == 1 - 2 - else - bullet = @list_index.last.to_s - @list_index[-1] = @list_index.last.succ - bullet.length + 2 - end - - @indent -= width - end - - ## - # Adds coloring to note and label list items - - def accept_list_item_start(list_item) - bullet = case @list_type.last - when :BULLET - '*' - when :NOTE, :LABEL - labels = Array(list_item.label).map do |label| - attributes(label).strip - end.join "\n" - - labels << ":\n" unless labels.empty? - - labels - else - @list_index.last.to_s + '.' - end - - case @list_type.last - when :NOTE, :LABEL - @indent += 2 - @prefix = bullet + (' ' * @indent) - else - @prefix = (' ' * @indent) + bullet.ljust(bullet.length + 1) - - width = bullet.gsub(/\e\[[\d;]*m/, '').length + 1 - - @indent += width end end - - def calculate_text_width(text) - text.gsub(/\e\[[\d;]*m/, '').size - end - - ## - # Starts accepting with a reset screen - - def start_accepting - super - - @res = ["\e[0m"] - end - end diff --git a/lib/rdoc/markup/to_bs.rb b/lib/rdoc/markup/to_bs.rb index d6b5cd6c86..f23960438b 100644 --- a/lib/rdoc/markup/to_bs.rb +++ b/lib/rdoc/markup/to_bs.rb @@ -1,86 +1,90 @@ # frozen_string_literal: true -## -# Outputs RDoc markup with hot backspace action! You will probably need a -# pager to use this output format. -# -# This formatter won't work on 1.8.6 because it lacks String#chars. +module RDoc + class Markup + ## + # Outputs RDoc markup with hot backspace action! You will probably need a + # pager to use this output format. + # + # This formatter won't work on 1.8.6 because it lacks String#chars. -class RDoc::Markup::ToBs < RDoc::Markup::ToRdoc + class ToBs < ::RDoc::Markup::ToRdoc - ## - # Returns a new ToBs that is ready for hot backspace action! + ## + # Returns a new ToBs that is ready for hot backspace action! - def initialize - super + def initialize + super - @in_b = false - @in_em = false - end - - def handle_inline(text) - initial_style = [] - initial_style << :BOLD if @in_b - initial_style << :EM if @in_em - super(text, initial_style) - end + @in_b = false + @in_em = false + end - def add_text(text) - attrs = @attributes.keys - if attrs.include? :BOLD - styled = +'' - text.chars.each do |c| - styled << "#{c}\b#{c}" + def handle_inline(text) + initial_style = [] + initial_style << :BOLD if @in_b + initial_style << :EM if @in_em + super(text, initial_style) end - text = styled - elsif attrs.include? :EM - styled = +'' - text.chars.each do |c| - styled << "_\b#{c}" + + def add_text(text) + attrs = @attributes.keys + if attrs.include? :BOLD + styled = +'' + text.chars.each do |c| + styled << "#{c}\b#{c}" + end + text = styled + elsif attrs.include? :EM + styled = +'' + text.chars.each do |c| + styled << "_\b#{c}" + end + text = styled + end + emit_inline(text) end - text = styled - end - emit_inline(text) - end - ## - # Makes heading text bold. + ## + # Makes heading text bold. - def accept_heading(heading) - use_prefix or @res << ' ' * @indent - @res << @headings[heading.level][0] - @in_b = true - @res << attributes(heading.text) - @in_b = false - @res << @headings[heading.level][1] - @res << "\n" - end + def accept_heading(heading) + use_prefix or @res << ' ' * @indent + @res << @headings[heading.level][0] + @in_b = true + @res << attributes(heading.text) + @in_b = false + @res << @headings[heading.level][1] + @res << "\n" + end - ## - # Prepares the visitor for consuming +list_item+ + ## + # Prepares the visitor for consuming +list_item+ - def accept_list_item_start(list_item) - type = @list_type.last + def accept_list_item_start(list_item) + type = @list_type.last - case type - when :NOTE, :LABEL - bullets = Array(list_item.label).map do |label| - attributes(label).strip - end.join "\n" + case type + when :NOTE, :LABEL + bullets = Array(list_item.label).map do |label| + attributes(label).strip + end.join "\n" - bullets << ":\n" unless bullets.empty? + bullets << ":\n" unless bullets.empty? - @prefix = ' ' * @indent - @indent += 2 - @prefix << bullets + (' ' * @indent) - else - bullet = type == :BULLET ? '*' : @list_index.last.to_s + '.' - @prefix = (' ' * @indent) + bullet.ljust(bullet.length + 1) - width = bullet.length + 1 - @indent += width - end - end + @prefix = ' ' * @indent + @indent += 2 + @prefix << bullets + (' ' * @indent) + else + bullet = type == :BULLET ? '*' : @list_index.last.to_s + '.' + @prefix = (' ' * @indent) + bullet.ljust(bullet.length + 1) + width = bullet.length + 1 + @indent += width + end + end - def calculate_text_width(text) - text.gsub(/_\x08/, '').gsub(/\x08./, '').size + def calculate_text_width(text) + text.gsub(/_\x08/, '').gsub(/\x08./, '').size + end + end end end diff --git a/lib/rdoc/markup/to_html.rb b/lib/rdoc/markup/to_html.rb index 95814f2023..09daf525d4 100644 --- a/lib/rdoc/markup/to_html.rb +++ b/lib/rdoc/markup/to_html.rb @@ -3,680 +3,684 @@ require 'cgi/util' unless defined?(CGI::EscapeExt) require 'prism' -## -# Outputs RDoc markup as HTML. - -class RDoc::Markup::ToHtml < RDoc::Markup::Formatter - - include RDoc::Text - - # :section: Utilities - - ## - # Maps RDoc::Markup::Parser::LIST_TOKENS types to HTML tags - - LIST_TYPE_TO_HTML = { - :BULLET => ['
    ', '
'], - :LABEL => ['
', '
'], - :LALPHA => ['
    ', '
'], - :NOTE => ['
', '
'], - :NUMBER => ['
    ', '
'], - :UALPHA => ['
    ', '
'], - } - - attr_reader :res # :nodoc: - attr_reader :in_list_entry # :nodoc: - attr_reader :list # :nodoc: - - ## - # The RDoc::CodeObject HTML is being generated for. This is used to - # generate namespaced URI fragments - - attr_accessor :code_object - - ## - # Path to this document for relative links - - attr_accessor :from_path - - # :section: - - # Maps an encoding to a Hash of characters properly transcoded for that - # encoding. - # - # See also encode_fallback. - - TO_HTML_CHARACTERS = Hash.new do |h, encoding| - h[encoding] = { - close_dquote: encode_fallback('”', encoding, '"'), - close_squote: encode_fallback('’', encoding, '\''), - copyright: encode_fallback('©', encoding, '(c)'), - ellipsis: encode_fallback('…', encoding, '...'), - dot_ellipsis: encode_fallback('.…', encoding, '....'), - em_dash: encode_fallback('—', encoding, '---'), - en_dash: encode_fallback('–', encoding, '--'), - open_dquote: encode_fallback('“', encoding, '"'), - open_squote: encode_fallback('‘', encoding, '\''), - trademark: encode_fallback('®', encoding, '(r)'), - } - end - - HTML_CHARACTER_ALIASES = { - '(c)' => :copyright, - '(C)' => :copyright, - '(r)' => :trademark, - '(R)' => :trademark, - '---' => :em_dash, - '--' => :en_dash, - '....' => :dot_ellipsis, - '...' => :ellipsis, - '``' => :open_dquote, - "''" => :close_dquote, - } - - # Transcodes +character+ to +encoding+ with a +fallback+ character. - - def self.encode_fallback(character, encoding, fallback) - character.encode( - encoding, - fallback: { character => fallback }, - undef: :replace, - replace: fallback - ) - end +module RDoc + class Markup + ## + # Outputs RDoc markup as HTML. + + class ToHtml < ::RDoc::Markup::Formatter + + include ::RDoc::Text + + # :section: Utilities + + ## + # Maps RDoc::Markup::Parser::LIST_TOKENS types to HTML tags + + LIST_TYPE_TO_HTML = { + :BULLET => ['
    ', '
'], + :LABEL => ['
', '
'], + :LALPHA => ['
    ', '
'], + :NOTE => ['
', '
'], + :NUMBER => ['
    ', '
'], + :UALPHA => ['
    ', '
'], + } + + attr_reader :res # :nodoc: + attr_reader :in_list_entry # :nodoc: + attr_reader :list # :nodoc: + + ## + # The RDoc::CodeObject HTML is being generated for. This is used to + # generate namespaced URI fragments + + attr_accessor :code_object + + ## + # Path to this document for relative links + + attr_accessor :from_path + + # :section: + + # Maps an encoding to a Hash of characters properly transcoded for that + # encoding. + # + # See also encode_fallback. + + TO_HTML_CHARACTERS = Hash.new do |h, encoding| + h[encoding] = { + close_dquote: encode_fallback('”', encoding, '"'), + close_squote: encode_fallback('’', encoding, '\''), + copyright: encode_fallback('©', encoding, '(c)'), + ellipsis: encode_fallback('…', encoding, '...'), + dot_ellipsis: encode_fallback('.…', encoding, '....'), + em_dash: encode_fallback('—', encoding, '---'), + en_dash: encode_fallback('–', encoding, '--'), + open_dquote: encode_fallback('“', encoding, '"'), + open_squote: encode_fallback('‘', encoding, '\''), + trademark: encode_fallback('®', encoding, '(r)'), + } + end - # Converts ascii quote pairs to multibyte quote characters - class QuoteConverter + HTML_CHARACTER_ALIASES = { + '(c)' => :copyright, + '(C)' => :copyright, + '(r)' => :trademark, + '(R)' => :trademark, + '---' => :em_dash, + '--' => :en_dash, + '....' => :dot_ellipsis, + '...' => :ellipsis, + '``' => :open_dquote, + "''" => :close_dquote, + } + + # Transcodes +character+ to +encoding+ with a +fallback+ character. + + def self.encode_fallback(character, encoding, fallback) + character.encode( + encoding, + fallback: { character => fallback }, + undef: :replace, + replace: fallback + ) + end - def initialize - @in_dquote = false - @in_squote = false - end + # Converts ascii quote pairs to multibyte quote characters + class QuoteConverter - def convert(quote, after_word:) - case quote - when '"' - type = @in_dquote ? :close_dquote : :open_dquote - @in_dquote = !@in_dquote - when "'" - if @in_squote - type = :close_squote + def initialize + @in_dquote = false @in_squote = false - elsif after_word - # Mary's dog, my parents' house: do not start paired quotes - type = :close_squote - else - type = :open_squote - @in_squote = true end - when '`' - # Opening quote of `quoted sentence'. - # This will conflict with code blocks `puts('hello')` in the future. - if !@in_squote && !after_word - type = :open_squote - @in_squote = true + + def convert(quote, after_word:) + case quote + when '"' + type = @in_dquote ? :close_dquote : :open_dquote + @in_dquote = !@in_dquote + when "'" + if @in_squote + type = :close_squote + @in_squote = false + elsif after_word + # Mary's dog, my parents' house: do not start paired quotes + type = :close_squote + else + type = :open_squote + @in_squote = true + end + when '`' + # Opening quote of `quoted sentence'. + # This will conflict with code blocks `puts('hello')` in the future. + if !@in_squote && !after_word + type = :open_squote + @in_squote = true + end + end + TO_HTML_CHARACTERS[quote.encoding][type] if type end end - TO_HTML_CHARACTERS[quote.encoding][type] if type - end - end - ## - # Creates a new formatter that will output HTML + ## + # Creates a new formatter that will output HTML - def initialize(pipe: false, output_decoration: true) - super() + def initialize(pipe: false, output_decoration: true) + super() - @pipe = pipe - @output_decoration = output_decoration - @code_object = nil - @from_path = '' - @in_list_entry = nil - @list = nil - @th = nil - @quote_converter = nil - @in_tidylink_label = false - @hard_break = "
\n" + @pipe = pipe + @output_decoration = output_decoration + @code_object = nil + @from_path = '' + @in_list_entry = nil + @list = nil + @th = nil + @quote_converter = nil + @in_tidylink_label = false + @hard_break = "
\n" - init_regexp_handlings - end + init_regexp_handlings + end - # :section: Regexp Handling - # - # These methods are used by regexp handling markup added by RDoc::Markup#add_regexp_handling. + # :section: Regexp Handling + # + # These methods are used by regexp handling markup added by RDoc::Markup#add_regexp_handling. - # :nodoc: - URL_CHARACTERS_REGEXP_STR = /[A-Za-z0-9\-._~:\/\?#\[\]@!$&'\(\)*+,;%=]/.source + # :nodoc: + URL_CHARACTERS_REGEXP_STR = /[A-Za-z0-9\-._~:\/\?#\[\]@!$&'\(\)*+,;%=]/.source - ## - # Adds regexp handlings. + ## + # Adds regexp handlings. - def init_regexp_handlings - # external links - @markup.add_regexp_handling(/(?:link:|https?:|mailto:|ftp:|irc:|www\.)#{URL_CHARACTERS_REGEXP_STR}+\w/, - :HYPERLINK) + def init_regexp_handlings + # external links + @markup.add_regexp_handling(/(?:link:|https?:|mailto:|ftp:|irc:|www\.)#{URL_CHARACTERS_REGEXP_STR}+\w/, + :HYPERLINK) - # suppress crossref: \#method \::method \ClassName \method_with_underscores - @markup.add_regexp_handling(/\\(?:[#:A-Z]|[a-z]+_[a-z0-9])/, :SUPPRESSED_CROSSREF) + # suppress crossref: \#method \::method \ClassName \method_with_underscores + @markup.add_regexp_handling(/\\(?:[#:A-Z]|[a-z]+_[a-z0-9])/, :SUPPRESSED_CROSSREF) - @markup.add_regexp_handling(Regexp.union(HTML_CHARACTER_ALIASES.keys), :HTML_CHARACTERS) + @markup.add_regexp_handling(Regexp.union(HTML_CHARACTER_ALIASES.keys), :HTML_CHARACTERS) - @markup.add_regexp_handling(/\b['"`]/, :QUOTE_AFTER_WORD) - @markup.add_regexp_handling(/\B['"`]/, :QUOTE_NOT_AFTER_WORD) + @markup.add_regexp_handling(/\b['"`]/, :QUOTE_AFTER_WORD) + @markup.add_regexp_handling(/\B['"`]/, :QUOTE_NOT_AFTER_WORD) - init_link_notation_regexp_handlings - end + init_link_notation_regexp_handlings + end - ## - # Adds regexp handlings about link notations. + ## + # Adds regexp handlings about link notations. - def init_link_notation_regexp_handlings - add_regexp_handling_RDOCLINK - end + def init_link_notation_regexp_handlings + add_regexp_handling_RDOCLINK + end - def handle_RDOCLINK(url) # :nodoc: - case url - when /^rdoc-ref:/ - CGI.escapeHTML($') - when /^rdoc-label:/ - return CGI.escapeHTML(url) if in_tidylink_label? - text = $' - - text = case text - when /\Alabel-/ then $' - when /\Afootmark-/ then $' - when /\Afoottext-/ then $' - else text - end - - gen_url CGI.escapeHTML(url), CGI.escapeHTML(text) - when /^rdoc-image:/ - # Split the string after "rdoc-image:" into url and alt. - # "path/to/image.jpg:alt text" => ["path/to/image.jpg", "alt text"] - # "http://example.com/path/to/image.jpg:alt text" => ["http://example.com/path/to/image.jpg", "alt text"] - url, alt = $'.split(/:(?!\/)/, 2) - if alt && !alt.empty? - %[#{CGI.escapeHTML(alt)}] - else - %[] - end - when /\Ardoc-[a-z]+:/ - CGI.escapeHTML($') - end - end + def handle_RDOCLINK(url) # :nodoc: + case url + when /^rdoc-ref:/ + CGI.escapeHTML($') + when /^rdoc-label:/ + return CGI.escapeHTML(url) if in_tidylink_label? + text = $' + + text = case text + when /\Alabel-/ then $' + when /\Afootmark-/ then $' + when /\Afoottext-/ then $' + else text + end + + gen_url CGI.escapeHTML(url), CGI.escapeHTML(text) + when /^rdoc-image:/ + # Split the string after "rdoc-image:" into url and alt. + # "path/to/image.jpg:alt text" => ["path/to/image.jpg", "alt text"] + # "http://example.com/path/to/image.jpg:alt text" => ["http://example.com/path/to/image.jpg", "alt text"] + url, alt = $'.split(/:(?!\/)/, 2) + if alt && !alt.empty? + %[#{CGI.escapeHTML(alt)}] + else + %[] + end + when /\Ardoc-[a-z]+:/ + CGI.escapeHTML($') + end + end - def handle_PLAIN_TEXT(text) - emit_inline(convert_string(text)) - end + def handle_PLAIN_TEXT(text) + emit_inline(convert_string(text)) + end - def handle_REGEXP_HANDLING_TEXT(text) - emit_inline(text) - end + def handle_REGEXP_HANDLING_TEXT(text) + emit_inline(text) + end - def handle_BOLD(nodes) - emit_inline('') - super - emit_inline('') - end + def handle_BOLD(nodes) + emit_inline('') + super + emit_inline('') + end - def handle_EM(nodes) - emit_inline('') - super - emit_inline('') - end + def handle_EM(nodes) + emit_inline('') + super + emit_inline('') + end - def handle_BOLD_WORD(word) - emit_inline('') - super - emit_inline('') - end + def handle_BOLD_WORD(word) + emit_inline('') + super + emit_inline('') + end - def handle_EM_WORD(word) - emit_inline('') - super - emit_inline('') - end + def handle_EM_WORD(word) + emit_inline('') + super + emit_inline('') + end - def handle_TT(code) - emit_inline('') - super - emit_inline('') - end + def handle_TT(code) + emit_inline('') + super + emit_inline('') + end - def handle_STRIKE(nodes) - emit_inline('') - super - emit_inline('') - end + def handle_STRIKE(nodes) + emit_inline('') + super + emit_inline('') + end - def handle_HARD_BREAK - emit_inline('
') - end + def handle_HARD_BREAK + emit_inline('
') + end - def emit_inline(text) - @inline_output << text - end + def emit_inline(text) + @inline_output << text + end - # Returns true if we are processing inside a tidy link label. + # Returns true if we are processing inside a tidy link label. - def in_tidylink_label? - @in_tidylink_label - end + def in_tidylink_label? + @in_tidylink_label + end - # Special handling for tidy link labels. - # When a tidy link is {rdoc-image:path/to/image.jpg:alt text}[http://example.com], - # label part is normally considered RDOCLINK rdoc-image:path/to/image.jpg:alt and a text " text" - # but RDoc's test code expects the whole label part to be treated as RDOCLINK only in tidy link label. - # When a tidy link is {^1}[url] or {*1}[url], the label part needs to drop leading * or ^. - # TODO: reconsider this workaround. + # Special handling for tidy link labels. + # When a tidy link is {rdoc-image:path/to/image.jpg:alt text}[http://example.com], + # label part is normally considered RDOCLINK rdoc-image:path/to/image.jpg:alt and a text " text" + # but RDoc's test code expects the whole label part to be treated as RDOCLINK only in tidy link label. + # When a tidy link is {^1}[url] or {*1}[url], the label part needs to drop leading * or ^. + # TODO: reconsider this workaround. - def apply_tidylink_label_special_handling(label, url) - # ^1 *1 will be converted to just 1 in tidy link label. - return label[1..] if label.match?(/\A[*^]\d+\z/) + def apply_tidylink_label_special_handling(label, url) + # ^1 *1 will be converted to just 1 in tidy link label. + return label[1..] if label.match?(/\A[*^]\d+\z/) - # rdoc-image in label specially allows spaces in alt text. - return handle_RDOCLINK(label) if label.start_with?('rdoc-image:') - end + # rdoc-image in label specially allows spaces in alt text. + return handle_RDOCLINK(label) if label.start_with?('rdoc-image:') + end - def handle_TIDYLINK(label_part, url) - # When url is an image, ignore label part (maybe bug?) and just generate img tag. - if url.match?(/\Ahttps?:\/\/.+\.(png|gif|jpg|jpeg|bmp)\z/) - emit_inline("") - return - elsif url.match?(/\Ardoc-image:/) - emit_inline(handle_RDOCLINK(url)) - return - end + def handle_TIDYLINK(label_part, url) + # When url is an image, ignore label part (maybe bug?) and just generate img tag. + if url.match?(/\Ahttps?:\/\/.+\.(png|gif|jpg|jpeg|bmp)\z/) + emit_inline("") + return + elsif url.match?(/\Ardoc-image:/) + emit_inline(handle_RDOCLINK(url)) + return + end - if label_part.size == 1 && String === label_part[0] - raw_label = label_part[0] + if label_part.size == 1 && String === label_part[0] + raw_label = label_part[0] - @in_tidylink_label = true - special = apply_tidylink_label_special_handling(raw_label, url) - @in_tidylink_label = false + @in_tidylink_label = true + special = apply_tidylink_label_special_handling(raw_label, url) + @in_tidylink_label = false - if special - tag = gen_url(CGI.escapeHTML(url), special) - unless tag.empty? - emit_inline(tag) - return + if special + tag = gen_url(CGI.escapeHTML(url), special) + unless tag.empty? + emit_inline(tag) + return + end + end end + + tag = gen_url(CGI.escapeHTML(url), '') + open_tag, close_tag = tag.split(/(?=<\/a>)/, 2) + valid_tag = open_tag && close_tag + emit_inline(open_tag) if valid_tag + @in_tidylink_label = true + traverse_inline_nodes(label_part) + @in_tidylink_label = false + emit_inline(close_tag) if valid_tag end - end - tag = gen_url(CGI.escapeHTML(url), '') - open_tag, close_tag = tag.split(/(?=<\/a>)/, 2) - valid_tag = open_tag && close_tag - emit_inline(open_tag) if valid_tag - @in_tidylink_label = true - traverse_inline_nodes(label_part) - @in_tidylink_label = false - emit_inline(close_tag) if valid_tag - end + def handle_inline(text) # :nodoc: + @inline_output = +'' + @quote_converter = QuoteConverter.new + super + out = @inline_output + @inline_output = nil + @quote_converter = nil + out + end - def handle_inline(text) # :nodoc: - @inline_output = +'' - @quote_converter = QuoteConverter.new - super - out = @inline_output - @inline_output = nil - @quote_converter = nil - out - end + # Converts (c), (r), --, --- , ..., ...., ``, '' to HTML characters. + def handle_regexp_HTML_CHARACTERS(text) + name = HTML_CHARACTER_ALIASES[text] + TO_HTML_CHARACTERS[text.encoding][name] if name + end - # Converts (c), (r), --, --- , ..., ...., ``, '' to HTML characters. - def handle_regexp_HTML_CHARACTERS(text) - name = HTML_CHARACTER_ALIASES[text] - TO_HTML_CHARACTERS[text.encoding][name] if name - end + def handle_regexp_QUOTE_NOT_AFTER_WORD(text) + @quote_converter.convert(text, after_word: false) || convert_string(text) + end - def handle_regexp_QUOTE_NOT_AFTER_WORD(text) - @quote_converter.convert(text, after_word: false) || convert_string(text) - end + def handle_regexp_QUOTE_AFTER_WORD(text) + @quote_converter.convert(text, after_word: true) || convert_string(text) + end - def handle_regexp_QUOTE_AFTER_WORD(text) - @quote_converter.convert(text, after_word: true) || convert_string(text) - end + # Converts suppressed cross-reference +text+ to HTML by removing the leading backslash. - # Converts suppressed cross-reference +text+ to HTML by removing the leading backslash. + def handle_regexp_SUPPRESSED_CROSSREF(text) + convert_string(text.delete_prefix('\\')) + end - def handle_regexp_SUPPRESSED_CROSSREF(text) - convert_string(text.delete_prefix('\\')) - end + ## + # +target+ is a potential link. The following schemes are handled: + # + # mailto::: + # Inserted as-is. + # http::: + # Links are checked to see if they reference an image. If so, that image + # gets inserted using an tag. Otherwise a conventional + # is used. + # link::: + # Reference to a local file relative to the output directory. + + def handle_regexp_HYPERLINK(text) + return convert_string(text) if in_tidylink_label? + + url = CGI.escapeHTML(text) + gen_url url, url + end - ## - # +target+ is a potential link. The following schemes are handled: - # - # mailto::: - # Inserted as-is. - # http::: - # Links are checked to see if they reference an image. If so, that image - # gets inserted using an tag. Otherwise a conventional - # is used. - # link::: - # Reference to a local file relative to the output directory. - - def handle_regexp_HYPERLINK(text) - return convert_string(text) if in_tidylink_label? - - url = CGI.escapeHTML(text) - gen_url url, url - end + ## + # +target+ is an rdoc-schemed link that will be converted into a hyperlink. + # + # For the +rdoc-ref+ scheme the named reference will be returned without + # creating a link. + # + # For the +rdoc-label+ scheme the footnote and label prefixes are stripped + # when creating a link. All other contents will be linked verbatim. + + def handle_regexp_RDOCLINK(text) + handle_RDOCLINK text + end - ## - # +target+ is an rdoc-schemed link that will be converted into a hyperlink. - # - # For the +rdoc-ref+ scheme the named reference will be returned without - # creating a link. - # - # For the +rdoc-label+ scheme the footnote and label prefixes are stripped - # when creating a link. All other contents will be linked verbatim. - - def handle_regexp_RDOCLINK(text) - handle_RDOCLINK text - end + # :section: Visitor + # + # These methods implement the HTML visitor. - # :section: Visitor - # - # These methods implement the HTML visitor. + ## + # Prepares the visitor for HTML generation - ## - # Prepares the visitor for HTML generation + def start_accepting + @res = [] + @in_list_entry = [] + @list = [] + @heading_ids = {} + end - def start_accepting - @res = [] - @in_list_entry = [] - @list = [] - @heading_ids = {} - end + ## + # Returns the generated output - ## - # Returns the generated output + def end_accepting + @res.join + end - def end_accepting - @res.join - end + ## + # Adds +block_quote+ to the output - ## - # Adds +block_quote+ to the output + def accept_block_quote(block_quote) + @res << "\n
" - def accept_block_quote(block_quote) - @res << "\n
" + block_quote.parts.each do |part| + part.accept self + end - block_quote.parts.each do |part| - part.accept self - end + @res << "
\n" + end - @res << "
\n" - end + ## + # Adds +paragraph+ to the output + + def accept_paragraph(paragraph) + @res << "\n

" + text = paragraph.text @hard_break + text = text.gsub(/(#{SPACE_SEPARATED_LETTER_CLASS})?\K(?:\r?\n)+(?=(?(1)(#{SPACE_SEPARATED_LETTER_CLASS})?))/o) { + defined?($2) && ' ' + } + @res << to_html(text) + @res << "

\n" + end - ## - # Adds +paragraph+ to the output - - def accept_paragraph(paragraph) - @res << "\n

" - text = paragraph.text @hard_break - text = text.gsub(/(#{SPACE_SEPARATED_LETTER_CLASS})?\K(?:\r?\n)+(?=(?(1)(#{SPACE_SEPARATED_LETTER_CLASS})?))/o) { - defined?($2) && ' ' - } - @res << to_html(text) - @res << "

\n" - end + # Generate syntax highlighted html for ruby-like text. - # Generate syntax highlighted html for ruby-like text. + def parsable_text_to_html(text) + tokens = ::RDoc::Parser::RubyColorizer.colorize(text) + result = ::RDoc::TokenStream.to_html tokens + result = result + "\n" unless result.end_with?("\n") + result + end - def parsable_text_to_html(text) - tokens = RDoc::Parser::RubyColorizer.colorize(text) - result = RDoc::TokenStream.to_html tokens - result = result + "\n" unless result.end_with?("\n") - result - end + ## + # Adds +verbatim+ to the output - ## - # Adds +verbatim+ to the output - - def accept_verbatim(verbatim) - text = verbatim.text.rstrip - format = verbatim.format - - # Apply Ruby syntax highlighting if - # - explicitly marked as Ruby (via ruby? which accepts :ruby or :rb) - # - no format specified but the text is parseable as Ruby - # Otherwise, add language class when applicable and skip Ruby highlighting - if verbatim.ruby? || (format.nil? && parseable?(text)) - content = parsable_text_to_html(text) - klass = ' class="ruby"' - else - content = CGI.escapeHTML text - klass = " class=\"#{format}\"" if format - end + def accept_verbatim(verbatim) + text = verbatim.text.rstrip + format = verbatim.format - if @pipe - @res << "\n
#{CGI.escapeHTML text}\n
\n" - else - @res << "\n#{content}\n" - end - end + # Apply Ruby syntax highlighting if + # - explicitly marked as Ruby (via ruby? which accepts :ruby or :rb) + # - no format specified but the text is parseable as Ruby + # Otherwise, add language class when applicable and skip Ruby highlighting + if verbatim.ruby? || (format.nil? && parseable?(text)) + content = parsable_text_to_html(text) + klass = ' class="ruby"' + else + content = CGI.escapeHTML text + klass = " class=\"#{format}\"" if format + end - ## - # Adds +rule+ to the output + if @pipe + @res << "\n
#{CGI.escapeHTML text}\n
\n" + else + @res << "\n#{content}\n" + end + end - def accept_rule(rule) - @res << "
\n" - end + ## + # Adds +rule+ to the output - ## - # Prepares the visitor for consuming +list+ + def accept_rule(rule) + @res << "
\n" + end - def accept_list_start(list) - @list << list.type - @res << html_list_name(list.type, true) - @in_list_entry.push false - end + ## + # Prepares the visitor for consuming +list+ - ## - # Finishes consumption of +list+ + def accept_list_start(list) + @list << list.type + @res << html_list_name(list.type, true) + @in_list_entry.push false + end - def accept_list_end(list) - @list.pop - if tag = @in_list_entry.pop - @res << tag - end - @res << html_list_name(list.type, false) << "\n" - end + ## + # Finishes consumption of +list+ - ## - # Prepares the visitor for consuming +list_item+ + def accept_list_end(list) + @list.pop + if tag = @in_list_entry.pop + @res << tag + end + @res << html_list_name(list.type, false) << "\n" + end - def accept_list_item_start(list_item) - if tag = @in_list_entry.last - @res << tag - end + ## + # Prepares the visitor for consuming +list_item+ - @res << list_item_start(list_item, @list.last) - end + def accept_list_item_start(list_item) + if tag = @in_list_entry.last + @res << tag + end - ## - # Finishes consumption of +list_item+ + @res << list_item_start(list_item, @list.last) + end - def accept_list_item_end(list_item) - @in_list_entry[-1] = list_end_for(@list.last) - end + ## + # Finishes consumption of +list_item+ - ## - # Adds +blank_line+ to the output + def accept_list_item_end(list_item) + @in_list_entry[-1] = list_end_for(@list.last) + end - def accept_blank_line(blank_line) - # @res << annotate("

") << "\n" - end + ## + # Adds +blank_line+ to the output - ## - # Adds +heading+ to the output. The headings greater than 6 are trimmed to - # level 6. + def accept_blank_line(blank_line) + # @res << annotate("

") << "\n" + end - def accept_heading(heading) - level = [6, heading.level].min + ## + # Adds +heading+ to the output. The headings greater than 6 are trimmed to + # level 6. - label = deduplicate_heading_id(heading.label(@code_object)) - legacy_label = deduplicate_heading_id(heading.legacy_label(@code_object)) + def accept_heading(heading) + level = [6, heading.level].min - # Add legacy anchor before the heading for backward compatibility. - # This allows old links with label- prefix to still work. - if @output_decoration && !@pipe - @res << "\n" - end + label = deduplicate_heading_id(heading.label(@code_object)) + legacy_label = deduplicate_heading_id(heading.legacy_label(@code_object)) - @res << if @output_decoration - "\n" - else - "\n" - end + # Add legacy anchor before the heading for backward compatibility. + # This allows old links with label- prefix to still work. + if @output_decoration && !@pipe + @res << "\n" + end - if @pipe - @res << to_html(heading.text) - else - @res << "#{to_html(heading.text)}" - end + @res << if @output_decoration + "\n" + else + "\n" + end - @res << "\n" - end + if @pipe + @res << to_html(heading.text) + else + @res << "#{to_html(heading.text)}" + end + + @res << "\n" + end - ## - # Adds +raw+ to the output + ## + # Adds +raw+ to the output - def accept_raw(raw) - @res << raw.parts.join("\n") - end + def accept_raw(raw) + @res << raw.parts.join("\n") + end - ## - # Adds +table+ to the output + ## + # Adds +table+ to the output - def accept_table(header, body, aligns) - @res << "\n\n\n\n" - header.zip(aligns) do |text, align| - @res << '' << to_html(text) << "\n" - end - @res << "\n\n\n" - body.each do |row| - @res << "\n" - row.zip(aligns) do |text, align| - @res << '' << to_html(text) << "\n" - end - @res << "\n" - end - @res << "\n
\n" - end + def accept_table(header, body, aligns) + @res << "\n\n\n\n" + header.zip(aligns) do |text, align| + @res << '' << to_html(text) << "\n" + end + @res << "\n\n\n" + body.each do |row| + @res << "\n" + row.zip(aligns) do |text, align| + @res << '' << to_html(text) << "\n" + end + @res << "\n" + end + @res << "\n
\n" + end - # :section: Utilities + # :section: Utilities - ## - # Returns a unique heading ID, appending -1, -2, etc. for duplicates. - # Matches GitHub's behavior for duplicate heading anchors. + ## + # Returns a unique heading ID, appending -1, -2, etc. for duplicates. + # Matches GitHub's behavior for duplicate heading anchors. - def deduplicate_heading_id(id) - if @heading_ids.key?(id) - @heading_ids[id] += 1 - "#{id}-#{@heading_ids[id]}" - else - @heading_ids[id] = 0 - id - end - end + def deduplicate_heading_id(id) + if @heading_ids.key?(id) + @heading_ids[id] += 1 + "#{id}-#{@heading_ids[id]}" + else + @heading_ids[id] = 0 + id + end + end - ## - # CGI-escapes +text+ + ## + # CGI-escapes +text+ - def convert_string(text) - CGI.escapeHTML text - end + def convert_string(text) + CGI.escapeHTML text + end - ## - # Generates an HTML link or image tag for the given +url+ and +text+. - # - # - Image URLs (http/https/link ending in .gif, .png, .jpg, .jpeg, .bmp) - # become tags - # - File references (.rb, .rdoc, .md) are converted to .html paths - # - Anchor URLs (#foo) pass through unchanged for GitHub-style header linking - # - Footnote links get wrapped in tags + ## + # Generates an HTML link or image tag for the given +url+ and +text+. + # + # - Image URLs (http/https/link ending in .gif, .png, .jpg, .jpeg, .bmp) + # become tags + # - File references (.rb, .rdoc, .md) are converted to .html paths + # - Anchor URLs (#foo) pass through unchanged for GitHub-style header linking + # - Footnote links get wrapped in tags - def gen_url(url, text) - scheme, url, id = parse_url url + def gen_url(url, text) + scheme, url, id = parse_url url - if %w[http https link].include?(scheme) && url =~ /\.(gif|png|jpg|jpeg|bmp)\z/ - "" - else - if scheme != 'link' and %r%\A((?!https?:)(?:[^/#]*/)*+)([^/#]+)\.(rb|rdoc|md)(?=\z|#)%i =~ url - url = "#$1#{$2.tr('.', '_')}_#$3.html#$'" - end + if %w[http https link].include?(scheme) && url =~ /\.(gif|png|jpg|jpeg|bmp)\z/ + "" + else + if scheme != 'link' and %r%\A((?!https?:)(?:[^/#]*/)*+)([^/#]+)\.(rb|rdoc|md)(?=\z|#)%i =~ url + url = "#$1#{$2.tr('.', '_')}_#$3.html#$'" + end - text = text.sub %r%^#{scheme}:/*%i, '' - text = text.sub %r%^[*\^](\d+)$%, '\1' + text = text.sub %r%^#{scheme}:/*%i, '' + text = text.sub %r%^[*\^](\d+)$%, '\1' - link = "#{text}" + link = "#{text}" - if /"foot/.match?(id) - "#{link}" - else - link + if /"foot/.match?(id) + "#{link}" + else + link + end + end end - end - end - ## - # Determines the HTML list element for +list_type+ and +open_tag+ + ## + # Determines the HTML list element for +list_type+ and +open_tag+ - def html_list_name(list_type, open_tag) - tags = LIST_TYPE_TO_HTML[list_type] - raise RDoc::Error, "Invalid list type: #{list_type.inspect}" unless tags - tags[open_tag ? 0 : 1] - end + def html_list_name(list_type, open_tag) + tags = LIST_TYPE_TO_HTML[list_type] + raise ::RDoc::Error, "Invalid list type: #{list_type.inspect}" unless tags + tags[open_tag ? 0 : 1] + end - ## - # Returns the HTML tag for +list_type+, possible using a label from - # +list_item+ - - def list_item_start(list_item, list_type) - case list_type - when :BULLET, :LALPHA, :NUMBER, :UALPHA - "

  • " - when :LABEL, :NOTE - Array(list_item.label).map do |label| - "
    #{to_html label}
    \n" - end.join << "
    " - else - raise RDoc::Error, "Invalid list type: #{list_type.inspect}" - end - end + ## + # Returns the HTML tag for +list_type+, possible using a label from + # +list_item+ + + def list_item_start(list_item, list_type) + case list_type + when :BULLET, :LALPHA, :NUMBER, :UALPHA + "
  • " + when :LABEL, :NOTE + Array(list_item.label).map do |label| + "
    #{to_html label}
    \n" + end.join << "
    " + else + raise ::RDoc::Error, "Invalid list type: #{list_type.inspect}" + end + end - ## - # Returns the HTML end-tag for +list_type+ - - def list_end_for(list_type) - case list_type - when :BULLET, :LALPHA, :NUMBER, :UALPHA - "
  • " - when :LABEL, :NOTE - "
    " - else - raise RDoc::Error, "Invalid list type: #{list_type.inspect}" - end - end + ## + # Returns the HTML end-tag for +list_type+ - ## - # Returns true if text is valid ruby syntax + def list_end_for(list_type) + case list_type + when :BULLET, :LALPHA, :NUMBER, :UALPHA + "
  • " + when :LABEL, :NOTE + "" + else + raise ::RDoc::Error, "Invalid list type: #{list_type.inspect}" + end + end - def parseable?(text) - Prism.parse_success?(text) - end + ## + # Returns true if text is valid ruby syntax - ## - # Converts +item+ to HTML using RDoc::Text#to_html + def parseable?(text) + Prism.parse_success?(text) + end + + ## + # Converts +item+ to HTML using RDoc::Text#to_html - def to_html(item) - handle_inline(item) + def to_html(item) + handle_inline(item) + end + end end end diff --git a/lib/rdoc/markup/to_html_crossref.rb b/lib/rdoc/markup/to_html_crossref.rb index 93e073470f..4644286105 100644 --- a/lib/rdoc/markup/to_html_crossref.rb +++ b/lib/rdoc/markup/to_html_crossref.rb @@ -1,261 +1,265 @@ # frozen_string_literal: true -## -# Subclass of the RDoc::Markup::ToHtml class that supports looking up method -# names, classes, etc to create links. RDoc::CrossReference is used to -# generate those links based on the current context. +module RDoc + class Markup + ## + # Subclass of the RDoc::Markup::ToHtml class that supports looking up method + # names, classes, etc to create links. RDoc::CrossReference is used to + # generate those links based on the current context. -class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml + class ToHtmlCrossref < ::RDoc::Markup::ToHtml - # :stopdoc: - ALL_CROSSREF_REGEXP = RDoc::CrossReference::ALL_CROSSREF_REGEXP - CROSSREF_REGEXP = RDoc::CrossReference::CROSSREF_REGEXP - # :startdoc: + # :stopdoc: + ALL_CROSSREF_REGEXP = ::RDoc::CrossReference::ALL_CROSSREF_REGEXP + CROSSREF_REGEXP = ::RDoc::CrossReference::CROSSREF_REGEXP + # :startdoc: - ## - # RDoc::CodeObject for generating references + ## + # RDoc::CodeObject for generating references - attr_accessor :context + attr_accessor :context - ## - # Should we show '#' characters on method references? + ## + # Should we show '#' characters on method references? - attr_accessor :show_hash + attr_accessor :show_hash - ## - # Creates a new crossref resolver that generates links relative to +context+ - # which lives at +from_path+ in the generated files. '#' characters on - # references are removed unless +show_hash+ is true. Only method names - # preceded by '#' or '::' are linked, unless +hyperlink_all+ is true. + ## + # Creates a new crossref resolver that generates links relative to +context+ + # which lives at +from_path+ in the generated files. '#' characters on + # references are removed unless +show_hash+ is true. Only method names + # preceded by '#' or '::' are linked, unless +hyperlink_all+ is true. - def initialize(from_path, context, pipe: false, output_decoration: true, - hyperlink_all: false, show_hash: false, - autolink_excluded_words: [], warn_missing_rdoc_ref: true) - raise ArgumentError, 'from_path cannot be nil' if from_path.nil? + def initialize(from_path, context, pipe: false, output_decoration: true, + hyperlink_all: false, show_hash: false, + autolink_excluded_words: [], warn_missing_rdoc_ref: true) + raise ArgumentError, 'from_path cannot be nil' if from_path.nil? - super(pipe: pipe, output_decoration: output_decoration) + super(pipe: pipe, output_decoration: output_decoration) - @context = context - @from_path = from_path - @hyperlink_all = hyperlink_all - @show_hash = show_hash - @autolink_excluded_words = autolink_excluded_words - @warn_missing_rdoc_ref = warn_missing_rdoc_ref + @context = context + @from_path = from_path + @hyperlink_all = hyperlink_all + @show_hash = show_hash + @autolink_excluded_words = autolink_excluded_words + @warn_missing_rdoc_ref = warn_missing_rdoc_ref - @cross_reference = RDoc::CrossReference.new @context - end - - # :nodoc: - def init_link_notation_regexp_handlings - add_regexp_handling_RDOCLINK - - # The crossref must be linked before tidylink because Klass.method[:sym] - # will be processed as a tidylink first and will be broken. - crossref_re = @hyperlink_all ? ALL_CROSSREF_REGEXP : CROSSREF_REGEXP - @markup.add_regexp_handling crossref_re, :CROSSREF - end - - ## - # Creates a link to the reference +name+ if the name exists. If +text+ is - # given it is used as the link text, otherwise +name+ is used. - # Returns +nil+ if the link target could not be resolved. - - def cross_reference(name, text = nil, code = true, rdoc_ref: false) - # Strip '#' for link display text (e.g. #method shows as "method" in links) - display = !@show_hash && name.start_with?('#') ? name[1..] : name - - if !display.end_with?('+@', '-@') && match = display.match(/(.*[^#:])?@(.*)/) - context_name = match[1] - label = convert_string(RDoc::Text.decode_legacy_label(match[2])) - text ||= "#{label} at #{convert_string(context_name)}" if context_name - text ||= label - code = false - else - text ||= convert_string(display) - end - - link(name, text, code, rdoc_ref: rdoc_ref) - end - - ## - # We're invoked when any text matches the CROSSREF pattern. If we find the - # corresponding reference, generate a link. If the name we're looking for - # contains no punctuation, we look for it up the module/class chain. For - # example, ToHtml is found, even without the RDoc::Markup:: prefix, - # because we look for it in module Markup first. - - def handle_regexp_CROSSREF(name) - return convert_string(name) if in_tidylink_label? - return name if @autolink_excluded_words&.include?(name) - - return name if name =~ /@[\w-]+\.[\w-]/ # labels that look like emails - - unless @hyperlink_all - # This ensures that words entirely consisting of lowercase letters will - # not have cross-references generated (to suppress lots of erroneous - # cross-references to "new" in text, for instance) - return name if name =~ /\A[a-z]*\z/ - end - cross_reference(name, rdoc_ref: false) || convert_string(name) - end - - ## - # Handles rdoc-ref: scheme links and allows RDoc::Markup::ToHtml to - # handle other schemes. - - def handle_regexp_HYPERLINK(url) - return convert_string(url) if in_tidylink_label? - - case url - when /\Ardoc-ref:/ - ref = $' - cross_reference(ref, rdoc_ref: true) || convert_string(ref) - else - super - end - end - - ## - # +target+ is an rdoc-schemed link that will be converted into a hyperlink. - # For the rdoc-ref scheme the cross-reference will be looked up and the - # given name will be used. - # - # All other contents are handled by - # {the superclass}[rdoc-ref:RDoc::Markup::ToHtml#handle_regexp_RDOCLINK] - - def handle_regexp_RDOCLINK(url) - case url - when /\Ardoc-ref:/ - if in_tidylink_label? - convert_string(url) - else - ref = $' - cross_reference(ref, rdoc_ref: true) || convert_string(ref) + @cross_reference = ::RDoc::CrossReference.new @context end - else - super - end - end - ## - # Generates links for rdoc-ref: scheme URLs and allows - # RDoc::Markup::ToHtml to handle other schemes. + # :nodoc: + def init_link_notation_regexp_handlings + add_regexp_handling_RDOCLINK - def gen_url(url, text) - if url =~ /\Ardoc-ref:/ - name = $' - cross_reference(name, text, name == text, rdoc_ref: true) || text - else - super - end - end - - ## - # Creates an HTML link to +name+ with the given +html_string+. - # +html_string+ should be already escaped and may contain HTML tags. - # Returns the link HTML string, or +nil+ if the reference could not be resolved. + # The crossref must be linked before tidylink because Klass.method[:sym] + # will be processed as a tidylink first and will be broken. + crossref_re = @hyperlink_all ? ALL_CROSSREF_REGEXP : CROSSREF_REGEXP + @markup.add_regexp_handling crossref_re, :CROSSREF + end - def link(name, html_string, code = true, rdoc_ref: false) - if !(name.end_with?('+@', '-@')) and name =~ /(.*[^#:])?@/ - name = $1 - label = $' - end + ## + # Creates a link to the reference +name+ if the name exists. If +text+ is + # given it is used as the link text, otherwise +name+ is used. + # Returns +nil+ if the link target could not be resolved. + + def cross_reference(name, text = nil, code = true, rdoc_ref: false) + # Strip '#' for link display text (e.g. #method shows as "method" in links) + display = !@show_hash && name.start_with?('#') ? name[1..] : name + + if !display.end_with?('+@', '-@') && match = display.match(/(.*[^#:])?@(.*)/) + context_name = match[1] + label = convert_string(::RDoc::Text.decode_legacy_label(match[2])) + text ||= "#{label} at #{convert_string(context_name)}" if context_name + text ||= label + code = false + else + text ||= convert_string(display) + end + + link(name, text, code, rdoc_ref: rdoc_ref) + end - ref = @cross_reference.resolve name if name + ## + # We're invoked when any text matches the CROSSREF pattern. If we find the + # corresponding reference, generate a link. If the name we're looking for + # contains no punctuation, we look for it up the module/class chain. For + # example, ToHtml is found, even without the RDoc::Markup:: prefix, + # because we look for it in module Markup first. + + def handle_regexp_CROSSREF(name) + return convert_string(name) if in_tidylink_label? + return name if @autolink_excluded_words&.include?(name) + + return name if name =~ /@[\w-]+\.[\w-]/ # labels that look like emails + + unless @hyperlink_all + # This ensures that words entirely consisting of lowercase letters will + # not have cross-references generated (to suppress lots of erroneous + # cross-references to "new" in text, for instance) + return name if name =~ /\A[a-z]*\z/ + end + cross_reference(name, rdoc_ref: false) || convert_string(name) + end - # Non-text source files (C, Ruby, etc.) don't get HTML pages generated, - # so don't auto-link to them. Explicit rdoc-ref: links are still allowed. - if !rdoc_ref && RDoc::TopLevel === ref && !ref.text? - return - end + ## + # Handles rdoc-ref: scheme links and allows RDoc::Markup::ToHtml to + # handle other schemes. - if ref - path = ref.as_href(@from_path) + def handle_regexp_HYPERLINK(url) + return convert_string(url) if in_tidylink_label? - if code and RDoc::CodeObject === ref and !(RDoc::TopLevel === ref) - html_string = "#{html_string}" + case url + when /\Ardoc-ref:/ + ref = $' + cross_reference(ref, rdoc_ref: true) || convert_string(ref) + else + super + end end - elsif name - if rdoc_ref && @warn_missing_rdoc_ref - puts "#{@from_path}: `rdoc-ref:#{name}` can't be resolved for `#{html_string}`" + + ## + # +target+ is an rdoc-schemed link that will be converted into a hyperlink. + # For the rdoc-ref scheme the cross-reference will be looked up and the + # given name will be used. + # + # All other contents are handled by + # {the superclass}[rdoc-ref:RDoc::Markup::ToHtml#handle_regexp_RDOCLINK] + + def handle_regexp_RDOCLINK(url) + case url + when /\Ardoc-ref:/ + if in_tidylink_label? + convert_string(url) + else + ref = $' + cross_reference(ref, rdoc_ref: true) || convert_string(ref) + end + else + super + end end - return - else - # A bare label reference like @foo still produces a valid anchor link - return unless label - path = +"" - end - if label - # Decode legacy labels (e.g., "What-27s+Here" -> "What's Here") - # then convert to GitHub-style anchor format - decoded_label = RDoc::Text.decode_legacy_label(label) - formatted_label = RDoc::Text.to_anchor(decoded_label) - - # Case 1: Path already has an anchor (e.g., method link) - # Input: C1#method@label -> path="C1.html#method-i-m" - # Output: C1.html#method-i-m-label - if path =~ /#/ - path << "-#{formatted_label}" - - # Case 2: Label matches a section title - # Input: C1@Section -> path="C1.html", section "Section" exists - # Output: C1.html#section (uses section.aref for GitHub-style) - elsif (section = ref&.sections&.find { |s| decoded_label == s.title }) - path << "##{section.aref}" - - # Case 3: Ref has an aref (class/module context) - # Input: C1@heading -> path="C1.html", ref=C1 class - # Output: C1.html#class-c1-heading - elsif ref.respond_to?(:aref) - path << "##{ref.aref}-#{formatted_label}" - - # Case 4: No context, just the label (e.g., TopLevel/file) - # Input: README@section -> path="README_md.html" - # Output: README_md.html#section - else - path << "##{formatted_label}" + ## + # Generates links for rdoc-ref: scheme URLs and allows + # RDoc::Markup::ToHtml to handle other schemes. + + def gen_url(url, text) + if url =~ /\Ardoc-ref:/ + name = $' + cross_reference(name, text, name == text, rdoc_ref: true) || text + else + super + end end - end - "#{html_string}" - end + ## + # Creates an HTML link to +name+ with the given +html_string+. + # +html_string+ should be already escaped and may contain HTML tags. + # Returns the link HTML string, or +nil+ if the reference could not be resolved. + + def link(name, html_string, code = true, rdoc_ref: false) + if !(name.end_with?('+@', '-@')) and name =~ /(.*[^#:])?@/ + name = $1 + label = $' + end + + ref = @cross_reference.resolve name if name + + # Non-text source files (C, Ruby, etc.) don't get HTML pages generated, + # so don't auto-link to them. Explicit rdoc-ref: links are still allowed. + if !rdoc_ref && ::RDoc::TopLevel === ref && !ref.text? + return + end + + if ref + path = ref.as_href(@from_path) + + if code and ::RDoc::CodeObject === ref and !(::RDoc::TopLevel === ref) + html_string = "#{html_string}" + end + elsif name + if rdoc_ref && @warn_missing_rdoc_ref + puts "#{@from_path}: `rdoc-ref:#{name}` can't be resolved for `#{html_string}`" + end + return + else + # A bare label reference like @foo still produces a valid anchor link + return unless label + path = +"" + end + + if label + # Decode legacy labels (e.g., "What-27s+Here" -> "What's Here") + # then convert to GitHub-style anchor format + decoded_label = ::RDoc::Text.decode_legacy_label(label) + formatted_label = ::RDoc::Text.to_anchor(decoded_label) + + # Case 1: Path already has an anchor (e.g., method link) + # Input: C1#method@label -> path="C1.html#method-i-m" + # Output: C1.html#method-i-m-label + if path =~ /#/ + path << "-#{formatted_label}" + + # Case 2: Label matches a section title + # Input: C1@Section -> path="C1.html", section "Section" exists + # Output: C1.html#section (uses section.aref for GitHub-style) + elsif (section = ref&.sections&.find { |s| decoded_label == s.title }) + path << "##{section.aref}" + + # Case 3: Ref has an aref (class/module context) + # Input: C1@heading -> path="C1.html", ref=C1 class + # Output: C1.html#class-c1-heading + elsif ref.respond_to?(:aref) + path << "##{ref.aref}-#{formatted_label}" + + # Case 4: No context, just the label (e.g., TopLevel/file) + # Input: README@section -> path="README_md.html" + # Output: README_md.html#section + else + path << "##{formatted_label}" + end + end + + "#{html_string}" + end - def handle_TT(code) - emit_inline(tt_cross_reference(code) || "#{convert_string(code)}") - end + def handle_TT(code) + emit_inline(tt_cross_reference(code) || "#{convert_string(code)}") + end - # Applies additional special handling on top of the one defined in ToHtml. - # When a tidy link is {Foo}[rdoc-ref:Foo], the label part is surrounded by . - # TODO: reconsider this workaround. - def apply_tidylink_label_special_handling(label, url) - if url == "rdoc-ref:#{label}" && cross_reference(label)&.include?('') - "#{convert_string(label)}" - else - super - end - end + # Applies additional special handling on top of the one defined in ToHtml. + # When a tidy link is {Foo}[rdoc-ref:Foo], the label part is surrounded by . + # TODO: reconsider this workaround. + def apply_tidylink_label_special_handling(label, url) + if url == "rdoc-ref:#{label}" && cross_reference(label)&.include?('') + "#{convert_string(label)}" + else + super + end + end - # Handles cross-reference and suppressed-crossref inside tt tag. - # Returns nil if code is not an existing cross-reference nor a suppressed-crossref. - def tt_cross_reference(code) - return if in_tidylink_label? - - crossref_regexp = @hyperlink_all ? ALL_CROSSREF_REGEXP : CROSSREF_REGEXP - # REGEXP sometimes matches a string that starts with a backslash but is not a - # suppressed cross-reference (for example, `\+`), so the backslash-removed - # part needs to be checked against crossref_regexp. - match = crossref_regexp.match(code.delete_prefix('\\')) - return unless match && match.begin(1).zero? - return unless match.post_match.match?(/\A[[:punct:]\s]*\z/) - - # cross_reference(file_page) may return a link without code tag. - # We need to check it because this method shouldn't return an html text without code tag. - if code.start_with?('\\') - # Remove leading backslash if crossref exists - "#{convert_string(code[1..])}" if cross_reference(code[1..])&.include?('') - else - html = cross_reference(code) - html if html&.include?('') + # Handles cross-reference and suppressed-crossref inside tt tag. + # Returns nil if code is not an existing cross-reference nor a suppressed-crossref. + def tt_cross_reference(code) + return if in_tidylink_label? + + crossref_regexp = @hyperlink_all ? ALL_CROSSREF_REGEXP : CROSSREF_REGEXP + # REGEXP sometimes matches a string that starts with a backslash but is not a + # suppressed cross-reference (for example, `\+`), so the backslash-removed + # part needs to be checked against crossref_regexp. + match = crossref_regexp.match(code.delete_prefix('\\')) + return unless match && match.begin(1).zero? + return unless match.post_match.match?(/\A[[:punct:]\s]*\z/) + + # cross_reference(file_page) may return a link without code tag. + # We need to check it because this method shouldn't return an html text without code tag. + if code.start_with?('\\') + # Remove leading backslash if crossref exists + "#{convert_string(code[1..])}" if cross_reference(code[1..])&.include?('') + else + html = cross_reference(code) + html if html&.include?('') + end + end end end end diff --git a/lib/rdoc/markup/to_html_snippet.rb b/lib/rdoc/markup/to_html_snippet.rb index 796c268539..7f2339ff9e 100644 --- a/lib/rdoc/markup/to_html_snippet.rb +++ b/lib/rdoc/markup/to_html_snippet.rb @@ -1,287 +1,291 @@ # frozen_string_literal: true -## -# Outputs RDoc markup as paragraphs with inline markup only. +module RDoc + class Markup + ## + # Outputs RDoc markup as paragraphs with inline markup only. -class RDoc::Markup::ToHtmlSnippet < RDoc::Markup::ToHtml + class ToHtmlSnippet < ::RDoc::Markup::ToHtml - ## - # After this many characters the input will be cut off. + ## + # After this many characters the input will be cut off. - attr_reader :character_limit + attr_reader :character_limit - ## - # The number of characters seen so far. + ## + # The number of characters seen so far. - attr_reader :characters # :nodoc: + attr_reader :characters # :nodoc: - ## - # The attribute bitmask + ## + # The attribute bitmask - attr_reader :mask + attr_reader :mask - ## - # After this many paragraphs the input will be cut off. + ## + # After this many paragraphs the input will be cut off. - attr_reader :paragraph_limit + attr_reader :paragraph_limit - ## - # Count of paragraphs found + ## + # Count of paragraphs found - attr_reader :paragraphs + attr_reader :paragraphs - ## - # Creates a new ToHtmlSnippet formatter that will cut off the input on the - # next word boundary after the given number of +characters+ or +paragraphs+ - # of text have been encountered. + ## + # Creates a new ToHtmlSnippet formatter that will cut off the input on the + # next word boundary after the given number of +characters+ or +paragraphs+ + # of text have been encountered. - def initialize(characters = 100, paragraphs = 3) - super() + def initialize(characters = 100, paragraphs = 3) + super() - @character_limit = characters - @paragraph_limit = paragraphs + @character_limit = characters + @paragraph_limit = paragraphs - @characters = 0 - @mask = 0 - @paragraphs = 0 + @characters = 0 + @mask = 0 + @paragraphs = 0 - @markup.add_regexp_handling RDoc::CrossReference::CROSSREF_REGEXP, :CROSSREF - end + @markup.add_regexp_handling ::RDoc::CrossReference::CROSSREF_REGEXP, :CROSSREF + end - ## - # Adds +heading+ to the output as a paragraph + ## + # Adds +heading+ to the output as a paragraph - def accept_heading(heading) - @res << "

    #{to_html heading.text}\n" + def accept_heading(heading) + @res << "

    #{to_html heading.text}\n" - add_paragraph - end + add_paragraph + end - ## - # Raw sections are untrusted and ignored + ## + # Raw sections are untrusted and ignored - alias accept_raw ignore + alias accept_raw ignore - ## - # Rules are ignored + ## + # Rules are ignored - alias accept_rule ignore + alias accept_rule ignore - ## - # Adds +paragraph+ to the output + ## + # Adds +paragraph+ to the output - def accept_paragraph(paragraph) - para = @in_list_entry.last || "

    " + def accept_paragraph(paragraph) + para = @in_list_entry.last || "

    " - text = paragraph.text @hard_break + text = paragraph.text @hard_break - @res << "#{para}#{to_html text}\n" + @res << "#{para}#{to_html text}\n" - add_paragraph - end + add_paragraph + end - ## - # Finishes consumption of +list_item+ + ## + # Finishes consumption of +list_item+ - def accept_list_item_end(list_item) - end + def accept_list_item_end(list_item) + end - ## - # Prepares the visitor for consuming +list_item+ + ## + # Prepares the visitor for consuming +list_item+ - def accept_list_item_start(list_item) - @res << list_item_start(list_item, @list.last) - end + def accept_list_item_start(list_item) + @res << list_item_start(list_item, @list.last) + end - ## - # Prepares the visitor for consuming +list+ + ## + # Prepares the visitor for consuming +list+ - def accept_list_start(list) - @list << list.type - @res << html_list_name(list.type, true) - @in_list_entry.push '' - end + def accept_list_start(list) + @list << list.type + @res << html_list_name(list.type, true) + @in_list_entry.push '' + end - ## - # Adds +verbatim+ to the output + ## + # Adds +verbatim+ to the output - def accept_verbatim(verbatim) - throw :done if @characters >= @character_limit - input = verbatim.text.rstrip - text = truncate(input, @character_limit - @characters) - @characters += input.length - text << " #{TO_HTML_CHARACTERS[text.encoding][:ellipsis]}" unless text == input + def accept_verbatim(verbatim) + throw :done if @characters >= @character_limit + input = verbatim.text.rstrip + text = truncate(input, @character_limit - @characters) + @characters += input.length + text << " #{TO_HTML_CHARACTERS[text.encoding][:ellipsis]}" unless text == input - super RDoc::Markup::Verbatim.new text + super ::RDoc::Markup::Verbatim.new text - add_paragraph - end + add_paragraph + end - ## - # Prepares the visitor for HTML snippet generation + ## + # Prepares the visitor for HTML snippet generation - def start_accepting - super + def start_accepting + super - @characters = 0 - end + @characters = 0 + end - ## - # Removes escaping from the cross-references in +target+ + ## + # Removes escaping from the cross-references in +target+ - def handle_regexp_CROSSREF(text) - text.sub(/\A\\/, '') - end + def handle_regexp_CROSSREF(text) + text.sub(/\A\\/, '') + end - ## - # Lists are paragraphs, but notes and labels have a separator + ## + # Lists are paragraphs, but notes and labels have a separator - def list_item_start(list_item, list_type) - throw :done if @characters >= @character_limit + def list_item_start(list_item, list_type) + throw :done if @characters >= @character_limit - case list_type - when :BULLET, :LALPHA, :NUMBER, :UALPHA - "

    " - when :LABEL, :NOTE - labels = Array(list_item.label).map do |label| - to_html label - end.join ', ' + case list_type + when :BULLET, :LALPHA, :NUMBER, :UALPHA + "

    " + when :LABEL, :NOTE + labels = Array(list_item.label).map do |label| + to_html label + end.join ', ' - labels << " — " unless labels.empty? + labels << " — " unless labels.empty? - start = "

    #{labels}" - @characters += 1 # try to include the label - start - else - raise RDoc::Error, "Invalid list type: #{list_type.inspect}" - end - end + start = "

    #{labels}" + @characters += 1 # try to include the label + start + else + raise ::RDoc::Error, "Invalid list type: #{list_type.inspect}" + end + end - ## - # Returns just the text of +link+, +url+ is only used to determine the link - # type. - - def gen_url(url, text) - if url =~ /^rdoc-label:([^:]*)(?::(.*))?/ - type = "link" - elsif url =~ /([A-Za-z]+):(.*)/ - type = $1 - else - type = "http" - end + ## + # Returns just the text of +link+, +url+ is only used to determine the link + # type. - if (type == "http" or type == "https" or type == "link") and - url =~ /\.(gif|png|jpg|jpeg|bmp)$/ - '' - else - text.sub(%r%^#{type}:/*%, '') - end - end + def gen_url(url, text) + if url =~ /^rdoc-label:([^:]*)(?::(.*))?/ + type = "link" + elsif url =~ /([A-Za-z]+):(.*)/ + type = $1 + else + type = "http" + end - ## - # In snippets, there are no lists + if (type == "http" or type == "https" or type == "link") and + url =~ /\.(gif|png|jpg|jpeg|bmp)$/ + '' + else + text.sub(%r%^#{type}:/*%, '') + end + end + + ## + # In snippets, there are no lists + + def html_list_name(list_type, open_tag) + '' + end + + ## + # Throws +:done+ when paragraph_limit paragraphs have been encountered + + def add_paragraph + @paragraphs += 1 + + throw :done if @paragraphs >= @paragraph_limit + end + + ## + # Marks up +content+ + + def convert(content) + catch :done do + return super + end + + end_accepting + end + + def handle_PLAIN_TEXT(text) # :nodoc: + return if inline_limit_reached? + + truncated = truncate(text, @inline_character_limit) + @inline_character_limit -= text.size + emit_inline(convert_string(truncated)) + end + + def handle_REGEXP_HANDLING_TEXT(text) # :nodoc: + return if inline_limit_reached? + + # We can't truncate text including html tags. + # Just emit as is, and count all characters including html tag part. + emit_inline(text) + @inline_character_limit -= text.size + end + + def handle_BOLD(nodes) + super unless inline_limit_reached? + end + + def handle_BOLD_WORD(word) + super unless inline_limit_reached? + end + + def handle_EM(nodes) + super unless inline_limit_reached? + end + + def handle_EM_WORD(word) + super unless inline_limit_reached? + end + + def handle_TT(code) + super unless inline_limit_reached? + end + + def handle_STRIKE(nodes) + super unless inline_limit_reached? + end + + def handle_HARD_BREAK + super unless inline_limit_reached? + end + + def handle_TIDYLINK(label_part, url) + traverse_inline_nodes(label_part) unless inline_limit_reached? + end + + def inline_limit_reached? + @inline_character_limit <= 0 + end + + def handle_inline(text) + limit = @character_limit - @characters + return ['', 0] if limit <= 0 + @inline_character_limit = limit + res = super + res << " #{TO_HTML_CHARACTERS[text.encoding][:ellipsis]}" if @inline_character_limit <= 0 + @characters += limit - @inline_character_limit + res + end - def html_list_name(list_type, open_tag) - '' - end + def to_html(item) + throw :done if @characters >= @character_limit + handle_inline(item) + end - ## - # Throws +:done+ when paragraph_limit paragraphs have been encountered + ## + # Truncates +text+ at the end of the first word after the limit. - def add_paragraph - @paragraphs += 1 + def truncate(text, limit) + return text if limit >= text.size + return '' if limit <= 0 - throw :done if @paragraphs >= @paragraph_limit - end + text =~ /\A(.{#{limit},}?)(\s|$)/m # TODO word-break instead of \s? - ## - # Marks up +content+ + $1 + end - def convert(content) - catch :done do - return super end - - end_accepting - end - - def handle_PLAIN_TEXT(text) # :nodoc: - return if inline_limit_reached? - - truncated = truncate(text, @inline_character_limit) - @inline_character_limit -= text.size - emit_inline(convert_string(truncated)) end - - def handle_REGEXP_HANDLING_TEXT(text) # :nodoc: - return if inline_limit_reached? - - # We can't truncate text including html tags. - # Just emit as is, and count all characters including html tag part. - emit_inline(text) - @inline_character_limit -= text.size - end - - def handle_BOLD(nodes) - super unless inline_limit_reached? - end - - def handle_BOLD_WORD(word) - super unless inline_limit_reached? - end - - def handle_EM(nodes) - super unless inline_limit_reached? - end - - def handle_EM_WORD(word) - super unless inline_limit_reached? - end - - def handle_TT(code) - super unless inline_limit_reached? - end - - def handle_STRIKE(nodes) - super unless inline_limit_reached? - end - - def handle_HARD_BREAK - super unless inline_limit_reached? - end - - def handle_TIDYLINK(label_part, url) - traverse_inline_nodes(label_part) unless inline_limit_reached? - end - - def inline_limit_reached? - @inline_character_limit <= 0 - end - - def handle_inline(text) - limit = @character_limit - @characters - return ['', 0] if limit <= 0 - @inline_character_limit = limit - res = super - res << " #{TO_HTML_CHARACTERS[text.encoding][:ellipsis]}" if @inline_character_limit <= 0 - @characters += limit - @inline_character_limit - res - end - - def to_html(item) - throw :done if @characters >= @character_limit - handle_inline(item) - end - - ## - # Truncates +text+ at the end of the first word after the limit. - - def truncate(text, limit) - return text if limit >= text.size - return '' if limit <= 0 - - text =~ /\A(.{#{limit},}?)(\s|$)/m # TODO word-break instead of \s? - - $1 - end - end diff --git a/lib/rdoc/markup/to_joined_paragraph.rb b/lib/rdoc/markup/to_joined_paragraph.rb index 27060e226c..00d1254d2a 100644 --- a/lib/rdoc/markup/to_joined_paragraph.rb +++ b/lib/rdoc/markup/to_joined_paragraph.rb @@ -1,41 +1,45 @@ # frozen_string_literal: true -## -# Joins the parts of an RDoc::Markup::Paragraph into a single String. -# -# This allows for easier maintenance and testing of Markdown support. -# -# This formatter only works on Paragraph instances. Attempting to process -# other markup syntax items will not work. +module RDoc + class Markup + ## + # Joins the parts of an RDoc::Markup::Paragraph into a single String. + # + # This allows for easier maintenance and testing of Markdown support. + # + # This formatter only works on Paragraph instances. Attempting to process + # other markup syntax items will not work. -class RDoc::Markup::ToJoinedParagraph < RDoc::Markup::Formatter - def start_accepting # :nodoc: - end + class ToJoinedParagraph < ::RDoc::Markup::Formatter + def start_accepting # :nodoc: + end - def end_accepting # :nodoc: - end + def end_accepting # :nodoc: + end - ## - # Converts the parts of +paragraph+ to a single entry. + ## + # Converts the parts of +paragraph+ to a single entry. - def accept_paragraph(paragraph) - parts = paragraph.parts.chunk do |part| - String === part - end.flat_map do |string, chunk| - string ? chunk.join.rstrip : chunk - end + def accept_paragraph(paragraph) + parts = paragraph.parts.chunk do |part| + String === part + end.flat_map do |string, chunk| + string ? chunk.join.rstrip : chunk + end - paragraph.parts.replace parts - end + paragraph.parts.replace parts + end - alias accept_block_quote ignore - alias accept_heading ignore - alias accept_list_end ignore - alias accept_list_item_end ignore - alias accept_list_item_start ignore - alias accept_list_start ignore - alias accept_raw ignore - alias accept_rule ignore - alias accept_verbatim ignore - alias accept_table ignore + alias accept_block_quote ignore + alias accept_heading ignore + alias accept_list_end ignore + alias accept_list_item_end ignore + alias accept_list_item_start ignore + alias accept_list_start ignore + alias accept_raw ignore + alias accept_rule ignore + alias accept_verbatim ignore + alias accept_table ignore + end + end end diff --git a/lib/rdoc/markup/to_label.rb b/lib/rdoc/markup/to_label.rb index ae11a5fe6b..9874c4e7ce 100644 --- a/lib/rdoc/markup/to_label.rb +++ b/lib/rdoc/markup/to_label.rb @@ -2,83 +2,87 @@ require 'cgi/escape' require 'cgi/util' unless defined?(CGI::EscapeExt) -## -# Creates HTML-safe labels suitable for use in id attributes. Tidylinks are -# converted to their link part and cross-reference links have the suppression -# marks removed (\\SomeClass is converted to SomeClass). +module RDoc + class Markup + ## + # Creates HTML-safe labels suitable for use in id attributes. Tidylinks are + # converted to their link part and cross-reference links have the suppression + # marks removed (\\SomeClass is converted to SomeClass). -class RDoc::Markup::ToLabel < RDoc::Markup::Formatter + class ToLabel < ::RDoc::Markup::Formatter - attr_reader :res # :nodoc: + attr_reader :res # :nodoc: - ## - # Creates a new formatter that will output HTML-safe labels + ## + # Creates a new formatter that will output HTML-safe labels - def initialize - super + def initialize + super - @markup.add_regexp_handling RDoc::CrossReference::CROSSREF_REGEXP, :CROSSREF + @markup.add_regexp_handling ::RDoc::CrossReference::CROSSREF_REGEXP, :CROSSREF - @res = [] - end + @res = [] + end - def handle_PLAIN_TEXT(text) - @res << text - end + def handle_PLAIN_TEXT(text) + @res << text + end - def handle_REGEXP_HANDLING_TEXT(text) - @res << text - end + def handle_REGEXP_HANDLING_TEXT(text) + @res << text + end - def handle_TT(text) - @res << text - end + def handle_TT(text) + @res << text + end - def extract_plaintext(text) - @res = [] - handle_inline(text) - @res.join - end + def extract_plaintext(text) + @res = [] + handle_inline(text) + @res.join + end - ## - # Converts +text+ to an HTML-safe label using GitHub-style anchor formatting. + ## + # Converts +text+ to an HTML-safe label using GitHub-style anchor formatting. - def convert(text) - label = extract_plaintext(text) + def convert(text) + label = extract_plaintext(text) - RDoc::Text.to_anchor(label) - end + ::RDoc::Text.to_anchor(label) + end - ## - # Converts +text+ to an HTML-safe label using legacy RDoc formatting. - # Used for generating backward-compatible anchor aliases. + ## + # Converts +text+ to an HTML-safe label using legacy RDoc formatting. + # Used for generating backward-compatible anchor aliases. - def convert_legacy(text) - label = extract_plaintext(text) + def convert_legacy(text) + label = extract_plaintext(text) - CGI.escape(label).gsub('%', '-').sub(/^-/, '') - end + CGI.escape(label).gsub('%', '-').sub(/^-/, '') + end - ## - # Converts the CROSSREF +target+ to plain text, removing the suppression - # marker, if any + ## + # Converts the CROSSREF +target+ to plain text, removing the suppression + # marker, if any - def handle_regexp_CROSSREF(text) - text.sub(/^\\/, '') - end + def handle_regexp_CROSSREF(text) + text.sub(/^\\/, '') + end - alias accept_blank_line ignore - alias accept_block_quote ignore - alias accept_heading ignore - alias accept_list_end ignore - alias accept_list_item_end ignore - alias accept_list_item_start ignore - alias accept_list_start ignore - alias accept_paragraph ignore - alias accept_raw ignore - alias accept_rule ignore - alias accept_verbatim ignore - alias end_accepting ignore - alias start_accepting ignore + alias accept_blank_line ignore + alias accept_block_quote ignore + alias accept_heading ignore + alias accept_list_end ignore + alias accept_list_item_end ignore + alias accept_list_item_start ignore + alias accept_list_start ignore + alias accept_paragraph ignore + alias accept_raw ignore + alias accept_rule ignore + alias accept_verbatim ignore + alias end_accepting ignore + alias start_accepting ignore + end + end end diff --git a/lib/rdoc/markup/to_markdown.rb b/lib/rdoc/markup/to_markdown.rb index 18e1c86f52..41825ccb38 100644 --- a/lib/rdoc/markup/to_markdown.rb +++ b/lib/rdoc/markup/to_markdown.rb @@ -1,215 +1,219 @@ # frozen_string_literal: true # :markup: markdown -## -# Outputs parsed markup as Markdown +module RDoc + class Markup + ## + # Outputs parsed markup as Markdown + + class ToMarkdown < ::RDoc::Markup::ToRdoc + + ## + # Creates a new formatter that will output Markdown format text + + def initialize + super + + @headings[1] = ['# ', ''] + @headings[2] = ['## ', ''] + @headings[3] = ['### ', ''] + @headings[4] = ['#### ', ''] + @headings[5] = ['##### ', ''] + @headings[6] = ['###### ', ''] + + add_regexp_handling_RDOCLINK + + @hard_break = " \n" + end + + ## + # Finishes consumption of `list` + + def accept_list_end(list) + super + end + + ## + # Finishes consumption of `list_item` + + def accept_list_item_end(list_item) + width = case @list_type.last + when :BULLET + 4 + when :NOTE, :LABEL + use_prefix + + @res << "\n" + + 4 + else + @list_index[-1] = @list_index.last.succ + 4 + end + + @indent -= width + end + + ## + # Prepares the visitor for consuming `list_item` + + def accept_list_item_start(list_item) + type = @list_type.last + + case type + when :NOTE, :LABEL + bullets = Array(list_item.label).map do |label| + attributes(label).strip + end.join "\n" + + bullets << "\n" unless bullets.empty? + + @prefix = ' ' * @indent + @indent += 4 + @prefix << bullets << ":" << (' ' * (@indent - 1)) + else + bullet = type == :BULLET ? '*' : @list_index.last.to_s + '.' + @prefix = (' ' * @indent) + bullet.ljust(4) + + @indent += 4 + end + end + + def add_tag(tag, simple_tag, content) + if content.match?(/\A[\w\s]+\z/) + emit_inline("#{simple_tag}#{content}#{simple_tag}") + else + emit_inline("<#{tag}>#{content}") + end + end + + def handle_tag(nodes, simple_tag, tag) + if nodes.size == 1 && String === nodes[0] + content = apply_regexp_handling(nodes[0]).map do |text, converted| + converted ? text : convert_string(text) + end.join + add_tag(tag, simple_tag, content) + else + emit_inline("<#{tag}>") + traverse_inline_nodes(nodes) + emit_inline("") + end + end + + def handle_TIDYLINK(label_part, url) + if url =~ /^rdoc-label:foot/ + emit_inline(handle_rdoc_link(url)) + else + emit_inline('[') + traverse_inline_nodes(label_part) + emit_inline("](#{url})") + end + end + + def handle_BOLD(nodes) + handle_tag(nodes, '**', 'strong') + end + + def handle_EM(nodes) + handle_tag(nodes, '*', 'em') + end + + def handle_BOLD_WORD(word) + add_tag('strong', '**', convert_string(word)) + end + + def handle_EM_WORD(word) + add_tag('em', '*', convert_string(word)) + end + + def handle_TT(text) + add_tag('code', '`', convert_string(text)) + end + + def handle_STRIKE(nodes) + handle_tag(nodes, '~~', 's') + end + + def handle_HARD_BREAK + emit_inline(" \n") + end + + ## + # Prepares the visitor for consuming `list` + + def accept_list_start(list) + case list.type + when :BULLET, :LABEL, :NOTE + @list_index << nil + when :LALPHA, :NUMBER, :UALPHA + @list_index << 1 + else + raise ::RDoc::Error, "invalid list type #{list.type}" + end + + @list_width << 4 + @list_type << list.type + end + + ## + # Adds `rule` to the output + + def accept_rule(rule) + use_prefix or @res << ' ' * @indent + @res << '-' * 3 + @res << "\n" + end + + ## + # Outputs `verbatim` indented 4 columns + + def accept_verbatim(verbatim) + indent = ' ' * (@indent + 4) + + verbatim.parts.each do |part| + @res << indent unless part == "\n" + @res << part + end + + @res << "\n" + end + + ## + # Creates a Markdown-style URL from +url+ with +text+. + + def gen_url(url, text) + scheme, url, = parse_url url + + "[#{text.sub(%r{^#{scheme}:/*}i, '')}](#{url})" + end + + ## + # Handles rdoc- type links for footnotes. + + def handle_rdoc_link(url) + case url + when /^rdoc-ref:/ + $' + when /^rdoc-label:footmark-(\d+)/ + "[^#{$1}]:" + when /^rdoc-label:foottext-(\d+)/ + "[^#{$1}]" + when /^rdoc-label:label-/ + gen_url url, $' + when /^rdoc-image:/ + "![](#{$'})" + when /^rdoc-[a-z]+:/ + $' + end + end + + ## + # Converts the rdoc-...: links into a Markdown.style links. + + def handle_regexp_RDOCLINK(text) + handle_rdoc_link text + end -class RDoc::Markup::ToMarkdown < RDoc::Markup::ToRdoc - - ## - # Creates a new formatter that will output Markdown format text - - def initialize - super - - @headings[1] = ['# ', ''] - @headings[2] = ['## ', ''] - @headings[3] = ['### ', ''] - @headings[4] = ['#### ', ''] - @headings[5] = ['##### ', ''] - @headings[6] = ['###### ', ''] - - add_regexp_handling_RDOCLINK - - @hard_break = " \n" - end - - ## - # Finishes consumption of `list` - - def accept_list_end(list) - super - end - - ## - # Finishes consumption of `list_item` - - def accept_list_item_end(list_item) - width = case @list_type.last - when :BULLET - 4 - when :NOTE, :LABEL - use_prefix - - @res << "\n" - - 4 - else - @list_index[-1] = @list_index.last.succ - 4 - end - - @indent -= width - end - - ## - # Prepares the visitor for consuming `list_item` - - def accept_list_item_start(list_item) - type = @list_type.last - - case type - when :NOTE, :LABEL - bullets = Array(list_item.label).map do |label| - attributes(label).strip - end.join "\n" - - bullets << "\n" unless bullets.empty? - - @prefix = ' ' * @indent - @indent += 4 - @prefix << bullets << ":" << (' ' * (@indent - 1)) - else - bullet = type == :BULLET ? '*' : @list_index.last.to_s + '.' - @prefix = (' ' * @indent) + bullet.ljust(4) - - @indent += 4 - end - end - - def add_tag(tag, simple_tag, content) - if content.match?(/\A[\w\s]+\z/) - emit_inline("#{simple_tag}#{content}#{simple_tag}") - else - emit_inline("<#{tag}>#{content}") - end - end - - def handle_tag(nodes, simple_tag, tag) - if nodes.size == 1 && String === nodes[0] - content = apply_regexp_handling(nodes[0]).map do |text, converted| - converted ? text : convert_string(text) - end.join - add_tag(tag, simple_tag, content) - else - emit_inline("<#{tag}>") - traverse_inline_nodes(nodes) - emit_inline("") - end - end - - def handle_TIDYLINK(label_part, url) - if url =~ /^rdoc-label:foot/ - emit_inline(handle_rdoc_link(url)) - else - emit_inline('[') - traverse_inline_nodes(label_part) - emit_inline("](#{url})") - end - end - - def handle_BOLD(nodes) - handle_tag(nodes, '**', 'strong') - end - - def handle_EM(nodes) - handle_tag(nodes, '*', 'em') - end - - def handle_BOLD_WORD(word) - add_tag('strong', '**', convert_string(word)) - end - - def handle_EM_WORD(word) - add_tag('em', '*', convert_string(word)) - end - - def handle_TT(text) - add_tag('code', '`', convert_string(text)) - end - - def handle_STRIKE(nodes) - handle_tag(nodes, '~~', 's') - end - - def handle_HARD_BREAK - emit_inline(" \n") - end - - ## - # Prepares the visitor for consuming `list` - - def accept_list_start(list) - case list.type - when :BULLET, :LABEL, :NOTE - @list_index << nil - when :LALPHA, :NUMBER, :UALPHA - @list_index << 1 - else - raise RDoc::Error, "invalid list type #{list.type}" - end - - @list_width << 4 - @list_type << list.type - end - - ## - # Adds `rule` to the output - - def accept_rule(rule) - use_prefix or @res << ' ' * @indent - @res << '-' * 3 - @res << "\n" - end - - ## - # Outputs `verbatim` indented 4 columns - - def accept_verbatim(verbatim) - indent = ' ' * (@indent + 4) - - verbatim.parts.each do |part| - @res << indent unless part == "\n" - @res << part end - - @res << "\n" end - - ## - # Creates a Markdown-style URL from +url+ with +text+. - - def gen_url(url, text) - scheme, url, = parse_url url - - "[#{text.sub(%r{^#{scheme}:/*}i, '')}](#{url})" - end - - ## - # Handles rdoc- type links for footnotes. - - def handle_rdoc_link(url) - case url - when /^rdoc-ref:/ - $' - when /^rdoc-label:footmark-(\d+)/ - "[^#{$1}]:" - when /^rdoc-label:foottext-(\d+)/ - "[^#{$1}]" - when /^rdoc-label:label-/ - gen_url url, $' - when /^rdoc-image:/ - "![](#{$'})" - when /^rdoc-[a-z]+:/ - $' - end - end - - ## - # Converts the rdoc-...: links into a Markdown.style links. - - def handle_regexp_RDOCLINK(text) - handle_rdoc_link text - end - end diff --git a/lib/rdoc/markup/to_rdoc.rb b/lib/rdoc/markup/to_rdoc.rb index 4001760426..bee8d5f62d 100644 --- a/lib/rdoc/markup/to_rdoc.rb +++ b/lib/rdoc/markup/to_rdoc.rb @@ -1,421 +1,425 @@ # frozen_string_literal: true -## -# Outputs RDoc markup as RDoc markup! (mostly) +module RDoc + class Markup + ## + # Outputs RDoc markup as RDoc markup! (mostly) -class RDoc::Markup::ToRdoc < RDoc::Markup::Formatter - DEFAULT_HEADINGS = { - 1 => ['= ', ''], - 2 => ['== ', ''], - 3 => ['=== ', ''], - 4 => ['==== ', ''], - 5 => ['===== ', ''], - 6 => ['====== ', ''] - } - DEFAULT_HEADINGS.default = [] - DEFAULT_HEADINGS.freeze + class ToRdoc < ::RDoc::Markup::Formatter + DEFAULT_HEADINGS = { + 1 => ['= ', ''], + 2 => ['== ', ''], + 3 => ['=== ', ''], + 4 => ['==== ', ''], + 5 => ['===== ', ''], + 6 => ['====== ', ''] + } + DEFAULT_HEADINGS.default = [] + DEFAULT_HEADINGS.freeze - ## - # Current indent amount for output in characters + ## + # Current indent amount for output in characters - attr_accessor :indent + attr_accessor :indent - ## - # Output width in characters + ## + # Output width in characters - attr_accessor :width + attr_accessor :width - ## - # Stack of current list indexes for alphabetic and numeric lists + ## + # Stack of current list indexes for alphabetic and numeric lists - attr_reader :list_index + attr_reader :list_index - ## - # Stack of list types + ## + # Stack of list types - attr_reader :list_type + attr_reader :list_type - ## - # Stack of list widths for indentation + ## + # Stack of list widths for indentation - attr_reader :list_width + attr_reader :list_width - ## - # Prefix for the next list item. See #use_prefix + ## + # Prefix for the next list item. See #use_prefix - attr_reader :prefix + attr_reader :prefix - ## - # Output accumulator + ## + # Output accumulator - attr_reader :res + attr_reader :res - ## - # Creates a new formatter that will output (mostly) \RDoc markup + ## + # Creates a new formatter that will output (mostly) \RDoc markup - def initialize - super + def initialize + super - @markup.add_regexp_handling(/\\\S/, :SUPPRESSED_CROSSREF) - @width = 78 + @markup.add_regexp_handling(/\\\S/, :SUPPRESSED_CROSSREF) + @width = 78 - @headings = DEFAULT_HEADINGS.dup - @hard_break = "\n" - end - - ## - # Adds +blank_line+ to the output - - def accept_blank_line(blank_line) - @res << "\n" - end - - ## - # Adds +paragraph+ to the output - - def accept_block_quote(block_quote) - @indent += 2 + @headings = DEFAULT_HEADINGS.dup + @hard_break = "\n" + end - block_quote.parts.each do |part| - @prefix = '> ' + ## + # Adds +blank_line+ to the output - part.accept self - end + def accept_blank_line(blank_line) + @res << "\n" + end - @indent -= 2 - end + ## + # Adds +paragraph+ to the output - ## - # Adds +heading+ to the output + def accept_block_quote(block_quote) + @indent += 2 - def accept_heading(heading) - use_prefix or @res << ' ' * @indent - @res << @headings[heading.level][0] - @res << attributes(heading.text) - @res << @headings[heading.level][1] - @res << "\n" - end + block_quote.parts.each do |part| + @prefix = '> ' - ## - # Finishes consumption of +list+ + part.accept self + end - def accept_list_end(list) - @list_index.pop - @list_type.pop - @list_width.pop - end + @indent -= 2 + end - ## - # Finishes consumption of +list_item+ - - def accept_list_item_end(list_item) - width = case @list_type.last - when :BULLET - 2 - when :NOTE, :LABEL - if @prefix - @res << @prefix.strip - @prefix = nil - end - - @res << "\n" - 2 - else - bullet = @list_index.last.to_s - @list_index[-1] = @list_index.last.succ - bullet.length + 2 - end - - @indent -= width - end + ## + # Adds +heading+ to the output - ## - # Prepares the visitor for consuming +list_item+ + def accept_heading(heading) + use_prefix or @res << ' ' * @indent + @res << @headings[heading.level][0] + @res << attributes(heading.text) + @res << @headings[heading.level][1] + @res << "\n" + end - def accept_list_item_start(list_item) - type = @list_type.last + ## + # Finishes consumption of +list+ - case type - when :NOTE, :LABEL - stripped_labels = Array(list_item.label).map do |label| - attributes(label).strip + def accept_list_end(list) + @list_index.pop + @list_type.pop + @list_width.pop end - bullets = case type - when :NOTE - stripped_labels.map { |b| "#{b}::" } - when :LABEL - stripped_labels.map { |b| "[#{b}]" } + ## + # Finishes consumption of +list_item+ + + def accept_list_item_end(list_item) + width = case @list_type.last + when :BULLET + 2 + when :NOTE, :LABEL + if @prefix + @res << @prefix.strip + @prefix = nil + end + + @res << "\n" + 2 + else + bullet = @list_index.last.to_s + @list_index[-1] = @list_index.last.succ + bullet.length + 2 + end + + @indent -= width end - bullets = bullets.join("\n") - bullets << "\n" unless stripped_labels.empty? - - @prefix = ' ' * @indent - @indent += 2 - @prefix << bullets + (' ' * @indent) - else - bullet = type == :BULLET ? '*' : @list_index.last.to_s + '.' - @prefix = (' ' * @indent) + bullet.ljust(bullet.length + 1) - width = bullet.length + 1 - @indent += width - end - end - - ## - # Prepares the visitor for consuming +list+ - - def accept_list_start(list) - case list.type - when :BULLET - @list_index << nil - @list_width << 1 - when :LABEL, :NOTE - @list_index << nil - @list_width << 2 - when :LALPHA - @list_index << 'a' - @list_width << list.items.length.to_s.length - when :NUMBER - @list_index << 1 - @list_width << list.items.length.to_s.length - when :UALPHA - @list_index << 'A' - @list_width << list.items.length.to_s.length - else - raise RDoc::Error, "invalid list type #{list.type}" - end + ## + # Prepares the visitor for consuming +list_item+ + + def accept_list_item_start(list_item) + type = @list_type.last + + case type + when :NOTE, :LABEL + stripped_labels = Array(list_item.label).map do |label| + attributes(label).strip + end + + bullets = case type + when :NOTE + stripped_labels.map { |b| "#{b}::" } + when :LABEL + stripped_labels.map { |b| "[#{b}]" } + end + + bullets = bullets.join("\n") + bullets << "\n" unless stripped_labels.empty? + + @prefix = ' ' * @indent + @indent += 2 + @prefix << bullets + (' ' * @indent) + else + bullet = type == :BULLET ? '*' : @list_index.last.to_s + '.' + @prefix = (' ' * @indent) + bullet.ljust(bullet.length + 1) + width = bullet.length + 1 + @indent += width + end + end - @list_type << list.type - end + ## + # Prepares the visitor for consuming +list+ + + def accept_list_start(list) + case list.type + when :BULLET + @list_index << nil + @list_width << 1 + when :LABEL, :NOTE + @list_index << nil + @list_width << 2 + when :LALPHA + @list_index << 'a' + @list_width << list.items.length.to_s.length + when :NUMBER + @list_index << 1 + @list_width << list.items.length.to_s.length + when :UALPHA + @list_index << 'A' + @list_width << list.items.length.to_s.length + else + raise ::RDoc::Error, "invalid list type #{list.type}" + end + + @list_type << list.type + end - ## - # Adds +paragraph+ to the output + ## + # Adds +paragraph+ to the output - def accept_paragraph(paragraph) - text = paragraph.text @hard_break - wrap attributes text - end + def accept_paragraph(paragraph) + text = paragraph.text @hard_break + wrap attributes text + end - ## - # Adds +paragraph+ to the output + ## + # Adds +paragraph+ to the output - def accept_indented_paragraph(paragraph) - @indent += paragraph.indent - text = paragraph.text @hard_break - wrap attributes text - @indent -= paragraph.indent - end + def accept_indented_paragraph(paragraph) + @indent += paragraph.indent + text = paragraph.text @hard_break + wrap attributes text + @indent -= paragraph.indent + end - ## - # Adds +raw+ to the output + ## + # Adds +raw+ to the output - def accept_raw(raw) - @res << raw.parts.join("\n") - end + def accept_raw(raw) + @res << raw.parts.join("\n") + end - ## - # Adds +rule+ to the output + ## + # Adds +rule+ to the output - def accept_rule(rule) - use_prefix or @res << ' ' * @indent - @res << '-' * (@width - @indent) - @res << "\n" - end + def accept_rule(rule) + use_prefix or @res << ' ' * @indent + @res << '-' * (@width - @indent) + @res << "\n" + end - ## - # Outputs +verbatim+ indented 2 columns + ## + # Outputs +verbatim+ indented 2 columns - def accept_verbatim(verbatim) - indent = ' ' * (@indent + 2) + def accept_verbatim(verbatim) + indent = ' ' * (@indent + 2) - verbatim.parts.each do |part| - @res << indent unless part == "\n" - @res << part - end + verbatim.parts.each do |part| + @res << indent unless part == "\n" + @res << part + end - @res << "\n" - end + @res << "\n" + end - ## - # Adds +table+ to the output + ## + # Adds +table+ to the output + + def accept_table(header, body, aligns) + header = header.map { |h| attributes h } + body = body.map { |row| row.map { |t| attributes t } } + widths = header.zip(*body).map do |cols| + cols.compact.map { |col| calculate_text_width(col) }.max + end + aligns = aligns.map do |a| + case a + when nil, :center + :center + when :left + :ljust + when :right + :rjust + end + end + @res << header.zip(widths, aligns).map do |h, w, a| + extra_width = h.size - calculate_text_width(h) + h.__send__(a, w + extra_width) + end.join("|").rstrip << "\n" + @res << widths.map {|w| "-" * w }.join("|") << "\n" + body.each do |row| + @res << widths.zip(aligns).each_with_index.map do |(w, a), i| + t = row[i] || "" + extra_width = t.size - calculate_text_width(t) + t.__send__(a, w + extra_width) + end.join("|").rstrip << "\n" + end + end - def accept_table(header, body, aligns) - header = header.map { |h| attributes h } - body = body.map { |row| row.map { |t| attributes t } } - widths = header.zip(*body).map do |cols| - cols.compact.map { |col| calculate_text_width(col) }.max - end - aligns = aligns.map do |a| - case a - when nil, :center - :center - when :left - :ljust - when :right - :rjust + def calculate_text_width(text) + text.size end - end - @res << header.zip(widths, aligns).map do |h, w, a| - extra_width = h.size - calculate_text_width(h) - h.__send__(a, w + extra_width) - end.join("|").rstrip << "\n" - @res << widths.map {|w| "-" * w }.join("|") << "\n" - body.each do |row| - @res << widths.zip(aligns).each_with_index.map do |(w, a), i| - t = row[i] || "" - extra_width = t.size - calculate_text_width(t) - t.__send__(a, w + extra_width) - end.join("|").rstrip << "\n" - end - end - def calculate_text_width(text) - text.size - end + def handle_PLAIN_TEXT(text) + add_text(text) + end - def handle_PLAIN_TEXT(text) - add_text(text) - end + def handle_REGEXP_HANDLING_TEXT(text) + add_text(text) + end - def handle_REGEXP_HANDLING_TEXT(text) - add_text(text) - end + def handle_BOLD(target) + on(:BOLD) + super + off(:BOLD) + end - def handle_BOLD(target) - on(:BOLD) - super - off(:BOLD) - end + def handle_EM(target) + on(:EM) + super + off(:EM) + end - def handle_EM(target) - on(:EM) - super - off(:EM) - end + def handle_BOLD_WORD(word) + on(:BOLD) + super + off(:BOLD) + end - def handle_BOLD_WORD(word) - on(:BOLD) - super - off(:BOLD) - end + def handle_EM_WORD(word) + on(:EM) + super + off(:EM) + end - def handle_EM_WORD(word) - on(:EM) - super - off(:EM) - end + def handle_TT(code) + on(:TT) + super + off(:TT) + end - def handle_TT(code) - on(:TT) - super - off(:TT) - end + def handle_STRIKE(target) + on(:STRIKE) + super + off(:STRIKE) + end - def handle_STRIKE(target) - on(:STRIKE) - super - off(:STRIKE) - end + def handle_HARD_BREAK + add_text("\n") + end - def handle_HARD_BREAK - add_text("\n") - end + def handle_TIDYLINK(label_part, url) + super + add_text("( #{url} )") + end - def handle_TIDYLINK(label_part, url) - super - add_text("( #{url} )") - end + def handle_inline(text, initial_attributes = []) + @attributes = Hash.new(0) + initial_attributes.each { |attr| on(attr) } + out = @inline_output = +'' + super(text) + @inline_output = nil + out + end - def handle_inline(text, initial_attributes = []) - @attributes = Hash.new(0) - initial_attributes.each { |attr| on(attr) } - out = @inline_output = +'' - super(text) - @inline_output = nil - out - end + def on(attr) + @attributes[attr] += 1 + end - def on(attr) - @attributes[attr] += 1 - end + def off(attr) + @attributes[attr] -= 1 + @attributes.delete(attr) if @attributes[attr] == 0 + end - def off(attr) - @attributes[attr] -= 1 - @attributes.delete(attr) if @attributes[attr] == 0 - end + def add_text(text) + emit_inline(text) + end - def add_text(text) - emit_inline(text) - end + def emit_inline(text) + @inline_output << text + end - def emit_inline(text) - @inline_output << text - end + ## + # Applies attribute-specific markup to +text+ using RDoc::Markup::InlineParser - ## - # Applies attribute-specific markup to +text+ using RDoc::Markup::InlineParser + def attributes(text) + handle_inline(text) + end - def attributes(text) - handle_inline(text) - end + ## + # Returns the generated output - ## - # Returns the generated output + def end_accepting + @res.join + end - def end_accepting - @res.join - end + ## + # Removes preceding \\ from the suppressed crossref +target+ - ## - # Removes preceding \\ from the suppressed crossref +target+ + def handle_regexp_SUPPRESSED_CROSSREF(text) + text.sub('\\', '') + end - def handle_regexp_SUPPRESSED_CROSSREF(text) - text.sub('\\', '') - end + ## + # Prepares the visitor for text generation - ## - # Prepares the visitor for text generation + def start_accepting + @res = [""] + @indent = 0 + @prefix = nil - def start_accepting - @res = [""] - @indent = 0 - @prefix = nil + @list_index = [] + @list_type = [] + @list_width = [] + end - @list_index = [] - @list_type = [] - @list_width = [] - end + ## + # Adds the stored #prefix to the output and clears it. Lists generate a + # prefix for later consumption. - ## - # Adds the stored #prefix to the output and clears it. Lists generate a - # prefix for later consumption. + def use_prefix + prefix, @prefix = @prefix, nil + @res << prefix if prefix - def use_prefix - prefix, @prefix = @prefix, nil - @res << prefix if prefix + prefix + end - prefix - end + ## + # Wraps +text+ to #width - ## - # Wraps +text+ to #width + def wrap(text) + return unless text && !text.empty? - def wrap(text) - return unless text && !text.empty? + text_len = @width - @indent - text_len = @width - @indent + text_len = 20 if text_len < 20 - text_len = 20 if text_len < 20 + next_prefix = ' ' * @indent - next_prefix = ' ' * @indent + prefix = @prefix || next_prefix + @prefix = nil - prefix = @prefix || next_prefix - @prefix = nil + text.scan(/\G(?:([^ \n]{#{text_len}})(?=[^ \n])|(.{1,#{text_len}})(?:[ \n]|\z))/) do + @res << prefix << ($1 || $2) << "\n" + prefix = next_prefix + end + end - text.scan(/\G(?:([^ \n]{#{text_len}})(?=[^ \n])|(.{1,#{text_len}})(?:[ \n]|\z))/) do - @res << prefix << ($1 || $2) << "\n" - prefix = next_prefix end end - end diff --git a/lib/rdoc/markup/to_table_of_contents.rb b/lib/rdoc/markup/to_table_of_contents.rb index 31c849e7d6..f62a9b08b1 100644 --- a/lib/rdoc/markup/to_table_of_contents.rb +++ b/lib/rdoc/markup/to_table_of_contents.rb @@ -1,88 +1,92 @@ # frozen_string_literal: true -## -# Extracts just the RDoc::Markup::Heading elements from a -# RDoc::Markup::Document to help build a table of contents +module RDoc + class Markup + ## + # Extracts just the RDoc::Markup::Heading elements from a + # RDoc::Markup::Document to help build a table of contents -class RDoc::Markup::ToTableOfContents < RDoc::Markup::Formatter + class ToTableOfContents < ::RDoc::Markup::Formatter - @to_toc = nil + @to_toc = nil - ## - # Singleton for table-of-contents generation + ## + # Singleton for table-of-contents generation - def self.to_toc - @to_toc ||= new - end + def self.to_toc + @to_toc ||= new + end - ## - # Output accumulator + ## + # Output accumulator - attr_reader :res + attr_reader :res - ## - # Omits headings with a level less than the given level. + ## + # Omits headings with a level less than the given level. - attr_accessor :omit_headings_below + attr_accessor :omit_headings_below - def initialize # :nodoc: - super + def initialize # :nodoc: + super - @omit_headings_below = nil - end + @omit_headings_below = nil + end - ## - # Adds +document+ to the output, using its heading cutoff if present + ## + # Adds +document+ to the output, using its heading cutoff if present - def accept_document(document) - @omit_headings_below = document.omit_headings_below + def accept_document(document) + @omit_headings_below = document.omit_headings_below - super - end + super + end - ## - # Adds +heading+ to the table of contents + ## + # Adds +heading+ to the table of contents - def accept_heading(heading) - @res << heading unless suppressed? heading - end + def accept_heading(heading) + @res << heading unless suppressed? heading + end - ## - # Returns the table of contents + ## + # Returns the table of contents - def end_accepting - @res - end + def end_accepting + @res + end - ## - # Prepares the visitor for text generation + ## + # Prepares the visitor for text generation - def start_accepting - @omit_headings_below = nil - @res = [] - end + def start_accepting + @omit_headings_below = nil + @res = [] + end - ## - # Returns true if +heading+ is below the display threshold + ## + # Returns true if +heading+ is below the display threshold - def suppressed?(heading) - return false unless @omit_headings_below + def suppressed?(heading) + return false unless @omit_headings_below - heading.level > @omit_headings_below - end + heading.level > @omit_headings_below + end - # :stopdoc: - alias accept_block_quote ignore - alias accept_raw ignore - alias accept_rule ignore - alias accept_blank_line ignore - alias accept_paragraph ignore - alias accept_verbatim ignore - alias accept_list_end ignore - alias accept_list_item_start ignore - alias accept_list_item_end ignore - alias accept_list_end_bullet ignore - alias accept_list_start ignore - alias accept_table ignore - # :startdoc: + # :stopdoc: + alias accept_block_quote ignore + alias accept_raw ignore + alias accept_rule ignore + alias accept_blank_line ignore + alias accept_paragraph ignore + alias accept_verbatim ignore + alias accept_list_end ignore + alias accept_list_item_start ignore + alias accept_list_item_end ignore + alias accept_list_end_bullet ignore + alias accept_list_start ignore + alias accept_table ignore + # :startdoc: + end + end end diff --git a/lib/rdoc/markup/to_test.rb b/lib/rdoc/markup/to_test.rb index 218d1e0e36..0c9367e2c1 100644 --- a/lib/rdoc/markup/to_test.rb +++ b/lib/rdoc/markup/to_test.rb @@ -1,77 +1,81 @@ # frozen_string_literal: true -## -# This Markup outputter is used for testing purposes. +module RDoc + class Markup + ## + # This Markup outputter is used for testing purposes. -class RDoc::Markup::ToTest < RDoc::Markup::Formatter + class ToTest < ::RDoc::Markup::Formatter - # :stopdoc: + # :stopdoc: - ## - # :section: Visitor + ## + # :section: Visitor - def start_accepting - @res = [] - @list = [] - end + def start_accepting + @res = [] + @list = [] + end - def end_accepting - @res - end + def end_accepting + @res + end - def handle_PLAIN_TEXT(text) - @res << text - end + def handle_PLAIN_TEXT(text) + @res << text + end - def handle_REGEXP_HANDLING_TEXT(text) - @res << text - end + def handle_REGEXP_HANDLING_TEXT(text) + @res << text + end - def accept_paragraph(paragraph) - handle_inline(paragraph.text) - end + def accept_paragraph(paragraph) + handle_inline(paragraph.text) + end - def accept_raw(raw) - @res << raw.parts.join - end + def accept_raw(raw) + @res << raw.parts.join + end - def accept_verbatim(verbatim) - @res << verbatim.text.gsub(/^(\S)/, ' \1') - end + def accept_verbatim(verbatim) + @res << verbatim.text.gsub(/^(\S)/, ' \1') + end - def accept_list_start(list) - @list << case list.type - when :BULLET - '*' - when :NUMBER - '1' - else - list.type - end - end + def accept_list_start(list) + @list << case list.type + when :BULLET + '*' + when :NUMBER + '1' + else + list.type + end + end - def accept_list_end(list) - @list.pop - end + def accept_list_end(list) + @list.pop + end - def accept_list_item_start(list_item) - @res << "#{' ' * (@list.size - 1)}#{@list.last}: " - end + def accept_list_item_start(list_item) + @res << "#{' ' * (@list.size - 1)}#{@list.last}: " + end - def accept_list_item_end(list_item) - end + def accept_list_item_end(list_item) + end - def accept_blank_line(blank_line) - @res << "\n" - end + def accept_blank_line(blank_line) + @res << "\n" + end - def accept_heading(heading) - @res << "#{'=' * heading.level} #{heading.text}" - end + def accept_heading(heading) + @res << "#{'=' * heading.level} #{heading.text}" + end - def accept_rule(rule) - @res << '-' * rule.weight - end + def accept_rule(rule) + @res << '-' * rule.weight + end - # :startdoc: + # :startdoc: + end + end end diff --git a/lib/rdoc/markup/to_tt_only.rb b/lib/rdoc/markup/to_tt_only.rb index 960f5e65db..3646322a0d 100644 --- a/lib/rdoc/markup/to_tt_only.rb +++ b/lib/rdoc/markup/to_tt_only.rb @@ -1,107 +1,111 @@ # frozen_string_literal: true -## -# Extracts sections of text enclosed in plus, tt or code. Used to discover -# undocumented parameters. +module RDoc + class Markup + ## + # Extracts sections of text enclosed in plus, tt or code. Used to discover + # undocumented parameters. -class RDoc::Markup::ToTtOnly < RDoc::Markup::Formatter + class ToTtOnly < ::RDoc::Markup::Formatter - ## - # Stack of list types + ## + # Stack of list types - attr_reader :list_type + attr_reader :list_type - ## - # Output accumulator + ## + # Output accumulator - attr_reader :res + attr_reader :res - ## - # Adds tts from +block_quote+ to the output + ## + # Adds tts from +block_quote+ to the output - def accept_block_quote(block_quote) - tt_sections block_quote.text - end + def accept_block_quote(block_quote) + tt_sections block_quote.text + end - ## - # Pops the list type for +list+ from #list_type + ## + # Pops the list type for +list+ from #list_type - def accept_list_end(list) - @list_type.pop - end + def accept_list_end(list) + @list_type.pop + end - ## - # Pushes the list type for +list+ onto #list_type + ## + # Pushes the list type for +list+ onto #list_type - def accept_list_start(list) - @list_type << list.type - end + def accept_list_start(list) + @list_type << list.type + end - ## - # Prepares the visitor for consuming +list_item+ + ## + # Prepares the visitor for consuming +list_item+ - def accept_list_item_start(list_item) - case @list_type.last - when :NOTE, :LABEL - Array(list_item.label).map do |label| - tt_sections label - end.flatten - end - end + def accept_list_item_start(list_item) + case @list_type.last + when :NOTE, :LABEL + Array(list_item.label).map do |label| + tt_sections label + end.flatten + end + end - ## - # Adds +paragraph+ to the output + ## + # Adds +paragraph+ to the output - def accept_paragraph(paragraph) - tt_sections(paragraph.text) - end + def accept_paragraph(paragraph) + tt_sections(paragraph.text) + end - ## - # Does nothing to +markup_item+ because it doesn't have any user-built - # content + ## + # Does nothing to +markup_item+ because it doesn't have any user-built + # content - def do_nothing(markup_item) - end + def do_nothing(markup_item) + end - alias accept_blank_line do_nothing # :nodoc: - alias accept_heading do_nothing # :nodoc: - alias accept_list_item_end do_nothing # :nodoc: - alias accept_raw do_nothing # :nodoc: - alias accept_rule do_nothing # :nodoc: - alias accept_verbatim do_nothing # :nodoc: - - ## - # Extracts tt sections from +text+ - - def tt_sections(text) - parsed = RDoc::Markup::InlineParser.new(text).parse - traverse = -> node { - next if String === node - if node[:type] == :TT - res << nil - res << node[:children][0] || '' - res << nil - else - node[:children].each(&traverse) + alias accept_blank_line do_nothing # :nodoc: + alias accept_heading do_nothing # :nodoc: + alias accept_list_item_end do_nothing # :nodoc: + alias accept_raw do_nothing # :nodoc: + alias accept_rule do_nothing # :nodoc: + alias accept_verbatim do_nothing # :nodoc: + + ## + # Extracts tt sections from +text+ + + def tt_sections(text) + parsed = ::RDoc::Markup::InlineParser.new(text).parse + traverse = -> node { + next if String === node + if node[:type] == :TT + res << nil + res << node[:children][0] || '' + res << nil + else + node[:children].each(&traverse) + end + } + parsed.each(&traverse) + res end - } - parsed.each(&traverse) - res - end - ## - # Returns an Array of items that were wrapped in plus, tt or code. + ## + # Returns an Array of items that were wrapped in plus, tt or code. - def end_accepting - @res.compact - end + def end_accepting + @res.compact + end - ## - # Prepares the visitor for gathering tt sections + ## + # Prepares the visitor for gathering tt sections - def start_accepting - @res = [] + def start_accepting + @res = [] - @list_type = [] - end + @list_type = [] + end + end + end end diff --git a/lib/rdoc/markup/verbatim.rb b/lib/rdoc/markup/verbatim.rb index 3f4b9d48cd..10f7fbb28c 100644 --- a/lib/rdoc/markup/verbatim.rb +++ b/lib/rdoc/markup/verbatim.rb @@ -1,83 +1,87 @@ # frozen_string_literal: true -## -# A section of verbatim text +module RDoc + class Markup + ## + # A section of verbatim text -class RDoc::Markup::Verbatim < RDoc::Markup::Raw + class Verbatim < ::RDoc::Markup::Raw - ## - # Format of this verbatim section + ## + # Format of this verbatim section - attr_accessor :format + attr_accessor :format - def initialize(*parts) # :nodoc: - super + def initialize(*parts) # :nodoc: + super - @format = nil - end - - def ==(other) # :nodoc: - super and @format == other.format - end + @format = nil + end - ## - # Calls #accept_verbatim on +visitor+ + def ==(other) # :nodoc: + super and @format == other.format + end - def accept(visitor) - visitor.accept_verbatim self - end + ## + # Calls #accept_verbatim on +visitor+ - ## - # Collapses 3+ newlines into two newlines + def accept(visitor) + visitor.accept_verbatim self + end - def normalize - parts = [] + ## + # Collapses 3+ newlines into two newlines - newlines = 0 + def normalize + parts = [] - @parts.each do |part| - case part - when /^\s*\n/ - newlines += 1 - parts << part if newlines == 1 - else newlines = 0 - parts << part - end - end - parts.pop if parts.last =~ /\A\r?\n\z/ - - @parts = parts - end + @parts.each do |part| + case part + when /^\s*\n/ + newlines += 1 + parts << part if newlines == 1 + else + newlines = 0 + parts << part + end + end - def pretty_print(q) # :nodoc: - self.class.name =~ /.*::(\w{1,4})/i + parts.pop if parts.last =~ /\A\r?\n\z/ - q.group 2, "[#{$1.downcase}: ", ']' do - if @format - q.text "format: #{@format}" - q.breakable + @parts = parts end - q.seplist @parts do |part| - q.pp part + def pretty_print(q) # :nodoc: + self.class.name =~ /.*::(\w{1,4})/i + + q.group 2, "[#{$1.downcase}: ", ']' do + if @format + q.text "format: #{@format}" + q.breakable + end + + q.seplist @parts do |part| + q.pp part + end + end end - end - end - ## - # Is this verbatim section Ruby code? + ## + # Is this verbatim section Ruby code? - def ruby? - @format ||= nil # TODO for older ri data, switch the tree to marshal_dump - @format == :ruby || @format == :rb - end + def ruby? + @format ||= nil # TODO for older ri data, switch the tree to marshal_dump + @format == :ruby || @format == :rb + end - ## - # The text of the section + ## + # The text of the section - def text - @parts.join - end + def text + @parts.join + end + end + end end diff --git a/lib/rdoc/options.rb b/lib/rdoc/options.rb index fe8053920c..5bb139f5cb 100644 --- a/lib/rdoc/options.rb +++ b/lib/rdoc/options.rb @@ -2,730 +2,731 @@ require 'optparse' require 'pathname' -## -# RDoc::Options handles the parsing and storage of options -# -# == Saved Options -# -# You can save some options like the markup format in the -# .rdoc_options file in your gem. The easiest way to do this is: -# -# rdoc --markup tomdoc --write-options -# -# Which will automatically create the file and fill it with the options you -# specified. -# -# The following options will not be saved since they interfere with the user's -# preferences or with the normal operation of RDoc: -# -# * +--coverage-report+ -# * +--dry-run+ -# * +--encoding+ -# * +--force-update+ -# * +--format+ -# * +--pipe+ -# * +--quiet+ -# * +--template+ -# * +--verbose+ -# -# == Custom Options -# -# Generators can hook into RDoc::Options to add generator-specific command -# line options. -# -# When --format is encountered in ARGV, RDoc calls ::setup_options on -# the generator class to add extra options to the option parser. Options for -# custom generators must occur after --format. rdoc --help -# will list options for all installed generators. -# -# Example: -# -# class RDoc::Generator::Spellcheck -# RDoc::RDoc.add_generator self -# -# def self.setup_options rdoc_options -# op = rdoc_options.option_parser -# -# op.on('--spell-dictionary DICTIONARY', -# RDoc::Options::Path) do |dictionary| -# rdoc_options.spell_dictionary = dictionary -# end -# end -# end -# -# Of course, RDoc::Options does not respond to +spell_dictionary+ by default -# so you will need to add it: -# -# class RDoc::Options -# -# ## -# # The spell dictionary used by the spell-checking plugin. -# -# attr_accessor :spell_dictionary -# -# end -# -# == Option Validators -# -# OptionParser validators will validate and cast user input values. In -# addition to the validators that ship with OptionParser (String, Integer, -# Float, TrueClass, FalseClass, Array, Regexp, Date, Time, URI, etc.), -# RDoc::Options adds Path, PathArray and Template. - -class RDoc::Options - +module RDoc ## - # RDoc options ignored (or handled specially) by --write-options - - SPECIAL = %w[ - coverage_report - dry_run - encoding - files - force_output - force_update - generator - generator_name - generator_options - generators - locale - op_dir - page_dir - option_parser - pipe - rdoc_include - root - server_port - static_path - template - template_dir - update_output_dir - verbosity - write_options - ] + # RDoc::Options handles the parsing and storage of options + # + # == Saved Options + # + # You can save some options like the markup format in the + # .rdoc_options file in your gem. The easiest way to do this is: + # + # rdoc --markup tomdoc --write-options + # + # Which will automatically create the file and fill it with the options you + # specified. + # + # The following options will not be saved since they interfere with the user's + # preferences or with the normal operation of RDoc: + # + # * +--coverage-report+ + # * +--dry-run+ + # * +--encoding+ + # * +--force-update+ + # * +--format+ + # * +--pipe+ + # * +--quiet+ + # * +--template+ + # * +--verbose+ + # + # == Custom Options + # + # Generators can hook into RDoc::Options to add generator-specific command + # line options. + # + # When --format is encountered in ARGV, RDoc calls ::setup_options on + # the generator class to add extra options to the option parser. Options for + # custom generators must occur after --format. rdoc --help + # will list options for all installed generators. + # + # Example: + # + # class RDoc::Generator::Spellcheck + # RDoc::RDoc.add_generator self + # + # def self.setup_options rdoc_options + # op = rdoc_options.option_parser + # + # op.on('--spell-dictionary DICTIONARY', + # RDoc::Options::Path) do |dictionary| + # rdoc_options.spell_dictionary = dictionary + # end + # end + # end + # + # Of course, RDoc::Options does not respond to +spell_dictionary+ by default + # so you will need to add it: + # + # class RDoc::Options + # + # ## + # # The spell dictionary used by the spell-checking plugin. + # + # attr_accessor :spell_dictionary + # + # end + # + # == Option Validators + # + # OptionParser validators will validate and cast user input values. In + # addition to the validators that ship with OptionParser (String, Integer, + # Float, TrueClass, FalseClass, Array, Regexp, Date, Time, URI, etc.), + # RDoc::Options adds Path, PathArray and Template. + + class Options + + ## + # RDoc options ignored (or handled specially) by --write-options + + SPECIAL = %w[ + coverage_report + dry_run + encoding + files + force_output + force_update + generator + generator_name + generator_options + generators + locale + op_dir + page_dir + option_parser + pipe + rdoc_include + root + server_port + static_path + template + template_dir + update_output_dir + verbosity + write_options + ] - ## - # Option validator for OptionParser that matches a directory that exists on - # the filesystem. + ## + # Option validator for OptionParser that matches a directory that exists on + # the filesystem. - Directory = Object.new + Directory = Object.new - ## - # Option validator for OptionParser that matches a file or directory that - # exists on the filesystem. + ## + # Option validator for OptionParser that matches a file or directory that + # exists on the filesystem. - Path = Object.new + Path = Object.new - ## - # Option validator for OptionParser that matches a comma-separated list of - # files or directories that exist on the filesystem. + ## + # Option validator for OptionParser that matches a comma-separated list of + # files or directories that exist on the filesystem. - PathArray = Object.new + PathArray = Object.new - ## - # Option validator for OptionParser that matches a template directory for an - # installed generator that lives in - # "rdoc/generator/template/#{template_name}" + ## + # Option validator for OptionParser that matches a template directory for an + # installed generator that lives in + # "rdoc/generator/template/#{template_name}" - Template = Object.new + Template = Object.new - ## - # Character-set for HTML output. #encoding is preferred over #charset + ## + # Character-set for HTML output. #encoding is preferred over #charset - attr_accessor :charset + attr_accessor :charset - ## - # If true, RDoc will not write any files. + ## + # If true, RDoc will not write any files. - attr_accessor :dry_run + attr_accessor :dry_run - ## - # The output encoding. All input files will be transcoded to this encoding. - # - # The default encoding is UTF-8. This is set via --encoding. + ## + # The output encoding. All input files will be transcoded to this encoding. + # + # The default encoding is UTF-8. This is set via --encoding. - attr_accessor :encoding + attr_accessor :encoding - ## - # Files matching this pattern will be excluded + ## + # Files matching this pattern will be excluded - attr_writer :exclude + attr_writer :exclude - ## - # The list of files to be processed + ## + # The list of files to be processed - attr_accessor :files + attr_accessor :files - ## - # Create the output even if the output directory does not look - # like an rdoc output directory + ## + # Create the output even if the output directory does not look + # like an rdoc output directory - attr_accessor :force_output + attr_accessor :force_output - ## - # Scan newer sources than the flag file if true. + ## + # Scan newer sources than the flag file if true. - attr_accessor :force_update + attr_accessor :force_update - ## - # Formatter to mark up text with + ## + # Formatter to mark up text with - attr_accessor :formatter + attr_accessor :formatter - ## - # Description of the output generator (set with the --format option) + ## + # Description of the output generator (set with the --format option) - attr_accessor :generator + attr_accessor :generator - ## - # For #== + ## + # For #== - attr_reader :generator_name # :nodoc: + attr_reader :generator_name # :nodoc: - ## - # Loaded generator options. Used to prevent --help from loading the same - # options multiple times. + ## + # Loaded generator options. Used to prevent --help from loading the same + # options multiple times. - attr_accessor :generator_options + attr_accessor :generator_options - ## - # Old rdoc behavior: hyperlink all words that match a method name, - # even if not preceded by '#' or '::' + ## + # Old rdoc behavior: hyperlink all words that match a method name, + # even if not preceded by '#' or '::' - attr_accessor :hyperlink_all + attr_accessor :hyperlink_all - ## - # Include line numbers in the source code + ## + # Include line numbers in the source code - attr_accessor :line_numbers + attr_accessor :line_numbers - ## - # The output locale. + ## + # The output locale. - attr_accessor :locale + attr_accessor :locale - ## - # The directory where locale data live. + ## + # The directory where locale data live. - attr_accessor :locale_dir + attr_accessor :locale_dir - ## - # Name of the file, class or module to display in the initial index page (if - # not specified the first file we encounter is used) + ## + # Name of the file, class or module to display in the initial index page (if + # not specified the first file we encounter is used) - attr_accessor :main_page - - ## - # The markup format. - # One of: +rdoc+ (the default), +markdown+, +rd+, +tomdoc+. - # See {Markup Formats}[rdoc-ref:RDoc::Markup@Markup+Formats]. - attr_accessor :markup + attr_accessor :main_page - ## - # If true, only report on undocumented files + ## + # The markup format. + # One of: +rdoc+ (the default), +markdown+, +rd+, +tomdoc+. + # See {Markup Formats}[rdoc-ref:RDoc::Markup@Markup+Formats]. + attr_accessor :markup - attr_accessor :coverage_report + ## + # If true, only report on undocumented files - ## - # The name of the output directory + attr_accessor :coverage_report - attr_accessor :op_dir + ## + # The name of the output directory - ## - # The OptionParser for this instance + attr_accessor :op_dir - attr_accessor :option_parser + ## + # The OptionParser for this instance - ## - # Output heading decorations? - attr_accessor :output_decoration + attr_accessor :option_parser - ## - # Directory where guides, FAQ, and other pages not associated with a class - # live. You may leave this unset if these are at the root of your project. + ## + # Output heading decorations? + attr_accessor :output_decoration - attr_accessor :page_dir + ## + # Directory where guides, FAQ, and other pages not associated with a class + # live. You may leave this unset if these are at the root of your project. - ## - # Is RDoc in pipe mode? + attr_accessor :page_dir - attr_accessor :pipe + ## + # Is RDoc in pipe mode? - ## - # Array of directories to search for files to satisfy an :include: + attr_accessor :pipe - attr_accessor :rdoc_include + ## + # Array of directories to search for files to satisfy an :include: - ## - # Root of the source documentation will be generated for. Set this when - # building documentation outside the source directory. Defaults to the - # current directory. + attr_accessor :rdoc_include - attr_accessor :root + ## + # Root of the source documentation will be generated for. Set this when + # building documentation outside the source directory. Defaults to the + # current directory. - ## - # Include the '#' at the front of hyperlinked instance method names + attr_accessor :root - attr_accessor :show_hash + ## + # Include the '#' at the front of hyperlinked instance method names - ## - # Directory to copy static files from + attr_accessor :show_hash - attr_accessor :static_path + ## + # Directory to copy static files from - ## - # The number of columns in a tab + attr_accessor :static_path - attr_accessor :tab_width + ## + # The number of columns in a tab - ## - # Template to be used when generating output + attr_accessor :tab_width - attr_accessor :template + ## + # Template to be used when generating output - ## - # Directory the template lives in + attr_accessor :template - attr_accessor :template_dir + ## + # Directory the template lives in - ## - # Additional template stylesheets + attr_accessor :template_dir - attr_accessor :template_stylesheets + ## + # Additional template stylesheets - ## - # Documentation title + attr_accessor :template_stylesheets - attr_accessor :title + ## + # Documentation title - ## - # Should RDoc update the timestamps in the output dir? + attr_accessor :title - attr_accessor :update_output_dir + ## + # Should RDoc update the timestamps in the output dir? - ## - # Verbosity, zero means quiet + attr_accessor :update_output_dir - attr_accessor :verbosity + ## + # Verbosity, zero means quiet - ## - # Warn if rdoc-ref links can't be resolved - # Default is +true+ + attr_accessor :verbosity - attr_accessor :warn_missing_rdoc_ref + ## + # Warn if rdoc-ref links can't be resolved + # Default is +true+ - ## - # URL of web cvs frontend + attr_accessor :warn_missing_rdoc_ref - attr_accessor :webcvs + ## + # URL of web cvs frontend - ## - # Minimum visibility of a documented method. One of +:public+, +:protected+, - # +:private+ or +:nodoc+. - # - # The +:nodoc+ visibility ignores all directives related to visibility. The - # other visibilities may be overridden on a per-method basis with the :doc: - # directive. + attr_accessor :webcvs - attr_reader :visibility + ## + # Minimum visibility of a documented method. One of +:public+, +:protected+, + # +:private+ or +:nodoc+. + # + # The +:nodoc+ visibility ignores all directives related to visibility. The + # other visibilities may be overridden on a per-method basis with the :doc: + # directive. - ## - # When set to a port number, starts a live-reloading server instead of - # writing files. Defaults to +false+ (no server). Set via - # --server[=PORT]. + attr_reader :visibility - attr_reader :server_port + ## + # When set to a port number, starts a live-reloading server instead of + # writing files. Defaults to +false+ (no server). Set via + # --server[=PORT]. - ## - # Indicates if files of test suites should be skipped - attr_accessor :skip_tests + attr_reader :server_port - ## - # Embed mixin methods, attributes, and constants into class documentation. Set via - # +--[no-]embed-mixins+ (Default is +false+.) - attr_accessor :embed_mixins + ## + # Indicates if files of test suites should be skipped + attr_accessor :skip_tests - ## - # Exclude the default patterns as well if true. - attr_reader :apply_default_exclude + ## + # Embed mixin methods, attributes, and constants into class documentation. Set via + # +--[no-]embed-mixins+ (Default is +false+.) + attr_accessor :embed_mixins - ## - # Words to be ignored in autolink cross-references - attr_accessor :autolink_excluded_words + ## + # Exclude the default patterns as well if true. + attr_reader :apply_default_exclude - ## - # The prefix to use for class and module page paths + ## + # Words to be ignored in autolink cross-references + attr_accessor :autolink_excluded_words - attr_accessor :class_module_path_prefix + ## + # The prefix to use for class and module page paths - ## - # The prefix to use for file page paths + attr_accessor :class_module_path_prefix - attr_accessor :file_path_prefix + ## + # The prefix to use for file page paths - ## - # The preferred root URL for the documentation + attr_accessor :file_path_prefix - attr_accessor :canonical_root + ## + # The preferred root URL for the documentation - ## - # Custom footer content configuration for themes that support it. - # Currently only supported by the Aliki theme. - # - # A hash where keys are column titles and values are hashes of link text => URL pairs. - # Each column will be displayed in the upper footer section. - # - # Example: - # { - # "DOCUMENTATION" => {"Home" => "/index.html", "Guide" => "/guide.html"}, - # "RESOURCES" => {"RDoc" => "https://ruby.github.io/rdoc/", "GitHub" => "https://github.com/ruby/rdoc"} - # } + attr_accessor :canonical_root - attr_accessor :footer_content + ## + # Custom footer content configuration for themes that support it. + # Currently only supported by the Aliki theme. + # + # A hash where keys are column titles and values are hashes of link text => URL pairs. + # Each column will be displayed in the upper footer section. + # + # Example: + # { + # "DOCUMENTATION" => {"Home" => "/index.html", "Guide" => "/guide.html"}, + # "RESOURCES" => {"RDoc" => "https://ruby.github.io/rdoc/", "GitHub" => "https://github.com/ruby/rdoc"} + # } - def initialize(loaded_options = nil) # :nodoc: - init_ivars - override loaded_options if loaded_options - end + attr_accessor :footer_content - DEFAULT_EXCLUDE = %w[ - ~\z \.orig\z \.rej\z \.bak\z - \.gemspec\z - ] - - def init_ivars # :nodoc: - @autolink_excluded_words = [] - @dry_run = false - @embed_mixins = false - @exclude = [] - @files = nil - @force_output = false - @force_update = true - @generator_name = "aliki" - @generators = RDoc::RDoc::GENERATORS - @generator_options = [] - @hyperlink_all = false - @line_numbers = false - @locale = nil - @locale_name = nil - @locale_dir = 'locale' - @main_page = nil - @markup = 'rdoc' - @coverage_report = false - @op_dir = nil - @page_dir = nil - @pipe = false - @output_decoration = true - @rdoc_include = [] - @root = Pathname(Dir.pwd) - @server_port = false - @show_hash = false - @static_path = [] - @tab_width = 8 - @template = nil - @template_dir = nil - @template_stylesheets = [] - @title = nil - @update_output_dir = true - @verbosity = 1 - @visibility = :protected - @warn_missing_rdoc_ref = true - @webcvs = nil - @write_options = false - @encoding = Encoding::UTF_8 - @charset = @encoding.name - @skip_tests = true - @apply_default_exclude = true - @class_module_path_prefix = nil - @file_path_prefix = nil - @canonical_root = nil - @footer_content = nil - end + def initialize(loaded_options = nil) # :nodoc: + init_ivars + override loaded_options if loaded_options + end - def init_with(map) # :nodoc: - init_ivars - - encoding = map['encoding'] - @encoding = encoding ? Encoding.find(encoding) : encoding - - @charset = map['charset'] - @embed_mixins = map['embed_mixins'] - @exclude = map['exclude'] - @generator_name = map['generator_name'] - @hyperlink_all = map['hyperlink_all'] - @line_numbers = map['line_numbers'] - @locale_name = map['locale_name'] - @locale_dir = map['locale_dir'] - @main_page = map['main_page'] - @markup = map['markup'] - @op_dir = map['op_dir'] - @show_hash = map['show_hash'] - @tab_width = map['tab_width'] - @template_dir = map['template_dir'] - @title = map['title'] - @visibility = map['visibility'] - @webcvs = map['webcvs'] - - @apply_default_exclude = map['apply_default_exclude'] - @autolink_excluded_words = map['autolink_excluded_words'] - @footer_content = map['footer_content'] - - @rdoc_include = sanitize_path map['rdoc_include'] - @static_path = sanitize_path map['static_path'] - end + DEFAULT_EXCLUDE = %w[ + ~\z \.orig\z \.rej\z \.bak\z + \.gemspec\z + ] + + def init_ivars # :nodoc: + @autolink_excluded_words = [] + @dry_run = false + @embed_mixins = false + @exclude = [] + @files = nil + @force_output = false + @force_update = true + @generator_name = "aliki" + @generators = ::RDoc::RDoc::GENERATORS + @generator_options = [] + @hyperlink_all = false + @line_numbers = false + @locale = nil + @locale_name = nil + @locale_dir = 'locale' + @main_page = nil + @markup = 'rdoc' + @coverage_report = false + @op_dir = nil + @page_dir = nil + @pipe = false + @output_decoration = true + @rdoc_include = [] + @root = Pathname(Dir.pwd) + @server_port = false + @show_hash = false + @static_path = [] + @tab_width = 8 + @template = nil + @template_dir = nil + @template_stylesheets = [] + @title = nil + @update_output_dir = true + @verbosity = 1 + @visibility = :protected + @warn_missing_rdoc_ref = true + @webcvs = nil + @write_options = false + @encoding = ::Encoding::UTF_8 + @charset = @encoding.name + @skip_tests = true + @apply_default_exclude = true + @class_module_path_prefix = nil + @file_path_prefix = nil + @canonical_root = nil + @footer_content = nil + end - def yaml_initialize(tag, map) # :nodoc: - init_with map - end + def init_with(map) # :nodoc: + init_ivars - def override(map) # :nodoc: - if map.has_key?('encoding') encoding = map['encoding'] - @encoding = encoding ? Encoding.find(encoding) : encoding - end + @encoding = encoding ? ::Encoding.find(encoding) : encoding + + @charset = map['charset'] + @embed_mixins = map['embed_mixins'] + @exclude = map['exclude'] + @generator_name = map['generator_name'] + @hyperlink_all = map['hyperlink_all'] + @line_numbers = map['line_numbers'] + @locale_name = map['locale_name'] + @locale_dir = map['locale_dir'] + @main_page = map['main_page'] + @markup = map['markup'] + @op_dir = map['op_dir'] + @show_hash = map['show_hash'] + @tab_width = map['tab_width'] + @template_dir = map['template_dir'] + @title = map['title'] + @visibility = map['visibility'] + @webcvs = map['webcvs'] + + @apply_default_exclude = map['apply_default_exclude'] + @autolink_excluded_words = map['autolink_excluded_words'] + @footer_content = map['footer_content'] - @charset = map['charset'] if map.has_key?('charset') - @embed_mixins = map['embed_mixins'] if map.has_key?('embed_mixins') - @exclude = map['exclude'] if map.has_key?('exclude') - @generator_name = map['generator_name'] if map.has_key?('generator_name') - @hyperlink_all = map['hyperlink_all'] if map.has_key?('hyperlink_all') - @line_numbers = map['line_numbers'] if map.has_key?('line_numbers') - @locale_name = map['locale_name'] if map.has_key?('locale_name') - @locale_dir = map['locale_dir'] if map.has_key?('locale_dir') - @main_page = map['main_page'] if map.has_key?('main_page') - @markup = map['markup'] if map.has_key?('markup') - @op_dir = map['op_dir'] if map.has_key?('op_dir') - @page_dir = map['page_dir'] if map.has_key?('page_dir') - @show_hash = map['show_hash'] if map.has_key?('show_hash') - @tab_width = map['tab_width'] if map.has_key?('tab_width') - @template_dir = map['template_dir'] if map.has_key?('template_dir') - @title = map['title'] if map.has_key?('title') - @visibility = map['visibility'] if map.has_key?('visibility') - @webcvs = map['webcvs'] if map.has_key?('webcvs') - @autolink_excluded_words = map['autolink_excluded_words'] if map.has_key?('autolink_excluded_words') - @apply_default_exclude = map['apply_default_exclude'] if map.has_key?('apply_default_exclude') - @canonical_root = map['canonical_root'] if map.has_key?('canonical_root') - @footer_content = map['footer_content'] if map.has_key?('footer_content') - - @warn_missing_rdoc_ref = map['warn_missing_rdoc_ref'] if map.has_key?('warn_missing_rdoc_ref') - - if map.has_key?('rdoc_include') @rdoc_include = sanitize_path map['rdoc_include'] - end - if map.has_key?('static_path') @static_path = sanitize_path map['static_path'] end - end - def ==(other) # :nodoc: - self.class === other and - @encoding == other.encoding and - @embed_mixins == other.embed_mixins and - @generator_name == other.generator_name and - @hyperlink_all == other.hyperlink_all and - @line_numbers == other.line_numbers and - @locale == other.locale and - @locale_dir == other.locale_dir and - @main_page == other.main_page and - @markup == other.markup and - @op_dir == other.op_dir and - @rdoc_include == other.rdoc_include and - @show_hash == other.show_hash and - @static_path == other.static_path and - @tab_width == other.tab_width and - @template == other.template and - @title == other.title and - @visibility == other.visibility and - @webcvs == other.webcvs and - @apply_default_exclude == other.apply_default_exclude and - @autolink_excluded_words == other.autolink_excluded_words - end + def yaml_initialize(tag, map) # :nodoc: + init_with map + end - ## - # Check that the files on the command line exist + def override(map) # :nodoc: + if map.has_key?('encoding') + encoding = map['encoding'] + @encoding = encoding ? ::Encoding.find(encoding) : encoding + end + + @charset = map['charset'] if map.has_key?('charset') + @embed_mixins = map['embed_mixins'] if map.has_key?('embed_mixins') + @exclude = map['exclude'] if map.has_key?('exclude') + @generator_name = map['generator_name'] if map.has_key?('generator_name') + @hyperlink_all = map['hyperlink_all'] if map.has_key?('hyperlink_all') + @line_numbers = map['line_numbers'] if map.has_key?('line_numbers') + @locale_name = map['locale_name'] if map.has_key?('locale_name') + @locale_dir = map['locale_dir'] if map.has_key?('locale_dir') + @main_page = map['main_page'] if map.has_key?('main_page') + @markup = map['markup'] if map.has_key?('markup') + @op_dir = map['op_dir'] if map.has_key?('op_dir') + @page_dir = map['page_dir'] if map.has_key?('page_dir') + @show_hash = map['show_hash'] if map.has_key?('show_hash') + @tab_width = map['tab_width'] if map.has_key?('tab_width') + @template_dir = map['template_dir'] if map.has_key?('template_dir') + @title = map['title'] if map.has_key?('title') + @visibility = map['visibility'] if map.has_key?('visibility') + @webcvs = map['webcvs'] if map.has_key?('webcvs') + @autolink_excluded_words = map['autolink_excluded_words'] if map.has_key?('autolink_excluded_words') + @apply_default_exclude = map['apply_default_exclude'] if map.has_key?('apply_default_exclude') + @canonical_root = map['canonical_root'] if map.has_key?('canonical_root') + @footer_content = map['footer_content'] if map.has_key?('footer_content') + + @warn_missing_rdoc_ref = map['warn_missing_rdoc_ref'] if map.has_key?('warn_missing_rdoc_ref') + + if map.has_key?('rdoc_include') + @rdoc_include = sanitize_path map['rdoc_include'] + end + if map.has_key?('static_path') + @static_path = sanitize_path map['static_path'] + end + end + + def ==(other) # :nodoc: + self.class === other and + @encoding == other.encoding and + @embed_mixins == other.embed_mixins and + @generator_name == other.generator_name and + @hyperlink_all == other.hyperlink_all and + @line_numbers == other.line_numbers and + @locale == other.locale and + @locale_dir == other.locale_dir and + @main_page == other.main_page and + @markup == other.markup and + @op_dir == other.op_dir and + @rdoc_include == other.rdoc_include and + @show_hash == other.show_hash and + @static_path == other.static_path and + @tab_width == other.tab_width and + @template == other.template and + @title == other.title and + @visibility == other.visibility and + @webcvs == other.webcvs and + @apply_default_exclude == other.apply_default_exclude and + @autolink_excluded_words == other.autolink_excluded_words + end + + ## + # Check that the files on the command line exist - def check_files - @files.delete_if do |file| - if File.exist? file - if File.readable? file - false + def check_files + @files.delete_if do |file| + if File.exist? file + if File.readable? file + false + else + warn "file '#{file}' not readable" + + true + end else - warn "file '#{file}' not readable" + warn "file '#{file}' not found" true end - else - warn "file '#{file}' not found" - - true end end - end - ## - # Ensure only one generator is loaded + ## + # Ensure only one generator is loaded - def check_generator - if @generator - raise OptionParser::InvalidOption, - "generator already set to #{@generator_name}" + def check_generator + if @generator + raise OptionParser::InvalidOption, + "generator already set to #{@generator_name}" + end end - end - ## - # Set the title, but only if not already set. Used to set the title - # from a source file, so that a title set from the command line - # will have the priority. + ## + # Set the title, but only if not already set. Used to set the title + # from a source file, so that a title set from the command line + # will have the priority. - def default_title=(string) - @title ||= string - end + def default_title=(string) + @title ||= string + end - ## - # For dumping YAML + ## + # For dumping YAML - def to_yaml(*options) # :nodoc: - encoding = @encoding ? @encoding.name : nil + def to_yaml(*options) # :nodoc: + encoding = @encoding ? @encoding.name : nil - yaml = {} - yaml['encoding'] = encoding - yaml['static_path'] = sanitize_path(@static_path) - yaml['rdoc_include'] = sanitize_path(@rdoc_include) - yaml['page_dir'] = (sanitize_path([@page_dir]).first if @page_dir) + yaml = {} + yaml['encoding'] = encoding + yaml['static_path'] = sanitize_path(@static_path) + yaml['rdoc_include'] = sanitize_path(@rdoc_include) + yaml['page_dir'] = (sanitize_path([@page_dir]).first if @page_dir) - ivars = instance_variables.map { |ivar| ivar.to_s[1..-1] } - ivars -= SPECIAL + ivars = instance_variables.map { |ivar| ivar.to_s[1..-1] } + ivars -= SPECIAL - ivars.sort.each do |ivar| - yaml[ivar] = instance_variable_get("@#{ivar}") - end + ivars.sort.each do |ivar| + yaml[ivar] = instance_variable_get("@#{ivar}") + end - if yaml.respond_to?(:to_yaml) - yaml.to_yaml - else - RDoc.yaml_serializer.dump(yaml) + if yaml.respond_to?(:to_yaml) + yaml.to_yaml + else + ::RDoc.yaml_serializer.dump(yaml) + end end - end - ## - # Create a regexp for #exclude - - def exclude - if @exclude.nil? or Regexp === @exclude - # done, #finish is being re-run - @exclude - elsif !@apply_default_exclude and @exclude.empty? - nil - else - exclude = @exclude - exclude |= DEFAULT_EXCLUDE if @apply_default_exclude - Regexp.new(exclude.join("|")) + ## + # Create a regexp for #exclude + + def exclude + if @exclude.nil? or Regexp === @exclude + # done, #finish is being re-run + @exclude + elsif !@apply_default_exclude and @exclude.empty? + nil + else + exclude = @exclude + exclude |= DEFAULT_EXCLUDE if @apply_default_exclude + Regexp.new(exclude.join("|")) + end end - end - ## - # Completes any unfinished option setup business such as filtering for - # existent files, creating a regexp for #exclude and setting a default - # #template. + ## + # Completes any unfinished option setup business such as filtering for + # existent files, creating a regexp for #exclude and setting a default + # #template. - def finish - if @write_options - write_options - exit - end + def finish + if @write_options + write_options + exit + end - @op_dir ||= 'doc' + @op_dir ||= 'doc' - root = @root.to_s - if @rdoc_include.empty? || !@rdoc_include.include?(root) - @rdoc_include << root - end + root = @root.to_s + if @rdoc_include.empty? || !@rdoc_include.include?(root) + @rdoc_include << root + end - @exclude = self.exclude + @exclude = self.exclude - finish_page_dir + finish_page_dir - check_files + check_files - # If no template was specified, use the default template for the output - # formatter + # If no template was specified, use the default template for the output + # formatter - unless @template - @template = @generator_name - @template_dir = template_dir_for @template - end + unless @template + @template = @generator_name + @template_dir = template_dir_for @template + end - if @locale_name - @locale = RDoc::I18n::Locale[@locale_name] - @locale.load(@locale_dir) - else - @locale = nil + if @locale_name + @locale = ::RDoc::I18n::Locale[@locale_name] + @locale.load(@locale_dir) + else + @locale = nil + end + + self end - self - end + ## + # Fixes the page_dir to be relative to the root_dir and adds the page_dir to + # the files list. - ## - # Fixes the page_dir to be relative to the root_dir and adds the page_dir to - # the files list. + def finish_page_dir + return unless @page_dir - def finish_page_dir - return unless @page_dir + @files << @page_dir - @files << @page_dir + page_dir = Pathname(@page_dir) + begin + page_dir = page_dir.expand_path.relative_path_from @root + rescue ArgumentError + # On Windows, sometimes crosses different drive letters. + page_dir = page_dir.expand_path + end - page_dir = Pathname(@page_dir) - begin - page_dir = page_dir.expand_path.relative_path_from @root - rescue ArgumentError - # On Windows, sometimes crosses different drive letters. - page_dir = page_dir.expand_path + @page_dir = page_dir end - @page_dir = page_dir - end - - ## - # Returns a properly-space list of generators and their descriptions. + ## + # Returns a properly-space list of generators and their descriptions. - def generator_descriptions - lengths = [] + def generator_descriptions + lengths = [] - generators = RDoc::RDoc::GENERATORS.map do |name, generator| - lengths << name.length + generators = ::RDoc::RDoc::GENERATORS.map do |name, generator| + lengths << name.length - description = generator::DESCRIPTION if - generator.const_defined? :DESCRIPTION + description = generator::DESCRIPTION if + generator.const_defined? :DESCRIPTION - [name, description] - end + [name, description] + end - longest = lengths.max + longest = lengths.max - generators.sort.map do |name, description| - if description - " %-*s - %s" % [longest, name, description] - else - " #{name}" - end - end.join "\n" - end + generators.sort.map do |name, description| + if description + " %-*s - %s" % [longest, name, description] + else + " #{name}" + end + end.join "\n" + end - ## - # Parses command line options. + ## + # Parses command line options. - def parse(argv) - ignore_invalid = true + def parse(argv) + ignore_invalid = true - argv.insert(0, *ENV['RDOCOPT'].split) if ENV['RDOCOPT'] + argv.insert(0, *ENV['RDOCOPT'].split) if ENV['RDOCOPT'] - opts = OptionParser.new do |opt| - @option_parser = opt - opt.program_name = File.basename $0 - opt.version = RDoc::VERSION - opt.release = nil - opt.summary_indent = ' ' * 4 - opt.banner = <<-EOF + opts = OptionParser.new do |opt| + @option_parser = opt + opt.program_name = File.basename $0 + opt.version = ::RDoc::VERSION + opt.release = nil + opt.summary_indent = ' ' * 4 + opt.banner = <<-EOF Usage: #{opt.program_name} [options] [names...] Files are parsed, and the information they contain collected, before any @@ -753,660 +754,661 @@ def parse(argv) EOF - parsers = Hash.new { |h, parser| h[parser] = [] } - - RDoc::Parser.parsers.each do |regexp, parser| - parsers[parser.name.sub('RDoc::Parser::', '')] << regexp.source - end - - parsers.sort.each do |parser, regexp| - opt.banner += " - #{parser}: #{regexp.join ', '}\n" - end - opt.banner += " - TomDoc: Only in ruby files\n" - - opt.accept Template do |template| - template_dir = template_dir_for template + parsers = Hash.new { |h, parser| h[parser] = [] } - unless template_dir - $stderr.puts "could not find template #{template}" - nil - else - [template, template_dir] + ::RDoc::Parser.parsers.each do |regexp, parser| + parsers[parser.name.sub('RDoc::Parser::', '')] << regexp.source end - end - - opt.accept Directory do |directory| - directory = File.expand_path directory - raise OptionParser::InvalidArgument unless File.directory? directory + parsers.sort.each do |parser, regexp| + opt.banner += " - #{parser}: #{regexp.join ', '}\n" + end + opt.banner += " - TomDoc: Only in ruby files\n" - directory - end + opt.accept Template do |template| + template_dir = template_dir_for template - opt.accept Path do |path| - path = File.expand_path path + unless template_dir + $stderr.puts "could not find template #{template}" + nil + else + [template, template_dir] + end + end - raise OptionParser::InvalidArgument unless File.exist? path + opt.accept Directory do |directory| + directory = File.expand_path directory - path - end + raise OptionParser::InvalidArgument unless File.directory? directory - opt.accept PathArray do |paths,| - paths = if paths - paths.split(',').map { |d| d unless d.empty? } - end + directory + end - paths.map do |path| + opt.accept Path do |path| path = File.expand_path path raise OptionParser::InvalidArgument unless File.exist? path path end - end - opt.separator nil - opt.separator "Parsing options:" - opt.separator nil + opt.accept PathArray do |paths,| + paths = if paths + paths.split(',').map { |d| d unless d.empty? } + end - opt.on("--encoding=ENCODING", "-e", Encoding.list.map { |e| e.name }, - "Specifies the output encoding. All files", - "read will be converted to this encoding.", - "The default encoding is UTF-8.", - "--encoding is preferred over --charset") do |value| - @encoding = Encoding.find value - @charset = @encoding.name # may not be valid value - end + paths.map do |path| + path = File.expand_path path - opt.separator nil + raise OptionParser::InvalidArgument unless File.exist? path - opt.on("--locale=NAME", - "Specifies the output locale.") do |value| - @locale_name = value - end + path + end + end - opt.on("--locale-data-dir=DIR", - "Specifies the directory where locale data live.") do |value| - @locale_dir = value - end + opt.separator nil + opt.separator "Parsing options:" + opt.separator nil - opt.separator nil + opt.on("--encoding=ENCODING", "-e", ::Encoding.list.map { |e| e.name }, + "Specifies the output encoding. All files", + "read will be converted to this encoding.", + "The default encoding is UTF-8.", + "--encoding is preferred over --charset") do |value| + @encoding = ::Encoding.find value + @charset = @encoding.name # may not be valid value + end - opt.on("--all", "-a", - "Synonym for --visibility=private.") do |value| - @visibility = :private - end + opt.separator nil - opt.separator nil + opt.on("--locale=NAME", + "Specifies the output locale.") do |value| + @locale_name = value + end - opt.on("--exclude=PATTERN", "-x", Regexp, - "Do not process files or directories", - "matching PATTERN.") do |value| - @exclude << value - end + opt.on("--locale-data-dir=DIR", + "Specifies the directory where locale data live.") do |value| + @locale_dir = value + end - opt.on("--[no-]apply-default-exclude", - "Use default PATTERN to exclude.") do |value| - @apply_default_exclude = value - end + opt.separator nil + + opt.on("--all", "-a", + "Synonym for --visibility=private.") do |value| + @visibility = :private + end - opt.separator nil + opt.separator nil - opt.on("--no-skipping-tests", nil, - "Don't skip generating documentation for test and spec files") do |value| - @skip_tests = false - end + opt.on("--exclude=PATTERN", "-x", Regexp, + "Do not process files or directories", + "matching PATTERN.") do |value| + @exclude << value + end - opt.separator nil + opt.on("--[no-]apply-default-exclude", + "Use default PATTERN to exclude.") do |value| + @apply_default_exclude = value + end - opt.on("--extension=NEW=OLD", "-E", - "Treat files ending with .new as if they", - "ended with .old. Using '-E cgi=rb' will", - "cause xxx.cgi to be parsed as a Ruby file.") do |value| - new, old = value.split(/=/, 2) + opt.separator nil - unless new and old - raise OptionParser::InvalidArgument, "Invalid parameter to '-E'" + opt.on("--no-skipping-tests", nil, + "Don't skip generating documentation for test and spec files") do |value| + @skip_tests = false end - unless RDoc::Parser.alias_extension old, new - raise OptionParser::InvalidArgument, "Unknown extension .#{old} to -E" + opt.separator nil + + opt.on("--extension=NEW=OLD", "-E", + "Treat files ending with .new as if they", + "ended with .old. Using '-E cgi=rb' will", + "cause xxx.cgi to be parsed as a Ruby file.") do |value| + new, old = value.split(/=/, 2) + + unless new and old + raise OptionParser::InvalidArgument, "Invalid parameter to '-E'" + end + + unless ::RDoc::Parser.alias_extension old, new + raise OptionParser::InvalidArgument, "Unknown extension .#{old} to -E" + end end - end - opt.separator nil + opt.separator nil - opt.on("--[no-]force-update", "-U", - "Forces rdoc to scan all sources even if", - "no files are newer than the flag file.") do |value| - @force_update = value - end + opt.on("--[no-]force-update", "-U", + "Forces rdoc to scan all sources even if", + "no files are newer than the flag file.") do |value| + @force_update = value + end - opt.separator nil + opt.separator nil - opt.on("--pipe", "-p", - "Convert RDoc on stdin to HTML") do - @pipe = true - end + opt.on("--pipe", "-p", + "Convert RDoc on stdin to HTML") do + @pipe = true + end - opt.separator nil + opt.separator nil - opt.on("--tab-width=WIDTH", "-w", Integer, - "Set the width of tab characters.") do |value| - raise OptionParser::InvalidArgument, - "#{value} is an invalid tab width" if value <= 0 - @tab_width = value - end + opt.on("--tab-width=WIDTH", "-w", Integer, + "Set the width of tab characters.") do |value| + raise OptionParser::InvalidArgument, + "#{value} is an invalid tab width" if value <= 0 + @tab_width = value + end - opt.separator nil + opt.separator nil - opt.on("--visibility=VISIBILITY", RDoc::VISIBILITIES + [:nodoc], - "Minimum visibility to document a method.", - "One of 'public', 'protected' (the default),", - "'private' or 'nodoc' (show everything)") do |value| - @visibility = value - end + opt.on("--visibility=VISIBILITY", ::RDoc::VISIBILITIES + [:nodoc], + "Minimum visibility to document a method.", + "One of 'public', 'protected' (the default),", + "'private' or 'nodoc' (show everything)") do |value| + @visibility = value + end - opt.separator nil + opt.separator nil - opt.on("--[no-]embed-mixins", - "Embed mixin methods, attributes, and constants", - "into class documentation. (default false)") do |value| - @embed_mixins = value - end + opt.on("--[no-]embed-mixins", + "Embed mixin methods, attributes, and constants", + "into class documentation. (default false)") do |value| + @embed_mixins = value + end - opt.separator nil + opt.separator nil - markup_formats = RDoc::Text::MARKUP_FORMAT.keys.sort + markup_formats = ::RDoc::Text::MARKUP_FORMAT.keys.sort - opt.on("--markup=MARKUP", markup_formats, - "The markup format for the named files.", - "The default is rdoc. Valid values are:", - markup_formats.join(', ')) do |value| - @markup = value - end + opt.on("--markup=MARKUP", markup_formats, + "The markup format for the named files.", + "The default is rdoc. Valid values are:", + markup_formats.join(', ')) do |value| + @markup = value + end - opt.separator nil + opt.separator nil - opt.on("--root=ROOT", Directory, - "Root of the source tree documentation", - "will be generated for. Set this when", - "building documentation outside the", - "source directory. Default is the", - "current directory.") do |root| - @root = Pathname(root) - end + opt.on("--root=ROOT", Directory, + "Root of the source tree documentation", + "will be generated for. Set this when", + "building documentation outside the", + "source directory. Default is the", + "current directory.") do |root| + @root = Pathname(root) + end - opt.separator nil + opt.separator nil - opt.on("--page-dir=DIR", Directory, - "Directory where guides, your FAQ or", - "other pages not associated with a class", - "live. Set this when you don't store", - "such files at your project root.", - "NOTE: Do not use the same file name in", - "the page dir and the root of your project") do |page_dir| - @page_dir = page_dir - end + opt.on("--page-dir=DIR", Directory, + "Directory where guides, your FAQ or", + "other pages not associated with a class", + "live. Set this when you don't store", + "such files at your project root.", + "NOTE: Do not use the same file name in", + "the page dir and the root of your project") do |page_dir| + @page_dir = page_dir + end - opt.separator nil - opt.separator "Common generator options:" - opt.separator nil + opt.separator nil + opt.separator "Common generator options:" + opt.separator nil - opt.on("--force-output", "-O", - "Forces rdoc to write the output files,", - "even if the output directory exists", - "and does not seem to have been created", - "by rdoc.") do |value| - @force_output = value - end + opt.on("--force-output", "-O", + "Forces rdoc to write the output files,", + "even if the output directory exists", + "and does not seem to have been created", + "by rdoc.") do |value| + @force_output = value + end - opt.separator nil + opt.separator nil - generator_text = @generators.keys.map { |name| " #{name}" }.sort + generator_text = @generators.keys.map { |name| " #{name}" }.sort - opt.on("-f", "--fmt=FORMAT", "--format=FORMAT", @generators.keys, - "Set the output formatter. One of:", *generator_text) do |value| - check_generator + opt.on("-f", "--fmt=FORMAT", "--format=FORMAT", @generators.keys, + "Set the output formatter. One of:", *generator_text) do |value| + check_generator - @generator_name = value.downcase - setup_generator - end + @generator_name = value.downcase + setup_generator + end - opt.separator nil + opt.separator nil - opt.on("--include=DIRECTORIES", "-i", PathArray, - "Set (or add to) the list of directories to", - "be searched when satisfying :include:", - "requests. Can be used more than once.") do |value| - @rdoc_include.concat value.map { |dir| dir.strip } - end + opt.on("--include=DIRECTORIES", "-i", PathArray, + "Set (or add to) the list of directories to", + "be searched when satisfying :include:", + "requests. Can be used more than once.") do |value| + @rdoc_include.concat value.map { |dir| dir.strip } + end - opt.separator nil + opt.separator nil - opt.on("--[no-]coverage-report=[LEVEL]", "--[no-]dcov", "-C", Integer, - "Prints a report on undocumented items.", - "Does not generate files.") do |value| - value = 0 if value.nil? # Integer converts -C to nil + opt.on("--[no-]coverage-report=[LEVEL]", "--[no-]dcov", "-C", Integer, + "Prints a report on undocumented items.", + "Does not generate files.") do |value| + value = 0 if value.nil? # Integer converts -C to nil - @coverage_report = value - @force_update = true if value - end + @coverage_report = value + @force_update = true if value + end - opt.separator nil + opt.separator nil - opt.on("--output=DIR", "--op", "-o", - "Set the output directory.") do |value| - @op_dir = value - end + opt.on("--output=DIR", "--op", "-o", + "Set the output directory.") do |value| + @op_dir = value + end - opt.separator nil - opt.separator 'HTML generator options:' - opt.separator nil + opt.separator nil + opt.separator 'HTML generator options:' + opt.separator nil - opt.on("--charset=CHARSET", "-c", - "Specifies the output HTML character-set.", - "Use --encoding instead of --charset if", - "available.") do |value| - @charset = value - end + opt.on("--charset=CHARSET", "-c", + "Specifies the output HTML character-set.", + "Use --encoding instead of --charset if", + "available.") do |value| + @charset = value + end - opt.separator nil + opt.separator nil - opt.on("--autolink-excluded-words=WORDS", Array, - "Words to be ignored in autolink cross-references") do |value| - @autolink_excluded_words.concat value - end + opt.on("--autolink-excluded-words=WORDS", Array, + "Words to be ignored in autolink cross-references") do |value| + @autolink_excluded_words.concat value + end - opt.separator nil + opt.separator nil - opt.on("--hyperlink-all", "-A", - "Generate hyperlinks for all words that", - "correspond to known methods, even if they", - "do not start with '#' or '::' (legacy", - "behavior).") do |value| - @hyperlink_all = value - end + opt.on("--hyperlink-all", "-A", + "Generate hyperlinks for all words that", + "correspond to known methods, even if they", + "do not start with '#' or '::' (legacy", + "behavior).") do |value| + @hyperlink_all = value + end - opt.separator nil + opt.separator nil - opt.on("--main=NAME", "-m", - "NAME will be the initial page displayed.") do |value| - @main_page = value - end + opt.on("--main=NAME", "-m", + "NAME will be the initial page displayed.") do |value| + @main_page = value + end - opt.separator nil + opt.separator nil - opt.on("--[no-]line-numbers", "-N", - "Include line numbers in the source code.", - "By default, only the number of the first", - "line is displayed, in a leading comment.") do |value| - @line_numbers = value - end + opt.on("--[no-]line-numbers", "-N", + "Include line numbers in the source code.", + "By default, only the number of the first", + "line is displayed, in a leading comment.") do |value| + @line_numbers = value + end - opt.separator nil + opt.separator nil - opt.on("--show-hash", "-H", - "A name of the form #name in a comment is a", - "possible hyperlink to an instance method", - "name. When displayed, the '#' is removed", - "unless this option is specified.") do |value| - @show_hash = value - end + opt.on("--show-hash", "-H", + "A name of the form #name in a comment is a", + "possible hyperlink to an instance method", + "name. When displayed, the '#' is removed", + "unless this option is specified.") do |value| + @show_hash = value + end - opt.separator nil + opt.separator nil - opt.on("--template=NAME", "-T", Template, - "Set the template used when generating", - "output. The default depends on the", - "formatter used.") do |(template, template_dir)| - @template = template - @template_dir = template_dir - end + opt.on("--template=NAME", "-T", Template, + "Set the template used when generating", + "output. The default depends on the", + "formatter used.") do |(template, template_dir)| + @template = template + @template_dir = template_dir + end - opt.separator nil + opt.separator nil - opt.on("--template-stylesheets=FILES", PathArray, - "Set (or add to) the list of files to", - "include with the html template.") do |value| - @template_stylesheets.concat value - end + opt.on("--template-stylesheets=FILES", PathArray, + "Set (or add to) the list of files to", + "include with the html template.") do |value| + @template_stylesheets.concat value + end - opt.separator nil + opt.separator nil - opt.on("--title=TITLE", "-t", - "Set TITLE as the title for HTML output.") do |value| - @title = value - end + opt.on("--title=TITLE", "-t", + "Set TITLE as the title for HTML output.") do |value| + @title = value + end - opt.separator nil + opt.separator nil - opt.on("--copy-files=PATH", Path, - "Specify a file or directory to copy static", - "files from.", - "If a file is given it will be copied into", - "the output dir. If a directory is given the", - "entire directory will be copied.", - "You can use this multiple times") do |value| - @static_path << value - end + opt.on("--copy-files=PATH", Path, + "Specify a file or directory to copy static", + "files from.", + "If a file is given it will be copied into", + "the output dir. If a directory is given the", + "entire directory will be copied.", + "You can use this multiple times") do |value| + @static_path << value + end - opt.separator nil + opt.separator nil - opt.on("--webcvs=URL", "-W", - "Specify a URL for linking to a web frontend", - "to CVS. If the URL contains a '\%s', the", - "name of the current file will be", - "substituted; if the URL doesn't contain a", - "'\%s', the filename will be appended to it.") do |value| - @webcvs = value - end + opt.on("--webcvs=URL", "-W", + "Specify a URL for linking to a web frontend", + "to CVS. If the URL contains a '\%s', the", + "name of the current file will be", + "substituted; if the URL doesn't contain a", + "'\%s', the filename will be appended to it.") do |value| + @webcvs = value + end - opt.separator nil - opt.separator "ri generator options:" - opt.separator nil - - opt.on("--ri", "-r", - "Generate output for use by `ri`. The files", - "are stored in the '.rdoc' directory under", - "your home directory unless overridden by a", - "subsequent --op parameter, so no special", - "privileges are needed.") do |value| - check_generator - - @generator_name = "ri" - @op_dir ||= RDoc::RI::Paths::HOMEDIR - setup_generator - end + opt.separator nil + opt.separator "ri generator options:" + opt.separator nil + + opt.on("--ri", "-r", + "Generate output for use by `ri`. The files", + "are stored in the '.rdoc' directory under", + "your home directory unless overridden by a", + "subsequent --op parameter, so no special", + "privileges are needed.") do |value| + check_generator + + @generator_name = "ri" + @op_dir ||= ::RDoc::RI::Paths::HOMEDIR + setup_generator + end - opt.separator nil + opt.separator nil - opt.on("--ri-site", "-R", - "Generate output for use by `ri`. The files", - "are stored in a site-wide directory,", - "making them accessible to others, so", - "special privileges are needed.") do |value| - check_generator + opt.on("--ri-site", "-R", + "Generate output for use by `ri`. The files", + "are stored in a site-wide directory,", + "making them accessible to others, so", + "special privileges are needed.") do |value| + check_generator - @generator_name = "ri" - @op_dir = RDoc::RI::Paths.site_dir - setup_generator - end + @generator_name = "ri" + @op_dir = ::RDoc::RI::Paths.site_dir + setup_generator + end - opt.separator nil - opt.separator "Generic options:" - opt.separator nil + opt.separator nil + opt.separator "Generic options:" + opt.separator nil - opt.on("--server[=PORT]", Integer, - "Start a web server to preview", - "documentation with live reload.", - "Defaults to port 4000.") do |port| - @server_port = port || 4000 - end + opt.on("--server[=PORT]", Integer, + "Start a web server to preview", + "documentation with live reload.", + "Defaults to port 4000.") do |port| + @server_port = port || 4000 + end - opt.separator nil + opt.separator nil - opt.on("--write-options", - "Write .rdoc_options to the current", - "directory with the given options. Not all", - "options will be used. See RDoc::Options", - "for details.") do |value| - @write_options = true - end + opt.on("--write-options", + "Write .rdoc_options to the current", + "directory with the given options. Not all", + "options will be used. See RDoc::Options", + "for details.") do |value| + @write_options = true + end - opt.separator nil + opt.separator nil - opt.on("--[no-]dry-run", - "Don't write any files") do |value| - @dry_run = value - end + opt.on("--[no-]dry-run", + "Don't write any files") do |value| + @dry_run = value + end - opt.separator nil + opt.separator nil - opt.on("-D", "--[no-]debug", - "Displays lots on internal stuff.") do |value| - $DEBUG_RDOC = value - end + opt.on("-D", "--[no-]debug", + "Displays lots on internal stuff.") do |value| + $DEBUG_RDOC = value + end - opt.separator nil + opt.separator nil - opt.on("--warn-missing-rdoc-ref", - "Warn if rdoc-ref links can't be resolved") do |value| - @warn_missing_rdoc_ref = value - end + opt.on("--warn-missing-rdoc-ref", + "Warn if rdoc-ref links can't be resolved") do |value| + @warn_missing_rdoc_ref = value + end - opt.separator nil + opt.separator nil - opt.on("--[no-]ignore-invalid", - "Ignore invalid options and continue", - "(default true).") do |value| - ignore_invalid = value - end + opt.on("--[no-]ignore-invalid", + "Ignore invalid options and continue", + "(default true).") do |value| + ignore_invalid = value + end - opt.separator nil + opt.separator nil - opt.on("--quiet", "-q", - "Don't show progress as we parse.") do |value| - @verbosity = 0 - end + opt.on("--quiet", "-q", + "Don't show progress as we parse.") do |value| + @verbosity = 0 + end - opt.separator nil + opt.separator nil - opt.on("--verbose", "-V", - "Display extra progress as RDoc parses") do |value| - @verbosity = 2 - end + opt.on("--verbose", "-V", + "Display extra progress as RDoc parses") do |value| + @verbosity = 2 + end - opt.separator nil + opt.separator nil - opt.on("--version", "-v", "print the version") do - puts opt.version - exit - end + opt.on("--version", "-v", "print the version") do + puts opt.version + exit + end - opt.separator nil + opt.separator nil - opt.on("--help", "-h", "Display this help") do - RDoc::RDoc::GENERATORS.each_key do |generator| - setup_generator generator + opt.on("--help", "-h", "Display this help") do + ::RDoc::RDoc::GENERATORS.each_key do |generator| + setup_generator generator + end + + puts opt.help + exit end - puts opt.help - exit + opt.separator nil end - opt.separator nil - end + invalid = [] - invalid = [] + begin + opts.parse! argv + rescue OptionParser::ParseError => e + if %w[--format --ri -r --ri-site -R].include? e.args.first + raise + else + invalid << e.args.join(' ') + end - begin - opts.parse! argv - rescue OptionParser::ParseError => e - if %w[--format --ri -r --ri-site -R].include? e.args.first - raise - else - invalid << e.args.join(' ') + retry end - retry - end + setup_generator unless @generator - setup_generator unless @generator - - if @pipe and not argv.empty? - @pipe = false - invalid << '-p (with files)' - end + if @pipe and not argv.empty? + @pipe = false + invalid << '-p (with files)' + end - unless invalid.empty? - invalid = "invalid options: #{invalid.join ', '}" + unless invalid.empty? + invalid = "invalid options: #{invalid.join ', '}" - if ignore_invalid - unless quiet + if ignore_invalid + unless quiet + $stderr.puts invalid + $stderr.puts '(invalid options are ignored)' + end + else + unless quiet + $stderr.puts opts + end $stderr.puts invalid - $stderr.puts '(invalid options are ignored)' - end - else - unless quiet - $stderr.puts opts + exit 1 end - $stderr.puts invalid - exit 1 end - end - - @files = argv.dup - self - end - - ## - # Don't display progress as we process the files + @files = argv.dup - def quiet - @verbosity.zero? - end + self + end - ## - # Set quietness to +bool+ + ## + # Don't display progress as we process the files - def quiet=(bool) - @verbosity = bool ? 0 : 1 - end + def quiet + @verbosity.zero? + end - ## - # Removes directories from +path+ that are outside the current directory + ## + # Set quietness to +bool+ - def sanitize_path(path) - require 'pathname' - dot = Pathname.new('.').expand_path + def quiet=(bool) + @verbosity = bool ? 0 : 1 + end - path.reject do |item| - path = Pathname.new(item).expand_path - is_reject = nil - relative = nil - begin - relative = path.relative_path_from(dot).to_s - rescue ArgumentError - # On Windows, sometimes crosses different drive letters. - is_reject = true - else - is_reject = relative.start_with? '..' + ## + # Removes directories from +path+ that are outside the current directory + + def sanitize_path(path) + require 'pathname' + dot = Pathname.new('.').expand_path + + path.reject do |item| + path = Pathname.new(item).expand_path + is_reject = nil + relative = nil + begin + relative = path.relative_path_from(dot).to_s + rescue ArgumentError + # On Windows, sometimes crosses different drive letters. + is_reject = true + else + is_reject = relative.start_with? '..' + end + is_reject end - is_reject end - end - ## - # Set up an output generator for the named +generator_name+. - # - # If the found generator responds to :setup_options it will be called with - # the options instance. This allows generators to add custom options or set - # default options. + ## + # Set up an output generator for the named +generator_name+. + # + # If the found generator responds to :setup_options it will be called with + # the options instance. This allows generators to add custom options or set + # default options. - def setup_generator(generator_name = @generator_name) - @generator = @generators[generator_name] + def setup_generator(generator_name = @generator_name) + @generator = @generators[generator_name] - unless @generator - raise OptionParser::InvalidArgument, - "Invalid output formatter #{generator_name}" - end + unless @generator + raise OptionParser::InvalidArgument, + "Invalid output formatter #{generator_name}" + end - return if @generator_options.include? @generator + return if @generator_options.include? @generator - @generator_name = generator_name - @generator_options << @generator + @generator_name = generator_name + @generator_options << @generator - if @generator.respond_to? :setup_options - @option_parser ||= OptionParser.new - @generator.setup_options self + if @generator.respond_to? :setup_options + @option_parser ||= OptionParser.new + @generator.setup_options self + end end - end - ## - # Finds the template dir for +template+ + ## + # Finds the template dir for +template+ - def template_dir_for(template) - template_path = File.join 'rdoc', 'generator', 'template', template + def template_dir_for(template) + template_path = File.join 'rdoc', 'generator', 'template', template - $LOAD_PATH.map do |path| - File.join File.expand_path(path), template_path - end.find do |dir| - File.directory? dir + $LOAD_PATH.map do |path| + File.join File.expand_path(path), template_path + end.find do |dir| + File.directory? dir + end end - end - # Sets the minimum visibility of a documented method. - # - # Accepts +:public+, +:protected+, +:private+, +:nodoc+, or +:all+. - # - # When +:all+ is passed, visibility is set to +:private+, similarly to - # RDOCOPT="--all", see #visibility for more information. - - def visibility=(visibility) - case visibility - when :all - @visibility = :private - else - @visibility = visibility + # Sets the minimum visibility of a documented method. + # + # Accepts +:public+, +:protected+, +:private+, +:nodoc+, or +:all+. + # + # When +:all+ is passed, visibility is set to +:private+, similarly to + # RDOCOPT="--all", see #visibility for more information. + + def visibility=(visibility) + case visibility + when :all + @visibility = :private + else + @visibility = visibility + end end - end - ## - # Displays a warning using Kernel#warn if we're being verbose + ## + # Displays a warning using Kernel#warn if we're being verbose - def warn(message) - super message if @verbosity > 1 - end + def warn(message) + super message if @verbosity > 1 + end - ## - # Writes the YAML file .rdoc_options to the current directory containing the - # parsed options. + ## + # Writes the YAML file .rdoc_options to the current directory containing the + # parsed options. - def write_options - RDoc.load_yaml + def write_options + ::RDoc.load_yaml - File.open '.rdoc_options', 'w' do |io| - io.set_encoding Encoding::UTF_8 + File.open '.rdoc_options', 'w' do |io| + io.set_encoding ::Encoding::UTF_8 - io.print to_yaml + io.print to_yaml + end end - end - ## - # Loads options from .rdoc_options if the file exists, otherwise creates a - # new RDoc::Options instance. + ## + # Loads options from .rdoc_options if the file exists, otherwise creates a + # new RDoc::Options instance. - def self.load_options - options_file = File.expand_path '.rdoc_options' - return RDoc::Options.new unless File.exist? options_file + def self.load_options + options_file = File.expand_path '.rdoc_options' + return ::RDoc::Options.new unless File.exist? options_file - RDoc.load_yaml + ::RDoc.load_yaml - content = File.read('.rdoc_options') + content = File.read('.rdoc_options') - if defined?(Psych) - begin - options = Psych.safe_load content, permitted_classes: [RDoc::Options, Symbol] - rescue Psych::SyntaxError - raise RDoc::Error, "#{options_file} is not a valid rdoc options file" + if defined?(Psych) + begin + options = Psych.safe_load content, permitted_classes: [::RDoc::Options, Symbol] + rescue Psych::SyntaxError + raise ::RDoc::Error, "#{options_file} is not a valid rdoc options file" + end + else + options = ::RDoc.yaml_serializer.load(content) end - else - options = RDoc.yaml_serializer.load(content) - end - return RDoc::Options.new unless options # Allow empty file. + return ::RDoc::Options.new unless options # Allow empty file. - raise RDoc::Error, "#{options_file} is not a valid rdoc options file" unless - RDoc::Options === options or Hash === options + raise ::RDoc::Error, "#{options_file} is not a valid rdoc options file" unless + ::RDoc::Options === options or Hash === options - if Hash === options - # Override the default values with the contents of YAML file. - options = RDoc::Options.new options + if Hash === options + # Override the default values with the contents of YAML file. + options = ::RDoc::Options.new options + end + + options end - options end - end diff --git a/lib/rdoc/parser.rb b/lib/rdoc/parser.rb index 5c6ce16ace..42a5b01f96 100644 --- a/lib/rdoc/parser.rb +++ b/lib/rdoc/parser.rb @@ -1,294 +1,296 @@ # -*- coding: us-ascii -*- # frozen_string_literal: true -## -# A parser is simple a class that subclasses RDoc::Parser and implements #scan -# to fill in an RDoc::TopLevel with parsed data. -# -# The initialize method takes an RDoc::TopLevel to fill with parsed content, -# the name of the file to be parsed, the content of the file, an RDoc::Options -# object and an RDoc::Stats object to inform the user of parsed items. The -# scan method is then called to parse the file and must return the -# RDoc::TopLevel object. By calling super these items will be set for you. -# -# In order to be used by RDoc the parser needs to register the file extensions -# it can parse. Use ::parse_files_matching to register extensions. -# -# require 'rdoc' -# -# class RDoc::Parser::Xyz < RDoc::Parser -# parse_files_matching /\.xyz$/ -# -# def initialize top_level, file_name, content, options, stats -# super -# -# # extra initialization if needed -# end -# -# def scan -# # parse file and fill in @top_level -# end -# end - -class RDoc::Parser - - @parsers = [] - - class << self +module RDoc + ## + # A parser is simple a class that subclasses RDoc::Parser and implements #scan + # to fill in an RDoc::TopLevel with parsed data. + # + # The initialize method takes an RDoc::TopLevel to fill with parsed content, + # the name of the file to be parsed, the content of the file, an RDoc::Options + # object and an RDoc::Stats object to inform the user of parsed items. The + # scan method is then called to parse the file and must return the + # RDoc::TopLevel object. By calling super these items will be set for you. + # + # In order to be used by RDoc the parser needs to register the file extensions + # it can parse. Use ::parse_files_matching to register extensions. + # + # require 'rdoc' + # + # class RDoc::Parser::Xyz < RDoc::Parser + # parse_files_matching /\.xyz$/ + # + # def initialize top_level, file_name, content, options, stats + # super + # + # # extra initialization if needed + # end + # + # def scan + # # parse file and fill in @top_level + # end + # end - ## - # An Array of arrays that maps file extension (or name) regular - # expressions to parser classes that will parse matching filenames. - # - # Use parse_files_matching to register a parser's file extensions. + class Parser - attr_reader :parsers + @parsers = [] - end + class << self - ## - # The name of the file being parsed + ## + # An Array of arrays that maps file extension (or name) regular + # expressions to parser classes that will parse matching filenames. + # + # Use parse_files_matching to register a parser's file extensions. - attr_reader :file_name + attr_reader :parsers - ## - # Alias an extension to another extension. After this call, files ending - # "new_ext" will be parsed using the same parser as "old_ext" + end - def self.alias_extension(old_ext, new_ext) - old_ext = old_ext.sub(/^\.(.*)/, '\1') - new_ext = new_ext.sub(/^\.(.*)/, '\1') + ## + # The name of the file being parsed - parser = can_parse_by_name "xxx.#{old_ext}" - return false unless parser + attr_reader :file_name - RDoc::Parser.parsers.unshift [/\.#{new_ext}$/, parser] + ## + # Alias an extension to another extension. After this call, files ending + # "new_ext" will be parsed using the same parser as "old_ext" - true - end + def self.alias_extension(old_ext, new_ext) + old_ext = old_ext.sub(/^\.(.*)/, '\1') + new_ext = new_ext.sub(/^\.(.*)/, '\1') - ## - # Determines if the file is a "binary" file which basically means it has - # content that an RDoc parser shouldn't try to consume. + parser = can_parse_by_name "xxx.#{old_ext}" + return false unless parser - def self.binary?(file) - return false if file =~ /\.(rdoc|txt)$/ + ::RDoc::Parser.parsers.unshift [/\.#{new_ext}$/, parser] - s = File.read(file, 1024) or return false + true + end - return true if s[0, 2] == Marshal.dump('')[0, 2] or s.index("\x00") + ## + # Determines if the file is a "binary" file which basically means it has + # content that an RDoc parser shouldn't try to consume. - mode = 'r:utf-8' # default source encoding has been changed to utf-8 - s.sub!(/\A#!.*\n/, '') # assume shebang line isn't longer than 1024. - encoding = s[/^\s*\#\s*(?:-\*-\s*)?(?:en)?coding:\s*([^\s;]+?)(?:-\*-|[\s;])/, 1] - mode = "rb:#{encoding}" if encoding - s = File.open(file, mode) {|f| f.gets(nil, 1024)} + def self.binary?(file) + return false if file =~ /\.(rdoc|txt)$/ - not s.valid_encoding? - end + s = File.read(file, 1024) or return false - ## - # Checks if +file+ is a zip file in disguise. Signatures from - # http://www.garykessler.net/library/file_sigs.html + return true if s[0, 2] == Marshal.dump('')[0, 2] or s.index("\x00") - def self.zip?(file) - zip_signature = File.read file, 4 + mode = 'r:utf-8' # default source encoding has been changed to utf-8 + s.sub!(/\A#!.*\n/, '') # assume shebang line isn't longer than 1024. + encoding = s[/^\s*\#\s*(?:-\*-\s*)?(?:en)?coding:\s*([^\s;]+?)(?:-\*-|[\s;])/, 1] + mode = "rb:#{encoding}" if encoding + s = File.open(file, mode) {|f| f.gets(nil, 1024)} - zip_signature == "PK\x03\x04" or - zip_signature == "PK\x05\x06" or - zip_signature == "PK\x07\x08" - rescue - false - end + not s.valid_encoding? + end - ## - # Return a parser that can handle a particular extension + ## + # Checks if +file+ is a zip file in disguise. Signatures from + # http://www.garykessler.net/library/file_sigs.html - def self.can_parse(file_name) - parser = can_parse_by_name file_name + def self.zip?(file) + zip_signature = File.read file, 4 - # HACK Selenium hides a jar file using a .txt extension - return if parser == RDoc::Parser::Simple and zip? file_name + zip_signature == "PK\x03\x04" or + zip_signature == "PK\x05\x06" or + zip_signature == "PK\x07\x08" + rescue + false + end - parser - end + ## + # Return a parser that can handle a particular extension - ## - # Returns a parser that can handle the extension for +file_name+. This does - # not depend upon the file being readable. + def self.can_parse(file_name) + parser = can_parse_by_name file_name - def self.can_parse_by_name(file_name) - _, parser = RDoc::Parser.parsers.find { |regexp,| regexp =~ file_name } + # HACK Selenium hides a jar file using a .txt extension + return if parser == ::RDoc::Parser::Simple and zip? file_name - # The default parser must not parse binary files - ext_name = File.extname file_name - return parser if ext_name.empty? + parser + end - if parser == RDoc::Parser::Simple and ext_name !~ /txt|rdoc/ - case mode = check_modeline(file_name) - when nil, 'rdoc' # continue - else - RDoc::Parser.parsers.find { |_, p| return p if mode.casecmp?(p.name[/\w+\z/]) } - return nil + ## + # Returns a parser that can handle the extension for +file_name+. This does + # not depend upon the file being readable. + + def self.can_parse_by_name(file_name) + _, parser = ::RDoc::Parser.parsers.find { |regexp,| regexp =~ file_name } + + # The default parser must not parse binary files + ext_name = File.extname file_name + return parser if ext_name.empty? + + if parser == ::RDoc::Parser::Simple and ext_name !~ /txt|rdoc/ + case mode = check_modeline(file_name) + when nil, 'rdoc' # continue + else + ::RDoc::Parser.parsers.find { |_, p| return p if mode.casecmp?(p.name[/\w+\z/]) } + return nil + end end - end - parser - rescue Errno::EACCES - end + parser + rescue Errno::EACCES + end - ## - # Returns the file type from the modeline in +file_name+ + ## + # Returns the file type from the modeline in +file_name+ - def self.check_modeline(file_name) - line = File.open file_name do |io| - io.gets - end + def self.check_modeline(file_name) + line = File.open file_name do |io| + io.gets + end - /-\*-\s*(.*?\S)\s*-\*-/ =~ line + /-\*-\s*(.*?\S)\s*-\*-/ =~ line - return nil unless type = $1 + return nil unless type = $1 - if /;/ =~ type - return nil unless /(?:\s|\A)mode:\s*([^\s;]+)/i =~ type - type = $1 - end + if /;/ =~ type + return nil unless /(?:\s|\A)mode:\s*([^\s;]+)/i =~ type + type = $1 + end - return nil if /coding:/i =~ type + return nil if /coding:/i =~ type - type.downcase - rescue ArgumentError - rescue Encoding::InvalidByteSequenceError # invalid byte sequence + type.downcase + rescue ArgumentError + rescue ::Encoding::InvalidByteSequenceError # invalid byte sequence - end + end - ## - # Finds and instantiates the correct parser for the given +file_name+ and - # +content+. + ## + # Finds and instantiates the correct parser for the given +file_name+ and + # +content+. - def self.for(top_level, content, options, stats) - file_name = top_level.absolute_name - return if binary? file_name + def self.for(top_level, content, options, stats) + file_name = top_level.absolute_name + return if binary? file_name - parser = use_markup content + parser = use_markup content - unless parser - parse_name = file_name + unless parser + parse_name = file_name - # If no extension, look for shebang - if file_name !~ /\.\w+$/ && content =~ %r{\A#!(.+)} - shebang = $1 - case shebang - when %r{env\s+ruby}, %r{/ruby} - parse_name = 'dummy.rb' + # If no extension, look for shebang + if file_name !~ /\.\w+$/ && content =~ %r{\A#!(.+)} + shebang = $1 + case shebang + when %r{env\s+ruby}, %r{/ruby} + parse_name = 'dummy.rb' + end end + + parser = can_parse parse_name end - parser = can_parse parse_name - end + return unless parser - return unless parser + content = remove_modeline content - content = remove_modeline content + parser.new top_level, content, options, stats + rescue SystemCallError + nil + end - parser.new top_level, content, options, stats - rescue SystemCallError - nil - end + ## + # Record which file types this parser can understand. + # + # It is ok to call this multiple times. - ## - # Record which file types this parser can understand. - # - # It is ok to call this multiple times. + def self.parse_files_matching(regexp) + ::RDoc::Parser.parsers.unshift [regexp, self] + end - def self.parse_files_matching(regexp) - RDoc::Parser.parsers.unshift [regexp, self] - end + ## + # Removes an emacs-style modeline from the first line of the document - ## - # Removes an emacs-style modeline from the first line of the document + def self.remove_modeline(content) + content.sub(/\A.*-\*-\s*(.*?\S)\s*-\*-.*\r?\n/, '') + end - def self.remove_modeline(content) - content.sub(/\A.*-\*-\s*(.*?\S)\s*-\*-.*\r?\n/, '') - end + ## + # If there is a markup: parser_name comment at the front of the + # file, use it to determine the parser. For example: + # + # # markup: rdoc + # # Class comment can go here + # + # class C + # end + # + # The comment should appear as the first line of the +content+. + # + # If the content contains a shebang or editor modeline the comment may + # appear on the second or third line. + # + # Any comment style may be used to hide the markup comment. + # + # The +tomdoc+ and +markdown+ markups name comment formats rather than + # parsers, so no parser is selected for them and RDoc::Parser.for picks one + # from the file name instead. - ## - # If there is a markup: parser_name comment at the front of the - # file, use it to determine the parser. For example: - # - # # markup: rdoc - # # Class comment can go here - # - # class C - # end - # - # The comment should appear as the first line of the +content+. - # - # If the content contains a shebang or editor modeline the comment may - # appear on the second or third line. - # - # Any comment style may be used to hide the markup comment. - # - # The +tomdoc+ and +markdown+ markups name comment formats rather than - # parsers, so no parser is selected for them and RDoc::Parser.for picks one - # from the file name instead. + def self.use_markup(content) + markup = content.lines.first(3).grep(/markup:\s+(\w+)/) { $1 }.first - def self.use_markup(content) - markup = content.lines.first(3).grep(/markup:\s+(\w+)/) { $1 }.first + return unless markup - return unless markup + # tomdoc and markdown name a comment format, not a parser. Skipping them + # keeps the search below from matching RDoc::Parser::Markdown for a file + # that is not markdown. + return if %w[tomdoc markdown].include? markup - # tomdoc and markdown name a comment format, not a parser. Skipping them - # keeps the search below from matching RDoc::Parser::Markdown for a file - # that is not markdown. - return if %w[tomdoc markdown].include? markup + markup = Regexp.escape markup - markup = Regexp.escape markup + _, selected = ::RDoc::Parser.parsers.find do |_, parser| + /^#{markup}$/i =~ parser.name.sub(/.*:/, '') + end - _, selected = RDoc::Parser.parsers.find do |_, parser| - /^#{markup}$/i =~ parser.name.sub(/.*:/, '') + selected end - selected - end - - ## - # Creates a new Parser storing +top_level+, +file_name+, +content+, - # +options+ and +stats+ in instance variables. In +@preprocess+ an - # RDoc::Markup::PreProcess object is created which allows processing of - # directives. - - def initialize(top_level, content, options, stats) - @top_level = top_level - @top_level.parser = self.class - @store = @top_level.store - - @file_name = top_level.absolute_name - @content = content - @options = options - @stats = stats - - @preprocess = RDoc::Markup::PreProcess.new @file_name, @options.rdoc_include - @preprocess.options = @options - end + ## + # Creates a new Parser storing +top_level+, +file_name+, +content+, + # +options+ and +stats+ in instance variables. In +@preprocess+ an + # RDoc::Markup::PreProcess object is created which allows processing of + # directives. + + def initialize(top_level, content, options, stats) + @top_level = top_level + @top_level.parser = self.class + @store = @top_level.store + + @file_name = top_level.absolute_name + @content = content + @options = options + @stats = stats + + @preprocess = ::RDoc::Markup::PreProcess.new @file_name, @options.rdoc_include + @preprocess.options = @options + end - autoload :Text, "#{__dir__}/parser/text" + autoload :Text, "#{__dir__}/parser/text" - ## - # Normalizes tabs in +body+ - - def handle_tab_width(body) - if /\t/ =~ body - tab_width = @options.tab_width - body.split(/\n/).map do |line| - 1 while line.gsub!(/\t+/) do - b, e = $~.offset(0) - ' ' * (tab_width * (e-b) - b % tab_width) - end - line - end.join "\n" - else - body + ## + # Normalizes tabs in +body+ + + def handle_tab_width(body) + if /\t/ =~ body + tab_width = @options.tab_width + body.split(/\n/).map do |line| + 1 while line.gsub!(/\t+/) do + b, e = $~.offset(0) + ' ' * (tab_width * (e-b) - b % tab_width) + end + line + end.join "\n" + else + body + end end end end diff --git a/lib/rdoc/parser/c.rb b/lib/rdoc/parser/c.rb index 72762be5d6..295313464f 100644 --- a/lib/rdoc/parser/c.rb +++ b/lib/rdoc/parser/c.rb @@ -1,1225 +1,1229 @@ # frozen_string_literal: true require 'tsort' -## -# RDoc::Parser::C attempts to parse C extension files. It looks for -# the standard patterns that you find in extensions: +rb_define_class+, -# +rb_define_method+ and so on. It tries to find the corresponding -# C source for the methods and extract comments, but if we fail -# we don't worry too much. -# -# The comments associated with a Ruby method are extracted from the C -# comment block associated with the routine that _implements_ that -# method, that is to say the method whose name is given in the -# +rb_define_method+ call. For example, you might write: -# -# /* -# * Returns a new array that is a one-dimensional flattening of this -# * array (recursively). That is, for every element that is an array, -# * extract its elements into the new array. -# * -# * s = [ 1, 2, 3 ] #=> [1, 2, 3] -# * t = [ 4, 5, 6, [7, 8] ] #=> [4, 5, 6, [7, 8]] -# * a = [ s, t, 9, 10 ] #=> [[1, 2, 3], [4, 5, 6, [7, 8]], 9, 10] -# * a.flatten #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] -# */ -# static VALUE -# rb_ary_flatten(VALUE ary) -# { -# ary = rb_obj_dup(ary); -# rb_ary_flatten_bang(ary); -# return ary; -# } -# -# ... -# -# void -# Init_Array(void) -# { -# ... -# rb_define_method(rb_cArray, "flatten", rb_ary_flatten, 0); -# -# Here RDoc will determine from the +rb_define_method+ line that there's a -# method called "flatten" in class Array, and will look for the implementation -# in the method +rb_ary_flatten+. It will then use the comment from that -# method in the HTML output. This method must be in the same source file -# as the +rb_define_method+. -# -# The comment blocks may include special directives: -# -# [Document-class: +name+] -# Documentation for the named class. -# -# [Document-module: +name+] -# Documentation for the named module. -# -# [Document-const: +name+] -# Documentation for the named +rb_define_const+. -# -# Constant values can be supplied on the first line of the comment like so: -# -# /* 300: The highest possible score in bowling */ -# rb_define_const(cFoo, "PERFECT", INT2FIX(300)); -# -# The value can contain internal colons so long as they are escaped with a \ -# -# [Document-global: +name+] -# Documentation for the named +rb_define_global_const+ -# -# [Document-variable: +name+] -# Documentation for the named +rb_define_variable+ -# -# [Document-method\: +method_name+] -# Documentation for the named method. Use this when the method name is -# unambiguous. -# -# [Document-method\: ClassName::method_name] -# Documentation for a singleton method in the given class. Use this when -# the method name alone is ambiguous. -# -# [Document-method\: ClassName#method_name] -# Documentation for a instance method in the given class. Use this when the -# method name alone is ambiguous. -# -# [Document-attr: +name+] -# Documentation for the named attribute. -# -# [call-seq: text up to an empty line] -# Because C source doesn't give descriptive names to Ruby-level parameters, -# you need to document the calling sequence explicitly -# -# In addition, RDoc assumes by default that the C method implementing a -# Ruby function is in the same source file as the rb_define_method call. -# If this isn't the case, add the comment: -# -# rb_define_method(....); // in filename -# -# As an example, we might have an extension that defines multiple classes -# in its Init_xxx method. We could document them using -# -# /* -# * Document-class: MyClass -# * -# * Encapsulate the writing and reading of the configuration -# * file. ... -# */ -# -# /* -# * Document-method: read_value -# * -# * call-seq: -# * cfg.read_value(key) -> value -# * cfg.read_value(key} { |key| } -> value -# * -# * Return the value corresponding to +key+ from the configuration. -# * In the second form, if the key isn't found, invoke the -# * block and return its value. -# */ - -class RDoc::Parser::C < RDoc::Parser - - parse_files_matching(/\.(?:([CcHh])\1?|c([+xp])\2|y)\z/) - - include RDoc::Text - - # :stopdoc: - BOOL_ARG_PATTERN = /\s*+\b([01]|Q?(?:true|false)|TRUE|FALSE)\b\s*/ - TRUE_VALUES = ['1', 'TRUE', 'true', 'Qtrue'].freeze - # :startdoc: - - ## - # Maps C variable names to names of Ruby classes or modules - - attr_reader :classes - - ## - # C file the parser is parsing - - attr_accessor :content - - ## - # Dependencies from a missing enclosing class to the classes in - # missing_dependencies that depend upon it. - - attr_reader :enclosure_dependencies - - ## - # Maps C variable names to names of Ruby classes (and singleton classes) - - attr_reader :known_classes - - ## - # Classes found while parsing the C file that were not yet registered due to - # a missing enclosing class. These are processed by do_missing - - attr_reader :missing_dependencies - - ## - # Maps C variable names to names of Ruby singleton classes - - attr_reader :singleton_classes - - ## - # The TopLevel items in the parsed file belong to - - attr_reader :top_level - - ## - # Prepares for parsing a C file. See RDoc::Parser#initialize for details on - # the arguments. - - def initialize(top_level, content, options, stats) - super - - @known_classes = RDoc::KNOWN_CLASSES.dup - @content = handle_tab_width handle_ifdefs_in @content - @file_dir = File.dirname @file_name +module RDoc + class Parser + ## + # RDoc::Parser::C attempts to parse C extension files. It looks for + # the standard patterns that you find in extensions: +rb_define_class+, + # +rb_define_method+ and so on. It tries to find the corresponding + # C source for the methods and extract comments, but if we fail + # we don't worry too much. + # + # The comments associated with a Ruby method are extracted from the C + # comment block associated with the routine that _implements_ that + # method, that is to say the method whose name is given in the + # +rb_define_method+ call. For example, you might write: + # + # /* + # * Returns a new array that is a one-dimensional flattening of this + # * array (recursively). That is, for every element that is an array, + # * extract its elements into the new array. + # * + # * s = [ 1, 2, 3 ] #=> [1, 2, 3] + # * t = [ 4, 5, 6, [7, 8] ] #=> [4, 5, 6, [7, 8]] + # * a = [ s, t, 9, 10 ] #=> [[1, 2, 3], [4, 5, 6, [7, 8]], 9, 10] + # * a.flatten #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + # */ + # static VALUE + # rb_ary_flatten(VALUE ary) + # { + # ary = rb_obj_dup(ary); + # rb_ary_flatten_bang(ary); + # return ary; + # } + # + # ... + # + # void + # Init_Array(void) + # { + # ... + # rb_define_method(rb_cArray, "flatten", rb_ary_flatten, 0); + # + # Here RDoc will determine from the +rb_define_method+ line that there's a + # method called "flatten" in class Array, and will look for the implementation + # in the method +rb_ary_flatten+. It will then use the comment from that + # method in the HTML output. This method must be in the same source file + # as the +rb_define_method+. + # + # The comment blocks may include special directives: + # + # [Document-class: +name+] + # Documentation for the named class. + # + # [Document-module: +name+] + # Documentation for the named module. + # + # [Document-const: +name+] + # Documentation for the named +rb_define_const+. + # + # Constant values can be supplied on the first line of the comment like so: + # + # /* 300: The highest possible score in bowling */ + # rb_define_const(cFoo, "PERFECT", INT2FIX(300)); + # + # The value can contain internal colons so long as they are escaped with a \ + # + # [Document-global: +name+] + # Documentation for the named +rb_define_global_const+ + # + # [Document-variable: +name+] + # Documentation for the named +rb_define_variable+ + # + # [Document-method\: +method_name+] + # Documentation for the named method. Use this when the method name is + # unambiguous. + # + # [Document-method\: ClassName::method_name] + # Documentation for a singleton method in the given class. Use this when + # the method name alone is ambiguous. + # + # [Document-method\: ClassName#method_name] + # Documentation for a instance method in the given class. Use this when the + # method name alone is ambiguous. + # + # [Document-attr: +name+] + # Documentation for the named attribute. + # + # [call-seq: text up to an empty line] + # Because C source doesn't give descriptive names to Ruby-level parameters, + # you need to document the calling sequence explicitly + # + # In addition, RDoc assumes by default that the C method implementing a + # Ruby function is in the same source file as the rb_define_method call. + # If this isn't the case, add the comment: + # + # rb_define_method(....); // in filename + # + # As an example, we might have an extension that defines multiple classes + # in its Init_xxx method. We could document them using + # + # /* + # * Document-class: MyClass + # * + # * Encapsulate the writing and reading of the configuration + # * file. ... + # */ + # + # /* + # * Document-method: read_value + # * + # * call-seq: + # * cfg.read_value(key) -> value + # * cfg.read_value(key} { |key| } -> value + # * + # * Return the value corresponding to +key+ from the configuration. + # * In the second form, if the key isn't found, invoke the + # * block and return its value. + # */ + + class C < ::RDoc::Parser + + parse_files_matching(/\.(?:([CcHh])\1?|c([+xp])\2|y)\z/) + + include ::RDoc::Text + + # :stopdoc: + BOOL_ARG_PATTERN = /\s*+\b([01]|Q?(?:true|false)|TRUE|FALSE)\b\s*/ + TRUE_VALUES = ['1', 'TRUE', 'true', 'Qtrue'].freeze + # :startdoc: + + ## + # Maps C variable names to names of Ruby classes or modules + + attr_reader :classes + + ## + # C file the parser is parsing + + attr_accessor :content + + ## + # Dependencies from a missing enclosing class to the classes in + # missing_dependencies that depend upon it. + + attr_reader :enclosure_dependencies + + ## + # Maps C variable names to names of Ruby classes (and singleton classes) + + attr_reader :known_classes + + ## + # Classes found while parsing the C file that were not yet registered due to + # a missing enclosing class. These are processed by do_missing + + attr_reader :missing_dependencies + + ## + # Maps C variable names to names of Ruby singleton classes + + attr_reader :singleton_classes + + ## + # The TopLevel items in the parsed file belong to + + attr_reader :top_level + + ## + # Prepares for parsing a C file. See RDoc::Parser#initialize for details on + # the arguments. + + def initialize(top_level, content, options, stats) + super + + @known_classes = ::RDoc::KNOWN_CLASSES.dup + @content = handle_tab_width handle_ifdefs_in @content + @file_dir = File.dirname @file_name - @classes = load_variable_map :c_class_variables - @singleton_classes = load_variable_map :c_singleton_class_variables + @classes = load_variable_map :c_class_variables + @singleton_classes = load_variable_map :c_singleton_class_variables - @markup = @options.markup + @markup = @options.markup - # class_variable => { function => [method, ...] } - @methods = Hash.new { |h, f| h[f] = Hash.new { |i, m| i[m] = [] } } + # class_variable => { function => [method, ...] } + @methods = Hash.new { |h, f| h[f] = Hash.new { |i, m| i[m] = [] } } - # missing variable => [handle_class_module arguments] - @missing_dependencies = {} + # missing variable => [handle_class_module arguments] + @missing_dependencies = {} - # missing enclosure variable => [dependent handle_class_module arguments] - @enclosure_dependencies = Hash.new { |h, k| h[k] = [] } - @enclosure_dependencies.instance_variable_set :@missing_dependencies, - @missing_dependencies + # missing enclosure variable => [dependent handle_class_module arguments] + @enclosure_dependencies = Hash.new { |h, k| h[k] = [] } + @enclosure_dependencies.instance_variable_set :@missing_dependencies, + @missing_dependencies - @enclosure_dependencies.extend TSort + @enclosure_dependencies.extend TSort - def @enclosure_dependencies.tsort_each_node(&block) - each_key(&block) - rescue TSort::Cyclic => e - cycle_vars = e.message.scan(/"(.*?)"/).flatten + def @enclosure_dependencies.tsort_each_node(&block) + each_key(&block) + rescue TSort::Cyclic => e + cycle_vars = e.message.scan(/"(.*?)"/).flatten - cycle = cycle_vars.sort.map do |var_name| - delete var_name + cycle = cycle_vars.sort.map do |var_name| + delete var_name - var_name, type, mod_name, = @missing_dependencies[var_name] + var_name, type, mod_name, = @missing_dependencies[var_name] - "#{type} #{mod_name} (#{var_name})" - end.join ', ' + "#{type} #{mod_name} (#{var_name})" + end.join ', ' - warn "Unable to create #{cycle} due to a cyclic class or module creation" + warn "Unable to create #{cycle} due to a cyclic class or module creation" - retry - end - - def @enclosure_dependencies.tsort_each_child(node, &block) - fetch(node, []).each(&block) - end - end + retry + end - ## - # Scans #content for rb_define_alias - - def do_aliases - @content.scan(/rb_define_alias\s*\( - \s*(\w+), - \s*"(.+?)", - \s*"(.+?)" - \s*\)/xm) do |var_name, new_name, old_name| - class_name = @known_classes[var_name] - - unless class_name - @options.warn "Enclosing class or module %p for alias %s %s is not known" % [ - var_name, new_name, old_name] - next + def @enclosure_dependencies.tsort_each_child(node, &block) + fetch(node, []).each(&block) + end end - class_obj = find_class var_name, class_name - comment = find_alias_comment var_name, new_name, old_name - comment.normalize - if comment.to_s.empty? and existing_method = class_obj.method_list.find { |m| m.name == old_name} - comment = existing_method.comment + ## + # Scans #content for rb_define_alias + + def do_aliases + @content.scan(/rb_define_alias\s*\( + \s*(\w+), + \s*"(.+?)", + \s*"(.+?)" + \s*\)/xm) do |var_name, new_name, old_name| + class_name = @known_classes[var_name] + + unless class_name + @options.warn "Enclosing class or module %p for alias %s %s is not known" % [ + var_name, new_name, old_name] + next + end + + class_obj = find_class var_name, class_name + comment = find_alias_comment var_name, new_name, old_name + comment.normalize + if comment.to_s.empty? and existing_method = class_obj.method_list.find { |m| m.name == old_name} + comment = existing_method.comment + end + add_alias(var_name, class_obj, old_name, new_name, comment, singleton: @singleton_classes.key?(var_name)) + end end - add_alias(var_name, class_obj, old_name, new_name, comment, singleton: @singleton_classes.key?(var_name)) - end - end - ## - # Add alias, either from a direct alias definition, or from two - # method that reference the same function. + ## + # Add alias, either from a direct alias definition, or from two + # method that reference the same function. - def add_alias(var_name, class_obj, old_name, new_name, comment, singleton:) - al = RDoc::Alias.new old_name, new_name, comment, singleton: singleton - al.record_location @top_level - class_obj.add_alias al - @stats.add_alias al - al - end + def add_alias(var_name, class_obj, old_name, new_name, comment, singleton:) + al = ::RDoc::Alias.new old_name, new_name, comment, singleton: singleton + al.record_location @top_level + class_obj.add_alias al + @stats.add_alias al + al + end - ## - # Scans #content for rb_attr and rb_define_attr - - def do_attrs - @content.scan(/rb_attr\s*\( - \s*(\w+), - \s*([\w"()]+), - #{BOOL_ARG_PATTERN}, - #{BOOL_ARG_PATTERN}, - \s*\w+\);/xmo) do |var_name, attr_name, read, write| - handle_attr var_name, attr_name, read, write - end + ## + # Scans #content for rb_attr and rb_define_attr + + def do_attrs + @content.scan(/rb_attr\s*\( + \s*(\w+), + \s*([\w"()]+), + #{BOOL_ARG_PATTERN}, + #{BOOL_ARG_PATTERN}, + \s*\w+\);/xmo) do |var_name, attr_name, read, write| + handle_attr var_name, attr_name, read, write + end - @content.scan(%r%rb_define_attr\( - \s*([\w\.]+), - \s*"([^"]+)", - #{BOOL_ARG_PATTERN}, - #{BOOL_ARG_PATTERN}\); - %xmo) do |var_name, attr_name, read, write| - handle_attr var_name, attr_name, read, write - end - end + @content.scan(%r%rb_define_attr\( + \s*([\w\.]+), + \s*"([^"]+)", + #{BOOL_ARG_PATTERN}, + #{BOOL_ARG_PATTERN}\); + %xmo) do |var_name, attr_name, read, write| + handle_attr var_name, attr_name, read, write + end + end - ## - # Scans #content for boot_defclass + ## + # Scans #content for boot_defclass - def do_boot_defclass - @content.scan(/(\w+)\s*=\s*boot_defclass\s*\(\s*"(\w+?)",\s*(\w+?)\s*\)/) do - |var_name, class_name, parent| - parent = nil if parent == "0" - handle_class_module(var_name, :class, class_name, parent, nil) - end - end + def do_boot_defclass + @content.scan(/(\w+)\s*=\s*boot_defclass\s*\(\s*"(\w+?)",\s*(\w+?)\s*\)/) do + |var_name, class_name, parent| + parent = nil if parent == "0" + handle_class_module(var_name, :class, class_name, parent, nil) + end + end - ## - # Scans #content for rb_define_class, boot_defclass, rb_define_class_under - # and rb_singleton_class - - def do_classes_and_modules - do_boot_defclass if @file_name == "class.c" - - @content.scan( - %r( - (?\s*\(\s*) {0} - (?\s*\)\s*) {0} - (?\s*"(?\w+)") {0} - (?\s*(?: - (?[\w\*\s\(\)\.\->]+) | - rb_path2class\s*\(\s*"(?[\w:]+)"\s*\) - )) {0} - (?\w+) {0} - - (?[\w\.]+)\s* = - \s*rb_(?: - define_(?: - class(?: # rb_define_class(name, parent_name) - \(\s* + ## + # Scans #content for rb_define_class, boot_defclass, rb_define_class_under + # and rb_singleton_class + + def do_classes_and_modules + do_boot_defclass if @file_name == "class.c" + + @content.scan( + %r( + (?\s*\(\s*) {0} + (?\s*\)\s*) {0} + (?\s*"(?\w+)") {0} + (?\s*(?: + (?[\w\*\s\(\)\.\->]+) | + rb_path2class\s*\(\s*"(?[\w:]+)"\s*\) + )) {0} + (?\w+) {0} + + (?[\w\.]+)\s* = + \s*rb_(?: + define_(?: + class(?: # rb_define_class(name, parent_name) + \(\s* + \g, + \g + \s*\) + | + _under\g # rb_define_class_under(under, name, parent_name...) + \g, + \g, + \g + \g + ) + | + (?) + module(?: # rb_define_module(name) + \g + \g + \g + | + _under\g # rb_define_module_under(under, name) + \g, + \g + \g + ) + ) + | + (?(?:\s*"\w+",)*\s*NULL\s*) {0} + struct_define(?: + \g # rb_struct_define(name, ...) \g, - \g - \s*\) | - _under\g # rb_define_class_under(under, name, parent_name...) + _under\g # rb_struct_define_under(under, name, ...) \g, \g, - \g - \g - ) - | - (?) - module(?: # rb_define_module(name) - \g - \g - \g | - _under\g # rb_define_module_under(under, name) - \g, - \g - \g + _without_accessor(?: + \g # rb_struct_define_without_accessor(name, parent_name, ...) + | + _under\g # rb_struct_define_without_accessor_under(under, name, parent_name, ...) + \g, + ) + \g, + \g, + \s*\w+, # Allocation function ) - ) - | - (?(?:\s*"\w+",)*\s*NULL\s*) {0} - struct_define(?: - \g # rb_struct_define(name, ...) - \g, - | - _under\g # rb_struct_define_under(under, name, ...) - \g, - \g, - | - _without_accessor(?: - \g # rb_struct_define_without_accessor(name, parent_name, ...) + \g + \g | - _under\g # rb_struct_define_without_accessor_under(under, name, parent_name, ...) - \g, - ) - \g, - \g, - \s*\w+, # Allocation function - ) - \g - \g - | - singleton_class\g # rb_singleton_class(target_class_name) - (?\w+) - \g - ) - )mx - ) do - if target_class_name = $~[:target_class_name] - # rb_singleton_class(target_class_name) - handle_singleton $~[:var_name], target_class_name - next + singleton_class\g # rb_singleton_class(target_class_name) + (?\w+) + \g + ) + )mx + ) do + if target_class_name = $~[:target_class_name] + # rb_singleton_class(target_class_name) + handle_singleton $~[:var_name], target_class_name + next + end + + var_name = $~[:var_name] + type = $~[:module] ? :module : :class + class_name = $~[:class_name] + parent_name = $~[:parent_name] || $~[:path] + under = $~[:under] + attributes = $~[:attributes] + + handle_class_module(var_name, type, class_name, parent_name, under) + if attributes and !parent_name # rb_struct_define *not* without_accessor + true_flag = 'Qtrue' + attributes.scan(/"\K\w+(?=")/) do |attr_name| + handle_attr var_name, attr_name, true_flag, true_flag + end + end + end end - var_name = $~[:var_name] - type = $~[:module] ? :module : :class - class_name = $~[:class_name] - parent_name = $~[:parent_name] || $~[:path] - under = $~[:under] - attributes = $~[:attributes] - - handle_class_module(var_name, type, class_name, parent_name, under) - if attributes and !parent_name # rb_struct_define *not* without_accessor - true_flag = 'Qtrue' - attributes.scan(/"\K\w+(?=")/) do |attr_name| - handle_attr var_name, attr_name, true_flag, true_flag + ## + # Scans #content for rb_define_variable, rb_define_readonly_variable, + # rb_define_const and rb_define_global_const + + def do_constants + @content.scan(%r%\Wrb_define_ + ( variable | + readonly_variable | + const | + global_const ) + \s*\( + (?:\s*(\w+),)? + \s*"(\w+)", + \s*(.*?)\s*\)\s*; + %xm) do |type, var_name, const_name, definition| + var_name = "rb_cObject" if !var_name or var_name == "rb_mKernel" + type = "const" if type == "global_const" + handle_constants type, var_name, const_name, definition end - end - end - end - ## - # Scans #content for rb_define_variable, rb_define_readonly_variable, - # rb_define_const and rb_define_global_const - - def do_constants - @content.scan(%r%\Wrb_define_ - ( variable | - readonly_variable | - const | - global_const ) - \s*\( - (?:\s*(\w+),)? - \s*"(\w+)", - \s*(.*?)\s*\)\s*; - %xm) do |type, var_name, const_name, definition| - var_name = "rb_cObject" if !var_name or var_name == "rb_mKernel" - type = "const" if type == "global_const" - handle_constants type, var_name, const_name, definition - end + @content.scan(%r% + \Wrb_curses_define_const + \s*\( + \s* + (\w+) + \s* + \) + \s*;%xm) do |consts| + const = consts.first + + handle_constants 'const', 'mCurses', const, "UINT2NUM(#{const})" + end - @content.scan(%r% - \Wrb_curses_define_const - \s*\( - \s* - (\w+) - \s* - \) - \s*;%xm) do |consts| - const = consts.first - - handle_constants 'const', 'mCurses', const, "UINT2NUM(#{const})" - end + @content.scan(%r% + \Wrb_file_const + \s*\( + \s* + "([^"]+)", + \s* + (.*?) + \s* + \) + \s*;%xm) do |name, value| + handle_constants 'const', 'rb_mFConst', name, value + end + end - @content.scan(%r% - \Wrb_file_const - \s*\( - \s* - "([^"]+)", - \s* - (.*?) - \s* - \) - \s*;%xm) do |name, value| - handle_constants 'const', 'rb_mFConst', name, value - end - end + ## + # Scans #content for rb_include_module - ## - # Scans #content for rb_include_module + def do_includes + @content.scan(/rb_include_module\s*\(\s*(\w+?),\s*(\w+?)\s*\)/) do |c, m| + next unless cls = @classes[c] + m = @known_classes[m] || m - def do_includes - @content.scan(/rb_include_module\s*\(\s*(\w+?),\s*(\w+?)\s*\)/) do |c, m| - next unless cls = @classes[c] - m = @known_classes[m] || m + comment = new_comment '', @top_level, :c + incl = cls.add_include ::RDoc::Include.new(m, comment) + incl.record_location @top_level + end + end - comment = new_comment '', @top_level, :c - incl = cls.add_include RDoc::Include.new(m, comment) - incl.record_location @top_level - end - end + ## + # Scans #content for rb_define_method, rb_define_singleton_method, + # rb_define_module_function, rb_define_private_method, + # rb_define_global_function and define_filetest_function + + def do_methods + @content.scan(%r%rb_define_ + ( + singleton_method | + method | + module_function | + private_method + ) + \s*\(\s*([\w\.]+), + \s*"([^"]+)", + \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\(|\(METHOD\))?(\w+)\)?, + \s*(-?\w+)\s*\) + (?:;\s*/[*/]\s+in\s+(\w+?\.(?:cpp|c|y)))? + %xm) do |type, var_name, meth_name, function, param_count, source_file| + + # Ignore top-object and weird struct.c dynamic stuff + next if var_name == "ruby_top_self" + next if var_name == "nstr" + + var_name = "rb_cObject" if var_name == "rb_mKernel" + handle_method(type, var_name, meth_name, function, param_count, + source_file) + end - ## - # Scans #content for rb_define_method, rb_define_singleton_method, - # rb_define_module_function, rb_define_private_method, - # rb_define_global_function and define_filetest_function - - def do_methods - @content.scan(%r%rb_define_ - ( - singleton_method | - method | - module_function | - private_method - ) - \s*\(\s*([\w\.]+), - \s*"([^"]+)", - \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\(|\(METHOD\))?(\w+)\)?, - \s*(-?\w+)\s*\) - (?:;\s*/[*/]\s+in\s+(\w+?\.(?:cpp|c|y)))? - %xm) do |type, var_name, meth_name, function, param_count, source_file| - - # Ignore top-object and weird struct.c dynamic stuff - next if var_name == "ruby_top_self" - next if var_name == "nstr" - - var_name = "rb_cObject" if var_name == "rb_mKernel" - handle_method(type, var_name, meth_name, function, param_count, - source_file) - end + @content.scan(%r%rb_define_global_function\s*\( + \s*"([^"]+)", + \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\()?(\w+)\)?, + \s*(-?\w+)\s*\) + (?:;\s*/[*/]\s+in\s+(\w+?\.[cy]))? + %xm) do |meth_name, function, param_count, source_file| + handle_method("method", "rb_mKernel", meth_name, function, param_count, + source_file) + end - @content.scan(%r%rb_define_global_function\s*\( - \s*"([^"]+)", - \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\()?(\w+)\)?, - \s*(-?\w+)\s*\) - (?:;\s*/[*/]\s+in\s+(\w+?\.[cy]))? - %xm) do |meth_name, function, param_count, source_file| - handle_method("method", "rb_mKernel", meth_name, function, param_count, - source_file) - end + @content.scan(/define_filetest_function\s*\( + \s*"([^"]+)", + \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\()?(\w+)\)?, + \s*(-?\w+)\s*\)/xm) do |meth_name, function, param_count| - @content.scan(/define_filetest_function\s*\( - \s*"([^"]+)", - \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\()?(\w+)\)?, - \s*(-?\w+)\s*\)/xm) do |meth_name, function, param_count| + handle_method("method", "rb_mFileTest", meth_name, function, param_count) + handle_method("singleton_method", "rb_cFile", meth_name, function, + param_count) + end + end - handle_method("method", "rb_mFileTest", meth_name, function, param_count) - handle_method("singleton_method", "rb_cFile", meth_name, function, - param_count) - end - end + ## + # Creates classes and module that were missing were defined due to the file + # order being different than the declaration order. - ## - # 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? - def do_missing - return if @missing_dependencies.empty? + @enclosure_dependencies.tsort.each do |in_module| + arguments = @missing_dependencies.delete in_module - @enclosure_dependencies.tsort.each do |in_module| - arguments = @missing_dependencies.delete in_module + next unless arguments # dependency on existing class - next unless arguments # dependency on existing class + handle_class_module(*arguments) + end + end - handle_class_module(*arguments) - end - end + ## + # Finds the comment for an alias on +class_name+ from +new_name+ to + # +old_name+ - ## - # Finds the comment for an alias on +class_name+ from +new_name+ to - # +old_name+ + def find_alias_comment(class_name, new_name, old_name) + content =~ %r%((?>/\*.*?\*/\s+)) + rb_define_alias\(\s*#{Regexp.escape class_name}\s*, + \s*"#{Regexp.escape new_name}"\s*, + \s*"#{Regexp.escape old_name}"\s*\);%xm - def find_alias_comment(class_name, new_name, old_name) - content =~ %r%((?>/\*.*?\*/\s+)) - rb_define_alias\(\s*#{Regexp.escape class_name}\s*, - \s*"#{Regexp.escape new_name}"\s*, - \s*"#{Regexp.escape old_name}"\s*\);%xm + new_comment($1 || '', @top_level, :c) + end - new_comment($1 || '', @top_level, :c) - end + ## + # Finds a comment for rb_define_attr, rb_attr or Document-attr. + # + # +var_name+ is the C class variable the attribute is defined on. + # +attr_name+ is the attribute's name. + # + # +read+ and +write+ are the read/write flags ('1' or '0'). Either both or + # neither must be provided. + + def find_attr_comment(var_name, attr_name, read = nil, write = nil) + attr_name = Regexp.escape attr_name + + rw = if read and write + /\s*#{read}\s*,\s*#{write}\s*/xm + else + /.*?/m + end + + comment = if @content =~ %r%((?>/\*.*?\*/\s+)) + rb_define_attr\((?:\s*#{var_name},)?\s* + "#{attr_name}"\s*, + #{rw}\)\s*;%xm + $1 + elsif @content =~ %r%((?>/\*.*?\*/\s+)) + rb_attr\(\s*#{var_name}\s*, + \s*#{attr_name}\s*, + #{rw},.*?\)\s*;%xm + $1 + elsif @content =~ %r%(/\*.*?(?:\s*\*\s*)?) + Document-attr:\s#{attr_name}\s*?\n + ((?>(.|\n)*?\*/))%x + "#{$1}\n#{$2}" + else + '' + end - ## - # Finds a comment for rb_define_attr, rb_attr or Document-attr. - # - # +var_name+ is the C class variable the attribute is defined on. - # +attr_name+ is the attribute's name. - # - # +read+ and +write+ are the read/write flags ('1' or '0'). Either both or - # neither must be provided. - - def find_attr_comment(var_name, attr_name, read = nil, write = nil) - attr_name = Regexp.escape attr_name - - rw = if read and write - /\s*#{read}\s*,\s*#{write}\s*/xm - else - /.*?/m - end - - comment = if @content =~ %r%((?>/\*.*?\*/\s+)) - rb_define_attr\((?:\s*#{var_name},)?\s* - "#{attr_name}"\s*, - #{rw}\)\s*;%xm - $1 - elsif @content =~ %r%((?>/\*.*?\*/\s+)) - rb_attr\(\s*#{var_name}\s*, - \s*#{attr_name}\s*, - #{rw},.*?\)\s*;%xm - $1 - elsif @content =~ %r%(/\*.*?(?:\s*\*\s*)?) - Document-attr:\s#{attr_name}\s*?\n - ((?>(.|\n)*?\*/))%x - "#{$1}\n#{$2}" - else - '' - end - - new_comment comment, @top_level, :c - end + new_comment comment, @top_level, :c + end - ## - # Generate a Ruby-method table - - def gen_body_table(file_content) - table = {} - file_content.scan(%r{ - ((?>/\*.*?\*/\s*)?) - ((?:\w+\s+){0,2} VALUE\s+(\w+) - \s*(?:\([^\)]*\))(?:[^\);]|$)) - | ((?>/\*.*?\*/\s*))^\s*(\#\s*define\s+(\w+)\s+(\w+)) - | ^\s*\#\s*define\s+(\w+)\s+(\w+) - }xm) do - case - when name = $3 - table[name] = [:func_def, $1, $2, $~.offset(2)] if !(t = table[name]) || t[0] != :func_def - when name = $6 - table[name] = [:macro_def, $4, $5, $~.offset(5), $7] if !(t = table[name]) || t[0] == :macro_alias - when name = $8 - table[name] ||= [:macro_alias, $9] + ## + # Generate a Ruby-method table + + def gen_body_table(file_content) + table = {} + file_content.scan(%r{ + ((?>/\*.*?\*/\s*)?) + ((?:\w+\s+){0,2} VALUE\s+(\w+) + \s*(?:\([^\)]*\))(?:[^\);]|$)) + | ((?>/\*.*?\*/\s*))^\s*(\#\s*define\s+(\w+)\s+(\w+)) + | ^\s*\#\s*define\s+(\w+)\s+(\w+) + }xm) do + case + when name = $3 + table[name] = [:func_def, $1, $2, $~.offset(2)] if !(t = table[name]) || t[0] != :func_def + when name = $6 + table[name] = [:macro_def, $4, $5, $~.offset(5), $7] if !(t = table[name]) || t[0] == :macro_alias + when name = $8 + table[name] ||= [:macro_alias, $9] + end + end + table end - end - table - end - ## - # Find the C code corresponding to a Ruby method + ## + # Find the C code corresponding to a Ruby method - def find_body(class_name, meth_name, meth_obj, file_content, quiet = false) - if file_content - @body_table ||= {} - @body_table[file_content] ||= gen_body_table file_content - type, *args = @body_table[file_content][meth_name] - end + def find_body(class_name, meth_name, meth_obj, file_content, quiet = false) + if file_content + @body_table ||= {} + @body_table[file_content] ||= gen_body_table file_content + type, *args = @body_table[file_content][meth_name] + end - case type - when :func_def - comment = new_comment args[0], @top_level, :c - body = args[1] - offset, = args[2] + case type + when :func_def + comment = new_comment args[0], @top_level, :c + body = args[1] + offset, = args[2] - # try to find the whole body - body = $& if /#{Regexp.escape body}[^(]*?\{.*?^\}/m =~ file_content + # try to find the whole body + body = $& if /#{Regexp.escape body}[^(]*?\{.*?^\}/m =~ file_content - # The comment block may have been overridden with a 'Document-method' - # block. This happens in the interpreter when multiple methods are - # vectored through to the same C method but those methods are logically - # distinct (for example Kernel.hash and Kernel.object_id share the same - # implementation + # The comment block may have been overridden with a 'Document-method' + # block. This happens in the interpreter when multiple methods are + # vectored through to the same C method but those methods are logically + # distinct (for example Kernel.hash and Kernel.object_id share the same + # implementation - override_comment = find_override_comment class_name, meth_obj - comment = override_comment if override_comment + override_comment = find_override_comment class_name, meth_obj + comment = override_comment if override_comment - find_modifiers comment, meth_obj if comment + find_modifiers comment, meth_obj if comment - #meth_obj.params = params - meth_obj.start_collecting_tokens(:c) - tk = { :line_no => 1, :char_no => 1, :text => body } - meth_obj.add_token tk - meth_obj.comment = comment - meth_obj.line = file_content[0, offset].count("\n") + 1 + #meth_obj.params = params + meth_obj.start_collecting_tokens(:c) + tk = { :line_no => 1, :char_no => 1, :text => body } + meth_obj.add_token tk + meth_obj.comment = comment + meth_obj.line = file_content[0, offset].count("\n") + 1 - body - when :macro_def - comment = new_comment args[0], @top_level, :c - body = args[1] - offset, = args[2] + body + when :macro_def + comment = new_comment args[0], @top_level, :c + body = args[1] + offset, = args[2] - find_body class_name, args[3], meth_obj, file_content, true + find_body class_name, args[3], meth_obj, file_content, true - find_modifiers comment, meth_obj + find_modifiers comment, meth_obj - meth_obj.start_collecting_tokens(:c) - tk = { :line_no => 1, :char_no => 1, :text => body } - meth_obj.add_token tk - meth_obj.comment = comment - meth_obj.line = file_content[0, offset].count("\n") + 1 + meth_obj.start_collecting_tokens(:c) + tk = { :line_no => 1, :char_no => 1, :text => body } + meth_obj.add_token tk + meth_obj.comment = comment + meth_obj.line = file_content[0, offset].count("\n") + 1 - body - when :macro_alias - # with no comment we hope the aliased definition has it and use it's - # definition + body + when :macro_alias + # with no comment we hope the aliased definition has it and use it's + # definition - body = find_body(class_name, args[0], meth_obj, file_content, true) + body = find_body(class_name, args[0], meth_obj, file_content, true) - return body if body + return body if body - @options.warn "No definition for #{meth_name}" - false - else # No body, but might still have an override comment - comment = find_override_comment class_name, meth_obj + @options.warn "No definition for #{meth_name}" + false + else # No body, but might still have an override comment + comment = find_override_comment class_name, meth_obj - if comment - find_modifiers comment, meth_obj - meth_obj.comment = comment + if comment + find_modifiers comment, meth_obj + meth_obj.comment = comment - '' - else - @options.warn "No definition for #{meth_name}" - false + '' + else + @options.warn "No definition for #{meth_name}" + false + end + end end - end - end - - ## - # Finds a RDoc::NormalClass or RDoc::NormalModule for +raw_name+ - def find_class(raw_name, name, base_name = nil) - unless @classes[raw_name] - if raw_name =~ /^rb_m/ - container = @top_level.add_module RDoc::NormalModule, name - else - container = @top_level.add_class RDoc::NormalClass, name + ## + # Finds a RDoc::NormalClass or RDoc::NormalModule for +raw_name+ + + def find_class(raw_name, name, base_name = nil) + unless @classes[raw_name] + if raw_name =~ /^rb_m/ + container = @top_level.add_module ::RDoc::NormalModule, name + else + container = @top_level.add_class ::RDoc::NormalClass, name + end + 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] end - 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] - end - - ## - # Look for class or module documentation above Init_+class_name+(void), - # in a Document-class +class_name+ (or module) comment or above an - # rb_define_class (or module). If a comment is supplied above a matching - # Init_ and a rb_define_class the Init_ comment is used. - # - # /* - # * This is a comment for Foo - # */ - # Init_Foo(void) { - # VALUE cFoo = rb_define_class("Foo", rb_cObject); - # } - # - # /* - # * Document-class: Foo - # * This is a comment for Foo - # */ - # Init_foo(void) { - # VALUE cFoo = rb_define_class("Foo", rb_cObject); - # } - # - # /* - # * This is a comment for Foo - # */ - # VALUE cFoo = rb_define_class("Foo", rb_cObject); - - def find_class_comment(class_name, class_mod) - comment = nil - - if @content =~ %r% - ((?>/\*.*?\*/\s+)) - (static\s+)? - void\s+ - Init(?:VM)?_(?i:#{class_name})\s*(?:_\(\s*)?\(\s*(?:void\s*)?\)%xm - comment = $1.sub(%r%Document-(?:class|module):\s+#{class_name}%, '') - elsif @content =~ %r%Document-(?:class|module):\s+#{class_name}\s*? - (?:<\s+[:,\w]+)?\n((?>.*?\*/))%xm - comment = "/*\n#{$1}" - elsif @content =~ %r%((?>/\*.*?\*/\s+)) - ([\w\.\s]+\s* = \s+)?rb_define_(class|module)[\t (]*?"(#{class_name})"%xm - comment = $1 - elsif @content =~ %r%((?>/\*.*?\*/\s+)) - ([\w\. \t]+ = \s+)?rb_define_(class|module)_under[\t\w, (]*?"(#{class_name.split('::').last})"%xm - comment = $1 - else - comment = '' - end - comment = new_comment comment, @top_level, :c + ## + # Look for class or module documentation above Init_+class_name+(void), + # in a Document-class +class_name+ (or module) comment or above an + # rb_define_class (or module). If a comment is supplied above a matching + # Init_ and a rb_define_class the Init_ comment is used. + # + # /* + # * This is a comment for Foo + # */ + # Init_Foo(void) { + # VALUE cFoo = rb_define_class("Foo", rb_cObject); + # } + # + # /* + # * Document-class: Foo + # * This is a comment for Foo + # */ + # Init_foo(void) { + # VALUE cFoo = rb_define_class("Foo", rb_cObject); + # } + # + # /* + # * This is a comment for Foo + # */ + # VALUE cFoo = rb_define_class("Foo", rb_cObject); + + def find_class_comment(class_name, class_mod) + comment = nil + + if @content =~ %r% + ((?>/\*.*?\*/\s+)) + (static\s+)? + void\s+ + Init(?:VM)?_(?i:#{class_name})\s*(?:_\(\s*)?\(\s*(?:void\s*)?\)%xm + comment = $1.sub(%r%Document-(?:class|module):\s+#{class_name}%, '') + elsif @content =~ %r%Document-(?:class|module):\s+#{class_name}\s*? + (?:<\s+[:,\w]+)?\n((?>.*?\*/))%xm + comment = "/*\n#{$1}" + elsif @content =~ %r%((?>/\*.*?\*/\s+)) + ([\w\.\s]+\s* = \s+)?rb_define_(class|module)[\t (]*?"(#{class_name})"%xm + comment = $1 + elsif @content =~ %r%((?>/\*.*?\*/\s+)) + ([\w\. \t]+ = \s+)?rb_define_(class|module)_under[\t\w, (]*?"(#{class_name.split('::').last})"%xm + comment = $1 + else + comment = '' + end - look_for_directives_in class_mod, comment + comment = new_comment comment, @top_level, :c - class_mod.add_comment comment, @top_level - end + look_for_directives_in class_mod, comment - ## - # Generate a const table - - def gen_const_table(file_content) - table = {} - @content.scan(%r{ - (?(?>^\s*/\*.*?\*/\s+)) - rb_define_(?\w+)\(\s*(?:\w+),\s* - "(?\w+)"\s*, - .*?\)\s*; - | (?(?>^\s*/\*.*?\*/\s+)) - rb_define_global_(?const)\(\s* - "(?\w+)"\s*, - .*?\)\s*; - | (?(?>^\s*/\*.*?\*/\s+)) - rb_file_(?const)\(\s* - "(?\w+)"\s*, - .*?\)\s*; - | (?(?>^\s*/\*.*?\*/\s+)) - rb_curses_define_(?const)\(\s* - (?\w+) - \s*\)\s*; - | Document-(?:const|global|variable):\s - (?(?:\w+::)*\w+) - \s*?\n(?(?>.*?\*/)) - }mxi) do - name, doc, type = $~.values_at(:name, :doc, :type) - if type - table[[type, name]] = doc - else - table[name] = "/*\n" + doc + class_mod.add_comment comment, @top_level end - end - table - end - - ## - # Finds a comment matching +type+ and +const_name+ either above the - # comment or in the matching Document- section. - - def find_const_comment(type, const_name, class_name = nil) - @const_table ||= {} - @const_table[@content] ||= gen_const_table @content - table = @const_table[@content] - - comment = - table[[type, const_name]] || - (class_name && table[class_name + "::" + const_name]) || - table[const_name] || - '' - - new_comment comment, @top_level, :c - end - ## - # Handles modifiers in +comment+ and updates +meth_obj+ as appropriate. - - def find_modifiers(comment, meth_obj) - look_for_directives_in meth_obj, comment - end - - ## - # Finds a Document-method override for +meth_obj+ on +class_name+ + ## + # Generate a const table + + def gen_const_table(file_content) + table = {} + @content.scan(%r{ + (?(?>^\s*/\*.*?\*/\s+)) + rb_define_(?\w+)\(\s*(?:\w+),\s* + "(?\w+)"\s*, + .*?\)\s*; + | (?(?>^\s*/\*.*?\*/\s+)) + rb_define_global_(?const)\(\s* + "(?\w+)"\s*, + .*?\)\s*; + | (?(?>^\s*/\*.*?\*/\s+)) + rb_file_(?const)\(\s* + "(?\w+)"\s*, + .*?\)\s*; + | (?(?>^\s*/\*.*?\*/\s+)) + rb_curses_define_(?const)\(\s* + (?\w+) + \s*\)\s*; + | Document-(?:const|global|variable):\s + (?(?:\w+::)*\w+) + \s*?\n(?(?>.*?\*/)) + }mxi) do + name, doc, type = $~.values_at(:name, :doc, :type) + if type + table[[type, name]] = doc + else + table[name] = "/*\n" + doc + end + end + table + end - def find_override_comment(class_name, meth_obj) - name = Regexp.escape meth_obj.name - prefix = Regexp.escape meth_obj.name_prefix + ## + # Finds a comment matching +type+ and +const_name+ either above the + # comment or in the matching Document- section. - comment = if @content =~ %r%Document-method: - \s+#{class_name}#{prefix}#{name} - \s*?\n((?>.*?\*/))%xm - "/*\n#{$1}" - elsif @content =~ %r%Document-method: - \s#{name}\s*?\n((?>.*?\*/))%xm - "/*\n#{$1}" - end + def find_const_comment(type, const_name, class_name = nil) + @const_table ||= {} + @const_table[@content] ||= gen_const_table @content + table = @const_table[@content] - return unless comment + comment = + table[[type, const_name]] || + (class_name && table[class_name + "::" + const_name]) || + table[const_name] || + '' - new_comment comment, @top_level, :c - end - - ## - # Creates a new RDoc::Attr +attr_name+ on class +var_name+ that is either - # +read+, +write+ or both + new_comment comment, @top_level, :c + end - def handle_attr(var_name, attr_name, read, write) - rw = '' - rw += 'R' if TRUE_VALUES.include?(read) - rw += 'W' if TRUE_VALUES.include?(write) + ## + # Handles modifiers in +comment+ and updates +meth_obj+ as appropriate. - class_name = @known_classes[var_name] + def find_modifiers(comment, meth_obj) + look_for_directives_in meth_obj, comment + end - return unless class_name + ## + # Finds a Document-method override for +meth_obj+ on +class_name+ - class_obj = find_class var_name, class_name + def find_override_comment(class_name, meth_obj) + name = Regexp.escape meth_obj.name + prefix = Regexp.escape meth_obj.name_prefix - return unless class_obj + comment = if @content =~ %r%Document-method: + \s+#{class_name}#{prefix}#{name} + \s*?\n((?>.*?\*/))%xm + "/*\n#{$1}" + elsif @content =~ %r%Document-method: + \s#{name}\s*?\n((?>.*?\*/))%xm + "/*\n#{$1}" + end - comment = find_attr_comment var_name, attr_name - comment.normalize + return unless comment - name = attr_name.gsub(/rb_intern(?:_const)?\("([^"]+)"\)/, '\1') + new_comment comment, @top_level, :c + end - attr = RDoc::Attr.new name, rw, comment + ## + # Creates a new RDoc::Attr +attr_name+ on class +var_name+ that is either + # +read+, +write+ or both - attr.record_location @top_level - class_obj.add_attribute attr - @stats.add_attribute attr - end + def handle_attr(var_name, attr_name, read, write) + rw = '' + rw += 'R' if TRUE_VALUES.include?(read) + rw += 'W' if TRUE_VALUES.include?(write) - ## - # Creates a new RDoc::NormalClass or RDoc::NormalModule based on +type+ - # named +class_name+ in +parent+ which was assigned to the C +var_name+. + class_name = @known_classes[var_name] - def handle_class_module(var_name, type, class_name, parent, in_module) - parent_name = @known_classes[parent] || parent + return unless class_name - if in_module - enclosure = @classes[in_module] || @store.find_c_enclosure(in_module) + class_obj = find_class var_name, class_name - if enclosure.nil? and enclosure = @known_classes[in_module] - enc_type = /^rb_m/ =~ in_module ? :module : :class - handle_class_module in_module, enc_type, enclosure, nil, nil - enclosure = @classes[in_module] - end + return unless class_obj - unless enclosure - @enclosure_dependencies[in_module] << var_name - @missing_dependencies[var_name] = - [var_name, type, class_name, parent, in_module] + comment = find_attr_comment var_name, attr_name + comment.normalize - return - end - else - enclosure = @top_level - end + name = attr_name.gsub(/rb_intern(?:_const)?\("([^"]+)"\)/, '\1') - if type == :class - full_name = if RDoc::ClassModule === enclosure - enclosure.full_name + "::#{class_name}" - else - class_name - end + attr = ::RDoc::Attr.new name, rw, comment - if @content =~ %r%Document-class:\s+#{full_name}\s*<\s+([:,\w]+)% - parent_name = $1 + attr.record_location @top_level + class_obj.add_attribute attr + @stats.add_attribute attr end - cm = enclosure.add_class RDoc::NormalClass, class_name, parent_name - else - cm = enclosure.add_module RDoc::NormalModule, class_name - end + ## + # Creates a new RDoc::NormalClass or RDoc::NormalModule based on +type+ + # named +class_name+ in +parent+ which was assigned to the C +var_name+. - cm.record_location enclosure.top_level - enclosure.top_level.add_to_classes_or_modules cm + def handle_class_module(var_name, type, class_name, parent, in_module) + parent_name = @known_classes[parent] || parent - find_class_comment cm.full_name, cm + if in_module + enclosure = @classes[in_module] || @store.find_c_enclosure(in_module) - case cm - when RDoc::NormalClass - @stats.add_class cm - when RDoc::NormalModule - @stats.add_module cm - end + if enclosure.nil? and enclosure = @known_classes[in_module] + enc_type = /^rb_m/ =~ in_module ? :module : :class + handle_class_module in_module, enc_type, enclosure, nil, nil + enclosure = @classes[in_module] + end - @classes[var_name] = cm - @known_classes[var_name] = cm.full_name - @store.add_c_enclosure var_name, cm - end + unless enclosure + @enclosure_dependencies[in_module] << var_name + @missing_dependencies[var_name] = + [var_name, type, class_name, parent, in_module] - ## - # Adds constants. By providing some_value: at the start of the comment you - # can override the C value of the comment to give a friendly definition. - # - # /* 300: The perfect score in bowling */ - # rb_define_const(cFoo, "PERFECT", INT2FIX(300)); - # - # Will override INT2FIX(300) with the value +300+ in the output - # RDoc. Values may include quotes and escaped colons (\:). - - def handle_constants(type, var_name, const_name, definition) - class_name = @known_classes[var_name] + return + end + else + enclosure = @top_level + end - return unless class_name + if type == :class + full_name = if ::RDoc::ClassModule === enclosure + enclosure.full_name + "::#{class_name}" + else + class_name + end - class_obj = find_class var_name, class_name, class_name[/::\K[^:]+\z/] + if @content =~ %r%Document-class:\s+#{full_name}\s*<\s+([:,\w]+)% + parent_name = $1 + end - unless class_obj - @options.warn 'Enclosing class or module %p is not known' % [const_name] - return - end + cm = enclosure.add_class ::RDoc::NormalClass, class_name, parent_name + else + cm = enclosure.add_module ::RDoc::NormalModule, class_name + end - comment = find_const_comment type, const_name, class_name - comment.normalize + cm.record_location enclosure.top_level + enclosure.top_level.add_to_classes_or_modules cm - # In the case of rb_define_const, the definition and comment are in - # "/* definition: comment */" form. The literal ':' and '\' characters - # can be escaped with a backslash. - if type.downcase == 'const' - if /\A(.+?)?:(?!\S)/ =~ comment.text - new_definition, new_comment = $1, $' + find_class_comment cm.full_name, cm - if !new_definition # Default to literal C definition - new_definition = definition - else - new_definition = new_definition.gsub(/\\([\\:])/, '\1') + case cm + when ::RDoc::NormalClass + @stats.add_class cm + when ::RDoc::NormalModule + @stats.add_module cm end - new_definition.sub!(/\A(\s+)/, '') + @classes[var_name] = cm + @known_classes[var_name] = cm.full_name + @store.add_c_enclosure var_name, cm + end - new_comment = "#{$1}#{new_comment.lstrip}" + ## + # Adds constants. By providing some_value: at the start of the comment you + # can override the C value of the comment to give a friendly definition. + # + # /* 300: The perfect score in bowling */ + # rb_define_const(cFoo, "PERFECT", INT2FIX(300)); + # + # Will override INT2FIX(300) with the value +300+ in the output + # RDoc. Values may include quotes and escaped colons (\:). - new_comment = self.new_comment(new_comment, @top_level, :c) + def handle_constants(type, var_name, const_name, definition) + class_name = @known_classes[var_name] - con = RDoc::Constant.new const_name, new_definition, new_comment - else - con = RDoc::Constant.new const_name, definition, comment - end - else - con = RDoc::Constant.new const_name, definition, comment - end + return unless class_name - con.record_location @top_level - @stats.add_constant con - class_obj.add_constant con - end + class_obj = find_class var_name, class_name, class_name[/::\K[^:]+\z/] - ## - # Removes #ifdefs that would otherwise confuse us + unless class_obj + @options.warn 'Enclosing class or module %p is not known' % [const_name] + return + end - def handle_ifdefs_in(body) - body.gsub(/^#ifdef HAVE_PROTOTYPES.*?#else.*?\n(.*?)#endif.*?\n/m, '\1') - end + comment = find_const_comment type, const_name, class_name + comment.normalize - ## - # Adds an RDoc::AnyMethod +meth_name+ defined on a class or module assigned - # to +var_name+. +type+ is the type of method definition function used. - # +singleton_method+ and +module_function+ create a singleton method. + # In the case of rb_define_const, the definition and comment are in + # "/* definition: comment */" form. The literal ':' and '\' characters + # can be escaped with a backslash. + if type.downcase == 'const' + if /\A(.+?)?:(?!\S)/ =~ comment.text + new_definition, new_comment = $1, $' - def handle_method(type, var_name, meth_name, function, param_count, - source_file = nil) - class_name = @known_classes[var_name] - singleton = @singleton_classes.key?(var_name) || %w[singleton_method module_function].include?(type) + if !new_definition # Default to literal C definition + new_definition = definition + else + new_definition = new_definition.gsub(/\\([\\:])/, '\1') + end - @methods[var_name][function] << meth_name + new_definition.sub!(/\A(\s+)/, '') - return unless class_name + new_comment = "#{$1}#{new_comment.lstrip}" - class_obj = find_class var_name, class_name + new_comment = self.new_comment(new_comment, @top_level, :c) - if existing_method = class_obj.method_list.find { |m| m.c_function == function && m.singleton == singleton } - add_alias(var_name, class_obj, existing_method.name, meth_name, existing_method.comment, singleton: singleton) - end + con = ::RDoc::Constant.new const_name, new_definition, new_comment + else + con = ::RDoc::Constant.new const_name, definition, comment + end + else + con = ::RDoc::Constant.new const_name, definition, comment + end - if class_obj - if meth_name == 'initialize' - meth_name = 'new' - singleton = true - type = 'method' # force public + con.record_location @top_level + @stats.add_constant con + class_obj.add_constant con end - meth_obj = RDoc::AnyMethod.new meth_name, singleton: singleton - meth_obj.c_function = function + ## + # Removes #ifdefs that would otherwise confuse us - p_count = Integer(param_count) rescue -1 + def handle_ifdefs_in(body) + body.gsub(/^#ifdef HAVE_PROTOTYPES.*?#else.*?\n(.*?)#endif.*?\n/m, '\1') + end - if source_file - file_name = File.join @file_dir, source_file + ## + # Adds an RDoc::AnyMethod +meth_name+ defined on a class or module assigned + # to +var_name+. +type+ is the type of method definition function used. + # +singleton_method+ and +module_function+ create a singleton method. - if File.exist? file_name - file_content = RDoc::Encoding.read_file file_name, @options.encoding - else - @options.warn "unknown source #{source_file} for #{meth_name} in #{@file_name}" - end - else - file_content = @content - end + def handle_method(type, var_name, meth_name, function, param_count, + source_file = nil) + class_name = @known_classes[var_name] + singleton = @singleton_classes.key?(var_name) || %w[singleton_method module_function].include?(type) - body = find_body class_name, function, meth_obj, file_content + @methods[var_name][function] << meth_name - if body and meth_obj.document_self - meth_obj.params = if p_count < -1 # -2 is Array - '(*args)' - elsif p_count == -1 # argc, argv - rb_scan_args body - else - args = (1..p_count).map { |i| "p#{i}" } - "(#{args.join ', '})" - end + return unless class_name + class_obj = find_class var_name, class_name - meth_obj.record_location @top_level + if existing_method = class_obj.method_list.find { |m| m.c_function == function && m.singleton == singleton } + add_alias(var_name, class_obj, existing_method.name, meth_name, existing_method.comment, singleton: singleton) + end - if meth_obj.section_title - class_obj.temporary_section = class_obj.add_section(meth_obj.section_title) + if class_obj + if meth_name == 'initialize' + meth_name = 'new' + singleton = true + type = 'method' # force public + end + + meth_obj = ::RDoc::AnyMethod.new meth_name, singleton: singleton + meth_obj.c_function = function + + p_count = Integer(param_count) rescue -1 + + if source_file + file_name = File.join @file_dir, source_file + + if File.exist? file_name + file_content = ::RDoc::Encoding.read_file file_name, @options.encoding + else + @options.warn "unknown source #{source_file} for #{meth_name} in #{@file_name}" + end + else + file_content = @content + end + + body = find_body class_name, function, meth_obj, file_content + + if body and meth_obj.document_self + meth_obj.params = if p_count < -1 # -2 is Array + '(*args)' + elsif p_count == -1 # argc, argv + rb_scan_args body + else + args = (1..p_count).map { |i| "p#{i}" } + "(#{args.join ', '})" + end + + + meth_obj.record_location @top_level + + if meth_obj.section_title + class_obj.temporary_section = class_obj.add_section(meth_obj.section_title) + end + meth_obj.visibility = type == 'private_method' ? :private : :public + class_obj.add_method meth_obj + @stats.add_method meth_obj + end end - meth_obj.visibility = type == 'private_method' ? :private : :public - class_obj.add_method meth_obj - @stats.add_method meth_obj end - end - end - ## - # Registers a singleton class +sclass_var+ as a singleton of +class_var+ + ## + # Registers a singleton class +sclass_var+ as a singleton of +class_var+ - def handle_singleton(sclass_var, class_var) - if (klass = @classes[class_var]) - @classes[sclass_var] = klass - end - if (class_name = @known_classes[class_var]) - @known_classes[sclass_var] = class_name - @singleton_classes[sclass_var] = class_name - end - end + def handle_singleton(sclass_var, class_var) + if (klass = @classes[class_var]) + @classes[sclass_var] = klass + end + if (class_name = @known_classes[class_var]) + @known_classes[sclass_var] = class_name + @singleton_classes[sclass_var] = class_name + end + end - ## - # Loads the variable map with the given +name+ from the RDoc::Store, if - # present. + ## + # Loads the variable map with the given +name+ from the RDoc::Store, if + # present. - def load_variable_map(map_name) - return {} unless files = @store.cache[map_name] - return {} unless name_map = files[@file_name] + def load_variable_map(map_name) + return {} unless files = @store.cache[map_name] + return {} unless name_map = files[@file_name] - class_map = {} + class_map = {} - name_map.each do |variable, name| - next unless mod = @store.find_class_or_module(name) + name_map.each do |variable, name| + next unless mod = @store.find_class_or_module(name) - class_map[variable] = if map_name == :c_class_variables - mod - else - name - end - @known_classes[variable] = name - end + class_map[variable] = if map_name == :c_class_variables + mod + else + name + end + @known_classes[variable] = name + end - class_map - end + class_map + end - ## - # Look for directives in a normal comment block: - # - # /* - # * :nodoc: - # */ - # - # This method modifies the +comment+ - - def look_for_directives_in(context, comment) - comment.text, format = @preprocess.run_pre_processes(comment.text, context, comment.line || 1, :c) - comment.format = format if format - @preprocess.run_post_processes(comment, context) - comment.normalized = true - comment - end + ## + # Look for directives in a normal comment block: + # + # /* + # * :nodoc: + # */ + # + # This method modifies the +comment+ + + def look_for_directives_in(context, comment) + comment.text, format = @preprocess.run_pre_processes(comment.text, context, comment.line || 1, :c) + comment.format = format if format + @preprocess.run_post_processes(comment, context) + comment.normalized = true + comment + end - ## - # Extracts parameters from the +method_body+ and returns a method - # parameter string. Follows 1.9.3dev's scan-arg-spec, see README.EXT + ## + # Extracts parameters from the +method_body+ and returns a method + # parameter string. Follows 1.9.3dev's scan-arg-spec, see README.EXT - def rb_scan_args(method_body) - method_body =~ /rb_scan_args\((.*?)\)/m - return '(*args)' unless $1 + def rb_scan_args(method_body) + method_body =~ /rb_scan_args\((.*?)\)/m + return '(*args)' unless $1 - $1.split(/,/)[2] =~ /"(.*?)"/ # format argument - format = $1.split(//) + $1.split(/,/)[2] =~ /"(.*?)"/ # format argument + format = $1.split(//) - lead = opt = trail = 0 + lead = opt = trail = 0 - if format.first =~ /\d/ - lead = $&.to_i - format.shift - if format.first =~ /\d/ - opt = $&.to_i - format.shift if format.first =~ /\d/ - trail = $&.to_i + lead = $&.to_i format.shift - block_arg = true + if format.first =~ /\d/ + opt = $&.to_i + format.shift + if format.first =~ /\d/ + trail = $&.to_i + format.shift + block_arg = true + end + end end - end - end - if format.first == '*' and not block_arg - var = true - format.shift - if format.first =~ /\d/ - trail = $&.to_i - format.shift - end - end + if format.first == '*' and not block_arg + var = true + format.shift + if format.first =~ /\d/ + trail = $&.to_i + format.shift + end + end - if format.first == ':' - hash = true - format.shift - end + if format.first == ':' + hash = true + format.shift + end - if format.first == '&' - block = true - format.shift - end + if format.first == '&' + block = true + format.shift + end - # if the format string is not empty there's a bug in the C code, ignore it + # if the format string is not empty there's a bug in the C code, ignore it - args = [] - position = 1 + args = [] + position = 1 - (1...(position + lead)).each do |index| - args << "p#{index}" - end + (1...(position + lead)).each do |index| + args << "p#{index}" + end - position += lead + position += lead - (position...(position + opt)).each do |index| - args << "p#{index} = v#{index}" - end + (position...(position + opt)).each do |index| + args << "p#{index} = v#{index}" + end - position += opt + position += opt - if var - args << '*args' - position += 1 - end + if var + args << '*args' + position += 1 + end - (position...(position + trail)).each do |index| - args << "p#{index}" - end + (position...(position + trail)).each do |index| + args << "p#{index}" + end - position += trail + position += trail - if hash - args << "p#{position} = {}" - end + if hash + args << "p#{position} = {}" + end - args << '&block' if block + args << '&block' if block - "(#{args.join ', '})" - end + "(#{args.join ', '})" + end - ## - # Removes lines that are commented out that might otherwise get picked up - # when scanning for classes and methods + ## + # Removes lines that are commented out that might otherwise get picked up + # when scanning for classes and methods - def remove_commented_out_lines - @content = @content.gsub(%r%//.*rb_define_%, '//') - end + def remove_commented_out_lines + @content = @content.gsub(%r%//.*rb_define_%, '//') + end - ## - # Extracts the classes, modules, methods, attributes, constants and aliases - # from a C file and returns an RDoc::TopLevel for this file + ## + # Extracts the classes, modules, methods, attributes, constants and aliases + # from a C file and returns an RDoc::TopLevel for this file - def scan - remove_commented_out_lines + def scan + remove_commented_out_lines - do_classes_and_modules - do_missing + do_classes_and_modules + do_missing - do_constants - do_methods - do_includes - do_aliases - do_attrs + do_constants + do_methods + do_includes + do_aliases + do_attrs - @store.add_c_variables self + @store.add_c_variables self - @top_level - end + @top_level + end - ## - # Creates a RDoc::Comment instance. + ## + # Creates a RDoc::Comment instance. - def new_comment(text = nil, location = nil, language = nil) - RDoc::Comment.new(text, location, language).tap do |comment| - comment.format = @markup + def new_comment(text = nil, location = nil, language = nil) + ::RDoc::Comment.new(text, location, language).tap do |comment| + comment.format = @markup + end + end end end end diff --git a/lib/rdoc/parser/changelog.rb b/lib/rdoc/parser/changelog.rb index da79d7dd7e..71a579e648 100644 --- a/lib/rdoc/parser/changelog.rb +++ b/lib/rdoc/parser/changelog.rb @@ -1,377 +1,381 @@ # frozen_string_literal: true -## -# 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 - - if last =~ /\)\s*\z/ and continuation =~ /\A\(/ - last.sub!(/\)\s*\z/, ',') - continuation = continuation.sub(/\A\(/, '') - end - - if last =~ /\s\z/ - last << continuation - else - last << ' ' + continuation - end - end +module RDoc + class Parser + ## + # 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 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 + + if last =~ /\)\s*\z/ and continuation =~ /\A\(/ + last.sub!(/\)\s*\z/, ',') + continuation = continuation.sub(/\A\(/, '') + end - ## - # Creates an RDoc::Markup::Document given the +groups+ of ChangeLog entries. + if last =~ /\s\z/ + last << continuation + else + last << ' ' + continuation + end + end - def create_document(groups) - doc = RDoc::Markup::Document.new - doc.omit_headings_below = 2 - doc.file = @top_level + ## + # Creates an RDoc::Markup::Document given the +groups+ of ChangeLog entries. - doc << RDoc::Markup::Heading.new(1, File.basename(@file_name)) - doc << RDoc::Markup::BlankLine.new + def create_document(groups) + doc = ::RDoc::Markup::Document.new + doc.omit_headings_below = 2 + doc.file = @top_level - groups.sort_by do |day,| day end.reverse_each do |day, entries| - doc << RDoc::Markup::Heading.new(2, day.dup) - doc << RDoc::Markup::BlankLine.new + doc << ::RDoc::Markup::Heading.new(1, File.basename(@file_name)) + doc << ::RDoc::Markup::BlankLine.new - doc.concat create_entries entries - end + groups.sort_by do |day,| day end.reverse_each do |day, entries| + doc << ::RDoc::Markup::Heading.new(2, day.dup) + doc << ::RDoc::Markup::BlankLine.new - doc - end + doc.concat create_entries entries + end - ## - # Returns a list of ChangeLog entries an RDoc::Markup nodes for the given - # +entries+. + doc + end - def create_entries(entries) - out = [] + ## + # Returns a list of ChangeLog entries an RDoc::Markup nodes for the given + # +entries+. - entries.each do |entry, items| - out << RDoc::Markup::Heading.new(3, entry) - out << RDoc::Markup::BlankLine.new + def create_entries(entries) + out = [] - out << create_items(items) - end + entries.each do |entry, items| + out << ::RDoc::Markup::Heading.new(3, entry) + out << ::RDoc::Markup::BlankLine.new - out - end + out << create_items(items) + end - ## - # Returns an RDoc::Markup::List containing the given +items+ in the - # ChangeLog + out + end - def create_items(items) - list = RDoc::Markup::List.new :NOTE + ## + # Returns an RDoc::Markup::List containing the given +items+ in the + # ChangeLog - items.each do |item| - item =~ /\A(.*?(?:\([^)]+\))?):\s*/ + def create_items(items) + list = ::RDoc::Markup::List.new :NOTE - title = $1 - body = $' + items.each do |item| + item =~ /\A(.*?(?:\([^)]+\))?):\s*/ - paragraph = RDoc::Markup::Paragraph.new body - list_item = RDoc::Markup::ListItem.new title, paragraph - list << list_item - end + title = $1 + body = $' - list - end + paragraph = ::RDoc::Markup::Paragraph.new body + list_item = ::RDoc::Markup::ListItem.new title, paragraph + list << list_item + end - ## - # Groups +entries+ by date. - - def group_entries(entries) - @time_cache ||= {} - entries.group_by do |title, _| - begin - time = @time_cache[title] - (time || parse_date(title)).strftime '%Y-%m-%d' - rescue NoMethodError, ArgumentError - time, = title.split ' ', 2 - parse_date(time).strftime '%Y-%m-%d' + list end - end - end - ## - # Parse date in ISO-8601, RFC-2822, or default of Git - - def parse_date(date) - case date - when /\A\s*(\d+)-(\d+)-(\d+)(?:[ T](\d+):(\d+):(\d+) *([-+]\d\d):?(\d\d))?\b/ - Time.new($1, $2, $3, $4, $5, $6, ("#{$7}:#{$8}" if $7)) - when /\A\s*\w{3}, +(\d+) (\w{3}) (\d+) (\d+):(\d+):(\d+) *(?:([-+]\d\d):?(\d\d))\b/ - Time.new($3, $2, $1, $4, $5, $6, ("#{$7}:#{$8}" if $7)) - when /\A\s*\w{3} (\w{3}) +(\d+) (\d+) (\d+):(\d+):(\d+) *(?:([-+]\d\d):?(\d\d))\b/ - Time.new($3, $1, $2, $4, $5, $6, ("#{$7}:#{$8}" if $7)) - when /\A\s*\w{3} (\w{3}) +(\d+) (\d+):(\d+):(\d+) (\d+)\b/ - Time.new($6, $1, $2, $3, $4, $5) - else - raise ArgumentError, "bad date: #{date}" - 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 - @time_cache ||= {} - - if /\A((?:.*\n){,3})commit\s/ =~ @content - class << self; prepend Git; end - parse_info($1) - return parse_entries - end - - entries = [] - entry_name = nil - entry_body = [] - - @content.each_line do |line| - case line - when /^\s*$/ - next - when /^\w.*/ - entries << [entry_name, entry_body] if entry_name + ## + # Groups +entries+ by date. + + def group_entries(entries) + @time_cache ||= {} + entries.group_by do |title, _| + begin + time = @time_cache[title] + (time || parse_date(title)).strftime '%Y-%m-%d' + rescue NoMethodError, ArgumentError + time, = title.split ' ', 2 + parse_date(time).strftime '%Y-%m-%d' + end + end + end - entry_name = $& + ## + # Parse date in ISO-8601, RFC-2822, or default of Git + + def parse_date(date) + case date + when /\A\s*(\d+)-(\d+)-(\d+)(?:[ T](\d+):(\d+):(\d+) *([-+]\d\d):?(\d\d))?\b/ + Time.new($1, $2, $3, $4, $5, $6, ("#{$7}:#{$8}" if $7)) + when /\A\s*\w{3}, +(\d+) (\w{3}) (\d+) (\d+):(\d+):(\d+) *(?:([-+]\d\d):?(\d\d))\b/ + Time.new($3, $2, $1, $4, $5, $6, ("#{$7}:#{$8}" if $7)) + when /\A\s*\w{3} (\w{3}) +(\d+) (\d+) (\d+):(\d+):(\d+) *(?:([-+]\d\d):?(\d\d))\b/ + Time.new($3, $1, $2, $4, $5, $6, ("#{$7}:#{$8}" if $7)) + when /\A\s*\w{3} (\w{3}) +(\d+) (\d+):(\d+):(\d+) (\d+)\b/ + Time.new($6, $1, $2, $3, $4, $5) + else + raise ArgumentError, "bad date: #{date}" + end + end - begin - time = parse_date entry_name - @time_cache[entry_name] = time - rescue ArgumentError - entry_name = nil + ## + # 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 + @time_cache ||= {} + + if /\A((?:.*\n){,3})commit\s/ =~ @content + class << self; prepend Git; end + parse_info($1) + return parse_entries end + entries = [] + entry_name = nil entry_body = [] - when /^(\t| {8})?\*\s*(.*)/ # "\t* file.c (func): ..." - entry_body << $2.dup - when /^(\t| {8})?\s*(\(.*)/ # "\t(func): ..." - entry = $2 - if entry_body.last =~ /:/ - entry_body << entry.dup - else - continue_entry_body entry_body, entry - end - when /^(\t| {8})?\s*(.*)/ - continue_entry_body entry_body, $2 - end - end + @content.each_line do |line| + case line + when /^\s*$/ + next + when /^\w.*/ + entries << [entry_name, entry_body] if entry_name - entries << [entry_name, entry_body] if entry_name + entry_name = $& - entries.reject! do |(entry, _)| - entry == nil - end + begin + time = parse_date entry_name + @time_cache[entry_name] = time + rescue ArgumentError + entry_name = nil + end - entries - end + entry_body = [] + when /^(\t| {8})?\*\s*(.*)/ # "\t* file.c (func): ..." + entry_body << $2.dup + when /^(\t| {8})?\s*(\(.*)/ # "\t(func): ..." + entry = $2 - ## - # Converts the ChangeLog into an RDoc::Markup::Document + if entry_body.last =~ /:/ + entry_body << entry.dup + else + continue_entry_body entry_body, entry + end + when /^(\t| {8})?\s*(.*)/ + continue_entry_body entry_body, $2 + end + end - def scan - @time_cache = {} + entries << [entry_name, entry_body] if entry_name - entries = parse_entries - grouped_entries = group_entries entries + entries.reject! do |(entry, _)| + entry == nil + end - doc = create_document grouped_entries - comment = RDoc::Comment.new(@content) - comment.document = doc - @top_level.comment = comment + entries + end - @top_level - end + ## + # Converts the ChangeLog into an RDoc::Markup::Document - ## - # The extension for Git commit log + def scan + @time_cache = {} - module Git - ## - # Parses auxiliary info. Currently `base-url` to expand - # references is effective. + entries = parse_entries + grouped_entries = group_entries entries - def parse_info(info) - /^\s*base-url\s*=\s*(.*\S)/ =~ info - @base_url = $1 - end + doc = create_document grouped_entries + comment = ::RDoc::Comment.new(@content) + comment.document = doc + @top_level.comment = comment - ## - # Parses the entries in the Git commit logs - - def parse_entries - entries = [] - - @content.scan(/^commit\s+(\h{20})\h*\n((?:.+\n)*)\n((?: {4}.*\n+)*)/) do - entry_name, header, entry_body = $1, $2, $3.gsub(/^ {4}/, '') - # header = header.scan(/^ *(\S+?): +(.*)/).to_h - # date = header["CommitDate"] || header["Date"] - date = header[/^ *(?:Author)?Date: +(.*)/, 1] - author = header[/^ *Author: +(.*)/, 1] - begin - time = parse_date(header[/^ *CommitDate: +(.*)/, 1] || date) - @time_cache[entry_name] = time - author.sub!(/\s*<(.*)>/, '') - email = $1 - entries << [entry_name, [author, email, date, entry_body]] - rescue ArgumentError - end + @top_level end - entries - end + ## + # The extension for Git commit log - ## - # Returns a list of ChangeLog entries as - # RDoc::Parser::ChangeLog::Git::LogEntry list for the given - # +entries+. - - def create_entries(entries) - # git log entries have no strictly itemized style like the old - # style, just assume Markdown. - entries.map do |commit, entry| - LogEntry.new(@base_url, commit, *entry) - end - end + module Git + ## + # Parses auxiliary info. Currently `base-url` to expand + # references is effective. - LogEntry = Struct.new(:base, :commit, :author, :email, :date, :contents) do - HEADING_LEVEL = 3 - - def initialize(base, commit, author, email, date, contents) - case contents - when String - if base&.match(%r[\A([^:/]+:/+[^/]+/)[^/]+/[^/]+/]) - repo, host = $&, $1 - contents = contents.dup - # base: https://github.com/ruby/ruby/ - # Fix #15791 -> Fix [#15791](https://github.com/ruby/ruby/pull/15791) - # GH-15791 -> [GH-15791](https://github.com/ruby/ruby/pull/15791) - # (#15791) -> ([#15791](https://github.com/ruby/ruby/pull/15791)) - contents.gsub!(/\b(?:(?i:fix(?:e[sd])?) +)\K\#(\d+\b)|\bGH-(\d+)\b|\(\K\#(\d+)(?=\))/) do - "[#{$&}](#{repo}pull/#{$1 || $2 || $3})" - end - # repo#PR, repo@HASH - # ruby/ruby#15791 -> [ruby/ruby#15791](https://github.com/ruby/ruby/pull/15791) - # ruby/ruby@a8a989b6 -> [ruby/ruby@a8a989b6](https://github.com/ruby/ruby/commit/a8a989b6) - # ref in branckets is not extended - # [ruby/net-imap#543][ruby/ruby#15791] -> [ruby/net-imap#543][ruby/ruby#15791] - contents.gsub!(%r[(?/, '') + email = $1 + entries << [entry_name, [author, email, date, entry_body]] + rescue ArgumentError end end - case first = contents[0] - when RDoc::Markup::Paragraph - contents[0] = RDoc::Markup::Heading.new(HEADING_LEVEL + 1, first.text) - end + + entries end - super - end - def level - HEADING_LEVEL - end + ## + # Returns a list of ChangeLog entries as + # RDoc::Parser::ChangeLog::Git::LogEntry list for the given + # +entries+. - def aref - commit - end + def create_entries(entries) + # git log entries have no strictly itemized style like the old + # style, just assume Markdown. + entries.map do |commit, entry| + LogEntry.new(@base_url, commit, *entry) + end + end - def legacy_aref - "label-#{commit}" - end + LogEntry = Struct.new(:base, :commit, :author, :email, :date, :contents) do + HEADING_LEVEL = 3 + + def initialize(base, commit, author, email, date, contents) + case contents + when String + if base&.match(%r[\A([^:/]+:/+[^/]+/)[^/]+/[^/]+/]) + repo, host = $&, $1 + contents = contents.dup + # base: https://github.com/ruby/ruby/ + # Fix #15791 -> Fix [#15791](https://github.com/ruby/ruby/pull/15791) + # GH-15791 -> [GH-15791](https://github.com/ruby/ruby/pull/15791) + # (#15791) -> ([#15791](https://github.com/ruby/ruby/pull/15791)) + contents.gsub!(/\b(?:(?i:fix(?:e[sd])?) +)\K\#(\d+\b)|\bGH-(\d+)\b|\(\K\#(\d+)(?=\))/) do + "[#{$&}](#{repo}pull/#{$1 || $2 || $3})" + end + # repo#PR, repo@HASH + # ruby/ruby#15791 -> [ruby/ruby#15791](https://github.com/ruby/ruby/pull/15791) + # ruby/ruby@a8a989b6 -> [ruby/ruby@a8a989b6](https://github.com/ruby/ruby/commit/a8a989b6) + # ref in branckets is not extended + # [ruby/net-imap#543][ruby/ruby#15791] -> [ruby/net-imap#543][ruby/ruby#15791] + contents.gsub!(%r[(?(mod, name, mode) { - created = - case mode - when :class - mod.add_class(RDoc::NormalClass, name, 'Object').tap { |m| m.store = @store } - when :module - mod.add_module(RDoc::NormalModule, name).tap { |m| m.store = @store } - end - # add_class/add_module may return an existing object created by another - # file (in_files is not empty then), which must not be ignored here. - # Documentable again when reopened or receiving contents outside the region. - created.ignore if document_suppressed? && created.in_files.empty? - created - } - if root_name.empty? - mod = @top_level - else - @module_nesting.reverse_each do |nesting, singleton| - next if singleton - mod = nesting.get_module_named(root_name) - break if mod - # If a constant is found and it is not a module or class, RDoc can't document about it. - # Return an anonymous module to avoid wrong document creation. - return RDoc::NormalModule.new(nil) if nesting.find_constant_named(root_name) + # Adds a method defined by `def` syntax + + def add_method(method_name, receiver_name:, receiver_fallback_type:, visibility:, singleton:, params:, calls_super:, block_params:, tokens:, start_line:, args_end_line:, end_line:) + comment, directives, type_signature_lines = consecutive_comment(start_line) + apply_document_control_directive(directives) if directives + handle_code_object_directives(@container, directives) if directives + # Resolve receiver after applying directives so that a namespace created + # here is marked as ignored when the comment starts a :stopdoc: region + receiver = receiver_name ? find_or_create_lexical_module_path(receiver_name, receiver_fallback_type) : @container + + internal_add_method( + method_name, + receiver, + comment: comment, + directives: directives, + modifier_comment_lines: [start_line, args_end_line, end_line].uniq, + line_no: start_line, + visibility: visibility, + singleton: singleton, + params: params, + calls_super: calls_super, + block_params: block_params, + tokens: tokens, + type_signature_lines: type_signature_lines + ) end - last_nesting, = @module_nesting.reverse_each.find { |_, singleton| !singleton } - return mod || add_module.call(last_nesting, root_name, create_mode) unless name - mod ||= add_module.call(last_nesting, root_name, :module) - end - path.each do |name| - mod = mod.get_module_named(name) || add_module.call(mod, name, :module) - end - mod.get_module_named(name) || add_module.call(mod, name, create_mode) - end - # Resolves constant path to a full path by searching module nesting + private def internal_add_method(method_name, container, comment:, dont_rename_initialize: false, directives:, modifier_comment_lines: nil, line_no:, visibility:, singleton:, params:, calls_super:, block_params:, tokens:, type_signature_lines: nil) # :nodoc: + meth = ::RDoc::AnyMethod.new(method_name, singleton: singleton) + meth.comment = comment + handle_code_object_directives(meth, directives) if directives + modifier_comment_lines&.each do |line| + handle_modifier_directive(meth, line) + end + return if document_suppressed? + return unless should_document?(meth) - def resolve_constant_path(constant_path) - owner_name, path = constant_path.split('::', 2) - return constant_path if owner_name.empty? # ::Foo, ::Foo::Bar - mod = nil - @module_nesting.reverse_each do |nesting, singleton| - next if singleton - mod = nesting.get_module_named(owner_name) - break if mod - end - mod ||= @top_level.get_module_named(owner_name) - [mod.full_name, path].compact.join('::') if mod - end + mark_container_documentable(container) - # Returns a pair of owner module and constant name from a given constant path - # using Ruby lexical nesting. Creates owner module if it does not exist. - - def find_or_create_lexical_constant_owner_name(constant_path) - const_path, colon, name = constant_path.rpartition('::') - if colon.empty? # class Foo - # Within `class C` or `module C`, owner is C(== current container) - # Within `class <(mod, name, mode) { + created = + case mode + when :class + mod.add_class(::RDoc::NormalClass, name, 'Object').tap { |m| m.store = @store } + when :module + mod.add_module(::RDoc::NormalModule, name).tap { |m| m.store = @store } + end + # add_class/add_module may return an existing object created by another + # file (in_files is not empty then), which must not be ignored here. + # Documentable again when reopened or receiving contents outside the region. + created.ignore if document_suppressed? && created.in_files.empty? + created + } + if root_name.empty? + mod = @top_level + else + @module_nesting.reverse_each do |nesting, singleton| + next if singleton + mod = nesting.get_module_named(root_name) + break if mod + # If a constant is found and it is not a module or class, RDoc can't document about it. + # Return an anonymous module to avoid wrong document creation. + return ::RDoc::NormalModule.new(nil) if nesting.find_constant_named(root_name) + end + last_nesting, = @module_nesting.reverse_each.find { |_, singleton| !singleton } + return mod || add_module.call(last_nesting, root_name, create_mode) unless name + mod ||= add_module.call(last_nesting, root_name, :module) + end + path.each do |name| + mod = mod.get_module_named(name) || add_module.call(mod, name, :module) + end + mod.get_module_named(name) || add_module.call(mod, name, create_mode) end - # Superclass with the same full path and superclass for BasicObject are not allowed - if superclass_name && mod.full_name != superclass_full_path && mod.full_name != 'BasicObject' - if superclass - mod.superclass = superclass - elsif mod.superclass.nil? || (mod.superclass.is_a?(String) || mod.superclass.name == 'Object') && mod.superclass != superclass_full_path - mod.superclass = superclass_full_path + # Resolves constant path to a full path by searching module nesting + + def resolve_constant_path(constant_path) + owner_name, path = constant_path.split('::', 2) + return constant_path if owner_name.empty? # ::Foo, ::Foo::Bar + mod = nil + @module_nesting.reverse_each do |nesting, singleton| + next if singleton + mod = nesting.get_module_named(owner_name) + break if mod end + mod ||= @top_level.get_module_named(owner_name) + [mod.full_name, path].compact.join('::') if mod end - else - mod = owner.modules_hash[name] - unless mod - mod = owner.add_module(RDoc::NormalModule, name) - mod.ignore if document_suppressed? && mod.in_files.empty? + + # Returns a pair of owner module and constant name from a given constant path + # using Ruby lexical nesting. Creates owner module if it does not exist. + + def find_or_create_lexical_constant_owner_name(constant_path) + const_path, colon, name = constant_path.rpartition('::') + if colon.empty? # class Foo + # Within `class C` or `module C`, owner is C(== current container) + # Within `class < 0 - colored_tokens - end +module RDoc + class Parser + # Ruby code syntax highlighter. + # Colorize result is an array of +RDoc::Parser::RubyColorizer::ColoredToken+ + # Actual color for each token kind is determined elsewhere (e.g., HTML generator) + module RubyColorizer + + ColoredToken = Struct.new(:kind, :text) + + # Prism operator token types except assignment '=' + OP_TOKENS = %i[ + AMPERSAND AMPERSAND_AMPERSAND + BANG BANG_EQUAL BANG_TILDE CARET COLON COLON_COLON + EQUAL_EQUAL EQUAL_GREATER EQUAL_TILDE + GREATER GREATER_GREATER + LESS LESS_EQUAL LESS_EQUAL_GREATER LESS_LESS + MINUS MINUS_GREATER PERCENT PIPE PIPE_PIPE PLUS + QUESTION_MARK SLASH STAR STAR_STAR TILDE + UAMPERSAND UMINUS UPLUS USTAR USTAR_STAR + ].to_set + + # Prism token type to ColoredToken kind map + TOKEN_TYPE_MAP = { + IDENTIFIER: :identifier, + METHOD_NAME: :identifier, + INSTANCE_VARIABLE: :ivar, + CLASS_VARIABLE: :identifier, + GLOBAL_VARIABLE: :identifier, + BACK_REFERENCE: :identifier, + NUMBERED_REFERENCE: :identifier, + CONSTANT: :constant, + LABEL: :value, + INTEGER: :value, + INTEGER_IMAGINARY: :value, + INTEGER_RATIONAL: :value, + INTEGER_RATIONAL_IMAGINARY: :value, + FLOAT: :value, + FLOAT_IMAGINARY: :value, + FLOAT_RATIONAL: :value, + FLOAT_RATIONAL_IMAGINARY: :value, + COMMENT: :comment, + EMBDOC_BEGIN: :comment, + EMBDOC_LINE: :comment, + EMBDOC_END: :comment + } - private + class << self - def slice_by_location(items, start_offset, end_offset) - start_index = items.bsearch_index { |item| item.location.end_offset > start_offset } || items.size - end_index = items.bsearch_index { |item| item.location.start_offset >= end_offset } || items.size - items[start_index...end_index] - end + # Colorize the entire +code+ and returns colored token stream. + def colorize(code) + result = Prism.parse_lex(code) + program_node, unordered_tokens = result.value + prism_tokens = unordered_tokens.map(&:first).sort_by! { |token| token.location.start_offset } + partial_colorize(code, program_node, prism_tokens, 0, code.bytesize) + end - # Unify prior tokens and normal tokens into a single token stream. - # Prior tokens have higher priority than normal tokens. - # Also adds missing text (spaces, newlines, etc.) as :plain tokens - # so that the entire range is covered. - def unify_tokens(whole_code, prior_tokens, normal_tokens, start_offset, end_offset) - tokens = [] - offset = start_offset + # Colorize partial +node+ in +whole_code+ and returns colored token stream. + def partial_colorize(whole_code, node, prism_tokens, start_offset = nil, end_offset = nil) + start_offset ||= node.location.start_offset + end_offset ||= node.location.end_offset + visitor = NodeColorizeVisitor.new + node.accept(visitor) + prior_tokens = visitor.tokens.sort_by {|_, start_offset, _| start_offset } + normal_tokens = normal_tokens(slice_by_location(prism_tokens, start_offset, end_offset)) + colored_tokens = unify_tokens(whole_code, prior_tokens, normal_tokens, start_offset, end_offset) + colored_tokens.unshift(ColoredToken.new(:plain, ' ' * node.location.start_column)) if node.location.start_column > 0 + colored_tokens + end - # Add missing text such as spaces and newlines as a separate :plain token - flush = -> next_offset { - return if offset == next_offset + private - whole_code.byteslice(offset...next_offset).scan(/\n|\s+|[^\s]+/) do |text| - tokens << ColoredToken.new(:plain, text) + def slice_by_location(items, start_offset, end_offset) + start_index = items.bsearch_index { |item| item.location.end_offset > start_offset } || items.size + end_index = items.bsearch_index { |item| item.location.start_offset >= end_offset } || items.size + items[start_index...end_index] end - } - until prior_tokens.empty? && normal_tokens.empty? - ptok = prior_tokens.first - ntok = normal_tokens.first - if ntok && (!ptok || ntok[2] <= ptok[1]) - token = normal_tokens.shift - else - token = prior_tokens.shift + # Unify prior tokens and normal tokens into a single token stream. + # Prior tokens have higher priority than normal tokens. + # Also adds missing text (spaces, newlines, etc.) as :plain tokens + # so that the entire range is covered. + def unify_tokens(whole_code, prior_tokens, normal_tokens, start_offset, end_offset) + tokens = [] + offset = start_offset + + # Add missing text such as spaces and newlines as a separate :plain token + flush = -> next_offset { + return if offset == next_offset + + whole_code.byteslice(offset...next_offset).scan(/\n|\s+|[^\s]+/) do |text| + tokens << ColoredToken.new(:plain, text) + end + } + + until prior_tokens.empty? && normal_tokens.empty? + ptok = prior_tokens.first + ntok = normal_tokens.first + if ntok && (!ptok || ntok[2] <= ptok[1]) + token = normal_tokens.shift + else + token = prior_tokens.shift + end + kind, start_pos, end_pos = token + next if start_pos < offset + + flush.call(start_pos) + tokens << ColoredToken.new(kind, whole_code.byteslice(start_pos...end_pos)) + offset = end_pos + end + flush.call(end_offset) + tokens end - kind, start_pos, end_pos = token - next if start_pos < offset - - flush.call(start_pos) - tokens << ColoredToken.new(kind, whole_code.byteslice(start_pos...end_pos)) - offset = end_pos - end - flush.call(end_offset) - tokens - end - # Convert normal Prism tokens to [kind, start_offset, end_offset] - def normal_tokens(tokens) - tokens.map do |token,| - kind = - if token.type.start_with?('KEYWORD_') - :keyword - elsif OP_TOKENS.include?(token.type.to_sym) - :operator - else - TOKEN_TYPE_MAP[token.type] || :plain + # Convert normal Prism tokens to [kind, start_offset, end_offset] + def normal_tokens(tokens) + tokens.map do |token,| + kind = + if token.type.start_with?('KEYWORD_') + :keyword + elsif OP_TOKENS.include?(token.type.to_sym) + :operator + else + TOKEN_TYPE_MAP[token.type] || :plain + end + [kind, token.location.start_offset, token.location.end_offset] end - [kind, token.location.start_offset, token.location.end_offset] + end end - end - end - # Visitor to determine node colorizing which can't be determined by tokens. - # STRING_CONTENT/EMBEXPR_BEGIN/EMBEXPR_END in string/regexp/symbol have different colorizing - class NodeColorizeVisitor < Prism::Visitor # :nodoc: - attr_reader :tokens + # Visitor to determine node colorizing which can't be determined by tokens. + # STRING_CONTENT/EMBEXPR_BEGIN/EMBEXPR_END in string/regexp/symbol have different colorizing + class NodeColorizeVisitor < Prism::Visitor # :nodoc: + attr_reader :tokens - def initialize - @tokens = [] - end + def initialize + @tokens = [] + end - def visit_symbol_node(node) - # SymbolNode#location may contain heredoc content and closing - # e.g., `<; end` - push_location(:identifier, node.name_loc) - super - end + def visit_def_node(node) + # For special colorizing of method name in def node + # e.g., `def <=>; end` + push_location(:identifier, node.name_loc) + super + end - private + private - def push_location(kind, location) - # Only push tokens that have a non-zero length - if location && location.start_offset < location.end_offset - @tokens << [kind, location.start_offset, location.end_offset] - end - end + def push_location(kind, location) + # Only push tokens that have a non-zero length + if location && location.start_offset < location.end_offset + @tokens << [kind, location.start_offset, location.end_offset] + end + end - def handle_interpolated_parts(kind, parts) - # StringNode, EmbeddedStatementsNode brackets, and EmbeddedVariableNode hash in - # interpolated regexp/symbol/string parts should be colored as regexp/symbol/string respectively. - parts.each do |part| - case part - when Prism::StringNode - # InterpolatedStringNode#parts may have its own opening/closing. e.g., `'a' "b"` - push_location(kind, part.opening_loc) - push_location(kind, part.content_loc) - push_location(kind, part.closing_loc) - when Prism::InterpolatedStringNode - # InterpolatedStringNode#parts may contain InterpolatedStringNode. e.g., `'a' "#{}"` - part.accept(self) - when Prism::EmbeddedStatementsNode - push_location(kind, part.opening_loc) - push_location(kind, part.closing_loc) - part.accept(self) - when Prism::EmbeddedVariableNode - push_location(kind, part.operator_loc) + def handle_interpolated_parts(kind, parts) + # StringNode, EmbeddedStatementsNode brackets, and EmbeddedVariableNode hash in + # interpolated regexp/symbol/string parts should be colored as regexp/symbol/string respectively. + parts.each do |part| + case part + when Prism::StringNode + # InterpolatedStringNode#parts may have its own opening/closing. e.g., `'a' "b"` + push_location(kind, part.opening_loc) + push_location(kind, part.content_loc) + push_location(kind, part.closing_loc) + when Prism::InterpolatedStringNode + # InterpolatedStringNode#parts may contain InterpolatedStringNode. e.g., `'a' "#{}"` + part.accept(self) + when Prism::EmbeddedStatementsNode + push_location(kind, part.opening_loc) + push_location(kind, part.closing_loc) + part.accept(self) + when Prism::EmbeddedVariableNode + push_location(kind, part.operator_loc) + end + end end end + + private_constant :NodeColorizeVisitor end end - - private_constant :NodeColorizeVisitor end diff --git a/lib/rdoc/parser/simple.rb b/lib/rdoc/parser/simple.rb index a0edca1b33..8d0308f0dc 100644 --- a/lib/rdoc/parser/simple.rb +++ b/lib/rdoc/parser/simple.rb @@ -1,44 +1,48 @@ # frozen_string_literal: true -## -# Parse a non-source file. We basically take the whole thing as one big -# comment. +module RDoc + class Parser + ## + # Parse a non-source file. We basically take the whole thing as one big + # comment. -class RDoc::Parser::Simple < RDoc::Parser + class Simple < ::RDoc::Parser - include RDoc::Parser::Text + include ::RDoc::Parser::Text - parse_files_matching(//) + parse_files_matching(//) - attr_reader :content # :nodoc: + attr_reader :content # :nodoc: - ## - # Prepare to parse a plain file + ## + # Prepare to parse a plain file - def initialize(top_level, content, options, stats) - super + def initialize(top_level, content, options, stats) + super - preprocess = RDoc::Markup::PreProcess.new @file_name, @options.rdoc_include + preprocess = ::RDoc::Markup::PreProcess.new @file_name, @options.rdoc_include - content = RDoc::Text.expand_tabs(@content) - @content, = preprocess.run_pre_processes(content, @top_level, 1, :simple) - end + content = ::RDoc::Text.expand_tabs(@content) + @content, = preprocess.run_pre_processes(content, @top_level, 1, :simple) + end - ## - # Extract the file contents and attach them to the TopLevel as a comment + ## + # Extract the file contents and attach them to the TopLevel as a comment - def scan - content = remove_coding_comment @content + def scan + content = remove_coding_comment @content - comment = RDoc::Comment.new content, @top_level + comment = ::RDoc::Comment.new content, @top_level - @top_level.comment = comment - @top_level - end + @top_level.comment = comment + @top_level + end - ## - # Removes the encoding magic comment from +text+ + ## + # Removes the encoding magic comment from +text+ - def remove_coding_comment(text) - text.sub(/\A# .*coding[=:].*$/, '') + def remove_coding_comment(text) + text.sub(/\A# .*coding[=:].*$/, '') + end + end end end diff --git a/lib/rdoc/parser/text.rb b/lib/rdoc/parser/text.rb index 5095d8cc64..46fb8efec3 100644 --- a/lib/rdoc/parser/text.rb +++ b/lib/rdoc/parser/text.rb @@ -1,11 +1,15 @@ # frozen_string_literal: true -## -# Indicates this parser is text and doesn't contain code constructs. -# -# Include this module in a RDoc::Parser subclass to make it show up as a file, -# not as part of a class or module. -#-- -# This is not named File to avoid overriding ::File +module RDoc + class Parser + ## + # Indicates this parser is text and doesn't contain code constructs. + # + # Include this module in a RDoc::Parser subclass to make it show up as a file, + # not as part of a class or module. + #-- + # This is not named File to avoid overriding ::File -module RDoc::Parser::Text + module Text + end + end end diff --git a/lib/rdoc/rd.rb b/lib/rdoc/rd.rb index e845543ac4..8ebdb875f0 100644 --- a/lib/rdoc/rd.rb +++ b/lib/rdoc/rd.rb @@ -1,99 +1,101 @@ # frozen_string_literal: true -## -# RDoc::RD implements the RD format from the rdtool gem. -# -# To choose RD as your only default format see -# RDoc::Options@Saved+Options for instructions on setting up a -# .doc_options file to store your project default. -# -# == LICENSE -# -# The grammar that produces RDoc::RD::BlockParser and RDoc::RD::InlineParser -# is included in RDoc under the Ruby License. -# -# You can find the original source for rdtool at -# https://github.com/uwabami/rdtool/ -# -# You can use, re-distribute or change these files under Ruby's License or GPL. -# -# 1. You may make and give away verbatim copies of the source form of the -# software without restriction, provided that you duplicate all of the -# original copyright notices and associated disclaimers. -# -# 2. You may modify your copy of the software in any way, provided that -# you do at least ONE of the following: -# -# a. place your modifications in the Public Domain or otherwise -# make them Freely Available, such as by posting said -# modifications to Usenet or an equivalent medium, or by allowing -# the author to include your modifications in the software. -# -# b. use the modified software only within your corporation or -# organization. -# -# c. give non-standard binaries non-standard names, with -# instructions on where to get the original software distribution. -# -# d. make other distribution arrangements with the author. -# -# 3. You may distribute the software in object code or binary form, -# provided that you do at least ONE of the following: -# -# a. distribute the binaries and library files of the software, -# together with instructions (in the manual page or equivalent) -# on where to get the original distribution. -# -# b. accompany the distribution with the machine-readable source of -# the software. -# -# c. give non-standard binaries non-standard names, with -# instructions on where to get the original software distribution. -# -# d. make other distribution arrangements with the author. -# -# 4. You may modify and include the part of the software into any other -# software (possibly commercial). But some files in the distribution -# are not written by the author, so that they are not under these terms. -# -# For the list of those files and their copying conditions, see the -# file LEGAL. -# -# 5. The scripts and library files supplied as input to or produced as -# output from the software do not automatically fall under the -# copyright of the software, but belong to whomever generated them, -# and may be sold commercially, and may be aggregated with this -# software. -# -# 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR -# IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -# PURPOSE. +module RDoc + ## + # RDoc::RD implements the RD format from the rdtool gem. + # + # To choose RD as your only default format see + # RDoc::Options@Saved+Options for instructions on setting up a + # .doc_options file to store your project default. + # + # == LICENSE + # + # The grammar that produces RDoc::RD::BlockParser and RDoc::RD::InlineParser + # is included in RDoc under the Ruby License. + # + # You can find the original source for rdtool at + # https://github.com/uwabami/rdtool/ + # + # You can use, re-distribute or change these files under Ruby's License or GPL. + # + # 1. You may make and give away verbatim copies of the source form of the + # software without restriction, provided that you duplicate all of the + # original copyright notices and associated disclaimers. + # + # 2. You may modify your copy of the software in any way, provided that + # you do at least ONE of the following: + # + # a. place your modifications in the Public Domain or otherwise + # make them Freely Available, such as by posting said + # modifications to Usenet or an equivalent medium, or by allowing + # the author to include your modifications in the software. + # + # b. use the modified software only within your corporation or + # organization. + # + # c. give non-standard binaries non-standard names, with + # instructions on where to get the original software distribution. + # + # d. make other distribution arrangements with the author. + # + # 3. You may distribute the software in object code or binary form, + # provided that you do at least ONE of the following: + # + # a. distribute the binaries and library files of the software, + # together with instructions (in the manual page or equivalent) + # on where to get the original distribution. + # + # b. accompany the distribution with the machine-readable source of + # the software. + # + # c. give non-standard binaries non-standard names, with + # instructions on where to get the original software distribution. + # + # d. make other distribution arrangements with the author. + # + # 4. You may modify and include the part of the software into any other + # software (possibly commercial). But some files in the distribution + # are not written by the author, so that they are not under these terms. + # + # For the list of those files and their copying conditions, see the + # file LEGAL. + # + # 5. The scripts and library files supplied as input to or produced as + # output from the software do not automatically fall under the + # copyright of the software, but belong to whomever generated them, + # and may be sold commercially, and may be aggregated with this + # software. + # + # 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR + # IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE. -class RDoc::RD + class RD - ## - # Parses +rd+ source and returns an RDoc::Markup::Document. If the - # =begin or =end lines are missing they will be added. + ## + # Parses +rd+ source and returns an RDoc::Markup::Document. If the + # =begin or =end lines are missing they will be added. - def self.parse(rd) - rd = rd.lines.to_a + def self.parse(rd) + rd = rd.lines.to_a - if rd.find { |i| /\S/ === i } and !rd.find{|i| /^=begin\b/ === i } - rd.unshift("=begin\n").push("=end\n") - end + if rd.find { |i| /\S/ === i } and !rd.find{|i| /^=begin\b/ === i } + rd.unshift("=begin\n").push("=end\n") + end - parser = RDoc::RD::BlockParser.new - document = parser.parse rd + parser = ::RDoc::RD::BlockParser.new + document = parser.parse rd - # isn't this always true? - document.parts.shift if RDoc::Markup::BlankLine === document.parts.first - document.parts.pop if RDoc::Markup::BlankLine === document.parts.last + # isn't this always true? + document.parts.shift if ::RDoc::Markup::BlankLine === document.parts.first + document.parts.pop if ::RDoc::Markup::BlankLine === document.parts.last - document - end + document + end - autoload :BlockParser, "#{__dir__}/rd/block_parser" - autoload :InlineParser, "#{__dir__}/rd/inline_parser" - autoload :Inline, "#{__dir__}/rd/inline" + autoload :BlockParser, "#{__dir__}/rd/block_parser" + autoload :InlineParser, "#{__dir__}/rd/inline_parser" + autoload :Inline, "#{__dir__}/rd/inline" + end end diff --git a/lib/rdoc/rd/inline.rb b/lib/rdoc/rd/inline.rb index 1c76e81f76..6a02e1136e 100644 --- a/lib/rdoc/rd/inline.rb +++ b/lib/rdoc/rd/inline.rb @@ -1,71 +1,75 @@ # frozen_string_literal: true -## -# Inline keeps track of markup and labels to create proper links. +module RDoc + class RD + ## + # Inline keeps track of markup and labels to create proper links. -class RDoc::RD::Inline + class Inline - ## - # The text of the reference + ## + # The text of the reference - attr_reader :reference + attr_reader :reference - ## - # The markup of this reference in RDoc format + ## + # The markup of this reference in RDoc format - attr_reader :rdoc + attr_reader :rdoc - ## - # Creates a new Inline for +rdoc+ and +reference+. - # - # +rdoc+ may be another Inline or a String. If +reference+ is not given it - # will use the text from +rdoc+. + ## + # Creates a new Inline for +rdoc+ and +reference+. + # + # +rdoc+ may be another Inline or a String. If +reference+ is not given it + # will use the text from +rdoc+. - def self.new(rdoc, reference = rdoc) - if self === rdoc and reference.equal? rdoc - rdoc - else - super - end - end + def self.new(rdoc, reference = rdoc) + if self === rdoc and reference.equal? rdoc + rdoc + else + super + end + end - ## - # Initializes the Inline with +rdoc+ and +inline+ + ## + # Initializes the Inline with +rdoc+ and +inline+ - def initialize(rdoc, reference) # :not-new: - @reference = reference.equal?(rdoc) ? reference.dup : reference + def initialize(rdoc, reference) # :not-new: + @reference = reference.equal?(rdoc) ? reference.dup : reference - # unpack - @reference = @reference.reference if self.class === @reference - @rdoc = rdoc - end + # unpack + @reference = @reference.reference if self.class === @reference + @rdoc = rdoc + end - def ==(other) # :nodoc: - self.class === other and - @reference == other.reference and @rdoc == other.rdoc - end + def ==(other) # :nodoc: + self.class === other and + @reference == other.reference and @rdoc == other.rdoc + end - ## - # Appends +more+ to this inline. +more+ may be a String or another Inline. - - def append(more) - case more - when String - @reference += more - @rdoc += more - when RDoc::RD::Inline - @reference += more.reference - @rdoc += more.rdoc - else - raise "unknown thingy #{more}" - end + ## + # Appends +more+ to this inline. +more+ may be a String or another Inline. - self - end + def append(more) + case more + when String + @reference += more + @rdoc += more + when ::RDoc::RD::Inline + @reference += more.reference + @rdoc += more.rdoc + else + raise "unknown thingy #{more}" + end - def inspect # :nodoc: - "(inline: #{self})" - end + self + end + + def inspect # :nodoc: + "(inline: #{self})" + end - alias to_s rdoc # :nodoc: + alias to_s rdoc # :nodoc: + end + end end diff --git a/lib/rdoc/rdoc.rb b/lib/rdoc/rdoc.rb index fefa83c6ba..1050ca3618 100644 --- a/lib/rdoc/rdoc.rb +++ b/lib/rdoc/rdoc.rb @@ -7,191 +7,192 @@ require 'time' require_relative 'rbs_helper' -## -# This is the driver for generating RDoc output. It handles file parsing and -# generation of output. -# -# To use this class to generate RDoc output via the API, the recommended way -# is: -# -# rdoc = RDoc::RDoc.new -# options = RDoc::Options.load_options # returns an RDoc::Options instance -# # set extra options -# rdoc.document options -# -# You can also generate output like the +rdoc+ executable: -# -# rdoc = RDoc::RDoc.new -# rdoc.document argv -# -# Where +argv+ is an array of strings, each corresponding to an argument you'd -# give rdoc on the command line. See rdoc --help for details. - -class RDoc::RDoc - - @current = nil - +module RDoc ## - # This is the list of supported output generators + # This is the driver for generating RDoc output. It handles file parsing and + # generation of output. + # + # To use this class to generate RDoc output via the API, the recommended way + # is: + # + # rdoc = RDoc::RDoc.new + # options = RDoc::Options.load_options # returns an RDoc::Options instance + # # set extra options + # rdoc.document options + # + # You can also generate output like the +rdoc+ executable: + # + # rdoc = RDoc::RDoc.new + # rdoc.document argv + # + # Where +argv+ is an array of strings, each corresponding to an argument you'd + # give rdoc on the command line. See rdoc --help for details. - GENERATORS = {} + class RDoc - ## - # List of directory names always skipped + @current = nil - UNCONDITIONALLY_SKIPPED_DIRECTORIES = %w[CVS .svn .git].freeze + ## + # This is the list of supported output generators - ## - # List of directory names skipped if test suites should be skipped + GENERATORS = {} - TEST_SUITE_DIRECTORY_NAMES = %w[spec test].freeze + ## + # List of directory names always skipped + UNCONDITIONALLY_SKIPPED_DIRECTORIES = %w[CVS .svn .git].freeze - ## - # Generator instance used for creating output + ## + # List of directory names skipped if test suites should be skipped - attr_accessor :generator + TEST_SUITE_DIRECTORY_NAMES = %w[spec test].freeze - ## - # Hash of files and their last modified times. - attr_reader :last_modified + ## + # Generator instance used for creating output - ## - # RDoc options + attr_accessor :generator - attr_accessor :options + ## + # Hash of files and their last modified times. - ## - # Accessor for statistics. Available after each call to parse_files + attr_reader :last_modified - attr_reader :stats + ## + # RDoc options - ## - # The current documentation store + attr_accessor :options - attr_accessor :store + ## + # Accessor for statistics. Available after each call to parse_files - ## - # Add +klass+ that can generate output after parsing + attr_reader :stats - def self.add_generator(klass) - name = klass.name.sub(/^RDoc::Generator::/, '').downcase - GENERATORS[name] = klass - end + ## + # The current documentation store - ## - # Active RDoc::RDoc instance + attr_accessor :store - def self.current - @current - end + ## + # Add +klass+ that can generate output after parsing - ## - # Sets the active RDoc::RDoc instance + def self.add_generator(klass) + name = klass.name.sub(/^RDoc::Generator::/, '').downcase + GENERATORS[name] = klass + end - def self.current=(rdoc) - @current = rdoc - end + ## + # Active RDoc::RDoc instance - ## - # Creates a new RDoc::RDoc instance. Call #document to parse files and - # generate documentation. - - def initialize - @current = nil - @generator = nil - @last_modified = {} - @old_siginfo = nil - @options = nil - @stats = nil - @store = nil - end + def self.current + @current + end - ## - # Report an error message and exit + ## + # Sets the active RDoc::RDoc instance - def error(msg) - raise RDoc::Error, msg - end + def self.current=(rdoc) + @current = rdoc + end - ## - # Gathers a set of parseable files from the files and directories listed in - # +files+. + ## + # Creates a new RDoc::RDoc instance. Call #document to parse files and + # generate documentation. + + def initialize + @current = nil + @generator = nil + @last_modified = {} + @old_siginfo = nil + @options = nil + @stats = nil + @store = nil + end - def gather_files(files) - files = [@options.root.to_s] if files.empty? + ## + # Report an error message and exit - file_list = normalized_file_list files, true, @options.exclude + def error(msg) + raise ::RDoc::Error, msg + end + + ## + # Gathers a set of parseable files from the files and directories listed in + # +files+. + + def gather_files(files) + files = [@options.root.to_s] if files.empty? - file_list = remove_duplicate_files(remove_unparseable(file_list)) + file_list = normalized_file_list files, true, @options.exclude - if file_list.count {|name, mtime| - file_list[name] = @last_modified[name] unless mtime - mtime - } > 0 - @last_modified.replace file_list - file_list.keys.sort - else - [] + file_list = remove_duplicate_files(remove_unparseable(file_list)) + + if file_list.count {|name, mtime| + file_list[name] = @last_modified[name] unless mtime + mtime + } > 0 + @last_modified.replace file_list + file_list.keys.sort + else + [] + end end - end - ## - # Turns RDoc from stdin into HTML + ## + # Turns RDoc from stdin into HTML - def handle_pipe - @html = RDoc::Markup::ToHtml.new(pipe: @options.pipe, output_decoration: @options.output_decoration) + def handle_pipe + @html = ::RDoc::Markup::ToHtml.new(pipe: @options.pipe, output_decoration: @options.output_decoration) - parser = RDoc::Text::MARKUP_FORMAT[@options.markup] + parser = ::RDoc::Text::MARKUP_FORMAT[@options.markup] - document = parser.parse $stdin.read + document = parser.parse $stdin.read - out = @html.convert document + out = @html.convert document - $stdout.write out - end + $stdout.write out + end - ## - # Installs a siginfo handler that prints the current filename. + ## + # Installs a siginfo handler that prints the current filename. - def install_siginfo_handler - return unless Signal.list.include? 'INFO' + def install_siginfo_handler + return unless Signal.list.include? 'INFO' - @old_siginfo = trap 'INFO' do - puts @current if @current + @old_siginfo = trap 'INFO' do + puts @current if @current + end end - end - ## - # Create an output dir if it doesn't exist. If it does exist, but doesn't - # contain the flag file created.rid then we refuse to use it, as - # we may clobber some manually generated documentation + ## + # Create an output dir if it doesn't exist. If it does exist, but doesn't + # contain the flag file created.rid then we refuse to use it, as + # we may clobber some manually generated documentation - def setup_output_dir(dir, force) - flag_file = output_flag_file dir + def setup_output_dir(dir, force) + flag_file = output_flag_file dir - last = {} + last = {} - if @options.dry_run - # do nothing - elsif File.exist? dir - error "#{dir} exists and is not a directory" unless File.directory? dir + if @options.dry_run + # do nothing + elsif File.exist? dir + error "#{dir} exists and is not a directory" unless File.directory? dir - begin - File.open flag_file do |io| - unless force - Time.parse io.gets - - io.each do |line| - file, time = line.split "\t", 2 - time = Time.parse(time) rescue next - last[file] = time + begin + File.open flag_file do |io| + unless force + Time.parse io.gets + + io.each do |line| + file, time = line.split "\t", 2 + time = Time.parse(time) rescue next + last[file] = time + end end end - end - rescue SystemCallError, TypeError - error <<-ERROR + rescue SystemCallError, TypeError + error <<-ERROR Directory #{dir} already exists, but it looks like it isn't an RDoc directory. @@ -200,179 +201,179 @@ def setup_output_dir(dir, force) option) ERROR - end unless @options.force_output - else - FileUtils.mkdir_p dir - FileUtils.touch flag_file + end unless @options.force_output + else + FileUtils.mkdir_p dir + FileUtils.touch flag_file + end + + last end - last - end + ## + # Update the flag file in an output directory. - ## - # Update the flag file in an output directory. - - def update_output_dir(op_dir, time, last = {}) - return if @options.dry_run or not @options.update_output_dir - unless ENV['SOURCE_DATE_EPOCH'].nil? - time = Time.at(ENV['SOURCE_DATE_EPOCH'].to_i).gmtime - end + def update_output_dir(op_dir, time, last = {}) + return if @options.dry_run or not @options.update_output_dir + unless ENV['SOURCE_DATE_EPOCH'].nil? + time = Time.at(ENV['SOURCE_DATE_EPOCH'].to_i).gmtime + end - File.open output_flag_file(op_dir), "w" do |f| - f.puts time.rfc2822 - last.each do |n, t| - f.puts "#{n}\t#{t.rfc2822}" + File.open output_flag_file(op_dir), "w" do |f| + f.puts time.rfc2822 + last.each do |n, t| + f.puts "#{n}\t#{t.rfc2822}" + end end end - end - ## - # Return the path name of the flag file in an output directory. + ## + # Return the path name of the flag file in an output directory. - def output_flag_file(op_dir) - File.join op_dir, "created.rid" - end + def output_flag_file(op_dir) + File.join op_dir, "created.rid" + end - ## - # The .document file contains a list of file and directory name patterns, - # representing candidates for documentation. It may also contain comments - # (starting with '#') + ## + # The .document file contains a list of file and directory name patterns, + # representing candidates for documentation. It may also contain comments + # (starting with '#') - def parse_dot_doc_file(in_dir, filename) - # read and strip comments - patterns = File.read(filename).gsub(/#.*/, '') + def parse_dot_doc_file(in_dir, filename) + # read and strip comments + patterns = File.read(filename).gsub(/#.*/, '') - result = {} + result = {} - patterns.split(' ').each do |patt| - candidates = Dir.glob(File.join(in_dir, patt)) - result.update normalized_file_list(candidates, false, @options.exclude) - end + patterns.split(' ').each do |patt| + candidates = Dir.glob(File.join(in_dir, patt)) + result.update normalized_file_list(candidates, false, @options.exclude) + end - result - end + result + end - ## - # Given a list of files and directories, create a list of all the Ruby - # files they contain. - # - # If +force_doc+ is true we always add the given files, if false, only - # add files that we guarantee we can parse. It is true when looking at - # files given on the command line, false when recursing through - # subdirectories. - # - # The effect of this is that if you want a file with a non-standard - # extension parsed, you must name it explicitly. - - def normalized_file_list(relative_files, force_doc = false, - exclude_pattern = nil) - file_list = {} - - relative_files.each do |rel_file_name| - rel_file_name = rel_file_name.sub(/^\.\//, '') - next if rel_file_name.end_with? 'created.rid' - next if exclude_pattern && exclude_pattern =~ rel_file_name - stat = File.stat rel_file_name rescue next - - case type = stat.ftype - when "file" - mtime = (stat.mtime unless (last_modified = @last_modified[rel_file_name] and - stat.mtime.to_i <= last_modified.to_i)) - - if force_doc or RDoc::Parser.can_parse(rel_file_name) - file_list[rel_file_name] = mtime - end - when "directory" - next if UNCONDITIONALLY_SKIPPED_DIRECTORIES.include?(rel_file_name) + ## + # Given a list of files and directories, create a list of all the Ruby + # files they contain. + # + # If +force_doc+ is true we always add the given files, if false, only + # add files that we guarantee we can parse. It is true when looking at + # files given on the command line, false when recursing through + # subdirectories. + # + # The effect of this is that if you want a file with a non-standard + # extension parsed, you must name it explicitly. + + def normalized_file_list(relative_files, force_doc = false, + exclude_pattern = nil) + file_list = {} + + relative_files.each do |rel_file_name| + rel_file_name = rel_file_name.sub(/^\.\//, '') + next if rel_file_name.end_with? 'created.rid' + next if exclude_pattern && exclude_pattern =~ rel_file_name + stat = File.stat rel_file_name rescue next + + case type = stat.ftype + when "file" + mtime = (stat.mtime unless (last_modified = @last_modified[rel_file_name] and + stat.mtime.to_i <= last_modified.to_i)) + + if force_doc or ::RDoc::Parser.can_parse(rel_file_name) + file_list[rel_file_name] = mtime + end + when "directory" + next if UNCONDITIONALLY_SKIPPED_DIRECTORIES.include?(rel_file_name) - basename = File.basename(rel_file_name) - next if options.skip_tests && TEST_SUITE_DIRECTORY_NAMES.include?(basename) + basename = File.basename(rel_file_name) + next if options.skip_tests && TEST_SUITE_DIRECTORY_NAMES.include?(basename) - created_rid = File.join rel_file_name, "created.rid" - next if File.file? created_rid + created_rid = File.join rel_file_name, "created.rid" + next if File.file? created_rid - dot_doc = File.join rel_file_name, RDoc::DOT_DOC_FILENAME + dot_doc = File.join rel_file_name, ::RDoc::DOT_DOC_FILENAME - if File.file? dot_doc - file_list.update(parse_dot_doc_file(rel_file_name, dot_doc)) + if File.file? dot_doc + file_list.update(parse_dot_doc_file(rel_file_name, dot_doc)) + else + file_list.update(list_files_in_directory(rel_file_name)) + end else - file_list.update(list_files_in_directory(rel_file_name)) + warn "rdoc can't parse the #{type} #{rel_file_name}" end - else - warn "rdoc can't parse the #{type} #{rel_file_name}" end - end - file_list - end + file_list + end - ## - # Return a list of the files to be processed in a directory. We know that - # this directory doesn't have a .document file, so we're looking for real - # files. However we may well contain subdirectories which must be tested - # for .document files. + ## + # Return a list of the files to be processed in a directory. We know that + # this directory doesn't have a .document file, so we're looking for real + # files. However we may well contain subdirectories which must be tested + # for .document files. - def list_files_in_directory(dir) - files = Dir.glob File.join(dir, "*") + def list_files_in_directory(dir) + files = Dir.glob File.join(dir, "*") - normalized_file_list files, false, @options.exclude - end + normalized_file_list files, false, @options.exclude + end - ## - # Parses +filename+ and returns an RDoc::TopLevel + ## + # Parses +filename+ and returns an RDoc::TopLevel - def parse_file(filename) - encoding = @options.encoding - filename = filename.encode encoding + def parse_file(filename) + encoding = @options.encoding + filename = filename.encode encoding - @stats.add_file filename + @stats.add_file filename - return if RDoc::Parser.binary? filename + return if ::RDoc::Parser.binary? filename - content = RDoc::Encoding.read_file filename, encoding + content = ::RDoc::Encoding.read_file filename, encoding - return unless content + return unless content - top_level = @store.add_file filename, relative_name: relative_path_for(filename) + top_level = @store.add_file filename, relative_name: relative_path_for(filename) - parser = RDoc::Parser.for top_level, content, @options, @stats + parser = ::RDoc::Parser.for top_level, content, @options, @stats - return unless parser + return unless parser - parser.scan + parser.scan - # restart documentation for the classes & modules found - top_level.classes_or_modules.each do |cm| - cm.done_documenting = false - end + # restart documentation for the classes & modules found + top_level.classes_or_modules.each do |cm| + cm.done_documenting = false + end - top_level + top_level - rescue Errno::EACCES => e - $stderr.puts <<-EOF + rescue Errno::EACCES => e + $stderr.puts <<-EOF Unable to read #{filename}, #{e.message} Please check the permissions for this file. Perhaps you do not have access to it or perhaps the original author's permissions are to restrictive. If the this is not your library please report a bug to the author. EOF - rescue => e - syntax_check_command = syntax_check_command_for filename, parser&.class - syntax_check_message = if syntax_check_command - <<~MESSAGE + rescue => e + syntax_check_command = syntax_check_command_for filename, parser&.class + syntax_check_message = if syntax_check_command + <<~MESSAGE Before reporting this, could you check that the file you're documenting has proper syntax: #{syntax_check_command} MESSAGE - else - <<~MESSAGE + else + <<~MESSAGE Before reporting this, could you check that the file you're documenting has proper syntax for its language? MESSAGE - end + end - $stderr.puts <<-EOF + $stderr.puts <<-EOF #{syntax_check_message} RDoc's parsers are not full language parsers and may fail when fed invalid source files. @@ -383,323 +384,324 @@ def parse_file(filename) EOF - $stderr.puts e.backtrace.join("\n\t") if $DEBUG_RDOC + $stderr.puts e.backtrace.join("\n\t") if $DEBUG_RDOC - raise e - end - - def syntax_check_command_for(filename, parser_class = RDoc::Parser.can_parse_by_name(filename)) - if parser_class == RDoc::Parser::Ruby - "#{Gem.ruby} -c #{filename}" - elsif parser_class == RDoc::Parser::C - cc = ENV['CC'] - cc = 'cc' if cc.nil? || cc.empty? - "#{cc} -fsyntax-only #{filename}" + raise e end - end - - ## - # Returns the relative path for +filename+ against +options.root+ (and - # +options.page_dir+ when set). This is the key used by RDoc::Store to - # identify files. - def relative_path_for(filename) - filename_path = Pathname(filename).expand_path - begin - relative_path = filename_path.relative_path_from @options.root - rescue ArgumentError - relative_path = filename_path + def syntax_check_command_for(filename, parser_class = ::RDoc::Parser.can_parse_by_name(filename)) + if parser_class == ::RDoc::Parser::Ruby + "#{Gem.ruby} -c #{filename}" + elsif parser_class == ::RDoc::Parser::C + cc = ENV['CC'] + cc = 'cc' if cc.nil? || cc.empty? + "#{cc} -fsyntax-only #{filename}" + end end - if @options.page_dir && - relative_path.to_s.start_with?(@options.page_dir.to_s) - relative_path = - relative_path.relative_path_from @options.page_dir - end + ## + # Returns the relative path for +filename+ against +options.root+ (and + # +options.page_dir+ when set). This is the key used by RDoc::Store to + # identify files. - relative_path.to_s - end + def relative_path_for(filename) + filename_path = Pathname(filename).expand_path + begin + relative_path = filename_path.relative_path_from @options.root + rescue ArgumentError + relative_path = filename_path + end - ## - # Parse each file on the command line, recursively entering directories. + if @options.page_dir && + relative_path.to_s.start_with?(@options.page_dir.to_s) + relative_path = + relative_path.relative_path_from @options.page_dir + end - def parse_files(files) - file_list = gather_files files - @stats = RDoc::Stats.new @store, file_list.length, @options.verbosity + relative_path.to_s + end - return [] if file_list.empty? + ## + # Parse each file on the command line, recursively entering directories. - # This workaround can be removed after the :main: directive is removed - original_options = @options.dup - @stats.begin_adding + def parse_files(files) + file_list = gather_files files + @stats = ::RDoc::Stats.new @store, file_list.length, @options.verbosity - file_info = file_list.map do |filename| - @current = filename - parse_file filename - end.compact + return [] if file_list.empty? - @store.resolve_c_superclasses + # This workaround can be removed after the :main: directive is removed + original_options = @options.dup + @stats.begin_adding - @stats.done_adding - @options = original_options + file_info = file_list.map do |filename| + @current = filename + parse_file filename + end.compact - file_info - end + @store.resolve_c_superclasses - ## - # Removes file extensions known to be unparseable from +files+ and TAGS - # files for emacs and vim. + @stats.done_adding + @options = original_options - def remove_unparseable(files) - files.reject do |file, *| - file =~ /\.(?:class|eps|erb|scpt\.txt|svg|ttf|yml)\z/i or - (file =~ /tags\z/i and - /\A(\f\n[^,]+,\d+$|!_TAG_)/.match?(File.binread(file, 100))) + file_info end - end - ## - # Removes duplicate canonical paths while preserving the first path found. + ## + # Removes file extensions known to be unparseable from +files+ and TAGS + # files for emacs and vim. - def remove_duplicate_files(files) - files.uniq { |file,| File.realpath(file) }.to_h - end + def remove_unparseable(files) + files.reject do |file, *| + file =~ /\.(?:class|eps|erb|scpt\.txt|svg|ttf|yml)\z/i or + (file =~ /tags\z/i and + /\A(\f\n[^,]+,\d+$|!_TAG_)/.match?(File.binread(file, 100))) + end + end - ## - # Generates documentation or a coverage report depending upon the settings - # in +options+. - # - # +options+ can be either an RDoc::Options instance or an array of strings - # equivalent to the strings that would be passed on the command line like - # %w[-q -o doc -t My\ Doc\ Title]. #document will automatically - # call RDoc::Options#finish if an options instance was given. - # - # For a list of options, see either RDoc::Options or rdoc --help. - # - # By default, output will be stored in a directory called "doc" below the - # current directory, so make sure you're somewhere writable before invoking. + ## + # Removes duplicate canonical paths while preserving the first path found. - def document(options) - if RDoc::Options === options - @options = options - else - @options = RDoc::Options.load_options - @options.parse options + def remove_duplicate_files(files) + files.uniq { |file,| File.realpath(file) }.to_h end - @options.finish - @store = RDoc::Store.new(@options) + ## + # Generates documentation or a coverage report depending upon the settings + # in +options+. + # + # +options+ can be either an RDoc::Options instance or an array of strings + # equivalent to the strings that would be passed on the command line like + # %w[-q -o doc -t My\ Doc\ Title]. #document will automatically + # call RDoc::Options#finish if an options instance was given. + # + # For a list of options, see either RDoc::Options or rdoc --help. + # + # By default, output will be stored in a directory called "doc" below the + # current directory, so make sure you're somewhere writable before invoking. + + def document(options) + if ::RDoc::Options === options + @options = options + else + @options = ::RDoc::Options.load_options + @options.parse options + end + @options.finish - if @options.pipe - handle_pipe - exit - end + @store = ::RDoc::Store.new(@options) - if @options.server_port - @store.load_cache + if @options.pipe + handle_pipe + exit + end - parse_files @options.files - record_auto_discovered_rbs_signature_mtimes + if @options.server_port + @store.load_cache - @options.default_title = "RDoc Documentation" + parse_files @options.files + record_auto_discovered_rbs_signature_mtimes - load_auto_discovered_rbs_signatures - @store.complete @options.visibility + @options.default_title = "RDoc Documentation" - start_server - exit - end + load_auto_discovered_rbs_signatures + @store.complete @options.visibility - unless @options.coverage_report - @last_modified = setup_output_dir @options.op_dir, @options.force_update - end + start_server + exit + end - @start_time = Time.now + unless @options.coverage_report + @last_modified = setup_output_dir @options.op_dir, @options.force_update + end - @store.load_cache + @start_time = Time.now - auto_discovered_rbs_signatures_changed = auto_discovered_rbs_signatures_changed? - # When only auto-discovered RBS signatures changed, no Ruby file would be - # reparsed under normal mtime checks. The store cache holds class metadata - # but not live RDoc::Context objects, so the generator would have nothing - # to iterate. Force a full reparse so updated signatures show up in the - # rendered output. - @last_modified.clear if auto_discovered_rbs_signatures_changed + @store.load_cache - file_info = parse_files @options.files - record_auto_discovered_rbs_signature_mtimes + auto_discovered_rbs_signatures_changed = auto_discovered_rbs_signatures_changed? + # When only auto-discovered RBS signatures changed, no Ruby file would be + # reparsed under normal mtime checks. The store cache holds class metadata + # but not live RDoc::Context objects, so the generator would have nothing + # to iterate. Force a full reparse so updated signatures show up in the + # rendered output. + @last_modified.clear if auto_discovered_rbs_signatures_changed - @options.default_title = "RDoc Documentation" + file_info = parse_files @options.files + record_auto_discovered_rbs_signature_mtimes - load_auto_discovered_rbs_signatures + @options.default_title = "RDoc Documentation" - @store.complete @options.visibility + load_auto_discovered_rbs_signatures - @stats.coverage_level = @options.coverage_report + @store.complete @options.visibility - if @options.coverage_report - puts + @stats.coverage_level = @options.coverage_report - puts @stats.report - elsif file_info.empty? && !auto_discovered_rbs_signatures_changed - $stderr.puts "\nNo newer files." unless @options.quiet - else - gen_klass = @options.generator + if @options.coverage_report + puts - @generator = gen_klass.new @store, @options + puts @stats.report + elsif file_info.empty? && !auto_discovered_rbs_signatures_changed + $stderr.puts "\nNo newer files." unless @options.quiet + else + gen_klass = @options.generator - generate - end + @generator = gen_klass.new @store, @options - if @stats and (@options.coverage_report or not @options.quiet) - puts - puts @stats.summary - end + generate + end - exit @stats.fully_documented? if @options.coverage_report - end + if @stats and (@options.coverage_report or not @options.quiet) + puts + puts @stats.summary + end - ## - # Generates documentation for +file_info+ (from #parse_files) into the - # output dir using the generator selected - # by the RDoc options - - def generate - if @options.dry_run - # do nothing - @generator.generate - else - Dir.chdir @options.op_dir do - unless @options.quiet - $stderr.puts "\nGenerating #{@generator.class.name.sub(/^.*::/, '')} format into #{Dir.pwd}..." - uri = "file://#{Dir.pwd}/index.html" - ref = $stderr.tty? ? "\e]8;;#{uri}\e\\#{uri}\e]8;;\e\\" : uri - $stderr.puts "\nYou can visit the home page at: #{ref}" - end + exit @stats.fully_documented? if @options.coverage_report + end + + ## + # Generates documentation for +file_info+ (from #parse_files) into the + # output dir using the generator selected + # by the RDoc options + def generate + if @options.dry_run + # do nothing @generator.generate - update_output_dir '.', @start_time, @last_modified + else + Dir.chdir @options.op_dir do + unless @options.quiet + $stderr.puts "\nGenerating #{@generator.class.name.sub(/^.*::/, '')} format into #{Dir.pwd}..." + uri = "file://#{Dir.pwd}/index.html" + ref = $stderr.tty? ? "\e]8;;#{uri}\e\\#{uri}\e]8;;\e\\" : uri + $stderr.puts "\nYou can visit the home page at: #{ref}" + end + + @generator.generate + update_output_dir '.', @start_time, @last_modified + end end end - end - ## - # Loads RBS type signatures from the project's +sig+ directory and RBS - # stdlib, then merges them into the store's code objects. - - def load_auto_discovered_rbs_signatures - sig_dirs = [] - sig_dir = File.join(@options.root.to_s, 'sig') - sig_dirs << sig_dir if File.directory?(sig_dir) - signatures = RDoc::RbsHelper.load_signatures(*sig_dirs) - @store.merge_rbs_signatures(signatures) - rescue RBS::BaseError, Errno::ENOENT, LoadError => e - # In server mode, a previous successful load may have populated the store; - # drop those signatures so a now-broken sig file doesn't keep showing - # stale types alongside the warning. - @store.clear_rbs_signatures - @options.warn "Failed to load RBS type signatures: #{e.message}" - end + ## + # Loads RBS type signatures from the project's +sig+ directory and RBS + # stdlib, then merges them into the store's code objects. + + def load_auto_discovered_rbs_signatures + sig_dirs = [] + sig_dir = File.join(@options.root.to_s, 'sig') + sig_dirs << sig_dir if File.directory?(sig_dir) + signatures = ::RDoc::RbsHelper.load_signatures(*sig_dirs) + @store.merge_rbs_signatures(signatures) + rescue RBS::BaseError, Errno::ENOENT, LoadError => e + # In server mode, a previous successful load may have populated the store; + # drop those signatures so a now-broken sig file doesn't keep showing + # stale types alongside the warning. + @store.clear_rbs_signatures + @options.warn "Failed to load RBS type signatures: #{e.message}" + end - ## - # Returns RBS files that RDoc auto-discovers for signature loading. + ## + # Returns RBS files that RDoc auto-discovers for signature loading. - def auto_discovered_rbs_signature_files - Dir[File.join(@options.root.to_s, 'sig', '**', '*.rbs')].sort - end + def auto_discovered_rbs_signature_files + Dir[File.join(@options.root.to_s, 'sig', '**', '*.rbs')].sort + end - ## - # Returns true if any auto-discovered RBS signature file has changed since - # the last run. + ## + # Returns true if any auto-discovered RBS signature file has changed since + # the last run. - def auto_discovered_rbs_signatures_changed? - current = auto_discovered_rbs_signature_mtimes - previous = @last_modified.select { |file, _| auto_discovered_rbs_signature_file?(file) } + def auto_discovered_rbs_signatures_changed? + current = auto_discovered_rbs_signature_mtimes + previous = @last_modified.select { |file, _| auto_discovered_rbs_signature_file?(file) } - return true unless (previous.keys - current.keys).empty? + return true unless (previous.keys - current.keys).empty? - current.any? do |file, mtime| - last_modified = @last_modified[file] - last_modified.nil? || mtime.to_i > last_modified.to_i + current.any? do |file, mtime| + last_modified = @last_modified[file] + last_modified.nil? || mtime.to_i > last_modified.to_i + end end - end - ## - # Records auto-discovered RBS signature file mtimes so normal generation - # freshness checks and the live server watcher can see signature-only edits. + ## + # Records auto-discovered RBS signature file mtimes so normal generation + # freshness checks and the live server watcher can see signature-only edits. - def record_auto_discovered_rbs_signature_mtimes - @last_modified.reject! { |file, _| auto_discovered_rbs_signature_file?(file) } - @last_modified.merge! auto_discovered_rbs_signature_mtimes - end + def record_auto_discovered_rbs_signature_mtimes + @last_modified.reject! { |file, _| auto_discovered_rbs_signature_file?(file) } + @last_modified.merge! auto_discovered_rbs_signature_mtimes + end - ## - # Files watched by the live preview server. + ## + # Files watched by the live preview server. - def watch_files - (@last_modified.keys + auto_discovered_rbs_signature_files).uniq - end + def watch_files + (@last_modified.keys + auto_discovered_rbs_signature_files).uniq + end - ## - # Returns true for project RBS files that are auto-discovered for signature - # loading. RDoc parses any selected .rbs file as documentation input, but - # only +sig/**/*.rbs+ files are loaded through RBS::EnvironmentLoader for - # type signature merging and live-reload bookkeeping. - - def auto_discovered_rbs_signature_file?(file) # :nodoc: - return false unless File.extname(file) == '.rbs' - - root = Pathname(@options.root.to_s).expand_path - relative_path = Pathname(file).expand_path.relative_path_from root - relative_path.each_filename.first == 'sig' - rescue ArgumentError - # file and root may be on different drives on Windows - false - end + ## + # Returns true for project RBS files that are auto-discovered for signature + # loading. RDoc parses any selected .rbs file as documentation input, but + # only +sig/**/*.rbs+ files are loaded through RBS::EnvironmentLoader for + # type signature merging and live-reload bookkeeping. - ## - # Returns mtimes for auto-discovered RBS signature files. + def auto_discovered_rbs_signature_file?(file) # :nodoc: + return false unless File.extname(file) == '.rbs' - def auto_discovered_rbs_signature_mtimes # :nodoc: - auto_discovered_rbs_signature_files.each_with_object({}) do |file, mtimes| - mtime = RDoc.safe_mtime(file) - mtimes[file] = mtime if mtime + root = Pathname(@options.root.to_s).expand_path + relative_path = Pathname(file).expand_path.relative_path_from root + relative_path.each_filename.first == 'sig' + rescue ArgumentError + # file and root may be on different drives on Windows + false end - end - ## - # Starts a live-reloading HTTP server for previewing documentation. - # Called from #document when --server is given. + ## + # Returns mtimes for auto-discovered RBS signature files. - def start_server - server = RDoc::Server.new(self, @options.server_port) - server.start - end + def auto_discovered_rbs_signature_mtimes # :nodoc: + auto_discovered_rbs_signature_files.each_with_object({}) do |file, mtimes| + mtime = ::RDoc.safe_mtime(file) + mtimes[file] = mtime if mtime + end + end - ## - # Removes a siginfo handler and replaces the previous + ## + # Starts a live-reloading HTTP server for previewing documentation. + # Called from #document when --server is given. - def remove_siginfo_handler - return unless Signal.list.key? 'INFO' + def start_server + server = ::RDoc::Server.new(self, @options.server_port) + server.start + end - handler = @old_siginfo || 'DEFAULT' + ## + # Removes a siginfo handler and replaces the previous - trap 'INFO', handler - end + def remove_siginfo_handler + return unless Signal.list.key? 'INFO' - ## - # Returns true when +extension+ is the RBS gem's RDoc discovery hook. - # Released RBS gems install their plugin through this hook, so skip it to - # avoid replacing the built-in parser during discovery. + handler = @old_siginfo || 'DEFAULT' - def self.rbs_discovery_extension?(extension) # :nodoc: - extension = File.expand_path(extension) + trap 'INFO', handler + end - Gem::Specification.find_all_by_name('rbs').any? do |spec| - File.expand_path('lib/rdoc/discover.rb', spec.full_gem_path) == extension + ## + # Returns true when +extension+ is the RBS gem's RDoc discovery hook. + # Released RBS gems install their plugin through this hook, so skip it to + # avoid replacing the built-in parser during discovery. + + def self.rbs_discovery_extension?(extension) # :nodoc: + extension = File.expand_path(extension) + + Gem::Specification.find_all_by_name('rbs').any? do |spec| + File.expand_path('lib/rdoc/discover.rb', spec.full_gem_path) == extension + end end - end + end end # Load built-in parser registrations before RubyGems discovery, then skip the @@ -712,7 +714,7 @@ def self.rbs_discovery_extension?(extension) # :nodoc: rdoc_extensions = Gem.find_latest_files 'rdoc/discover' rdoc_extensions.each do |extension| - next if RDoc::RDoc.rbs_discovery_extension?(extension) + next if ::RDoc::RDoc.rbs_discovery_extension?(extension) begin load extension diff --git a/lib/rdoc/ri.rb b/lib/rdoc/ri.rb index ccf11c4636..b8c6683c1e 100644 --- a/lib/rdoc/ri.rb +++ b/lib/rdoc/ri.rb @@ -1,21 +1,23 @@ # frozen_string_literal: true require_relative '../rdoc' -## -# Namespace for the ri command line tool's implementation. -# -# See ri --help for details. +module RDoc + ## + # Namespace for the ri command line tool's implementation. + # + # See ri --help for details. -module RDoc::RI + module RI - ## - # Base RI error class + ## + # Base RI error class - class Error < RDoc::Error; end + class Error < ::RDoc::Error; end - autoload :Driver, "#{__dir__}/ri/driver" - autoload :Paths, "#{__dir__}/ri/paths" - autoload :Servlet, "#{__dir__}/ri/servlet" - autoload :Store, "#{__dir__}/ri/store" + autoload :Driver, "#{__dir__}/ri/driver" + autoload :Paths, "#{__dir__}/ri/paths" + autoload :Servlet, "#{__dir__}/ri/servlet" + autoload :Store, "#{__dir__}/ri/store" + end end diff --git a/lib/rdoc/ri/driver.rb b/lib/rdoc/ri/driver.rb index 3882ee3c08..01797a4f74 100644 --- a/lib/rdoc/ri/driver.rb +++ b/lib/rdoc/ri/driver.rb @@ -6,117 +6,119 @@ require_relative 'formatter' # For RubyGems backwards compatibility # TODO: Fix weird documentation with `require_relative` -## -# The RI driver implements the command-line ri tool. -# -# The driver supports: -# * loading RI data from: -# * Ruby's standard library -# * RubyGems -# * ~/.rdoc -# * A user-supplied directory -# * Paging output (uses RI_PAGER environment variable, PAGER environment -# variable or the less, more and pager programs) -# * Interactive mode with tab-completion -# * Abbreviated names (ri Zl shows Zlib documentation) -# * Colorized output -# * Merging output from multiple RI data sources - -class RDoc::RI::Driver - - ## - # Base Driver error class - - class Error < RDoc::RI::Error; end - - ## - # Raised when a name isn't found in the ri data stores - - class NotFoundError < Error - - def initialize(klass, suggestion_proc = nil) # :nodoc: - @klass = klass - @suggestion_proc = suggestion_proc - end - +module RDoc + module RI ## - # Name that wasn't found + # The RI driver implements the command-line ri tool. + # + # The driver supports: + # * loading RI data from: + # * Ruby's standard library + # * RubyGems + # * ~/.rdoc + # * A user-supplied directory + # * Paging output (uses RI_PAGER environment variable, PAGER environment + # variable or the less, more and pager programs) + # * Interactive mode with tab-completion + # * Abbreviated names (ri Zl shows Zlib documentation) + # * Colorized output + # * Merging output from multiple RI data sources + + class Driver + + ## + # Base Driver error class + + class Error < ::RDoc::RI::Error; end + + ## + # Raised when a name isn't found in the ri data stores + + class NotFoundError < Error + + def initialize(klass, suggestion_proc = nil) # :nodoc: + @klass = klass + @suggestion_proc = suggestion_proc + end - def name - @klass - end + ## + # Name that wasn't found + + def name + @klass + end - def message # :nodoc: - str = "Nothing known about #{@klass}" - suggestions = @suggestion_proc&.call - if suggestions and !suggestions.empty? - str += "\nDid you mean? #{suggestions.join("\n ")}" + def message # :nodoc: + str = "Nothing known about #{@klass}" + suggestions = @suggestion_proc&.call + if suggestions and !suggestions.empty? + str += "\nDid you mean? #{suggestions.join("\n ")}" + end + str + end end - str - end - end - ## - # Show all method documentation following a class or module + ## + # Show all method documentation following a class or module - attr_accessor :show_all + attr_accessor :show_all - ## - # An RDoc::RI::Store for each entry in the RI path + ## + # An RDoc::RI::Store for each entry in the RI path - attr_accessor :stores + attr_accessor :stores - ## - # Controls the user of the pager vs $stdout + ## + # Controls the user of the pager vs $stdout - attr_accessor :use_stdout + attr_accessor :use_stdout - ## - # Default options for ri + ## + # Default options for ri - def self.default_options - options = {} - options[:interactive] = false - options[:profile] = false - options[:show_all] = false - options[:expand_refs] = true - options[:use_stdout] = !$stdout.tty? - options[:width] = 72 + def self.default_options + options = {} + options[:interactive] = false + options[:profile] = false + options[:show_all] = false + options[:expand_refs] = true + options[:use_stdout] = !$stdout.tty? + options[:width] = 72 - # By default all standard paths are used. - options[:use_system] = true - options[:use_site] = true - options[:use_home] = true - options[:use_gems] = true - options[:extra_doc_dirs] = [] + # By default all standard paths are used. + options[:use_system] = true + options[:use_site] = true + options[:use_home] = true + options[:use_gems] = true + options[:extra_doc_dirs] = [] - return options - end + return options + end - ## - # Dump +data_path+ using pp + ## + # Dump +data_path+ using pp - def self.dump(data_path) - require 'pp' + def self.dump(data_path) + require 'pp' - File.open data_path, 'rb' do |io| - pp Marshal.load(io.read) - end - end + File.open data_path, 'rb' do |io| + pp Marshal.load(io.read) + end + end - ## - # Parses +argv+ and returns a Hash of options + ## + # Parses +argv+ and returns a Hash of options - def self.process_args(argv) - options = default_options + def self.process_args(argv) + options = default_options - opts = OptionParser.new do |opt| - opt.program_name = File.basename $0 - opt.version = RDoc::VERSION - opt.release = nil - opt.summary_indent = ' ' * 4 + opts = OptionParser.new do |opt| + opt.program_name = File.basename $0 + opt.version = ::RDoc::VERSION + opt.release = nil + opt.summary_indent = ' ' * 4 - opt.banner = <<-EOT + opt.banner = <<-EOT Usage: #{opt.program_name} [options] [name ...] Where name can be: @@ -172,1401 +174,1403 @@ def self.process_args(argv) or the PAGER environment variable. EOT - opt.separator nil - opt.separator "Options:" + opt.separator nil + opt.separator "Options:" - opt.separator nil + opt.separator nil - opt.on("--[no-]interactive", "-i", - "In interactive mode you can repeatedly", - "look up methods with autocomplete.") do |interactive| - options[:interactive] = interactive - end + opt.on("--[no-]interactive", "-i", + "In interactive mode you can repeatedly", + "look up methods with autocomplete.") do |interactive| + options[:interactive] = interactive + end - opt.separator nil + opt.separator nil - opt.on("--[no-]all", "-a", - "Show all documentation for a class or", - "module.") do |show_all| - options[:show_all] = show_all - end + opt.on("--[no-]all", "-a", + "Show all documentation for a class or", + "module.") do |show_all| + options[:show_all] = show_all + end - opt.separator nil + opt.separator nil - opt.on("--[no-]list", "-l", - "List classes ri knows about.") do |list| - options[:list] = list - end + opt.on("--[no-]list", "-l", + "List classes ri knows about.") do |list| + options[:list] = list + end - opt.separator nil + opt.separator nil - opt.on("--[no-]pager", - "Send output to a pager,", - "rather than directly to stdout.") do |use_pager| - options[:use_stdout] = !use_pager - end + opt.on("--[no-]pager", + "Send output to a pager,", + "rather than directly to stdout.") do |use_pager| + options[:use_stdout] = !use_pager + end - opt.separator nil + opt.separator nil - opt.on("-T", - "Synonym for --no-pager.") do - options[:use_stdout] = true - end + opt.on("-T", + "Synonym for --no-pager.") do + options[:use_stdout] = true + end - opt.separator nil + opt.separator nil - opt.on("--width=WIDTH", "-w", OptionParser::DecimalInteger, - "Set the width of the output.") do |width| - options[:width] = width - end + opt.on("--width=WIDTH", "-w", OptionParser::DecimalInteger, + "Set the width of the output.") do |width| + options[:width] = width + end - opt.separator nil + opt.separator nil - opt.on("--server[=PORT]", Integer, - "Run RDoc server on the given port.", - "The default port is 8214.") do |port| - options[:server] = port || 8214 - end + opt.on("--server[=PORT]", Integer, + "Run RDoc server on the given port.", + "The default port is 8214.") do |port| + options[:server] = port || 8214 + end - opt.separator nil + opt.separator nil - formatters = RDoc::Markup.constants.grep(/^To[A-Z][a-z]+$/).sort - formatters = formatters.sort.map do |formatter| - formatter.to_s.sub('To', '').downcase - end - formatters -= %w[html label test] # remove useless output formats - - opt.on("--format=NAME", "-f", - "Use the selected formatter. The default", - "formatter is bs for paged output and ansi", - "otherwise. Valid formatters are:", - "#{formatters.join(', ')}.", formatters) do |value| - options[:formatter] = RDoc::Markup.const_get "To#{value.capitalize}" - end + formatters = ::RDoc::Markup.constants.grep(/^To[A-Z][a-z]+$/).sort + formatters = formatters.sort.map do |formatter| + formatter.to_s.sub('To', '').downcase + end + formatters -= %w[html label test] # remove useless output formats + + opt.on("--format=NAME", "-f", + "Use the selected formatter. The default", + "formatter is bs for paged output and ansi", + "otherwise. Valid formatters are:", + "#{formatters.join(', ')}.", formatters) do |value| + options[:formatter] = ::RDoc::Markup.const_get "To#{value.capitalize}" + end - opt.separator nil + opt.separator nil - opt.on("--[no-]expand-refs", "Expand rdoc-refs at the end of output") do |value| - options[:expand_refs] = value - end + opt.on("--[no-]expand-refs", "Expand rdoc-refs at the end of output") do |value| + options[:expand_refs] = value + end - opt.separator nil + opt.separator nil - opt.on("--help", "-h", - "Show help and exit.") do - puts opts - exit - end + opt.on("--help", "-h", + "Show help and exit.") do + puts opts + exit + end - opt.separator nil + opt.separator nil - opt.on("--version", "-v", - "Output version information and exit.") do - puts "#{opts.program_name} #{opts.version}" - exit - end + opt.on("--version", "-v", + "Output version information and exit.") do + puts "#{opts.program_name} #{opts.version}" + exit + end - opt.separator nil - opt.separator "Data source options:" - opt.separator nil + opt.separator nil + opt.separator "Data source options:" + opt.separator nil - opt.on("--[no-]list-doc-dirs", - "List the directories from which ri will", - "source documentation on stdout and exit.") do |list_doc_dirs| - options[:list_doc_dirs] = list_doc_dirs - end + opt.on("--[no-]list-doc-dirs", + "List the directories from which ri will", + "source documentation on stdout and exit.") do |list_doc_dirs| + options[:list_doc_dirs] = list_doc_dirs + end - opt.separator nil + opt.separator nil - opt.on("--doc-dir=DIRNAME", "-d", Array, - "List of directories from which to source", - "documentation in addition to the standard", - "directories. May be repeated.") do |value| - value.each do |dir| - unless File.directory? dir - raise OptionParser::InvalidArgument, "#{dir} is not a directory" + opt.on("--doc-dir=DIRNAME", "-d", Array, + "List of directories from which to source", + "documentation in addition to the standard", + "directories. May be repeated.") do |value| + value.each do |dir| + unless File.directory? dir + raise OptionParser::InvalidArgument, "#{dir} is not a directory" + end + + options[:extra_doc_dirs] << File.expand_path(dir) + end end - options[:extra_doc_dirs] << File.expand_path(dir) - end - end + opt.separator nil + + opt.on("--no-standard-docs", + "Do not include documentation from", + "the Ruby standard library, site_lib,", + "installed gems, or ~/.rdoc.", + "Use with --doc-dir.") do + options[:use_system] = false + options[:use_site] = false + options[:use_gems] = false + options[:use_home] = false + end - opt.separator nil - - opt.on("--no-standard-docs", - "Do not include documentation from", - "the Ruby standard library, site_lib,", - "installed gems, or ~/.rdoc.", - "Use with --doc-dir.") do - options[:use_system] = false - options[:use_site] = false - options[:use_gems] = false - options[:use_home] = false - end + opt.separator nil - opt.separator nil + opt.on("--[no-]system", + "Include documentation from Ruby's", + "standard library. Defaults to true.") do |value| + options[:use_system] = value + end - opt.on("--[no-]system", - "Include documentation from Ruby's", - "standard library. Defaults to true.") do |value| - options[:use_system] = value - end + opt.separator nil - opt.separator nil + opt.on("--[no-]site", + "Include documentation from libraries", + "installed in site_lib.", + "Defaults to true.") do |value| + options[:use_site] = value + end - opt.on("--[no-]site", - "Include documentation from libraries", - "installed in site_lib.", - "Defaults to true.") do |value| - options[:use_site] = value - end + opt.separator nil - opt.separator nil + opt.on("--[no-]gems", + "Include documentation from RubyGems.", + "Defaults to true.") do |value| + options[:use_gems] = value + end - opt.on("--[no-]gems", - "Include documentation from RubyGems.", - "Defaults to true.") do |value| - options[:use_gems] = value - end + opt.separator nil - opt.separator nil + opt.on("--[no-]home", + "Include documentation stored in ~/.rdoc.", + "Defaults to true.") do |value| + options[:use_home] = value + end - opt.on("--[no-]home", - "Include documentation stored in ~/.rdoc.", - "Defaults to true.") do |value| - options[:use_home] = value - end + opt.separator nil + opt.separator "Debug options:" + opt.separator nil - opt.separator nil - opt.separator "Debug options:" - opt.separator nil + opt.on("--[no-]profile", + "Run with the ruby profiler.") do |value| + options[:profile] = value + end - opt.on("--[no-]profile", - "Run with the ruby profiler.") do |value| - options[:profile] = value - end + opt.separator nil - opt.separator nil + opt.on("--dump=CACHE", + "Dump data from an ri cache or data file.") do |value| + unless File.readable?(value) + abort "#{value.inspect} is not readable" + end - opt.on("--dump=CACHE", - "Dump data from an ri cache or data file.") do |value| - unless File.readable?(value) - abort "#{value.inspect} is not readable" - end + if File.directory?(value) + abort "#{value.inspect} is a directory" + end - if File.directory?(value) - abort "#{value.inspect} is a directory" + options[:dump_path] = File.new(value) + end end - options[:dump_path] = File.new(value) - end - end - - argv = ENV['RI'].to_s.split(' ').concat argv + argv = ENV['RI'].to_s.split(' ').concat argv - opts.parse! argv + opts.parse! argv - options[:names] = argv + options[:names] = argv - options[:use_stdout] ||= !$stdout.tty? - options[:use_stdout] ||= options[:interactive] - options[:width] ||= 72 + options[:use_stdout] ||= !$stdout.tty? + options[:use_stdout] ||= options[:interactive] + options[:width] ||= 72 - options + options - rescue OptionParser::InvalidArgument, OptionParser::InvalidOption => e - puts opts - puts - puts e - exit 1 - end + rescue OptionParser::InvalidArgument, OptionParser::InvalidOption => e + puts opts + puts + puts e + exit 1 + end - ## - # Runs the ri command line executable using +argv+ + ## + # Runs the ri command line executable using +argv+ - def self.run(argv = ARGV) - options = process_args argv + def self.run(argv = ARGV) + options = process_args argv - if options[:dump_path] - dump options[:dump_path] - return - end + if options[:dump_path] + dump options[:dump_path] + return + end - ri = new options - ri.run - end + ri = new options + ri.run + end - ## - # Creates a new driver using +initial_options+ from ::process_args + ## + # Creates a new driver using +initial_options+ from ::process_args - def initialize(initial_options = {}) - @paging = false - @classes = nil + def initialize(initial_options = {}) + @paging = false + @classes = nil - options = self.class.default_options.update(initial_options) + options = self.class.default_options.update(initial_options) - @formatter_klass = options[:formatter] + @formatter_klass = options[:formatter] - require 'profile' if options[:profile] + require 'profile' if options[:profile] - @names = options[:names] - @list = options[:list] + @names = options[:names] + @list = options[:list] - @doc_dirs = [] - @stores = [] + @doc_dirs = [] + @stores = [] - RDoc::RI::Paths.each(options[:use_system], options[:use_site], - options[:use_home], options[:use_gems], - *options[:extra_doc_dirs]) do |path, type| - @doc_dirs << path + ::RDoc::RI::Paths.each(options[:use_system], options[:use_site], + options[:use_home], options[:use_gems], + *options[:extra_doc_dirs]) do |path, type| + @doc_dirs << path - store = RDoc::RI::Store.new(RDoc::Options.new, path: path, type: type) - store.load_cache - @stores << store - end + store = ::RDoc::RI::Store.new(::RDoc::Options.new, path: path, type: type) + store.load_cache + @stores << store + end - @list_doc_dirs = options[:list_doc_dirs] + @list_doc_dirs = options[:list_doc_dirs] - @interactive = options[:interactive] - @server = options[:server] - @use_stdout = options[:use_stdout] - @show_all = options[:show_all] - @width = options[:width] - @expand_refs = options[:expand_refs] - end + @interactive = options[:interactive] + @server = options[:server] + @use_stdout = options[:use_stdout] + @show_all = options[:show_all] + @width = options[:width] + @expand_refs = options[:expand_refs] + end - ## - # Adds paths for undocumented classes +also_in+ to +out+ + ## + # Adds paths for undocumented classes +also_in+ to +out+ - def add_also_in(out, also_in) - return if also_in.empty? + def add_also_in(out, also_in) + return if also_in.empty? - out << RDoc::Markup::Rule.new(1) - out << RDoc::Markup::Paragraph.new("Also found in:") + out << ::RDoc::Markup::Rule.new(1) + out << ::RDoc::Markup::Paragraph.new("Also found in:") - paths = RDoc::Markup::Verbatim.new - also_in.each do |store| - paths.parts.push store.friendly_path, "\n" - end - out << paths - end + paths = ::RDoc::Markup::Verbatim.new + also_in.each do |store| + paths.parts.push store.friendly_path, "\n" + end + out << paths + end - ## - # Adds a class header to +out+ for class +name+ which is described in - # +classes+. + ## + # Adds a class header to +out+ for class +name+ which is described in + # +classes+. - def add_class(out, name, classes) - heading = if classes.all? { |klass| klass.module? } - name - else - superclass = classes.map do |klass| - klass.superclass unless klass.module? - end.compact.shift || 'Object' + def add_class(out, name, classes) + heading = if classes.all? { |klass| klass.module? } + name + else + superclass = classes.map do |klass| + klass.superclass unless klass.module? + end.compact.shift || 'Object' - superclass = superclass.full_name unless String === superclass + superclass = superclass.full_name unless String === superclass - "#{name} < #{superclass}" - end + "#{name} < #{superclass}" + end - out << RDoc::Markup::Heading.new(1, heading) - out << RDoc::Markup::BlankLine.new - end + out << ::RDoc::Markup::Heading.new(1, heading) + out << ::RDoc::Markup::BlankLine.new + end - ## - # Adds "(from ...)" to +out+ for +store+ + ## + # Adds "(from ...)" to +out+ for +store+ - def add_from(out, store) - out << RDoc::Markup::Paragraph.new("(from #{store.friendly_path})") - end + def add_from(out, store) + out << ::RDoc::Markup::Paragraph.new("(from #{store.friendly_path})") + end - ## - # Adds +extends+ to +out+ + ## + # Adds +extends+ to +out+ - def add_extends(out, extends) - add_extension_modules out, 'Extended by', extends - end + def add_extends(out, extends) + add_extension_modules out, 'Extended by', extends + end - ## - # Adds a list of +extensions+ to this module of the given +type+ to +out+. - # add_includes and add_extends call this, so you should use those directly. + ## + # Adds a list of +extensions+ to this module of the given +type+ to +out+. + # add_includes and add_extends call this, so you should use those directly. - def add_extension_modules(out, type, extensions) - return if extensions.empty? + def add_extension_modules(out, type, extensions) + return if extensions.empty? - out << RDoc::Markup::Rule.new(1) - out << RDoc::Markup::Heading.new(1, "#{type}:") + out << ::RDoc::Markup::Rule.new(1) + out << ::RDoc::Markup::Heading.new(1, "#{type}:") - extensions.each do |modules, store| - if modules.length == 1 - add_extension_modules_single out, store, modules.first - else - add_extension_modules_multiple out, store, modules + extensions.each do |modules, store| + if modules.length == 1 + add_extension_modules_single out, store, modules.first + else + add_extension_modules_multiple out, store, modules + end + end end - end - end - ## - # Renders multiple included +modules+ from +store+ to +out+. + ## + # Renders multiple included +modules+ from +store+ to +out+. - def add_extension_modules_multiple(out, store, modules) # :nodoc: - out << RDoc::Markup::Paragraph.new("(from #{store.friendly_path})") + def add_extension_modules_multiple(out, store, modules) # :nodoc: + out << ::RDoc::Markup::Paragraph.new("(from #{store.friendly_path})") - wout, with = modules.partition { |incl| incl.comment.empty? } + wout, with = modules.partition { |incl| incl.comment.empty? } - out << RDoc::Markup::BlankLine.new unless with.empty? + out << ::RDoc::Markup::BlankLine.new unless with.empty? - with.each do |incl| - out << RDoc::Markup::Paragraph.new(incl.name) - out << RDoc::Markup::BlankLine.new - out << incl.comment.parse - end + with.each do |incl| + out << ::RDoc::Markup::Paragraph.new(incl.name) + out << ::RDoc::Markup::BlankLine.new + out << incl.comment.parse + end - unless wout.empty? - verb = RDoc::Markup::Verbatim.new + unless wout.empty? + verb = ::RDoc::Markup::Verbatim.new - wout.each do |incl| - verb.push incl.name, "\n" - end + wout.each do |incl| + verb.push incl.name, "\n" + end - out << verb - end - end + out << verb + end + end - ## - # Adds a single extension module +include+ from +store+ to +out+ + ## + # Adds a single extension module +include+ from +store+ to +out+ - def add_extension_modules_single(out, store, include) # :nodoc: - name = include.name - path = store.friendly_path - out << RDoc::Markup::Paragraph.new("#{name} (from #{path})") + def add_extension_modules_single(out, store, include) # :nodoc: + name = include.name + path = store.friendly_path + out << ::RDoc::Markup::Paragraph.new("#{name} (from #{path})") - if include.comment - out << RDoc::Markup::BlankLine.new - out << include.comment.parse - end - end + if include.comment + out << ::RDoc::Markup::BlankLine.new + out << include.comment.parse + end + end - ## - # Adds +includes+ to +out+ + ## + # Adds +includes+ to +out+ - def add_includes(out, includes) - add_extension_modules out, 'Includes', includes - end + def add_includes(out, includes) + add_extension_modules out, 'Includes', includes + end - ## - # Looks up the method +name+ and adds it to +out+ + ## + # Looks up the method +name+ and adds it to +out+ - def add_method(out, name) - filtered = lookup_method name - method_document out, name, filtered - end + def add_method(out, name) + filtered = lookup_method name + method_document out, name, filtered + end - ## - # Adds documentation for all methods in +klass+ to +out+ + ## + # Adds documentation for all methods in +klass+ to +out+ - def add_method_documentation(out, klass) - klass.method_list.each do |method| - begin - add_method out, method.full_name - rescue NotFoundError - next + def add_method_documentation(out, klass) + klass.method_list.each do |method| + begin + add_method out, method.full_name + rescue NotFoundError + next + end + end end - end - end - ## - # Adds a list of +methods+ to +out+ with a heading of +name+ + ## + # Adds a list of +methods+ to +out+ with a heading of +name+ - def add_method_list(out, methods, name) - return if methods.empty? + def add_method_list(out, methods, name) + return if methods.empty? - out << RDoc::Markup::Heading.new(1, "#{name}:") - out << RDoc::Markup::BlankLine.new + out << ::RDoc::Markup::Heading.new(1, "#{name}:") + out << ::RDoc::Markup::BlankLine.new - if @use_stdout and !@interactive - out.concat methods.map { |method| - RDoc::Markup::Verbatim.new method - } - else - out << RDoc::Markup::IndentedParagraph.new(2, methods.join(', ')) - end + if @use_stdout and !@interactive + out.concat methods.map { |method| + ::RDoc::Markup::Verbatim.new method + } + else + out << ::RDoc::Markup::IndentedParagraph.new(2, methods.join(', ')) + end - out << RDoc::Markup::BlankLine.new - end + out << ::RDoc::Markup::BlankLine.new + end - ## - # Returns ancestor classes of +klass+ + ## + # Returns ancestor classes of +klass+ - def ancestors_of(klass) - ancestors = [] + def ancestors_of(klass) + ancestors = [] - unexamined = [klass] - seen = [] + unexamined = [klass] + seen = [] - loop do - break if unexamined.empty? - current = unexamined.shift - seen << current + loop do + break if unexamined.empty? + current = unexamined.shift + seen << current - stores = classes[current] + stores = classes[current] - next unless stores and not stores.empty? + next unless stores and not stores.empty? - klasses = stores.flat_map do |store| - store.ancestors[current] || [] - end.uniq + klasses = stores.flat_map do |store| + store.ancestors[current] || [] + end.uniq - klasses = klasses - seen + klasses = klasses - seen - ancestors.concat klasses - unexamined.concat klasses - end + ancestors.concat klasses + unexamined.concat klasses + end - ancestors.reverse - end + ancestors.reverse + end - ## - # For RubyGems backwards compatibility + ## + # For RubyGems backwards compatibility - def class_cache # :nodoc: - end + def class_cache # :nodoc: + end - ## - # Builds a RDoc::Markup::Document from +found+, +klasess+ and +includes+ + ## + # Builds a RDoc::Markup::Document from +found+, +klasess+ and +includes+ - def class_document(name, found, klasses, includes, extends) - also_in = [] + def class_document(name, found, klasses, includes, extends) + also_in = [] - out = RDoc::Markup::Document.new + out = ::RDoc::Markup::Document.new - add_class out, name, klasses + add_class out, name, klasses - add_includes out, includes - add_extends out, extends + add_includes out, includes + add_extends out, extends - found.each do |store, klass| - render_class out, store, klass, also_in - end + found.each do |store, klass| + render_class out, store, klass, also_in + end - add_also_in out, also_in + add_also_in out, also_in - expand_rdoc_refs_at_the_bottom(out) - out - end + expand_rdoc_refs_at_the_bottom(out) + out + end - ## - # Adds the class +comment+ to +out+. + ## + # Adds the class +comment+ to +out+. - def class_document_comment(out, document) # :nodoc: - unless document.empty? - out << RDoc::Markup::Rule.new(1) + def class_document_comment(out, document) # :nodoc: + unless document.empty? + out << ::RDoc::Markup::Rule.new(1) - if document.merged? - parts = document.parts - parts = parts.zip [RDoc::Markup::BlankLine.new] * parts.length - parts.flatten! - parts.pop + if document.merged? + parts = document.parts + parts = parts.zip [::RDoc::Markup::BlankLine.new] * parts.length + parts.flatten! + parts.pop - out.concat parts - else - out << comment + out.concat parts + else + out << comment + end + end end - end - end - ## - # Adds the constants from +klass+ to the Document +out+. + ## + # Adds the constants from +klass+ to the Document +out+. - def class_document_constants(out, klass) # :nodoc: - return if klass.constants.empty? + def class_document_constants(out, klass) # :nodoc: + return if klass.constants.empty? - out << RDoc::Markup::Heading.new(1, "Constants:") - out << RDoc::Markup::BlankLine.new - list = RDoc::Markup::List.new :NOTE + out << ::RDoc::Markup::Heading.new(1, "Constants:") + out << ::RDoc::Markup::BlankLine.new + list = ::RDoc::Markup::List.new :NOTE - constants = klass.constants.sort_by { |constant| constant.name } + constants = klass.constants.sort_by { |constant| constant.name } - list.items.concat constants.map { |constant| - parts = constant.comment.parse.parts - parts << RDoc::Markup::Paragraph.new('[not documented]') if - parts.empty? + list.items.concat constants.map { |constant| + parts = constant.comment.parse.parts + parts << ::RDoc::Markup::Paragraph.new('[not documented]') if + parts.empty? - RDoc::Markup::ListItem.new(constant.name, *parts) - } + ::RDoc::Markup::ListItem.new(constant.name, *parts) + } - out << list - out << RDoc::Markup::BlankLine.new - end + out << list + out << ::RDoc::Markup::BlankLine.new + end - ## - # Hash mapping a known class or module to the stores it can be loaded from + ## + # Hash mapping a known class or module to the stores it can be loaded from - def classes - return @classes if @classes + def classes + return @classes if @classes - @classes = {} + @classes = {} - @stores.each do |store| - store.cache[:modules].each do |mod| - # using default block causes searched-for modules to be added - @classes[mod] ||= [] - @classes[mod] << store - end - end - - @classes - end + @stores.each do |store| + store.cache[:modules].each do |mod| + # using default block causes searched-for modules to be added + @classes[mod] ||= [] + @classes[mod] << store + end + end - ## - # Returns the stores wherein +name+ is found along with the classes, - # extends and includes that match it - - def classes_and_includes_and_extends_for(name) - klasses = [] - extends = [] - includes = [] - - found = @stores.map do |store| - begin - klass = store.load_class name - klasses << klass - extends << [klass.extends, store] if klass.extends - includes << [klass.includes, store] if klass.includes - [store, klass] - rescue RDoc::Store::MissingFileError + @classes end - end.compact - extends.reject! do |modules,| modules.empty? end - includes.reject! do |modules,| modules.empty? end + ## + # Returns the stores wherein +name+ is found along with the classes, + # extends and includes that match it + + def classes_and_includes_and_extends_for(name) + klasses = [] + extends = [] + includes = [] + + found = @stores.map do |store| + begin + klass = store.load_class name + klasses << klass + extends << [klass.extends, store] if klass.extends + includes << [klass.includes, store] if klass.includes + [store, klass] + rescue ::RDoc::Store::MissingFileError + end + end.compact - [found, klasses, includes, extends] - end + extends.reject! do |modules,| modules.empty? end + includes.reject! do |modules,| modules.empty? end - ## - # Completes +name+ based on the caches. For Readline + [found, klasses, includes, extends] + end - def complete(name) - completions = [] + ## + # Completes +name+ based on the caches. For Readline - klass, selector, method = parse_name name + def complete(name) + completions = [] - complete_klass name, klass, selector, method, completions - complete_method name, klass, selector, completions + klass, selector, method = parse_name name - completions.uniq.select {|s| s.start_with? name }.sort - end + complete_klass name, klass, selector, method, completions + complete_method name, klass, selector, completions - def complete_klass(name, klass, selector, method, completions) # :nodoc: - klasses = classes.keys + completions.uniq.select {|s| s.start_with? name }.sort + end - # may need to include Foo when given Foo:: - klass_name = method ? name : klass + def complete_klass(name, klass, selector, method, completions) # :nodoc: + klasses = classes.keys - if name !~ /#|\./ - completions.replace klasses.grep(/^#{Regexp.escape klass_name}[^:]*$/) - completions.concat klasses.grep(/^#{Regexp.escape name}[^:]*$/) if - name =~ /::$/ + # may need to include Foo when given Foo:: + klass_name = method ? name : klass - completions << klass if classes.key? klass # to complete a method name - elsif selector - completions << klass if classes.key? klass - elsif classes.key? klass_name - completions << klass_name - end - end + if name !~ /#|\./ + completions.replace klasses.grep(/^#{Regexp.escape klass_name}[^:]*$/) + completions.concat klasses.grep(/^#{Regexp.escape name}[^:]*$/) if + name =~ /::$/ - def complete_method(name, klass, selector, completions) # :nodoc: - if completions.include? klass and name =~ /#|\.|::/ - methods = list_methods_matching name - - if not methods.empty? - # remove Foo if given Foo:: and a method was found - completions.delete klass - elsif selector - # replace Foo with Foo:: as given - completions.delete klass - completions << "#{klass}#{selector}" + completions << klass if classes.key? klass # to complete a method name + elsif selector + completions << klass if classes.key? klass + elsif classes.key? klass_name + completions << klass_name + end end - methods.each do |klass_sel_method| - match = klass_sel_method.match(/^(.+)(#|\.|::)([^#.:]+)$/) - # match[2] is `::` for class method and `#` for instance method. - # To be consistent with old completion that completes `['Foo#i', 'Foo::c']` for `Foo.`, - # `.` should be a wildcard for both `#` and `::` here. - if match && match[2] == selector || selector == '.' - completions << match[1] + selector + match[3] + def complete_method(name, klass, selector, completions) # :nodoc: + if completions.include? klass and name =~ /#|\.|::/ + methods = list_methods_matching name + + if not methods.empty? + # remove Foo if given Foo:: and a method was found + completions.delete klass + elsif selector + # replace Foo with Foo:: as given + completions.delete klass + completions << "#{klass}#{selector}" + end + + methods.each do |klass_sel_method| + match = klass_sel_method.match(/^(.+)(#|\.|::)([^#.:]+)$/) + # match[2] is `::` for class method and `#` for instance method. + # To be consistent with old completion that completes `['Foo#i', 'Foo::c']` for `Foo.`, + # `.` should be a wildcard for both `#` and `::` here. + if match && match[2] == selector || selector == '.' + completions << match[1] + selector + match[3] + end + end end end - end - end - ## - # Converts +document+ to text and writes it to the pager + ## + # Converts +document+ to text and writes it to the pager - def display(document) - page do |io| - f = formatter(io) - f.width = @width if @width and f.respond_to?(:width) - text = document.accept f + def display(document) + page do |io| + f = formatter(io) + f.width = @width if @width and f.respond_to?(:width) + text = document.accept f - io.write text - end - end + io.write text + end + end - ## - # Outputs formatted RI data for class +name+. Groups undocumented classes + ## + # Outputs formatted RI data for class +name+. Groups undocumented classes - def display_class(name) - return if name =~ /#|\./ + def display_class(name) + return if name =~ /#|\./ - found, klasses, includes, extends = - classes_and_includes_and_extends_for name + found, klasses, includes, extends = + classes_and_includes_and_extends_for name - return if found.empty? + return if found.empty? - out = class_document name, found, klasses, includes, extends + out = class_document name, found, klasses, includes, extends - display out - end + display out + end - ## - # Outputs formatted RI data for method +name+ + ## + # Outputs formatted RI data for method +name+ - def display_method(name) - out = RDoc::Markup::Document.new + def display_method(name) + out = ::RDoc::Markup::Document.new - add_method out, name + add_method out, name - expand_rdoc_refs_at_the_bottom(out) + expand_rdoc_refs_at_the_bottom(out) - display out - end + display out + end - ## - # Outputs formatted RI data for the class or method +name+. - # - # Returns true if +name+ was found, false if it was not an alternative could - # be guessed, raises an error if +name+ couldn't be guessed. + ## + # Outputs formatted RI data for the class or method +name+. + # + # Returns true if +name+ was found, false if it was not an alternative could + # be guessed, raises an error if +name+ couldn't be guessed. - def display_name(name) - if name =~ /\w:(\w|$)/ - display_page name - return true - end + def display_name(name) + if name =~ /\w:(\w|$)/ + display_page name + return true + end - return true if display_class name + return true if display_class name - display_method name if name =~ /::|#|\./ + display_method name if name =~ /::|#|\./ - true - rescue NotFoundError - matches = list_methods_matching name if name =~ /::|#|\./ - matches = classes.keys.grep(/^#{Regexp.escape name}/) if matches.empty? + true + rescue NotFoundError + matches = list_methods_matching name if name =~ /::|#|\./ + matches = classes.keys.grep(/^#{Regexp.escape name}/) if matches.empty? - raise if matches.empty? + raise if matches.empty? - page do |io| - io.puts "#{name} not found, maybe you meant:" - io.puts - io.puts matches.sort.join("\n") - end + page do |io| + io.puts "#{name} not found, maybe you meant:" + io.puts + io.puts matches.sort.join("\n") + end - false - end + false + end - ## - # Displays each name in +name+ + ## + # Displays each name in +name+ - def display_names(names) - names.each do |name| - name = expand_name name + def display_names(names) + names.each do |name| + name = expand_name name - display_name name - end - end + display_name name + end + end - ## - # Outputs formatted RI data for page +name+. + ## + # Outputs formatted RI data for page +name+. - def display_page(name) - store_name, page_name = name.split ':', 2 + def display_page(name) + store_name, page_name = name.split ':', 2 - store = @stores.find { |s| s.source == store_name } + store = @stores.find { |s| s.source == store_name } - return display_page_list store if page_name.empty? + return display_page_list store if page_name.empty? - pages = store.cache[:pages] + pages = store.cache[:pages] - unless pages.include? page_name - found_names = pages.select do |n| - n =~ /#{Regexp.escape page_name}\.[^.]+$/ - end + unless pages.include? page_name + found_names = pages.select do |n| + n =~ /#{Regexp.escape page_name}\.[^.]+$/ + end - if found_names.length.zero? - return display_page_list store, pages - elsif found_names.length > 1 - return display_page_list store, found_names, page_name - end + if found_names.length.zero? + return display_page_list store, pages + elsif found_names.length > 1 + return display_page_list store, found_names, page_name + end - page_name = found_names.first - end + page_name = found_names.first + end - page = store.load_page page_name + page = store.load_page page_name - display page.comment.parse - end + display page.comment.parse + end - ## - # Outputs a formatted RI page list for the pages in +store+. + ## + # Outputs a formatted RI page list for the pages in +store+. - def display_page_list(store, pages = store.cache[:pages], search = nil) - out = RDoc::Markup::Document.new + def display_page_list(store, pages = store.cache[:pages], search = nil) + out = ::RDoc::Markup::Document.new - title = if search - "#{search} pages" - else - 'Pages' - end + title = if search + "#{search} pages" + else + 'Pages' + end - out << RDoc::Markup::Heading.new(1, "#{title} in #{store.friendly_path}") - out << RDoc::Markup::BlankLine.new + out << ::RDoc::Markup::Heading.new(1, "#{title} in #{store.friendly_path}") + out << ::RDoc::Markup::BlankLine.new - list = RDoc::Markup::List.new(:BULLET) + list = ::RDoc::Markup::List.new(:BULLET) - pages.each do |page| - list << RDoc::Markup::Paragraph.new(page) - end + pages.each do |page| + list << ::RDoc::Markup::Paragraph.new(page) + end - out << list + out << list - display out - end + display out + end - def check_did_you_mean # :nodoc: - if defined? DidYouMean::SpellChecker - true - else - begin - require 'did_you_mean' + def check_did_you_mean # :nodoc: if defined? DidYouMean::SpellChecker true else - false + begin + require 'did_you_mean' + if defined? DidYouMean::SpellChecker + true + else + false + end + rescue LoadError + false + end end - rescue LoadError - false end - end - end - ## - # Expands abbreviated klass +klass+ into a fully-qualified class. "Zl::Da" - # will be expanded to Zlib::DataError. - - def expand_class(klass) - class_names = classes.keys - ary = class_names.grep(Regexp.new("\\A#{klass.gsub(/(?=::|\z)/, '[^:]*')}\\z")) - if ary.length != 1 && ary.first != klass - if check_did_you_mean - suggestion_proc = -> { DidYouMean::SpellChecker.new(dictionary: class_names).correct(klass) } - raise NotFoundError.new(klass, suggestion_proc) - else - raise NotFoundError, klass + ## + # Expands abbreviated klass +klass+ into a fully-qualified class. "Zl::Da" + # will be expanded to Zlib::DataError. + + def expand_class(klass) + class_names = classes.keys + ary = class_names.grep(Regexp.new("\\A#{klass.gsub(/(?=::|\z)/, '[^:]*')}\\z")) + if ary.length != 1 && ary.first != klass + if check_did_you_mean + suggestion_proc = -> { DidYouMean::SpellChecker.new(dictionary: class_names).correct(klass) } + raise NotFoundError.new(klass, suggestion_proc) + else + raise NotFoundError, klass + end + end + ary.first end - end - ary.first - end - ## - # Expands the class portion of +name+ into a fully-qualified class. See - # #expand_class. + ## + # Expands the class portion of +name+ into a fully-qualified class. See + # #expand_class. - def expand_name(name) - klass, selector, method = parse_name name + def expand_name(name) + klass, selector, method = parse_name name - return [selector, method].join if klass.empty? + return [selector, method].join if klass.empty? - case selector - when ':' - [find_store(klass), selector, method] - else - [expand_class(klass), selector, method] - end.join - end + case selector + when ':' + [find_store(klass), selector, method] + else + [expand_class(klass), selector, method] + end.join + end - ## - # Filters the methods in +found+ trying to find a match for +name+. + ## + # Filters the methods in +found+ trying to find a match for +name+. - def filter_methods(found, name) - regexp = name_regexp name + def filter_methods(found, name) + regexp = name_regexp name - filtered = found.find_all do |store, methods| - methods.any? { |method| method.full_name =~ regexp } - end + filtered = found.find_all do |store, methods| + methods.any? { |method| method.full_name =~ regexp } + end - return filtered unless filtered.empty? + return filtered unless filtered.empty? - found - end + found + end - ## - # Yields items matching +name+ including the store they were found in, the - # class being searched for, the class they were found in (an ancestor) the - # types of methods to look up (from #method_type), and the method name being - # searched for + ## + # Yields items matching +name+ including the store they were found in, the + # class being searched for, the class they were found in (an ancestor) the + # types of methods to look up (from #method_type), and the method name being + # searched for - def find_methods(name) - klass, selector, method = parse_name name + def find_methods(name) + klass, selector, method = parse_name name - types = method_type selector + types = method_type selector - klasses = nil - ambiguous = klass.empty? + klasses = nil + ambiguous = klass.empty? - if ambiguous - klasses = classes.keys - else - klasses = ancestors_of klass - klasses.unshift klass - end + if ambiguous + klasses = classes.keys + else + klasses = ancestors_of klass + klasses.unshift klass + end - methods = [] + methods = [] - klasses.each do |ancestor| - ancestors = classes[ancestor] + klasses.each do |ancestor| + ancestors = classes[ancestor] - next unless ancestors + next unless ancestors - klass = ancestor if ambiguous + klass = ancestor if ambiguous - ancestors.each do |store| - methods << [store, klass, ancestor, types, method] - end - end + ancestors.each do |store| + methods << [store, klass, ancestor, types, method] + end + end - methods = methods.sort_by do |_, k, a, _, m| - [k, a, m].compact - end + methods = methods.sort_by do |_, k, a, _, m| + [k, a, m].compact + end - methods.each do |item| - yield(*item) # :yields: store, klass, ancestor, types, method - end + methods.each do |item| + yield(*item) # :yields: store, klass, ancestor, types, method + end - self - end + self + end - ## - # Finds a store that matches +name+ which can be the name of a gem, "ruby", - # "home" or "site". - # - # See also RDoc::Store#source + ## + # Finds a store that matches +name+ which can be the name of a gem, "ruby", + # "home" or "site". + # + # See also RDoc::Store#source - def find_store(name) - @stores.each do |store| - source = store.source + def find_store(name) + @stores.each do |store| + source = store.source - return source if source == name + return source if source == name - return source if - store.type == :gem and source =~ /^#{Regexp.escape name}-\d/ - end + return source if + store.type == :gem and source =~ /^#{Regexp.escape name}-\d/ + end - raise RDoc::RI::Driver::NotFoundError, name - end + raise ::RDoc::RI::Driver::NotFoundError, name + end - ## - # Creates a new RDoc::Markup::Formatter. If a formatter is given with -f, - # use it. If we're outputting to a pager, use bs, otherwise ansi. - - def formatter(io) - if @formatter_klass - @formatter_klass.new - elsif paging? or !io.tty? - RDoc::Markup::ToBs.new - else - RDoc::Markup::ToAnsi.new - end - end + ## + # Creates a new RDoc::Markup::Formatter. If a formatter is given with -f, + # use it. If we're outputting to a pager, use bs, otherwise ansi. - ## - # Runs ri interactively using Readline if it is available. + def formatter(io) + if @formatter_klass + @formatter_klass.new + elsif paging? or !io.tty? + ::RDoc::Markup::ToBs.new + else + ::RDoc::Markup::ToAnsi.new + end + end - def interactive - puts "\nEnter the method name you want to look up." + ## + # Runs ri interactively using Readline if it is available. - begin - require 'readline' - rescue LoadError - end - if defined? Readline - Readline.completion_proc = method :complete - puts "You can use tab to autocomplete." - end + def interactive + puts "\nEnter the method name you want to look up." + + begin + require 'readline' + rescue LoadError + end + if defined? Readline + Readline.completion_proc = method :complete + puts "You can use tab to autocomplete." + end - puts "Enter a blank line to exit.\n\n" + puts "Enter a blank line to exit.\n\n" - loop do - name = if defined? Readline - Readline.readline ">> ", true - else - print ">> " - $stdin.gets - end + loop do + name = if defined? Readline + Readline.readline ">> ", true + else + print ">> " + $stdin.gets + end - return if name.nil? or name.empty? + return if name.nil? or name.empty? - begin - display_name expand_name(name.strip) - rescue NotFoundError => e - puts e.message + begin + display_name expand_name(name.strip) + rescue NotFoundError => e + puts e.message + end + end + + rescue Interrupt + exit end - end - rescue Interrupt - exit - end + ## + # Lists classes known to ri starting with +names+. If +names+ is empty all + # known classes are shown. - ## - # Lists classes known to ri starting with +names+. If +names+ is empty all - # known classes are shown. + def list_known_classes(names = []) + classes = [] - def list_known_classes(names = []) - classes = [] + stores.each do |store| + classes << store.module_names + end - stores.each do |store| - classes << store.module_names - end + classes = classes.flatten.uniq.sort - classes = classes.flatten.uniq.sort + unless names.empty? + filter = Regexp.union names.map { |name| /^#{name}/ } - unless names.empty? - filter = Regexp.union names.map { |name| /^#{name}/ } + classes = classes.grep filter + end - classes = classes.grep filter - end + page do |io| + if paging? or io.tty? + if names.empty? + io.puts "Classes and Modules known to ri:" + else + io.puts "Classes and Modules starting with #{names.join ', '}:" + end + io.puts + end - page do |io| - if paging? or io.tty? - if names.empty? - io.puts "Classes and Modules known to ri:" - else - io.puts "Classes and Modules starting with #{names.join ', '}:" + io.puts classes.join("\n") end - io.puts end - io.puts classes.join("\n") - end - end + ## + # Returns an Array of methods matching +name+ - ## - # Returns an Array of methods matching +name+ + def list_methods_matching(name) + found = [] - def list_methods_matching(name) - found = [] + find_methods name do |store, klass, ancestor, types, method| + if types == :instance or types == :both + methods = store.instance_methods[ancestor] - find_methods name do |store, klass, ancestor, types, method| - if types == :instance or types == :both - methods = store.instance_methods[ancestor] + if methods + matches = methods.grep(/^#{Regexp.escape method.to_s}/) - if methods - matches = methods.grep(/^#{Regexp.escape method.to_s}/) + matches = matches.map do |match| + "#{klass}##{match}" + end - matches = matches.map do |match| - "#{klass}##{match}" + found.concat matches + end end - found.concat matches - end - end + if types == :class or types == :both + methods = store.class_methods[ancestor] - if types == :class or types == :both - methods = store.class_methods[ancestor] + next unless methods + matches = methods.grep(/^#{Regexp.escape method.to_s}/) - next unless methods - matches = methods.grep(/^#{Regexp.escape method.to_s}/) + matches = matches.map do |match| + "#{klass}::#{match}" + end - matches = matches.map do |match| - "#{klass}::#{match}" + found.concat matches + end end - found.concat matches + found.uniq end - end - - found.uniq - end - ## - # Loads RI data for method +name+ on +klass+ from +store+. +type+ and - # +cache+ indicate if it is a class or instance method. + ## + # Loads RI data for method +name+ on +klass+ from +store+. +type+ and + # +cache+ indicate if it is a class or instance method. - def load_method(store, cache, klass, type, name) - methods = store.public_send(cache)[klass] + def load_method(store, cache, klass, type, name) + methods = store.public_send(cache)[klass] - return unless methods + return unless methods - method = methods.find do |method_name| - method_name == name - end + method = methods.find do |method_name| + method_name == name + end - return unless method + return unless method - store.load_method klass, "#{type}#{method}" - rescue RDoc::Store::MissingFileError => e - comment = RDoc::Comment.new("missing documentation at #{e.file}") - comment.parse + store.load_method klass, "#{type}#{method}" + rescue ::RDoc::Store::MissingFileError => e + comment = ::RDoc::Comment.new("missing documentation at #{e.file}") + comment.parse - method = RDoc::AnyMethod.new name - method.comment = comment - method - end + method = ::RDoc::AnyMethod.new name + method.comment = comment + method + end - ## - # Returns an Array of RI data for methods matching +name+ + ## + # Returns an Array of RI data for methods matching +name+ - def load_methods_matching(name) - found = [] + def load_methods_matching(name) + found = [] - find_methods name do |store, klass, ancestor, types, method| - methods = [] + find_methods name do |store, klass, ancestor, types, method| + methods = [] - methods << load_method(store, :class_methods, ancestor, '::', method) if - [:class, :both].include? types + methods << load_method(store, :class_methods, ancestor, '::', method) if + [:class, :both].include? types - methods << load_method(store, :instance_methods, ancestor, '#', method) if - [:instance, :both].include? types + methods << load_method(store, :instance_methods, ancestor, '#', method) if + [:instance, :both].include? types - found << [store, methods.compact] - end + found << [store, methods.compact] + end - found.reject do |path, methods| methods.empty? end - end + found.reject do |path, methods| methods.empty? end + end - ## - # Returns a filtered list of methods matching +name+ + ## + # Returns a filtered list of methods matching +name+ - def lookup_method(name) - found = load_methods_matching name + def lookup_method(name) + found = load_methods_matching name - if found.empty? - if check_did_you_mean - methods = [] - _, _, method_name = parse_name name - find_methods name do |store, klass, ancestor, types, method| - methods.push(*store.class_methods[klass]) if [:class, :both].include? types - methods.push(*store.instance_methods[klass]) if [:instance, :both].include? types + if found.empty? + if check_did_you_mean + methods = [] + _, _, method_name = parse_name name + find_methods name do |store, klass, ancestor, types, method| + methods.push(*store.class_methods[klass]) if [:class, :both].include? types + methods.push(*store.instance_methods[klass]) if [:instance, :both].include? types + end + methods = methods.uniq + suggestion_proc = -> { DidYouMean::SpellChecker.new(dictionary: methods).correct(method_name) } + raise NotFoundError.new(name, suggestion_proc) + else + raise NotFoundError, name + end end - methods = methods.uniq - suggestion_proc = -> { DidYouMean::SpellChecker.new(dictionary: methods).correct(method_name) } - raise NotFoundError.new(name, suggestion_proc) - else - raise NotFoundError, name + + filter_methods found, name end - end - filter_methods found, name - end + ## + # Builds a RDoc::Markup::Document from +found+, +klasses+ and +includes+ - ## - # Builds a RDoc::Markup::Document from +found+, +klasses+ and +includes+ + def method_document(out, name, filtered) + out << ::RDoc::Markup::Heading.new(1, name) + out << ::RDoc::Markup::BlankLine.new - def method_document(out, name, filtered) - out << RDoc::Markup::Heading.new(1, name) - out << RDoc::Markup::BlankLine.new + filtered.each do |store, methods| + methods.each do |method| + render_method out, store, method, name + end + end - filtered.each do |store, methods| - methods.each do |method| - render_method out, store, method, name + out end - end - out - end - - ## - # Returns the type of method (:both, :instance, :class) for +selector+ + ## + # Returns the type of method (:both, :instance, :class) for +selector+ - def method_type(selector) - case selector - when '.', nil then :both - when '#' then :instance - else :class - end - end + def method_type(selector) + case selector + when '.', nil then :both + when '#' then :instance + else :class + end + end - ## - # Returns a regular expression for +name+ that will match an - # RDoc::AnyMethod's name. + ## + # Returns a regular expression for +name+ that will match an + # RDoc::AnyMethod's name. - def name_regexp(name) - klass, type, name = parse_name name + def name_regexp(name) + klass, type, name = parse_name name - case type - when '#', '::' - /^#{klass}#{type}#{Regexp.escape name}$/ - else - /^#{klass}(#|::)#{Regexp.escape name}$/ - end - end + case type + when '#', '::' + /^#{klass}#{type}#{Regexp.escape name}$/ + else + /^#{klass}(#|::)#{Regexp.escape name}$/ + end + end - ## - # Paginates output through a pager program. + ## + # Paginates output through a pager program. - def page - if pager = setup_pager - begin - yield pager + def page + if pager = setup_pager + begin + yield pager + ensure + pager.close + end + else + yield $stdout + end + rescue Errno::EPIPE ensure - pager.close + @paging = false end - else - yield $stdout - end - rescue Errno::EPIPE - ensure - @paging = false - end - ## - # Are we using a pager? + ## + # Are we using a pager? - def paging? - @paging - end + def paging? + @paging + end + + ## + # Extracts the class, selector and method name parts from +name+ like + # Foo::Bar#baz. + # + # NOTE: Given Foo::Bar, Bar is considered a class even though it may be a + # method + + def parse_name(name) + parts = name.split(/(::?|#|\.)/) + + if parts.length == 1 + if parts.first =~ /^[a-z]|^([%&*+\/<>^`|~-]|\+@|-@|<<|<=>?|===?|=>|=~|>>|\[\]=?|~@)$/ + type = '.' + meth = parts.pop + else + type = nil + meth = nil + end + elsif parts.length == 2 or parts.last =~ /::|#|\./ + type = parts.pop + meth = nil + elsif parts[1] == ':' + klass = parts.shift + type = parts.shift + meth = parts.join + elsif parts[-2] != '::' or parts.last !~ /^[A-Z]/ + meth = parts.pop + type = parts.pop + end + + klass ||= parts.join - ## - # Extracts the class, selector and method name parts from +name+ like - # Foo::Bar#baz. - # - # NOTE: Given Foo::Bar, Bar is considered a class even though it may be a - # method - - def parse_name(name) - parts = name.split(/(::?|#|\.)/) - - if parts.length == 1 - if parts.first =~ /^[a-z]|^([%&*+\/<>^`|~-]|\+@|-@|<<|<=>?|===?|=>|=~|>>|\[\]=?|~@)$/ - type = '.' - meth = parts.pop - else - type = nil - meth = nil + [klass, type, meth] end - elsif parts.length == 2 or parts.last =~ /::|#|\./ - type = parts.pop - meth = nil - elsif parts[1] == ':' - klass = parts.shift - type = parts.shift - meth = parts.join - elsif parts[-2] != '::' or parts.last !~ /^[A-Z]/ - meth = parts.pop - type = parts.pop - end - klass ||= parts.join + ## + # Renders the +klass+ from +store+ to +out+. If the klass has no + # documentable items the class is added to +also_in+ instead. + + def render_class(out, store, klass, also_in) # :nodoc: + document = klass.comment.parse + # TODO the store's cache should always return an empty Array + class_methods = store.class_methods[klass.full_name] || [] + instance_methods = store.instance_methods[klass.full_name] || [] + attributes = store.attributes[klass.full_name] || [] + + if document.empty? and + instance_methods.empty? and class_methods.empty? + also_in << store + return + end - [klass, type, meth] - end + add_from out, store - ## - # Renders the +klass+ from +store+ to +out+. If the klass has no - # documentable items the class is added to +also_in+ instead. - - def render_class(out, store, klass, also_in) # :nodoc: - document = klass.comment.parse - # TODO the store's cache should always return an empty Array - class_methods = store.class_methods[klass.full_name] || [] - instance_methods = store.instance_methods[klass.full_name] || [] - attributes = store.attributes[klass.full_name] || [] - - if document.empty? and - instance_methods.empty? and class_methods.empty? - also_in << store - return - end + class_document_comment out, document - add_from out, store + if class_methods or instance_methods or not klass.constants.empty? + out << ::RDoc::Markup::Rule.new(1) + end - class_document_comment out, document + class_document_constants out, klass - if class_methods or instance_methods or not klass.constants.empty? - out << RDoc::Markup::Rule.new(1) - end + add_method_list out, class_methods, 'Class methods' + add_method_list out, instance_methods, 'Instance methods' + add_method_list out, attributes, 'Attributes' - class_document_constants out, klass + add_method_documentation out, klass if @show_all + end - add_method_list out, class_methods, 'Class methods' - add_method_list out, instance_methods, 'Instance methods' - add_method_list out, attributes, 'Attributes' + def render_method(out, store, method, name) # :nodoc: + out << ::RDoc::Markup::Paragraph.new("(from #{store.friendly_path})") - add_method_documentation out, klass if @show_all - end + unless name =~ /^#{Regexp.escape method.parent_name}/ + out << ::RDoc::Markup::Heading.new(3, "Implementation from #{method.parent_name}") + end - def render_method(out, store, method, name) # :nodoc: - out << RDoc::Markup::Paragraph.new("(from #{store.friendly_path})") + out << ::RDoc::Markup::Rule.new(1) - unless name =~ /^#{Regexp.escape method.parent_name}/ - out << RDoc::Markup::Heading.new(3, "Implementation from #{method.parent_name}") - end + render_method_arguments out, method.arglists + sig = method.type_signature_lines || store.rbs_signature_for(method) + render_method_type_signature out, sig if sig + render_method_superclass out, method + if method.is_alias_for + al = method.is_alias_for + alias_for = store.load_method al.parent_name, "#{al.name_prefix}#{al.name}" + render_method_comment out, method, alias_for + else + render_method_comment out, method + end + end - out << RDoc::Markup::Rule.new(1) - - render_method_arguments out, method.arglists - sig = method.type_signature_lines || store.rbs_signature_for(method) - render_method_type_signature out, sig if sig - render_method_superclass out, method - if method.is_alias_for - al = method.is_alias_for - alias_for = store.load_method al.parent_name, "#{al.name_prefix}#{al.name}" - render_method_comment out, method, alias_for - else - render_method_comment out, method - end - end + def render_method_arguments(out, arglists) # :nodoc: + return unless arglists - def render_method_arguments(out, arglists) # :nodoc: - return unless arglists + arglists = arglists.chomp.split "\n" + arglists = arglists.map { |line| line + "\n" } + out << ::RDoc::Markup::Verbatim.new(*arglists) + out << ::RDoc::Markup::Rule.new(1) + end - arglists = arglists.chomp.split "\n" - arglists = arglists.map { |line| line + "\n" } - out << RDoc::Markup::Verbatim.new(*arglists) - out << RDoc::Markup::Rule.new(1) - end + def render_method_comment(out, method, alias_for = nil)# :nodoc: + if alias_for + unless method.comment.nil? or method.comment.empty? + out << ::RDoc::Markup::BlankLine.new + out << method.comment.parse + end + out << ::RDoc::Markup::BlankLine.new + out << ::RDoc::Markup::Paragraph.new("(This method is an alias for #{alias_for.full_name}.)") + out << ::RDoc::Markup::BlankLine.new + out << alias_for.comment.parse + out << ::RDoc::Markup::BlankLine.new + else + out << ::RDoc::Markup::BlankLine.new + out << method.comment.parse + out << ::RDoc::Markup::BlankLine.new + end + end - def render_method_comment(out, method, alias_for = nil)# :nodoc: - if alias_for - unless method.comment.nil? or method.comment.empty? - out << RDoc::Markup::BlankLine.new - out << method.comment.parse + def render_method_type_signature(out, lines) # :nodoc: + out << ::RDoc::Markup::Verbatim.new(*lines.map { |s| s + "\n" }) end - out << RDoc::Markup::BlankLine.new - out << RDoc::Markup::Paragraph.new("(This method is an alias for #{alias_for.full_name}.)") - out << RDoc::Markup::BlankLine.new - out << alias_for.comment.parse - out << RDoc::Markup::BlankLine.new - else - out << RDoc::Markup::BlankLine.new - out << method.comment.parse - out << RDoc::Markup::BlankLine.new - end - end - def render_method_type_signature(out, lines) # :nodoc: - out << RDoc::Markup::Verbatim.new(*lines.map { |s| s + "\n" }) - end + def render_method_superclass(out, method) # :nodoc: + return unless + method.respond_to?(:superclass_method) and method.superclass_method - def render_method_superclass(out, method) # :nodoc: - return unless - method.respond_to?(:superclass_method) and method.superclass_method + out << ::RDoc::Markup::BlankLine.new + out << ::RDoc::Markup::Heading.new(4, "(Uses superclass method #{method.superclass_method})") + out << ::RDoc::Markup::Rule.new(1) + end - out << RDoc::Markup::BlankLine.new - out << RDoc::Markup::Heading.new(4, "(Uses superclass method #{method.superclass_method})") - out << RDoc::Markup::Rule.new(1) - end + ## + # Looks up and displays ri data according to the options given. + + def run + if @list_doc_dirs + puts @doc_dirs + elsif @list + list_known_classes @names + elsif @server + start_server + elsif @interactive or @names.empty? + interactive + else + display_names @names + end + rescue NotFoundError => e + abort e.message + end - ## - # Looks up and displays ri data according to the options given. - - def run - if @list_doc_dirs - puts @doc_dirs - elsif @list - list_known_classes @names - elsif @server - start_server - elsif @interactive or @names.empty? - interactive - else - display_names @names - end - rescue NotFoundError => e - abort e.message - end + ## + # Sets up a pager program to pass output through. Tries the RI_PAGER and + # PAGER environment variables followed by pager, less then more. - ## - # Sets up a pager program to pass output through. Tries the RI_PAGER and - # PAGER environment variables followed by pager, less then more. + def setup_pager + return if @use_stdout - def setup_pager - return if @use_stdout + pagers = [ENV['RI_PAGER'], ENV['PAGER'], 'pager', 'less', 'more'] - pagers = [ENV['RI_PAGER'], ENV['PAGER'], 'pager', 'less', 'more'] + require 'shellwords' + pagers.compact.uniq.each do |pager| + pager = Shellwords.split(pager) + next if pager.empty? - require 'shellwords' - pagers.compact.uniq.each do |pager| - pager = Shellwords.split(pager) - next if pager.empty? + io = IO.popen(pager, 'w') rescue next + next if $? and $?.pid == io.pid and $?.exited? # pager didn't work - io = IO.popen(pager, 'w') rescue next - next if $? and $?.pid == io.pid and $?.exited? # pager didn't work + @paging = true - @paging = true + return io + end - return io - end + @use_stdout = true - @use_stdout = true + nil + end - nil - end + ## + # Starts a WEBrick server for ri. - ## - # Starts a WEBrick server for ri. + def start_server + begin + require 'webrick' + rescue LoadError + abort "webrick is not found. You may need to `gem install webrick` to install webrick." + end - def start_server - begin - require 'webrick' - rescue LoadError - abort "webrick is not found. You may need to `gem install webrick` to install webrick." - end + server = WEBrick::HTTPServer.new :Port => @server - server = WEBrick::HTTPServer.new :Port => @server + extra_doc_dirs = @stores.map {|s| s.type == :extra ? s.path : nil}.compact - extra_doc_dirs = @stores.map {|s| s.type == :extra ? s.path : nil}.compact + server.mount '/', ::RDoc::RI::Servlet, nil, extra_doc_dirs - server.mount '/', RDoc::RI::Servlet, nil, extra_doc_dirs + trap 'INT' do server.shutdown end + trap 'TERM' do server.shutdown end - trap 'INT' do server.shutdown end - trap 'TERM' do server.shutdown end + server.start + end - server.start - end + RDOC_REFS_REGEXP = /\[rdoc-ref:([\w.]+)(@.*)?\]/ - RDOC_REFS_REGEXP = /\[rdoc-ref:([\w.]+)(@.*)?\]/ + def expand_rdoc_refs_at_the_bottom(out) + return unless @expand_refs - def expand_rdoc_refs_at_the_bottom(out) - return unless @expand_refs + extracted_rdoc_refs = [] - extracted_rdoc_refs = [] + out.each do |part| + content = if part.respond_to?(:text) + part.text + else + next + end - out.each do |part| - content = if part.respond_to?(:text) - part.text - else - next - end + rdoc_refs = content.scan(RDOC_REFS_REGEXP).uniq.map do |file_name, _anchor| + file_name + end - rdoc_refs = content.scan(RDOC_REFS_REGEXP).uniq.map do |file_name, _anchor| - file_name - end + extracted_rdoc_refs.concat(rdoc_refs) + end - extracted_rdoc_refs.concat(rdoc_refs) - end + found_pages = extracted_rdoc_refs.map do |ref| + begin + @stores.first.load_page(ref) + rescue ::RDoc::Store::MissingFileError + end + end.compact - found_pages = extracted_rdoc_refs.map do |ref| - begin - @stores.first.load_page(ref) - rescue RDoc::Store::MissingFileError + found_pages.each do |page| + out << ::RDoc::Markup::Heading.new(4, "Expanded from #{page.full_name}") + out << ::RDoc::Markup::BlankLine.new + out << page.comment.parse + end end - end.compact - - found_pages.each do |page| - out << RDoc::Markup::Heading.new(4, "Expanded from #{page.full_name}") - out << RDoc::Markup::BlankLine.new - out << page.comment.parse end end end diff --git a/lib/rdoc/ri/formatter.rb b/lib/rdoc/ri/formatter.rb index 832a101e6c..ed2606e515 100644 --- a/lib/rdoc/ri/formatter.rb +++ b/lib/rdoc/ri/formatter.rb @@ -1,6 +1,10 @@ # frozen_string_literal: true -## -# For RubyGems backwards compatibility +module RDoc + module RI + ## + # For RubyGems backwards compatibility -module RDoc::RI::Formatter # :nodoc: + module Formatter # :nodoc: + end + end end diff --git a/lib/rdoc/ri/paths.rb b/lib/rdoc/ri/paths.rb index 067b493132..2e42bb9a95 100644 --- a/lib/rdoc/ri/paths.rb +++ b/lib/rdoc/ri/paths.rb @@ -1,171 +1,175 @@ # frozen_string_literal: true require_relative '../rdoc' -## -# The directories where ri data lives. Paths can be enumerated via ::each, or -# queried individually via ::system_dir, ::site_dir, ::home_dir and ::gem_dir. - -module RDoc::RI::Paths - - #:stopdoc: - require 'rbconfig' - - version = RbConfig::CONFIG['ruby_version'] +module RDoc + module RI + ## + # The directories where ri data lives. Paths can be enumerated via ::each, or + # queried individually via ::system_dir, ::site_dir, ::home_dir and ::gem_dir. + + module Paths + + #:stopdoc: + require 'rbconfig' + + version = RbConfig::CONFIG['ruby_version'] + + BASE = File.join RbConfig::CONFIG['ridir'], version + + HOMEDIR = ::RDoc.home + #:startdoc: + + ## + # Iterates over each selected path yielding the directory and type. + # + # Yielded types: + # :system:: Where Ruby's ri data is stored. Yielded when +system+ is + # true + # :site:: Where ri for installed libraries are stored. Yielded when + # +site+ is true. Normally no ri data is stored here. + # :home:: ~/.rdoc. Yielded when +home+ is true. + # :gem:: ri data for an installed gem. Yielded when +gems+ is true. + # :extra:: ri data directory from the command line. Yielded for each + # entry in +extra_dirs+ + + def self.each(system = true, site = true, home = true, gems = :latest, *extra_dirs) # :yields: directory, type + return enum_for __method__, system, site, home, gems, *extra_dirs unless + block_given? + + extra_dirs.each do |dir| + yield dir, :extra + end - BASE = File.join RbConfig::CONFIG['ridir'], version + yield system_dir, :system if system + yield site_dir, :site if site + yield home_dir, :home if home and HOMEDIR - HOMEDIR = RDoc.home - #:startdoc: + gemdirs(gems).each do |dir| + yield dir, :gem + end if gems - ## - # Iterates over each selected path yielding the directory and type. - # - # Yielded types: - # :system:: Where Ruby's ri data is stored. Yielded when +system+ is - # true - # :site:: Where ri for installed libraries are stored. Yielded when - # +site+ is true. Normally no ri data is stored here. - # :home:: ~/.rdoc. Yielded when +home+ is true. - # :gem:: ri data for an installed gem. Yielded when +gems+ is true. - # :extra:: ri data directory from the command line. Yielded for each - # entry in +extra_dirs+ + nil + end - def self.each(system = true, site = true, home = true, gems = :latest, *extra_dirs) # :yields: directory, type - return enum_for __method__, system, site, home, gems, *extra_dirs unless - block_given? + ## + # The ri directory for the gem with +gem_name+. - extra_dirs.each do |dir| - yield dir, :extra - end + def self.gem_dir(name, version) + req = Gem::Requirement.new "= #{version}" - yield system_dir, :system if system - yield site_dir, :site if site - yield home_dir, :home if home and HOMEDIR + spec = Gem::Specification.find_by_name name, req - gemdirs(gems).each do |dir| - yield dir, :gem - end if gems + File.join spec.doc_dir, 'ri' + end - nil - end + ## + # The latest installed gems' ri directories. +filter+ can be :all or + # :latest. + # + # A +filter+ :all includes all versions of gems and includes gems without + # ri documentation. - ## - # The ri directory for the gem with +gem_name+. + def self.gemdirs(filter = :latest) + ri_paths = {} - def self.gem_dir(name, version) - req = Gem::Requirement.new "= #{version}" + all = Gem::Specification.map do |spec| + [File.join(spec.doc_dir, 'ri'), spec.name, spec.version] + end - spec = Gem::Specification.find_by_name name, req + if filter == :all + gemdirs = [] + + all.group_by do |_, name, _| + name + end.sort_by do |group, _| + group + end.map do |group, items| + items.sort_by do |_, _, version| + version + end.reverse_each do |dir,| + gemdirs << dir + end + end + + return gemdirs + end - File.join spec.doc_dir, 'ri' - end + all.each do |dir, name, ver| + next unless File.exist? dir - ## - # The latest installed gems' ri directories. +filter+ can be :all or - # :latest. - # - # A +filter+ :all includes all versions of gems and includes gems without - # ri documentation. + if ri_paths[name].nil? or ver > ri_paths[name].first + ri_paths[name] = [ver, name, dir] + end + end - def self.gemdirs(filter = :latest) - ri_paths = {} + ri_paths.sort_by { |_, (_, name, _)| name }.map { |k, v| v.last } + rescue LoadError + [] + end - all = Gem::Specification.map do |spec| - [File.join(spec.doc_dir, 'ri'), spec.name, spec.version] - end + ## + # The location of the rdoc data in the user's home directory. + # + # Like ::system, ri data in the user's home directory is rare and predates + # libraries distributed via RubyGems. ri data is rarely generated into this + # directory. - if filter == :all - gemdirs = [] - - all.group_by do |_, name, _| - name - end.sort_by do |group, _| - group - end.map do |group, items| - items.sort_by do |_, _, version| - version - end.reverse_each do |dir,| - gemdirs << dir - end + def self.home_dir + HOMEDIR end - return gemdirs - end + ## + # Returns existing directories from the selected documentation directories + # as an Array. + # + # See also ::each - all.each do |dir, name, ver| - next unless File.exist? dir + def self.path(system = true, site = true, home = true, gems = :latest, *extra_dirs) + path = raw_path system, site, home, gems, *extra_dirs - if ri_paths[name].nil? or ver > ri_paths[name].first - ri_paths[name] = [ver, name, dir] + path.select { |directory| File.directory? directory } end - end - - ri_paths.sort_by { |_, (_, name, _)| name }.map { |k, v| v.last } - rescue LoadError - [] - end - ## - # The location of the rdoc data in the user's home directory. - # - # Like ::system, ri data in the user's home directory is rare and predates - # libraries distributed via RubyGems. ri data is rarely generated into this - # directory. + ## + # Returns selected documentation directories including nonexistent + # directories. + # + # See also ::each - def self.home_dir - HOMEDIR - end + def self.raw_path(system, site, home, gems, *extra_dirs) + path = [] - ## - # Returns existing directories from the selected documentation directories - # as an Array. - # - # See also ::each + each(system, site, home, gems, *extra_dirs) do |dir, type| + path << dir + end - def self.path(system = true, site = true, home = true, gems = :latest, *extra_dirs) - path = raw_path system, site, home, gems, *extra_dirs + path.compact + end - path.select { |directory| File.directory? directory } - end + ## + # The location of ri data installed into the site dir. + # + # Historically this was available for documentation installed by Ruby + # libraries predating RubyGems. It is unlikely to contain any content for + # modern Ruby installations. - ## - # Returns selected documentation directories including nonexistent - # directories. - # - # See also ::each + def self.site_dir + File.join BASE, 'site' + end - def self.raw_path(system, site, home, gems, *extra_dirs) - path = [] + ## + # The location of the built-in ri data. + # + # This data is built automatically when `make` is run when Ruby is + # installed. If you did not install Ruby by hand you may need to install + # the documentation yourself. Please consult the documentation for your + # package manager or Ruby installer for details. You can also use the + # rdoc-data gem to install system ri data for common versions of Ruby. + + def self.system_dir + File.join BASE, 'system' + end - each(system, site, home, gems, *extra_dirs) do |dir, type| - path << dir end - - path.compact - end - - ## - # The location of ri data installed into the site dir. - # - # Historically this was available for documentation installed by Ruby - # libraries predating RubyGems. It is unlikely to contain any content for - # modern Ruby installations. - - def self.site_dir - File.join BASE, 'site' - end - - ## - # The location of the built-in ri data. - # - # This data is built automatically when `make` is run when Ruby is - # installed. If you did not install Ruby by hand you may need to install - # the documentation yourself. Please consult the documentation for your - # package manager or Ruby installer for details. You can also use the - # rdoc-data gem to install system ri data for common versions of Ruby. - - def self.system_dir - File.join BASE, 'system' end - end diff --git a/lib/rdoc/ri/servlet.rb b/lib/rdoc/ri/servlet.rb index c27aefd828..132317d37c 100644 --- a/lib/rdoc/ri/servlet.rb +++ b/lib/rdoc/ri/servlet.rb @@ -10,202 +10,204 @@ abort "webrick is not found. You may need to `gem install webrick` to install webrick." end -## -# 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::RI::Servlet -# -# If you want to mount the servlet some other place than the root, provide the -# base path when mounting: -# -# server.mount '/rdoc', RDoc::RI::Servlet, '/rdoc' - -class RDoc::RI::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 - - ## - # 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 /. - # - # Use +extra_doc_dirs+ for additional documentation directories. - # - # +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, extra_doc_dirs = []) - super server - - @cache = cache - @mount_path = mount_path - @extra_doc_dirs = extra_doc_dirs - @stores = stores - - @options = RDoc::Options.new - @options.op_dir = '.' - - darkfish_dir = nil - - # HACK dup - $LOAD_PATH.each do |path| - darkfish_dir = File.join path, 'rdoc/generator/template/darkfish/' - next unless File.directory? darkfish_dir - @options.template_dir = darkfish_dir - break - end +module RDoc + module RI + ## + # 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::RI::Servlet + # + # If you want to mount the servlet some other place than the root, provide the + # base path when mounting: + # + # server.mount '/rdoc', RDoc::RI::Servlet, '/rdoc' + + class 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 + + ## + # 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 - @asset_dirs = { - :darkfish => darkfish_dir, - :json_index => - File.expand_path('../generator/template/json_index/', __FILE__), - } - end + ## + # Creates a new WEBrick servlet. + # + # Use +mount_path+ when mounting the servlet somewhere other than /. + # + # Use +extra_doc_dirs+ for additional documentation directories. + # + # +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, extra_doc_dirs = []) + super server + + @cache = cache + @mount_path = mount_path + @extra_doc_dirs = extra_doc_dirs + @stores = stores + + @options = ::RDoc::Options.new + @options.op_dir = '.' + + darkfish_dir = nil + + # HACK dup + $LOAD_PATH.each do |path| + darkfish_dir = File.join path, 'rdoc/generator/template/darkfish/' + next unless File.directory? darkfish_dir + @options.template_dir = darkfish_dir + break + end + + @asset_dirs = { + :darkfish => darkfish_dir, + :json_index => + File.expand_path('../generator/template/json_index/', __FILE__), + } + end - ## - # Serves the asset at the path in +req+ for +generator_name+ via +res+. + ## + # 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] + def asset(generator_name, req, res) + asset_dir = @asset_dirs[generator_name] - asset_path = File.join asset_dir, req.path + asset_path = File.join asset_dir, req.path - if_modified_since req, res, asset_path + if_modified_since req, res, asset_path - res.body = File.read asset_path + res.body = File.read asset_path - res.content_type = case req.path - when /\.css\z/ then 'text/css' - when /\.js\z/ then 'application/javascript' - else 'application/octet-stream' - end - end + res.content_type = case req.path + when /\.css\z/ then 'text/css' + when /\.js\z/ then 'application/javascript' + else 'application/octet-stream' + end + end - ## - # GET request entry point. Fills in +res+ for the path, etc. in +req+. - - def do_GET(req, res) - req.path.sub!(/\A#{Regexp.escape @mount_path}/, '') if @mount_path - - case req.path - when '/' - root req, res - when '/js/darkfish.js', '/js/jquery.js', '/js/search.js', - %r%^/css/%, %r%^/images/%, %r%^/fonts/% - asset :darkfish, req, res - when '/js/navigation.js', '/js/searcher.js' - asset :json_index, req, res - when '/js/search_index.js' - root_search req, res - else - show_documentation req, res - end - rescue WEBrick::HTTPStatus::NotFound => e - generator = generator_for RDoc::Store.new(@options) - - not_found generator, req, res, e.message - rescue WEBrick::HTTPStatus::Status - raise - rescue => e - error e, req, res - end + ## + # GET request entry point. Fills in +res+ for the path, etc. in +req+. + + def do_GET(req, res) + req.path.sub!(/\A#{Regexp.escape @mount_path}/, '') if @mount_path + + case req.path + when '/' + root req, res + when '/js/darkfish.js', '/js/jquery.js', '/js/search.js', + %r%^/css/%, %r%^/images/%, %r%^/fonts/% + asset :darkfish, req, res + when '/js/navigation.js', '/js/searcher.js' + asset :json_index, req, res + when '/js/search_index.js' + root_search req, res + else + show_documentation req, res + end + rescue WEBrick::HTTPStatus::NotFound => e + generator = generator_for ::RDoc::Store.new(@options) + + not_found generator, req, res, e.message + rescue WEBrick::HTTPStatus::Status + raise + rescue => e + 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) - text_name = path.chomp '.html' - name = text_name.gsub '/', '::' - - if klass = store.find_class_or_module(name) - res.body = generator.generate_class klass - elsif page = store.find_text_page(name.sub(/_([^_]*)\z/, '.\1')) - res.body = generator.generate_page page - elsif page = store.find_text_page(text_name.sub(/_([^_]*)\z/, '.\1')) - res.body = generator.generate_page page - else - not_found generator, req, res - end - 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) + text_name = path.chomp '.html' + name = text_name.gsub '/', '::' + + if klass = store.find_class_or_module(name) + res.body = generator.generate_class klass + elsif page = store.find_text_page(name.sub(/_([^_]*)\z/, '.\1')) + res.body = generator.generate_page page + elsif page = store.find_text_page(text_name.sub(/_([^_]*)\z/, '.\1')) + res.body = generator.generate_page page + else + not_found generator, req, res + end + end - ## - # Creates the JSON search index on +res+ for the given +store+. +generator+ - # must respond to \#json_index to build. +req+ is ignored. + ## + # 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] = - JSON.dump generator.json_index.build_index - end + def documentation_search(store, generator, req, res) + json_index = @cache[store].fetch :json_index do + @cache[store][:json_index] = + JSON.dump generator.json_index.build_index + end - res.content_type = 'application/javascript' - res.body = "var search_data = #{json_index}" - end + res.content_type = 'application/javascript' + res.body = "var search_data = #{json_index}" + end - ## - # Returns the RDoc::Store and path relative to +mount_path+ for - # documentation at +path+. + ## + # Returns the RDoc::Store and path relative to +mount_path+ for + # documentation at +path+. - def documentation_source(path) - _, source_name, path = path.split '/', 3 + def documentation_source(path) + _, source_name, path = path.split '/', 3 - store = @stores[source_name] - return store, path if store + store = @stores[source_name] + return store, path if store - store = store_for source_name + store = store_for source_name - store.load_all + store.load_all - @stores[source_name] = store + @stores[source_name] = store - return store, path - end + return store, path + end - ## - # Generates an error page for the +exception+ while handling +req+ on +res+. + ## + # Generates an error page for the +exception+ while handling +req+ on +res+. - def error(exception, req, res) - backtrace = exception.backtrace.join "\n" + def error(exception, req, res) + backtrace = exception.backtrace.join "\n" - res.content_type = 'text/html' - res.status = 500 - res.body = <<-BODY + res.content_type = 'text/html' + res.status = 500 + res.body = <<-BODY @@ -219,7 +221,7 @@ def error(exception, req, res)

    Error

    While processing #{ERB::Util.html_escape req.request_uri} the -RDoc (#{ERB::Util.html_escape RDoc::VERSION}) server has encountered a +RDoc (#{ERB::Util.html_escape(::RDoc::VERSION)}) server has encountered a #{ERB::Util.html_escape exception.class} exception: @@ -238,215 +240,217 @@ def error(exception, req, res) BODY - end + end - ## - # Instantiates a Darkfish generator for +store+ + ## + # Instantiates a Darkfish generator for +store+ - def generator_for(store) - generator = RDoc::Generator::Darkfish.new store, @options - generator.file_output = false - generator.asset_rel_path = '..' - generator.setup + def generator_for(store) + generator = ::RDoc::Generator::Darkfish.new store, @options + generator.file_output = false + generator.asset_rel_path = '..' + generator.setup - rdoc = RDoc::RDoc.new - rdoc.store = store - rdoc.generator = generator - rdoc.options = @options + rdoc = ::RDoc::RDoc.new + rdoc.store = store + rdoc.generator = generator + rdoc.options = @options - @options.main_page = store.main - @options.title = store.title + @options.main_page = store.main + @options.title = store.title - generator - end + 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+. + ## + # 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 + def if_modified_since(req, res, path = nil) + last_modified = File.stat(path).mtime if path - res['last-modified'] = last_modified.httpdate + res['last-modified'] = last_modified.httpdate - return unless ims = req['if-modified-since'] + return unless ims = req['if-modified-since'] - ims = Time.parse ims + ims = Time.parse ims - unless ims < last_modified - res.body = '' - raise WEBrick::HTTPStatus::NotModified - end - end + unless ims < last_modified + res.body = '' + raise WEBrick::HTTPStatus::NotModified + 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 - extra_counter = 0 - ri_paths.map do |path, type| - store = RDoc::Store.new(@options, path: path, type: type) - exists = File.exist? store.cache_path - - case type - when :gem - gem_path = path[%r%/([^/]*)/ri$%, 1] - [gem_path, "#{gem_path}/", exists, type, path] - when :system - ['Ruby Documentation', 'ruby/', exists, type, path] - when :site - ['Site Documentation', 'site/', exists, type, path] - when :home - ['Home Documentation', 'home/', exists, type, path] - when :extra - extra_counter += 1 - store.load_cache if exists - title = store.title || "Extra Documentation" - [title, "extra-#{extra_counter}/", exists, type, path] + ## + # 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 + extra_counter = 0 + ri_paths.map do |path, type| + store = ::RDoc::Store.new(@options, path: path, type: type) + exists = File.exist? store.cache_path + + case type + when :gem + gem_path = path[%r%/([^/]*)/ri$%, 1] + [gem_path, "#{gem_path}/", exists, type, path] + when :system + ['Ruby Documentation', 'ruby/', exists, type, path] + when :site + ['Site Documentation', 'site/', exists, type, path] + when :home + ['Home Documentation', 'home/', exists, type, path] + when :extra + extra_counter += 1 + store.load_cache if exists + title = store.title || "Extra Documentation" + [title, "extra-#{extra_counter}/", exists, type, path] + end + end end - end - end - ## - # Returns a 404 page built by +generator+ for +req+ on +res+. + ## + # Returns a 404 page built by +generator+ for +req+ on +res+. - def not_found(generator, req, res, message = nil) - message ||= "The page #{ERB::Util.h req.path} was not found" - res.body = generator.generate_servlet_not_found message - res.status = 404 - end + def not_found(generator, req, res, message = nil) + message ||= "The page #{ERB::Util.h req.path} was not found" + res.body = generator.generate_servlet_not_found message + res.status = 404 + end - ## - # Enumerates the ri paths. See RDoc::RI::Paths#each + ## + # Enumerates the ri paths. See RDoc::RI::Paths#each - def ri_paths(&block) - RDoc::RI::Paths.each true, true, true, :all, *@extra_doc_dirs, &block #TODO: pass extra_dirs - end + def ri_paths(&block) + ::RDoc::RI::Paths.each true, true, true, :all, *@extra_doc_dirs, &block #TODO: pass extra_dirs + end - ## - # Generates the root page on +res+. +req+ is ignored. + ## + # Generates the root page on +res+. +req+ is ignored. - def root(req, res) - generator = RDoc::Generator::Darkfish.new nil, @options + def root(req, res) + generator = ::RDoc::Generator::Darkfish.new nil, @options - res.body = generator.generate_servlet_root installed_docs + res.body = generator.generate_servlet_root installed_docs - res.content_type = 'text/html' - end + 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 = [] - - installed_docs.map do |name, href, exists, type, path| - next unless exists - - search_index << name - - case type - when :gem - gemspec = path.gsub(%r%/doc/([^/]*?)/ri$%, - '/specifications/\1.gemspec') - - spec = Gem::Specification.load gemspec - - path = spec.full_name - comment = spec.summary - when :system - path = 'ruby' - comment = 'Documentation for the Ruby standard library' - when :site - path = 'site' - comment = 'Documentation for non-gem libraries' - when :home - path = 'home' - comment = 'Documentation from your home directory' - when :extra - comment = name + ## + # Generates a search index for the root page on +res+. +req+ is ignored. + + def root_search(req, res) + search_index = [] + info = [] + + installed_docs.map do |name, href, exists, type, path| + next unless exists + + search_index << name + + case type + when :gem + gemspec = path.gsub(%r%/doc/([^/]*?)/ri$%, + '/specifications/\1.gemspec') + + spec = Gem::Specification.load gemspec + + path = spec.full_name + comment = spec.summary + when :system + path = 'ruby' + comment = 'Documentation for the Ruby standard library' + when :site + path = 'site' + comment = 'Documentation for non-gem libraries' + when :home + path = 'home' + comment = 'Documentation from your home directory' + when :extra + comment = name + end + + info << [name, '', path, '', comment] + end + + index = { + :index => { + :searchIndex => search_index, + :longSearchIndex => search_index, + :info => info, + } + } + + res.body = "var search_data = #{JSON.dump index};" + res.content_type = 'application/javascript' end - info << [name, '', path, '', comment] - 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 + + if_modified_since req, res, store.cache_path + + generator = generator_for store + + case path + when nil, '', 'index.html' + res.body = generator.generate_index + when 'table_of_contents.html' + res.body = generator.generate_table_of_contents + when 'js/search_index.js' + documentation_search store, generator, req, res + else + documentation_page store, generator, path, req, res + end + ensure + res.content_type ||= 'text/html' + end - index = { - :index => { - :searchIndex => search_index, - :longSearchIndex => search_index, - :info => info, - } - } + ## + # Returns an RDoc::Store for the given +source_name+ ('ruby' or a gem name). - res.body = "var search_data = #{JSON.dump index};" - res.content_type = 'application/javascript' - end + def store_for(source_name) + case source_name + when 'home' + ::RDoc::Store.new(@options, path: ::RDoc::RI::Paths.home_dir, type: :home) + when 'ruby' + ::RDoc::Store.new(@options, path: ::RDoc::RI::Paths.system_dir, type: :system) + when 'site' + ::RDoc::Store.new(@options, path: ::RDoc::RI::Paths.site_dir, type: :site) + when /\Aextra-(\d+)\z/ + index = $1.to_i - 1 + ri_dir = installed_docs[index][4] + ::RDoc::Store.new(@options, path: ri_dir, type: :extra) + else + ri_dir, type = ri_paths.find do |dir, dir_type| + next unless dir_type == :gem - ## - # Displays documentation for +req+ on +res+, whether that be HTML or some - # asset. + source_name == dir[%r%/([^/]*)/ri$%, 1] + end - def show_documentation(req, res) - store, path = documentation_source req.path + raise WEBrick::HTTPStatus::NotFound, + "Could not find gem \"#{ERB::Util.html_escape(source_name)}\". Are you sure you installed it?" unless ri_dir - if_modified_since req, res, store.cache_path + store = ::RDoc::Store.new(@options, path: ri_dir, type: type) - generator = generator_for store + return store if File.exist? store.cache_path - case path - when nil, '', 'index.html' - res.body = generator.generate_index - when 'table_of_contents.html' - res.body = generator.generate_table_of_contents - when 'js/search_index.js' - documentation_search store, generator, req, res - else - documentation_page store, generator, path, req, res - end - ensure - res.content_type ||= 'text/html' - end + raise WEBrick::HTTPStatus::NotFound, + "Could not find documentation for \"#{ERB::Util.html_escape(source_name)}\". Please run `gem rdoc --ri gem_name`" - ## - # Returns an RDoc::Store for the given +source_name+ ('ruby' or a gem name). - - def store_for(source_name) - case source_name - when 'home' - RDoc::Store.new(@options, path: RDoc::RI::Paths.home_dir, type: :home) - when 'ruby' - RDoc::Store.new(@options, path: RDoc::RI::Paths.system_dir, type: :system) - when 'site' - RDoc::Store.new(@options, path: RDoc::RI::Paths.site_dir, type: :site) - when /\Aextra-(\d+)\z/ - index = $1.to_i - 1 - ri_dir = installed_docs[index][4] - RDoc::Store.new(@options, path: ri_dir, type: :extra) - else - ri_dir, type = ri_paths.find do |dir, dir_type| - next unless dir_type == :gem - - source_name == dir[%r%/([^/]*)/ri$%, 1] + end end - raise WEBrick::HTTPStatus::NotFound, - "Could not find gem \"#{ERB::Util.html_escape(source_name)}\". Are you sure you installed it?" unless ri_dir - - store = RDoc::Store.new(@options, path: ri_dir, type: type) - - return store if File.exist? store.cache_path - - raise WEBrick::HTTPStatus::NotFound, - "Could not find documentation for \"#{ERB::Util.html_escape(source_name)}\". Please run `gem rdoc --ri gem_name`" - end end - end diff --git a/lib/rdoc/ri/store.rb b/lib/rdoc/ri/store.rb index 96742e7ae3..ea2aebfecf 100644 --- a/lib/rdoc/ri/store.rb +++ b/lib/rdoc/ri/store.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true -module RDoc::RI +module RDoc + module RI - Store = RDoc::Store # :nodoc: + Store = ::RDoc::Store # :nodoc: + end end diff --git a/lib/rdoc/ri/task.rb b/lib/rdoc/ri/task.rb index 214ac62208..685a706b1e 100644 --- a/lib/rdoc/ri/task.rb +++ b/lib/rdoc/ri/task.rb @@ -2,70 +2,74 @@ begin gem 'rdoc' rescue Gem::LoadError -end unless defined?(RDoc) +end unless defined?(::RDoc) require_relative '../task' -## -# RDoc::RI::Task creates ri data in ./.rdoc for your project. -# -# It contains the following tasks: -# -# [ri] -# Build ri data -# -# [clobber_ri] -# Delete ri data files. This target is automatically added to the main -# clobber target. -# -# [reri] -# Rebuild the ri data from scratch even if they are not out of date. -# -# Simple example: -# -# require 'rdoc/ri/task' -# -# RDoc::RI::Task.new do |ri| -# ri.main = 'README.md' -# ri.rdoc_files.include 'README.md', 'lib/**/*.rb' -# end -# -# For further configuration details see RDoc::Task. +module RDoc + module RI + ## + # RDoc::RI::Task creates ri data in ./.rdoc for your project. + # + # It contains the following tasks: + # + # [ri] + # Build ri data + # + # [clobber_ri] + # Delete ri data files. This target is automatically added to the main + # clobber target. + # + # [reri] + # Rebuild the ri data from scratch even if they are not out of date. + # + # Simple example: + # + # require 'rdoc/ri/task' + # + # RDoc::RI::Task.new do |ri| + # ri.main = 'README.md' + # ri.rdoc_files.include 'README.md', 'lib/**/*.rb' + # end + # + # For further configuration details see RDoc::Task. -class RDoc::RI::Task < RDoc::Task + class Task < ::RDoc::Task - DEFAULT_NAMES = { # :nodoc: - :clobber_rdoc => :clobber_ri, - :rdoc => :ri, - :rerdoc => :reri, - } + DEFAULT_NAMES = { # :nodoc: + :clobber_rdoc => :clobber_ri, + :rdoc => :ri, + :rerdoc => :reri, + } - ## - # Create an ri task with the given name. See RDoc::Task for documentation on - # setting names. + ## + # Create an ri task with the given name. See RDoc::Task for documentation on + # setting names. - def initialize(name = DEFAULT_NAMES) # :yield: self - super - end + def initialize(name = DEFAULT_NAMES) # :yield: self + super + end - def clobber_task_description # :nodoc: - "Remove RI data files" - end + def clobber_task_description # :nodoc: + "Remove RI data files" + end - ## - # Sets default task values + ## + # Sets default task values - def defaults - super + def defaults + super - @rdoc_dir = '.rdoc' - end + @rdoc_dir = '.rdoc' + end - def rdoc_task_description # :nodoc: - 'Build RI data files' - end + def rdoc_task_description # :nodoc: + 'Build RI data files' + end - def rerdoc_task_description # :nodoc: - 'Rebuild RI data files' + def rerdoc_task_description # :nodoc: + 'Rebuild RI data files' + end + end end end diff --git a/lib/rdoc/rubygems_hook.rb b/lib/rdoc/rubygems_hook.rb index e1a1e93494..a6718f30d7 100644 --- a/lib/rdoc/rubygems_hook.rb +++ b/lib/rdoc/rubygems_hook.rb @@ -3,255 +3,257 @@ require 'fileutils' require_relative '../rdoc' -# We define the following two similar name classes in this file: -# -# - RDoc::RubyGemsHook -# - RDoc::RubygemsHook -# -# RDoc::RubyGemsHook is the main class that has real logic. -# -# RDoc::RubygemsHook is a class that is only for -# compatibility. RDoc::RubygemsHook is used by RubyGems directly. We -# can remove this when all maintained RubyGems remove -# `rubygems/rdoc.rb`. - -class RDoc::RubyGemsHook - - include Gem::UserInteraction - extend Gem::UserInteraction - - @rdoc_version = nil - @specs = [] +module RDoc + # We define the following two similar name classes in this file: + # + # - RDoc::RubyGemsHook + # - RDoc::RubygemsHook + # + # RDoc::RubyGemsHook is the main class that has real logic. + # + # RDoc::RubygemsHook is a class that is only for + # compatibility. RDoc::RubygemsHook is used by RubyGems directly. We + # can remove this when all maintained RubyGems remove + # `rubygems/rdoc.rb`. - ## - # Force installation of documentation? + class RubyGemsHook - attr_accessor :force + include Gem::UserInteraction + extend Gem::UserInteraction - ## - # Generate rdoc? + @rdoc_version = nil + @specs = [] - attr_accessor :generate_rdoc + ## + # Force installation of documentation? - ## - # Generate ri data? + attr_accessor :force - attr_accessor :generate_ri + ## + # Generate rdoc? - class << self + attr_accessor :generate_rdoc ## - # Loaded version of RDoc. Set by ::load_rdoc - - attr_reader :rdoc_version + # Generate ri data? - end + attr_accessor :generate_ri - ## - # Post installs hook that generates documentation for each specification in - # +specs+ + class << self - def self.generate(installer, specs) - start = Time.now - types = installer.document + ## + # Loaded version of RDoc. Set by ::load_rdoc - generate_rdoc = types.include? 'rdoc' - generate_ri = types.include? 'ri' + attr_reader :rdoc_version - specs.each do |spec| - new(spec, generate_rdoc, generate_ri).generate end - return unless generate_rdoc or generate_ri + ## + # Post installs hook that generates documentation for each specification in + # +specs+ - duration = (Time.now - start).to_i - names = specs.map(&:name).join ', ' + def self.generate(installer, specs) + start = Time.now + types = installer.document - say "Done installing documentation for #{names} after #{duration} seconds" - end + generate_rdoc = types.include? 'rdoc' + generate_ri = types.include? 'ri' - def self.remove(uninstaller) - new(uninstaller.spec).remove - end + specs.each do |spec| + new(spec, generate_rdoc, generate_ri).generate + end - ## - # Loads the RDoc generator + return unless generate_rdoc or generate_ri - def self.load_rdoc - return if @rdoc_version + duration = (Time.now - start).to_i + names = specs.map(&:name).join ', ' - require_relative 'rdoc' + say "Done installing documentation for #{names} after #{duration} seconds" + end - @rdoc_version = Gem::Version.new ::RDoc::VERSION - end + def self.remove(uninstaller) + new(uninstaller.spec).remove + end - ## - # Creates a new documentation generator for +spec+. RDoc and ri data - # generation can be enabled or disabled through +generate_rdoc+ and - # +generate_ri+ respectively. - # - # Only +generate_ri+ is enabled by default. + ## + # Loads the RDoc generator - def initialize(spec, generate_rdoc = false, generate_ri = true) - @doc_dir = spec.doc_dir - @force = false - @rdoc = nil - @spec = spec + def self.load_rdoc + return if @rdoc_version - @generate_rdoc = generate_rdoc - @generate_ri = generate_ri + require_relative 'rdoc' - @rdoc_dir = spec.doc_dir 'rdoc' - @ri_dir = spec.doc_dir 'ri' - end + @rdoc_version = Gem::Version.new ::RDoc::VERSION + end - ## - # Removes legacy rdoc arguments from +args+ - #-- - # TODO move to RDoc::Options + ## + # Creates a new documentation generator for +spec+. RDoc and ri data + # generation can be enabled or disabled through +generate_rdoc+ and + # +generate_ri+ respectively. + # + # Only +generate_ri+ is enabled by default. - def delete_legacy_args(args) - args.delete '--inline-source' - args.delete '--promiscuous' - args.delete '-p' - args.delete '--one-file' - end + def initialize(spec, generate_rdoc = false, generate_ri = true) + @doc_dir = spec.doc_dir + @force = false + @rdoc = nil + @spec = spec - ## - # Generates documentation using the named +generator+ ("aliki" or "ri") - # and following the given +options+. - # - # Documentation will be generated into +destination+ + @generate_rdoc = generate_rdoc + @generate_ri = generate_ri - def document(generator, options, destination) - generator_name = generator + @rdoc_dir = spec.doc_dir 'rdoc' + @ri_dir = spec.doc_dir 'ri' + end - options = options.dup - options.exclude ||= [] # TODO maybe move to RDoc::Options#finish - options.setup_generator generator - options.op_dir = destination - Dir.chdir @spec.full_gem_path do - options.finish + ## + # Removes legacy rdoc arguments from +args+ + #-- + # TODO move to RDoc::Options + + def delete_legacy_args(args) + args.delete '--inline-source' + args.delete '--promiscuous' + args.delete '-p' + args.delete '--one-file' end - generator = options.generator.new @rdoc.store, options + ## + # Generates documentation using the named +generator+ ("aliki" or "ri") + # and following the given +options+. + # + # Documentation will be generated into +destination+ + + def document(generator, options, destination) + generator_name = generator + + options = options.dup + options.exclude ||= [] # TODO maybe move to RDoc::Options#finish + options.setup_generator generator + options.op_dir = destination + Dir.chdir @spec.full_gem_path do + options.finish + end + + generator = options.generator.new @rdoc.store, options - @rdoc.options = options - @rdoc.generator = generator + @rdoc.options = options + @rdoc.generator = generator - say "Installing #{generator_name} documentation for #{@spec.full_name}" + say "Installing #{generator_name} documentation for #{@spec.full_name}" - FileUtils.mkdir_p options.op_dir + FileUtils.mkdir_p options.op_dir - Dir.chdir options.op_dir do - begin - @rdoc.class.current = @rdoc - @rdoc.generator.generate - ensure - @rdoc.class.current = nil + Dir.chdir options.op_dir do + begin + @rdoc.class.current = @rdoc + @rdoc.generator.generate + ensure + @rdoc.class.current = nil + end end end - end - ## - # Generates RDoc and ri data + ## + # Generates RDoc and ri data - def generate - return if @spec.default_gem? - return unless @generate_ri or @generate_rdoc + def generate + return if @spec.default_gem? + return unless @generate_ri or @generate_rdoc - setup + setup - options = nil + options = nil - args = @spec.rdoc_options - args.concat @spec.source_paths - args.concat @spec.extra_rdoc_files + args = @spec.rdoc_options + args.concat @spec.source_paths + args.concat @spec.extra_rdoc_files - case config_args = Gem.configuration[:rdoc] - when String - args = args.concat config_args.split(' ') - when Array - args = args.concat config_args - end + case config_args = Gem.configuration[:rdoc] + when String + args = args.concat config_args.split(' ') + when Array + args = args.concat config_args + end - delete_legacy_args args + delete_legacy_args args - Dir.chdir @spec.full_gem_path do - options = ::RDoc::Options.new - options.default_title = "#{@spec.full_name} Documentation" - options.parse args - options.quiet = !Gem.configuration.really_verbose - end + Dir.chdir @spec.full_gem_path do + options = ::RDoc::Options.new + options.default_title = "#{@spec.full_name} Documentation" + options.parse args + options.quiet = !Gem.configuration.really_verbose + end - @rdoc = new_rdoc + @rdoc = new_rdoc - say "Parsing documentation for #{@spec.full_name}" + say "Parsing documentation for #{@spec.full_name}" - Dir.chdir @spec.full_gem_path do - # RDoc::Options#finish must be called before parse_files. - # RDoc::Options#finish is also called after ri/aliki generator setup. - # We need to dup the options to avoid modifying it after finish is called. - parse_options = options.dup - parse_options.finish - @rdoc.options = parse_options - @rdoc.store = RDoc::Store.new(parse_options) - @rdoc.parse_files parse_options.files - end + Dir.chdir @spec.full_gem_path do + # RDoc::Options#finish must be called before parse_files. + # RDoc::Options#finish is also called after ri/aliki generator setup. + # We need to dup the options to avoid modifying it after finish is called. + parse_options = options.dup + parse_options.finish + @rdoc.options = parse_options + @rdoc.store = ::RDoc::Store.new(parse_options) + @rdoc.parse_files parse_options.files + end - document 'ri', options, @ri_dir if - @generate_ri and (@force or not File.exist? @ri_dir) + document 'ri', options, @ri_dir if + @generate_ri and (@force or not File.exist? @ri_dir) - document 'aliki', options, @rdoc_dir if - @generate_rdoc and (@force or not File.exist? @rdoc_dir) - end + document 'aliki', options, @rdoc_dir if + @generate_rdoc and (@force or not File.exist? @rdoc_dir) + end - ## - # #new_rdoc creates a new RDoc instance. This method is provided only to - # make testing easier. + ## + # #new_rdoc creates a new RDoc instance. This method is provided only to + # make testing easier. - def new_rdoc # :nodoc: - ::RDoc::RDoc.new - end + def new_rdoc # :nodoc: + ::RDoc::RDoc.new + end - ## - # Is rdoc documentation installed? + ## + # Is rdoc documentation installed? - def rdoc_installed? - File.exist? @rdoc_dir - end + def rdoc_installed? + File.exist? @rdoc_dir + end - ## - # Removes generated RDoc and ri data + ## + # Removes generated RDoc and ri data - def remove - base_dir = @spec.base_dir + def remove + base_dir = @spec.base_dir - raise Gem::FilePermissionError, base_dir unless File.writable? base_dir + raise Gem::FilePermissionError, base_dir unless File.writable? base_dir - FileUtils.rm_rf @rdoc_dir - FileUtils.rm_rf @ri_dir - end + FileUtils.rm_rf @rdoc_dir + FileUtils.rm_rf @ri_dir + end - ## - # Is ri data installed? + ## + # Is ri data installed? - def ri_installed? - File.exist? @ri_dir - end + def ri_installed? + File.exist? @ri_dir + end - ## - # Prepares the spec for documentation generation + ## + # Prepares the spec for documentation generation - def setup - self.class.load_rdoc + def setup + self.class.load_rdoc - raise Gem::FilePermissionError, @doc_dir if - File.exist?(@doc_dir) and not File.writable?(@doc_dir) + raise Gem::FilePermissionError, @doc_dir if + File.exist?(@doc_dir) and not File.writable?(@doc_dir) - FileUtils.mkdir_p @doc_dir unless File.exist? @doc_dir - end + FileUtils.mkdir_p @doc_dir unless File.exist? @doc_dir + end + end end module RDoc diff --git a/lib/rdoc/server.rb b/lib/rdoc/server.rb index a5b4862f6b..af35f79ae4 100644 --- a/lib/rdoc/server.rb +++ b/lib/rdoc/server.rb @@ -6,26 +6,27 @@ require 'set' require 'uri' -## -# A minimal HTTP server for live-reloading RDoc documentation. -# -# Uses Ruby's built-in +TCPServer+ (no external dependencies). -# -# Used by rdoc --server to let developers preview documentation -# while editing source files. Parses sources once on startup, watches for -# file changes, re-parses only the changed files, and auto-refreshes the -# browser via a simple polling script. - -class RDoc::Server - +module RDoc ## - # Returns a live-reload polling script with the given +last_change_time+ - # embedded so the browser knows the exact timestamp of the content it - # received. This avoids a race where a change that occurs between page - # generation and the first poll would be silently skipped. + # A minimal HTTP server for live-reloading RDoc documentation. + # + # Uses Ruby's built-in +TCPServer+ (no external dependencies). + # + # Used by rdoc --server to let developers preview documentation + # while editing source files. Parses sources once on startup, watches for + # file changes, re-parses only the changed files, and auto-refreshes the + # browser via a simple polling script. + + class Server - def self.live_reload_script(last_change_time) - <<~JS + ## + # Returns a live-reload polling script with the given +last_change_time+ + # embedded so the browser knows the exact timestamp of the content it + # received. This avoids a race where a change that occurs between page + # generation and the first poll would be silently skipped. + + def self.live_reload_script(last_change_time) + <<~JS JS - end - - CONTENT_TYPES = { - '.html' => 'text/html', - '.css' => 'text/css', - '.js' => 'application/javascript', - '.json' => 'application/json', - }.freeze - - STATUS_TEXTS = { - 200 => 'OK', - 400 => 'Bad Request', - 404 => 'Not Found', - 405 => 'Method Not Allowed', - 500 => 'Internal Server Error', - }.freeze - - class FileChanges # :nodoc: - attr_reader :changed_files, :removed_files - - def initialize(rdoc) - @rdoc = rdoc - @changed_files = [] - @removed_files = [] - @reload_rbs_signatures = false end - def record_changed(file) - reload_rbs_signatures_if_needed file - changed_files << file - end + CONTENT_TYPES = { + '.html' => 'text/html', + '.css' => 'text/css', + '.js' => 'application/javascript', + '.json' => 'application/json', + }.freeze + + STATUS_TEXTS = { + 200 => 'OK', + 400 => 'Bad Request', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 500 => 'Internal Server Error', + }.freeze + + class FileChanges # :nodoc: + attr_reader :changed_files, :removed_files + + def initialize(rdoc) + @rdoc = rdoc + @changed_files = [] + @removed_files = [] + @reload_rbs_signatures = false + end - def record_removed(file) - reload_rbs_signatures_if_needed file - removed_files << file - end + def record_changed(file) + reload_rbs_signatures_if_needed file + changed_files << file + end - def reload_rbs_signatures? - @reload_rbs_signatures - end + def record_removed(file) + reload_rbs_signatures_if_needed file + removed_files << file + end - def source_files_changed? - !changed_files.empty? || !removed_files.empty? - end + def reload_rbs_signatures? + @reload_rbs_signatures + end + + def source_files_changed? + !changed_files.empty? || !removed_files.empty? + end - private + private - def reload_rbs_signatures_if_needed(file) - @reload_rbs_signatures = true if @rdoc.auto_discovered_rbs_signature_file?(file) + def reload_rbs_signatures_if_needed(file) + @reload_rbs_signatures = true if @rdoc.auto_discovered_rbs_signature_file?(file) + end end - end - ## - # Creates a new server. - # - # +rdoc+ is the RDoc::RDoc instance that has already parsed the source - # files. - # +port+ is the TCP port to listen on. - - def initialize(rdoc, port) - @rdoc = rdoc - @options = rdoc.options - @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 = {} - @last_change_time = Time.now.to_f - @mutex = Mutex.new - @running = false - end + ## + # Creates a new server. + # + # +rdoc+ is the RDoc::RDoc instance that has already parsed the source + # files. + # +port+ is the TCP port to listen on. - ## - # Starts the server. Blocks until interrupted. + def initialize(rdoc, port) + @rdoc = rdoc + @options = rdoc.options + @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 = {} + @last_change_time = Time.now.to_f + @mutex = Mutex.new + @running = false + end - def start - @tcp_server = TCPServer.new('127.0.0.1', @port) - @running = true + ## + # Starts the server. Blocks until interrupted. - @watcher_thread = start_watcher(@rdoc.watch_files) + def start + @tcp_server = TCPServer.new('127.0.0.1', @port) + @running = true - url = "http://localhost:#{@port}" - $stderr.puts "\nServing documentation at: \e]8;;#{url}\e\\#{url}\e]8;;\e\\" - $stderr.puts "Press Ctrl+C to stop.\n\n" + @watcher_thread = start_watcher(@rdoc.watch_files) - loop do - client = @tcp_server.accept - Thread.new(client) { |c| handle_client(c) } + url = "http://localhost:#{@port}" + $stderr.puts "\nServing documentation at: \e]8;;#{url}\e\\#{url}\e]8;;\e\\" + $stderr.puts "Press Ctrl+C to stop.\n\n" + + loop do + client = @tcp_server.accept + Thread.new(client) { |c| handle_client(c) } + end + rescue Interrupt + # Ctrl+C + ensure + @running = false + @tcp_server&.close + @watcher_thread&.join(2) end - rescue Interrupt - # Ctrl+C - ensure - @running = false - @tcp_server&.close - @watcher_thread&.join(2) - end 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 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 - gen.asset_rel_path = '' - gen.setup - gen - end + def create_generator + gen = ::RDoc::Generator::Aliki.new(@store, @options) + gen.file_output = false + gen.asset_rel_path = '' + gen.setup + gen + end - ## - # Reads an HTTP request from +client+ and dispatches to the router. + ## + # Reads an HTTP request from +client+ and dispatches to the router. - def handle_client(client) - client.binmode + def handle_client(client) + client.binmode - return unless IO.select([client], nil, nil, 5) + return unless IO.select([client], nil, nil, 5) - request_line = client.gets("\n") - return unless request_line + request_line = client.gets("\n") + return unless request_line - method, request_uri, = request_line.split(' ', 3) - return write_response(client, 400, 'text/plain', 'Bad Request') unless request_uri + method, request_uri, = request_line.split(' ', 3) + return write_response(client, 400, 'text/plain', 'Bad Request') unless request_uri - begin - path = URI.parse(request_uri).path - rescue URI::InvalidURIError - return write_response(client, 400, 'text/plain', 'Bad Request') - end + begin + path = URI.parse(request_uri).path + rescue URI::InvalidURIError + return write_response(client, 400, 'text/plain', 'Bad Request') + end - while (line = client.gets("\n")) - break if line.strip.empty? - end + while (line = client.gets("\n")) + break if line.strip.empty? + end - unless method == 'GET' - return write_response(client, 405, 'text/plain', 'Method Not Allowed') - end + unless method == 'GET' + return write_response(client, 405, 'text/plain', 'Method Not Allowed') + end - if path.start_with?('/__') || %r{\A/(?:css|js)/}.match?(path) - status, content_type, body = route(path) - else - duration_ms = measure do + 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 - $stderr.puts "#{status} #{path} (#{duration_ms}ms)" - end - write_response(client, status, content_type, body) - rescue => e - write_response(client, 500, 'text/html', <<~HTML) + write_response(client, status, content_type, body) + rescue => e + write_response(client, 500, 'text/html', <<~HTML)

    Internal Server Error

    #{ERB::Util.html_escape e.message}\n#{ERB::Util.html_escape e.backtrace.join("\n")}
    HTML - ensure - client.close rescue nil - end + ensure + client.close rescue nil + end - ## - # Routes a request path and returns [status, content_type, body]. - - def route(path) - case path - when '/__status' - t = @mutex.synchronize { @last_change_time } - [200, 'application/json', JSON.generate(last_change: t)] - when '/js/search_data.js' - # Search data is dynamically generated, not a static asset - serve_page(path) - when %r{\A/(?:css|js)/} - serve_asset(path) - else - serve_page(path) + ## + # Routes a request path and returns [status, content_type, body]. + + def route(path) + case path + when '/__status' + t = @mutex.synchronize { @last_change_time } + [200, 'application/json', JSON.generate(last_change: t)] + when '/js/search_data.js' + # Search data is dynamically generated, not a static asset + serve_page(path) + when %r{\A/(?:css|js)/} + serve_asset(path) + else + serve_page(path) + end end - end - ## - # Writes an HTTP/1.1 response to +client+. - - def write_response(client, status, content_type, body) - body_bytes = body.b - - header = +"HTTP/1.1 #{status} #{STATUS_TEXTS[status] || 'Unknown'}\r\n" - header << "Content-Type: #{content_type}\r\n" - header << "Content-Length: #{body_bytes.bytesize}\r\n" - header << "Connection: close\r\n" - header << "\r\n" - - client.write(header) - client.write(body_bytes) - client.flush - rescue Errno::EPIPE - # Client disconnected before we finished writing — harmless. - end + ## + # Writes an HTTP/1.1 response to +client+. - ## - # Serves a static asset (CSS, JS) from the Aliki template directory. + def write_response(client, status, content_type, body) + body_bytes = body.b - def serve_asset(path) - rel_path = path.delete_prefix("/") - asset_path = File.join(@generator.template_dir, rel_path) - real_asset = File.expand_path(asset_path) + header = +"HTTP/1.1 #{status} #{STATUS_TEXTS[status] || 'Unknown'}\r\n" + header << "Content-Type: #{content_type}\r\n" + header << "Content-Length: #{body_bytes.bytesize}\r\n" + header << "Connection: close\r\n" + header << "\r\n" - unless real_asset.start_with?("#{@template_dir}/") && File.file?(real_asset) - return [404, 'text/plain', "Asset not found: #{rel_path}"] + client.write(header) + client.write(body_bytes) + client.flush + rescue Errno::EPIPE + # Client disconnected before we finished writing — harmless. end - ext = File.extname(rel_path) - content_type = CONTENT_TYPES[ext] || 'application/octet-stream' - [200, content_type, File.read(real_asset)] - end + ## + # Serves a static asset (CSS, JS) from the Aliki template directory. - ## - # Serves an HTML page, rendering from the generator or returning a cached - # version. + def serve_asset(path) + rel_path = path.delete_prefix("/") + asset_path = File.join(@generator.template_dir, rel_path) + real_asset = File.expand_path(asset_path) - def serve_page(path) - name = path.delete_prefix("/") - name = 'index.html' if name.empty? - - html = render_page(name) + unless real_asset.start_with?("#{@template_dir}/") && File.file?(real_asset) + return [404, 'text/plain', "Asset not found: #{rel_path}"] + end - unless html - not_found = @generator.generate_servlet_not_found( - "The page #{ERB::Util.html_escape path} was not found" - ) - t = @mutex.synchronize { @last_change_time } - return [404, 'text/html', inject_live_reload(not_found || '', t)] + ext = File.extname(rel_path) + content_type = CONTENT_TYPES[ext] || 'application/octet-stream' + [200, content_type, File.read(real_asset)] end - ext = File.extname(name) - content_type = CONTENT_TYPES[ext] || 'text/html' - [200, content_type, html] - end + ## + # Serves an HTML page, rendering from the generator or returning a cached + # version. - ## - # Renders a page through the Aliki generator and caches the result. + def serve_page(path) + name = path.delete_prefix("/") + name = 'index.html' if name.empty? - def render_page(name) - @mutex.synchronize do - return @page_cache[name] if @page_cache[name] + html = render_page(name) - result = generate_page(name) - return nil unless result + unless html + not_found = @generator.generate_servlet_not_found( + "The page #{ERB::Util.html_escape path} was not found" + ) + t = @mutex.synchronize { @last_change_time } + return [404, 'text/html', inject_live_reload(not_found || '', t)] + end - result = inject_live_reload(result, @last_change_time) if name.end_with?('.html') - @page_cache[name] = result + ext = File.extname(name) + content_type = CONTENT_TYPES[ext] || 'text/html' + [200, content_type, html] end - end - ## - # Dispatches to the appropriate generator method based on the page name. - - def generate_page(name) - case name - when 'index.html' - @generator.generate_index - when 'table_of_contents.html' - @generator.generate_table_of_contents - when 'js/search_data.js' - "var search_data = #{JSON.generate(index: @generator.build_search_index)};" - else - text_name = name.chomp('.html') - class_name = text_name.gsub('/', '::') - - if klass = @store.find_class_or_module(class_name) - @generator.generate_class(klass) - elsif page = @store.find_text_page(text_name.sub(/_([^_]*)\z/, '.\1')) - @generator.generate_page(page) + ## + # Renders a page through the Aliki generator and caches the result. + + def render_page(name) + @mutex.synchronize do + return @page_cache[name] if @page_cache[name] + + result = generate_page(name) + return nil unless result + + result = inject_live_reload(result, @last_change_time) if name.end_with?('.html') + @page_cache[name] = result end end - end - ## - # Injects the live-reload polling script before ++. + ## + # Dispatches to the appropriate generator method based on the page name. + + def generate_page(name) + case name + when 'index.html' + @generator.generate_index + when 'table_of_contents.html' + @generator.generate_table_of_contents + when 'js/search_data.js' + "var search_data = #{JSON.generate(index: @generator.build_search_index)};" + else + text_name = name.chomp('.html') + class_name = text_name.gsub('/', '::') + + if klass = @store.find_class_or_module(class_name) + @generator.generate_class(klass) + elsif page = @store.find_text_page(text_name.sub(/_([^_]*)\z/, '.\1')) + @generator.generate_page(page) + end + end + end - def inject_live_reload(html, last_change_time) - html.sub('', "#{self.class.live_reload_script(last_change_time)}") - end + ## + # Injects the live-reload polling script before ++. - ## - # Starts a background thread that polls source file mtimes and triggers - # re-parsing when changes are detected. - - def start_watcher(source_files) - @file_mtimes = file_mtimes_for(source_files) - - Thread.new do - while @running - begin - sleep 1 - check_for_changes - rescue => e - $stderr.puts "RDoc server watcher error: #{e.message}" + def inject_live_reload(html, last_change_time) + html.sub('', "#{self.class.live_reload_script(last_change_time)}") + end + + ## + # Starts a background thread that polls source file mtimes and triggers + # re-parsing when changes are detected. + + def start_watcher(source_files) + @file_mtimes = file_mtimes_for(source_files) + + Thread.new do + while @running + begin + sleep 1 + check_for_changes + rescue => e + $stderr.puts "RDoc server watcher error: #{e.message}" + end end end end - end - def file_mtimes_for(files) - files.each_with_object({}) do |f, h| - h[f] = RDoc.safe_mtime(f) + def file_mtimes_for(files) + files.each_with_object({}) do |f, h| + h[f] = ::RDoc.safe_mtime(f) + end end - end - ## - # Checks for modified, new, and deleted files. Returns true if any - # changes were found and processed. + ## + # Checks for modified, new, and deleted files. Returns true if any + # changes were found and processed. - def check_for_changes - changes = FileChanges.new @rdoc - current_files = current_watch_files - current_file_set = current_files.to_set + def check_for_changes + changes = FileChanges.new @rdoc + current_files = current_watch_files + current_file_set = current_files.to_set - @file_mtimes.each_key do |file| - changes.record_removed file unless current_file_set.include? file - end + @file_mtimes.each_key do |file| + changes.record_removed file unless current_file_set.include? file + end - current_files.each do |file| - next unless file_changed? file + current_files.each do |file| + next unless file_changed? file - @file_mtimes[file] = nil unless @file_mtimes.key? file - changes.record_changed file - end + @file_mtimes[file] = nil unless @file_mtimes.key? file + changes.record_changed file + end - return false unless changes.source_files_changed? + return false unless changes.source_files_changed? - reparse_and_refresh changes - true - end + reparse_and_refresh changes + true + end - ## - # Re-parses changed files, removes deleted files from the store, - # refreshes the generator, and invalidates caches. - - def reparse_and_refresh(changes) - @mutex.synchronize do - remove_files changes.removed_files - reparse_files changes.changed_files - reload_rbs_signatures if changes.reload_rbs_signatures? - @store.complete(@options.visibility) - @store.invalidate_type_name_lookup if changes.source_files_changed? - - @generator.refresh_store_data - @page_cache.clear - @last_change_time = Time.now.to_f + ## + # Re-parses changed files, removes deleted files from the store, + # refreshes the generator, and invalidates caches. + + def reparse_and_refresh(changes) + @mutex.synchronize do + remove_files changes.removed_files + reparse_files changes.changed_files + reload_rbs_signatures if changes.reload_rbs_signatures? + @store.complete(@options.visibility) + @store.invalidate_type_name_lookup if changes.source_files_changed? + + @generator.refresh_store_data + @page_cache.clear + @last_change_time = Time.now.to_f + end end - end - def current_watch_files - file_list = @rdoc.normalized_file_list( - @options.files.empty? ? [@options.root.to_s] : @options.files, - true, @options.exclude - ) - @rdoc.remove_duplicate_files(@rdoc.remove_unparseable(file_list)).keys | @rdoc.auto_discovered_rbs_signature_files - end + def current_watch_files + file_list = @rdoc.normalized_file_list( + @options.files.empty? ? [@options.root.to_s] : @options.files, + true, @options.exclude + ) + @rdoc.remove_duplicate_files(@rdoc.remove_unparseable(file_list)).keys | @rdoc.auto_discovered_rbs_signature_files + end - def file_changed?(file) - return true unless @file_mtimes.key? file + def file_changed?(file) + return true unless @file_mtimes.key? file - old_mtime = @file_mtimes[file] - return true unless old_mtime + old_mtime = @file_mtimes[file] + return true unless old_mtime - current_mtime = RDoc.safe_mtime(file) - current_mtime && current_mtime > old_mtime - end + current_mtime = ::RDoc.safe_mtime(file) + current_mtime && current_mtime > old_mtime + end - def remove_files(files) - return if files.empty? + def remove_files(files) + return if files.empty? - $stderr.puts "Removed: #{files.join(', ')}" - files.each do |f| - @file_mtimes.delete(f) - relative = @rdoc.relative_path_for(f) - @store.clear_file_contributions(relative) - @store.remove_file(relative) + $stderr.puts "Removed: #{files.join(', ')}" + files.each do |f| + @file_mtimes.delete(f) + relative = @rdoc.relative_path_for(f) + @store.clear_file_contributions(relative) + @store.remove_file(relative) + end end - end - def reload_rbs_signatures - duration_ms = measure do - @rdoc.load_auto_discovered_rbs_signatures - @rdoc.record_auto_discovered_rbs_signature_mtimes - @rdoc.auto_discovered_rbs_signature_files.each do |file| - @file_mtimes[file] = RDoc.safe_mtime(file) + def reload_rbs_signatures + duration_ms = measure do + @rdoc.load_auto_discovered_rbs_signatures + @rdoc.record_auto_discovered_rbs_signature_mtimes + @rdoc.auto_discovered_rbs_signature_files.each do |file| + @file_mtimes[file] = ::RDoc.safe_mtime(file) + end end + $stderr.puts "Reloaded RBS signatures (#{duration_ms}ms)" end - $stderr.puts "Reloaded RBS signatures (#{duration_ms}ms)" - end - def reparse_files(files) - return if files.empty? + def reparse_files(files) + return if files.empty? - changed_file_names = [] - duration_ms = measure do - files.each do |f| - relative = @rdoc.relative_path_for(f) - changed_file_names << relative - begin - @store.clear_file_contributions(relative, keep_position: true) - @rdoc.parse_file(f) - @file_mtimes[f] = RDoc.safe_mtime(f) - rescue => e - $stderr.puts "Error parsing #{f}: #{e.message}" + changed_file_names = [] + duration_ms = measure do + files.each do |f| + relative = @rdoc.relative_path_for(f) + changed_file_names << relative + begin + @store.clear_file_contributions(relative, keep_position: true) + @rdoc.parse_file(f) + @file_mtimes[f] = ::RDoc.safe_mtime(f) + 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 - $stderr.puts "Re-parsed #{changed_file_names.join(', ')} (#{duration_ms}ms)" - end + end end diff --git a/lib/rdoc/stats.rb b/lib/rdoc/stats.rb index ac057a4114..626c69c1fd 100644 --- a/lib/rdoc/stats.rb +++ b/lib/rdoc/stats.rb @@ -1,484 +1,486 @@ # frozen_string_literal: true -## -# RDoc statistics collector which prints a summary and report of a project's -# documentation totals. +module RDoc + ## + # RDoc statistics collector which prints a summary and report of a project's + # documentation totals. -class RDoc::Stats + class Stats - include RDoc::Text + include ::RDoc::Text - ## - # Display order for item types in the coverage report + ## + # Display order for item types in the coverage report - TYPE_ORDER = %w[Class Module Constant Attribute Method].freeze + TYPE_ORDER = %w[Class Module Constant Attribute Method].freeze - ## - # Message displayed when all items are documented + ## + # Message displayed when all items are documented - GREAT_JOB_MESSAGE = <<~MSG + GREAT_JOB_MESSAGE = <<~MSG 100% documentation! Great Job! MSG - ## - # Output level for the coverage report + ## + # Output level for the coverage report - attr_reader :coverage_level + attr_reader :coverage_level - ## - # Count of files parsed during parsing + ## + # Count of files parsed during parsing - attr_reader :files_so_far + attr_reader :files_so_far - ## - # Total number of files found + ## + # Total number of files found - attr_reader :num_files + attr_reader :num_files - ## - # Creates a new Stats that will have +num_files+. +verbosity+ defaults to 1 - # which will create an RDoc::Stats::Normal outputter. - - def initialize(store, num_files, verbosity = 1) - @num_files = num_files - @store = store - - @coverage_level = 0 - @doc_items = nil - @files_so_far = 0 - @fully_documented = false - @num_params = 0 - @percent_doc = nil - @start = Time.now - @undoc_params = 0 - - self.verbosity = verbosity - end + ## + # Creates a new Stats that will have +num_files+. +verbosity+ defaults to 1 + # which will create an RDoc::Stats::Normal outputter. - ## - # 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 - end - end + def initialize(store, num_files, verbosity = 1) + @num_files = num_files + @store = store - ## - # Records the parsing of an alias +as+. + @coverage_level = 0 + @doc_items = nil + @files_so_far = 0 + @fully_documented = false + @num_params = 0 + @percent_doc = nil + @start = Time.now + @undoc_params = 0 - def add_alias(as) - @display.print_alias as - end + self.verbosity = verbosity + end - ## - # Records the parsing of an attribute +attribute+ + ## + # Sets the verbosity level, rebuilding the display outputter. - def add_attribute(attribute) - @display.print_attribute attribute - end + 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 + end + end - ## - # Records the parsing of a class +klass+ + ## + # Records the parsing of an alias +as+. - def add_class(klass) - @display.print_class klass - end + def add_alias(as) + @display.print_alias as + end - ## - # Records the parsing of +constant+ + ## + # Records the parsing of an attribute +attribute+ - def add_constant(constant) - @display.print_constant constant - end + def add_attribute(attribute) + @display.print_attribute attribute + end - ## - # Records the parsing of +file+ + ## + # Records the parsing of a class +klass+ - def add_file(file) - @files_so_far += 1 - @display.print_file @files_so_far, file - end + def add_class(klass) + @display.print_class klass + end - ## - # Records the parsing of +method+ + ## + # Records the parsing of +constant+ - def add_method(method) - @display.print_method method - end + def add_constant(constant) + @display.print_constant constant + end - ## - # Records the parsing of a module +mod+ + ## + # Records the parsing of +file+ - def add_module(mod) - @display.print_module mod - end + def add_file(file) + @files_so_far += 1 + @display.print_file @files_so_far, file + end - ## - # Call this to mark the beginning of parsing for display purposes + ## + # Records the parsing of +method+ - def begin_adding - @display.begin_adding - end + def add_method(method) + @display.print_method method + end - ## - # Calculates documentation totals and percentages for classes, modules, - # constants, attributes and methods. + ## + # Records the parsing of a module +mod+ - def calculate - return if @doc_items + def add_module(mod) + @display.print_module mod + end - ucm = @store.unique_classes_and_modules + ## + # Call this to mark the beginning of parsing for display purposes - classes = @store.unique_classes.reject { |cm| cm.full_name == 'Object' } + def begin_adding + @display.begin_adding + end - constants = [] - ucm.each { |cm| constants.concat cm.constants } + ## + # Calculates documentation totals and percentages for classes, modules, + # constants, attributes and methods. - methods = [] - ucm.each { |cm| methods.concat cm.method_list } + def calculate + return if @doc_items - attributes = [] - ucm.each { |cm| attributes.concat cm.attributes } + ucm = @store.unique_classes_and_modules - @num_attributes, @undoc_attributes = doc_stats attributes - @num_classes, @undoc_classes = doc_stats classes - @num_constants, @undoc_constants = doc_stats constants - @num_methods, @undoc_methods = doc_stats methods - @num_modules, @undoc_modules = doc_stats @store.unique_modules + classes = @store.unique_classes.reject { |cm| cm.full_name == 'Object' } - @num_items = - @num_attributes + - @num_classes + - @num_constants + - @num_methods + - @num_modules + - @num_params + constants = [] + ucm.each { |cm| constants.concat cm.constants } - @undoc_items = - @undoc_attributes + - @undoc_classes + - @undoc_constants + - @undoc_methods + - @undoc_modules + - @undoc_params + methods = [] + ucm.each { |cm| methods.concat cm.method_list } - @doc_items = @num_items - @undoc_items - end + attributes = [] + ucm.each { |cm| attributes.concat cm.attributes } - ## - # Sets coverage report level. Accepted values are: - # - # false or nil:: No report - # 0:: Classes, modules, constants, attributes, methods - # 1:: Level 0 + method parameters + @num_attributes, @undoc_attributes = doc_stats attributes + @num_classes, @undoc_classes = doc_stats classes + @num_constants, @undoc_constants = doc_stats constants + @num_methods, @undoc_methods = doc_stats methods + @num_modules, @undoc_modules = doc_stats @store.unique_modules - def coverage_level=(level) - level = -1 unless level + @num_items = + @num_attributes + + @num_classes + + @num_constants + + @num_methods + + @num_modules + + @num_params - @coverage_level = level - end + @undoc_items = + @undoc_attributes + + @undoc_classes + + @undoc_constants + + @undoc_methods + + @undoc_modules + + @undoc_params - ## - # Returns the length and number of undocumented items in +collection+. + @doc_items = @num_items - @undoc_items + end - def doc_stats(collection) - visible = collection.select { |item| item.display? } - [visible.length, visible.count { |item| not item.documented? }] - end + ## + # Sets coverage report level. Accepted values are: + # + # false or nil:: No report + # 0:: Classes, modules, constants, attributes, methods + # 1:: Level 0 + method parameters - ## - # Call this to mark the end of parsing for display purposes + def coverage_level=(level) + level = -1 unless level - def done_adding - @display.done_adding - end + @coverage_level = level + end - ## - # The documentation status of this project. +true+ when 100%, +false+ when - # less than 100% and +nil+ when unknown. - # - # Set by calling #calculate + ## + # Returns the length and number of undocumented items in +collection+. - def fully_documented? - @fully_documented - end + def doc_stats(collection) + visible = collection.select { |item| item.display? } + [visible.length, visible.count { |item| not item.documented? }] + end - ## - # Calculates the percentage of items documented. + ## + # Call this to mark the end of parsing for display purposes - def percent_doc - return @percent_doc if @percent_doc + def done_adding + @display.done_adding + end - @fully_documented = (@num_items - @doc_items) == 0 + ## + # The documentation status of this project. +true+ when 100%, +false+ when + # less than 100% and +nil+ when unknown. + # + # Set by calling #calculate - @percent_doc = @doc_items.to_f / @num_items * 100 if @num_items.nonzero? - @percent_doc ||= 0 + def fully_documented? + @fully_documented + end - @percent_doc - end + ## + # Calculates the percentage of items documented. - ## - # Returns a report on which items are not documented + def percent_doc + return @percent_doc if @percent_doc - def report - if @coverage_level > 0 - extend RDoc::Text - end + @fully_documented = (@num_items - @doc_items) == 0 - if @coverage_level.zero? - calculate + @percent_doc = @doc_items.to_f / @num_items * 100 if @num_items.nonzero? + @percent_doc ||= 0 - return GREAT_JOB_MESSAGE if @num_items == @doc_items + @percent_doc end - items, empty_classes = collect_undocumented_items + ## + # Returns a report on which items are not documented - if @coverage_level > 0 - calculate + def report + if @coverage_level > 0 + extend ::RDoc::Text + end - return GREAT_JOB_MESSAGE if @num_items == @doc_items - end + if @coverage_level.zero? + calculate - report = +"" - report << "The following items are not documented:\n\n" + return GREAT_JOB_MESSAGE if @num_items == @doc_items + end - # Referenced-but-empty classes - empty_classes.each do |cm| - report << "#{cm.full_name} is referenced but empty.\n" - report << "It probably came from another project. I'm sorry I'm holding it against you.\n\n" - end + items, empty_classes = collect_undocumented_items - # Group items by file, then by type - by_file = items.group_by { |item| item[:file] } + if @coverage_level > 0 + calculate - by_file.sort_by { |file, _| file }.each do |file, file_items| - report << "#{file}:\n" + return GREAT_JOB_MESSAGE if @num_items == @doc_items + end - by_type = file_items.group_by { |item| item[:type] } + report = +"" + report << "The following items are not documented:\n\n" - TYPE_ORDER.each do |type| - next unless by_type[type] + # Referenced-but-empty classes + empty_classes.each do |cm| + report << "#{cm.full_name} is referenced but empty.\n" + report << "It probably came from another project. I'm sorry I'm holding it against you.\n\n" + end - report << " #{type}:\n" + # Group items by file, then by type + by_file = items.group_by { |item| item[:file] } - sorted = by_type[type].sort_by { |item| [item[:line] || 0, item[:name]] } - name_width = sorted.reduce(0) { |max, item| item[:line] && item[:name].length > max ? item[:name].length : max } + by_file.sort_by { |file, _| file }.each do |file, file_items| + report << "#{file}:\n" - sorted.each do |item| - if item[:line] - report << " %-*s %s:%d\n" % [name_width, item[:name], item[:file], item[:line]] - else - report << " #{item[:name]}\n" - end + by_type = file_items.group_by { |item| item[:type] } - if item[:undoc_params] - report << " Undocumented params: #{item[:undoc_params].join(', ')}\n" + TYPE_ORDER.each do |type| + next unless by_type[type] + + report << " #{type}:\n" + + sorted = by_type[type].sort_by { |item| [item[:line] || 0, item[:name]] } + name_width = sorted.reduce(0) { |max, item| item[:line] && item[:name].length > max ? item[:name].length : max } + + sorted.each do |item| + if item[:line] + report << " %-*s %s:%d\n" % [name_width, item[:name], item[:file], item[:line]] + else + report << " #{item[:name]}\n" + end + + if item[:undoc_params] + report << " Undocumented params: #{item[:undoc_params].join(', ')}\n" + end end end + + report << "\n" end - report << "\n" + report end - report - end + ## + # Collects all undocumented items across all classes and modules. + # Returns [items, empty_classes] where items is an Array of Hashes + # with keys :type, :name, :file, :line, and empty_classes is an + # Array of ClassModule objects that are referenced but have no files. - ## - # Collects all undocumented items across all classes and modules. - # Returns [items, empty_classes] where items is an Array of Hashes - # with keys :type, :name, :file, :line, and empty_classes is an - # Array of ClassModule objects that are referenced but have no files. + def collect_undocumented_items + empty_classes = [] + items = [] - def collect_undocumented_items - empty_classes = [] - items = [] + @store.unique_classes_and_modules.each do |class_module| + next unless class_module.display? - @store.unique_classes_and_modules.each do |class_module| - next unless class_module.display? + if class_module.in_files.empty? + empty_classes << class_module + next + end - if class_module.in_files.empty? - empty_classes << class_module - next - end + unless class_module.documented? || class_module.full_name == 'Object' + collect_undocumented_class_module(class_module, items) + end - unless class_module.documented? || class_module.full_name == 'Object' - collect_undocumented_class_module(class_module, items) + collect_undocumented_constants(class_module, items) + collect_undocumented_attributes(class_module, items) + collect_undocumented_methods(class_module, items) end - collect_undocumented_constants(class_module, items) - collect_undocumented_attributes(class_module, items) - collect_undocumented_methods(class_module, items) + [items, empty_classes] end - [items, empty_classes] - end - - ## - # Collects undocumented classes or modules from +class_module+ into +items+. - # Reopened classes/modules are reported in every file they appear in. - - def collect_undocumented_class_module(class_module, items) - class_module.in_files.map(&:full_name).uniq.each do |file| - items << { - type: class_module.type.capitalize, - name: class_module.full_name, - file: file, - line: nil, - } + ## + # Collects undocumented classes or modules from +class_module+ into +items+. + # Reopened classes/modules are reported in every file they appear in. + + def collect_undocumented_class_module(class_module, items) + class_module.in_files.map(&:full_name).uniq.each do |file| + items << { + type: class_module.type.capitalize, + name: class_module.full_name, + file: file, + line: nil, + } + end end - end - ## - # Collects undocumented constants from +class_module+ into +items+. - - def collect_undocumented_constants(class_module, items) - class_module.constants.each do |constant| - next unless constant.display? - next if constant.documented? || constant.is_alias_for - - file = constant.file&.full_name - next unless file - - items << { - type: "Constant", - name: constant.full_name, - file: file, - line: constant.line, - } + ## + # Collects undocumented constants from +class_module+ into +items+. + + def collect_undocumented_constants(class_module, items) + class_module.constants.each do |constant| + next unless constant.display? + next if constant.documented? || constant.is_alias_for + + file = constant.file&.full_name + next unless file + + items << { + type: "Constant", + name: constant.full_name, + file: file, + line: constant.line, + } + end end - end - ## - # Collects undocumented attributes from +class_module+ into +items+. - - def collect_undocumented_attributes(class_module, items) - class_module.attributes.each do |attr| - next unless attr.display? - next if attr.documented? - - file = attr.file&.full_name - next unless file - - scope = attr.singleton ? "." : "#" - items << { - type: "Attribute", - name: "#{class_module.full_name}#{scope}#{attr.name}", - file: file, - line: attr.line, - } + ## + # Collects undocumented attributes from +class_module+ into +items+. + + def collect_undocumented_attributes(class_module, items) + class_module.attributes.each do |attr| + next unless attr.display? + next if attr.documented? + + file = attr.file&.full_name + next unless file + + scope = attr.singleton ? "." : "#" + items << { + type: "Attribute", + name: "#{class_module.full_name}#{scope}#{attr.name}", + file: file, + line: attr.line, + } + end end - end - ## - # Collects undocumented methods from +class_module+ into +items+. - # At coverage level > 0, also counts undocumented parameters. + ## + # Collects undocumented methods from +class_module+ into +items+. + # At coverage level > 0, also counts undocumented parameters. - def collect_undocumented_methods(class_module, items) - class_module.each_method do |method| - next unless method.display? - next if method.documented? && @coverage_level.zero? + def collect_undocumented_methods(class_module, items) + class_module.each_method do |method| + next unless method.display? + next if method.documented? && @coverage_level.zero? - undoc_param_names = nil + undoc_param_names = nil - if @coverage_level > 0 - params, undoc = undoc_params method - @num_params += params + if @coverage_level > 0 + params, undoc = undoc_params method + @num_params += params - unless undoc.empty? - @undoc_params += undoc.length - undoc_param_names = undoc + unless undoc.empty? + @undoc_params += undoc.length + undoc_param_names = undoc + end end - end - next if method.documented? && !undoc_param_names + next if method.documented? && !undoc_param_names - file = method.file&.full_name - next unless file + file = method.file&.full_name + next unless file - scope = method.singleton ? "." : "#" - item = { - type: "Method", - name: "#{class_module.full_name}#{scope}#{method.name}", - file: file, - line: method.line, - } - item[:undoc_params] = undoc_param_names if undoc_param_names + scope = method.singleton ? "." : "#" + item = { + type: "Method", + name: "#{class_module.full_name}#{scope}#{method.name}", + file: file, + line: method.line, + } + item[:undoc_params] = undoc_param_names if undoc_param_names - items << item + items << item + end end - end - ## - # Returns a summary of the collected statistics. - - def summary - calculate - - num_width = [@num_files, @num_items].max.to_s.length - undoc_width = [ - @undoc_attributes, - @undoc_classes, - @undoc_constants, - @undoc_items, - @undoc_methods, - @undoc_modules, - @undoc_params, - ].max.to_s.length - - report = +"" - - report << "Files: %*d\n" % [num_width, @num_files] - report << "\n" - report << "Classes: %*d (%*d undocumented)\n" % [ - num_width, @num_classes, undoc_width, @undoc_classes] - report << "Modules: %*d (%*d undocumented)\n" % [ - num_width, @num_modules, undoc_width, @undoc_modules] - report << "Constants: %*d (%*d undocumented)\n" % [ - num_width, @num_constants, undoc_width, @undoc_constants] - report << "Attributes: %*d (%*d undocumented)\n" % [ - num_width, @num_attributes, undoc_width, @undoc_attributes] - report << "Methods: %*d (%*d undocumented)\n" % [ - num_width, @num_methods, undoc_width, @undoc_methods] - report << "Parameters: %*d (%*d undocumented)\n" % [ - num_width, @num_params, undoc_width, @undoc_params] if - @coverage_level > 0 - report << "\n" - report << "Total: %*d (%*d undocumented)\n" % [ - num_width, @num_items, undoc_width, @undoc_items] - report << "%6.2f%% documented\n" % percent_doc - report << "\n" - report << "Elapsed: %0.1fs\n" % (Time.now - @start) - - report - end + ## + # Returns a summary of the collected statistics. - ## - # Determines which parameters in +method+ were not documented. Returns a - # total parameter count and an Array of undocumented methods. + def summary + calculate - def undoc_params(method) - @formatter ||= RDoc::Markup::ToTtOnly.new + num_width = [@num_files, @num_items].max.to_s.length + undoc_width = [ + @undoc_attributes, + @undoc_classes, + @undoc_constants, + @undoc_items, + @undoc_methods, + @undoc_modules, + @undoc_params, + ].max.to_s.length - params = method.param_list + report = +"" - params = params.map { |param| param.gsub(/^\*\*?/, '') } + report << "Files: %*d\n" % [num_width, @num_files] + report << "\n" + report << "Classes: %*d (%*d undocumented)\n" % [ + num_width, @num_classes, undoc_width, @undoc_classes] + report << "Modules: %*d (%*d undocumented)\n" % [ + num_width, @num_modules, undoc_width, @undoc_modules] + report << "Constants: %*d (%*d undocumented)\n" % [ + num_width, @num_constants, undoc_width, @undoc_constants] + report << "Attributes: %*d (%*d undocumented)\n" % [ + num_width, @num_attributes, undoc_width, @undoc_attributes] + report << "Methods: %*d (%*d undocumented)\n" % [ + num_width, @num_methods, undoc_width, @undoc_methods] + report << "Parameters: %*d (%*d undocumented)\n" % [ + num_width, @num_params, undoc_width, @undoc_params] if + @coverage_level > 0 + report << "\n" + report << "Total: %*d (%*d undocumented)\n" % [ + num_width, @num_items, undoc_width, @undoc_items] + report << "%6.2f%% documented\n" % percent_doc + report << "\n" + report << "Elapsed: %0.1fs\n" % (Time.now - @start) - return 0, [] if params.empty? + report + end - document = parse method.comment + ## + # Determines which parameters in +method+ were not documented. Returns a + # total parameter count and an Array of undocumented methods. - tts = document.accept @formatter + def undoc_params(method) + @formatter ||= ::RDoc::Markup::ToTtOnly.new - undoc = params - tts + params = method.param_list - [params.length, undoc] - end + params = params.map { |param| param.gsub(/^\*\*?/, '') } - autoload :Quiet, "#{__dir__}/stats/quiet" - autoload :Normal, "#{__dir__}/stats/normal" - autoload :Verbose, "#{__dir__}/stats/verbose" + return 0, [] if params.empty? + document = parse method.comment + + tts = document.accept @formatter + + undoc = params - tts + + [params.length, undoc] + end + + autoload :Quiet, "#{__dir__}/stats/quiet" + autoload :Normal, "#{__dir__}/stats/normal" + autoload :Verbose, "#{__dir__}/stats/verbose" + + end end diff --git a/lib/rdoc/stats/normal.rb b/lib/rdoc/stats/normal.rb index e0e754804b..aa4b3f7b99 100644 --- a/lib/rdoc/stats/normal.rb +++ b/lib/rdoc/stats/normal.rb @@ -6,53 +6,57 @@ require 'io/console' end -## -# Stats printer that prints just the files being documented with a progress -# bar +module RDoc + class Stats + ## + # Stats printer that prints just the files being documented with a progress + # bar -class RDoc::Stats::Normal < RDoc::Stats::Quiet + class Normal < ::RDoc::Stats::Quiet - def begin_adding # :nodoc: - puts "Parsing sources..." - @last_width = 0 - end + def begin_adding # :nodoc: + puts "Parsing sources..." + @last_width = 0 + end - ## - # Prints a file with a progress bar - - def print_file(files_so_far, filename) - progress_bar = sprintf("%3d%% [%2d/%2d] ", - 100 * files_so_far / @num_files, - files_so_far, - @num_files) - - if $stdout.tty? - # Print a progress bar, but make sure it fits on a single line. Filename - # will be truncated if necessary. - size = IO.respond_to?(:console_size) ? IO.console_size : IO.console.winsize - terminal_width = size[1].to_i.nonzero? || 80 - max_filename_size = (terminal_width - progress_bar.size) - 1 - - if filename.size > max_filename_size - # Turn "some_long_filename.rb" to "...ong_filename.rb" - filename = filename[(filename.size - max_filename_size) .. -1] - filename[0..2] = "..." + ## + # Prints a file with a progress bar + + def print_file(files_so_far, filename) + progress_bar = sprintf("%3d%% [%2d/%2d] ", + 100 * files_so_far / @num_files, + files_so_far, + @num_files) + + if $stdout.tty? + # Print a progress bar, but make sure it fits on a single line. Filename + # will be truncated if necessary. + size = IO.respond_to?(:console_size) ? IO.console_size : IO.console.winsize + terminal_width = size[1].to_i.nonzero? || 80 + max_filename_size = (terminal_width - progress_bar.size) - 1 + + if filename.size > max_filename_size + # Turn "some_long_filename.rb" to "...ong_filename.rb" + filename = filename[(filename.size - max_filename_size) .. -1] + filename[0..2] = "..." + end + + # Clean the line with whitespaces so that leftover output from the + # previous line doesn't show up. + $stdout.print("\r\e[K") if @last_width && @last_width > 0 + @last_width = progress_bar.size + filename.size + term = "\r" + else + term = "\n" + end + $stdout.print(progress_bar, filename, term) + $stdout.flush end - # Clean the line with whitespaces so that leftover output from the - # previous line doesn't show up. - $stdout.print("\r\e[K") if @last_width && @last_width > 0 - @last_width = progress_bar.size + filename.size - term = "\r" - else - term = "\n" - end - $stdout.print(progress_bar, filename, term) - $stdout.flush - end + def done_adding # :nodoc: + puts + end - def done_adding # :nodoc: - puts + end end - end diff --git a/lib/rdoc/stats/quiet.rb b/lib/rdoc/stats/quiet.rb index 72fdcb23fc..569314c53b 100644 --- a/lib/rdoc/stats/quiet.rb +++ b/lib/rdoc/stats/quiet.rb @@ -1,59 +1,63 @@ # frozen_string_literal: true -## -# Stats printer that prints nothing +module RDoc + class Stats + ## + # Stats printer that prints nothing -class RDoc::Stats::Quiet + class Quiet - ## - # Creates a new Quiet that will print nothing + ## + # Creates a new Quiet that will print nothing - def initialize(num_files) - @num_files = num_files - end + def initialize(num_files) + @num_files = num_files + end - ## - # Prints a message at the beginning of parsing + ## + # Prints a message at the beginning of parsing - def begin_adding(*) end + def begin_adding(*) end - ## - # Prints when an alias is added + ## + # Prints when an alias is added - def print_alias(*) end + def print_alias(*) end - ## - # Prints when an attribute is added + ## + # Prints when an attribute is added - def print_attribute(*) end + def print_attribute(*) end - ## - # Prints when a class is added + ## + # Prints when a class is added - def print_class(*) end + def print_class(*) end - ## - # Prints when a constant is added + ## + # Prints when a constant is added - def print_constant(*) end + def print_constant(*) end - ## - # Prints when a file is added + ## + # Prints when a file is added - def print_file(*) end + def print_file(*) end - ## - # Prints when a method is added + ## + # Prints when a method is added - def print_method(*) end + def print_method(*) end - ## - # Prints when a module is added + ## + # Prints when a module is added - def print_module(*) end + def print_module(*) end - ## - # Prints when RDoc is done + ## + # Prints when RDoc is done - def done_adding(*) end + def done_adding(*) end + end + end end diff --git a/lib/rdoc/stats/verbose.rb b/lib/rdoc/stats/verbose.rb index 4e2c239615..51cc335fab 100644 --- a/lib/rdoc/stats/verbose.rb +++ b/lib/rdoc/stats/verbose.rb @@ -1,44 +1,48 @@ # frozen_string_literal: true -## -# Stats printer that prints everything documented, including the documented -# status +module RDoc + class Stats + ## + # Stats printer that prints everything documented, including the documented + # status -class RDoc::Stats::Verbose < RDoc::Stats::Normal + class Verbose < ::RDoc::Stats::Normal - ## - # Returns a marker for RDoc::CodeObject +co+ being undocumented + ## + # Returns a marker for RDoc::CodeObject +co+ being undocumented - def nodoc(co) - " (undocumented)" unless co.documented? - end + def nodoc(co) + " (undocumented)" unless co.documented? + end - def print_alias(as) # :nodoc: - puts " alias #{as.new_name} #{as.old_name}#{nodoc as}" - end + def print_alias(as) # :nodoc: + puts " alias #{as.new_name} #{as.old_name}#{nodoc as}" + end - def print_attribute(attribute) # :nodoc: - puts " #{attribute.definition} #{attribute.name}#{nodoc attribute}" - end + def print_attribute(attribute) # :nodoc: + puts " #{attribute.definition} #{attribute.name}#{nodoc attribute}" + end - def print_class(klass) # :nodoc: - puts " class #{klass.full_name}#{nodoc klass}" - end + def print_class(klass) # :nodoc: + puts " class #{klass.full_name}#{nodoc klass}" + end - def print_constant(constant) # :nodoc: - puts " #{constant.name}#{nodoc constant}" - end + def print_constant(constant) # :nodoc: + puts " #{constant.name}#{nodoc constant}" + end - def print_file(files_so_far, file) # :nodoc: - super - puts - end + def print_file(files_so_far, file) # :nodoc: + super + puts + end - def print_method(method) # :nodoc: - puts " #{method.singleton ? '::' : '#'}#{method.name}#{nodoc method}" - end + def print_method(method) # :nodoc: + puts " #{method.singleton ? '::' : '#'}#{method.name}#{nodoc method}" + end - def print_module(mod) # :nodoc: - puts " module #{mod.full_name}#{nodoc mod}" - end + def print_module(mod) # :nodoc: + puts " module #{mod.full_name}#{nodoc mod}" + end + end + end end diff --git a/lib/rdoc/store.rb b/lib/rdoc/store.rb index e4e7df09b9..b43670ee50 100644 --- a/lib/rdoc/store.rb +++ b/lib/rdoc/store.rb @@ -1,263 +1,297 @@ # frozen_string_literal: true require 'fileutils' -## -# A set of rdoc data for a single project (gem, path, etc.). -# -# The store manages reading and writing ri data for a project and maintains a -# cache of methods, classes and ancestors in the store. -# -# The store maintains a #cache of its contents for faster lookup. After -# adding items to the store it must be flushed using #save_cache. The cache -# contains the following structures: -# -# @cache = { -# :ancestors => {}, # class name => ancestor names -# :attributes => {}, # class name => attributes -# :class_methods => {}, # class name => class methods -# :instance_methods => {}, # class name => instance methods -# :modules => [], # classes and modules in this store -# :pages => [], # page names -# } -#-- -# TODO need to prune classes - -class RDoc::Store - +module RDoc ## - # Errors raised from loading or saving the store + # A set of rdoc data for a single project (gem, path, etc.). + # + # The store manages reading and writing ri data for a project and maintains a + # cache of methods, classes and ancestors in the store. + # + # The store maintains a #cache of its contents for faster lookup. After + # adding items to the store it must be flushed using #save_cache. The cache + # contains the following structures: + # + # @cache = { + # :ancestors => {}, # class name => ancestor names + # :attributes => {}, # class name => attributes + # :class_methods => {}, # class name => class methods + # :instance_methods => {}, # class name => instance methods + # :modules => [], # classes and modules in this store + # :pages => [], # page names + # } + #-- + # TODO need to prune classes - class Error < RDoc::Error - end + class Store - ## - # Raised when a stored file for a class, module, page or method is missing. + ## + # Errors raised from loading or saving the store - class MissingFileError < Error + class Error < ::RDoc::Error + end ## - # The store the file should exist in + # Raised when a stored file for a class, module, page or method is missing. - attr_reader :store + class MissingFileError < Error - ## - # The file the #name should be saved as + ## + # The store the file should exist in - attr_reader :file + attr_reader :store - ## - # The name of the object the #file would be loaded from + ## + # The file the #name should be saved as - attr_reader :name + attr_reader :file - ## - # Creates a new MissingFileError for the missing +file+ for the given - # +name+ that should have been in the +store+. + ## + # The name of the object the #file would be loaded from - def initialize(store, file, name) - @store = store - @file = file - @name = name - end + attr_reader :name + + ## + # Creates a new MissingFileError for the missing +file+ for the given + # +name+ that should have been in the +store+. + + def initialize(store, file, name) + @store = store + @file = file + @name = name + end + + def message # :nodoc: + "store at #{@store.path} missing file #{@file} for #{@name}" + end - def message # :nodoc: - "store at #{@store.path} missing file #{@file} for #{@name}" end - end + ## + # Stores the name of the C variable a class belongs to. This helps wire up + # classes defined from C across files. - ## - # Stores the name of the C variable a class belongs to. This helps wire up - # classes defined from C across files. + attr_reader :c_enclosure_classes # :nodoc: - attr_reader :c_enclosure_classes # :nodoc: + attr_reader :c_enclosure_names # :nodoc: - attr_reader :c_enclosure_names # :nodoc: + ## + # Maps C variables to class or module names for each parsed C file. - ## - # Maps C variables to class or module names for each parsed C file. + attr_reader :c_class_variables - attr_reader :c_class_variables + ## + # Maps C variables to singleton class names for each parsed C file. - ## - # Maps C variables to singleton class names for each parsed C file. + attr_reader :c_singleton_class_variables - attr_reader :c_singleton_class_variables + ## + # If true this Store will not write any files - ## - # If true this Store will not write any files + attr_accessor :dry_run - attr_accessor :dry_run + ## + # Path this store reads or writes - ## - # Path this store reads or writes + attr_accessor :path - attr_accessor :path + attr_reader :options - attr_reader :options + ## + # Type of ri datastore this was loaded from. See RDoc::RI::Driver, + # RDoc::RI::Paths. - ## - # Type of ri datastore this was loaded from. See RDoc::RI::Driver, - # RDoc::RI::Paths. + attr_accessor :type - attr_accessor :type + ## + # The contents of the Store - ## - # The contents of the Store + attr_reader :cache - attr_reader :cache + ## + # The encoding of the contents in the Store - ## - # The encoding of the contents in the Store + attr_accessor :encoding - attr_accessor :encoding + ## + # Creates a new Store of +type+ that will load or save to +path+ + + def initialize(options, path: nil, type: nil) + @options = options + @dry_run = options.dry_run + @encoding = options.encoding + @path = path || options.op_dir + @type = type + + @cache = { + :ancestors => {}, + :attributes => {}, + :class_methods => {}, + :c_class_variables => {}, + :c_singleton_class_variables => {}, + :encoding => @encoding, + :instance_methods => {}, + :main => options.main_page, + :modules => [], + :pages => [], + :title => options.title, + } - ## - # Creates a new Store of +type+ that will load or save to +path+ - - def initialize(options, path: nil, type: nil) - @options = options - @dry_run = options.dry_run - @encoding = options.encoding - @path = path || options.op_dir - @type = type - - @cache = { - :ancestors => {}, - :attributes => {}, - :class_methods => {}, - :c_class_variables => {}, - :c_singleton_class_variables => {}, - :encoding => @encoding, - :instance_methods => {}, - :main => options.main_page, - :modules => [], - :pages => [], - :title => options.title, - } - - @classes_hash = {} - @modules_hash = {} - @files_hash = {} - @text_files_hash = {} - - @c_enclosure_classes = {} - @c_enclosure_names = {} - - @c_class_variables = {} - @c_singleton_class_variables = {} - - @unique_classes = nil - @unique_modules = nil - end + @classes_hash = {} + @modules_hash = {} + @files_hash = {} + @text_files_hash = {} - ## - # Adds +module+ as an enclosure (namespace) for the given +variable+ for C - # files. + @c_enclosure_classes = {} + @c_enclosure_names = {} - def add_c_enclosure(variable, namespace) - @c_enclosure_classes[variable] = namespace - end + @c_class_variables = {} + @c_singleton_class_variables = {} - ## - # Adds C variables from an RDoc::Parser::C + @unique_classes = nil + @unique_modules = nil + end + + ## + # Adds +module+ as an enclosure (namespace) for the given +variable+ for C + # files. - def add_c_variables(c_parser) - filename = c_parser.top_level.relative_name + def add_c_enclosure(variable, namespace) + @c_enclosure_classes[variable] = namespace + end - @c_class_variables[filename] = make_variable_map c_parser.classes + ## + # Adds C variables from an RDoc::Parser::C - @c_singleton_class_variables[filename] = c_parser.singleton_classes - end + def add_c_variables(c_parser) + filename = c_parser.top_level.relative_name - ## - # Adds the file with +name+ as an RDoc::TopLevel to the store. Returns the - # created RDoc::TopLevel. + @c_class_variables[filename] = make_variable_map c_parser.classes - def add_file(absolute_name, relative_name: absolute_name, parser: nil) - unless top_level = @files_hash[relative_name] - top_level = RDoc::TopLevel.new absolute_name, relative_name - top_level.parser = parser if parser - top_level.store = self - @files_hash[relative_name] = top_level - @text_files_hash[relative_name] = top_level if top_level.text? + @c_singleton_class_variables[filename] = c_parser.singleton_classes end - top_level - end + ## + # Adds the file with +name+ as an RDoc::TopLevel to the store. Returns the + # created RDoc::TopLevel. + + def add_file(absolute_name, relative_name: absolute_name, parser: nil) + unless top_level = @files_hash[relative_name] + top_level = ::RDoc::TopLevel.new absolute_name, relative_name + top_level.parser = parser if parser + top_level.store = self + @files_hash[relative_name] = top_level + @text_files_hash[relative_name] = top_level if top_level.text? + end - ## - # Removes a file and its classes/modules from the store. Used by the - # live-reloading server when a source file is deleted. - # - # Note: this does not handle reopened classes correctly. If a class is - # defined across multiple files (e.g. +Foo+ in both +a.rb+ and +b.rb+), - # deleting one file removes the entire class from the store — including - # the parts contributed by the other file. Saving the remaining file - # triggers a re-parse that restores it. - - def remove_file(relative_name) - top_level = @files_hash.delete(relative_name) - @text_files_hash.delete(relative_name) - @c_class_variables.delete(relative_name) - @c_singleton_class_variables.delete(relative_name) - return unless top_level - - remove_classes_and_modules(top_level.classes_or_modules) - end + top_level + end - ## - # Removes a file's contributions (methods, constants, comments, etc.) - # from its classes and modules. If no other files contribute to a - # class or module, it is removed from the store entirely. This - # prevents duplication when the file is re-parsed while preserving - # shared namespaces like +RDoc+ that span many files. - - def clear_file_contributions(relative_name, keep_position: false) - top_level = @files_hash[relative_name] - return unless top_level - - top_level.classes_or_modules.each do |cm| - # Remove methods and attributes contributed by this file - cm.method_list.reject! { |m| m.file == top_level } - cm.attributes.reject! { |a| a.file == top_level } - - # Rebuild methods_hash from remaining methods and attributes - cm.methods_hash.clear - cm.method_list.each { |m| cm.methods_hash[m.pretty_name] = m } - cm.attributes.each { |a| cm.methods_hash[a.pretty_name] = a } - - # Remove constants contributed by this file - cm.constants.reject! { |c| c.file == top_level } - cm.constants_hash.clear - cm.constants.each { |c| cm.constants_hash[c.name] = c } - - # Remove includes, extends, and aliases from this file - cm.includes.reject! { |i| i.file == top_level } - cm.extends.reject! { |e| e.file == top_level } - cm.aliases.reject! { |a| a.file == top_level } - cm.external_aliases.reject! { |a| a.file == top_level } - - # Clear or remove comment entries from this file - if cm.is_a?(RDoc::ClassModule) - if keep_position - cm.comment_location[top_level] = [] if cm.comment_location.key?(top_level) - else - cm.comment_location.delete(top_level) + ## + # Removes a file and its classes/modules from the store. Used by the + # live-reloading server when a source file is deleted. + # + # Note: this does not handle reopened classes correctly. If a class is + # defined across multiple files (e.g. +Foo+ in both +a.rb+ and +b.rb+), + # deleting one file removes the entire class from the store — including + # the parts contributed by the other file. Saving the remaining file + # triggers a re-parse that restores it. + + def remove_file(relative_name) + top_level = @files_hash.delete(relative_name) + @text_files_hash.delete(relative_name) + @c_class_variables.delete(relative_name) + @c_singleton_class_variables.delete(relative_name) + return unless top_level + + remove_classes_and_modules(top_level.classes_or_modules) + end + + ## + # Removes a file's contributions (methods, constants, comments, etc.) + # from its classes and modules. If no other files contribute to a + # class or module, it is removed from the store entirely. This + # prevents duplication when the file is re-parsed while preserving + # shared namespaces like +RDoc+ that span many files. + + def clear_file_contributions(relative_name, keep_position: false) + top_level = @files_hash[relative_name] + return unless top_level + + top_level.classes_or_modules.each do |cm| + # Remove methods and attributes contributed by this file + cm.method_list.reject! { |m| m.file == top_level } + cm.attributes.reject! { |a| a.file == top_level } + + # Rebuild methods_hash from remaining methods and attributes + cm.methods_hash.clear + cm.method_list.each { |m| cm.methods_hash[m.pretty_name] = m } + cm.attributes.each { |a| cm.methods_hash[a.pretty_name] = a } + + # Remove constants contributed by this file + cm.constants.reject! { |c| c.file == top_level } + cm.constants_hash.clear + cm.constants.each { |c| cm.constants_hash[c.name] = c } + + # Remove includes, extends, and aliases from this file + cm.includes.reject! { |i| i.file == top_level } + cm.extends.reject! { |e| e.file == top_level } + cm.aliases.reject! { |a| a.file == top_level } + cm.external_aliases.reject! { |a| a.file == top_level } + + # Clear or remove comment entries from this file + if cm.is_a?(::RDoc::ClassModule) + if keep_position + cm.comment_location[top_level] = [] if cm.comment_location.key?(top_level) + else + cm.comment_location.delete(top_level) + end + cm.rebuild_comment_from_location + end + + unless keep_position + # Remove this file from the class/module's file list + cm.in_files.delete(top_level) + + # If no files contribute to this class/module anymore, remove it + # from the store entirely. This handles file deletion correctly + # for classes that are only defined in the deleted file, while + # preserving classes that span multiple files. + if cm.in_files.empty? + if cm.is_a?(::RDoc::NormalModule) + @modules_hash.delete(cm.full_name) + else + @classes_hash.delete(cm.full_name) + end + cm.parent&.classes_hash&.delete(cm.name) + cm.parent&.modules_hash&.delete(cm.name) + end end - cm.rebuild_comment_from_location end - unless keep_position - # Remove this file from the class/module's file list - cm.in_files.delete(top_level) + # Clear the TopLevel's class/module list to prevent duplicates + top_level.classes_or_modules.clear + end + + ## + # Removes stale empty placeholders left by +clear_file_contributions+ with + # keep_position: true. After re-parsing, a file may no longer + # define a class it previously contributed to, leaving an empty entry in + # +comment_location+ and a stale +in_files+ reference. Call this after + # all re-parsing is complete. + + def cleanup_stale_contributions + all_classes_and_modules.each do |cm| + cm.comment_location.delete_if { |_, comments| comments.empty? } + cm.rebuild_comment_from_location + + cm.in_files.select! { |tl| cm.comment_location.key?(tl) || + cm.method_list.any? { |m| m.file == tl } || + cm.attributes.any? { |a| a.file == tl } || + cm.constants.any? { |c| c.file == tl } } - # If no files contribute to this class/module anymore, remove it - # from the store entirely. This handles file deletion correctly - # for classes that are only defined in the deleted file, while - # preserving classes that span multiple files. if cm.in_files.empty? - if cm.is_a?(RDoc::NormalModule) + if cm.is_a?(::RDoc::NormalModule) @modules_hash.delete(cm.full_name) else @classes_hash.delete(cm.full_name) @@ -268,931 +302,899 @@ def clear_file_contributions(relative_name, keep_position: false) end end - # Clear the TopLevel's class/module list to prevent duplicates - top_level.classes_or_modules.clear - end + ## + # Make sure any references to C variable names are resolved to the corresponding class. + # - ## - # Removes stale empty placeholders left by +clear_file_contributions+ with - # keep_position: true. After re-parsing, a file may no longer - # define a class it previously contributed to, leaving an empty entry in - # +comment_location+ and a stale +in_files+ reference. Call this after - # all re-parsing is complete. - - def cleanup_stale_contributions - all_classes_and_modules.each do |cm| - cm.comment_location.delete_if { |_, comments| comments.empty? } - cm.rebuild_comment_from_location - - cm.in_files.select! { |tl| cm.comment_location.key?(tl) || - cm.method_list.any? { |m| m.file == tl } || - cm.attributes.any? { |a| a.file == tl } || - cm.constants.any? { |c| c.file == tl } } - - if cm.in_files.empty? - if cm.is_a?(RDoc::NormalModule) - @modules_hash.delete(cm.full_name) - else - @classes_hash.delete(cm.full_name) + def resolve_c_superclasses + @classes_hash.each_value do |klass| + if klass.superclass.is_a?(String) && (candidate = find_c_enclosure(klass.superclass)) + klass.superclass = candidate end - cm.parent&.classes_hash&.delete(cm.name) - cm.parent&.modules_hash&.delete(cm.name) end end - end - ## - # Make sure any references to C variable names are resolved to the corresponding class. - # + ## + # Caches +relative_name+ in the text files hash, if it is a text file. - def resolve_c_superclasses - @classes_hash.each_value do |klass| - if klass.superclass.is_a?(String) && (candidate = find_c_enclosure(klass.superclass)) - klass.superclass = candidate + def cache_text_file(relative_name) + if top_level = @files_hash[relative_name] + @text_files_hash[relative_name] = top_level if top_level.text? end end - end - ## - # Caches +relative_name+ in the text files hash, if it is a text file. + ## + # Returns all classes discovered by RDoc - def cache_text_file(relative_name) - if top_level = @files_hash[relative_name] - @text_files_hash[relative_name] = top_level if top_level.text? + def all_classes + @classes_hash.values end - end - - ## - # Returns all classes discovered by RDoc - - def all_classes - @classes_hash.values - end - ## - # Returns all classes and modules discovered by RDoc + ## + # Returns all classes and modules discovered by RDoc - def all_classes_and_modules - @classes_hash.values + @modules_hash.values - end + def all_classes_and_modules + @classes_hash.values + @modules_hash.values + end - ## - # Returns a hash mapping class/module names to their paths, for use - # by type signature linking. Maps both qualified names (Foo::Bar) and - # unambiguous unqualified names (Bar). Ambiguous unqualified names - # (where multiple classes share the same name) are excluded to avoid - # wrong links. Cached after first call. + ## + # Returns a hash mapping class/module names to their paths, for use + # by type signature linking. Maps both qualified names (Foo::Bar) and + # unambiguous unqualified names (Bar). Ambiguous unqualified names + # (where multiple classes share the same name) are excluded to avoid + # wrong links. Cached after first call. - ## - # Invalidates the cached type name lookup. Server mode calls this after - # re-parsing changes the set of classes and modules. + ## + # Invalidates the cached type name lookup. Server mode calls this after + # re-parsing changes the set of classes and modules. - def invalidate_type_name_lookup # :nodoc: - @type_name_lookup = nil - end + def invalidate_type_name_lookup # :nodoc: + @type_name_lookup = nil + end - def type_name_lookup - @type_name_lookup ||= begin - lookup = {} - unqualified_names = {} - ambiguous_names = {} - all_classes_and_modules.each do |cm| - lookup[cm.full_name] = cm.path - unqualified_name = cm.name - - if ambiguous_names[unqualified_name] - # already known ambiguous, skip - elsif unqualified_names.key?(unqualified_name) - unqualified_names.delete(unqualified_name) - ambiguous_names[unqualified_name] = true - else - unqualified_names[unqualified_name] = cm.path + def type_name_lookup + @type_name_lookup ||= begin + lookup = {} + unqualified_names = {} + ambiguous_names = {} + all_classes_and_modules.each do |cm| + lookup[cm.full_name] = cm.path + unqualified_name = cm.name + + if ambiguous_names[unqualified_name] + # already known ambiguous, skip + elsif unqualified_names.key?(unqualified_name) + unqualified_names.delete(unqualified_name) + ambiguous_names[unqualified_name] = true + else + unqualified_names[unqualified_name] = cm.path + end end + lookup.merge!(unqualified_names) end - lookup.merge!(unqualified_names) end - end - ## - # Stores RBS type signatures loaded from sidecar .rbs files, keyed by - # "ClassName#method" or "ClassName.method". Replaces any previously - # stored set, so passing +{}+ clears it. Inline +#:+ annotations on - # method objects are NOT touched — those are owned by the source file. - - def merge_rbs_signatures(signatures) - @rbs_signatures = signatures - end + ## + # Stores RBS type signatures loaded from sidecar .rbs files, keyed by + # "ClassName#method" or "ClassName.method". Replaces any previously + # stored set, so passing +{}+ clears it. Inline +#:+ annotations on + # method objects are NOT touched — those are owned by the source file. - ## - # Returns the RBS type signature lines for +method_attr+ from loaded - # sidecar +.rbs+ files, or +nil+ if none. Falls through to the - # canonical method for aliases, and handles +initialize+ -> +.new+ - # singleton mapping. + def merge_rbs_signatures(signatures) + @rbs_signatures = signatures + end - def rbs_signature_for(method_attr) - return nil unless @rbs_signatures - cm = method_attr.parent - return nil unless cm.respond_to?(:full_name) + ## + # Returns the RBS type signature lines for +method_attr+ from loaded + # sidecar +.rbs+ files, or +nil+ if none. Falls through to the + # canonical method for aliases, and handles +initialize+ -> +.new+ + # singleton mapping. + + def rbs_signature_for(method_attr) + return nil unless @rbs_signatures + cm = method_attr.parent + return nil unless cm.respond_to?(:full_name) + + key = method_attr.singleton ? "#{cm.full_name}.#{method_attr.name}" : "#{cm.full_name}##{method_attr.name}" + sig = @rbs_signatures[key] + + # RBS keys constructors as #initialize, but RDoc renames them to .new + if !sig && method_attr.name == 'new' && method_attr.singleton + sig = @rbs_signatures["#{cm.full_name}#initialize"] + end - key = method_attr.singleton ? "#{cm.full_name}.#{method_attr.name}" : "#{cm.full_name}##{method_attr.name}" - sig = @rbs_signatures[key] + # For aliases, fall through to the canonical method (its inline #: + # takes precedence over any sidecar signature on the alias's name). + if !sig && method_attr.is_alias_for + canonical = method_attr.is_alias_for + sig = canonical.type_signature_lines || rbs_signature_for(canonical) + end - # RBS keys constructors as #initialize, but RDoc renames them to .new - if !sig && method_attr.name == 'new' && method_attr.singleton - sig = @rbs_signatures["#{cm.full_name}#initialize"] + sig end - # For aliases, fall through to the canonical method (its inline #: - # takes precedence over any sidecar signature on the alias's name). - if !sig && method_attr.is_alias_for - canonical = method_attr.is_alias_for - sig = canonical.type_signature_lines || rbs_signature_for(canonical) + def clear_rbs_signatures # :nodoc: + @rbs_signatures = nil end - sig - end - - def clear_rbs_signatures # :nodoc: - @rbs_signatures = nil - end - - ## - # All TopLevels known to RDoc + ## + # All TopLevels known to RDoc - def all_files - @files_hash.values - end + def all_files + @files_hash.values + end - ## - # Returns all modules discovered by RDoc + ## + # Returns all modules discovered by RDoc - def all_modules - modules_hash.values - end + def all_modules + modules_hash.values + end - ## - # Ancestors cache accessor. Maps a klass name to an Array of its ancestors - # in this store. If Foo in this store inherits from Object, Kernel won't be - # listed (it will be included from ruby's ri store). + ## + # Ancestors cache accessor. Maps a klass name to an Array of its ancestors + # in this store. If Foo in this store inherits from Object, Kernel won't be + # listed (it will be included from ruby's ri store). - def ancestors - @cache[:ancestors] - end + def ancestors + @cache[:ancestors] + end - ## - # Attributes cache accessor. Maps a class to an Array of its attributes. + ## + # Attributes cache accessor. Maps a class to an Array of its attributes. - def attributes - @cache[:attributes] - end + def attributes + @cache[:attributes] + end - ## - # Path to the cache file + ## + # Path to the cache file - def cache_path - File.join @path, 'cache.ri' - end + def cache_path + File.join @path, 'cache.ri' + end - ## - # Path to the ri data for +klass_name+ + ## + # Path to the ri data for +klass_name+ - def class_file(klass_name) - name = klass_name.split('::').last - File.join class_path(klass_name), "cdesc-#{name}.ri" - end + def class_file(klass_name) + name = klass_name.split('::').last + File.join class_path(klass_name), "cdesc-#{name}.ri" + end - ## - # Class methods cache accessor. Maps a class to an Array of its class - # methods (not full name). + ## + # Class methods cache accessor. Maps a class to an Array of its class + # methods (not full name). - def class_methods - @cache[:class_methods] - end + def class_methods + @cache[:class_methods] + end - ## - # Path where data for +klass_name+ will be stored (methods or class data) + ## + # Path where data for +klass_name+ will be stored (methods or class data) - def class_path(klass_name) - File.join @path, *klass_name.split('::') - end + def class_path(klass_name) + File.join @path, *klass_name.split('::') + end - ## - # Hash of all classes known to RDoc + ## + # Hash of all classes known to RDoc - def classes_hash - @classes_hash - end + def classes_hash + @classes_hash + end - ## - # Removes empty items and ensures item in each collection are unique and - # sorted + ## + # Removes empty items and ensures item in each collection are unique and + # sorted - def clean_cache_collection(collection) # :nodoc: - collection.each do |name, item| - if item.empty? - collection.delete name - else - # HACK mongrel-1.1.5 documents its files twice - item.uniq! - item.sort! + def clean_cache_collection(collection) # :nodoc: + collection.each do |name, item| + if item.empty? + collection.delete name + else + # HACK mongrel-1.1.5 documents its files twice + item.uniq! + item.sort! + end end end - end - ## - # Prepares the RDoc code object tree for use by a generator. - # - # It finds unique classes/modules defined, and replaces classes/modules that - # are aliases for another one by a copy with RDoc::ClassModule#is_alias_for - # set. - # - # It updates the RDoc::ClassModule#constant_aliases attribute of "real" - # classes or modules. - # - # It also completely removes the classes and modules that should be removed - # from the documentation and the methods that have a visibility below - # +min_visibility+, which is the --visibility option. - # - # See also RDoc::Context#remove_from_documentation? + ## + # Prepares the RDoc code object tree for use by a generator. + # + # It finds unique classes/modules defined, and replaces classes/modules that + # are aliases for another one by a copy with RDoc::ClassModule#is_alias_for + # set. + # + # It updates the RDoc::ClassModule#constant_aliases attribute of "real" + # classes or modules. + # + # It also completely removes the classes and modules that should be removed + # from the documentation and the methods that have a visibility below + # +min_visibility+, which is the --visibility option. + # + # See also RDoc::Context#remove_from_documentation? - def complete(min_visibility) - fix_basic_object_inheritance + def complete(min_visibility) + fix_basic_object_inheritance - # cache included modules before they are removed from the documentation - all_classes_and_modules.each { |cm| cm.ancestors } + # cache included modules before they are removed from the documentation + all_classes_and_modules.each { |cm| cm.ancestors } - unless min_visibility == :nodoc - remove_nodoc @classes_hash - remove_nodoc @modules_hash - end + unless min_visibility == :nodoc + remove_nodoc @classes_hash + remove_nodoc @modules_hash + end - @unique_classes = find_unique @classes_hash - @unique_modules = find_unique @modules_hash + @unique_classes = find_unique @classes_hash + @unique_modules = find_unique @modules_hash - unique_classes_and_modules.each do |cm| - cm.complete min_visibility - end + unique_classes_and_modules.each do |cm| + cm.complete min_visibility + end - @files_hash.each_key do |file_name| - tl = @files_hash[file_name] + @files_hash.each_key do |file_name| + tl = @files_hash[file_name] - unless tl.text? - tl.modules_hash.clear - tl.classes_hash.clear + unless tl.text? + tl.modules_hash.clear + tl.classes_hash.clear - tl.classes_or_modules.each do |cm| - name = cm.full_name - if cm.type == 'class' - tl.classes_hash[name] = cm if @classes_hash[name] - else - tl.modules_hash[name] = cm if @modules_hash[name] + tl.classes_or_modules.each do |cm| + name = cm.full_name + if cm.type == 'class' + tl.classes_hash[name] = cm if @classes_hash[name] + else + tl.modules_hash[name] = cm if @modules_hash[name] + end end end end end - end - ## - # Hash of all files known to RDoc + ## + # Hash of all files known to RDoc - def files_hash - @files_hash - end + def files_hash + @files_hash + end - ## - # Finds the enclosure (namespace) for the given C +variable+. + ## + # Finds the enclosure (namespace) for the given C +variable+. - def find_c_enclosure(variable) - @c_enclosure_classes.fetch variable do - break unless name = @c_enclosure_names[variable] + def find_c_enclosure(variable) + @c_enclosure_classes.fetch variable do + break unless name = @c_enclosure_names[variable] - mod = find_class_or_module name + mod = find_class_or_module name - unless mod - loaded_mod = load_class_data name + unless mod + loaded_mod = load_class_data name - file = loaded_mod.in_files.first + file = loaded_mod.in_files.first - return unless file # legacy data source + return unless file # legacy data source - file.store = self + file.store = self - mod = file.add_module RDoc::NormalModule, name - end + mod = file.add_module ::RDoc::NormalModule, name + end - @c_enclosure_classes[variable] = mod + @c_enclosure_classes[variable] = mod + end end - end - ## - # Finds the class with +name+ in all discovered classes + ## + # Finds the class with +name+ in all discovered classes - def find_class_named(name) - @classes_hash[name] - end + def find_class_named(name) + @classes_hash[name] + end - ## - # Finds the class with +name+ starting in namespace +from+ + ## + # Finds the class with +name+ starting in namespace +from+ - def find_class_named_from(name, from) - from = find_class_named from unless RDoc::Context === from + def find_class_named_from(name, from) + from = find_class_named from unless ::RDoc::Context === from - until RDoc::TopLevel === from do - return nil unless from + until ::RDoc::TopLevel === from do + return nil unless from - klass = from.find_class_named name - return klass if klass + klass = from.find_class_named name + return klass if klass - from = from.parent - end + from = from.parent + end - find_class_named name - end + find_class_named name + end - ## - # Finds the class or module with +name+ + ## + # Finds the class or module with +name+ - def find_class_or_module(name) - name = $' if name =~ /^::/ - @classes_hash[name] || @modules_hash[name] - end + def find_class_or_module(name) + name = $' if name =~ /^::/ + @classes_hash[name] || @modules_hash[name] + end - ## - # Finds the file with +name+ in all discovered files + ## + # Finds the file with +name+ in all discovered files - def find_file_named(name) - @files_hash[name] - end + def find_file_named(name) + @files_hash[name] + end - ## - # Finds the module with +name+ in all discovered modules + ## + # Finds the module with +name+ in all discovered modules - def find_module_named(name) - @modules_hash[name] - end + def find_module_named(name) + @modules_hash[name] + end - ## - # Returns the RDoc::TopLevel that is a text file and has the given - # +file_name+ + ## + # Returns the RDoc::TopLevel that is a text file and has the given + # +file_name+ - def find_text_page(file_name) - @text_files_hash.each_value.find do |file| - file.full_name == file_name + def find_text_page(file_name) + @text_files_hash.each_value.find do |file| + file.full_name == file_name + end end - end - ## - # Finds unique classes/modules defined in +all_hash+, - # and returns them as an array. Performs the alias - # updates in +all_hash+: see ::complete. - #-- - # TODO aliases should be registered by Context#add_module_alias + ## + # Finds unique classes/modules defined in +all_hash+, + # and returns them as an array. Performs the alias + # updates in +all_hash+: see ::complete. + #-- + # TODO aliases should be registered by Context#add_module_alias + + def find_unique(all_hash) + unique = [] - def find_unique(all_hash) - unique = [] + all_hash.each_pair do |full_name, cm| + unique << cm if full_name == cm.full_name + end - all_hash.each_pair do |full_name, cm| - unique << cm if full_name == cm.full_name + unique end - unique - end + ## + # Fixes the erroneous BasicObject < Object in 1.9. + # + # Because we assumed all classes without a stated superclass + # inherit from Object, we have the above wrong inheritance. + # + # We fix BasicObject right away if we are running in a Ruby + # version >= 1.9. - ## - # Fixes the erroneous BasicObject < Object in 1.9. - # - # Because we assumed all classes without a stated superclass - # inherit from Object, we have the above wrong inheritance. - # - # We fix BasicObject right away if we are running in a Ruby - # version >= 1.9. + def fix_basic_object_inheritance + basic = classes_hash['BasicObject'] + return unless basic + basic.superclass = nil + end - def fix_basic_object_inheritance - basic = classes_hash['BasicObject'] - return unless basic - basic.superclass = nil - end + ## + # Friendly rendition of #path + + def friendly_path + case type + when :gem + parent = File.expand_path '..', @path + "gem #{File.basename parent}" + when :home then ::RDoc.home + when :site then 'ruby site' + when :system then 'ruby core' + else @path + end + end - ## - # Friendly rendition of #path - - def friendly_path - case type - when :gem - parent = File.expand_path '..', @path - "gem #{File.basename parent}" - when :home then RDoc.home - when :site then 'ruby site' - when :system then 'ruby core' - else @path + def inspect # :nodoc: + "#<%s:0x%x %s %p>" % [self.class, object_id, @path, module_names.sort] end - end - def inspect # :nodoc: - "#<%s:0x%x %s %p>" % [self.class, object_id, @path, module_names.sort] - end + ## + # Instance methods cache accessor. Maps a class to an Array of its + # instance methods (not full name). - ## - # Instance methods cache accessor. Maps a class to an Array of its - # instance methods (not full name). + def instance_methods + @cache[:instance_methods] + end - def instance_methods - @cache[:instance_methods] - end + ## + # Loads all items from this store into memory. This recreates a + # documentation tree for use by a generator - ## - # Loads all items from this store into memory. This recreates a - # documentation tree for use by a generator + def load_all + load_cache - def load_all - load_cache + module_names.each do |module_name| + mod = find_class_or_module(module_name) || load_class(module_name) - module_names.each do |module_name| - mod = find_class_or_module(module_name) || load_class(module_name) + # load method documentation since the loaded class/module does not have + # it + loaded_methods = mod.method_list.map do |method| + load_method module_name, method.full_name + end - # load method documentation since the loaded class/module does not have - # it - loaded_methods = mod.method_list.map do |method| - load_method module_name, method.full_name - end + mod.method_list.replace loaded_methods - mod.method_list.replace loaded_methods + loaded_attributes = mod.attributes.map do |attribute| + load_method module_name, attribute.full_name + end - loaded_attributes = mod.attributes.map do |attribute| - load_method module_name, attribute.full_name + mod.attributes.replace loaded_attributes end - mod.attributes.replace loaded_attributes - end - - all_classes_and_modules.each do |mod| - descendent_re = /^#{mod.full_name}::[^:]+$/ + all_classes_and_modules.each do |mod| + descendent_re = /^#{mod.full_name}::[^:]+$/ - module_names.each do |name| - next unless name =~ descendent_re + module_names.each do |name| + next unless name =~ descendent_re - descendent = find_class_or_module name + descendent = find_class_or_module name - case descendent - when RDoc::NormalClass - mod.classes_hash[name] = descendent - when RDoc::NormalModule - mod.modules_hash[name] = descendent + case descendent + when ::RDoc::NormalClass + mod.classes_hash[name] = descendent + when ::RDoc::NormalModule + mod.modules_hash[name] = descendent + end end end - end - @cache[:pages].each do |page_name| - page = load_page page_name - @files_hash[page_name] = page - @text_files_hash[page_name] = page if page.text? + @cache[:pages].each do |page_name| + page = load_page page_name + @files_hash[page_name] = page + @text_files_hash[page_name] = page if page.text? + end end - end - ## - # Loads cache file for this store + ## + # Loads cache file for this store - def load_cache - #orig_enc = @encoding + def load_cache + #orig_enc = @encoding - @cache = marshal_load(cache_path) + @cache = marshal_load(cache_path) - load_enc = @cache[:encoding] + load_enc = @cache[:encoding] - # TODO this feature will be time-consuming to add: - # a) Encodings may be incompatible but transcodeable - # b) Need to warn in the appropriate spots, wherever they may be - # c) Need to handle cross-cache differences in encodings - # d) Need to warn when generating into a cache with different encodings - # - #if orig_enc and load_enc != orig_enc then - # warn "Cached encoding #{load_enc} is incompatible with #{orig_enc}\n" \ - # "from #{path}/cache.ri" unless - # Encoding.compatible? orig_enc, load_enc - #end - - @encoding = load_enc unless @encoding - - @cache[:pages] ||= [] - @cache[:main] ||= nil - @cache[:c_class_variables] ||= {} - @cache[:c_singleton_class_variables] ||= {} - - @cache[:c_class_variables].each do |_, map| - map.each do |variable, name| - @c_enclosure_names[variable] = name + # TODO this feature will be time-consuming to add: + # a) Encodings may be incompatible but transcodeable + # b) Need to warn in the appropriate spots, wherever they may be + # c) Need to handle cross-cache differences in encodings + # d) Need to warn when generating into a cache with different encodings + # + #if orig_enc and load_enc != orig_enc then + # warn "Cached encoding #{load_enc} is incompatible with #{orig_enc}\n" \ + # "from #{path}/cache.ri" unless + # Encoding.compatible? orig_enc, load_enc + #end + + @encoding = load_enc unless @encoding + + @cache[:pages] ||= [] + @cache[:main] ||= nil + @cache[:c_class_variables] ||= {} + @cache[:c_singleton_class_variables] ||= {} + + @cache[:c_class_variables].each do |_, map| + map.each do |variable, name| + @c_enclosure_names[variable] = name + end end - end - @cache - rescue Errno::ENOENT - end + @cache + rescue Errno::ENOENT + end - ## - # Loads ri data for +klass_name+ and hooks it up to this store. + ## + # Loads ri data for +klass_name+ and hooks it up to this store. - def load_class(klass_name) - obj = load_class_data klass_name + def load_class(klass_name) + obj = load_class_data klass_name - obj.store = self + obj.store = self - case obj - when RDoc::NormalClass - @classes_hash[klass_name] = obj - when RDoc::SingleClass - @classes_hash[klass_name] = obj - when RDoc::NormalModule - @modules_hash[klass_name] = obj + case obj + when ::RDoc::NormalClass + @classes_hash[klass_name] = obj + when ::RDoc::SingleClass + @classes_hash[klass_name] = obj + when ::RDoc::NormalModule + @modules_hash[klass_name] = obj + end end - end - ## - # Loads ri data for +klass_name+ + ## + # Loads ri data for +klass_name+ - def load_class_data(klass_name) - file = class_file klass_name + def load_class_data(klass_name) + file = class_file klass_name - marshal_load(file) - rescue Errno::ENOENT => e - error = MissingFileError.new(self, file, klass_name) - error.set_backtrace e.backtrace - raise error - end + marshal_load(file) + rescue Errno::ENOENT => e + error = MissingFileError.new(self, file, klass_name) + error.set_backtrace e.backtrace + raise error + end - ## - # Loads ri data for +method_name+ in +klass_name+ - - def load_method(klass_name, method_name) - file = method_file klass_name, method_name - - obj = marshal_load(file) - obj.store = self - obj.parent ||= find_class_or_module(klass_name) || load_class(klass_name) - obj - rescue Errno::ENOENT => e - error = MissingFileError.new(self, file, klass_name + method_name) - error.set_backtrace e.backtrace - raise error - end + ## + # Loads ri data for +method_name+ in +klass_name+ + + def load_method(klass_name, method_name) + file = method_file klass_name, method_name + + obj = marshal_load(file) + obj.store = self + obj.parent ||= find_class_or_module(klass_name) || load_class(klass_name) + obj + rescue Errno::ENOENT => e + error = MissingFileError.new(self, file, klass_name + method_name) + error.set_backtrace e.backtrace + raise error + end - ## - # Loads ri data for +page_name+ - - def load_page(page_name) - file = page_file page_name - - obj = marshal_load(file) - obj.store = self - obj - rescue Errno::ENOENT => e - error = MissingFileError.new(self, file, page_name) - error.set_backtrace e.backtrace - raise error - end + ## + # Loads ri data for +page_name+ + + def load_page(page_name) + file = page_file page_name + + obj = marshal_load(file) + obj.store = self + obj + rescue Errno::ENOENT => e + error = MissingFileError.new(self, file, page_name) + error.set_backtrace e.backtrace + raise error + end - ## - # Gets the main page for this RDoc store. This page is used as the root of - # the RDoc server. + ## + # Gets the main page for this RDoc store. This page is used as the root of + # the RDoc server. - def main - @cache[:main] - end + def main + @cache[:main] + end - ## - # Sets the main page for this RDoc store. + ## + # Sets the main page for this RDoc store. - def main=(page) - @cache[:main] = page - end + def main=(page) + @cache[:main] = page + end - ## - # Converts the variable => ClassModule map +variables+ from a C parser into - # a variable => class name map. + ## + # Converts the variable => ClassModule map +variables+ from a C parser into + # a variable => class name map. - def make_variable_map(variables) - map = {} + def make_variable_map(variables) + map = {} - variables.each { |variable, class_module| - map[variable] = class_module.full_name - } + variables.each { |variable, class_module| + map[variable] = class_module.full_name + } - map - end + map + end - ## - # Path to the ri data for +method_name+ in +klass_name+ + ## + # Path to the ri data for +method_name+ in +klass_name+ - def method_file(klass_name, method_name) - method_name = method_name.split('::').last - method_name =~ /#(.*)/ - method_type = $1 ? 'i' : 'c' - method_name = $1 if $1 - method_name = method_name.gsub(/\W/) { "%%%02x" % $&[0].ord } + def method_file(klass_name, method_name) + method_name = method_name.split('::').last + method_name =~ /#(.*)/ + method_type = $1 ? 'i' : 'c' + method_name = $1 if $1 + method_name = method_name.gsub(/\W/) { "%%%02x" % $&[0].ord } - File.join class_path(klass_name), "#{method_name}-#{method_type}.ri" - end + File.join class_path(klass_name), "#{method_name}-#{method_type}.ri" + end - ## - # Modules cache accessor. An Array of all the module (and class) names in - # the store. + ## + # Modules cache accessor. An Array of all the module (and class) names in + # the store. - def module_names - @cache[:modules] - end + def module_names + @cache[:modules] + end - ## - # Hash of all modules known to RDoc + ## + # Hash of all modules known to RDoc - def modules_hash - @modules_hash - end + def modules_hash + @modules_hash + end - ## - # Returns the RDoc::TopLevel that is a file and has the given +name+ + ## + # Returns the RDoc::TopLevel that is a file and has the given +name+ - def page(name) - @files_hash.each_value.find do |file| - file.page_name == name or file.base_name == name + def page(name) + @files_hash.each_value.find do |file| + file.page_name == name or file.base_name == name + end end - end - ## - # Path to the ri data for +page_name+ + ## + # Path to the ri data for +page_name+ - def page_file(page_name) - file_name = File.basename(page_name).gsub('.', '_') + def page_file(page_name) + file_name = File.basename(page_name).gsub('.', '_') - File.join @path, File.dirname(page_name), "page-#{file_name}.ri" - end + File.join @path, File.dirname(page_name), "page-#{file_name}.ri" + end - ## - # Removes from +all_hash+ the contexts that are nodoc or have no content. - # - # See RDoc::Context#remove_from_documentation? + ## + # Removes from +all_hash+ the contexts that are nodoc or have no content. + # + # See RDoc::Context#remove_from_documentation? - def remove_nodoc(all_hash) - all_hash.keys.each do |name| - context = all_hash[name] - all_hash.delete(name) if context.remove_from_documentation? + def remove_nodoc(all_hash) + all_hash.keys.each do |name| + context = all_hash[name] + all_hash.delete(name) if context.remove_from_documentation? + end end - end - ## - # Saves all entries in the store + ## + # Saves all entries in the store + + def save + load_cache - def save - load_cache + all_classes_and_modules.each do |klass| + save_class klass - all_classes_and_modules.each do |klass| - save_class klass + klass.each_method do |method| + save_method klass, method + end - klass.each_method do |method| - save_method klass, method + klass.attributes.each do |attribute| + save_method klass, attribute + end end - klass.attributes.each do |attribute| - save_method klass, attribute + all_files.each do |file| + save_page file end - end - all_files.each do |file| - save_page file + save_cache end - save_cache - end - - ## - # Writes the cache file for this store + ## + # Writes the cache file for this store - def save_cache - clean_cache_collection @cache[:ancestors] - clean_cache_collection @cache[:attributes] - clean_cache_collection @cache[:class_methods] - clean_cache_collection @cache[:instance_methods] + def save_cache + clean_cache_collection @cache[:ancestors] + clean_cache_collection @cache[:attributes] + clean_cache_collection @cache[:class_methods] + clean_cache_collection @cache[:instance_methods] - @cache[:modules].uniq! - @cache[:modules].sort! + @cache[:modules].uniq! + @cache[:modules].sort! - @cache[:pages].uniq! - @cache[:pages].sort! + @cache[:pages].uniq! + @cache[:pages].sort! - @cache[:encoding] = @encoding # this gets set twice due to assert_cache + @cache[:encoding] = @encoding # this gets set twice due to assert_cache - @cache[:c_class_variables].merge! @c_class_variables - @cache[:c_singleton_class_variables].merge! @c_singleton_class_variables + @cache[:c_class_variables].merge! @c_class_variables + @cache[:c_singleton_class_variables].merge! @c_singleton_class_variables - return if @dry_run + return if @dry_run - File.open cache_path, 'wb' do |io| - Marshal.dump @cache, io + File.open cache_path, 'wb' do |io| + Marshal.dump @cache, io + end end - end - ## - # Writes the ri data for +klass+ (or module) + ## + # Writes the ri data for +klass+ (or module) - def save_class(klass) - full_name = klass.full_name + def save_class(klass) + full_name = klass.full_name - FileUtils.mkdir_p class_path(full_name) unless @dry_run + FileUtils.mkdir_p class_path(full_name) unless @dry_run - @cache[:modules] << full_name + @cache[:modules] << full_name - path = class_file full_name + path = class_file full_name - begin - disk_klass = load_class full_name + begin + disk_klass = load_class full_name - klass = disk_klass.merge klass - rescue MissingFileError - end + klass = disk_klass.merge klass + rescue MissingFileError + end - # BasicObject has no ancestors - ancestors = klass.direct_ancestors.compact.map do |ancestor| - # HACK for classes we don't know about (class X < RuntimeError) - String === ancestor ? ancestor : ancestor.full_name - end + # BasicObject has no ancestors + ancestors = klass.direct_ancestors.compact.map do |ancestor| + # HACK for classes we don't know about (class X < RuntimeError) + String === ancestor ? ancestor : ancestor.full_name + end - @cache[:ancestors][full_name] ||= [] - @cache[:ancestors][full_name].concat ancestors + @cache[:ancestors][full_name] ||= [] + @cache[:ancestors][full_name].concat ancestors - attribute_definitions = klass.attributes.map do |attribute| - "#{attribute.definition} #{attribute.name}" - end + attribute_definitions = klass.attributes.map do |attribute| + "#{attribute.definition} #{attribute.name}" + end - unless attribute_definitions.empty? - @cache[:attributes][full_name] ||= [] - @cache[:attributes][full_name].concat attribute_definitions - end + unless attribute_definitions.empty? + @cache[:attributes][full_name] ||= [] + @cache[:attributes][full_name].concat attribute_definitions + end - to_delete = [] + to_delete = [] - unless klass.method_list.empty? - @cache[:class_methods][full_name] ||= [] - @cache[:instance_methods][full_name] ||= [] + unless klass.method_list.empty? + @cache[:class_methods][full_name] ||= [] + @cache[:instance_methods][full_name] ||= [] - class_methods, instance_methods = - klass.method_list.partition { |meth| meth.singleton } + class_methods, instance_methods = + klass.method_list.partition { |meth| meth.singleton } - class_methods = class_methods. map { |method| method.name } - instance_methods = instance_methods.map { |method| method.name } - attribute_names = klass.attributes.map { |attr| attr.name } + class_methods = class_methods. map { |method| method.name } + instance_methods = instance_methods.map { |method| method.name } + attribute_names = klass.attributes.map { |attr| attr.name } - old = @cache[:class_methods][full_name] - class_methods - to_delete.concat old.map { |method| - method_file full_name, "#{full_name}::#{method}" - } + old = @cache[:class_methods][full_name] - class_methods + to_delete.concat old.map { |method| + method_file full_name, "#{full_name}::#{method}" + } - old = @cache[:instance_methods][full_name] - - instance_methods - attribute_names - to_delete.concat old.map { |method| - method_file full_name, "#{full_name}##{method}" - } + old = @cache[:instance_methods][full_name] - + instance_methods - attribute_names + to_delete.concat old.map { |method| + method_file full_name, "#{full_name}##{method}" + } - @cache[:class_methods][full_name] = class_methods - @cache[:instance_methods][full_name] = instance_methods - end + @cache[:class_methods][full_name] = class_methods + @cache[:instance_methods][full_name] = instance_methods + end - return if @dry_run + return if @dry_run - FileUtils.rm_f to_delete + FileUtils.rm_f to_delete - File.open path, 'wb' do |io| - Marshal.dump klass, io + File.open path, 'wb' do |io| + Marshal.dump klass, io + end end - end - ## - # Writes the ri data for +method+ on +klass+ + ## + # Writes the ri data for +method+ on +klass+ - def save_method(klass, method) - full_name = klass.full_name + def save_method(klass, method) + full_name = klass.full_name - FileUtils.mkdir_p class_path(full_name) unless @dry_run + FileUtils.mkdir_p class_path(full_name) unless @dry_run - cache = if method.singleton - @cache[:class_methods] - else - @cache[:instance_methods] - end - cache[full_name] ||= [] - cache[full_name] << method.name + cache = if method.singleton + @cache[:class_methods] + else + @cache[:instance_methods] + end + cache[full_name] ||= [] + cache[full_name] << method.name - return if @dry_run + return if @dry_run - File.open method_file(full_name, method.full_name), 'wb' do |io| - Marshal.dump method, io + File.open method_file(full_name, method.full_name), 'wb' do |io| + Marshal.dump method, io + end end - end - ## - # Writes the ri data for +page+ + ## + # Writes the ri data for +page+ - def save_page(page) - return unless page.text? + def save_page(page) + return unless page.text? - path = page_file page.full_name + path = page_file page.full_name - FileUtils.mkdir_p File.dirname(path) unless @dry_run + FileUtils.mkdir_p File.dirname(path) unless @dry_run - cache[:pages] ||= [] - cache[:pages] << page.full_name + cache[:pages] ||= [] + cache[:pages] << page.full_name - return if @dry_run + return if @dry_run - File.open path, 'wb' do |io| - Marshal.dump page, io + File.open path, 'wb' do |io| + Marshal.dump page, io + end end - end - ## - # Source of the contents of this store. - # - # For a store from a gem the source is the gem name. For a store from the - # home directory the source is "home". For system ri store (the standard - # library documentation) the source is"ruby". For a store from the site - # ri directory the store is "site". For other stores the source is the - # #path. - - def source - case type - when :gem then File.basename File.expand_path '..', @path - when :home then 'home' - when :site then 'site' - when :system then 'ruby' - else @path + ## + # Source of the contents of this store. + # + # For a store from a gem the source is the gem name. For a store from the + # home directory the source is "home". For system ri store (the standard + # library documentation) the source is"ruby". For a store from the site + # ri directory the store is "site". For other stores the source is the + # #path. + + def source + case type + when :gem then File.basename File.expand_path '..', @path + when :home then 'home' + when :site then 'site' + when :system then 'ruby' + else @path + end end - end - ## - # Gets the title for this RDoc store. This is used as the title in each - # page on the RDoc server + ## + # Gets the title for this RDoc store. This is used as the title in each + # page on the RDoc server - def title - @cache[:title] - end + def title + @cache[:title] + end - ## - # Sets the title page for this RDoc store. + ## + # Sets the title page for this RDoc store. - def title=(title) - @cache[:title] = title - end + def title=(title) + @cache[:title] = title + end - ## - # Returns the unique classes discovered by RDoc. - # - # ::complete must have been called prior to using this method. + ## + # Returns the unique classes discovered by RDoc. + # + # ::complete must have been called prior to using this method. - def unique_classes - @unique_classes - end + def unique_classes + @unique_classes + end - ## - # Returns the unique classes and modules discovered by RDoc. - # ::complete must have been called prior to using this method. + ## + # Returns the unique classes and modules discovered by RDoc. + # ::complete must have been called prior to using this method. - def unique_classes_and_modules - @unique_classes + @unique_modules - end + def unique_classes_and_modules + @unique_classes + @unique_modules + end - ## - # Returns the unique modules discovered by RDoc. - # ::complete must have been called prior to using this method. + ## + # Returns the unique modules discovered by RDoc. + # ::complete must have been called prior to using this method. - def unique_modules - @unique_modules - end + def unique_modules + @unique_modules + end private - def remove_classes_and_modules(cms) - cms.each do |cm| - remove_classes_and_modules(cm.classes_and_modules) + def remove_classes_and_modules(cms) + cms.each do |cm| + remove_classes_and_modules(cm.classes_and_modules) - if cm.is_a?(RDoc::NormalModule) - @modules_hash.delete(cm.full_name) - else - @classes_hash.delete(cm.full_name) + if cm.is_a?(::RDoc::NormalModule) + @modules_hash.delete(cm.full_name) + else + @classes_hash.delete(cm.full_name) + end end end - end - def marshal_load(file) - File.open(file, 'rb') {|io| Marshal.load(io, MarshalFilter)} - end + def marshal_load(file) + File.open(file, 'rb') {|io| Marshal.load(io, MarshalFilter)} + end - MarshalFilter = proc do |obj| - case obj - when true, false, nil, Array, Class, Encoding, Hash, Integer, String, Symbol, RDoc::Text - else - unless obj.class.name.start_with?("RDoc::") - raise TypeError, "not permitted class: #{obj.class.name}" + MarshalFilter = proc do |obj| + case obj + when true, false, nil, Array, Class, ::Encoding, Hash, Integer, String, Symbol, ::RDoc::Text + else + unless obj.class.name.start_with?("RDoc::") + raise TypeError, "not permitted class: #{obj.class.name}" + end end + obj end - obj - end - private_constant :MarshalFilter + private_constant :MarshalFilter + end end diff --git a/lib/rdoc/task.rb b/lib/rdoc/task.rb index 293eb02eff..59acd413fa 100644 --- a/lib/rdoc/task.rb +++ b/lib/rdoc/task.rb @@ -25,7 +25,7 @@ begin gem 'rdoc' rescue Gem::LoadError -end unless defined?(RDoc) +end unless defined?(::RDoc) begin gem 'rake' @@ -36,313 +36,315 @@ require 'rake' require 'rake/tasklib' -## -# RDoc::Task creates the following rake tasks to generate and clean up RDoc -# output: -# -# [rdoc] -# Main task for this RDoc task. -# -# [clobber_rdoc] -# Delete all the rdoc files. This target is automatically added to the main -# clobber target. -# -# [rerdoc] -# Rebuild the rdoc files from scratch, even if they are not out of date. -# -# [rdoc:coverage] -# Print RDoc coverage report for all rdoc files. -# -# Simple Example: -# -# require 'rdoc/task' -# -# RDoc::Task.new do |rdoc| -# rdoc.main = "README.md" -# rdoc.rdoc_files.include("README.md", "lib/**/*.rb") -# end -# -# The +rdoc+ object passed to the block is an RDoc::Task object. See the -# attributes list for the RDoc::Task class for available customization options. -# -# == Specifying different task names -# -# You may wish to give the task a different name, such as if you are -# generating two sets of documentation. For instance, if you want to have a -# development set of documentation including private methods: -# -# require 'rdoc/task' -# -# RDoc::Task.new :rdoc_dev do |rdoc| -# rdoc.main = "README.md" -# rdoc.rdoc_files.include("README.md", "lib/**/*.rb") -# rdoc.options << "--all" -# end -# -# The tasks would then be named :rdoc_dev, -# :clobber_rdoc_dev, and :rerdoc_dev. -# -# If you wish to have completely different task names, then pass a Hash as -# first argument. With the :rdoc, :clobber_rdoc and -# :rerdoc options, you can customize the task names to your liking. -# -# For example: -# -# require 'rdoc/task' -# -# RDoc::Task.new(:rdoc => "rdoc", :clobber_rdoc => "rdoc:clean", -# :rerdoc => "rdoc:force") -# -# This will create the tasks :rdoc, :rdoc:clean, -# :rdoc:force, and :rdoc:coverage. - -class RDoc::Task < Rake::TaskLib - - ## - # Name of the main, top level task. (default is :rdoc) - - attr_accessor :name - - ## - # The markup format; one of: +rdoc+ (the default), +markdown+, +rd+, +tomdoc+. - # See {Markup Formats}[rdoc-ref:RDoc::Markup@Markup+Formats]. - attr_accessor :markup - - ## - # Name of directory to receive the html output files. (default is "html") - - attr_accessor :rdoc_dir - - ## - # Title of RDoc documentation. (defaults to rdoc's default) - - attr_accessor :title - - ## - # Name of file to be used as the main, top level file of the RDoc. (default - # is none) - - attr_accessor :main - - ## - # Name of template to be used by rdoc. (defaults to rdoc's default) - - attr_accessor :template - - ## - # Name of format generator (--format) used by rdoc. (defaults to - # rdoc's default) - - attr_accessor :generator - +module RDoc ## - # List of files to be included in the rdoc generation. (default is []) - - attr_accessor :rdoc_files + # RDoc::Task creates the following rake tasks to generate and clean up RDoc + # output: + # + # [rdoc] + # Main task for this RDoc task. + # + # [clobber_rdoc] + # Delete all the rdoc files. This target is automatically added to the main + # clobber target. + # + # [rerdoc] + # Rebuild the rdoc files from scratch, even if they are not out of date. + # + # [rdoc:coverage] + # Print RDoc coverage report for all rdoc files. + # + # Simple Example: + # + # require 'rdoc/task' + # + # RDoc::Task.new do |rdoc| + # rdoc.main = "README.md" + # rdoc.rdoc_files.include("README.md", "lib/**/*.rb") + # end + # + # The +rdoc+ object passed to the block is an RDoc::Task object. See the + # attributes list for the RDoc::Task class for available customization options. + # + # == Specifying different task names + # + # You may wish to give the task a different name, such as if you are + # generating two sets of documentation. For instance, if you want to have a + # development set of documentation including private methods: + # + # require 'rdoc/task' + # + # RDoc::Task.new :rdoc_dev do |rdoc| + # rdoc.main = "README.md" + # rdoc.rdoc_files.include("README.md", "lib/**/*.rb") + # rdoc.options << "--all" + # end + # + # The tasks would then be named :rdoc_dev, + # :clobber_rdoc_dev, and :rerdoc_dev. + # + # If you wish to have completely different task names, then pass a Hash as + # first argument. With the :rdoc, :clobber_rdoc and + # :rerdoc options, you can customize the task names to your liking. + # + # For example: + # + # require 'rdoc/task' + # + # RDoc::Task.new(:rdoc => "rdoc", :clobber_rdoc => "rdoc:clean", + # :rerdoc => "rdoc:force") + # + # This will create the tasks :rdoc, :rdoc:clean, + # :rdoc:force, and :rdoc:coverage. + + class Task < Rake::TaskLib + + ## + # Name of the main, top level task. (default is :rdoc) + + attr_accessor :name + + ## + # The markup format; one of: +rdoc+ (the default), +markdown+, +rd+, +tomdoc+. + # See {Markup Formats}[rdoc-ref:RDoc::Markup@Markup+Formats]. + attr_accessor :markup + + ## + # Name of directory to receive the html output files. (default is "html") + + attr_accessor :rdoc_dir + + ## + # Title of RDoc documentation. (defaults to rdoc's default) + + attr_accessor :title + + ## + # Name of file to be used as the main, top level file of the RDoc. (default + # is none) + + attr_accessor :main + + ## + # Name of template to be used by rdoc. (defaults to rdoc's default) + + attr_accessor :template + + ## + # Name of format generator (--format) used by rdoc. (defaults to + # rdoc's default) + + attr_accessor :generator + + ## + # List of files to be included in the rdoc generation. (default is []) + + attr_accessor :rdoc_files + + ## + # Additional list of options to be passed rdoc. (default is []) + + attr_accessor :options + + ## + # Whether to run the rdoc process as an external shell (default is false) + + attr_accessor :external + + ## + # Create an RDoc task with the given name. See the RDoc::Task class overview + # for documentation. + + def initialize(name = :rdoc) # :yield: self + defaults + + check_names name - ## - # Additional list of options to be passed rdoc. (default is []) + @name = name - attr_accessor :options + yield self if block_given? - ## - # Whether to run the rdoc process as an external shell (default is false) - - attr_accessor :external - - ## - # Create an RDoc task with the given name. See the RDoc::Task class overview - # for documentation. - - def initialize(name = :rdoc) # :yield: self - defaults - - check_names name - - @name = name - - yield self if block_given? - - define - end + define + end - ## - # Ensures that +names+ only includes names for the :rdoc, :clobber_rdoc and - # :rerdoc. If other names are given an ArgumentError is raised. + ## + # Ensures that +names+ only includes names for the :rdoc, :clobber_rdoc and + # :rerdoc. If other names are given an ArgumentError is raised. - def check_names(names) - return unless Hash === names + def check_names(names) + return unless Hash === names - invalid_options = - names.keys.map { |k| k.to_sym } - [:rdoc, :clobber_rdoc, :rerdoc] + invalid_options = + names.keys.map { |k| k.to_sym } - [:rdoc, :clobber_rdoc, :rerdoc] - unless invalid_options.empty? - raise ArgumentError, "invalid options: #{invalid_options.join ', '}" + unless invalid_options.empty? + raise ArgumentError, "invalid options: #{invalid_options.join ', '}" + end end - end - ## - # Task description for the clobber rdoc task or its renamed equivalent - - def clobber_task_description - "Remove RDoc HTML files" - end + ## + # Task description for the clobber rdoc task or its renamed equivalent - ## - # Sets default task values - - def defaults - @name = :rdoc - @rdoc_files = Rake::FileList.new - @rdoc_dir = 'html' - @main = nil - @title = nil - @template = nil - @generator = nil - @options = [] - end - - ## - # Create the tasks defined by this task lib. + def clobber_task_description + "Remove RDoc HTML files" + end - def define - desc rdoc_task_description - task rdoc_task_name + ## + # Sets default task values + + def defaults + @name = :rdoc + @rdoc_files = Rake::FileList.new + @rdoc_dir = 'html' + @main = nil + @title = nil + @template = nil + @generator = nil + @options = [] + end - desc rerdoc_task_description - task rerdoc_task_name => [clobber_task_name, rdoc_task_name] + ## + # Create the tasks defined by this task lib. - desc clobber_task_description - task clobber_task_name do - rm_r @rdoc_dir rescue nil - end + def define + desc rdoc_task_description + task rdoc_task_name - task :clobber => [clobber_task_name] + desc rerdoc_task_description + task rerdoc_task_name => [clobber_task_name, rdoc_task_name] - directory @rdoc_dir + desc clobber_task_description + task clobber_task_name do + rm_r @rdoc_dir rescue nil + end - rdoc_target_deps = [ - @rdoc_files, - Rake.application.rakefile - ].flatten.compact + task :clobber => [clobber_task_name] - task rdoc_task_name => [rdoc_target] - file rdoc_target => rdoc_target_deps do - @before_running_rdoc.call if @before_running_rdoc - args = option_list + @rdoc_files + directory @rdoc_dir - $stderr.puts "rdoc #{args.join ' '}" if Rake.application.options.trace - RDoc::RDoc.new.document args - end + rdoc_target_deps = [ + @rdoc_files, + Rake.application.rakefile + ].flatten.compact - namespace rdoc_task_name do - desc coverage_task_description - task coverage_task_name do + task rdoc_task_name => [rdoc_target] + file rdoc_target => rdoc_target_deps do @before_running_rdoc.call if @before_running_rdoc - opts = option_list << "-C" - args = opts + @rdoc_files + args = option_list + @rdoc_files $stderr.puts "rdoc #{args.join ' '}" if Rake.application.options.trace - RDoc::RDoc.new.document args - rescue SystemExit => exit - raise "RDoc coverage is incomplete" unless exit.success? + ::RDoc::RDoc.new.document args end - desc server_task_description - task "server" do - @before_running_rdoc.call if @before_running_rdoc - args = option_list + ["--server"] + @rdoc_files - - $stderr.puts "rdoc #{args.join ' '}" if Rake.application.options.trace - RDoc::RDoc.new.document args + namespace rdoc_task_name do + desc coverage_task_description + task coverage_task_name do + @before_running_rdoc.call if @before_running_rdoc + opts = option_list << "-C" + args = opts + @rdoc_files + + $stderr.puts "rdoc #{args.join ' '}" if Rake.application.options.trace + ::RDoc::RDoc.new.document args + rescue SystemExit => exit + raise "RDoc coverage is incomplete" unless exit.success? + end + + desc server_task_description + task "server" do + @before_running_rdoc.call if @before_running_rdoc + args = option_list + ["--server"] + @rdoc_files + + $stderr.puts "rdoc #{args.join ' '}" if Rake.application.options.trace + ::RDoc::RDoc.new.document args + end end - end - self - end + self + end - ## - # List of options that will be supplied to RDoc - - def option_list - result = @options.dup - result << "-o" << @rdoc_dir - result << "--main" << main if main - result << "--markup" << markup if markup - result << "--title" << title if title - result << "-T" << template if template - result << '-f' << generator if generator - result - end + ## + # List of options that will be supplied to RDoc + + def option_list + result = @options.dup + result << "-o" << @rdoc_dir + result << "--main" << main if main + result << "--markup" << markup if markup + result << "--title" << title if title + result << "-T" << template if template + result << '-f' << generator if generator + result + end - ## - # The block passed to this method will be called just before running the - # RDoc generator. It is allowed to modify RDoc::Task attributes inside the - # block. + ## + # The block passed to this method will be called just before running the + # RDoc generator. It is allowed to modify RDoc::Task attributes inside the + # block. - def before_running_rdoc(&block) - @before_running_rdoc = block - end + def before_running_rdoc(&block) + @before_running_rdoc = block + end - ## - # Task description for the rdoc task or its renamed equivalent + ## + # Task description for the rdoc task or its renamed equivalent - def rdoc_task_description - 'Build RDoc HTML files' - end + def rdoc_task_description + 'Build RDoc HTML files' + end - ## - # Task description for the rerdoc task or its renamed description + ## + # Task description for the rerdoc task or its renamed description - def rerdoc_task_description - "Rebuild RDoc HTML files" - end + def rerdoc_task_description + "Rebuild RDoc HTML files" + end - ## - # Task description for the coverage task or its renamed description + ## + # Task description for the coverage task or its renamed description - def coverage_task_description - "Print RDoc coverage report" - end + def coverage_task_description + "Print RDoc coverage report" + end - ## - # Task description for the server task + ## + # Task description for the server task - def server_task_description - "Start a live-reloading documentation server" - end + def server_task_description + "Start a live-reloading documentation server" + end private - def rdoc_target - "#{rdoc_dir}/created.rid" - end + def rdoc_target + "#{rdoc_dir}/created.rid" + end - def rdoc_task_name - case name - when Hash then (name[:rdoc] || "rdoc").to_s - else name.to_s + def rdoc_task_name + case name + when Hash then (name[:rdoc] || "rdoc").to_s + else name.to_s + end + end + + def clobber_task_name + case name + when Hash then (name[:clobber_rdoc] || "clobber_rdoc").to_s + else "clobber_#{name}" + end end - end - def clobber_task_name - case name - when Hash then (name[:clobber_rdoc] || "clobber_rdoc").to_s - else "clobber_#{name}" + def rerdoc_task_name + case name + when Hash then (name[:rerdoc] || "rerdoc").to_s + else "re#{name}" + end end - end - def rerdoc_task_name - case name - when Hash then (name[:rerdoc] || "rerdoc").to_s - else "re#{name}" + def coverage_task_name + "coverage" end - end - def coverage_task_name - "coverage" end - end # :stopdoc: @@ -351,7 +353,7 @@ module Rake ## # For backwards compatibility - RDocTask = RDoc::Task # :nodoc: + RDocTask = ::RDoc::Task # :nodoc: end # :startdoc: diff --git a/lib/rdoc/text.rb b/lib/rdoc/text.rb index f65861868f..ea91ba7f98 100644 --- a/lib/rdoc/text.rb +++ b/lib/rdoc/text.rb @@ -5,29 +5,30 @@ require 'strscan' -## -# Methods for manipulating comment text +module RDoc + ## + # Methods for manipulating comment text -module RDoc::Text + module Text - ## - # The language for this text. This affects stripping comments - # markers. + ## + # The language for this text. This affects stripping comments + # markers. - attr_accessor :language + attr_accessor :language - ## - # Maps markup formats to classes that can parse them. If the format is - # unknown, "rdoc" format is used. + ## + # Maps markup formats to classes that can parse them. If the format is + # unknown, "rdoc" format is used. - MARKUP_FORMAT = { - 'markdown' => RDoc::Markdown, - 'rdoc' => RDoc::Markup, - 'rd' => RDoc::RD, - 'tomdoc' => RDoc::TomDoc, - } + MARKUP_FORMAT = { + 'markdown' => ::RDoc::Markdown, + 'rdoc' => ::RDoc::Markup, + 'rd' => ::RDoc::RD, + 'tomdoc' => ::RDoc::TomDoc, + } - MARKUP_FORMAT.default = RDoc::Markup + MARKUP_FORMAT.default = ::RDoc::Markup ## # Expands tab characters in +text+ to eight spaces @@ -38,7 +39,7 @@ module RDoc::Text text.each_line do |line| nil while line.gsub!(/(?:\G|\r)((?:.{8})*?)([^\t\r\n]{0,7})\t/) do r = "#{$1}#{$2}#{' ' * (8 - $2.size)}" - r = RDoc::Encoding.change_encoding r, text.encoding + r = ::RDoc::Encoding.change_encoding r, text.encoding r end @@ -48,160 +49,160 @@ module RDoc::Text expanded.join end - ## - # Flush +text+ left based on the shortest line + ## + # Flush +text+ left based on the shortest line - def flush_left(text) - indent = 9999 + def flush_left(text) + indent = 9999 - text.each_line do |line| - line_indent = line =~ /\S/ || 9999 - indent = line_indent if indent > line_indent - end + text.each_line do |line| + line_indent = line =~ /\S/ || 9999 + indent = line_indent if indent > line_indent + end - empty = '' - empty = RDoc::Encoding.change_encoding empty, text.encoding + empty = '' + empty = ::RDoc::Encoding.change_encoding empty, text.encoding - text.gsub(/^ {0,#{indent}}/, empty) - end + text.gsub(/^ {0,#{indent}}/, empty) + end - ## - # Convert a string in markup format into HTML. - # - # Requires the including class to implement #formatter + ## + # Convert a string in markup format into HTML. + # + # Requires the including class to implement #formatter - def markup(text) - if @store.options - locale = @store.options.locale - else - locale = nil - end - if locale - i18n_text = RDoc::I18n::Text.new(text) - text = i18n_text.translate(locale) + def markup(text) + if @store.options + locale = @store.options.locale + else + locale = nil + end + if locale + i18n_text = ::RDoc::I18n::Text.new(text) + text = i18n_text.translate(locale) + end + parse(text).accept formatter end - parse(text).accept formatter - end - ## - # Strips hashes, expands tabs then flushes +text+ to the left + ## + # Strips hashes, expands tabs then flushes +text+ to the left - def normalize_comment(text) - return text if text.empty? + def normalize_comment(text) + return text if text.empty? - case language - when :ruby - text = strip_hashes text - when :c - text = strip_stars text + case language + when :ruby + text = strip_hashes text + when :c + text = strip_stars text + end + text = expand_tabs text + text = flush_left text + text = strip_newlines text + text end - text = expand_tabs text - text = flush_left text - text = strip_newlines text - text - end - ## - # Normalizes +text+ then builds a RDoc::Markup::Document from it + ## + # Normalizes +text+ then builds a RDoc::Markup::Document from it - def parse(text, format = 'rdoc') - return text if RDoc::Markup::Document === text - return text.parse if RDoc::Comment === text + def parse(text, format = 'rdoc') + return text if ::RDoc::Markup::Document === text + return text.parse if ::RDoc::Comment === text - text = normalize_comment text # TODO remove, should not be necessary + text = normalize_comment text # TODO remove, should not be necessary - return RDoc::Markup::Document.new if text =~ /\A\n*\z/ + return ::RDoc::Markup::Document.new if text =~ /\A\n*\z/ - MARKUP_FORMAT[format].parse text - end + MARKUP_FORMAT[format].parse text + end - ## - # The first +limit+ characters of +text+ as HTML + ## + # The first +limit+ characters of +text+ as HTML - def snippet(text, limit = 100) - document = parse text + def snippet(text, limit = 100) + document = parse text - RDoc::Markup::ToHtmlSnippet.new(limit).convert document - end + ::RDoc::Markup::ToHtmlSnippet.new(limit).convert document + end - ## - # Strips leading # characters from +text+ + ## + # Strips leading # characters from +text+ - def strip_hashes(text) - return text if text =~ /^(?>\s*)[^\#]/ + def strip_hashes(text) + return text if text =~ /^(?>\s*)[^\#]/ - empty = '' - empty = RDoc::Encoding.change_encoding empty, text.encoding + empty = '' + empty = ::RDoc::Encoding.change_encoding empty, text.encoding - text.gsub(/^\s*(#+)/) { $1.tr '#', ' ' }.gsub(/^\s+$/, empty) - end + text.gsub(/^\s*(#+)/) { $1.tr '#', ' ' }.gsub(/^\s+$/, empty) + end - ## - # Strips leading and trailing \n characters from +text+ + ## + # Strips leading and trailing \n characters from +text+ - def strip_newlines(text) - text.gsub(/\A\n*(.*?)\n*\z/m) do $1 end # block preserves String encoding - end + def strip_newlines(text) + text.gsub(/\A\n*(.*?)\n*\z/m) do $1 end # block preserves String encoding + end - ## - # Strips /* */ style comments + ## + # Strips /* */ style comments - def strip_stars(text) - return text unless text =~ %r%/\*.*\*/%m + def strip_stars(text) + return text unless text =~ %r%/\*.*\*/%m - encoding = text.encoding + encoding = text.encoding - text = text.gsub %r%Document-method:\s+[\w:.#=!?|^&<>~+\-/*\%@`\[\]]+%, '' + text = text.gsub %r%Document-method:\s+[\w:.#=!?|^&<>~+\-/*\%@`\[\]]+%, '' - space = ' ' - space = RDoc::Encoding.change_encoding space, encoding if encoding + space = ' ' + space = ::RDoc::Encoding.change_encoding space, encoding if encoding - text.sub! %r%/\*+% do space * $&.length end - text.sub! %r%\*+/% do space * $&.length end - text.gsub! %r%^[ \t]*\*%m do space * $&.length end + text.sub! %r%/\*+% do space * $&.length end + text.sub! %r%\*+/% do space * $&.length end + text.gsub! %r%^[ \t]*\*%m do space * $&.length end - empty = '' - empty = RDoc::Encoding.change_encoding empty, encoding if encoding - text.gsub(/^\s+$/, empty) - end + empty = '' + empty = ::RDoc::Encoding.change_encoding empty, encoding if encoding + text.gsub(/^\s+$/, empty) + end - ## - # Wraps +txt+ to +line_len+ - - def wrap(txt, line_len = 76) - res = [] - sp = 0 - ep = txt.length - - while sp < ep - # scan back for a space - p = sp + line_len - 1 - if p >= ep - p = ep - else - while p > sp and txt[p] != ?\s - p -= 1 - end - if p <= sp - p = sp + line_len - while p < ep and txt[p] != ?\s - p += 1 + ## + # Wraps +txt+ to +line_len+ + + def wrap(txt, line_len = 76) + res = [] + sp = 0 + ep = txt.length + + while sp < ep + # scan back for a space + p = sp + line_len - 1 + if p >= ep + p = ep + else + while p > sp and txt[p] != ?\s + p -= 1 + end + if p <= sp + p = sp + line_len + while p < ep and txt[p] != ?\s + p += 1 + end end end + res << txt[sp...p] << "\n" + sp = p + sp += 1 while sp < ep and txt[sp] == ?\s end - res << txt[sp...p] << "\n" - sp = p - sp += 1 while sp < ep and txt[sp] == ?\s + + res.join.strip end - res.join.strip - end + ## + # Character class to be separated by a space when concatenating + # lines. - ## - # Character class to be separated by a space when concatenating - # lines. - - SPACE_SEPARATED_LETTER_CLASS = /[\p{Nd}\p{Lc}\p{Pc}]|[!-~&&\W]/ + SPACE_SEPARATED_LETTER_CLASS = /[\p{Nd}\p{Lc}\p{Pc}]|[!-~&&\W]/ ## # Converts +text+ to a GitHub-style anchor ID: @@ -241,4 +242,5 @@ def wrap(txt, line_len = 76) label end + end end diff --git a/lib/rdoc/token_stream.rb b/lib/rdoc/token_stream.rb index 5541d37e8b..a3411d83f0 100644 --- a/lib/rdoc/token_stream.rb +++ b/lib/rdoc/token_stream.rb @@ -1,104 +1,106 @@ # frozen_string_literal: true -## -# A TokenStream is a list of tokens, gathered during the parse of some entity -# (say a method). Entities populate these streams by being registered with the -# lexer. Any class can collect tokens by including TokenStream. From the -# outside, you use such an object by calling the start_collecting_tokens -# method, followed by calls to add_token and pop_token. - -module RDoc::TokenStream - - ## - # Converts +token_stream+ to HTML wrapping various tokens with - # elements. Some tokens types are wrapped in spans - # with the given class names. Other token types are not wrapped in spans. - - def self.to_html(token_stream) - token_stream.map do |t| - next unless t - - style = case t[:kind] - when :operator then 'ruby-operator' - when :keyword then 'ruby-keyword' - when :constant then 'ruby-constant' - when :ivar then 'ruby-ivar' - when :comment then 'ruby-comment' - when :value then 'ruby-value' - when :string then 'ruby-string' - when :symbol then 'ruby-value' - when :x_string then 'ruby-string' - when :regexp then 'ruby-regexp' - when :identifier then 'ruby-identifier' - end - - text = t[:text] - - text = CGI.escapeHTML text - - if style - end_with_newline = text.end_with?("\n") - text = text.chomp if end_with_newline - "#{text}#{"\n" if end_with_newline}" - else - text - end - end.join - end - - ## - # Adds +tokens+ to the collected tokens - - def add_tokens(tokens) - @token_stream.concat(tokens) - end - - ## - # Adds one +token+ to the collected tokens - - def add_token(token) - @token_stream.push(token) - end - +module RDoc ## - # Starts collecting tokens - # + # A TokenStream is a list of tokens, gathered during the parse of some entity + # (say a method). Entities populate these streams by being registered with the + # lexer. Any class can collect tokens by including TokenStream. From the + # outside, you use such an object by calling the start_collecting_tokens + # method, followed by calls to add_token and pop_token. + + module TokenStream + + ## + # Converts +token_stream+ to HTML wrapping various tokens with + # elements. Some tokens types are wrapped in spans + # with the given class names. Other token types are not wrapped in spans. + + def self.to_html(token_stream) + token_stream.map do |t| + next unless t + + style = case t[:kind] + when :operator then 'ruby-operator' + when :keyword then 'ruby-keyword' + when :constant then 'ruby-constant' + when :ivar then 'ruby-ivar' + when :comment then 'ruby-comment' + when :value then 'ruby-value' + when :string then 'ruby-string' + when :symbol then 'ruby-value' + when :x_string then 'ruby-string' + when :regexp then 'ruby-regexp' + when :identifier then 'ruby-identifier' + end + + text = t[:text] + + text = CGI.escapeHTML text + + if style + end_with_newline = text.end_with?("\n") + text = text.chomp if end_with_newline + "#{text}#{"\n" if end_with_newline}" + else + text + end + end.join + end + + ## + # Adds +tokens+ to the collected tokens + + def add_tokens(tokens) + @token_stream.concat(tokens) + end + + ## + # Adds one +token+ to the collected tokens + + def add_token(token) + @token_stream.push(token) + end + + ## + # Starts collecting tokens + # + + def collect_tokens(language) + @token_stream = [] + @token_stream_language = language + end + + alias start_collecting_tokens collect_tokens + + ## + # Remove the last token from the collected tokens + + def pop_token + @token_stream.pop + end + + ## + # Current token stream + + def token_stream + @token_stream + end + + ## + # Returns a string representation of the token stream + + def tokens_to_s + (token_stream or return '').compact.map { |token| token[:text] }.join '' + end + + ## + # Returns the source language of the token stream as a string + # + # Returns 'c' or 'ruby' + + def source_language + @token_stream_language == :c ? 'c' : 'ruby' + end - def collect_tokens(language) - @token_stream = [] - @token_stream_language = language end - - alias start_collecting_tokens collect_tokens - - ## - # Remove the last token from the collected tokens - - def pop_token - @token_stream.pop - end - - ## - # Current token stream - - def token_stream - @token_stream - end - - ## - # Returns a string representation of the token stream - - def tokens_to_s - (token_stream or return '').compact.map { |token| token[:text] }.join '' - end - - ## - # Returns the source language of the token stream as a string - # - # Returns 'c' or 'ruby' - - def source_language - @token_stream_language == :c ? 'c' : 'ruby' - end - end diff --git a/lib/rdoc/tom_doc.rb b/lib/rdoc/tom_doc.rb index 9f085cde15..ea694bbb2c 100644 --- a/lib/rdoc/tom_doc.rb +++ b/lib/rdoc/tom_doc.rb @@ -1,257 +1,259 @@ # frozen_string_literal: true # :markup: tomdoc -# A parser for TomDoc based on TomDoc 1.0.0-rc1 (02adef9b5a) -# -# The TomDoc specification can be found at http://tomdoc.org. -# -# To choose TomDoc as your only default format see RDoc::Options@Saved+Options -# for instructions on setting up a .rdoc_options file to store -# your project default. -# -# There are a few differences between this parser and the specification. A -# best-effort was made to follow the specification as closely as possible but -# some choices to deviate were made. -# -# A future version of RDoc will warn when a MUST or MUST NOT is violated and -# may warn when a SHOULD or SHOULD NOT is violated. RDoc will always try -# to emit documentation even if given invalid TomDoc. -# -# Here are some implementation choices this parser currently makes: -# -# This parser allows rdoc-style inline markup but you should not depended on -# it. -# -# This parser allows a space between the comment and the method body. -# -# This parser does not require the default value to be described for an -# optional argument. -# -# This parser does not examine the order of sections. An Examples section may -# precede the Arguments section. -# -# This class is documented in TomDoc format. Since this is a subclass of the -# RDoc markup parser there isn't much to see here, unfortunately. - -class RDoc::TomDoc < RDoc::Markup::Parser - - # Internal: Token accessor - - attr_reader :tokens - - # Internal: Adds a post-processor which sets the RDoc section based on the - # comment's status. +module RDoc + # A parser for TomDoc based on TomDoc 1.0.0-rc1 (02adef9b5a) # - # Returns nothing. - - def self.add_post_processor # :nodoc: - RDoc::Markup::PreProcess.post_process do |comment, code_object| - next unless code_object and - RDoc::Comment === comment and comment.format == 'tomdoc' - - comment.text.gsub!(/\A(\s*# |)(Public|Internal|Deprecated):\s+/) do - section = code_object.add_section $2 - code_object.temporary_section = section - - $1 - end - end - end - - add_post_processor - - # Public: Parses TomDoc from text + # The TomDoc specification can be found at http://tomdoc.org. # - # text - A String containing TomDoc-format text. + # To choose TomDoc as your only default format see RDoc::Options@Saved+Options + # for instructions on setting up a .rdoc_options file to store + # your project default. # - # Examples + # There are a few differences between this parser and the specification. A + # best-effort was made to follow the specification as closely as possible but + # some choices to deviate were made. # - # RDoc::TomDoc.parse <<-TOMDOC - # This method does some things + # A future version of RDoc will warn when a MUST or MUST NOT is violated and + # may warn when a SHOULD or SHOULD NOT is violated. RDoc will always try + # to emit documentation even if given invalid TomDoc. # - # Returns nothing. - # TOMDOC - # # => # + # Here are some implementation choices this parser currently makes: # - # Returns an RDoc::Markup::Document representing the TomDoc format. - - def self.parse(text) - parser = new - - parser.tokenize text - doc = RDoc::Markup::Document.new - parser.parse doc - doc - end - - # Internal: Extracts the Signature section's method signature + # This parser allows rdoc-style inline markup but you should not depended on + # it. # - # comment - An RDoc::Comment that will be parsed and have the signature - # extracted + # This parser allows a space between the comment and the method body. # - # Returns a String containing the signature and nil if not - - def self.signature(comment) - return unless comment.tomdoc? + # This parser does not require the default value to be described for an + # optional argument. + # + # This parser does not examine the order of sections. An Examples section may + # precede the Arguments section. + # + # This class is documented in TomDoc format. Since this is a subclass of the + # RDoc markup parser there isn't much to see here, unfortunately. - document = comment.parse + class TomDoc < ::RDoc::Markup::Parser - signature = nil - found_heading = false - found_signature = false + # Internal: Token accessor - document.parts.delete_if do |part| - next false if found_signature + attr_reader :tokens - found_heading ||= - RDoc::Markup::Heading === part && part.text == 'Signature' + # Internal: Adds a post-processor which sets the RDoc section based on the + # comment's status. + # + # Returns nothing. - next false unless found_heading + def self.add_post_processor # :nodoc: + ::RDoc::Markup::PreProcess.post_process do |comment, code_object| + next unless code_object and + ::RDoc::Comment === comment and comment.format == 'tomdoc' - next true if RDoc::Markup::BlankLine === part + comment.text.gsub!(/\A(\s*# |)(Public|Internal|Deprecated):\s+/) do + section = code_object.add_section $2 + code_object.temporary_section = section - if RDoc::Markup::Verbatim === part - signature = part - found_signature = true + $1 + end end end - signature and signature.text - end + add_post_processor + + # Public: Parses TomDoc from text + # + # text - A String containing TomDoc-format text. + # + # Examples + # + # RDoc::TomDoc.parse <<-TOMDOC + # This method does some things + # + # Returns nothing. + # TOMDOC + # # => # + # + # Returns an RDoc::Markup::Document representing the TomDoc format. + + def self.parse(text) + parser = new + + parser.tokenize text + doc = ::RDoc::Markup::Document.new + parser.parse doc + doc + end - # Public: Creates a new TomDoc parser. See also RDoc::Markup::parse + # Internal: Extracts the Signature section's method signature + # + # comment - An RDoc::Comment that will be parsed and have the signature + # extracted + # + # Returns a String containing the signature and nil if not - def initialize - super + def self.signature(comment) + return unless comment.tomdoc? - @section = nil - @seen_returns = false - end + document = comment.parse - # Internal: Builds a heading from the token stream - # - # level - The level of heading to create - # - # Returns an RDoc::Markup::Heading + signature = nil + found_heading = false + found_signature = false - def build_heading(level) - heading = super + document.parts.delete_if do |part| + next false if found_signature - @section = heading.text + found_heading ||= + ::RDoc::Markup::Heading === part && part.text == 'Signature' - heading - end + next false unless found_heading - # Internal: Builds a verbatim from the token stream. A verbatim in the - # Examples section will be marked as in Ruby format. - # - # margin - The indentation from the margin for lines that belong to this - # verbatim section. - # - # Returns an RDoc::Markup::Verbatim + next true if ::RDoc::Markup::BlankLine === part + + if ::RDoc::Markup::Verbatim === part + signature = part + found_signature = true + end + end - def build_verbatim(margin) - verbatim = super + signature and signature.text + end - verbatim.format = :ruby if @section == 'Examples' + # Public: Creates a new TomDoc parser. See also RDoc::Markup::parse - verbatim - end + def initialize + super - # Internal: Builds a paragraph from the token stream - # - # margin - Unused - # - # Returns an RDoc::Markup::Paragraph. + @section = nil + @seen_returns = false + end - def build_paragraph(margin) - p :paragraph_start => margin if @debug + # Internal: Builds a heading from the token stream + # + # level - The level of heading to create + # + # Returns an RDoc::Markup::Heading - paragraph = RDoc::Markup::Paragraph.new + def build_heading(level) + heading = super - until @tokens.empty? do - type, data, = get + @section = heading.text - case type - when :TEXT - @section = 'Returns' if data =~ /\A(Returns|Raises)/ + heading + end - paragraph << data - when :NEWLINE - if :TEXT == peek_token[0] - # Lines beginning with 'Raises' in the Returns section should not be - # treated as multiline text - if 'Returns' == @section and - peek_token[1].start_with?('Raises') - break + # Internal: Builds a verbatim from the token stream. A verbatim in the + # Examples section will be marked as in Ruby format. + # + # margin - The indentation from the margin for lines that belong to this + # verbatim section. + # + # Returns an RDoc::Markup::Verbatim + + def build_verbatim(margin) + verbatim = super + + verbatim.format = :ruby if @section == 'Examples' + + verbatim + end + + # Internal: Builds a paragraph from the token stream + # + # margin - Unused + # + # Returns an RDoc::Markup::Paragraph. + + def build_paragraph(margin) + p :paragraph_start => margin if @debug + + paragraph = ::RDoc::Markup::Paragraph.new + + until @tokens.empty? do + type, data, = get + + case type + when :TEXT + @section = 'Returns' if data =~ /\A(Returns|Raises)/ + + paragraph << data + when :NEWLINE + if :TEXT == peek_token[0] + # Lines beginning with 'Raises' in the Returns section should not be + # treated as multiline text + if 'Returns' == @section and + peek_token[1].start_with?('Raises') + break + else + paragraph << ' ' + end else - paragraph << ' ' + break end else + unget break end - else - unget - break end - end - p :paragraph_end => margin if @debug + p :paragraph_end => margin if @debug - paragraph - end + paragraph + end + + ## + # Detects a section change to "Returns" and adds a heading - ## - # Detects a section change to "Returns" and adds a heading + def parse_text(parent, indent) # :nodoc: + paragraph = build_paragraph indent - def parse_text(parent, indent) # :nodoc: - paragraph = build_paragraph indent + if false == @seen_returns and 'Returns' == @section + @seen_returns = true + parent << ::RDoc::Markup::Heading.new(3, 'Returns') + parent << ::RDoc::Markup::BlankLine.new + end - if false == @seen_returns and 'Returns' == @section - @seen_returns = true - parent << RDoc::Markup::Heading.new(3, 'Returns') - parent << RDoc::Markup::BlankLine.new + parent << paragraph end - parent << paragraph - end + # Internal: Turns text into an Array of tokens + # + # text - A String containing TomDoc-format text. + # + # Returns self. + + def tokenize(text) + text = text.sub(/\A(Public|Internal|Deprecated):\s+/, '') + + setup_scanner text + + until @s.eos? do + pos = @s.pos + + # leading spaces will be reflected by the column of the next token + # the only thing we loose are trailing spaces at the end of the file + next if @s.scan(/ +/) + + @tokens << case + when @s.scan(/\r?\n/) + token = [:NEWLINE, @s.matched, *pos] + @s.newline! + token + when @s.scan(/(Examples|Signature)$/) + @tokens << [:HEADER, 3, *pos] + + [:TEXT, @s[1], *pos] + when @s.scan(/([:\w][\w\[\]]*)[ ]+- /) + [:NOTE, @s[1], *pos] + else + @s.scan(/.*/) + [:TEXT, @s.matched.sub(/\r$/, ''), *pos] + end + end - # Internal: Turns text into an Array of tokens - # - # text - A String containing TomDoc-format text. - # - # Returns self. - - def tokenize(text) - text = text.sub(/\A(Public|Internal|Deprecated):\s+/, '') - - setup_scanner text - - until @s.eos? do - pos = @s.pos - - # leading spaces will be reflected by the column of the next token - # the only thing we loose are trailing spaces at the end of the file - next if @s.scan(/ +/) - - @tokens << case - when @s.scan(/\r?\n/) - token = [:NEWLINE, @s.matched, *pos] - @s.newline! - token - when @s.scan(/(Examples|Signature)$/) - @tokens << [:HEADER, 3, *pos] - - [:TEXT, @s[1], *pos] - when @s.scan(/([:\w][\w\[\]]*)[ ]+- /) - [:NOTE, @s[1], *pos] - else - @s.scan(/.*/) - [:TEXT, @s.matched.sub(/\r$/, ''), *pos] - end + self end - self end - end diff --git a/test/rdoc/markup/pre_process_test.rb b/test/rdoc/markup/pre_process_test.rb index d8367a4c04..7cdba56fac 100644 --- a/test/rdoc/markup/pre_process_test.rb +++ b/test/rdoc/markup/pre_process_test.rb @@ -2,472 +2,476 @@ require_relative '../helper' -class RDoc::Markup::PreProcessTest < RDoc::TestCase +module RDoc + class Markup + class PreProcessTest < ::RDoc::TestCase - def setup - super + def setup + super - @tempfile = Tempfile.new 'test_rdoc_markup_pre_process' - @file_name = File.basename @tempfile.path - @dir = File.dirname @tempfile.path + @tempfile = Tempfile.new 'test_rdoc_markup_pre_process' + @file_name = File.basename @tempfile.path + @dir = File.dirname @tempfile.path - @pp = RDoc::Markup::PreProcess.new @tempfile.path, [@dir, File.expand_path('..', File.dirname(__FILE__))] - end + @pp = ::RDoc::Markup::PreProcess.new @tempfile.path, [@dir, File.expand_path('..', File.dirname(__FILE__))] + end - def teardown - super + def teardown + super - @tempfile.close! - end + @tempfile.close! + end - def test_class_register - RDoc::Markup::PreProcess.register 'for_test' do raise 'fail' end + def test_class_register + ::RDoc::Markup::PreProcess.register 'for_test' do raise 'fail' end - assert_equal %w[for_test], RDoc::Markup::PreProcess.registered.keys - end + assert_equal %w[for_test], ::RDoc::Markup::PreProcess.registered.keys + end - def test_class_post_process - RDoc::Markup::PreProcess.post_process do end + def test_class_post_process + ::RDoc::Markup::PreProcess.post_process do end - assert_equal 1, RDoc::Markup::PreProcess.post_processors.length, - proc{RDoc::Markup::PreProcess.post_processors.inspect} - end + assert_equal 1, ::RDoc::Markup::PreProcess.post_processors.length, + proc{::RDoc::Markup::PreProcess.post_processors.inspect} + end - def test_include_file - @tempfile.write <<-INCLUDE + def test_include_file + @tempfile.write <<-INCLUDE # -*- mode: rdoc; coding: utf-8; fill-column: 74; -*- Regular expressions (regexps) are patterns which describe the contents of a string. INCLUDE - @tempfile.flush - @tempfile.rewind + @tempfile.flush + @tempfile.rewind - content = @pp.include_file @file_name, '', nil + content = @pp.include_file @file_name, '', nil - expected = <<-EXPECTED + expected = <<-EXPECTED Regular expressions (regexps) are patterns which describe the contents of a string. EXPECTED - assert_equal expected, content - end + assert_equal expected, content + end - def test_include_file_encoding_incompatible - @tempfile.write <<-INCLUDE + def test_include_file_encoding_incompatible + @tempfile.write <<-INCLUDE # -*- mode: rdoc; coding: utf-8; fill-column: 74; -*- π INCLUDE - @tempfile.flush - @tempfile.rewind + @tempfile.flush + @tempfile.rewind - content = @pp.include_file @file_name, '', Encoding::US_ASCII + content = @pp.include_file @file_name, '', ::Encoding::US_ASCII - expected = "?\n" + expected = "?\n" - assert_equal expected, content - end + assert_equal expected, content + end - def test_include_file_in_other_directory - content = nil - out, err = capture_output do - content = @pp.include_file "test.txt", '', nil - end + def test_include_file_in_other_directory + content = nil + out, err = capture_output do + content = @pp.include_file "test.txt", '', nil + end - assert_empty out - assert_empty err + assert_empty out + assert_empty err - assert_equal "test file\n", content - end + assert_equal "test file\n", content + end - def test_handle - text = "# :stopdoc:\n" - code_object = RDoc::CodeObject.new - output = @pp.handle text, code_object + def test_handle + text = "# :stopdoc:\n" + code_object = ::RDoc::CodeObject.new + output = @pp.handle text, code_object - assert_equal "#\n", output - end + assert_equal "#\n", output + end - def test_handle_comment - text = "# :stopdoc:\n" - c = comment text - code_object = RDoc::CodeObject.new + def test_handle_comment + text = "# :stopdoc:\n" + c = comment text + code_object = ::RDoc::CodeObject.new - output = @pp.handle c, code_object + output = @pp.handle c, code_object - assert_equal "#\n", output - end + assert_equal "#\n", output + end - def test_handle_markup - c = comment ':markup: rd' + def test_handle_markup + c = comment ':markup: rd' - @pp.handle c + @pp.handle c - assert_equal 'rd', c.format - end + assert_equal 'rd', c.format + end - def test_handle_markup_empty - c = comment ':markup:' + def test_handle_markup_empty + c = comment ':markup:' - @pp.handle c + @pp.handle c - assert_equal 'rdoc', c.format - end + assert_equal 'rdoc', c.format + end - def test_handle_post_process - cd = RDoc::CodeObject.new + def test_handle_post_process + cd = ::RDoc::CodeObject.new - RDoc::Markup::PreProcess.post_process do |text, code_object| - code_object.metadata[:stuff] = text + ::RDoc::Markup::PreProcess.post_process do |text, code_object| + code_object.metadata[:stuff] = text - :junk - end + :junk + end - text = "# a b c\n" + text = "# a b c\n" - out = @pp.handle text, cd + out = @pp.handle text, cd - assert_equal "# a b c\n", out - assert_equal "# a b c\n", cd.metadata[:stuff] - end + assert_equal "# a b c\n", out + assert_equal "# a b c\n", cd.metadata[:stuff] + end - def test_handle_unregistered - text = "# :x: y\n" - out = @pp.handle text + def test_handle_unregistered + text = "# :x: y\n" + out = @pp.handle text - assert_equal text, out - end + assert_equal text, out + end - def test_handle_directive_blankline - result = @pp.handle_directive '#', 'arg', 'a, b' + def test_handle_directive_blankline + result = @pp.handle_directive '#', 'arg', 'a, b' - assert_equal "#:arg: a, b\n", result - end + assert_equal "#:arg: a, b\n", result + end - def test_handle_directive_downcase - method = RDoc::AnyMethod.new 'm' + def test_handle_directive_downcase + method = ::RDoc::AnyMethod.new 'm' - @pp.handle_directive '', 'ARG', 'a, b', method + @pp.handle_directive '', 'ARG', 'a, b', method - assert_equal 'a, b', method.params - end + assert_equal 'a, b', method.params + end - def test_handle_directive_arg - method = RDoc::AnyMethod.new 'm' + def test_handle_directive_arg + method = ::RDoc::AnyMethod.new 'm' - @pp.handle_directive '', 'arg', 'a, b', method + @pp.handle_directive '', 'arg', 'a, b', method - assert_equal 'a, b', method.params - end + assert_equal 'a, b', method.params + end - def test_handle_directive_arg_no_context - result = @pp.handle_directive '', 'arg', 'a, b', nil + def test_handle_directive_arg_no_context + result = @pp.handle_directive '', 'arg', 'a, b', nil - assert_equal ":arg: a, b\n", result - end + assert_equal ":arg: a, b\n", result + end - def test_handle_directive_args - method = RDoc::AnyMethod.new 'm' + def test_handle_directive_args + method = ::RDoc::AnyMethod.new 'm' - @pp.handle_directive '', 'args', 'a, b', method + @pp.handle_directive '', 'args', 'a, b', method - assert_equal 'a, b', method.params - end + assert_equal 'a, b', method.params + end - def test_handle_directive_block - result = @pp.handle_directive '', 'x', 'y' do |directive, param| - '' - end + def test_handle_directive_block + result = @pp.handle_directive '', 'x', 'y' do |directive, param| + '' + end - assert_empty result - end + assert_empty result + end - def test_handle_directive_block_false - result = @pp.handle_directive '', 'x', 'y' do |directive, param| - false - end + def test_handle_directive_block_false + result = @pp.handle_directive '', 'x', 'y' do |directive, param| + false + end - assert_equal ":x: y\n", result - end + assert_equal ":x: y\n", result + end - def test_handle_directive_block_nil - result = @pp.handle_directive '', 'x', 'y' do |directive, param| - nil - end + def test_handle_directive_block_nil + result = @pp.handle_directive '', 'x', 'y' do |directive, param| + nil + end - assert_equal ":x: y\n", result - end + assert_equal ":x: y\n", result + end - def test_handle_directive_category - context = RDoc::Context.new - original_section = context.current_section + def test_handle_directive_category + context = ::RDoc::Context.new + original_section = context.current_section - @pp.handle_directive '', 'category', 'other', context + @pp.handle_directive '', 'category', 'other', context - refute_equal original_section, context.current_section - end + refute_equal original_section, context.current_section + end - def test_handle_directive_doc - code_object = RDoc::CodeObject.new - code_object.document_self = false - code_object.force_documentation = false + def test_handle_directive_doc + code_object = ::RDoc::CodeObject.new + code_object.document_self = false + code_object.force_documentation = false - @pp.handle_directive '', 'doc', nil, code_object + @pp.handle_directive '', 'doc', nil, code_object - assert code_object.document_self - assert code_object.force_documentation - end + assert code_object.document_self + assert code_object.force_documentation + end - def test_handle_directive_doc_no_context - result = @pp.handle_directive '', 'doc', nil + def test_handle_directive_doc_no_context + result = @pp.handle_directive '', 'doc', nil - assert_equal "\n", result - end + assert_equal "\n", result + end - def test_handle_directive_enddoc - code_object = RDoc::CodeObject.new + def test_handle_directive_enddoc + code_object = ::RDoc::CodeObject.new - @pp.handle_directive '', 'enddoc', nil, code_object + @pp.handle_directive '', 'enddoc', nil, code_object - assert code_object.done_documenting - end + assert code_object.done_documenting + end - def test_handle_directive_include - @tempfile.write 'included' - @tempfile.flush + def test_handle_directive_include + @tempfile.write 'included' + @tempfile.flush - result = @pp.handle_directive '', 'include', @file_name + result = @pp.handle_directive '', 'include', @file_name - assert_equal 'included', result - end + assert_equal 'included', result + end - def test_handle_directive_main - @pp.options = RDoc::Options.new + def test_handle_directive_main + @pp.options = ::RDoc::Options.new - # :main: directive was removed; it is now treated as unhandled metadata - code_object = RDoc::CodeObject.new - @pp.handle_directive '', 'main', 'M', code_object + # :main: directive was removed; it is now treated as unhandled metadata + code_object = ::RDoc::CodeObject.new + @pp.handle_directive '', 'main', 'M', code_object - assert_equal 'M', code_object.metadata['main'] - end + assert_equal 'M', code_object.metadata['main'] + end - def test_handle_directive_notnew - m = RDoc::AnyMethod.new 'm' + def test_handle_directive_notnew + m = ::RDoc::AnyMethod.new 'm' - @pp.handle_directive '', 'notnew', nil, m + @pp.handle_directive '', 'notnew', nil, m - assert m.dont_rename_initialize - end + assert m.dont_rename_initialize + end - def test_handle_directive_not_new - m = RDoc::AnyMethod.new 'm' + def test_handle_directive_not_new + m = ::RDoc::AnyMethod.new 'm' - @pp.handle_directive '', 'not_new', nil, m + @pp.handle_directive '', 'not_new', nil, m - assert m.dont_rename_initialize - end + assert m.dont_rename_initialize + end - def test_handle_directive_not_dash_new - m = RDoc::AnyMethod.new 'm' + def test_handle_directive_not_dash_new + m = ::RDoc::AnyMethod.new 'm' - @pp.handle_directive '', 'not-new', nil, m + @pp.handle_directive '', 'not-new', nil, m - assert m.dont_rename_initialize - end + assert m.dont_rename_initialize + end - def test_handle_directive_nodoc - code_object = RDoc::CodeObject.new - code_object.document_self = true - code_object.document_children = true + def test_handle_directive_nodoc + code_object = ::RDoc::CodeObject.new + code_object.document_self = true + code_object.document_children = true - @pp.handle_directive '', 'nodoc', nil, code_object + @pp.handle_directive '', 'nodoc', nil, code_object - refute code_object.document_self - assert code_object.document_children - end + refute code_object.document_self + assert code_object.document_children + end - def test_handle_directive_nodoc_all - code_object = RDoc::CodeObject.new - code_object.document_self = true - code_object.document_children = true + def test_handle_directive_nodoc_all + code_object = ::RDoc::CodeObject.new + code_object.document_self = true + code_object.document_children = true - @pp.handle_directive '', 'nodoc', 'all', code_object + @pp.handle_directive '', 'nodoc', 'all', code_object - refute code_object.document_self - refute code_object.document_children - end + refute code_object.document_self + refute code_object.document_children + end - def test_handle_directive_nodoc_no_context - result = @pp.handle_directive '', 'nodoc', nil + def test_handle_directive_nodoc_no_context + result = @pp.handle_directive '', 'nodoc', nil - assert_equal "\n", result - end + assert_equal "\n", result + end - def test_handle_directive_registered - RDoc::Markup::PreProcess.register 'x' + def test_handle_directive_registered + ::RDoc::Markup::PreProcess.register 'x' - result = @pp.handle_directive '', 'x', 'y' + result = @pp.handle_directive '', 'x', 'y' - assert_nil result + assert_nil result - result = @pp.handle_directive '', 'x', 'y' do |directive, param| - false - end + result = @pp.handle_directive '', 'x', 'y' do |directive, param| + false + end - assert_equal ":x: y\n", result + assert_equal ":x: y\n", result - result = @pp.handle_directive '', 'x', 'y' do |directive, param| - '' - end + result = @pp.handle_directive '', 'x', 'y' do |directive, param| + '' + end - assert_equal '', result - end + assert_equal '', result + end - def test_handle_directive_registered_block - called = nil + def test_handle_directive_registered_block + called = nil - RDoc::Markup::PreProcess.register 'x' do |directive, param| - called = [directive, param] - 'blah' - end + ::RDoc::Markup::PreProcess.register 'x' do |directive, param| + called = [directive, param] + 'blah' + end - result = @pp.handle_directive '', 'x', 'y' + result = @pp.handle_directive '', 'x', 'y' - assert_equal 'blah', result - assert_equal %w[x y], called - end + assert_equal 'blah', result + assert_equal %w[x y], called + end - def test_handle_directive_registered_code_object - RDoc::Markup::PreProcess.register 'x' - code_object = RDoc::CodeObject.new + def test_handle_directive_registered_code_object + ::RDoc::Markup::PreProcess.register 'x' + code_object = ::RDoc::CodeObject.new - @pp.handle_directive '', 'x', 'y', code_object + @pp.handle_directive '', 'x', 'y', code_object - assert_equal 'y', code_object.metadata['x'] + assert_equal 'y', code_object.metadata['x'] - code_object.metadata.clear + code_object.metadata.clear - result = @pp.handle_directive '', 'x', 'y' do |directive, param| - false - end + result = @pp.handle_directive '', 'x', 'y' do |directive, param| + false + end - assert_equal ":x: y\n", result - assert_empty code_object.metadata + assert_equal ":x: y\n", result + assert_empty code_object.metadata - result = @pp.handle_directive '', 'x', 'y' do |directive, param| - '' - end + result = @pp.handle_directive '', 'x', 'y' do |directive, param| + '' + end - assert_equal '', result - assert_empty code_object.metadata - end + assert_equal '', result + assert_empty code_object.metadata + end - def test_handle_directive_startdoc - code_object = RDoc::CodeObject.new - code_object.stop_doc - code_object.force_documentation = false + def test_handle_directive_startdoc + code_object = ::RDoc::CodeObject.new + code_object.stop_doc + code_object.force_documentation = false - @pp.handle_directive '', 'startdoc', nil, code_object + @pp.handle_directive '', 'startdoc', nil, code_object - assert code_object.document_self - assert code_object.document_children - assert code_object.force_documentation - end + assert code_object.document_self + assert code_object.document_children + assert code_object.force_documentation + end - def test_handle_directive_stopdoc - code_object = RDoc::CodeObject.new + def test_handle_directive_stopdoc + code_object = ::RDoc::CodeObject.new - @pp.handle_directive '', 'stopdoc', nil, code_object + @pp.handle_directive '', 'stopdoc', nil, code_object - refute code_object.document_self - refute code_object.document_children - end + refute code_object.document_self + refute code_object.document_children + end - def test_handle_directive_title - @pp.options = RDoc::Options.new + def test_handle_directive_title + @pp.options = ::RDoc::Options.new - # :title: directive was removed; it is now treated as unhandled metadata - code_object = RDoc::CodeObject.new - @pp.handle_directive '', 'title', 'T', code_object + # :title: directive was removed; it is now treated as unhandled metadata + code_object = ::RDoc::CodeObject.new + @pp.handle_directive '', 'title', 'T', code_object - assert_equal 'T', code_object.metadata['title'] - end + assert_equal 'T', code_object.metadata['title'] + end - def test_handle_directive_unhandled - code_object = RDoc::CodeObject.new + def test_handle_directive_unhandled + code_object = ::RDoc::CodeObject.new - @pp.handle_directive '', 'x', 'y', code_object + @pp.handle_directive '', 'x', 'y', code_object - assert_equal 'y', code_object.metadata['x'] + assert_equal 'y', code_object.metadata['x'] - code_object.metadata.clear + code_object.metadata.clear - @pp.handle_directive '', 'x', '', code_object + @pp.handle_directive '', 'x', '', code_object - assert_includes code_object.metadata, 'x' - end + assert_includes code_object.metadata, 'x' + end - def test_handle_directive_unhandled_block - code_object = RDoc::CodeObject.new + def test_handle_directive_unhandled_block + code_object = ::RDoc::CodeObject.new - @pp.handle_directive '', 'x', 'y', code_object do - false - end + @pp.handle_directive '', 'x', 'y', code_object do + false + end - assert_empty code_object.metadata + assert_empty code_object.metadata - @pp.handle_directive '', 'x', 'y', code_object do - nil - end + @pp.handle_directive '', 'x', 'y', code_object do + nil + end - assert_equal 'y', code_object.metadata['x'] + assert_equal 'y', code_object.metadata['x'] - code_object.metadata.clear + code_object.metadata.clear - @pp.handle_directive '', 'x', 'y', code_object do - '' - end + @pp.handle_directive '', 'x', 'y', code_object do + '' + end - assert_empty code_object.metadata - end + assert_empty code_object.metadata + end - def test_handle_directive_yield - method = RDoc::AnyMethod.new 'm' - method.params = 'index, &block' + def test_handle_directive_yield + method = ::RDoc::AnyMethod.new 'm' + method.params = 'index, &block' - @pp.handle_directive '', 'yield', 'item', method + @pp.handle_directive '', 'yield', 'item', method - assert_equal 'item', method.block_params - assert_equal 'index', method.params - end + assert_equal 'item', method.block_params + assert_equal 'index', method.params + end - def test_handle_directive_yield_block_param - method = RDoc::AnyMethod.new 'm' - method.params = '&block' + def test_handle_directive_yield_block_param + method = ::RDoc::AnyMethod.new 'm' + method.params = '&block' - @pp.handle_directive '', 'yield', 'item', method + @pp.handle_directive '', 'yield', 'item', method - assert_equal 'item', method.block_params - assert_empty method.params - end + assert_equal 'item', method.block_params + assert_empty method.params + end - def test_handle_directive_yield_no_context - method = RDoc::AnyMethod.new 'm' + def test_handle_directive_yield_no_context + method = ::RDoc::AnyMethod.new 'm' - @pp.handle_directive '', 'yield', 'item', method + @pp.handle_directive '', 'yield', 'item', method - assert_equal 'item', method.block_params - end + assert_equal 'item', method.block_params + end - def test_handle_directive_yields - method = RDoc::AnyMethod.new 'm' + def test_handle_directive_yields + method = ::RDoc::AnyMethod.new 'm' - @pp.handle_directive '', 'yields', 'item', method + @pp.handle_directive '', 'yields', 'item', method - assert_equal 'item', method.block_params - end + assert_equal 'item', method.block_params + end + end + end end diff --git a/test/rdoc/support/formatter_test_case.rb b/test/rdoc/support/formatter_test_case.rb index ae081f5188..b77b32e7c2 100644 --- a/test/rdoc/support/formatter_test_case.rb +++ b/test/rdoc/support/formatter_test_case.rb @@ -1,764 +1,768 @@ # frozen_string_literal: true require 'test/unit' -## -# Test case for creating new RDoc::Markup formatters. See -# test/test_rdoc_markup_to_*.rb for examples. -# -# This test case adds a variety of tests to your subclass when -# #add_visitor_tests is called. Most tests set up a scenario then call a -# method you will provide to perform the assertion on the output. -# -# Your subclass must instantiate a visitor and assign it to @to. -# -# For example, test_accept_blank_line sets up a RDoc::Markup::BlockLine then -# calls accept_blank_line on your visitor. You are responsible for asserting -# that the output is correct. -# -# Example: -# -# class RDocMarkupToNewFormatTest < RDoc::Markup::FormatterTestCase -# -# add_visitor_tests -# -# def setup -# super -# -# @to = RDoc::Markup::ToNewFormat.new -# end -# -# def accept_blank_line -# assert_equal :junk, @to.res.join -# end -# -# # ... -# -# end - -class RDoc::Markup::FormatterTestCase < RDoc::TestCase - - ## - # Call #setup when inheriting from this test case. - # - # Provides the following instance variables: - # - # +@m+:: RDoc::Markup.new - # +@RM+:: RDoc::Markup # to reduce typing - # +@bullet_list+:: @RM::List.new :BULLET, # ... - # +@label_list+:: @RM::List.new :LABEL, # ... - # +@lalpha_list+:: @RM::List.new :LALPHA, # ... - # +@note_list+:: @RM::List.new :NOTE, # ... - # +@number_list+:: @RM::List.new :NUMBER, # ... - # +@ualpha_list+:: @RM::List.new :UALPHA, # ... - - def setup - super - - @options = RDoc::Options.new - - @m = @RM.new - - @bullet_list = @RM::List.new(:BULLET, - @RM::ListItem.new(nil, @RM::Paragraph.new('l1')), - @RM::ListItem.new(nil, @RM::Paragraph.new('l2'))) - - @label_list = @RM::List.new(:LABEL, - @RM::ListItem.new('cat', @RM::Paragraph.new('cats are cool')), - @RM::ListItem.new('dog', @RM::Paragraph.new('dogs are cool too'))) - - @lalpha_list = @RM::List.new(:LALPHA, - @RM::ListItem.new(nil, @RM::Paragraph.new('l1')), - @RM::ListItem.new(nil, @RM::Paragraph.new('l2'))) - - @note_list = @RM::List.new(:NOTE, - @RM::ListItem.new('cat', @RM::Paragraph.new('cats are cool')), - @RM::ListItem.new('dog', @RM::Paragraph.new('dogs are cool too'))) - - @number_list = @RM::List.new(:NUMBER, - @RM::ListItem.new(nil, @RM::Paragraph.new('l1')), - @RM::ListItem.new(nil, @RM::Paragraph.new('l2'))) - - @ualpha_list = @RM::List.new(:UALPHA, - @RM::ListItem.new(nil, @RM::Paragraph.new('l1')), - @RM::ListItem.new(nil, @RM::Paragraph.new('l2'))) - end +module RDoc + class Markup + ## + # Test case for creating new RDoc::Markup formatters. See + # test/test_rdoc_markup_to_*.rb for examples. + # + # This test case adds a variety of tests to your subclass when + # #add_visitor_tests is called. Most tests set up a scenario then call a + # method you will provide to perform the assertion on the output. + # + # Your subclass must instantiate a visitor and assign it to @to. + # + # For example, test_accept_blank_line sets up a RDoc::Markup::BlockLine then + # calls accept_blank_line on your visitor. You are responsible for asserting + # that the output is correct. + # + # Example: + # + # class RDocMarkupToNewFormatTest < RDoc::Markup::FormatterTestCase + # + # add_visitor_tests + # + # def setup + # super + # + # @to = RDoc::Markup::ToNewFormat.new + # end + # + # def accept_blank_line + # assert_equal :junk, @to.res.join + # end + # + # # ... + # + # end + + class FormatterTestCase < ::RDoc::TestCase + + ## + # Call #setup when inheriting from this test case. + # + # Provides the following instance variables: + # + # +@m+:: RDoc::Markup.new + # +@RM+:: RDoc::Markup # to reduce typing + # +@bullet_list+:: @RM::List.new :BULLET, # ... + # +@label_list+:: @RM::List.new :LABEL, # ... + # +@lalpha_list+:: @RM::List.new :LALPHA, # ... + # +@note_list+:: @RM::List.new :NOTE, # ... + # +@number_list+:: @RM::List.new :NUMBER, # ... + # +@ualpha_list+:: @RM::List.new :UALPHA, # ... + + def setup + super - ## - # Call to add the visitor tests to your test case + @options = ::RDoc::Options.new + + @m = @RM.new - def self.add_visitor_tests - class_eval do + @bullet_list = @RM::List.new(:BULLET, + @RM::ListItem.new(nil, @RM::Paragraph.new('l1')), + @RM::ListItem.new(nil, @RM::Paragraph.new('l2'))) - ## - # Calls start_accepting which needs to verify startup state + @label_list = @RM::List.new(:LABEL, + @RM::ListItem.new('cat', @RM::Paragraph.new('cats are cool')), + @RM::ListItem.new('dog', @RM::Paragraph.new('dogs are cool too'))) + + @lalpha_list = @RM::List.new(:LALPHA, + @RM::ListItem.new(nil, @RM::Paragraph.new('l1')), + @RM::ListItem.new(nil, @RM::Paragraph.new('l2'))) + + @note_list = @RM::List.new(:NOTE, + @RM::ListItem.new('cat', @RM::Paragraph.new('cats are cool')), + @RM::ListItem.new('dog', @RM::Paragraph.new('dogs are cool too'))) - def test_start_accepting - @to.start_accepting + @number_list = @RM::List.new(:NUMBER, + @RM::ListItem.new(nil, @RM::Paragraph.new('l1')), + @RM::ListItem.new(nil, @RM::Paragraph.new('l2'))) - start_accepting + @ualpha_list = @RM::List.new(:UALPHA, + @RM::ListItem.new(nil, @RM::Paragraph.new('l1')), + @RM::ListItem.new(nil, @RM::Paragraph.new('l2'))) end ## - # Calls end_accepting on your test case which needs to call - # @to.end_accepting and verify document generation + # Call to add the visitor tests to your test case - def test_end_accepting - @to.start_accepting - @to.res << 'hi' + def self.add_visitor_tests + class_eval do - end_accepting - end + ## + # Calls start_accepting which needs to verify startup state - ## - # Calls accept_blank_line + def test_start_accepting + @to.start_accepting - def test_accept_blank_line - @to.start_accepting + start_accepting + end - @to.accept_blank_line @RM::BlankLine.new + ## + # Calls end_accepting on your test case which needs to call + # @to.end_accepting and verify document generation - accept_blank_line - end + def test_end_accepting + @to.start_accepting + @to.res << 'hi' - ## - # Calls accept_block_quote + end_accepting + end - def test_accept_block_quote - @to.start_accepting + ## + # Calls accept_blank_line - @to.accept_block_quote block para 'quote' + def test_accept_blank_line + @to.start_accepting - accept_block_quote - end - ## - # Test case that calls @to.accept_document + @to.accept_blank_line @RM::BlankLine.new - def test_accept_document - @to.start_accepting - @to.accept_document @RM::Document.new @RM::Paragraph.new 'hello' + accept_blank_line + end - accept_document - end + ## + # Calls accept_block_quote - ## - # Calls accept_heading with a level 5 RDoc::Markup::Heading + def test_accept_block_quote + @to.start_accepting - def test_accept_heading - @to.start_accepting + @to.accept_block_quote block para 'quote' - @to.accept_heading @RM::Heading.new(5, 'Hello') + accept_block_quote + end + ## + # Test case that calls @to.accept_document - accept_heading - end + def test_accept_document + @to.start_accepting + @to.accept_document @RM::Document.new @RM::Paragraph.new 'hello' - ## - # Calls accept_heading_1 with a level 1 RDoc::Markup::Heading + accept_document + end - def test_accept_heading_1 - @to.start_accepting + ## + # Calls accept_heading with a level 5 RDoc::Markup::Heading - @to.accept_heading @RM::Heading.new(1, 'Hello') + def test_accept_heading + @to.start_accepting - accept_heading_1 - end + @to.accept_heading @RM::Heading.new(5, 'Hello') - ## - # Calls accept_heading_2 with a level 2 RDoc::Markup::Heading + accept_heading + end - def test_accept_heading_2 - @to.start_accepting + ## + # Calls accept_heading_1 with a level 1 RDoc::Markup::Heading - @to.accept_heading @RM::Heading.new(2, 'Hello') + def test_accept_heading_1 + @to.start_accepting - accept_heading_2 - end + @to.accept_heading @RM::Heading.new(1, 'Hello') - ## - # Calls accept_heading_3 with a level 3 RDoc::Markup::Heading + accept_heading_1 + end - def test_accept_heading_3 - @to.start_accepting + ## + # Calls accept_heading_2 with a level 2 RDoc::Markup::Heading - @to.accept_heading @RM::Heading.new(3, 'Hello') + def test_accept_heading_2 + @to.start_accepting - accept_heading_3 - end + @to.accept_heading @RM::Heading.new(2, 'Hello') - ## - # Calls accept_heading_4 with a level 4 RDoc::Markup::Heading + accept_heading_2 + end - def test_accept_heading_4 - @to.start_accepting + ## + # Calls accept_heading_3 with a level 3 RDoc::Markup::Heading - @to.accept_heading @RM::Heading.new(4, 'Hello') + def test_accept_heading_3 + @to.start_accepting - accept_heading_4 - end + @to.accept_heading @RM::Heading.new(3, 'Hello') - ## - # Calls accept_heading_b with a bold level 1 RDoc::Markup::Heading + accept_heading_3 + end - def test_accept_heading_b - @to.start_accepting + ## + # Calls accept_heading_4 with a level 4 RDoc::Markup::Heading - @to.accept_heading @RM::Heading.new(1, '*Hello*') + def test_accept_heading_4 + @to.start_accepting - accept_heading_b - end + @to.accept_heading @RM::Heading.new(4, 'Hello') - ## - # Calls accept_heading_suppressed_crossref with a level 1 - # RDoc::Markup::Heading containing a suppressed crossref + accept_heading_4 + end - def test_accept_heading_suppressed_crossref # HACK to_html_crossref test - @to.start_accepting + ## + # Calls accept_heading_b with a bold level 1 RDoc::Markup::Heading - @to.accept_heading @RM::Heading.new(1, '\\Hello') + def test_accept_heading_b + @to.start_accepting - accept_heading_suppressed_crossref - end + @to.accept_heading @RM::Heading.new(1, '*Hello*') - ## - # Calls accept_paragraph + accept_heading_b + end - def test_accept_paragraph - @to.start_accepting + ## + # Calls accept_heading_suppressed_crossref with a level 1 + # RDoc::Markup::Heading containing a suppressed crossref - @to.accept_paragraph @RM::Paragraph.new('hi') + def test_accept_heading_suppressed_crossref # HACK to_html_crossref test + @to.start_accepting - accept_paragraph - end + @to.accept_heading @RM::Heading.new(1, '\\Hello') - ## - # Calls accept_paragraph_b with a RDoc::Markup::Paragraph containing - # bold words + accept_heading_suppressed_crossref + end - def test_accept_paragraph_b - @to.start_accepting + ## + # Calls accept_paragraph - @to.accept_paragraph @RM::Paragraph.new('reg bold words reg') + def test_accept_paragraph + @to.start_accepting - accept_paragraph_b - end + @to.accept_paragraph @RM::Paragraph.new('hi') - ## - # Calls accept_paragraph_br with a RDoc::Markup::Paragraph containing - # a \
    + accept_paragraph + end - def test_accept_paragraph_br - @to.start_accepting + ## + # Calls accept_paragraph_b with a RDoc::Markup::Paragraph containing + # bold words - @to.accept_paragraph para 'one
    two' + def test_accept_paragraph_b + @to.start_accepting - accept_paragraph_br - end + @to.accept_paragraph @RM::Paragraph.new('reg bold words reg') - ## - # Calls accept_paragraph with a Paragraph containing a hard break + accept_paragraph_b + end - def test_accept_paragraph_break - @to.start_accepting + ## + # Calls accept_paragraph_br with a RDoc::Markup::Paragraph containing + # a \
    - @to.accept_paragraph para('hello', hard_break, 'world') + def test_accept_paragraph_br + @to.start_accepting - accept_paragraph_break - end + @to.accept_paragraph para 'one
    two' - ## - # Calls accept_paragraph_i with a RDoc::Markup::Paragraph containing - # emphasized words + accept_paragraph_br + end - def test_accept_paragraph_i - @to.start_accepting + ## + # Calls accept_paragraph with a Paragraph containing a hard break - @to.accept_paragraph @RM::Paragraph.new('reg italic words reg') + def test_accept_paragraph_break + @to.start_accepting - accept_paragraph_i - end + @to.accept_paragraph para('hello', hard_break, 'world') - ## - # Calls accept_paragraph_plus with a RDoc::Markup::Paragraph containing - # teletype words + accept_paragraph_break + end - def test_accept_paragraph_plus - @to.start_accepting + ## + # Calls accept_paragraph_i with a RDoc::Markup::Paragraph containing + # emphasized words - @to.accept_paragraph @RM::Paragraph.new('reg +teletype+ reg') + def test_accept_paragraph_i + @to.start_accepting - accept_paragraph_plus - end + @to.accept_paragraph @RM::Paragraph.new('reg italic words reg') - ## - # Calls accept_paragraph_star with a RDoc::Markup::Paragraph containing - # bold words + accept_paragraph_i + end - def test_accept_paragraph_star - @to.start_accepting + ## + # Calls accept_paragraph_plus with a RDoc::Markup::Paragraph containing + # teletype words - @to.accept_paragraph @RM::Paragraph.new('reg *bold* reg') + def test_accept_paragraph_plus + @to.start_accepting - accept_paragraph_star - end + @to.accept_paragraph @RM::Paragraph.new('reg +teletype+ reg') - ## - # Calls accept_paragraph_underscore with a RDoc::Markup::Paragraph - # containing emphasized words + accept_paragraph_plus + end - def test_accept_paragraph_underscore - @to.start_accepting + ## + # Calls accept_paragraph_star with a RDoc::Markup::Paragraph containing + # bold words - @to.accept_paragraph @RM::Paragraph.new('reg _italic_ reg') + def test_accept_paragraph_star + @to.start_accepting - accept_paragraph_underscore - end + @to.accept_paragraph @RM::Paragraph.new('reg *bold* reg') - ## - # Calls accept_verbatim with a RDoc::Markup::Verbatim + accept_paragraph_star + end - def test_accept_verbatim - @to.start_accepting + ## + # Calls accept_paragraph_underscore with a RDoc::Markup::Paragraph + # containing emphasized words - @to.accept_verbatim @RM::Verbatim.new("hi\n", " world\n") + def test_accept_paragraph_underscore + @to.start_accepting - accept_verbatim - end + @to.accept_paragraph @RM::Paragraph.new('reg _italic_ reg') - ## - # Calls accept_raw with a RDoc::Markup::Raw + accept_paragraph_underscore + end - def test_accept_raw - @to.start_accepting + ## + # Calls accept_verbatim with a RDoc::Markup::Verbatim - @to.accept_raw @RM::Raw.new("", - "
    NameCount", - "
    a1", - "
    b2", - "
    ") + def test_accept_verbatim + @to.start_accepting - accept_raw - end + @to.accept_verbatim @RM::Verbatim.new("hi\n", " world\n") - ## - # Calls accept_rule with a RDoc::Markup::Rule + accept_verbatim + end - def test_accept_rule - @to.start_accepting + ## + # Calls accept_raw with a RDoc::Markup::Raw - @to.accept_rule @RM::Rule.new(4) + def test_accept_raw + @to.start_accepting - accept_rule - end + @to.accept_raw @RM::Raw.new("", + "
    NameCount", + "
    a1", + "
    b2", + "
    ") - ## - # Calls accept_list_item_start_bullet + accept_raw + end - def test_accept_list_item_start_bullet - @to.start_accepting + ## + # Calls accept_rule with a RDoc::Markup::Rule - @to.accept_list_start @bullet_list + def test_accept_rule + @to.start_accepting - @to.accept_list_item_start @bullet_list.items.first + @to.accept_rule @RM::Rule.new(4) - accept_list_item_start_bullet - end + accept_rule + end - ## - # Calls accept_list_item_start_label + ## + # Calls accept_list_item_start_bullet - def test_accept_list_item_start_label - @to.start_accepting + def test_accept_list_item_start_bullet + @to.start_accepting - @to.accept_list_start @label_list + @to.accept_list_start @bullet_list - @to.accept_list_item_start @label_list.items.first + @to.accept_list_item_start @bullet_list.items.first - accept_list_item_start_label - end + accept_list_item_start_bullet + end - ## - # Calls accept_list_item_start_lalpha + ## + # Calls accept_list_item_start_label - def test_accept_list_item_start_lalpha - @to.start_accepting + def test_accept_list_item_start_label + @to.start_accepting - @to.accept_list_start @lalpha_list + @to.accept_list_start @label_list - @to.accept_list_item_start @lalpha_list.items.first + @to.accept_list_item_start @label_list.items.first - accept_list_item_start_lalpha - end + accept_list_item_start_label + end - ## - # Calls accept_list_item_start_note + ## + # Calls accept_list_item_start_lalpha - def test_accept_list_item_start_note - @to.start_accepting + def test_accept_list_item_start_lalpha + @to.start_accepting - @to.accept_list_start @note_list + @to.accept_list_start @lalpha_list - @to.accept_list_item_start @note_list.items.first + @to.accept_list_item_start @lalpha_list.items.first - accept_list_item_start_note - end + accept_list_item_start_lalpha + end - ## - # Calls accept_list_item_start_note_2 + ## + # Calls accept_list_item_start_note - def test_accept_list_item_start_note_2 - list = list(:NOTE, - item('teletype', - para('teletype description'))) + def test_accept_list_item_start_note + @to.start_accepting - @to.start_accepting + @to.accept_list_start @note_list - list.accept @to + @to.accept_list_item_start @note_list.items.first - @to.end_accepting + accept_list_item_start_note + end - accept_list_item_start_note_2 - end + ## + # Calls accept_list_item_start_note_2 - ## - # Calls accept_list_item_start_note_multi_description + def test_accept_list_item_start_note_2 + list = list(:NOTE, + item('teletype', + para('teletype description'))) - def test_accept_list_item_start_note_multi_description - list = list(:NOTE, - item(%w[label], - para('description one')), - item(nil, para('description two'))) + @to.start_accepting - @to.start_accepting + list.accept @to - list.accept @to + @to.end_accepting - @to.end_accepting + accept_list_item_start_note_2 + end - accept_list_item_start_note_multi_description - end + ## + # Calls accept_list_item_start_note_multi_description - ## - # Calls accept_list_item_start_note_multi_label + def test_accept_list_item_start_note_multi_description + list = list(:NOTE, + item(%w[label], + para('description one')), + item(nil, para('description two'))) - def test_accept_list_item_start_note_multi_label - list = list(:NOTE, - item(%w[one two], - para('two headers'))) + @to.start_accepting - @to.start_accepting + list.accept @to - list.accept @to + @to.end_accepting - @to.end_accepting + accept_list_item_start_note_multi_description + end - accept_list_item_start_note_multi_label - end + ## + # Calls accept_list_item_start_note_multi_label - ## - # Calls accept_list_item_start_number + def test_accept_list_item_start_note_multi_label + list = list(:NOTE, + item(%w[one two], + para('two headers'))) - def test_accept_list_item_start_number - @to.start_accepting + @to.start_accepting - @to.accept_list_start @number_list + list.accept @to - @to.accept_list_item_start @number_list.items.first + @to.end_accepting - accept_list_item_start_number - end + accept_list_item_start_note_multi_label + end - ## - # Calls accept_list_item_start_ualpha + ## + # Calls accept_list_item_start_number - def test_accept_list_item_start_ualpha - @to.start_accepting + def test_accept_list_item_start_number + @to.start_accepting - @to.accept_list_start @ualpha_list + @to.accept_list_start @number_list - @to.accept_list_item_start @ualpha_list.items.first + @to.accept_list_item_start @number_list.items.first - accept_list_item_start_ualpha - end + accept_list_item_start_number + end - ## - # Calls accept_list_item_end_bullet + ## + # Calls accept_list_item_start_ualpha - def test_accept_list_item_end_bullet - @to.start_accepting + def test_accept_list_item_start_ualpha + @to.start_accepting - @to.accept_list_start @bullet_list + @to.accept_list_start @ualpha_list - @to.accept_list_item_start @bullet_list.items.first + @to.accept_list_item_start @ualpha_list.items.first - @to.accept_list_item_end @bullet_list.items.first + accept_list_item_start_ualpha + end - accept_list_item_end_bullet - end + ## + # Calls accept_list_item_end_bullet - ## - # Calls accept_list_item_end_label + def test_accept_list_item_end_bullet + @to.start_accepting - def test_accept_list_item_end_label - @to.start_accepting + @to.accept_list_start @bullet_list - @to.accept_list_start @label_list + @to.accept_list_item_start @bullet_list.items.first - @to.accept_list_item_start @label_list.items.first + @to.accept_list_item_end @bullet_list.items.first - @to.accept_list_item_end @label_list.items.first + accept_list_item_end_bullet + end - accept_list_item_end_label - end + ## + # Calls accept_list_item_end_label - ## - # Calls accept_list_item_end_lalpha + def test_accept_list_item_end_label + @to.start_accepting - def test_accept_list_item_end_lalpha - @to.start_accepting + @to.accept_list_start @label_list - @to.accept_list_start @lalpha_list + @to.accept_list_item_start @label_list.items.first - @to.accept_list_item_start @lalpha_list.items.first + @to.accept_list_item_end @label_list.items.first - @to.accept_list_item_end @lalpha_list.items.first + accept_list_item_end_label + end - accept_list_item_end_lalpha - end + ## + # Calls accept_list_item_end_lalpha - ## - # Calls accept_list_item_end_note + def test_accept_list_item_end_lalpha + @to.start_accepting - def test_accept_list_item_end_note - @to.start_accepting + @to.accept_list_start @lalpha_list - @to.accept_list_start @note_list + @to.accept_list_item_start @lalpha_list.items.first - @to.accept_list_item_start @note_list.items.first + @to.accept_list_item_end @lalpha_list.items.first - @to.accept_list_item_end @note_list.items.first + accept_list_item_end_lalpha + end - accept_list_item_end_note - end + ## + # Calls accept_list_item_end_note - ## - # Calls accept_list_item_end_number + def test_accept_list_item_end_note + @to.start_accepting - def test_accept_list_item_end_number - @to.start_accepting + @to.accept_list_start @note_list - @to.accept_list_start @number_list + @to.accept_list_item_start @note_list.items.first - @to.accept_list_item_start @number_list.items.first + @to.accept_list_item_end @note_list.items.first - @to.accept_list_item_end @number_list.items.first + accept_list_item_end_note + end - accept_list_item_end_number - end + ## + # Calls accept_list_item_end_number - ## - # Calls accept_list_item_end_ualpha + def test_accept_list_item_end_number + @to.start_accepting - def test_accept_list_item_end_ualpha - @to.start_accepting + @to.accept_list_start @number_list - @to.accept_list_start @ualpha_list + @to.accept_list_item_start @number_list.items.first - @to.accept_list_item_start @ualpha_list.items.first + @to.accept_list_item_end @number_list.items.first - @to.accept_list_item_end @ualpha_list.items.first + accept_list_item_end_number + end - accept_list_item_end_ualpha - end + ## + # Calls accept_list_item_end_ualpha - ## - # Calls accept_list_start_bullet + def test_accept_list_item_end_ualpha + @to.start_accepting - def test_accept_list_start_bullet - @to.start_accepting + @to.accept_list_start @ualpha_list - @to.accept_list_start @bullet_list + @to.accept_list_item_start @ualpha_list.items.first - accept_list_start_bullet - end + @to.accept_list_item_end @ualpha_list.items.first - ## - # Calls accept_list_start_label + accept_list_item_end_ualpha + end - def test_accept_list_start_label - @to.start_accepting + ## + # Calls accept_list_start_bullet - @to.accept_list_start @label_list + def test_accept_list_start_bullet + @to.start_accepting - accept_list_start_label - end + @to.accept_list_start @bullet_list - ## - # Calls accept_list_start_lalpha + accept_list_start_bullet + end - def test_accept_list_start_lalpha - @to.start_accepting + ## + # Calls accept_list_start_label - @to.accept_list_start @lalpha_list + def test_accept_list_start_label + @to.start_accepting - accept_list_start_lalpha - end + @to.accept_list_start @label_list - ## - # Calls accept_list_start_note + accept_list_start_label + end - def test_accept_list_start_note - @to.start_accepting + ## + # Calls accept_list_start_lalpha - @to.accept_list_start @note_list + def test_accept_list_start_lalpha + @to.start_accepting - accept_list_start_note - end + @to.accept_list_start @lalpha_list - ## - # Calls accept_list_start_number + accept_list_start_lalpha + end - def test_accept_list_start_number - @to.start_accepting + ## + # Calls accept_list_start_note - @to.accept_list_start @number_list + def test_accept_list_start_note + @to.start_accepting - accept_list_start_number - end + @to.accept_list_start @note_list - ## - # Calls accept_list_start_ualpha + accept_list_start_note + end - def test_accept_list_start_ualpha - @to.start_accepting + ## + # Calls accept_list_start_number - @to.accept_list_start @ualpha_list + def test_accept_list_start_number + @to.start_accepting - accept_list_start_ualpha - end + @to.accept_list_start @number_list - ## - # Calls accept_list_end_bullet + accept_list_start_number + end - def test_accept_list_end_bullet - @to.start_accepting + ## + # Calls accept_list_start_ualpha - @to.accept_list_start @bullet_list + def test_accept_list_start_ualpha + @to.start_accepting - @to.accept_list_end @bullet_list + @to.accept_list_start @ualpha_list - accept_list_end_bullet - end + accept_list_start_ualpha + end - ## - # Calls accept_list_end_label + ## + # Calls accept_list_end_bullet - def test_accept_list_end_label - @to.start_accepting + def test_accept_list_end_bullet + @to.start_accepting - @to.accept_list_start @label_list + @to.accept_list_start @bullet_list - @to.accept_list_end @label_list + @to.accept_list_end @bullet_list - accept_list_end_label - end + accept_list_end_bullet + end - ## - # Calls accept_list_end_lalpha + ## + # Calls accept_list_end_label - def test_accept_list_end_lalpha - @to.start_accepting + def test_accept_list_end_label + @to.start_accepting - @to.accept_list_start @lalpha_list + @to.accept_list_start @label_list - @to.accept_list_end @lalpha_list + @to.accept_list_end @label_list - accept_list_end_lalpha - end + accept_list_end_label + end - ## - # Calls accept_list_end_number + ## + # Calls accept_list_end_lalpha - def test_accept_list_end_number - @to.start_accepting + def test_accept_list_end_lalpha + @to.start_accepting - @to.accept_list_start @number_list + @to.accept_list_start @lalpha_list - @to.accept_list_end @number_list + @to.accept_list_end @lalpha_list - accept_list_end_number - end + accept_list_end_lalpha + end - ## - # Calls accept_list_end_note + ## + # Calls accept_list_end_number - def test_accept_list_end_note - @to.start_accepting + def test_accept_list_end_number + @to.start_accepting - @to.accept_list_start @note_list + @to.accept_list_start @number_list - @to.accept_list_end @note_list + @to.accept_list_end @number_list - accept_list_end_note - end + accept_list_end_number + end - ## - # Calls accept_list_end_ualpha + ## + # Calls accept_list_end_note - def test_accept_list_end_ualpha - @to.start_accepting + def test_accept_list_end_note + @to.start_accepting - @to.accept_list_start @ualpha_list + @to.accept_list_start @note_list - @to.accept_list_end @ualpha_list + @to.accept_list_end @note_list - accept_list_end_ualpha - end + accept_list_end_note + end - ## - # Calls list_nested with a two-level list + ## + # Calls accept_list_end_ualpha - def test_list_nested - doc = @RM::Document.new( - @RM::List.new(:BULLET, - @RM::ListItem.new(nil, - @RM::Paragraph.new('l1'), - @RM::List.new(:BULLET, - @RM::ListItem.new(nil, - @RM::Paragraph.new('l1.1')))), - @RM::ListItem.new(nil, - @RM::Paragraph.new('l2')))) + def test_accept_list_end_ualpha + @to.start_accepting - doc.accept @to + @to.accept_list_start @ualpha_list - list_nested - end + @to.accept_list_end @ualpha_list - ## - # Calls list_verbatim with a list containing a verbatim block - - def test_list_verbatim # HACK overblown - doc = - doc( - list(:BULLET, - item(nil, - para('list stuff'), - blank_line, - verb("* list\n", - " with\n", - "\n", - " second\n", - "\n", - " 1. indented\n", - " 2. numbered\n", - "\n", - " third\n", - "\n", - "* second\n")))) - - doc.accept @to - - list_verbatim + accept_list_end_ualpha + end + + ## + # Calls list_nested with a two-level list + + def test_list_nested + doc = @RM::Document.new( + @RM::List.new(:BULLET, + @RM::ListItem.new(nil, + @RM::Paragraph.new('l1'), + @RM::List.new(:BULLET, + @RM::ListItem.new(nil, + @RM::Paragraph.new('l1.1')))), + @RM::ListItem.new(nil, + @RM::Paragraph.new('l2')))) + + doc.accept @to + + list_nested + end + + ## + # Calls list_verbatim with a list containing a verbatim block + + def test_list_verbatim # HACK overblown + doc = + doc( + list(:BULLET, + item(nil, + para('list stuff'), + blank_line, + verb("* list\n", + " with\n", + "\n", + " second\n", + "\n", + " 1. indented\n", + " 2. numbered\n", + "\n", + " third\n", + "\n", + "* second\n")))) + + doc.accept @to + + list_verbatim + end + end end + end end - end diff --git a/test/rdoc/support/test_case.rb b/test/rdoc/support/test_case.rb index 99af043991..39a807c9dd 100644 --- a/test/rdoc/support/test_case.rb +++ b/test/rdoc/support/test_case.rb @@ -19,200 +19,202 @@ end require_relative '../../../lib/rdoc' -## -# RDoc::TestCase is an abstract TestCase to provide common setup and teardown -# across all RDoc tests. The test case uses test-unit, so all the assertions -# of test-unit may be used. -# -# The testcase provides the following: -# -# * A reset code-object tree -# * A reset markup preprocessor (RDoc::Markup::PreProcess) -# * The @RM alias of RDoc::Markup (for less typing) -# * @pwd containing the current working directory -# * FileUtils, pp, Tempfile, Dir.tmpdir and StringIO - -class RDoc::TestCase < Test::Unit::TestCase - +module RDoc ## - # Abstract test-case setup + # RDoc::TestCase is an abstract TestCase to provide common setup and teardown + # across all RDoc tests. The test case uses test-unit, so all the assertions + # of test-unit may be used. + # + # The testcase provides the following: + # + # * A reset code-object tree + # * A reset markup preprocessor (RDoc::Markup::PreProcess) + # * The @RM alias of RDoc::Markup (for less typing) + # * @pwd containing the current working directory + # * FileUtils, pp, Tempfile, Dir.tmpdir and StringIO - def setup - super + class TestCase < Test::Unit::TestCase - @orig_home = ENV["HOME"] - FileUtils.mkdir_p(@test_home = Dir.mktmpdir("test_rdoc_")) - ENV["HOME"] = @test_home + ## + # Abstract test-case setup - @top_level = nil + def setup + super - @RM = RDoc::Markup + @orig_home = ENV["HOME"] + FileUtils.mkdir_p(@test_home = Dir.mktmpdir("test_rdoc_")) + ENV["HOME"] = @test_home - @pwd = Dir.pwd + @top_level = nil - @options = RDoc::Options.new - @store = RDoc::Store.new(@options) + @RM = ::RDoc::Markup - @rdoc = RDoc::RDoc.new - @rdoc.store = @store - @rdoc.options = @options + @pwd = Dir.pwd - @rdoc.generator = Object.new + @options = ::RDoc::Options.new + @store = ::RDoc::Store.new(@options) - RDoc::Markup::PreProcess.reset - end + @rdoc = ::RDoc::RDoc.new + @rdoc.store = @store + @rdoc.options = @options - ## - # Abstract test-case teardown + @rdoc.generator = Object.new - def teardown - ENV["HOME"] = @orig_home if defined?(@orig_home) - defined?(@test_home) and FileUtils.rm_rf @test_home + ::RDoc::Markup::PreProcess.reset + end - super - end + ## + # Abstract test-case teardown - ## - # Asserts +path+ is a file + def teardown + ENV["HOME"] = @orig_home if defined?(@orig_home) + defined?(@test_home) and FileUtils.rm_rf @test_home - def assert_file(path) - assert File.file?(path), "#{path} is not a file" - end + super + end - ## - # Asserts +path+ is a directory + ## + # Asserts +path+ is a file - def assert_directory(path) - assert File.directory?(path), "#{path} is not a directory" - end + def assert_file(path) + assert File.file?(path), "#{path} is not a file" + end - ## - # Refutes +path+ exists + ## + # Asserts +path+ is a directory - def refute_file(path) - refute File.exist?(path), "#{path} exists" - end + def assert_directory(path) + assert File.directory?(path), "#{path} is not a directory" + end - ## - # Shortcut for RDoc::Markup::BlankLine.new + ## + # Refutes +path+ exists - def blank_line - @RM::BlankLine.new - end + def refute_file(path) + refute File.exist?(path), "#{path} exists" + end - ## - # Shortcut for RDoc::Markup::BlockQuote.new with +contents+ + ## + # Shortcut for RDoc::Markup::BlankLine.new - def block(*contents) - @RM::BlockQuote.new(*contents) - end + def blank_line + @RM::BlankLine.new + end - ## - # Creates an RDoc::Comment with +text+ which was defined on +top_level+. - # By default the comment has the 'rdoc' format. + ## + # Shortcut for RDoc::Markup::BlockQuote.new with +contents+ - def comment(text, top_level = @top_level, language = nil) - comment = RDoc::Comment.new text, top_level, language - comment - end + def block(*contents) + @RM::BlockQuote.new(*contents) + end - ## - # Shortcut for RDoc::Markup::Document.new with +contents+ + ## + # Creates an RDoc::Comment with +text+ which was defined on +top_level+. + # By default the comment has the 'rdoc' format. - def doc(*contents) - @RM::Document.new(*contents) - end + def comment(text, top_level = @top_level, language = nil) + comment = ::RDoc::Comment.new text, top_level, language + comment + end - ## - # Shortcut for RDoc::Markup::HardBreak.new + ## + # Shortcut for RDoc::Markup::Document.new with +contents+ - def hard_break - @RM::HardBreak.new - end + def doc(*contents) + @RM::Document.new(*contents) + end - ## - # Shortcut for RDoc::Markup::Heading.new with +level+ and +text+ + ## + # Shortcut for RDoc::Markup::HardBreak.new - def head(level, text) - @RM::Heading.new level, text - end + def hard_break + @RM::HardBreak.new + end - ## - # Shortcut for RDoc::Markup::ListItem.new with +label+ and +parts+ + ## + # Shortcut for RDoc::Markup::Heading.new with +level+ and +text+ - def item(label = nil, *parts) - @RM::ListItem.new label, *parts - end + def head(level, text) + @RM::Heading.new level, text + end - ## - # Shortcut for RDoc::Markup::List.new with +type+ and +items+ + ## + # Shortcut for RDoc::Markup::ListItem.new with +label+ and +parts+ - def list(type = nil, *items) - @RM::List.new type, *items - end + def item(label = nil, *parts) + @RM::ListItem.new label, *parts + end - ## - # Enables pretty-print output + ## + # Shortcut for RDoc::Markup::List.new with +type+ and +items+ - def mu_pp(obj) # :nodoc: - s = obj.pretty_inspect - s = RDoc::Encoding.change_encoding s, Encoding.default_external - s.chomp - end + def list(type = nil, *items) + @RM::List.new type, *items + end - ## - # Shortcut for RDoc::Markup::Paragraph.new with +contents+ + ## + # Enables pretty-print output - def para(*a) - @RM::Paragraph.new(*a) - end + def mu_pp(obj) # :nodoc: + s = obj.pretty_inspect + s = ::RDoc::Encoding.change_encoding s, ::Encoding.default_external + s.chomp + end - ## - # Shortcut for RDoc::Markup::Rule.new with +weight+ + ## + # Shortcut for RDoc::Markup::Paragraph.new with +contents+ - def rule(weight) - @RM::Rule.new weight - end + def para(*a) + @RM::Paragraph.new(*a) + end - ## - # Shortcut for RDoc::Markup::Raw.new with +contents+ + ## + # Shortcut for RDoc::Markup::Rule.new with +weight+ - def raw(*contents) - @RM::Raw.new(*contents) - end + def rule(weight) + @RM::Rule.new weight + end - ## - # Creates a temporary directory changes the current directory to it for the - # duration of the block. - # - # Depends upon Dir.mktmpdir + ## + # Shortcut for RDoc::Markup::Raw.new with +contents+ - def temp_dir - Dir.mktmpdir do |temp_dir| - Dir.chdir temp_dir do - yield temp_dir + def raw(*contents) + @RM::Raw.new(*contents) + end + + ## + # Creates a temporary directory changes the current directory to it for the + # duration of the block. + # + # Depends upon Dir.mktmpdir + + def temp_dir + Dir.mktmpdir do |temp_dir| + Dir.chdir temp_dir do + yield temp_dir + end end end - end - ## - # Shortcut for RDoc::Markup::Verbatim.new with +parts+ + ## + # Shortcut for RDoc::Markup::Verbatim.new with +parts+ - def verb(*parts) - @RM::Verbatim.new(*parts) - end + def verb(*parts) + @RM::Verbatim.new(*parts) + end - ## - # run capture_io with setting $VERBOSE = true - - def verbose_capture_output - capture_output do - begin - orig_verbose = $VERBOSE - $VERBOSE = true - yield - ensure - $VERBOSE = orig_verbose + ## + # run capture_io with setting $VERBOSE = true + + def verbose_capture_output + capture_output do + begin + orig_verbose = $VERBOSE + $VERBOSE = true + yield + ensure + $VERBOSE = orig_verbose + end end end end diff --git a/test/rdoc/support/text_formatter_test_case.rb b/test/rdoc/support/text_formatter_test_case.rb index ae9b91ef3e..cab14462de 100644 --- a/test/rdoc/support/text_formatter_test_case.rb +++ b/test/rdoc/support/text_formatter_test_case.rb @@ -1,148 +1,152 @@ # frozen_string_literal: true -## -# Test case for creating new plain-text RDoc::Markup formatters. See also -# RDoc::Markup::FormatterTestCase -# -# See test_rdoc_markup_to_rdoc.rb for a complete example. -# -# Example: -# -# class RDocMarkupToNewTextFormatTest < RDoc::Markup::TextFormatterTestCase -# -# add_visitor_tests -# add_text_tests -# -# def setup -# super -# -# @to = RDoc::Markup::ToNewTextFormat.new -# end -# -# def accept_blank_line -# assert_equal :junk, @to.res.join -# end -# -# # ... -# -# end - -class RDoc::Markup::TextFormatterTestCase < RDoc::Markup::FormatterTestCase - - ## - # Adds test cases to the calling TestCase. - - def self.add_text_tests - self.class_eval do +module RDoc + class Markup + ## + # Test case for creating new plain-text RDoc::Markup formatters. See also + # RDoc::Markup::FormatterTestCase + # + # See test_rdoc_markup_to_rdoc.rb for a complete example. + # + # Example: + # + # class RDocMarkupToNewTextFormatTest < RDoc::Markup::TextFormatterTestCase + # + # add_visitor_tests + # add_text_tests + # + # def setup + # super + # + # @to = RDoc::Markup::ToNewTextFormat.new + # end + # + # def accept_blank_line + # assert_equal :junk, @to.res.join + # end + # + # # ... + # + # end + + class TextFormatterTestCase < ::RDoc::Markup::FormatterTestCase ## - # Test case that calls @to.accept_heading + # Adds test cases to the calling TestCase. - def test_accept_heading_indent - @to.start_accepting - @to.indent = 3 - @to.accept_heading @RM::Heading.new(1, 'Hello') + def self.add_text_tests + self.class_eval do - accept_heading_indent - end + ## + # Test case that calls @to.accept_heading + + def test_accept_heading_indent + @to.start_accepting + @to.indent = 3 + @to.accept_heading @RM::Heading.new(1, 'Hello') - ## - # Test case that calls @to.accept_rule + accept_heading_indent + end - def test_accept_rule_indent - @to.start_accepting - @to.indent = 3 - @to.accept_rule @RM::Rule.new(1) + ## + # Test case that calls @to.accept_rule - accept_rule_indent - end + def test_accept_rule_indent + @to.start_accepting + @to.indent = 3 + @to.accept_rule @RM::Rule.new(1) - ## - # Test case that calls @to.accept_verbatim + accept_rule_indent + end - def test_accept_verbatim_indent - @to.start_accepting - @to.indent = 2 - @to.accept_verbatim @RM::Verbatim.new("hi\n", " world\n") + ## + # Test case that calls @to.accept_verbatim - accept_verbatim_indent - end + def test_accept_verbatim_indent + @to.start_accepting + @to.indent = 2 + @to.accept_verbatim @RM::Verbatim.new("hi\n", " world\n") - ## - # Test case that calls @to.accept_verbatim with a big indent + accept_verbatim_indent + end - def test_accept_verbatim_big_indent - @to.start_accepting - @to.indent = 2 - @to.accept_verbatim @RM::Verbatim.new("hi\n", "world\n") + ## + # Test case that calls @to.accept_verbatim with a big indent - accept_verbatim_big_indent - end + def test_accept_verbatim_big_indent + @to.start_accepting + @to.indent = 2 + @to.accept_verbatim @RM::Verbatim.new("hi\n", "world\n") - ## - # Test case that calls @to.accept_paragraph with an indent + accept_verbatim_big_indent + end - def test_accept_paragraph_indent - @to.start_accepting - @to.indent = 3 - @to.accept_paragraph @RM::Paragraph.new(('words ' * 30).strip) + ## + # Test case that calls @to.accept_paragraph with an indent - accept_paragraph_indent - end + def test_accept_paragraph_indent + @to.start_accepting + @to.indent = 3 + @to.accept_paragraph @RM::Paragraph.new(('words ' * 30).strip) - ## - # Test case that calls @to.accept_paragraph with a long line + accept_paragraph_indent + end - def test_accept_paragraph_wrap - @to.start_accepting - @to.accept_paragraph @RM::Paragraph.new(('words ' * 30).strip) + ## + # Test case that calls @to.accept_paragraph with a long line - accept_paragraph_wrap - end + def test_accept_paragraph_wrap + @to.start_accepting + @to.accept_paragraph @RM::Paragraph.new(('words ' * 30).strip) - ## - # Test case that calls @to.accept_table - - def test_accept_table_align - header = ['AA', 'BB', 'CCCCC', 'DDDDD'] - body = [ - ['', 'bbb', 'c', ''], - ['aaaa', 'b', '', 'dd'], - ['a', '', 'cc', 'dd'] - ] - aligns = [nil, :left, :right, :center] - @to.start_accepting - @to.accept_table header, body, aligns - - accept_table_align - end + accept_paragraph_wrap + end - ## - # Test case that calls @to.accept_table with body rows - # that have fewer columns than the header - - def test_accept_table_ragged_rows - header = ['Name', 'Description'] - body = [ - ['foo', 'Foo description'], - ['bar'], - ] - aligns = [:left, :left] - @to.start_accepting - @to.accept_table header, body, aligns - - accept_table_ragged_rows - end + ## + # Test case that calls @to.accept_table - ## - # Test case that calls @to.attributes with an escaped - # cross-reference. If this test doesn't pass something may be very - # wrong. + def test_accept_table_align + header = ['AA', 'BB', 'CCCCC', 'DDDDD'] + body = [ + ['', 'bbb', 'c', ''], + ['aaaa', 'b', '', 'dd'], + ['a', '', 'cc', 'dd'] + ] + aligns = [nil, :left, :right, :center] + @to.start_accepting + @to.accept_table header, body, aligns + + accept_table_align + end - def test_attributes - assert_equal 'Dog', @to.attributes("\\Dog") + ## + # Test case that calls @to.accept_table with body rows + # that have fewer columns than the header + + def test_accept_table_ragged_rows + header = ['Name', 'Description'] + body = [ + ['foo', 'Foo description'], + ['bar'], + ] + aligns = [:left, :left] + @to.start_accepting + @to.accept_table header, body, aligns + + accept_table_ragged_rows + end + + ## + # Test case that calls @to.attributes with an escaped + # cross-reference. If this test doesn't pass something may be very + # wrong. + + def test_attributes + assert_equal 'Dog', @to.attributes("\\Dog") + end + + end end end end - end From 3d1a7332c04042d2fd5c59c91433143b664fa321 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Sat, 29 Aug 2026 15:36:32 +0100 Subject: [PATCH 2/2] Use relative constant paths after nesting Assisted-By: devx/0d67a386-6fea-4c9d-96ef-064c1cc8810c --- lib/rdoc/code_object.rb | 12 +- lib/rdoc/code_object/alias.rb | 2 +- lib/rdoc/code_object/any_method.rb | 12 +- lib/rdoc/code_object/attr.rb | 6 +- lib/rdoc/code_object/class_module.rb | 52 +++---- lib/rdoc/code_object/constant.rb | 8 +- lib/rdoc/code_object/context.rb | 40 +++--- lib/rdoc/code_object/context/section.rb | 12 +- lib/rdoc/code_object/extend.rb | 2 +- lib/rdoc/code_object/include.rb | 2 +- lib/rdoc/code_object/method_attr.rb | 2 +- lib/rdoc/code_object/mixin.rb | 4 +- lib/rdoc/code_object/normal_class.rb | 2 +- lib/rdoc/code_object/normal_module.rb | 2 +- lib/rdoc/code_object/require.rb | 8 +- lib/rdoc/code_object/single_class.rb | 2 +- lib/rdoc/code_object/top_level.rb | 10 +- lib/rdoc/comment.rb | 8 +- lib/rdoc/cross_reference.rb | 4 +- lib/rdoc/encoding.rb | 14 +- lib/rdoc/generator/aliki.rb | 10 +- lib/rdoc/generator/darkfish.rb | 26 ++-- lib/rdoc/generator/json_index.rb | 2 +- lib/rdoc/generator/markup.rb | 8 +- lib/rdoc/generator/pot.rb | 2 +- lib/rdoc/generator/pot/message_extractor.rb | 6 +- lib/rdoc/generator/pot/po.rb | 2 +- lib/rdoc/generator/ri.rb | 2 +- .../generator/template/aliki/_footer.rhtml | 2 +- lib/rdoc/generator/template/aliki/_head.rhtml | 20 +-- .../generator/template/darkfish/_footer.rhtml | 2 +- lib/rdoc/i18n/text.rb | 2 +- lib/rdoc/markup.rb | 10 +- lib/rdoc/markup/block_quote.rb | 2 +- lib/rdoc/markup/document.rb | 10 +- lib/rdoc/markup/formatter.rb | 6 +- lib/rdoc/markup/indented_paragraph.rb | 4 +- lib/rdoc/markup/paragraph.rb | 4 +- lib/rdoc/markup/parser.rb | 24 ++-- lib/rdoc/markup/pre_process.rb | 22 +-- lib/rdoc/markup/to_ansi.rb | 2 +- lib/rdoc/markup/to_bs.rb | 2 +- lib/rdoc/markup/to_html.rb | 12 +- lib/rdoc/markup/to_html_crossref.rb | 18 +-- lib/rdoc/markup/to_html_snippet.rb | 8 +- lib/rdoc/markup/to_joined_paragraph.rb | 2 +- lib/rdoc/markup/to_label.rb | 6 +- lib/rdoc/markup/to_markdown.rb | 4 +- lib/rdoc/markup/to_rdoc.rb | 4 +- lib/rdoc/markup/to_table_of_contents.rb | 2 +- lib/rdoc/markup/to_test.rb | 2 +- lib/rdoc/markup/to_tt_only.rb | 4 +- lib/rdoc/markup/verbatim.rb | 2 +- lib/rdoc/options.rb | 36 ++--- lib/rdoc/parser.rb | 16 +-- lib/rdoc/parser/c.rb | 36 ++--- lib/rdoc/parser/changelog.rb | 32 ++--- lib/rdoc/parser/markdown.rb | 6 +- lib/rdoc/parser/rbs.rb | 28 ++-- lib/rdoc/parser/rd.rb | 6 +- lib/rdoc/parser/ruby.rb | 66 ++++----- lib/rdoc/parser/simple.rb | 8 +- lib/rdoc/rbs_helper.rb | 2 +- lib/rdoc/rd.rb | 6 +- lib/rdoc/rd/inline.rb | 2 +- lib/rdoc/rdoc.rb | 36 ++--- lib/rdoc/ri/driver.rb | 136 +++++++++--------- lib/rdoc/ri/servlet.rb | 18 +-- lib/rdoc/ri/task.rb | 2 +- lib/rdoc/rubygems_hook.rb | 8 +- lib/rdoc/server.rb | 2 +- lib/rdoc/stats.rb | 6 +- lib/rdoc/stats/normal.rb | 2 +- lib/rdoc/stats/verbose.rb | 2 +- lib/rdoc/store.rb | 28 ++-- lib/rdoc/task.rb | 10 +- lib/rdoc/text.rb | 30 ++-- lib/rdoc/tom_doc.rb | 20 +-- test/rdoc/markup/pre_process_test.rb | 76 +++++----- test/rdoc/support/formatter_test_case.rb | 4 +- test/rdoc/support/test_case.rb | 14 +- test/rdoc/support/text_formatter_test_case.rb | 2 +- 82 files changed, 539 insertions(+), 539 deletions(-) diff --git a/lib/rdoc/code_object.rb b/lib/rdoc/code_object.rb index fba667145c..9d8c0f13f2 100644 --- a/lib/rdoc/code_object.rb +++ b/lib/rdoc/code_object.rb @@ -26,7 +26,7 @@ module RDoc class CodeObject - include ::RDoc::Text + include Text ## # Our comment @@ -133,7 +133,7 @@ def initialize_visibility # :nodoc: def comment=(comment) @comment = case comment when NilClass then '' - when ::RDoc::Comment then comment.normalize + when Comment then comment.normalize else if comment and not comment.empty? normalize_comment comment @@ -141,7 +141,7 @@ def comment=(comment) # HACK correct fix is to have #initialize create @comment # with the correct encoding if String === @comment and @comment.empty? - @comment = ::RDoc::Encoding.change_encoding @comment, comment.encoding + @comment = Encoding.change_encoding @comment, comment.encoding end @comment end @@ -279,7 +279,7 @@ def ignored? # ClassModule#embed_mixins def options - @store&.options || ::RDoc::Options.new + @store&.options || Options.new end ## @@ -290,7 +290,7 @@ def parent return @parent if @parent return nil unless @parent_name - if @parent_class == ::RDoc::TopLevel + if @parent_class == TopLevel @parent = @store.add_file @parent_name else @parent = @store.find_class_or_module @parent_name @@ -299,7 +299,7 @@ def parent begin @parent = @store.load_class @parent_name - rescue ::RDoc::Store::MissingFileError + rescue Store::MissingFileError nil end end diff --git a/lib/rdoc/code_object/alias.rb b/lib/rdoc/code_object/alias.rb index fdf75ef758..1e17c06e5a 100644 --- a/lib/rdoc/code_object/alias.rb +++ b/lib/rdoc/code_object/alias.rb @@ -7,7 +7,7 @@ module RDoc # TODO implement Alias as a proxy to a method/attribute, inheriting from # MethodAttr - class Alias < ::RDoc::CodeObject + class Alias < CodeObject ## # Aliased method's name diff --git a/lib/rdoc/code_object/any_method.rb b/lib/rdoc/code_object/any_method.rb index bf21f546f8..a3c7b3986a 100644 --- a/lib/rdoc/code_object/any_method.rb +++ b/lib/rdoc/code_object/any_method.rb @@ -3,7 +3,7 @@ module RDoc ## # AnyMethod is the base class for objects representing methods - class AnyMethod < ::RDoc::MethodAttr + class AnyMethod < MethodAttr ## # 2:: @@ -37,7 +37,7 @@ class AnyMethod < ::RDoc::MethodAttr attr_accessor :calls_super - include ::RDoc::TokenStream + include TokenStream ## # Creates a new AnyMethod with name +name+ @@ -127,7 +127,7 @@ def has_call_seq? def is_alias_for # :nodoc: case @is_alias_for - when ::RDoc::MethodAttr + when MethodAttr @is_alias_for when Array return nil unless @store @@ -198,7 +198,7 @@ def marshal_load(array) @full_name = array[2] @singleton = array[3] @visibility = array[4] || :public - @comment = ::RDoc::Comment.from_document array[5] + @comment = Comment.from_document array[5] @call_seq = array[6] @block_params = array[7] # 8 handled below @@ -212,7 +212,7 @@ def marshal_load(array) @type_signature_lines = array[16]&.split("\n") array[8].each do |new_name, document| - add_alias ::RDoc::Alias.new(@name, new_name, ::RDoc::Comment.from_document(document), singleton: @singleton) + add_alias Alias.new(@name, new_name, Comment.from_document(document), singleton: @singleton) end @parent_name ||= if @full_name =~ /#/ @@ -223,7 +223,7 @@ def marshal_load(array) name.join '::' end - @file = ::RDoc::TopLevel.new array[10] if version > 0 + @file = TopLevel.new array[10] if version > 0 end ## diff --git a/lib/rdoc/code_object/attr.rb b/lib/rdoc/code_object/attr.rb index 3d18075c77..00942324db 100644 --- a/lib/rdoc/code_object/attr.rb +++ b/lib/rdoc/code_object/attr.rb @@ -4,7 +4,7 @@ module RDoc # An attribute created by \#attr, \#attr_reader, \#attr_writer or # \#attr_accessor - class Attr < ::RDoc::MethodAttr + class Attr < MethodAttr ## # 3:: @@ -139,7 +139,7 @@ def marshal_load(array) @full_name = array[2] @rw = array[3] @visibility = array[4] || :public - @comment = ::RDoc::Comment.from_document array[5] + @comment = Comment.from_document array[5] @singleton = array[6] || false # MARSHAL_VERSION == 0 # 7 handled below @parent_name = array[8] @@ -147,7 +147,7 @@ def marshal_load(array) @section_title = array[10] @type_signature_lines = array[11]&.split("\n") - @file = ::RDoc::TopLevel.new array[7] if version > 1 + @file = TopLevel.new array[7] if version > 1 @parent_name ||= @full_name.split('#', 2).first end diff --git a/lib/rdoc/code_object/class_module.rb b/lib/rdoc/code_object/class_module.rb index c3bf7be545..e0a8831c9b 100644 --- a/lib/rdoc/code_object/class_module.rb +++ b/lib/rdoc/code_object/class_module.rb @@ -4,7 +4,7 @@ module RDoc # ClassModule is the base class for objects representing either a class or a # module. - class ClassModule < ::RDoc::Context + class ClassModule < Context ## # 1:: @@ -138,7 +138,7 @@ def add_comment(comment, location) original = comment comment = case comment - when ::RDoc::Comment + when Comment comment.normalize else normalize_comment comment @@ -228,7 +228,7 @@ def clear_comment def comment=(comment) # :nodoc: comment = case comment - when ::RDoc::Comment + when Comment comment.normalize else normalize_comment comment @@ -313,7 +313,7 @@ def find_class_named(name) # Return the fully qualified name of this class or module def full_name - @full_name ||= if ::RDoc::ClassModule === parent + @full_name ||= if ClassModule === parent "#{parent.full_name}::#{@name}" else @name @@ -404,9 +404,9 @@ def marshal_load(array) # :nodoc: @superclass = array[3] document = array[4] - @comment = ::RDoc::Comment.from_document document + @comment = Comment.from_document document - @comment_location = if document.parts.first.is_a?(::RDoc::Markup::Document) + @comment_location = if document.parts.first.is_a?(Markup::Document) document.parts.group_by(&:file) else { document.file => [document] } @@ -416,33 +416,33 @@ def marshal_load(array) # :nodoc: singleton ||= false visibility ||= :public - attr = ::RDoc::Attr.new name, rw, nil, singleton: singleton + attr = Attr.new name, rw, nil, singleton: singleton add_attribute attr attr.visibility = visibility - attr.record_location ::RDoc::TopLevel.new file + attr.record_location TopLevel.new file end array[6].each do |constant, document, file| case constant - when ::RDoc::Constant + when Constant add_constant constant else - constant = add_constant ::RDoc::Constant.new(constant, nil, ::RDoc::Comment.from_document(document)) - constant.record_location ::RDoc::TopLevel.new file + constant = add_constant Constant.new(constant, nil, Comment.from_document(document)) + constant.record_location TopLevel.new file end end array[7].each do |name, document, file| - incl = add_include ::RDoc::Include.new(name, ::RDoc::Comment.from_document(document)) - incl.record_location ::RDoc::TopLevel.new file + incl = add_include Include.new(name, Comment.from_document(document)) + incl.record_location TopLevel.new file end array[8].each do |type, visibilities| visibilities.each do |visibility, methods| methods.each do |name, file| - method = ::RDoc::AnyMethod.new name, singleton: type == 'class' - method.record_location ::RDoc::TopLevel.new file + method = AnyMethod.new name, singleton: type == 'class' + method.record_location TopLevel.new file method.visibility = visibility add_method method end @@ -450,8 +450,8 @@ def marshal_load(array) # :nodoc: end array[9].each do |name, document, file| - ext = add_extend ::RDoc::Extend.new(name, ::RDoc::Comment.from_document(document)) - ext.record_location ::RDoc::TopLevel.new file + ext = add_extend Extend.new(name, Comment.from_document(document)) + ext.record_location TopLevel.new file end if array[9] # Support Marshal version 1 sections = (array[10] || []).map do |section| @@ -464,7 +464,7 @@ def marshal_load(array) # :nodoc: @in_files = [] (array[11] || []).each do |filename| - record_location ::RDoc::TopLevel.new filename + record_location TopLevel.new filename end @parent_name = array[12] @@ -487,9 +487,9 @@ def merge(class_module) document = document.merge other_document - @comment = ::RDoc::Comment.from_document(document) + @comment = Comment.from_document(document) - @comment_location = if document.parts.first.is_a?(::RDoc::Markup::Document) + @comment_location = if document.parts.first.is_a?(Markup::Document) document.parts.group_by(&:file) else { document.file => [document] } @@ -647,12 +647,12 @@ def parse(comment_location) end end - ::RDoc::Markup::Document.new(*docs) - when ::RDoc::Comment + Markup::Document.new(*docs) + when Comment doc = super comment_location.text, comment_location.format doc.file = comment_location.location doc - when ::RDoc::Markup::Document + when Markup::Document return comment_location else raise ArgumentError, "unknown comment class #{comment_location.class}" @@ -757,7 +757,7 @@ def rebuild_comment_from_location comments.filter_map { |c| c.to_s unless c.empty? } } merged = texts.join("\n---\n") - @comment = merged.empty? ? '' : ::RDoc::Comment.new(merged) + @comment = merged.empty? ? '' : Comment.new(merged) end ## @@ -793,7 +793,7 @@ def superclass def superclass=(superclass) raise NoMethodError, "#{full_name} is a module" if module? case superclass - when ::RDoc::ClassModule + when ClassModule @superclass = superclass.full_name when nil, String @superclass = superclass @@ -856,7 +856,7 @@ def type def update_aliases constants.each do |const| cm = const.is_alias_for - cm ||= const.resolved_alias_target if const.is_a?(::RDoc::Constant) + cm ||= const.resolved_alias_target if const.is_a?(Constant) next unless cm # Resolve chained aliases (A = B = C) to the real class/module. diff --git a/lib/rdoc/code_object/constant.rb b/lib/rdoc/code_object/constant.rb index 0c418eefe7..c517d4b2f9 100644 --- a/lib/rdoc/code_object/constant.rb +++ b/lib/rdoc/code_object/constant.rb @@ -3,7 +3,7 @@ module RDoc ## # A constant - class Constant < ::RDoc::CodeObject + class Constant < CodeObject MARSHAL_VERSION = 0 # :nodoc: @@ -135,7 +135,7 @@ def inspect # :nodoc: def marshal_dump alias_name = case found = is_alias_for - when ::RDoc::CodeObject then found.full_name + when CodeObject then found.full_name else found end @@ -160,7 +160,7 @@ def marshal_dump # * #parent_name def marshal_load(array) - initialize array[1], nil, ::RDoc::Comment.from_document(array[5]) + initialize array[1], nil, Comment.from_document(array[5]) @full_name = array[2] @visibility = array[3] || :public @@ -171,7 +171,7 @@ def marshal_load(array) @parent_class = array[8] @section_title = array[9] - @file = ::RDoc::TopLevel.new array[6] + @file = TopLevel.new array[6] end ## diff --git a/lib/rdoc/code_object/context.rb b/lib/rdoc/code_object/context.rb index 592c47af32..4f28d62600 100644 --- a/lib/rdoc/code_object/context.rb +++ b/lib/rdoc/code_object/context.rb @@ -5,7 +5,7 @@ module RDoc # aliases, requires, and includes. Classes, modules, and files are all # Contexts. - class Context < ::RDoc::CodeObject + class Context < CodeObject include Comparable @@ -156,7 +156,7 @@ def initialize_methods_etc # Contexts are sorted by full_name def <=>(other) - return nil unless ::RDoc::CodeObject === other + return nil unless CodeObject === other full_name <=> other.full_name end @@ -168,11 +168,11 @@ def <=>(other) # Currently only RDoc::Extend and RDoc::Include are supported. def add(klass, name, comment) - if ::RDoc::Extend == klass - ext = ::RDoc::Extend.new name, comment + if Extend == klass + ext = Extend.new name, comment add_extend ext - elsif ::RDoc::Include == klass - incl = ::RDoc::Include.new name, comment + elsif Include == klass + incl = Include.new name, comment add_include incl else raise NotImplementedError, "adding a #{klass} is not implemented" @@ -225,7 +225,7 @@ def add_attribute(attribute) if known known.comment = attribute.comment if known.comment.empty? elsif registered = @methods_hash[attribute.pretty_name + '='] and - ::RDoc::Attr === registered + Attr === registered registered.rw = 'RW' else @methods_hash[key] = attribute @@ -240,7 +240,7 @@ def add_attribute(attribute) if known known.comment = attribute.comment if known.comment.empty? elsif registered = @methods_hash[attribute.pretty_name] and - ::RDoc::Attr === registered + Attr === registered registered.rw = 'RW' else @methods_hash[key] = attribute @@ -332,7 +332,7 @@ def add_class(class_type, given_name, superclass = '::Object') # did we believe it was a module? mod = @store.modules_hash.delete superclass - upgrade_to_class mod, ::RDoc::NormalClass, mod.parent if mod + upgrade_to_class mod, NormalClass, mod.parent if mod # e.g., Object < Object superclass = nil if superclass == full_name @@ -358,7 +358,7 @@ def add_class(class_type, given_name, superclass = '::Object') mod = @store.modules_hash.delete full_name if mod - klass = upgrade_to_class mod, ::RDoc::NormalClass, enclosing + klass = upgrade_to_class mod, NormalClass, enclosing klass.superclass = superclass unless superclass.nil? else @@ -495,7 +495,7 @@ def find_or_create_namespace_path(path) # :nodoc: path.to_s.split('::').inject(self) do |owner, name| owner.classes_hash[name] || owner.modules_hash[name] || - owner.add_module(::RDoc::NormalModule, name) + owner.add_module(NormalModule, name) end end @@ -555,7 +555,7 @@ def add_module_alias(from, to, file) # 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 to.name, nil, new_to.comment + const = Constant.new to.name, nil, new_to.comment const.record_location file const.is_alias_for = from add_constant const @@ -569,7 +569,7 @@ def add_module_alias(from, to, file) def add_require(require) return require unless @document_self - if ::RDoc::TopLevel === self + if TopLevel === self add_to @requires, require else parent.add_require require @@ -633,7 +633,7 @@ def any_content(includes = true) def child_name(name) if name =~ /^:+/ $' #' - elsif ::RDoc::TopLevel === self + elsif TopLevel === self name else "#{self.full_name}::#{name}" @@ -683,7 +683,7 @@ def current_section end def display(method_attr) # :nodoc: - if method_attr.is_a? ::RDoc::Attr + if method_attr.is_a? Attr "#{method_attr.definition} #{method_attr.pretty_name}" else "method #{method_attr.pretty_name}" @@ -899,14 +899,14 @@ def find_symbol_module(symbol) mod = searched.find_module_named(top) break unless mod result = @store.find_class_or_module "#{mod.full_name}::#{suffix}" - break if result || searched.is_a?(::RDoc::TopLevel) + break if result || searched.is_a?(TopLevel) searched = searched.parent end else searched = self while searched do result = searched.find_module_named(symbol) - break if result || searched.is_a?(::RDoc::TopLevel) + break if result || searched.is_a?(TopLevel) searched = searched.parent end end @@ -963,7 +963,7 @@ def methods_by_type(section = nil) TYPES.each do |type| visibilities = {} - ::RDoc::VISIBILITIES.each do |vis| + VISIBILITIES.each do |vis| visibilities[vis] = [] end @@ -1180,7 +1180,7 @@ def to_s # :nodoc: def top_level return @top_level if defined? @top_level @top_level = self - @top_level = @top_level.parent until ::RDoc::TopLevel === @top_level + @top_level = @top_level.parent until TopLevel === @top_level @top_level end @@ -1190,7 +1190,7 @@ def top_level def upgrade_to_class(mod, class_type, enclosing) enclosing.modules_hash.delete mod.name - klass = ::RDoc::ClassModule.from_module class_type, mod + klass = ClassModule.from_module class_type, mod klass.store = @store # if it was there, then we keep it even if done_documenting diff --git a/lib/rdoc/code_object/context/section.rb b/lib/rdoc/code_object/context/section.rb index 51cd08b482..2b0f4f6f7a 100644 --- a/lib/rdoc/code_object/context/section.rb +++ b/lib/rdoc/code_object/context/section.rb @@ -15,7 +15,7 @@ class Context class Section - include ::RDoc::Text + include Text MARSHAL_VERSION = 0 # :nodoc: @@ -67,7 +67,7 @@ def ==(other) def add_comment(comment) Array(comment).each do |c| next if c.nil? - raise TypeError, "unknown comment #{c.inspect}" unless ::RDoc::Comment === c + raise TypeError, "unknown comment #{c.inspect}" unless Comment === c @comments << c unless c.empty? end end @@ -83,7 +83,7 @@ def add_comment(comment) def aref title = @title || '[untitled]' - ::RDoc::Text.to_anchor(title) + Text.to_anchor(title) end ## @@ -134,7 +134,7 @@ def marshal_load(array) @parent = nil @title = array[1] - @comments = array[2].parts.map { |doc| ::RDoc::Comment.from_document(doc) } + @comments = array[2].parts.map { |doc| Comment.from_document(doc) } end ## @@ -142,7 +142,7 @@ def marshal_load(array) # multiple RDoc::Markup::Documents with their file set. def to_document - ::RDoc::Markup::Document.new(*@comments.map(&:parse)) + Markup::Document.new(*@comments.map(&:parse)) end ## @@ -159,7 +159,7 @@ def plain_html def comment return nil if @comments.empty? - ::RDoc::Comment.from_document(to_document) + Comment.from_document(to_document) end def description diff --git a/lib/rdoc/code_object/extend.rb b/lib/rdoc/code_object/extend.rb index bde2f6f5a9..41ae8bf8b2 100644 --- a/lib/rdoc/code_object/extend.rb +++ b/lib/rdoc/code_object/extend.rb @@ -5,7 +5,7 @@ module RDoc # # RDoc::Extend.new 'Enumerable', 'comment ...' - class Extend < ::RDoc::Mixin + class Extend < Mixin end end diff --git a/lib/rdoc/code_object/include.rb b/lib/rdoc/code_object/include.rb index bc49d7f4af..d3d9c96aac 100644 --- a/lib/rdoc/code_object/include.rb +++ b/lib/rdoc/code_object/include.rb @@ -5,7 +5,7 @@ module RDoc # # RDoc::Include.new 'Enumerable', 'comment ...' - class Include < ::RDoc::Mixin + class Include < Mixin end end diff --git a/lib/rdoc/code_object/method_attr.rb b/lib/rdoc/code_object/method_attr.rb index 396df01f6a..a760a89aae 100644 --- a/lib/rdoc/code_object/method_attr.rb +++ b/lib/rdoc/code_object/method_attr.rb @@ -3,7 +3,7 @@ module RDoc ## # Abstract class representing either a method or an attribute. - class MethodAttr < ::RDoc::CodeObject + class MethodAttr < CodeObject include Comparable diff --git a/lib/rdoc/code_object/mixin.rb b/lib/rdoc/code_object/mixin.rb index df2588b438..0a4d8f8bb4 100644 --- a/lib/rdoc/code_object/mixin.rb +++ b/lib/rdoc/code_object/mixin.rb @@ -4,7 +4,7 @@ module RDoc # A Mixin adds features from a module into another context. RDoc::Include and # RDoc::Extend are both mixins. - class Mixin < ::RDoc::CodeObject + class Mixin < CodeObject ## # Name of included module @@ -41,7 +41,7 @@ def ==(other) # :nodoc: def full_name m = self.module - ::RDoc::ClassModule === m ? m.full_name : @name + ClassModule === m ? m.full_name : @name end def hash # :nodoc: diff --git a/lib/rdoc/code_object/normal_class.rb b/lib/rdoc/code_object/normal_class.rb index 527a812fab..b8da1a7258 100644 --- a/lib/rdoc/code_object/normal_class.rb +++ b/lib/rdoc/code_object/normal_class.rb @@ -3,7 +3,7 @@ module RDoc ## # A normal class, neither singleton nor anonymous - class NormalClass < ::RDoc::ClassModule + class NormalClass < ClassModule ## # The ancestors of this class including modules. Unlike Module#ancestors, diff --git a/lib/rdoc/code_object/normal_module.rb b/lib/rdoc/code_object/normal_module.rb index ade8a0ffaf..956df492b1 100644 --- a/lib/rdoc/code_object/normal_module.rb +++ b/lib/rdoc/code_object/normal_module.rb @@ -3,7 +3,7 @@ module RDoc ## # A normal module, like NormalClass - class NormalModule < ::RDoc::ClassModule + class NormalModule < ClassModule def aref_prefix # :nodoc: 'module' diff --git a/lib/rdoc/code_object/require.rb b/lib/rdoc/code_object/require.rb index 9d0e9c23c5..ed8f595ddd 100644 --- a/lib/rdoc/code_object/require.rb +++ b/lib/rdoc/code_object/require.rb @@ -3,7 +3,7 @@ module RDoc ## # A file loaded by \#require - class Require < ::RDoc::CodeObject + class Require < CodeObject ## # Name of the required file @@ -38,11 +38,11 @@ def to_s # :nodoc: def top_level @top_level ||= begin - tl = ::RDoc::TopLevel.all_files_hash[name + '.rb'] + tl = TopLevel.all_files_hash[name + '.rb'] - if tl.nil? and ::RDoc::TopLevel.all_files.first.full_name =~ %r(^lib/) + if tl.nil? and TopLevel.all_files.first.full_name =~ %r(^lib/) # second chance - tl = ::RDoc::TopLevel.all_files_hash['lib/' + name + '.rb'] + tl = TopLevel.all_files_hash['lib/' + name + '.rb'] end tl diff --git a/lib/rdoc/code_object/single_class.rb b/lib/rdoc/code_object/single_class.rb index 6cef965bab..273a39b0e3 100644 --- a/lib/rdoc/code_object/single_class.rb +++ b/lib/rdoc/code_object/single_class.rb @@ -3,7 +3,7 @@ module RDoc ## # A singleton class - class SingleClass < ::RDoc::ClassModule + class SingleClass < ClassModule ## # Adds the superclass to the included modules. diff --git a/lib/rdoc/code_object/top_level.rb b/lib/rdoc/code_object/top_level.rb index 5a2ea84d29..3f5e2d0822 100644 --- a/lib/rdoc/code_object/top_level.rb +++ b/lib/rdoc/code_object/top_level.rb @@ -3,7 +3,7 @@ module RDoc ## # A TopLevel context is a representation of the contents of a single file - class TopLevel < ::RDoc::Context + class TopLevel < Context MARSHAL_VERSION = 0 # :nodoc: @@ -202,7 +202,7 @@ def marshal_load(array) # :nodoc: initialize array[1] @parser = array[2] - @comment = ::RDoc::Comment.from_document array[3] + @comment = Comment.from_document array[3] end ## @@ -212,7 +212,7 @@ def marshal_load(array) # :nodoc: def object_class @object_class ||= begin - oc = @store.find_class_named('Object') || add_class(::RDoc::NormalClass, 'Object') + oc = @store.find_class_named('Object') || add_class(NormalClass, 'Object') oc.record_location self oc end @@ -251,7 +251,7 @@ def pretty_print(q) # :nodoc: # Use #search_snippet instead for getting documentation snippets. def search_record - return unless @parser < ::RDoc::Parser::Text + return unless @parser < Parser::Text [ page_name, @@ -277,7 +277,7 @@ def search_snippet # Is this TopLevel from a text file instead of a source code file? def text? - @parser and @parser.include? ::RDoc::Parser::Text + @parser and @parser.include? Parser::Text end def to_s # :nodoc: diff --git a/lib/rdoc/comment.rb b/lib/rdoc/comment.rb index af17c62dc8..70b3741e08 100644 --- a/lib/rdoc/comment.rb +++ b/lib/rdoc/comment.rb @@ -12,7 +12,7 @@ module RDoc class Comment - include ::RDoc::Text + include Text ## # The format of this comment. Defaults to RDoc::Markup @@ -151,7 +151,7 @@ def parse # 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 + raise Error, 'replacing document-only comment is not allowed' if @text.nil? and @document @document = nil @@ -180,9 +180,9 @@ class << self # Create a new parsed comment from a document def from_document(document) # :nodoc: - comment = ::RDoc::Comment.new('') + comment = Comment.new('') comment.document = document - comment.location = ::RDoc::TopLevel.new(document.file) if document.file + comment.location = TopLevel.new(document.file) if document.file comment end diff --git a/lib/rdoc/cross_reference.rb b/lib/rdoc/cross_reference.rb index e85327f7a1..c88d796ead 100644 --- a/lib/rdoc/cross_reference.rb +++ b/lib/rdoc/cross_reference.rb @@ -167,7 +167,7 @@ def resolve_local_symbol(name) end if container - unless ::RDoc::TopLevel === container + unless TopLevel === container if '.' == type if 'new' == symbol # AnyClassName.new will be class method ref = container.find_local_symbol symbol @@ -204,7 +204,7 @@ def resolve(name) # 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 + ref = nil if Alias === ref # external alias, can't link to it ref = nil unless ref&.display? diff --git a/lib/rdoc/encoding.rb b/lib/rdoc/encoding.rb index 1f1c479f2f..2ecdad7f17 100644 --- a/lib/rdoc/encoding.rb +++ b/lib/rdoc/encoding.rb @@ -36,8 +36,8 @@ def self.read_file(filename, encoding, force_transcode = false) utf8 = content.sub!(/\A\xef\xbb\xbf/, '') - enc = ::RDoc::Encoding.detect_encoding content - content = ::RDoc::Encoding.change_encoding content, enc if enc + enc = Encoding.detect_encoding content + content = Encoding.change_encoding content, enc if enc begin encoding ||= ::Encoding.default_external @@ -46,16 +46,16 @@ def self.read_file(filename, encoding, force_transcode = false) if not orig_encoding.ascii_compatible? content = content.encode encoding elsif utf8 - content = ::RDoc::Encoding.change_encoding content, ::Encoding::UTF_8 + content = Encoding.change_encoding content, ::Encoding::UTF_8 content = content.encode encoding else # assume the content is in our output encoding - content = ::RDoc::Encoding.change_encoding content, encoding + content = Encoding.change_encoding content, encoding end unless content.valid_encoding? # revert and try to transcode - content = ::RDoc::Encoding.change_encoding content, orig_encoding + content = Encoding.change_encoding content, orig_encoding content = content.encode encoding end @@ -66,7 +66,7 @@ def self.read_file(filename, encoding, force_transcode = false) rescue ::Encoding::InvalidByteSequenceError, ::Encoding::UndefinedConversionError => e if force_transcode - content = ::RDoc::Encoding.change_encoding content, orig_encoding + content = Encoding.change_encoding content, orig_encoding content = content.encode(encoding, :invalid => :replace, :undef => :replace, @@ -111,7 +111,7 @@ def self.remove_magic_comment(string) # string def self.change_encoding(text, encoding) - if text.kind_of? ::RDoc::Comment + if text.kind_of? Comment text.encode! encoding else String.new text, encoding: encoding diff --git a/lib/rdoc/generator/aliki.rb b/lib/rdoc/generator/aliki.rb index fea23b6cfb..5157283b3a 100644 --- a/lib/rdoc/generator/aliki.rb +++ b/lib/rdoc/generator/aliki.rb @@ -10,10 +10,10 @@ module Generator # Author: Stan Lo # - class Aliki < ::RDoc::Generator::Darkfish + class Aliki < Generator::Darkfish DESCRIPTION = 'HTML generator, written by Stan Lo' - ::RDoc::RDoc.add_generator self + RDoc.add_generator self def initialize(store, options) super @@ -129,7 +129,7 @@ def type_signature_html(method_attr, from_path) lines = method_attr.type_signature_lines || @store.rbs_signature_for(method_attr) return unless lines - ::RDoc::RbsHelper.signature_to_html( + RbsHelper.signature_to_html( lines, lookup: @store.type_name_lookup, from_path: from_path @@ -159,8 +159,8 @@ def template_encoding def build_class_module_entry(klass) type = case klass - when ::RDoc::NormalClass then 'class' - when ::RDoc::NormalModule then 'module' + when NormalClass then 'class' + when NormalModule then 'module' else 'class' end diff --git a/lib/rdoc/generator/darkfish.rb b/lib/rdoc/generator/darkfish.rb index 3766533476..1919b9aa0f 100644 --- a/lib/rdoc/generator/darkfish.rb +++ b/lib/rdoc/generator/darkfish.rb @@ -56,7 +56,7 @@ module Generator class Darkfish - ::RDoc::RDoc.add_generator self + RDoc.add_generator self include ERB::Util @@ -164,7 +164,7 @@ def initialize(store, options) @methods = nil @modsort = nil - @json_index = ::RDoc::Generator::JsonIndex.new self, options + @json_index = Generator::JsonIndex.new self, options end ## @@ -300,7 +300,7 @@ def generate_index here end rescue => e - error = ::RDoc::Error.new \ + error = Error.new \ "error generating index.html: #{e.message} (#{e.class})" error.set_backtrace e.backtrace @@ -361,7 +361,7 @@ def generate_class_files generate_class klass, template_file end rescue => e - error = ::RDoc::Error.new \ + error = Error.new \ "error generating #{current.path}: #{e.message} (#{e.class})" error.set_backtrace e.backtrace @@ -431,7 +431,7 @@ def generate_file_files end rescue => e error = - ::RDoc::Error.new "error generating #{out_file}: #{e.message} (#{e.class})" + Error.new "error generating #{out_file}: #{e.message} (#{e.class})" error.set_backtrace e.backtrace raise error @@ -488,7 +488,7 @@ def generate_servlet_not_found(message) here end rescue => e - error = ::RDoc::Error.new \ + error = Error.new \ "error generating servlet_not_found: #{e.message} (#{e.class})" error.set_backtrace e.backtrace @@ -513,7 +513,7 @@ def generate_servlet_root(installed) render_template template_file do |io| binding end rescue => e - error = ::RDoc::Error.new \ + error = Error.new \ "error generating servlet_root: #{e.message} (#{e.class})" error.set_backtrace e.backtrace @@ -545,7 +545,7 @@ def generate_table_of_contents here end rescue => e - error = ::RDoc::Error.new \ + error = Error.new \ "error generating table_of_contents.html: #{e.message} (#{e.class})" error.set_backtrace e.backtrace @@ -624,7 +624,7 @@ def assemble_template(body_file) def render(file_name) template_file = @template_dir + file_name - template = template_for template_file, false, ::RDoc::ERBPartial + template = template_for template_file, false, ERBPartial template.filename = template_file.to_s @@ -641,7 +641,7 @@ def render(file_name) 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 + erb_klass = io_output ? ERBIO : ERB template = template_for template_file, true, erb_klass @@ -677,7 +677,7 @@ def template_result(template, context, template_file) template.filename = template_file.to_s template.result context rescue NoMethodError => e - raise ::RDoc::Error, "Error while evaluating %s: %s" % [ + raise Error, "Error while evaluating %s: %s" % [ template_file.expand_path, e.message, ], e.backtrace @@ -717,7 +717,7 @@ def template_for(file, page = true, klass = ERB) # Returns an excerpt of the comment for usage in meta description tags def excerpt(comment) text = case comment - when ::RDoc::Comment + when Comment comment.text else comment @@ -749,7 +749,7 @@ def generate_ancestor_list(ancestors, klass) ancestor = ancestors.shift content = +'
    • ' - if ancestor.is_a?(::RDoc::NormalClass) + if ancestor.is_a?(NormalClass) content << "#{ancestor.full_name}" else content << ancestor.to_s diff --git a/lib/rdoc/generator/json_index.rb b/lib/rdoc/generator/json_index.rb index e21ba66bc2..cd4ea688cd 100644 --- a/lib/rdoc/generator/json_index.rb +++ b/lib/rdoc/generator/json_index.rb @@ -78,7 +78,7 @@ module Generator class JsonIndex - include ::RDoc::Text + include Text ## # Where the search index lives in the generated output diff --git a/lib/rdoc/generator/markup.rb b/lib/rdoc/generator/markup.rb index d617a5820f..944cd1c1a3 100644 --- a/lib/rdoc/generator/markup.rb +++ b/lib/rdoc/generator/markup.rb @@ -37,7 +37,7 @@ def formatter return @formatter if defined? @formatter options = @store.options - this = ::RDoc::Context === self ? self : @parent + this = Context === self ? self : @parent @formatter = ::RDoc::Markup::ToHtmlCrossref.new( this.path, this, @@ -84,7 +84,7 @@ def canonical_url module RDoc class CodeObject - include ::RDoc::Generator::Markup + include Generator::Markup end end @@ -149,7 +149,7 @@ def add_location_comment(src) def markup_code return '' if !@token_stream - src = ::RDoc::TokenStream.to_html @token_stream + src = TokenStream.to_html @token_stream # dedent the source common_indent = src.length @@ -187,7 +187,7 @@ module RDoc class Context class Section - include ::RDoc::Generator::Markup + include Generator::Markup end end diff --git a/lib/rdoc/generator/pot.rb b/lib/rdoc/generator/pot.rb index f6f9d0c73f..5f8d7ee35d 100644 --- a/lib/rdoc/generator/pot.rb +++ b/lib/rdoc/generator/pot.rb @@ -57,7 +57,7 @@ module Generator class POT - ::RDoc::RDoc.add_generator self + RDoc.add_generator self ## # Description of this generator diff --git a/lib/rdoc/generator/pot/message_extractor.rb b/lib/rdoc/generator/pot/message_extractor.rb index 68328d06a7..c15ca81f3a 100644 --- a/lib/rdoc/generator/pot/message_extractor.rb +++ b/lib/rdoc/generator/pot/message_extractor.rb @@ -12,7 +12,7 @@ class MessageExtractor def initialize(store) @store = store - @po = ::RDoc::Generator::POT::PO.new + @po = Generator::POT::PO.new end ## @@ -58,14 +58,14 @@ def extract_text(text, comment, location = nil) :extracted_comment => comment, :references => [location].compact, } - i18n_text = ::RDoc::I18n::Text.new(text) + i18n_text = I18n::Text.new(text) i18n_text.extract_messages do |part| @po.add(entry(part[:paragraph], options)) end end def entry(msgid, options) - ::RDoc::Generator::POT::POEntry.new(msgid, options) + Generator::POT::POEntry.new(msgid, options) end end diff --git a/lib/rdoc/generator/pot/po.rb b/lib/rdoc/generator/pot/po.rb index 95be17a0b5..000f69c65e 100644 --- a/lib/rdoc/generator/pot/po.rb +++ b/lib/rdoc/generator/pot/po.rb @@ -65,7 +65,7 @@ def header_entry :translator_comment => comment, :flags => ['fuzzy'], } - ::RDoc::Generator::POT::POEntry.new('', options) + Generator::POT::POEntry.new('', options) end def sort_entries diff --git a/lib/rdoc/generator/ri.rb b/lib/rdoc/generator/ri.rb index a6d2b00d30..d1f74b8abe 100644 --- a/lib/rdoc/generator/ri.rb +++ b/lib/rdoc/generator/ri.rb @@ -6,7 +6,7 @@ module Generator class RI - ::RDoc::RDoc.add_generator self + RDoc.add_generator self ## # Description of this generator diff --git a/lib/rdoc/generator/template/aliki/_footer.rhtml b/lib/rdoc/generator/template/aliki/_footer.rhtml index d64c3797d3..ae2f831443 100644 --- a/lib/rdoc/generator/template/aliki/_footer.rhtml +++ b/lib/rdoc/generator/template/aliki/_footer.rhtml @@ -16,7 +16,7 @@ diff --git a/lib/rdoc/generator/template/aliki/_head.rhtml b/lib/rdoc/generator/template/aliki/_head.rhtml index 4412f40531..96ba1d65e5 100644 --- a/lib/rdoc/generator/template/aliki/_head.rhtml +++ b/lib/rdoc/generator/template/aliki/_head.rhtml @@ -112,52 +112,52 @@ <%- @options.template_stylesheets.each do |stylesheet| %> <%- end %> diff --git a/lib/rdoc/generator/template/darkfish/_footer.rhtml b/lib/rdoc/generator/template/darkfish/_footer.rhtml index db483866ba..3ccaa2d836 100644 --- a/lib/rdoc/generator/template/darkfish/_footer.rhtml +++ b/lib/rdoc/generator/template/darkfish/_footer.rhtml @@ -1,5 +1,5 @@ diff --git a/lib/rdoc/i18n/text.rb b/lib/rdoc/i18n/text.rb index 5a68a78126..f4579ffcda 100644 --- a/lib/rdoc/i18n/text.rb +++ b/lib/rdoc/i18n/text.rb @@ -89,7 +89,7 @@ def parse(&block) def each_line(raw, &block) case raw - when ::RDoc::Comment + when Comment raw.text.each_line(&block) when Hash raw.each_value do |comments| diff --git a/lib/rdoc/markup.rb b/lib/rdoc/markup.rb index dcea269806..ea89b4600a 100644 --- a/lib/rdoc/markup.rb +++ b/lib/rdoc/markup.rb @@ -120,8 +120,8 @@ class Markup # Parses +str+ into an RDoc::Markup::Document. def self.parse(str) - ::RDoc::Markup::Parser.parse str - rescue ::RDoc::Markup::Parser::Error => e + Markup::Parser.parse str + rescue Markup::Parser::Error => e $stderr.puts <<-EOF While parsing markup, RDoc encountered a #{e.class}: @@ -132,7 +132,7 @@ def self.parse(str) #{text} ---8<--- -RDoc #{::RDoc::VERSION} +RDoc #{VERSION} Ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL} #{RUBY_RELEASE_DATE} @@ -172,10 +172,10 @@ def add_regexp_handling(pattern, name) def convert(input, formatter) document = case input - when ::RDoc::Markup::Document + when Markup::Document input else - ::RDoc::Markup::Parser.parse input + Markup::Parser.parse input end document.accept formatter diff --git a/lib/rdoc/markup/block_quote.rb b/lib/rdoc/markup/block_quote.rb index ee3b3566e9..79b7d37cb6 100644 --- a/lib/rdoc/markup/block_quote.rb +++ b/lib/rdoc/markup/block_quote.rb @@ -4,7 +4,7 @@ class Markup ## # A quoted section which contains markup items. - class BlockQuote < ::RDoc::Markup::Raw + class BlockQuote < Markup::Raw ## # Calls #accept_block_quote on +visitor+ diff --git a/lib/rdoc/markup/document.rb b/lib/rdoc/markup/document.rb index 50489e31e7..7d0f8ea396 100644 --- a/lib/rdoc/markup/document.rb +++ b/lib/rdoc/markup/document.rb @@ -41,10 +41,10 @@ def initialize(*parts) def <<(part) case part - when ::RDoc::Markup::Document + when Markup::Document unless part.empty? parts.concat part.parts - parts << ::RDoc::Markup::BlankLine.new + parts << Markup::BlankLine.new end when String raise ArgumentError, @@ -98,7 +98,7 @@ def empty? def file=(location) @file = case location - when ::RDoc::TopLevel + when TopLevel location.relative_name else location @@ -134,7 +134,7 @@ def merge(other) # Does this Document contain other Documents? def merged? - ::RDoc::Markup::Document === @parts.first + Markup::Document === @parts.first end def pretty_print(q) # :nodoc: @@ -160,7 +160,7 @@ def push(*parts) # Require 'rdoc/markup/formatter' before calling this method. def table_of_contents - accept ::RDoc::Markup::ToTableOfContents.to_toc + accept Markup::ToTableOfContents.to_toc end end diff --git a/lib/rdoc/markup/formatter.rb b/lib/rdoc/markup/formatter.rb index e0bcd393c1..b2e7435ecb 100644 --- a/lib/rdoc/markup/formatter.rb +++ b/lib/rdoc/markup/formatter.rb @@ -44,7 +44,7 @@ def self.gen_relative_url(path, target) # Creates a new Formatter def initialize - @markup = ::RDoc::Markup.new + @markup = Markup.new @from_path = '.' end @@ -55,7 +55,7 @@ def initialize def accept_document(document) document.parts.each do |item| case item - when ::RDoc::Markup::Document # HACK + when Markup::Document # HACK accept_document item else item.accept self @@ -198,7 +198,7 @@ def handle_TIDYLINK(label_part, url) # Parses inline +text+, traverse the resulting nodes, and calls the appropriate handler methods. def handle_inline(text) - nodes = ::RDoc::Markup::InlineParser.new(text).parse + nodes = Markup::InlineParser.new(text).parse traverse_inline_nodes(nodes) end diff --git a/lib/rdoc/markup/indented_paragraph.rb b/lib/rdoc/markup/indented_paragraph.rb index 6ab581b008..6b923fed50 100644 --- a/lib/rdoc/markup/indented_paragraph.rb +++ b/lib/rdoc/markup/indented_paragraph.rb @@ -4,7 +4,7 @@ class Markup ## # An Indented Paragraph of text - class IndentedParagraph < ::RDoc::Markup::Raw + class IndentedParagraph < Markup::Raw ## # The indent in number of spaces @@ -38,7 +38,7 @@ def accept(visitor) def text(hard_break = nil) @parts.map do |part| - if ::RDoc::Markup::HardBreak === part + if Markup::HardBreak === part '%1$s%3$*2$s' % [hard_break, @indent, ' '] if hard_break else part diff --git a/lib/rdoc/markup/paragraph.rb b/lib/rdoc/markup/paragraph.rb index 7fb0f03164..ae8357290e 100644 --- a/lib/rdoc/markup/paragraph.rb +++ b/lib/rdoc/markup/paragraph.rb @@ -4,7 +4,7 @@ class Markup ## # A Paragraph of text - class Paragraph < ::RDoc::Markup::Raw + class Paragraph < Markup::Raw ## # Calls #accept_paragraph on +visitor+ @@ -19,7 +19,7 @@ def accept(visitor) def text(hard_break = '') @parts.map do |part| - if ::RDoc::Markup::HardBreak === part + if Markup::HardBreak === part hard_break else part diff --git a/lib/rdoc/markup/parser.rb b/lib/rdoc/markup/parser.rb index b91e369378..9e26611eb5 100644 --- a/lib/rdoc/markup/parser.rb +++ b/lib/rdoc/markup/parser.rb @@ -20,7 +20,7 @@ class Markup class Parser - include ::RDoc::Text + include Text ## # List token types @@ -62,7 +62,7 @@ class ParseError < Error; end def self.parse(str) parser = new parser.tokenize str - doc = ::RDoc::Markup::Document.new + doc = Markup::Document.new parser.parse doc end @@ -101,7 +101,7 @@ def build_heading(level) '' end - ::RDoc::Markup::Heading.new level, text + Markup::Heading.new level, text end ## @@ -110,7 +110,7 @@ def build_heading(level) def build_list(margin) p :list_start => margin if @debug - list = ::RDoc::Markup::List.new + list = Markup::List.new label = nil until @tokens.empty? do @@ -181,7 +181,7 @@ def build_list(margin) label = nil end - list_item = ::RDoc::Markup::ListItem.new data + list_item = Markup::ListItem.new data parse list_item, column list << list_item @@ -197,7 +197,7 @@ def build_list(margin) return nil unless label return nil unless [:LABEL, :NOTE].include? list.type - list_item = ::RDoc::Markup::ListItem.new label, ::RDoc::Markup::BlankLine.new + list_item = Markup::ListItem.new label, Markup::BlankLine.new list << list_item end @@ -210,7 +210,7 @@ def build_list(margin) def build_paragraph(margin) p :paragraph_start => margin if @debug - paragraph = ::RDoc::Markup::Paragraph.new + paragraph = Markup::Paragraph.new until @tokens.empty? do type, data, column, = get @@ -244,7 +244,7 @@ def build_paragraph(margin) def build_verbatim(margin) p :verbatim_begin => margin if @debug - verbatim = ::RDoc::Markup::Verbatim.new + verbatim = Markup::Verbatim.new min_indent = nil generate_leading_spaces = true @@ -349,12 +349,12 @@ def parse(parent, indent = 0) case type when :BREAK - parent << ::RDoc::Markup::BlankLine.new + parent << Markup::BlankLine.new skip :NEWLINE, false next when :NEWLINE # trailing newlines are skipped below, so this is a blank line - parent << ::RDoc::Markup::BlankLine.new + parent << Markup::BlankLine.new skip :NEWLINE, false next end @@ -374,7 +374,7 @@ def parse(parent, indent = 0) when :HEADER parent << build_heading(data) when :RULE - parent << ::RDoc::Markup::Rule.new(data) + parent << Markup::Rule.new(data) skip :NEWLINE when :TEXT unget @@ -382,7 +382,7 @@ def parse(parent, indent = 0) when :BLOCKQUOTE nil while (type, = get; type) and type != :NEWLINE _, _, column, = peek_token - bq = ::RDoc::Markup::BlockQuote.new + bq = Markup::BlockQuote.new p :blockquote_start => [data, column] if @debug parse bq, column p :blockquote_end => indent if @debug diff --git a/lib/rdoc/markup/pre_process.rb b/lib/rdoc/markup/pre_process.rb index c385a7778c..9e2c6ed81e 100644 --- a/lib/rdoc/markup/pre_process.rb +++ b/lib/rdoc/markup/pre_process.rb @@ -99,7 +99,7 @@ def initialize(input_file_name, include_path) # RDoc::CodeObject#metadata for details. def handle(text, code_object = nil, &block) - if ::RDoc::Comment === text + if Comment === text comment = text text = text.text end @@ -142,7 +142,7 @@ def run_pre_processes(comment_text, code_object, start_line_no, type) directives.each do |directive, (param, line_no)| handle_directive('', directive, param, code_object) end - if code_object.is_a?(::RDoc::AnyMethod) && (call_seq, = directives['call-seq']) && call_seq + if code_object.is_a?(AnyMethod) && (call_seq, = directives['call-seq']) && call_seq code_object.call_seq = call_seq.lines.map(&:chomp).reject(&:empty?).join("\n") end format, = directives['markup'] @@ -160,7 +160,7 @@ def run_post_processes(comment, code_object) # Parse comment and return [normalized_comment_text, directives_hash] def parse_comment(text, line_no, type) - ::RDoc::Comment.parse(text, @input_file_name, line_no, type) do |filename, prefix_indent| + Comment.parse(text, @input_file_name, line_no, type) do |filename, prefix_indent| include_file(filename, prefix_indent, text.encoding) end end @@ -183,16 +183,16 @@ def handle_directive(prefix, directive, param, code_object = nil, case directive when 'arg', 'args' - return "#{prefix}:#{directive}: #{param}\n" unless code_object && code_object.kind_of?(::RDoc::AnyMethod) + return "#{prefix}:#{directive}: #{param}\n" unless code_object && code_object.kind_of?(AnyMethod) code_object.params = param blankline when 'category' - if ::RDoc::Context === code_object + if Context === code_object section = code_object.add_section param code_object.temporary_section = section - elsif ::RDoc::AnyMethod === code_object + elsif AnyMethod === code_object code_object.section_title = param end @@ -218,7 +218,7 @@ def handle_directive(prefix, directive, param, code_object = nil, blankline when 'notnew', 'not_new', 'not-new' - return blankline unless ::RDoc::AnyMethod === code_object + return blankline unless AnyMethod === code_object code_object.dont_rename_initialize = true @@ -251,8 +251,8 @@ def handle_directive(prefix, directive, param, code_object = nil, when nil code_object.metadata[directive] = param if code_object - if ::RDoc::Markup::PreProcess.registered.include? directive - handler = ::RDoc::Markup::PreProcess.registered[directive] + if Markup::PreProcess.registered.include? directive + handler = Markup::PreProcess.registered[directive] result = handler.call directive, param if handler else result = "#{prefix}:#{directive}: #{param}\n" @@ -288,8 +288,8 @@ def include_file(name, indent, encoding) return '' end - content = ::RDoc::Encoding.read_file full_name, encoding, true - content = ::RDoc::Encoding.remove_magic_comment content + content = Encoding.read_file full_name, encoding, true + content = Encoding.remove_magic_comment content # strip magic comment content = content.sub(/\A# .*coding[=:].*$/, '').lstrip diff --git a/lib/rdoc/markup/to_ansi.rb b/lib/rdoc/markup/to_ansi.rb index 115781c6ea..a470c29bfa 100644 --- a/lib/rdoc/markup/to_ansi.rb +++ b/lib/rdoc/markup/to_ansi.rb @@ -4,7 +4,7 @@ class Markup ## # Outputs RDoc markup with vibrant ANSI color! - class ToAnsi < ::RDoc::Markup::ToRdoc + class ToAnsi < Markup::ToRdoc ## # Creates a new ToAnsi visitor that is ready to output vibrant ANSI color! diff --git a/lib/rdoc/markup/to_bs.rb b/lib/rdoc/markup/to_bs.rb index f23960438b..0aaf70e6f0 100644 --- a/lib/rdoc/markup/to_bs.rb +++ b/lib/rdoc/markup/to_bs.rb @@ -7,7 +7,7 @@ class Markup # # This formatter won't work on 1.8.6 because it lacks String#chars. - class ToBs < ::RDoc::Markup::ToRdoc + class ToBs < Markup::ToRdoc ## # Returns a new ToBs that is ready for hot backspace action! diff --git a/lib/rdoc/markup/to_html.rb b/lib/rdoc/markup/to_html.rb index 09daf525d4..474a74ccc3 100644 --- a/lib/rdoc/markup/to_html.rb +++ b/lib/rdoc/markup/to_html.rb @@ -8,9 +8,9 @@ class Markup ## # Outputs RDoc markup as HTML. - class ToHtml < ::RDoc::Markup::Formatter + class ToHtml < Markup::Formatter - include ::RDoc::Text + include Text # :section: Utilities @@ -429,7 +429,7 @@ def accept_paragraph(paragraph) def parsable_text_to_html(text) tokens = ::RDoc::Parser::RubyColorizer.colorize(text) - result = ::RDoc::TokenStream.to_html tokens + result = TokenStream.to_html tokens result = result + "\n" unless result.end_with?("\n") result end @@ -633,7 +633,7 @@ def gen_url(url, text) def html_list_name(list_type, open_tag) tags = LIST_TYPE_TO_HTML[list_type] - raise ::RDoc::Error, "Invalid list type: #{list_type.inspect}" unless tags + raise Error, "Invalid list type: #{list_type.inspect}" unless tags tags[open_tag ? 0 : 1] end @@ -650,7 +650,7 @@ def list_item_start(list_item, list_type) "
      #{to_html label}
      \n" end.join << "
      " else - raise ::RDoc::Error, "Invalid list type: #{list_type.inspect}" + raise Error, "Invalid list type: #{list_type.inspect}" end end @@ -664,7 +664,7 @@ def list_end_for(list_type) when :LABEL, :NOTE "
      " else - raise ::RDoc::Error, "Invalid list type: #{list_type.inspect}" + raise Error, "Invalid list type: #{list_type.inspect}" end end diff --git a/lib/rdoc/markup/to_html_crossref.rb b/lib/rdoc/markup/to_html_crossref.rb index 4644286105..613ee30913 100644 --- a/lib/rdoc/markup/to_html_crossref.rb +++ b/lib/rdoc/markup/to_html_crossref.rb @@ -6,11 +6,11 @@ class Markup # names, classes, etc to create links. RDoc::CrossReference is used to # generate those links based on the current context. - class ToHtmlCrossref < ::RDoc::Markup::ToHtml + class ToHtmlCrossref < Markup::ToHtml # :stopdoc: - ALL_CROSSREF_REGEXP = ::RDoc::CrossReference::ALL_CROSSREF_REGEXP - CROSSREF_REGEXP = ::RDoc::CrossReference::CROSSREF_REGEXP + ALL_CROSSREF_REGEXP = CrossReference::ALL_CROSSREF_REGEXP + CROSSREF_REGEXP = CrossReference::CROSSREF_REGEXP # :startdoc: ## @@ -43,7 +43,7 @@ def initialize(from_path, context, pipe: false, output_decoration: true, @autolink_excluded_words = autolink_excluded_words @warn_missing_rdoc_ref = warn_missing_rdoc_ref - @cross_reference = ::RDoc::CrossReference.new @context + @cross_reference = CrossReference.new @context end # :nodoc: @@ -67,7 +67,7 @@ def cross_reference(name, text = nil, code = true, rdoc_ref: false) if !display.end_with?('+@', '-@') && match = display.match(/(.*[^#:])?@(.*)/) context_name = match[1] - label = convert_string(::RDoc::Text.decode_legacy_label(match[2])) + label = convert_string(Text.decode_legacy_label(match[2])) text ||= "#{label} at #{convert_string(context_name)}" if context_name text ||= label code = false @@ -166,14 +166,14 @@ def link(name, html_string, code = true, rdoc_ref: false) # Non-text source files (C, Ruby, etc.) don't get HTML pages generated, # so don't auto-link to them. Explicit rdoc-ref: links are still allowed. - if !rdoc_ref && ::RDoc::TopLevel === ref && !ref.text? + if !rdoc_ref && TopLevel === ref && !ref.text? return end if ref path = ref.as_href(@from_path) - if code and ::RDoc::CodeObject === ref and !(::RDoc::TopLevel === ref) + if code and CodeObject === ref and !(TopLevel === ref) html_string = "#{html_string}" end elsif name @@ -190,8 +190,8 @@ def link(name, html_string, code = true, rdoc_ref: false) if label # Decode legacy labels (e.g., "What-27s+Here" -> "What's Here") # then convert to GitHub-style anchor format - decoded_label = ::RDoc::Text.decode_legacy_label(label) - formatted_label = ::RDoc::Text.to_anchor(decoded_label) + decoded_label = Text.decode_legacy_label(label) + formatted_label = Text.to_anchor(decoded_label) # Case 1: Path already has an anchor (e.g., method link) # Input: C1#method@label -> path="C1.html#method-i-m" diff --git a/lib/rdoc/markup/to_html_snippet.rb b/lib/rdoc/markup/to_html_snippet.rb index 7f2339ff9e..3d184d6fff 100644 --- a/lib/rdoc/markup/to_html_snippet.rb +++ b/lib/rdoc/markup/to_html_snippet.rb @@ -4,7 +4,7 @@ class Markup ## # Outputs RDoc markup as paragraphs with inline markup only. - class ToHtmlSnippet < ::RDoc::Markup::ToHtml + class ToHtmlSnippet < Markup::ToHtml ## # After this many characters the input will be cut off. @@ -46,7 +46,7 @@ def initialize(characters = 100, paragraphs = 3) @mask = 0 @paragraphs = 0 - @markup.add_regexp_handling ::RDoc::CrossReference::CROSSREF_REGEXP, :CROSSREF + @markup.add_regexp_handling CrossReference::CROSSREF_REGEXP, :CROSSREF end ## @@ -113,7 +113,7 @@ def accept_verbatim(verbatim) @characters += input.length text << " #{TO_HTML_CHARACTERS[text.encoding][:ellipsis]}" unless text == input - super ::RDoc::Markup::Verbatim.new text + super Markup::Verbatim.new text add_paragraph end @@ -154,7 +154,7 @@ def list_item_start(list_item, list_type) @characters += 1 # try to include the label start else - raise ::RDoc::Error, "Invalid list type: #{list_type.inspect}" + raise Error, "Invalid list type: #{list_type.inspect}" end end diff --git a/lib/rdoc/markup/to_joined_paragraph.rb b/lib/rdoc/markup/to_joined_paragraph.rb index 00d1254d2a..4ed73b1306 100644 --- a/lib/rdoc/markup/to_joined_paragraph.rb +++ b/lib/rdoc/markup/to_joined_paragraph.rb @@ -9,7 +9,7 @@ class Markup # This formatter only works on Paragraph instances. Attempting to process # other markup syntax items will not work. - class ToJoinedParagraph < ::RDoc::Markup::Formatter + class ToJoinedParagraph < Markup::Formatter def start_accepting # :nodoc: end diff --git a/lib/rdoc/markup/to_label.rb b/lib/rdoc/markup/to_label.rb index 9874c4e7ce..20166c6ec4 100644 --- a/lib/rdoc/markup/to_label.rb +++ b/lib/rdoc/markup/to_label.rb @@ -9,7 +9,7 @@ class Markup # converted to their link part and cross-reference links have the suppression # marks removed (\\SomeClass is converted to SomeClass). - class ToLabel < ::RDoc::Markup::Formatter + class ToLabel < Markup::Formatter attr_reader :res # :nodoc: @@ -19,7 +19,7 @@ class ToLabel < ::RDoc::Markup::Formatter def initialize super - @markup.add_regexp_handling ::RDoc::CrossReference::CROSSREF_REGEXP, :CROSSREF + @markup.add_regexp_handling CrossReference::CROSSREF_REGEXP, :CROSSREF @res = [] end @@ -48,7 +48,7 @@ def extract_plaintext(text) def convert(text) label = extract_plaintext(text) - ::RDoc::Text.to_anchor(label) + Text.to_anchor(label) end ## diff --git a/lib/rdoc/markup/to_markdown.rb b/lib/rdoc/markup/to_markdown.rb index 41825ccb38..24a0a2f4ac 100644 --- a/lib/rdoc/markup/to_markdown.rb +++ b/lib/rdoc/markup/to_markdown.rb @@ -6,7 +6,7 @@ class Markup ## # Outputs parsed markup as Markdown - class ToMarkdown < ::RDoc::Markup::ToRdoc + class ToMarkdown < Markup::ToRdoc ## # Creates a new formatter that will output Markdown format text @@ -148,7 +148,7 @@ def accept_list_start(list) when :LALPHA, :NUMBER, :UALPHA @list_index << 1 else - raise ::RDoc::Error, "invalid list type #{list.type}" + raise Error, "invalid list type #{list.type}" end @list_width << 4 diff --git a/lib/rdoc/markup/to_rdoc.rb b/lib/rdoc/markup/to_rdoc.rb index bee8d5f62d..e2428fdda2 100644 --- a/lib/rdoc/markup/to_rdoc.rb +++ b/lib/rdoc/markup/to_rdoc.rb @@ -4,7 +4,7 @@ class Markup ## # Outputs RDoc markup as RDoc markup! (mostly) - class ToRdoc < ::RDoc::Markup::Formatter + class ToRdoc < Markup::Formatter DEFAULT_HEADINGS = { 1 => ['= ', ''], 2 => ['== ', ''], @@ -184,7 +184,7 @@ def accept_list_start(list) @list_index << 'A' @list_width << list.items.length.to_s.length else - raise ::RDoc::Error, "invalid list type #{list.type}" + raise Error, "invalid list type #{list.type}" end @list_type << list.type diff --git a/lib/rdoc/markup/to_table_of_contents.rb b/lib/rdoc/markup/to_table_of_contents.rb index f62a9b08b1..44079f0189 100644 --- a/lib/rdoc/markup/to_table_of_contents.rb +++ b/lib/rdoc/markup/to_table_of_contents.rb @@ -5,7 +5,7 @@ class Markup # Extracts just the RDoc::Markup::Heading elements from a # RDoc::Markup::Document to help build a table of contents - class ToTableOfContents < ::RDoc::Markup::Formatter + class ToTableOfContents < Markup::Formatter @to_toc = nil diff --git a/lib/rdoc/markup/to_test.rb b/lib/rdoc/markup/to_test.rb index 0c9367e2c1..b5eb64c955 100644 --- a/lib/rdoc/markup/to_test.rb +++ b/lib/rdoc/markup/to_test.rb @@ -4,7 +4,7 @@ class Markup ## # This Markup outputter is used for testing purposes. - class ToTest < ::RDoc::Markup::Formatter + class ToTest < Markup::Formatter # :stopdoc: diff --git a/lib/rdoc/markup/to_tt_only.rb b/lib/rdoc/markup/to_tt_only.rb index 3646322a0d..16f2566b99 100644 --- a/lib/rdoc/markup/to_tt_only.rb +++ b/lib/rdoc/markup/to_tt_only.rb @@ -5,7 +5,7 @@ class Markup # Extracts sections of text enclosed in plus, tt or code. Used to discover # undocumented parameters. - class ToTtOnly < ::RDoc::Markup::Formatter + class ToTtOnly < Markup::Formatter ## # Stack of list types @@ -75,7 +75,7 @@ def do_nothing(markup_item) # Extracts tt sections from +text+ def tt_sections(text) - parsed = ::RDoc::Markup::InlineParser.new(text).parse + parsed = Markup::InlineParser.new(text).parse traverse = -> node { next if String === node if node[:type] == :TT diff --git a/lib/rdoc/markup/verbatim.rb b/lib/rdoc/markup/verbatim.rb index 10f7fbb28c..c0b999f7d6 100644 --- a/lib/rdoc/markup/verbatim.rb +++ b/lib/rdoc/markup/verbatim.rb @@ -4,7 +4,7 @@ class Markup ## # A section of verbatim text - class Verbatim < ::RDoc::Markup::Raw + class Verbatim < Markup::Raw ## # Format of this verbatim section diff --git a/lib/rdoc/options.rb b/lib/rdoc/options.rb index 5bb139f5cb..8f4f9e37b1 100644 --- a/lib/rdoc/options.rb +++ b/lib/rdoc/options.rb @@ -403,7 +403,7 @@ def init_ivars # :nodoc: @force_output = false @force_update = true @generator_name = "aliki" - @generators = ::RDoc::RDoc::GENERATORS + @generators = RDoc::GENERATORS @generator_options = [] @hyperlink_all = false @line_numbers = false @@ -657,7 +657,7 @@ def finish end if @locale_name - @locale = ::RDoc::I18n::Locale[@locale_name] + @locale = I18n::Locale[@locale_name] @locale.load(@locale_dir) else @locale = nil @@ -692,7 +692,7 @@ def finish_page_dir def generator_descriptions lengths = [] - generators = ::RDoc::RDoc::GENERATORS.map do |name, generator| + generators = RDoc::GENERATORS.map do |name, generator| lengths << name.length description = generator::DESCRIPTION if @@ -723,7 +723,7 @@ def parse(argv) opts = OptionParser.new do |opt| @option_parser = opt opt.program_name = File.basename $0 - opt.version = ::RDoc::VERSION + opt.version = VERSION opt.release = nil opt.summary_indent = ' ' * 4 opt.banner = <<-EOF @@ -756,7 +756,7 @@ def parse(argv) parsers = Hash.new { |h, parser| h[parser] = [] } - ::RDoc::Parser.parsers.each do |regexp, parser| + Parser.parsers.each do |regexp, parser| parsers[parser.name.sub('RDoc::Parser::', '')] << regexp.source end @@ -870,7 +870,7 @@ def parse(argv) raise OptionParser::InvalidArgument, "Invalid parameter to '-E'" end - unless ::RDoc::Parser.alias_extension old, new + unless Parser.alias_extension old, new raise OptionParser::InvalidArgument, "Unknown extension .#{old} to -E" end end @@ -901,7 +901,7 @@ def parse(argv) opt.separator nil - opt.on("--visibility=VISIBILITY", ::RDoc::VISIBILITIES + [:nodoc], + opt.on("--visibility=VISIBILITY", VISIBILITIES + [:nodoc], "Minimum visibility to document a method.", "One of 'public', 'protected' (the default),", "'private' or 'nodoc' (show everything)") do |value| @@ -918,7 +918,7 @@ def parse(argv) opt.separator nil - markup_formats = ::RDoc::Text::MARKUP_FORMAT.keys.sort + markup_formats = Text::MARKUP_FORMAT.keys.sort opt.on("--markup=MARKUP", markup_formats, "The markup format for the named files.", @@ -1116,7 +1116,7 @@ def parse(argv) check_generator @generator_name = "ri" - @op_dir ||= ::RDoc::RI::Paths::HOMEDIR + @op_dir ||= RI::Paths::HOMEDIR setup_generator end @@ -1130,7 +1130,7 @@ def parse(argv) check_generator @generator_name = "ri" - @op_dir = ::RDoc::RI::Paths.site_dir + @op_dir = RI::Paths.site_dir setup_generator end @@ -1208,7 +1208,7 @@ def parse(argv) opt.separator nil opt.on("--help", "-h", "Display this help") do - ::RDoc::RDoc::GENERATORS.each_key do |generator| + RDoc::GENERATORS.each_key do |generator| setup_generator generator end @@ -1381,7 +1381,7 @@ def write_options def self.load_options options_file = File.expand_path '.rdoc_options' - return ::RDoc::Options.new unless File.exist? options_file + return Options.new unless File.exist? options_file ::RDoc.load_yaml @@ -1389,22 +1389,22 @@ def self.load_options if defined?(Psych) begin - options = Psych.safe_load content, permitted_classes: [::RDoc::Options, Symbol] + options = Psych.safe_load content, permitted_classes: [Options, Symbol] rescue Psych::SyntaxError - raise ::RDoc::Error, "#{options_file} is not a valid rdoc options file" + raise Error, "#{options_file} is not a valid rdoc options file" end else options = ::RDoc.yaml_serializer.load(content) end - return ::RDoc::Options.new unless options # Allow empty file. + return Options.new unless options # Allow empty file. - raise ::RDoc::Error, "#{options_file} is not a valid rdoc options file" unless - ::RDoc::Options === options or Hash === options + raise Error, "#{options_file} is not a valid rdoc options file" unless + Options === options or Hash === options if Hash === options # Override the default values with the contents of YAML file. - options = ::RDoc::Options.new options + options = Options.new options end options diff --git a/lib/rdoc/parser.rb b/lib/rdoc/parser.rb index 42a5b01f96..7c7831b51b 100644 --- a/lib/rdoc/parser.rb +++ b/lib/rdoc/parser.rb @@ -63,7 +63,7 @@ def self.alias_extension(old_ext, new_ext) parser = can_parse_by_name "xxx.#{old_ext}" return false unless parser - ::RDoc::Parser.parsers.unshift [/\.#{new_ext}$/, parser] + Parser.parsers.unshift [/\.#{new_ext}$/, parser] true end @@ -109,7 +109,7 @@ def self.can_parse(file_name) parser = can_parse_by_name file_name # HACK Selenium hides a jar file using a .txt extension - return if parser == ::RDoc::Parser::Simple and zip? file_name + return if parser == Parser::Simple and zip? file_name parser end @@ -119,17 +119,17 @@ def self.can_parse(file_name) # not depend upon the file being readable. def self.can_parse_by_name(file_name) - _, parser = ::RDoc::Parser.parsers.find { |regexp,| regexp =~ file_name } + _, parser = Parser.parsers.find { |regexp,| regexp =~ file_name } # The default parser must not parse binary files ext_name = File.extname file_name return parser if ext_name.empty? - if parser == ::RDoc::Parser::Simple and ext_name !~ /txt|rdoc/ + if parser == Parser::Simple and ext_name !~ /txt|rdoc/ case mode = check_modeline(file_name) when nil, 'rdoc' # continue else - ::RDoc::Parser.parsers.find { |_, p| return p if mode.casecmp?(p.name[/\w+\z/]) } + Parser.parsers.find { |_, p| return p if mode.casecmp?(p.name[/\w+\z/]) } return nil end end @@ -203,7 +203,7 @@ def self.for(top_level, content, options, stats) # It is ok to call this multiple times. def self.parse_files_matching(regexp) - ::RDoc::Parser.parsers.unshift [regexp, self] + Parser.parsers.unshift [regexp, self] end ## @@ -246,7 +246,7 @@ def self.use_markup(content) markup = Regexp.escape markup - _, selected = ::RDoc::Parser.parsers.find do |_, parser| + _, selected = Parser.parsers.find do |_, parser| /^#{markup}$/i =~ parser.name.sub(/.*:/, '') end @@ -269,7 +269,7 @@ def initialize(top_level, content, options, stats) @options = options @stats = stats - @preprocess = ::RDoc::Markup::PreProcess.new @file_name, @options.rdoc_include + @preprocess = Markup::PreProcess.new @file_name, @options.rdoc_include @preprocess.options = @options end diff --git a/lib/rdoc/parser/c.rb b/lib/rdoc/parser/c.rb index 295313464f..c8bc98e6ae 100644 --- a/lib/rdoc/parser/c.rb +++ b/lib/rdoc/parser/c.rb @@ -118,7 +118,7 @@ class Parser # * block and return its value. # */ - class C < ::RDoc::Parser + class C < Parser parse_files_matching(/\.(?:([CcHh])\1?|c([+xp])\2|y)\z/) @@ -173,7 +173,7 @@ class C < ::RDoc::Parser def initialize(top_level, content, options, stats) super - @known_classes = ::RDoc::KNOWN_CLASSES.dup + @known_classes = KNOWN_CLASSES.dup @content = handle_tab_width handle_ifdefs_in @content @file_dir = File.dirname @file_name @@ -250,7 +250,7 @@ def do_aliases # method that reference the same function. def add_alias(var_name, class_obj, old_name, new_name, comment, singleton:) - al = ::RDoc::Alias.new old_name, new_name, comment, singleton: singleton + al = Alias.new old_name, new_name, comment, singleton: singleton al.record_location @top_level class_obj.add_alias al @stats.add_alias al @@ -446,7 +446,7 @@ def do_includes m = @known_classes[m] || m comment = new_comment '', @top_level, :c - incl = cls.add_include ::RDoc::Include.new(m, comment) + incl = cls.add_include Include.new(m, comment) incl.record_location @top_level end end @@ -678,9 +678,9 @@ def find_body(class_name, meth_name, meth_obj, file_content, quiet = false) def find_class(raw_name, name, base_name = nil) unless @classes[raw_name] if raw_name =~ /^rb_m/ - container = @top_level.add_module ::RDoc::NormalModule, name + container = @top_level.add_module NormalModule, name else - container = @top_level.add_class ::RDoc::NormalClass, name + container = @top_level.add_class NormalClass, name end container.name = base_name if base_name @@ -850,7 +850,7 @@ def handle_attr(var_name, attr_name, read, write) name = attr_name.gsub(/rb_intern(?:_const)?\("([^"]+)"\)/, '\1') - attr = ::RDoc::Attr.new name, rw, comment + attr = Attr.new name, rw, comment attr.record_location @top_level class_obj.add_attribute attr @@ -885,7 +885,7 @@ def handle_class_module(var_name, type, class_name, parent, in_module) end if type == :class - full_name = if ::RDoc::ClassModule === enclosure + full_name = if ClassModule === enclosure enclosure.full_name + "::#{class_name}" else class_name @@ -895,9 +895,9 @@ def handle_class_module(var_name, type, class_name, parent, in_module) parent_name = $1 end - cm = enclosure.add_class ::RDoc::NormalClass, class_name, parent_name + cm = enclosure.add_class NormalClass, class_name, parent_name else - cm = enclosure.add_module ::RDoc::NormalModule, class_name + cm = enclosure.add_module NormalModule, class_name end cm.record_location enclosure.top_level @@ -906,9 +906,9 @@ def handle_class_module(var_name, type, class_name, parent, in_module) find_class_comment cm.full_name, cm case cm - when ::RDoc::NormalClass + when NormalClass @stats.add_class cm - when ::RDoc::NormalModule + when NormalModule @stats.add_module cm end @@ -961,12 +961,12 @@ def handle_constants(type, var_name, const_name, definition) new_comment = self.new_comment(new_comment, @top_level, :c) - con = ::RDoc::Constant.new const_name, new_definition, new_comment + con = Constant.new const_name, new_definition, new_comment else - con = ::RDoc::Constant.new const_name, definition, comment + con = Constant.new const_name, definition, comment end else - con = ::RDoc::Constant.new const_name, definition, comment + con = Constant.new const_name, definition, comment end con.record_location @top_level @@ -1008,7 +1008,7 @@ def handle_method(type, var_name, meth_name, function, param_count, type = 'method' # force public end - meth_obj = ::RDoc::AnyMethod.new meth_name, singleton: singleton + meth_obj = AnyMethod.new meth_name, singleton: singleton meth_obj.c_function = function p_count = Integer(param_count) rescue -1 @@ -1017,7 +1017,7 @@ def handle_method(type, var_name, meth_name, function, param_count, file_name = File.join @file_dir, source_file if File.exist? file_name - file_content = ::RDoc::Encoding.read_file file_name, @options.encoding + file_content = Encoding.read_file file_name, @options.encoding else @options.warn "unknown source #{source_file} for #{meth_name} in #{@file_name}" end @@ -1220,7 +1220,7 @@ def scan # Creates a RDoc::Comment instance. def new_comment(text = nil, location = nil, language = nil) - ::RDoc::Comment.new(text, location, language).tap do |comment| + Comment.new(text, location, language).tap do |comment| comment.format = @markup end end diff --git a/lib/rdoc/parser/changelog.rb b/lib/rdoc/parser/changelog.rb index 71a579e648..7ecebbe8af 100644 --- a/lib/rdoc/parser/changelog.rb +++ b/lib/rdoc/parser/changelog.rb @@ -13,9 +13,9 @@ class Parser # {GNU style Change # Log}[http://www.gnu.org/prep/standards/html_node/Style-of-Change-Logs.html]. - class ChangeLog < ::RDoc::Parser + class ChangeLog < Parser - include ::RDoc::Parser::Text + include Parser::Text parse_files_matching(/(\/|\\|\A)ChangeLog[^\/\\]*\z/) @@ -44,16 +44,16 @@ def continue_entry_body(entry_body, continuation) # Creates an RDoc::Markup::Document given the +groups+ of ChangeLog entries. def create_document(groups) - doc = ::RDoc::Markup::Document.new + doc = Markup::Document.new doc.omit_headings_below = 2 doc.file = @top_level - doc << ::RDoc::Markup::Heading.new(1, File.basename(@file_name)) - doc << ::RDoc::Markup::BlankLine.new + doc << Markup::Heading.new(1, File.basename(@file_name)) + doc << Markup::BlankLine.new groups.sort_by do |day,| day end.reverse_each do |day, entries| - doc << ::RDoc::Markup::Heading.new(2, day.dup) - doc << ::RDoc::Markup::BlankLine.new + doc << Markup::Heading.new(2, day.dup) + doc << Markup::BlankLine.new doc.concat create_entries entries end @@ -69,8 +69,8 @@ def create_entries(entries) out = [] entries.each do |entry, items| - out << ::RDoc::Markup::Heading.new(3, entry) - out << ::RDoc::Markup::BlankLine.new + out << Markup::Heading.new(3, entry) + out << Markup::BlankLine.new out << create_items(items) end @@ -83,7 +83,7 @@ def create_entries(entries) # ChangeLog def create_items(items) - list = ::RDoc::Markup::List.new :NOTE + list = Markup::List.new :NOTE items.each do |item| item =~ /\A(.*?(?:\([^)]+\))?):\s*/ @@ -91,8 +91,8 @@ def create_items(items) title = $1 body = $' - paragraph = ::RDoc::Markup::Paragraph.new body - list_item = ::RDoc::Markup::ListItem.new title, paragraph + paragraph = Markup::Paragraph.new body + list_item = Markup::ListItem.new title, paragraph list << list_item end @@ -212,7 +212,7 @@ def scan grouped_entries = group_entries entries doc = create_document grouped_entries - comment = ::RDoc::Comment.new(@content) + comment = Comment.new(@content) comment.document = doc @top_level.comment = comment @@ -299,13 +299,13 @@ def initialize(base, commit, author, email, date, contents) contents = ::RDoc::Markdown.parse(contents).parts.each do |body| case body - when ::RDoc::Markup::Heading + when Markup::Heading body.level += HEADING_LEVEL + 1 end end case first = contents[0] - when ::RDoc::Markup::Paragraph - contents[0] = ::RDoc::Markup::Heading.new(HEADING_LEVEL + 1, first.text) + when Markup::Paragraph + contents[0] = Markup::Heading.new(HEADING_LEVEL + 1, first.text) end end super diff --git a/lib/rdoc/parser/markdown.rb b/lib/rdoc/parser/markdown.rb index ff6c47f077..80d65701c6 100644 --- a/lib/rdoc/parser/markdown.rb +++ b/lib/rdoc/parser/markdown.rb @@ -5,9 +5,9 @@ class Parser # Parse a Markdown format file. The parsed RDoc::Markup::Document is attached # as a file comment. - class Markdown < ::RDoc::Parser + class Markdown < Parser - include ::RDoc::Parser::Text + include Parser::Text parse_files_matching(/\.(md|markdown)(?:\.[^.]+)?$/) @@ -15,7 +15,7 @@ class Markdown < ::RDoc::Parser # Creates an Markdown-format TopLevel for the given file. def scan - comment = ::RDoc::Comment.new @content, @top_level + comment = Comment.new @content, @top_level comment.format = 'markdown' @top_level.comment = comment diff --git a/lib/rdoc/parser/rbs.rb b/lib/rdoc/parser/rbs.rb index 00c93cd485..7ceda649d0 100644 --- a/lib/rdoc/parser/rbs.rb +++ b/lib/rdoc/parser/rbs.rb @@ -7,7 +7,7 @@ class Parser ## # Parse RBS signature files as first-class RDoc input. - class RBS < ::RDoc::Parser + class RBS < Parser RBS_FILE_EXTENSION = /\.rbs$/ parse_files_matching RBS_FILE_EXTENSION @@ -25,7 +25,7 @@ def scan def record_object_location(object, location) object.line = location.start_line if location - if ::RDoc::ClassModule === object + if ClassModule === object @top_level.add_to_classes_or_modules object unless @top_level.classes_or_modules.include? object end @@ -40,7 +40,7 @@ def rdoc_comment_for(decl) # TODO: Run RBS comments through RDoc's directive preprocessor so # directives like :nodoc: affect the documented object. - comment = ::RDoc::Comment.new rbs_comment.string, @top_level + comment = Comment.new rbs_comment.string, @top_level comment.format = 'markdown' comment end @@ -76,13 +76,13 @@ def merge_documentation(object, comment, type_signature_lines) end def merge_comments(object, comment) - document = ::RDoc::Markup::Document.new + document = Markup::Document.new document.concat object.parse(object.comment).parts - document << ::RDoc::Markup::Rule.new(1) + document << Markup::Rule.new(1) document.concat comment.parse.parts # Keep this text separator in sync with the Rule node above. - merged_comment = ::RDoc::Comment.new "#{object.comment}\n---\n#{comment}", comment.location + merged_comment = Comment.new "#{object.comment}\n---\n#{comment}", comment.location merged_comment.format = 'markdown' merged_comment.document = document merged_comment @@ -147,7 +147,7 @@ def parse_attr_decl(decl, context) return end - attribute = ::RDoc::Attr.new( + attribute = Attr.new( name, rw, comment, @@ -162,7 +162,7 @@ def parse_attr_decl(decl, context) def parse_class_decl(decl, context) owner, name = context.find_or_create_constant_owner_for_path decl.name superclass = decl.super_class&.name&.to_s || '::Object' - klass = owner.add_class ::RDoc::NormalClass, name, superclass + klass = owner.add_class NormalClass, name, superclass record_object_location klass, decl.location comment = rdoc_comment_for decl klass.add_comment comment, @top_level if comment @@ -171,7 +171,7 @@ def parse_class_decl(decl, context) end def parse_constant_decl(decl, context) - constant = ::RDoc::Constant.new decl.name.to_s, decl.type.to_s, + constant = Constant.new decl.name.to_s, decl.type.to_s, rdoc_comment_for(decl) record_object_location constant, decl.location context.add_constant constant @@ -193,14 +193,14 @@ def parse_decl(decl, context) end def parse_extend_decl(decl, context) - extend_decl = ::RDoc::Extend.new local_module_name(decl.name, context), + extend_decl = Extend.new local_module_name(decl.name, context), rdoc_comment_for(decl) record_object_location extend_decl, decl.location context.add_extend extend_decl end def parse_include_decl(decl, context) - include_decl = ::RDoc::Include.new local_module_name(decl.name, context), + include_decl = Include.new local_module_name(decl.name, context), rdoc_comment_for(decl) record_object_location include_decl, decl.location context.add_include include_decl @@ -230,7 +230,7 @@ def parse_member_decl(decl, context) end def parse_method_alias_decl(decl, context) - alias_def = ::RDoc::Alias.new( + alias_def = Alias.new( decl.old_name.to_s, decl.new_name.to_s, rdoc_comment_for(decl), @@ -252,7 +252,7 @@ def parse_method_decl(decl, context) return end - method = ::RDoc::AnyMethod.new method_name, singleton: singleton + method = AnyMethod.new method_name, singleton: singleton record_object_location method, decl.location method.type_signature_lines = type_signature_lines @@ -267,7 +267,7 @@ def parse_method_decl(decl, context) end def parse_module_decl(decl, context) - mod = context.add_module ::RDoc::NormalModule, decl.name.to_s + mod = context.add_module NormalModule, decl.name.to_s record_object_location mod, decl.location comment = rdoc_comment_for decl mod.add_comment comment, @top_level if comment diff --git a/lib/rdoc/parser/rd.rb b/lib/rdoc/parser/rd.rb index c355702118..a1fccaf979 100644 --- a/lib/rdoc/parser/rd.rb +++ b/lib/rdoc/parser/rd.rb @@ -5,9 +5,9 @@ class Parser # Parse a RD format file. The parsed RDoc::Markup::Document is attached as a # file comment. - class RD < ::RDoc::Parser + class RD < Parser - include ::RDoc::Parser::Text + include Parser::Text parse_files_matching(/\.rd(?:\.[^.]+)?$/) @@ -15,7 +15,7 @@ class RD < ::RDoc::Parser # Creates an rd-format TopLevel for the given file. def scan - comment = ::RDoc::Comment.new @content, @top_level + comment = Comment.new @content, @top_level comment.format = 'rd' @top_level.comment = comment diff --git a/lib/rdoc/parser/ruby.rb b/lib/rdoc/parser/ruby.rb index cb081d8e81..78e80546ad 100644 --- a/lib/rdoc/parser/ruby.rb +++ b/lib/rdoc/parser/ruby.rb @@ -126,7 +126,7 @@ class Parser # Note that by default, the :method: directive will be ignored if there is a # standard rdocable item following it. - class Ruby < ::RDoc::Parser + class Ruby < Parser parse_files_matching(/\.rbw?$/) @@ -143,7 +143,7 @@ def initialize(top_level, content, options, stats) @size = 0 @token_listeners = nil - content = ::RDoc::Encoding.remove_magic_comment content + content = Encoding.remove_magic_comment content @content = content @markup = @options.markup @track_visibility = :nodoc != @options.visibility @@ -200,7 +200,7 @@ def mark_container_documentable(container) return if container.received_nodoc || !container.ignored? record_location(container) container.start_doc - mark_container_documentable(container.parent) if container.parent.is_a?(::RDoc::ClassModule) + mark_container_documentable(container.parent) if container.parent.is_a?(ClassModule) end # Suppress `extend` and `include` within block @@ -242,7 +242,7 @@ def with_container(container, singleton: false) def record_location(container) # :nodoc: case container - when ::RDoc::ClassModule + when ClassModule @top_level.add_to_classes_or_modules container end @@ -316,7 +316,7 @@ def prepare_comments(comments) consecutive_comments << [comment] << (current = []) elsif comment.location.start_line_slice.match?(/\S/) text = comment.slice - text = ::RDoc::Encoding.change_encoding(text, @encoding) if @encoding + text = Encoding.change_encoding(text, @encoding) if @encoding @modifier_comments[comment.location.start_line] = text elsif current.empty? || current.last.location.end_line + 1 == comment.location.start_line current << comment @@ -340,7 +340,7 @@ def prepare_comments(comments) c.is_a?(Prism::EmbDocComment) ? c.slice.lines[1...-1].join : c.slice end text = texts.join("\n") - text = ::RDoc::Encoding.change_encoding(text, @encoding) if @encoding + text = Encoding.change_encoding(text, @encoding) if @encoding line_no += 1 while @lines[line_no - 1]&.match?(/\A\s*$/) [line_no, start_line, text] end @@ -359,11 +359,11 @@ def prepare_comments(comments) def parse_comment_tomdoc(container, comment, line_no, start_line) return if document_suppressed? - return unless signature = ::RDoc::TomDoc.signature(comment) + return unless signature = TomDoc.signature(comment) name, = signature.split %r%[ \(]%, 2 - meth = ::RDoc::AnyMethod.new name + meth = AnyMethod.new name record_location(meth) meth.line = start_line meth.call_seq = signature @@ -432,7 +432,7 @@ def handle_meta_method_comment(comment, directives, node) if attributes attributes.each do |attr| - a = ::RDoc::Attr.new(attr, rw, comment, singleton: @singleton) + a = Attr.new(attr, rw, comment, singleton: @singleton) a.store = @store a.line = line_no a.visibility = visibility @@ -494,7 +494,7 @@ def process_comments_until(line_no_until) while !@unprocessed_comments.empty? && @unprocessed_comments.first[0] <= line_no_until line_no, start_line, text = @unprocessed_comments.shift if @markup == 'tomdoc' - comment = ::RDoc::Comment.new(text, @top_level, :ruby) + comment = Comment.new(text, @top_level, :ruby) comment.format = 'tomdoc' parse_comment_tomdoc(@container, comment, line_no, start_line) @preprocess.run_post_processes(comment, @container) @@ -528,7 +528,7 @@ def consecutive_comment(line_no) def parse_comment_text_to_directives(comment_text, start_line) # :nodoc: type_signature_lines = extract_type_signature!(comment_text, start_line) comment_text, directives = @preprocess.parse_comment(comment_text, start_line, :ruby) - comment = ::RDoc::Comment.new(comment_text, @top_level, :ruby) + comment = Comment.new(comment_text, @top_level, :ruby) comment.normalized = true comment.line = start_line markup, = directives['markup'] @@ -558,7 +558,7 @@ def extract_section_comment(comment_text, prefix_line_count) # :nodoc: # Returns syntax highlighted tokens of the given node def syntax_highlighted_tokens(node) - ::RDoc::Parser::RubyColorizer.partial_colorize(@content, node, @prism_tokens) + Parser::RubyColorizer.partial_colorize(@content, node, @prism_tokens) end # Handles `public :foo, :bar` `private :foo, :bar` and `protected :foo, :bar` @@ -580,9 +580,9 @@ def change_method_visibility(names, visibility, singleton: @singleton) new_methods.each do |method| method.visibility = visibility case method - when ::RDoc::AnyMethod + when AnyMethod @container.add_method(method) - when ::RDoc::Attr + when Attr @container.add_attribute(method) end end @@ -606,9 +606,9 @@ def change_method_to_module_function(names) new_methods.each do |method| method.visibility = :public case method - when ::RDoc::AnyMethod + when AnyMethod @container.add_method(method) - when ::RDoc::Attr + when Attr @container.add_attribute(method) end end @@ -631,7 +631,7 @@ def add_alias_method(old_name, new_name, line_no) handle_code_object_directives(@container, directives) if directives return if document_suppressed? - a = ::RDoc::Alias.new(old_name, new_name, comment, singleton: @singleton) + a = Alias.new(old_name, new_name, comment, singleton: @singleton) handle_modifier_directive(a, line_no) a.store = @store a.line = line_no @@ -652,7 +652,7 @@ def add_attributes(names, rw, line_no) return unless @container.document_children names.each do |symbol| - a = ::RDoc::Attr.new(symbol.to_s, rw, comment, singleton: @singleton) + a = Attr.new(symbol.to_s, rw, comment, singleton: @singleton) a.store = @store a.line = line_no a.type_signature_lines = type_signature_lines @@ -688,13 +688,13 @@ def add_includes_extends(names, rdoc_class, line_no) # :nodoc: # Handle `include Foo, Bar` def add_includes(names, line_no) # :nodoc: - add_includes_extends(names, ::RDoc::Include, line_no) + add_includes_extends(names, Include, line_no) end # Handle `extend Foo, Bar` def add_extends(names, line_no) # :nodoc: - add_includes_extends(names, ::RDoc::Extend, line_no) + add_includes_extends(names, Extend, line_no) end # Adds a method defined by `def` syntax @@ -725,7 +725,7 @@ def add_method(method_name, receiver_name:, receiver_fallback_type:, visibility: end private def internal_add_method(method_name, container, comment:, dont_rename_initialize: false, directives:, modifier_comment_lines: nil, line_no:, visibility:, singleton:, params:, calls_super:, block_params:, tokens:, type_signature_lines: nil) # :nodoc: - meth = ::RDoc::AnyMethod.new(method_name, singleton: singleton) + meth = AnyMethod.new(method_name, singleton: singleton) meth.comment = comment handle_code_object_directives(meth, directives) if directives modifier_comment_lines&.each do |line| @@ -778,9 +778,9 @@ def find_or_create_lexical_module_path(module_name, create_mode) created = case mode when :class - mod.add_class(::RDoc::NormalClass, name, 'Object').tap { |m| m.store = @store } + mod.add_class(NormalClass, name, 'Object').tap { |m| m.store = @store } when :module - mod.add_module(::RDoc::NormalModule, name).tap { |m| m.store = @store } + mod.add_module(NormalModule, name).tap { |m| m.store = @store } end # add_class/add_module may return an existing object created by another # file (in_files is not empty then), which must not be ignored here. @@ -797,7 +797,7 @@ def find_or_create_lexical_module_path(module_name, create_mode) break if mod # If a constant is found and it is not a module or class, RDoc can't document about it. # Return an anonymous module to avoid wrong document creation. - return ::RDoc::NormalModule.new(nil) if nesting.find_constant_named(root_name) + return NormalModule.new(nil) if nesting.find_constant_named(root_name) end last_nesting, = @module_nesting.reverse_each.find { |_, singleton| !singleton } return mod || add_module.call(last_nesting, root_name, create_mode) unless name @@ -852,14 +852,14 @@ def add_constant(constant_name, rhs_name, start_line, end_line, alias_path: nil) owner, name = find_or_create_lexical_constant_owner_name(constant_name) return unless owner - constant = ::RDoc::Constant.new(name, rhs_name, comment) + constant = Constant.new(name, rhs_name, comment) constant.store = @store constant.line = start_line constant.is_alias_for_path = alias_path handle_modifier_directive(constant, start_line) handle_modifier_directive(constant, end_line) # A constant marked :nodoc: must not make an ignored owner documentable - mark_container_documentable(owner) if constant.document_self && owner.is_a?(::RDoc::ClassModule) + mark_container_documentable(owner) if constant.document_self && owner.is_a?(ClassModule) record_location(constant) owner.add_constant(constant) return unless alias_path @@ -903,7 +903,7 @@ def add_module_or_class(module_name, start_line, end_line, is_class: false, supe unless mod # add_class may return an existing class created by another file # (in_files is not empty then), which must not be ignored here - mod = owner.add_class(::RDoc::NormalClass, name, superclass_name || superclass_expr || '::Object') + mod = owner.add_class(NormalClass, name, superclass_name || superclass_expr || '::Object') mod.ignore if document_suppressed? && mod.in_files.empty? end @@ -918,7 +918,7 @@ def add_module_or_class(module_name, start_line, end_line, is_class: false, supe else mod = owner.modules_hash[name] unless mod - mod = owner.add_module(::RDoc::NormalModule, name) + mod = owner.add_module(NormalModule, name) mod.ignore if document_suppressed? && mod.in_files.empty? end end @@ -938,7 +938,7 @@ def add_module_or_class(module_name, start_line, end_line, is_class: false, supe mark_container_documentable(mod) else # A class/module marked :nodoc: must not make an ignored owner documentable - mark_container_documentable(owner) if mod.document_self && owner.is_a?(::RDoc::ClassModule) + mark_container_documentable(owner) if mod.document_self && owner.is_a?(ClassModule) record_location(mod) end mod.add_comment(comment, @top_level) if comment @@ -971,8 +971,8 @@ def extract_type_signature!(text, start_line) def warn_invalid_type_signature(type_signature_lines, line_no) type_signature_lines.each_with_index do |line, i| - next if ::RDoc::RbsHelper.valid_method_type?(line) - next if ::RDoc::RbsHelper.valid_type?(line) + next if RbsHelper.valid_method_type?(line) + next if RbsHelper.valid_type?(line) @options.warn "#{@top_level.relative_name}:#{line_no + i}: invalid RBS type signature: #{line.inspect}" end end @@ -1113,7 +1113,7 @@ def visit_singleton_class_node(node) case expression when Prism::ConstantWriteNode # Accept `class << (NameErrorCheckers = Object.new)` as a module which is not actually a module - mod = @scanner.container.add_module(::RDoc::NormalModule, expression.name.to_s) + mod = @scanner.container.add_module(NormalModule, expression.name.to_s) mod.ignore if @scanner.document_suppressed? && mod.in_files.empty? when Prism::ConstantPathNode, Prism::ConstantReadNode expression_name = constant_path_string(expression) @@ -1279,7 +1279,7 @@ def _visit_call_require(call_node) return unless call_node.arguments&.arguments&.size == 1 arg = call_node.arguments.arguments.first return unless arg.is_a?(Prism::StringNode) - @scanner.container.add_require(::RDoc::Require.new(arg.unescaped, nil)) + @scanner.container.add_require(Require.new(arg.unescaped, nil)) end def _visit_call_module_function(call_node) diff --git a/lib/rdoc/parser/simple.rb b/lib/rdoc/parser/simple.rb index 8d0308f0dc..8274a05e82 100644 --- a/lib/rdoc/parser/simple.rb +++ b/lib/rdoc/parser/simple.rb @@ -5,9 +5,9 @@ class Parser # Parse a non-source file. We basically take the whole thing as one big # comment. - class Simple < ::RDoc::Parser + class Simple < Parser - include ::RDoc::Parser::Text + include Parser::Text parse_files_matching(//) @@ -19,7 +19,7 @@ class Simple < ::RDoc::Parser def initialize(top_level, content, options, stats) super - preprocess = ::RDoc::Markup::PreProcess.new @file_name, @options.rdoc_include + preprocess = Markup::PreProcess.new @file_name, @options.rdoc_include content = ::RDoc::Text.expand_tabs(@content) @content, = preprocess.run_pre_processes(content, @top_level, 1, :simple) @@ -31,7 +31,7 @@ def initialize(top_level, content, options, stats) def scan content = remove_coding_comment @content - comment = ::RDoc::Comment.new content, @top_level + comment = Comment.new content, @top_level @top_level.comment = comment @top_level diff --git a/lib/rdoc/rbs_helper.rb b/lib/rdoc/rbs_helper.rb index 5750663eee..513474dcac 100644 --- a/lib/rdoc/rbs_helper.rb +++ b/lib/rdoc/rbs_helper.rb @@ -122,7 +122,7 @@ def link_type_names_in_line(line, lookup, from_path) start_in_escaped = prefix.length end_in_escaped = start_in_escaped + escaped_name.length - href = ERB::Util.html_escape(::RDoc::Markup::Formatter.gen_relative_url(from_path, target_path)) + href = ERB::Util.html_escape(Markup::Formatter.gen_relative_url(from_path, target_path)) result[start_in_escaped...end_in_escaped] = "#{escaped_name}" end diff --git a/lib/rdoc/rd.rb b/lib/rdoc/rd.rb index 8ebdb875f0..e0139e16ad 100644 --- a/lib/rdoc/rd.rb +++ b/lib/rdoc/rd.rb @@ -83,12 +83,12 @@ def self.parse(rd) rd.unshift("=begin\n").push("=end\n") end - parser = ::RDoc::RD::BlockParser.new + parser = RD::BlockParser.new document = parser.parse rd # isn't this always true? - document.parts.shift if ::RDoc::Markup::BlankLine === document.parts.first - document.parts.pop if ::RDoc::Markup::BlankLine === document.parts.last + document.parts.shift if Markup::BlankLine === document.parts.first + document.parts.pop if Markup::BlankLine === document.parts.last document end diff --git a/lib/rdoc/rd/inline.rb b/lib/rdoc/rd/inline.rb index 6a02e1136e..c3e055d83b 100644 --- a/lib/rdoc/rd/inline.rb +++ b/lib/rdoc/rd/inline.rb @@ -54,7 +54,7 @@ def append(more) when String @reference += more @rdoc += more - when ::RDoc::RD::Inline + when RD::Inline @reference += more.reference @rdoc += more.rdoc else diff --git a/lib/rdoc/rdoc.rb b/lib/rdoc/rdoc.rb index 1050ca3618..0aa91ba111 100644 --- a/lib/rdoc/rdoc.rb +++ b/lib/rdoc/rdoc.rb @@ -113,7 +113,7 @@ def initialize # Report an error message and exit def error(msg) - raise ::RDoc::Error, msg + raise Error, msg end ## @@ -142,9 +142,9 @@ def gather_files(files) # Turns RDoc from stdin into HTML def handle_pipe - @html = ::RDoc::Markup::ToHtml.new(pipe: @options.pipe, output_decoration: @options.output_decoration) + @html = Markup::ToHtml.new(pipe: @options.pipe, output_decoration: @options.output_decoration) - parser = ::RDoc::Text::MARKUP_FORMAT[@options.markup] + parser = Text::MARKUP_FORMAT[@options.markup] document = parser.parse $stdin.read @@ -280,7 +280,7 @@ def normalized_file_list(relative_files, force_doc = false, mtime = (stat.mtime unless (last_modified = @last_modified[rel_file_name] and stat.mtime.to_i <= last_modified.to_i)) - if force_doc or ::RDoc::Parser.can_parse(rel_file_name) + if force_doc or Parser.can_parse(rel_file_name) file_list[rel_file_name] = mtime end when "directory" @@ -292,7 +292,7 @@ def normalized_file_list(relative_files, force_doc = false, created_rid = File.join rel_file_name, "created.rid" next if File.file? created_rid - dot_doc = File.join rel_file_name, ::RDoc::DOT_DOC_FILENAME + dot_doc = File.join rel_file_name, DOT_DOC_FILENAME if File.file? dot_doc file_list.update(parse_dot_doc_file(rel_file_name, dot_doc)) @@ -328,15 +328,15 @@ def parse_file(filename) @stats.add_file filename - return if ::RDoc::Parser.binary? filename + return if Parser.binary? filename - content = ::RDoc::Encoding.read_file filename, encoding + content = Encoding.read_file filename, encoding return unless content top_level = @store.add_file filename, relative_name: relative_path_for(filename) - parser = ::RDoc::Parser.for top_level, content, @options, @stats + parser = Parser.for top_level, content, @options, @stats return unless parser @@ -389,10 +389,10 @@ def parse_file(filename) raise e end - def syntax_check_command_for(filename, parser_class = ::RDoc::Parser.can_parse_by_name(filename)) - if parser_class == ::RDoc::Parser::Ruby + def syntax_check_command_for(filename, parser_class = Parser.can_parse_by_name(filename)) + if parser_class == Parser::Ruby "#{Gem.ruby} -c #{filename}" - elsif parser_class == ::RDoc::Parser::C + elsif parser_class == Parser::C cc = ENV['CC'] cc = 'cc' if cc.nil? || cc.empty? "#{cc} -fsyntax-only #{filename}" @@ -426,7 +426,7 @@ def relative_path_for(filename) def parse_files(files) file_list = gather_files files - @stats = ::RDoc::Stats.new @store, file_list.length, @options.verbosity + @stats = Stats.new @store, file_list.length, @options.verbosity return [] if file_list.empty? @@ -481,15 +481,15 @@ def remove_duplicate_files(files) # current directory, so make sure you're somewhere writable before invoking. def document(options) - if ::RDoc::Options === options + if Options === options @options = options else - @options = ::RDoc::Options.load_options + @options = Options.load_options @options.parse options end @options.finish - @store = ::RDoc::Store.new(@options) + @store = Store.new(@options) if @options.pipe handle_pipe @@ -592,7 +592,7 @@ def load_auto_discovered_rbs_signatures sig_dirs = [] sig_dir = File.join(@options.root.to_s, 'sig') sig_dirs << sig_dir if File.directory?(sig_dir) - signatures = ::RDoc::RbsHelper.load_signatures(*sig_dirs) + signatures = RbsHelper.load_signatures(*sig_dirs) @store.merge_rbs_signatures(signatures) rescue RBS::BaseError, Errno::ENOENT, LoadError => e # In server mode, a previous successful load may have populated the store; @@ -673,7 +673,7 @@ def auto_discovered_rbs_signature_mtimes # :nodoc: # Called from #document when --server is given. def start_server - server = ::RDoc::Server.new(self, @options.server_port) + server = Server.new(self, @options.server_port) server.start end @@ -714,7 +714,7 @@ def self.rbs_discovery_extension?(extension) # :nodoc: rdoc_extensions = Gem.find_latest_files 'rdoc/discover' rdoc_extensions.each do |extension| - next if ::RDoc::RDoc.rbs_discovery_extension?(extension) + next if RDoc::RDoc.rbs_discovery_extension?(extension) begin load extension diff --git a/lib/rdoc/ri/driver.rb b/lib/rdoc/ri/driver.rb index 01797a4f74..759430fa98 100644 --- a/lib/rdoc/ri/driver.rb +++ b/lib/rdoc/ri/driver.rb @@ -29,7 +29,7 @@ class Driver ## # Base Driver error class - class Error < ::RDoc::RI::Error; end + class Error < RI::Error; end ## # Raised when a name isn't found in the ri data stores @@ -114,7 +114,7 @@ def self.process_args(argv) opts = OptionParser.new do |opt| opt.program_name = File.basename $0 - opt.version = ::RDoc::VERSION + opt.version = VERSION opt.release = nil opt.summary_indent = ' ' * 4 @@ -232,7 +232,7 @@ def self.process_args(argv) opt.separator nil - formatters = ::RDoc::Markup.constants.grep(/^To[A-Z][a-z]+$/).sort + formatters = Markup.constants.grep(/^To[A-Z][a-z]+$/).sort formatters = formatters.sort.map do |formatter| formatter.to_s.sub('To', '').downcase end @@ -243,7 +243,7 @@ def self.process_args(argv) "formatter is bs for paged output and ansi", "otherwise. Valid formatters are:", "#{formatters.join(', ')}.", formatters) do |value| - options[:formatter] = ::RDoc::Markup.const_get "To#{value.capitalize}" + options[:formatter] = Markup.const_get "To#{value.capitalize}" end opt.separator nil @@ -417,12 +417,12 @@ def initialize(initial_options = {}) @doc_dirs = [] @stores = [] - ::RDoc::RI::Paths.each(options[:use_system], options[:use_site], + RI::Paths.each(options[:use_system], options[:use_site], options[:use_home], options[:use_gems], *options[:extra_doc_dirs]) do |path, type| @doc_dirs << path - store = ::RDoc::RI::Store.new(::RDoc::Options.new, path: path, type: type) + store = RI::Store.new(Options.new, path: path, type: type) store.load_cache @stores << store end @@ -443,10 +443,10 @@ def initialize(initial_options = {}) def add_also_in(out, also_in) return if also_in.empty? - out << ::RDoc::Markup::Rule.new(1) - out << ::RDoc::Markup::Paragraph.new("Also found in:") + out << Markup::Rule.new(1) + out << Markup::Paragraph.new("Also found in:") - paths = ::RDoc::Markup::Verbatim.new + paths = Markup::Verbatim.new also_in.each do |store| paths.parts.push store.friendly_path, "\n" end @@ -470,15 +470,15 @@ def add_class(out, name, classes) "#{name} < #{superclass}" end - out << ::RDoc::Markup::Heading.new(1, heading) - out << ::RDoc::Markup::BlankLine.new + out << Markup::Heading.new(1, heading) + out << Markup::BlankLine.new end ## # Adds "(from ...)" to +out+ for +store+ def add_from(out, store) - out << ::RDoc::Markup::Paragraph.new("(from #{store.friendly_path})") + out << Markup::Paragraph.new("(from #{store.friendly_path})") end ## @@ -495,8 +495,8 @@ def add_extends(out, extends) def add_extension_modules(out, type, extensions) return if extensions.empty? - out << ::RDoc::Markup::Rule.new(1) - out << ::RDoc::Markup::Heading.new(1, "#{type}:") + out << Markup::Rule.new(1) + out << Markup::Heading.new(1, "#{type}:") extensions.each do |modules, store| if modules.length == 1 @@ -511,20 +511,20 @@ def add_extension_modules(out, type, extensions) # Renders multiple included +modules+ from +store+ to +out+. def add_extension_modules_multiple(out, store, modules) # :nodoc: - out << ::RDoc::Markup::Paragraph.new("(from #{store.friendly_path})") + out << Markup::Paragraph.new("(from #{store.friendly_path})") wout, with = modules.partition { |incl| incl.comment.empty? } - out << ::RDoc::Markup::BlankLine.new unless with.empty? + out << Markup::BlankLine.new unless with.empty? with.each do |incl| - out << ::RDoc::Markup::Paragraph.new(incl.name) - out << ::RDoc::Markup::BlankLine.new + out << Markup::Paragraph.new(incl.name) + out << Markup::BlankLine.new out << incl.comment.parse end unless wout.empty? - verb = ::RDoc::Markup::Verbatim.new + verb = Markup::Verbatim.new wout.each do |incl| verb.push incl.name, "\n" @@ -540,10 +540,10 @@ def add_extension_modules_multiple(out, store, modules) # :nodoc: def add_extension_modules_single(out, store, include) # :nodoc: name = include.name path = store.friendly_path - out << ::RDoc::Markup::Paragraph.new("#{name} (from #{path})") + out << Markup::Paragraph.new("#{name} (from #{path})") if include.comment - out << ::RDoc::Markup::BlankLine.new + out << Markup::BlankLine.new out << include.comment.parse end end @@ -582,18 +582,18 @@ def add_method_documentation(out, klass) def add_method_list(out, methods, name) return if methods.empty? - out << ::RDoc::Markup::Heading.new(1, "#{name}:") - out << ::RDoc::Markup::BlankLine.new + out << Markup::Heading.new(1, "#{name}:") + out << Markup::BlankLine.new if @use_stdout and !@interactive out.concat methods.map { |method| - ::RDoc::Markup::Verbatim.new method + Markup::Verbatim.new method } else - out << ::RDoc::Markup::IndentedParagraph.new(2, methods.join(', ')) + out << Markup::IndentedParagraph.new(2, methods.join(', ')) end - out << ::RDoc::Markup::BlankLine.new + out << Markup::BlankLine.new end ## @@ -639,7 +639,7 @@ def class_cache # :nodoc: def class_document(name, found, klasses, includes, extends) also_in = [] - out = ::RDoc::Markup::Document.new + out = Markup::Document.new add_class out, name, klasses @@ -661,11 +661,11 @@ def class_document(name, found, klasses, includes, extends) def class_document_comment(out, document) # :nodoc: unless document.empty? - out << ::RDoc::Markup::Rule.new(1) + out << Markup::Rule.new(1) if document.merged? parts = document.parts - parts = parts.zip [::RDoc::Markup::BlankLine.new] * parts.length + parts = parts.zip [Markup::BlankLine.new] * parts.length parts.flatten! parts.pop @@ -682,22 +682,22 @@ def class_document_comment(out, document) # :nodoc: def class_document_constants(out, klass) # :nodoc: return if klass.constants.empty? - out << ::RDoc::Markup::Heading.new(1, "Constants:") - out << ::RDoc::Markup::BlankLine.new - list = ::RDoc::Markup::List.new :NOTE + out << Markup::Heading.new(1, "Constants:") + out << Markup::BlankLine.new + list = Markup::List.new :NOTE constants = klass.constants.sort_by { |constant| constant.name } list.items.concat constants.map { |constant| parts = constant.comment.parse.parts - parts << ::RDoc::Markup::Paragraph.new('[not documented]') if + parts << Markup::Paragraph.new('[not documented]') if parts.empty? - ::RDoc::Markup::ListItem.new(constant.name, *parts) + Markup::ListItem.new(constant.name, *parts) } out << list - out << ::RDoc::Markup::BlankLine.new + out << Markup::BlankLine.new end ## @@ -836,7 +836,7 @@ def display_class(name) # Outputs formatted RI data for method +name+ def display_method(name) - out = ::RDoc::Markup::Document.new + out = Markup::Document.new add_method out, name @@ -923,7 +923,7 @@ def display_page(name) # Outputs a formatted RI page list for the pages in +store+. def display_page_list(store, pages = store.cache[:pages], search = nil) - out = ::RDoc::Markup::Document.new + out = Markup::Document.new title = if search "#{search} pages" @@ -931,13 +931,13 @@ def display_page_list(store, pages = store.cache[:pages], search = nil) 'Pages' end - out << ::RDoc::Markup::Heading.new(1, "#{title} in #{store.friendly_path}") - out << ::RDoc::Markup::BlankLine.new + out << Markup::Heading.new(1, "#{title} in #{store.friendly_path}") + out << Markup::BlankLine.new - list = ::RDoc::Markup::List.new(:BULLET) + list = Markup::List.new(:BULLET) pages.each do |page| - list << ::RDoc::Markup::Paragraph.new(page) + list << Markup::Paragraph.new(page) end out << list @@ -1074,7 +1074,7 @@ def find_store(name) store.type == :gem and source =~ /^#{Regexp.escape name}-\d/ end - raise ::RDoc::RI::Driver::NotFoundError, name + raise RI::Driver::NotFoundError, name end ## @@ -1085,9 +1085,9 @@ def formatter(io) if @formatter_klass @formatter_klass.new elsif paging? or !io.tty? - ::RDoc::Markup::ToBs.new + Markup::ToBs.new else - ::RDoc::Markup::ToAnsi.new + Markup::ToAnsi.new end end @@ -1217,10 +1217,10 @@ def load_method(store, cache, klass, type, name) store.load_method klass, "#{type}#{method}" rescue ::RDoc::Store::MissingFileError => e - comment = ::RDoc::Comment.new("missing documentation at #{e.file}") + comment = Comment.new("missing documentation at #{e.file}") comment.parse - method = ::RDoc::AnyMethod.new name + method = AnyMethod.new name method.comment = comment method end @@ -1275,8 +1275,8 @@ def lookup_method(name) # Builds a RDoc::Markup::Document from +found+, +klasses+ and +includes+ def method_document(out, name, filtered) - out << ::RDoc::Markup::Heading.new(1, name) - out << ::RDoc::Markup::BlankLine.new + out << Markup::Heading.new(1, name) + out << Markup::BlankLine.new filtered.each do |store, methods| methods.each do |method| @@ -1395,7 +1395,7 @@ def render_class(out, store, klass, also_in) # :nodoc: class_document_comment out, document if class_methods or instance_methods or not klass.constants.empty? - out << ::RDoc::Markup::Rule.new(1) + out << Markup::Rule.new(1) end class_document_constants out, klass @@ -1408,13 +1408,13 @@ def render_class(out, store, klass, also_in) # :nodoc: end def render_method(out, store, method, name) # :nodoc: - out << ::RDoc::Markup::Paragraph.new("(from #{store.friendly_path})") + out << Markup::Paragraph.new("(from #{store.friendly_path})") unless name =~ /^#{Regexp.escape method.parent_name}/ - out << ::RDoc::Markup::Heading.new(3, "Implementation from #{method.parent_name}") + out << Markup::Heading.new(3, "Implementation from #{method.parent_name}") end - out << ::RDoc::Markup::Rule.new(1) + out << Markup::Rule.new(1) render_method_arguments out, method.arglists sig = method.type_signature_lines || store.rbs_signature_for(method) @@ -1434,39 +1434,39 @@ def render_method_arguments(out, arglists) # :nodoc: arglists = arglists.chomp.split "\n" arglists = arglists.map { |line| line + "\n" } - out << ::RDoc::Markup::Verbatim.new(*arglists) - out << ::RDoc::Markup::Rule.new(1) + out << Markup::Verbatim.new(*arglists) + out << Markup::Rule.new(1) end def render_method_comment(out, method, alias_for = nil)# :nodoc: if alias_for unless method.comment.nil? or method.comment.empty? - out << ::RDoc::Markup::BlankLine.new + out << Markup::BlankLine.new out << method.comment.parse end - out << ::RDoc::Markup::BlankLine.new - out << ::RDoc::Markup::Paragraph.new("(This method is an alias for #{alias_for.full_name}.)") - out << ::RDoc::Markup::BlankLine.new + out << Markup::BlankLine.new + out << Markup::Paragraph.new("(This method is an alias for #{alias_for.full_name}.)") + out << Markup::BlankLine.new out << alias_for.comment.parse - out << ::RDoc::Markup::BlankLine.new + out << Markup::BlankLine.new else - out << ::RDoc::Markup::BlankLine.new + out << Markup::BlankLine.new out << method.comment.parse - out << ::RDoc::Markup::BlankLine.new + out << Markup::BlankLine.new end end def render_method_type_signature(out, lines) # :nodoc: - out << ::RDoc::Markup::Verbatim.new(*lines.map { |s| s + "\n" }) + out << Markup::Verbatim.new(*lines.map { |s| s + "\n" }) end def render_method_superclass(out, method) # :nodoc: return unless method.respond_to?(:superclass_method) and method.superclass_method - out << ::RDoc::Markup::BlankLine.new - out << ::RDoc::Markup::Heading.new(4, "(Uses superclass method #{method.superclass_method})") - out << ::RDoc::Markup::Rule.new(1) + out << Markup::BlankLine.new + out << Markup::Heading.new(4, "(Uses superclass method #{method.superclass_method})") + out << Markup::Rule.new(1) end ## @@ -1529,7 +1529,7 @@ def start_server extra_doc_dirs = @stores.map {|s| s.type == :extra ? s.path : nil}.compact - server.mount '/', ::RDoc::RI::Servlet, nil, extra_doc_dirs + server.mount '/', RI::Servlet, nil, extra_doc_dirs trap 'INT' do server.shutdown end trap 'TERM' do server.shutdown end @@ -1566,8 +1566,8 @@ def expand_rdoc_refs_at_the_bottom(out) end.compact found_pages.each do |page| - out << ::RDoc::Markup::Heading.new(4, "Expanded from #{page.full_name}") - out << ::RDoc::Markup::BlankLine.new + out << Markup::Heading.new(4, "Expanded from #{page.full_name}") + out << Markup::BlankLine.new out << page.comment.parse end end diff --git a/lib/rdoc/ri/servlet.rb b/lib/rdoc/ri/servlet.rb index 132317d37c..e8d6bcf997 100644 --- a/lib/rdoc/ri/servlet.rb +++ b/lib/rdoc/ri/servlet.rb @@ -76,7 +76,7 @@ def initialize(server, stores, cache, mount_path = nil, extra_doc_dirs = []) @extra_doc_dirs = extra_doc_dirs @stores = stores - @options = ::RDoc::Options.new + @options = Options.new @options.op_dir = '.' darkfish_dir = nil @@ -221,7 +221,7 @@ def error(exception, req, res)

      Error

      While processing #{ERB::Util.html_escape req.request_uri} the -RDoc (#{ERB::Util.html_escape(::RDoc::VERSION)}) server has encountered a +RDoc (#{ERB::Util.html_escape(VERSION)}) server has encountered a #{ERB::Util.html_escape exception.class} exception: @@ -246,12 +246,12 @@ def error(exception, req, res) # Instantiates a Darkfish generator for +store+ def generator_for(store) - generator = ::RDoc::Generator::Darkfish.new store, @options + generator = Generator::Darkfish.new store, @options generator.file_output = false generator.asset_rel_path = '..' generator.setup - rdoc = ::RDoc::RDoc.new + rdoc = RDoc.new rdoc.store = store rdoc.generator = generator rdoc.options = @options @@ -328,14 +328,14 @@ def not_found(generator, req, res, message = nil) # Enumerates the ri paths. See RDoc::RI::Paths#each def ri_paths(&block) - ::RDoc::RI::Paths.each true, true, true, :all, *@extra_doc_dirs, &block #TODO: pass extra_dirs + RI::Paths.each true, true, true, :all, *@extra_doc_dirs, &block #TODO: pass extra_dirs end ## # Generates the root page on +res+. +req+ is ignored. def root(req, res) - generator = ::RDoc::Generator::Darkfish.new nil, @options + generator = Generator::Darkfish.new nil, @options res.body = generator.generate_servlet_root installed_docs @@ -422,11 +422,11 @@ def show_documentation(req, res) def store_for(source_name) case source_name when 'home' - ::RDoc::Store.new(@options, path: ::RDoc::RI::Paths.home_dir, type: :home) + ::RDoc::Store.new(@options, path: RI::Paths.home_dir, type: :home) when 'ruby' - ::RDoc::Store.new(@options, path: ::RDoc::RI::Paths.system_dir, type: :system) + ::RDoc::Store.new(@options, path: RI::Paths.system_dir, type: :system) when 'site' - ::RDoc::Store.new(@options, path: ::RDoc::RI::Paths.site_dir, type: :site) + ::RDoc::Store.new(@options, path: RI::Paths.site_dir, type: :site) when /\Aextra-(\d+)\z/ index = $1.to_i - 1 ri_dir = installed_docs[index][4] diff --git a/lib/rdoc/ri/task.rb b/lib/rdoc/ri/task.rb index 685a706b1e..540952f94d 100644 --- a/lib/rdoc/ri/task.rb +++ b/lib/rdoc/ri/task.rb @@ -2,7 +2,7 @@ begin gem 'rdoc' rescue Gem::LoadError -end unless defined?(::RDoc) +end unless defined?(RDoc) require_relative '../task' diff --git a/lib/rdoc/rubygems_hook.rb b/lib/rdoc/rubygems_hook.rb index a6718f30d7..b209262a4e 100644 --- a/lib/rdoc/rubygems_hook.rb +++ b/lib/rdoc/rubygems_hook.rb @@ -83,7 +83,7 @@ def self.load_rdoc require_relative 'rdoc' - @rdoc_version = Gem::Version.new ::RDoc::VERSION + @rdoc_version = Gem::Version.new VERSION end ## @@ -179,7 +179,7 @@ def generate delete_legacy_args args Dir.chdir @spec.full_gem_path do - options = ::RDoc::Options.new + options = Options.new options.default_title = "#{@spec.full_name} Documentation" options.parse args options.quiet = !Gem.configuration.really_verbose @@ -196,7 +196,7 @@ def generate parse_options = options.dup parse_options.finish @rdoc.options = parse_options - @rdoc.store = ::RDoc::Store.new(parse_options) + @rdoc.store = Store.new(parse_options) @rdoc.parse_files parse_options.files end @@ -212,7 +212,7 @@ def generate # make testing easier. def new_rdoc # :nodoc: - ::RDoc::RDoc.new + RDoc.new end ## diff --git a/lib/rdoc/server.rb b/lib/rdoc/server.rb index af35f79ae4..ee8021c7c9 100644 --- a/lib/rdoc/server.rb +++ b/lib/rdoc/server.rb @@ -148,7 +148,7 @@ def measure end def create_generator - gen = ::RDoc::Generator::Aliki.new(@store, @options) + gen = Generator::Aliki.new(@store, @options) gen.file_output = false gen.asset_rel_path = '' gen.setup diff --git a/lib/rdoc/stats.rb b/lib/rdoc/stats.rb index 626c69c1fd..c5de281cd0 100644 --- a/lib/rdoc/stats.rb +++ b/lib/rdoc/stats.rb @@ -6,7 +6,7 @@ module RDoc class Stats - include ::RDoc::Text + include Text ## # Display order for item types in the coverage report @@ -226,7 +226,7 @@ def percent_doc def report if @coverage_level > 0 - extend ::RDoc::Text + extend Text end if @coverage_level.zero? @@ -461,7 +461,7 @@ def summary # total parameter count and an Array of undocumented methods. def undoc_params(method) - @formatter ||= ::RDoc::Markup::ToTtOnly.new + @formatter ||= Markup::ToTtOnly.new params = method.param_list diff --git a/lib/rdoc/stats/normal.rb b/lib/rdoc/stats/normal.rb index aa4b3f7b99..34757e08bd 100644 --- a/lib/rdoc/stats/normal.rb +++ b/lib/rdoc/stats/normal.rb @@ -12,7 +12,7 @@ class Stats # Stats printer that prints just the files being documented with a progress # bar - class Normal < ::RDoc::Stats::Quiet + class Normal < Stats::Quiet def begin_adding # :nodoc: puts "Parsing sources..." diff --git a/lib/rdoc/stats/verbose.rb b/lib/rdoc/stats/verbose.rb index 51cc335fab..678851d3fa 100644 --- a/lib/rdoc/stats/verbose.rb +++ b/lib/rdoc/stats/verbose.rb @@ -5,7 +5,7 @@ class Stats # Stats printer that prints everything documented, including the documented # status - class Verbose < ::RDoc::Stats::Normal + class Verbose < Stats::Normal ## # Returns a marker for RDoc::CodeObject +co+ being undocumented diff --git a/lib/rdoc/store.rb b/lib/rdoc/store.rb index b43670ee50..50bd25021d 100644 --- a/lib/rdoc/store.rb +++ b/lib/rdoc/store.rb @@ -177,7 +177,7 @@ def add_c_variables(c_parser) def add_file(absolute_name, relative_name: absolute_name, parser: nil) unless top_level = @files_hash[relative_name] - top_level = ::RDoc::TopLevel.new absolute_name, relative_name + top_level = TopLevel.new absolute_name, relative_name top_level.parser = parser if parser top_level.store = self @files_hash[relative_name] = top_level @@ -240,7 +240,7 @@ def clear_file_contributions(relative_name, keep_position: false) cm.external_aliases.reject! { |a| a.file == top_level } # Clear or remove comment entries from this file - if cm.is_a?(::RDoc::ClassModule) + if cm.is_a?(ClassModule) if keep_position cm.comment_location[top_level] = [] if cm.comment_location.key?(top_level) else @@ -258,7 +258,7 @@ def clear_file_contributions(relative_name, keep_position: false) # for classes that are only defined in the deleted file, while # preserving classes that span multiple files. if cm.in_files.empty? - if cm.is_a?(::RDoc::NormalModule) + if cm.is_a?(NormalModule) @modules_hash.delete(cm.full_name) else @classes_hash.delete(cm.full_name) @@ -291,7 +291,7 @@ def cleanup_stale_contributions cm.constants.any? { |c| c.file == tl } } if cm.in_files.empty? - if cm.is_a?(::RDoc::NormalModule) + if cm.is_a?(NormalModule) @modules_hash.delete(cm.full_name) else @classes_hash.delete(cm.full_name) @@ -578,7 +578,7 @@ def find_c_enclosure(variable) file.store = self - mod = file.add_module ::RDoc::NormalModule, name + mod = file.add_module NormalModule, name end @c_enclosure_classes[variable] = mod @@ -596,9 +596,9 @@ def find_class_named(name) # Finds the class with +name+ starting in namespace +from+ def find_class_named_from(name, from) - from = find_class_named from unless ::RDoc::Context === from + from = find_class_named from unless Context === from - until ::RDoc::TopLevel === from do + until TopLevel === from do return nil unless from klass = from.find_class_named name @@ -735,9 +735,9 @@ def load_all descendent = find_class_or_module name case descendent - when ::RDoc::NormalClass + when NormalClass mod.classes_hash[name] = descendent - when ::RDoc::NormalModule + when NormalModule mod.modules_hash[name] = descendent end end @@ -798,11 +798,11 @@ def load_class(klass_name) obj.store = self case obj - when ::RDoc::NormalClass + when NormalClass @classes_hash[klass_name] = obj - when ::RDoc::SingleClass + when SingleClass @classes_hash[klass_name] = obj - when ::RDoc::NormalModule + when NormalModule @modules_hash[klass_name] = obj end end @@ -1172,7 +1172,7 @@ def remove_classes_and_modules(cms) cms.each do |cm| remove_classes_and_modules(cm.classes_and_modules) - if cm.is_a?(::RDoc::NormalModule) + if cm.is_a?(NormalModule) @modules_hash.delete(cm.full_name) else @classes_hash.delete(cm.full_name) @@ -1186,7 +1186,7 @@ def marshal_load(file) MarshalFilter = proc do |obj| case obj - when true, false, nil, Array, Class, ::Encoding, Hash, Integer, String, Symbol, ::RDoc::Text + when true, false, nil, Array, Class, ::Encoding, Hash, Integer, String, Symbol, Text else unless obj.class.name.start_with?("RDoc::") raise TypeError, "not permitted class: #{obj.class.name}" diff --git a/lib/rdoc/task.rb b/lib/rdoc/task.rb index 59acd413fa..266ec07500 100644 --- a/lib/rdoc/task.rb +++ b/lib/rdoc/task.rb @@ -25,7 +25,7 @@ begin gem 'rdoc' rescue Gem::LoadError -end unless defined?(::RDoc) +end unless defined?(RDoc) begin gem 'rake' @@ -233,7 +233,7 @@ def define args = option_list + @rdoc_files $stderr.puts "rdoc #{args.join ' '}" if Rake.application.options.trace - ::RDoc::RDoc.new.document args + RDoc.new.document args end namespace rdoc_task_name do @@ -244,7 +244,7 @@ def define args = opts + @rdoc_files $stderr.puts "rdoc #{args.join ' '}" if Rake.application.options.trace - ::RDoc::RDoc.new.document args + RDoc.new.document args rescue SystemExit => exit raise "RDoc coverage is incomplete" unless exit.success? end @@ -255,7 +255,7 @@ def define args = option_list + ["--server"] + @rdoc_files $stderr.puts "rdoc #{args.join ' '}" if Rake.application.options.trace - ::RDoc::RDoc.new.document args + RDoc.new.document args end end @@ -353,7 +353,7 @@ module Rake ## # For backwards compatibility - RDocTask = ::RDoc::Task # :nodoc: + RDocTask = RDoc::Task # :nodoc: end # :startdoc: diff --git a/lib/rdoc/text.rb b/lib/rdoc/text.rb index ea91ba7f98..9fb074d2da 100644 --- a/lib/rdoc/text.rb +++ b/lib/rdoc/text.rb @@ -22,13 +22,13 @@ module Text # unknown, "rdoc" format is used. MARKUP_FORMAT = { - 'markdown' => ::RDoc::Markdown, - 'rdoc' => ::RDoc::Markup, - 'rd' => ::RDoc::RD, - 'tomdoc' => ::RDoc::TomDoc, + 'markdown' => Markdown, + 'rdoc' => Markup, + 'rd' => RD, + 'tomdoc' => TomDoc, } - MARKUP_FORMAT.default = ::RDoc::Markup + MARKUP_FORMAT.default = Markup ## # Expands tab characters in +text+ to eight spaces @@ -39,7 +39,7 @@ module Text text.each_line do |line| nil while line.gsub!(/(?:\G|\r)((?:.{8})*?)([^\t\r\n]{0,7})\t/) do r = "#{$1}#{$2}#{' ' * (8 - $2.size)}" - r = ::RDoc::Encoding.change_encoding r, text.encoding + r = Encoding.change_encoding r, text.encoding r end @@ -61,7 +61,7 @@ def flush_left(text) end empty = '' - empty = ::RDoc::Encoding.change_encoding empty, text.encoding + empty = Encoding.change_encoding empty, text.encoding text.gsub(/^ {0,#{indent}}/, empty) end @@ -78,7 +78,7 @@ def markup(text) locale = nil end if locale - i18n_text = ::RDoc::I18n::Text.new(text) + i18n_text = I18n::Text.new(text) text = i18n_text.translate(locale) end parse(text).accept formatter @@ -106,12 +106,12 @@ def normalize_comment(text) # Normalizes +text+ then builds a RDoc::Markup::Document from it def parse(text, format = 'rdoc') - return text if ::RDoc::Markup::Document === text - return text.parse if ::RDoc::Comment === text + return text if Markup::Document === text + return text.parse if Comment === text text = normalize_comment text # TODO remove, should not be necessary - return ::RDoc::Markup::Document.new if text =~ /\A\n*\z/ + return Markup::Document.new if text =~ /\A\n*\z/ MARKUP_FORMAT[format].parse text end @@ -122,7 +122,7 @@ def parse(text, format = 'rdoc') def snippet(text, limit = 100) document = parse text - ::RDoc::Markup::ToHtmlSnippet.new(limit).convert document + Markup::ToHtmlSnippet.new(limit).convert document end ## @@ -132,7 +132,7 @@ def strip_hashes(text) return text if text =~ /^(?>\s*)[^\#]/ empty = '' - empty = ::RDoc::Encoding.change_encoding empty, text.encoding + empty = Encoding.change_encoding empty, text.encoding text.gsub(/^\s*(#+)/) { $1.tr '#', ' ' }.gsub(/^\s+$/, empty) end @@ -155,14 +155,14 @@ def strip_stars(text) text = text.gsub %r%Document-method:\s+[\w:.#=!?|^&<>~+\-/*\%@`\[\]]+%, '' space = ' ' - space = ::RDoc::Encoding.change_encoding space, encoding if encoding + space = Encoding.change_encoding space, encoding if encoding text.sub! %r%/\*+% do space * $&.length end text.sub! %r%\*+/% do space * $&.length end text.gsub! %r%^[ \t]*\*%m do space * $&.length end empty = '' - empty = ::RDoc::Encoding.change_encoding empty, encoding if encoding + empty = Encoding.change_encoding empty, encoding if encoding text.gsub(/^\s+$/, empty) end diff --git a/lib/rdoc/tom_doc.rb b/lib/rdoc/tom_doc.rb index ea694bbb2c..8a003c7336 100644 --- a/lib/rdoc/tom_doc.rb +++ b/lib/rdoc/tom_doc.rb @@ -34,7 +34,7 @@ module RDoc # This class is documented in TomDoc format. Since this is a subclass of the # RDoc markup parser there isn't much to see here, unfortunately. - class TomDoc < ::RDoc::Markup::Parser + class TomDoc < Markup::Parser # Internal: Token accessor @@ -46,9 +46,9 @@ class TomDoc < ::RDoc::Markup::Parser # Returns nothing. def self.add_post_processor # :nodoc: - ::RDoc::Markup::PreProcess.post_process do |comment, code_object| + Markup::PreProcess.post_process do |comment, code_object| next unless code_object and - ::RDoc::Comment === comment and comment.format == 'tomdoc' + Comment === comment and comment.format == 'tomdoc' comment.text.gsub!(/\A(\s*# |)(Public|Internal|Deprecated):\s+/) do section = code_object.add_section $2 @@ -80,7 +80,7 @@ def self.parse(text) parser = new parser.tokenize text - doc = ::RDoc::Markup::Document.new + doc = Markup::Document.new parser.parse doc doc end @@ -105,13 +105,13 @@ def self.signature(comment) next false if found_signature found_heading ||= - ::RDoc::Markup::Heading === part && part.text == 'Signature' + Markup::Heading === part && part.text == 'Signature' next false unless found_heading - next true if ::RDoc::Markup::BlankLine === part + next true if Markup::BlankLine === part - if ::RDoc::Markup::Verbatim === part + if Markup::Verbatim === part signature = part found_signature = true end @@ -168,7 +168,7 @@ def build_verbatim(margin) def build_paragraph(margin) p :paragraph_start => margin if @debug - paragraph = ::RDoc::Markup::Paragraph.new + paragraph = Markup::Paragraph.new until @tokens.empty? do type, data, = get @@ -210,8 +210,8 @@ def parse_text(parent, indent) # :nodoc: if false == @seen_returns and 'Returns' == @section @seen_returns = true - parent << ::RDoc::Markup::Heading.new(3, 'Returns') - parent << ::RDoc::Markup::BlankLine.new + parent << Markup::Heading.new(3, 'Returns') + parent << Markup::BlankLine.new end parent << paragraph diff --git a/test/rdoc/markup/pre_process_test.rb b/test/rdoc/markup/pre_process_test.rb index 7cdba56fac..4dd8c0ece4 100644 --- a/test/rdoc/markup/pre_process_test.rb +++ b/test/rdoc/markup/pre_process_test.rb @@ -4,7 +4,7 @@ module RDoc class Markup - class PreProcessTest < ::RDoc::TestCase + class PreProcessTest < TestCase def setup super @@ -13,7 +13,7 @@ def setup @file_name = File.basename @tempfile.path @dir = File.dirname @tempfile.path - @pp = ::RDoc::Markup::PreProcess.new @tempfile.path, [@dir, File.expand_path('..', File.dirname(__FILE__))] + @pp = Markup::PreProcess.new @tempfile.path, [@dir, File.expand_path('..', File.dirname(__FILE__))] end def teardown @@ -23,16 +23,16 @@ def teardown end def test_class_register - ::RDoc::Markup::PreProcess.register 'for_test' do raise 'fail' end + Markup::PreProcess.register 'for_test' do raise 'fail' end - assert_equal %w[for_test], ::RDoc::Markup::PreProcess.registered.keys + assert_equal %w[for_test], Markup::PreProcess.registered.keys end def test_class_post_process - ::RDoc::Markup::PreProcess.post_process do end + Markup::PreProcess.post_process do end - assert_equal 1, ::RDoc::Markup::PreProcess.post_processors.length, - proc{::RDoc::Markup::PreProcess.post_processors.inspect} + assert_equal 1, Markup::PreProcess.post_processors.length, + proc{Markup::PreProcess.post_processors.inspect} end def test_include_file @@ -87,7 +87,7 @@ def test_include_file_in_other_directory def test_handle text = "# :stopdoc:\n" - code_object = ::RDoc::CodeObject.new + code_object = CodeObject.new output = @pp.handle text, code_object assert_equal "#\n", output @@ -96,7 +96,7 @@ def test_handle def test_handle_comment text = "# :stopdoc:\n" c = comment text - code_object = ::RDoc::CodeObject.new + code_object = CodeObject.new output = @pp.handle c, code_object @@ -120,9 +120,9 @@ def test_handle_markup_empty end def test_handle_post_process - cd = ::RDoc::CodeObject.new + cd = CodeObject.new - ::RDoc::Markup::PreProcess.post_process do |text, code_object| + Markup::PreProcess.post_process do |text, code_object| code_object.metadata[:stuff] = text :junk @@ -150,7 +150,7 @@ def test_handle_directive_blankline end def test_handle_directive_downcase - method = ::RDoc::AnyMethod.new 'm' + method = AnyMethod.new 'm' @pp.handle_directive '', 'ARG', 'a, b', method @@ -158,7 +158,7 @@ def test_handle_directive_downcase end def test_handle_directive_arg - method = ::RDoc::AnyMethod.new 'm' + method = AnyMethod.new 'm' @pp.handle_directive '', 'arg', 'a, b', method @@ -172,7 +172,7 @@ def test_handle_directive_arg_no_context end def test_handle_directive_args - method = ::RDoc::AnyMethod.new 'm' + method = AnyMethod.new 'm' @pp.handle_directive '', 'args', 'a, b', method @@ -204,7 +204,7 @@ def test_handle_directive_block_nil end def test_handle_directive_category - context = ::RDoc::Context.new + context = Context.new original_section = context.current_section @pp.handle_directive '', 'category', 'other', context @@ -213,7 +213,7 @@ def test_handle_directive_category end def test_handle_directive_doc - code_object = ::RDoc::CodeObject.new + code_object = CodeObject.new code_object.document_self = false code_object.force_documentation = false @@ -230,7 +230,7 @@ def test_handle_directive_doc_no_context end def test_handle_directive_enddoc - code_object = ::RDoc::CodeObject.new + code_object = CodeObject.new @pp.handle_directive '', 'enddoc', nil, code_object @@ -247,17 +247,17 @@ def test_handle_directive_include end def test_handle_directive_main - @pp.options = ::RDoc::Options.new + @pp.options = Options.new # :main: directive was removed; it is now treated as unhandled metadata - code_object = ::RDoc::CodeObject.new + code_object = CodeObject.new @pp.handle_directive '', 'main', 'M', code_object assert_equal 'M', code_object.metadata['main'] end def test_handle_directive_notnew - m = ::RDoc::AnyMethod.new 'm' + m = AnyMethod.new 'm' @pp.handle_directive '', 'notnew', nil, m @@ -265,7 +265,7 @@ def test_handle_directive_notnew end def test_handle_directive_not_new - m = ::RDoc::AnyMethod.new 'm' + m = AnyMethod.new 'm' @pp.handle_directive '', 'not_new', nil, m @@ -273,7 +273,7 @@ def test_handle_directive_not_new end def test_handle_directive_not_dash_new - m = ::RDoc::AnyMethod.new 'm' + m = AnyMethod.new 'm' @pp.handle_directive '', 'not-new', nil, m @@ -281,7 +281,7 @@ def test_handle_directive_not_dash_new end def test_handle_directive_nodoc - code_object = ::RDoc::CodeObject.new + code_object = CodeObject.new code_object.document_self = true code_object.document_children = true @@ -292,7 +292,7 @@ def test_handle_directive_nodoc end def test_handle_directive_nodoc_all - code_object = ::RDoc::CodeObject.new + code_object = CodeObject.new code_object.document_self = true code_object.document_children = true @@ -309,7 +309,7 @@ def test_handle_directive_nodoc_no_context end def test_handle_directive_registered - ::RDoc::Markup::PreProcess.register 'x' + Markup::PreProcess.register 'x' result = @pp.handle_directive '', 'x', 'y' @@ -331,7 +331,7 @@ def test_handle_directive_registered def test_handle_directive_registered_block called = nil - ::RDoc::Markup::PreProcess.register 'x' do |directive, param| + Markup::PreProcess.register 'x' do |directive, param| called = [directive, param] 'blah' end @@ -343,8 +343,8 @@ def test_handle_directive_registered_block end def test_handle_directive_registered_code_object - ::RDoc::Markup::PreProcess.register 'x' - code_object = ::RDoc::CodeObject.new + Markup::PreProcess.register 'x' + code_object = CodeObject.new @pp.handle_directive '', 'x', 'y', code_object @@ -368,7 +368,7 @@ def test_handle_directive_registered_code_object end def test_handle_directive_startdoc - code_object = ::RDoc::CodeObject.new + code_object = CodeObject.new code_object.stop_doc code_object.force_documentation = false @@ -380,7 +380,7 @@ def test_handle_directive_startdoc end def test_handle_directive_stopdoc - code_object = ::RDoc::CodeObject.new + code_object = CodeObject.new @pp.handle_directive '', 'stopdoc', nil, code_object @@ -389,17 +389,17 @@ def test_handle_directive_stopdoc end def test_handle_directive_title - @pp.options = ::RDoc::Options.new + @pp.options = Options.new # :title: directive was removed; it is now treated as unhandled metadata - code_object = ::RDoc::CodeObject.new + code_object = CodeObject.new @pp.handle_directive '', 'title', 'T', code_object assert_equal 'T', code_object.metadata['title'] end def test_handle_directive_unhandled - code_object = ::RDoc::CodeObject.new + code_object = CodeObject.new @pp.handle_directive '', 'x', 'y', code_object @@ -413,7 +413,7 @@ def test_handle_directive_unhandled end def test_handle_directive_unhandled_block - code_object = ::RDoc::CodeObject.new + code_object = CodeObject.new @pp.handle_directive '', 'x', 'y', code_object do false @@ -437,7 +437,7 @@ def test_handle_directive_unhandled_block end def test_handle_directive_yield - method = ::RDoc::AnyMethod.new 'm' + method = AnyMethod.new 'm' method.params = 'index, &block' @pp.handle_directive '', 'yield', 'item', method @@ -447,7 +447,7 @@ def test_handle_directive_yield end def test_handle_directive_yield_block_param - method = ::RDoc::AnyMethod.new 'm' + method = AnyMethod.new 'm' method.params = '&block' @pp.handle_directive '', 'yield', 'item', method @@ -457,7 +457,7 @@ def test_handle_directive_yield_block_param end def test_handle_directive_yield_no_context - method = ::RDoc::AnyMethod.new 'm' + method = AnyMethod.new 'm' @pp.handle_directive '', 'yield', 'item', method @@ -465,7 +465,7 @@ def test_handle_directive_yield_no_context end def test_handle_directive_yields - method = ::RDoc::AnyMethod.new 'm' + method = AnyMethod.new 'm' @pp.handle_directive '', 'yields', 'item', method diff --git a/test/rdoc/support/formatter_test_case.rb b/test/rdoc/support/formatter_test_case.rb index b77b32e7c2..765c8169af 100644 --- a/test/rdoc/support/formatter_test_case.rb +++ b/test/rdoc/support/formatter_test_case.rb @@ -37,7 +37,7 @@ class Markup # # end - class FormatterTestCase < ::RDoc::TestCase + class FormatterTestCase < TestCase ## # Call #setup when inheriting from this test case. @@ -56,7 +56,7 @@ class FormatterTestCase < ::RDoc::TestCase def setup super - @options = ::RDoc::Options.new + @options = Options.new @m = @RM.new diff --git a/test/rdoc/support/test_case.rb b/test/rdoc/support/test_case.rb index 39a807c9dd..fa6bd57464 100644 --- a/test/rdoc/support/test_case.rb +++ b/test/rdoc/support/test_case.rb @@ -47,20 +47,20 @@ def setup @top_level = nil - @RM = ::RDoc::Markup + @RM = Markup @pwd = Dir.pwd - @options = ::RDoc::Options.new - @store = ::RDoc::Store.new(@options) + @options = Options.new + @store = Store.new(@options) - @rdoc = ::RDoc::RDoc.new + @rdoc = RDoc.new @rdoc.store = @store @rdoc.options = @options @rdoc.generator = Object.new - ::RDoc::Markup::PreProcess.reset + Markup::PreProcess.reset end ## @@ -113,7 +113,7 @@ def block(*contents) # By default the comment has the 'rdoc' format. def comment(text, top_level = @top_level, language = nil) - comment = ::RDoc::Comment.new text, top_level, language + comment = Comment.new text, top_level, language comment end @@ -157,7 +157,7 @@ def list(type = nil, *items) def mu_pp(obj) # :nodoc: s = obj.pretty_inspect - s = ::RDoc::Encoding.change_encoding s, ::Encoding.default_external + s = Encoding.change_encoding s, ::Encoding.default_external s.chomp end diff --git a/test/rdoc/support/text_formatter_test_case.rb b/test/rdoc/support/text_formatter_test_case.rb index cab14462de..7e279764a4 100644 --- a/test/rdoc/support/text_formatter_test_case.rb +++ b/test/rdoc/support/text_formatter_test_case.rb @@ -28,7 +28,7 @@ class Markup # # end - class TextFormatterTestCase < ::RDoc::Markup::FormatterTestCase + class TextFormatterTestCase < Markup::FormatterTestCase ## # Adds test cases to the calling TestCase.