diff --git a/lib/rdoc/rubygems_hook.rb b/lib/rdoc/rubygems_hook.rb index 3160072e53..76ec328620 100644 --- a/lib/rdoc/rubygems_hook.rb +++ b/lib/rdoc/rubygems_hook.rb @@ -1,248 +1,17 @@ -# frozen_string_literal: true -require 'rubygems/user_interaction' -require 'fileutils' -require_relative '../rdoc' - -## -# Gem::RDoc provides methods to generate RDoc and ri data for installed gems -# upon gem installation. # -# This file is automatically required by RubyGems 1.9 and newer. - -class RDoc::RubygemsHook - - include Gem::UserInteraction - extend Gem::UserInteraction - - @rdoc_version = nil - @specs = [] - - ## - # Force installation of documentation? - - attr_accessor :force - - ## - # Generate rdoc? - - attr_accessor :generate_rdoc - - ## - # Generate ri data? - - attr_accessor :generate_ri - - class << self - - ## - # Loaded version of RDoc. Set by ::load_rdoc - - attr_reader :rdoc_version - - end - - ## - # Post installs hook that generates documentation for each specification in - # +specs+ - - def self.generation_hook installer, specs - start = Time.now - types = installer.document - - generate_rdoc = types.include? 'rdoc' - generate_ri = types.include? 'ri' - - specs.each do |spec| - new(spec, generate_rdoc, generate_ri).generate - end - - return unless generate_rdoc or generate_ri - - duration = (Time.now - start).to_i - names = specs.map(&:name).join ', ' - - say "Done installing documentation for #{names} after #{duration} seconds" - end - - ## - # Loads the RDoc generator - - def self.load_rdoc - return if @rdoc_version - - require_relative 'rdoc' - - @rdoc_version = Gem::Version.new ::RDoc::VERSION - 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. - - def initialize spec, generate_rdoc = false, generate_ri = true - @doc_dir = spec.doc_dir - @force = false - @rdoc = nil - @spec = spec - - @generate_rdoc = generate_rdoc - @generate_ri = generate_ri - - @rdoc_dir = spec.doc_dir 'rdoc' - @ri_dir = spec.doc_dir 'ri' - end - - ## - # 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 - - ## - # Generates documentation using the named +generator+ ("darkfish" 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 - - say "Installing #{generator_name} documentation for #{@spec.full_name}" - - 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 - end - end - end - - ## - # Generates RDoc and ri data - - def generate - return if @spec.default_gem? - return unless @generate_ri or @generate_rdoc - - setup - - options = nil - - args = @spec.rdoc_options - args.concat @spec.source_paths - args.concat @spec.extra_rdoc_files - - case config_args = Gem.configuration[:rdoc] - when String then - args = args.concat config_args.split(' ') - when Array then - args = args.concat config_args - end - - 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 - end - - options.quiet = !Gem.configuration.really_verbose - - @rdoc = new_rdoc - @rdoc.options = options - - store = RDoc::Store.new - store.encoding = options.encoding - store.dry_run = options.dry_run - store.main = options.main_page - store.title = options.title - - @rdoc.store = store - - say "Parsing documentation for #{@spec.full_name}" - - Dir.chdir @spec.full_gem_path do - @rdoc.parse_files options.files - end - - document 'ri', options, @ri_dir if - @generate_ri and (@force or not File.exist? @ri_dir) - - document 'darkfish', 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. - - def new_rdoc # :nodoc: - ::RDoc::RDoc.new - end - - ## - # Is rdoc documentation installed? - - def rdoc_installed? - File.exist? @rdoc_dir - end - - ## - # Removes generated RDoc and ri data - - def remove - base_dir = @spec.base_dir - - raise Gem::FilePermissionError, base_dir unless File.writable? base_dir - - FileUtils.rm_rf @rdoc_dir - FileUtils.rm_rf @ri_dir - end - - ## - # Is ri data installed? - - def ri_installed? - File.exist? @ri_dir - end - - ## - # Prepares the spec for documentation generation - - def setup - self.class.load_rdoc +# This class is referenced by RubyGems to create documents. +# Now, methods are moved to rubygems_plugin.rb. +# +# When old version RDoc is not used, +# this class is not used from RubyGems too. +# Then, remove this class. +# +module RDoc + class RubygemsHook + def initialize(spec); end - raise Gem::FilePermissionError, @doc_dir if - File.exist?(@doc_dir) and not File.writable?(@doc_dir) + def remove; end - FileUtils.mkdir_p @doc_dir unless File.exist? @doc_dir + def self.generation_hook installer, specs; end end - end diff --git a/lib/rubygems_plugin.rb b/lib/rubygems_plugin.rb new file mode 100644 index 0000000000..e2a4af767d --- /dev/null +++ b/lib/rubygems_plugin.rb @@ -0,0 +1,277 @@ +# frozen_string_literal: true + +require 'rubygems/user_interaction' +require 'fileutils' + +require_relative 'rdoc' + +## +# Gem::RDoc provides methods to generate RDoc and ri data for installed gems +# upon gem installation. +# +# This file is automatically required by RubyGems 1.9 and newer. +# +# Difference between RubygemsHook and RubyGemsHook. +# - RubygemsHook is executed from RubyGems. +# - RubyGemsHook is executed from rubygems_plugin. +# +# The reason why we use two classes. +# If there are classes named 'RubygemsHook', +# conflicts of name may happen. +# +# We can add methods to existing RubygemsHook, +# but we didn't adopt it because of less mentenability and create new class 'RubyGemsHook'. +# +class RDoc::RubyGemsHook + + include Gem::UserInteraction + extend Gem::UserInteraction + + @rdoc_version = nil + @specs = [] + + ## + # Force installation of documentation? + + attr_accessor :force + + ## + # Generate rdoc? + + attr_accessor :generate_rdoc + + ## + # Generate ri data? + + attr_accessor :generate_ri + + class << self + + ## + # Loaded version of RDoc. Set by ::load_rdoc + + attr_reader :rdoc_version + + end + + ## + # Post installs hook that generates documentation for each specification in + # +specs+ + + def self.generate installer, specs + start = Time.now + types = installer.document + + generate_rdoc = types.include? 'rdoc' + generate_ri = types.include? 'ri' + + specs.each do |spec| + new(spec, generate_rdoc, generate_ri).generate + end + + return unless generate_rdoc or generate_ri + + duration = (Time.now - start).to_i + names = specs.map(&:name).join ', ' + + say "Done installing documentation for #{names} after #{duration} seconds" + end + + def self.remove uninstaller + new(uninstaller.spec).remove + end + + ## + # Loads the RDoc generator + + def self.load_rdoc + return if @rdoc_version + + require_relative 'rdoc' + + @rdoc_version = Gem::Version.new ::RDoc::VERSION + 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. + + def initialize spec, generate_rdoc = false, generate_ri = true + @doc_dir = spec.doc_dir + @force = false + @rdoc = nil + @spec = spec + + @generate_rdoc = generate_rdoc + @generate_ri = generate_ri + + @rdoc_dir = spec.doc_dir 'rdoc' + @ri_dir = spec.doc_dir 'ri' + end + + ## + # 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 + + ## + # Generates documentation using the named +generator+ ("darkfish" 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 + + say "Installing #{generator_name} documentation for #{@spec.full_name}" + + 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 + end + end + end + + ## + # Generates RDoc and ri data + + def generate + return if @spec.default_gem? + return unless @generate_ri or @generate_rdoc + + setup + + options = nil + + args = @spec.rdoc_options + args.concat @spec.source_paths + args.concat @spec.extra_rdoc_files + + case config_args = Gem.configuration[:rdoc] + when String then + args = args.concat config_args.split(' ') + when Array then + args = args.concat config_args + end + + 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 + end + + options.quiet = !Gem.configuration.really_verbose + + @rdoc = new_rdoc + @rdoc.options = options + + store = RDoc::Store.new + store.encoding = options.encoding + store.dry_run = options.dry_run + store.main = options.main_page + store.title = options.title + + @rdoc.store = store + + say "Parsing documentation for #{@spec.full_name}" + + Dir.chdir @spec.full_gem_path do + @rdoc.parse_files options.files + end + + document 'ri', options, @ri_dir if + @generate_ri and (@force or not File.exist? @ri_dir) + + document 'darkfish', 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. + + def new_rdoc # :nodoc: + ::RDoc::RDoc.new + end + + ## + # Is rdoc documentation installed? + + def rdoc_installed? + File.exist? @rdoc_dir + end + + ## + # Removes generated RDoc and ri data + + def remove + base_dir = @spec.base_dir + + raise Gem::FilePermissionError, base_dir unless File.writable? base_dir + + FileUtils.rm_rf @rdoc_dir + FileUtils.rm_rf @ri_dir + end + + ## + # Is ri data installed? + + def ri_installed? + File.exist? @ri_dir + end + + ## + # Prepares the spec for documentation generation + + def setup + self.class.load_rdoc + + 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 + +end + +# To install dependency libraries of RDoc, you need to run bundle install. +# At that time, rdoc/markdown is not generated. +# If generate and remove are executed at that time, an error will occur. +# So, we can't register generate and remove to Gem at that time. +begin + require_relative 'rdoc/markdown' +rescue LoadError +else + Gem.done_installing(&RDoc::RubyGemsHook.method(:generate)) + Gem.pre_uninstall(&RDoc::RubyGemsHook.method(:remove)) +end diff --git a/rdoc.gemspec b/rdoc.gemspec index 93a281c8ae..a4a6e26543 100644 --- a/rdoc.gemspec +++ b/rdoc.gemspec @@ -222,6 +222,7 @@ RDoc includes the +rdoc+ and +ri+ tools for generating and displaying documentat "lib/rdoc/tom_doc.rb", "lib/rdoc/top_level.rb", "lib/rdoc/version.rb", + "lib/rubygems_plugin.rb", "man/ri.1", ] # files from .gitignore diff --git a/test/rdoc/test_rdoc_rubygems_hook.rb b/test/rdoc/test_rdoc_rubygems_hook.rb index 59a7ed0f89..5e1ee332a2 100644 --- a/test/rdoc/test_rdoc_rubygems_hook.rb +++ b/test/rdoc/test_rdoc_rubygems_hook.rb @@ -2,10 +2,17 @@ require 'rubygems' require 'fileutils' require 'tmpdir' -require_relative '../../lib/rdoc/rubygems_hook' require 'test/unit' -class TestRDocRubygemsHook < Test::Unit::TestCase +# This test requires lib/rubygems_plugin.rb . +# To execute this test under a ruby-core, lib/rubygems_plugin.rb should be synced to ruby/ruby. +# But I don't do so and skip this test under the situation because of avoiding ambiguity about rubygems_plugins on ruby/ruby. +begin + require_relative '../../lib/rubygems_plugin' +rescue LoadError +end + +class TestRDocRubyGemsHook < Test::Unit::TestCase def setup @a = Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY @@ -40,10 +47,10 @@ def setup FileUtils.touch File.join(@tempdir, 'a-2', 'lib', 'a.rb') FileUtils.touch File.join(@tempdir, 'a-2', 'README') - @hook = RDoc::RubygemsHook.new @a + @hook = RDoc::RubyGemsHook.new @a begin - RDoc::RubygemsHook.load_rdoc + RDoc::RubyGemsHook.load_rdoc rescue Gem::DocumentError => e omit e.message end @@ -63,7 +70,7 @@ def test_initialize refute @hook.generate_rdoc assert @hook.generate_ri - rdoc = RDoc::RubygemsHook.new @a, false, false + rdoc = RDoc::RubyGemsHook.new @a, false, false refute rdoc.generate_rdoc refute rdoc.generate_ri @@ -284,4 +291,4 @@ def test_setup_unwritable end end -end +end if defined?(RDoc::RubyGemsHook)