1require 'rubygems'
2require 'rubygems/user_interaction'
3require 'fileutils'
4require 'rdoc'
5
6##
7# Gem::RDoc provides methods to generate RDoc and ri data for installed gems
8# upon gem installation.
9#
10# This file is automatically required by RubyGems 1.9 and newer.
11
12class RDoc::RubygemsHook
13
14  include Gem::UserInteraction
15
16  @rdoc_version = nil
17  @specs = []
18
19  ##
20  # Force installation of documentation?
21
22  attr_accessor :force
23
24  ##
25  # Generate rdoc?
26
27  attr_accessor :generate_rdoc
28
29  ##
30  # Generate ri data?
31
32  attr_accessor :generate_ri
33
34  class << self
35
36    ##
37    # Loaded version of RDoc.  Set by ::load_rdoc
38
39    attr_reader :rdoc_version
40
41  end
42
43  ##
44  # Post installs hook that generates documentation for each specification in
45  # +specs+
46
47  def self.generation_hook installer, specs
48    types     = installer.document
49
50    generate_rdoc = types.include? 'rdoc'
51    generate_ri   = types.include? 'ri'
52
53    specs.each do |spec|
54      new(spec, generate_rdoc, generate_ri).generate
55    end
56  end
57
58  ##
59  # Loads the RDoc generator
60
61  def self.load_rdoc
62    return if @rdoc_version
63
64    require 'rdoc/rdoc'
65
66    @rdoc_version = Gem::Version.new ::RDoc::VERSION
67  end
68
69  ##
70  # Creates a new documentation generator for +spec+.  RDoc and ri data
71  # generation can be enabled or disabled through +generate_rdoc+ and
72  # +generate_ri+ respectively.
73  #
74  # Only +generate_ri+ is enabled by default.
75
76  def initialize spec, generate_rdoc = false, generate_ri = true
77    @doc_dir   = spec.doc_dir
78    @force     = false
79    @rdoc      = nil
80    @spec      = spec
81
82    @generate_rdoc = generate_rdoc
83    @generate_ri   = generate_ri
84
85    @rdoc_dir = spec.doc_dir 'rdoc'
86    @ri_dir   = spec.doc_dir 'ri'
87  end
88
89  ##
90  # Removes legacy rdoc arguments from +args+
91  #--
92  # TODO move to RDoc::Options
93
94  def delete_legacy_args args
95    args.delete '--inline-source'
96    args.delete '--promiscuous'
97    args.delete '-p'
98    args.delete '--one-file'
99  end
100
101  ##
102  # Generates documentation using the named +generator+ ("darkfish" or "ri")
103  # and following the given +options+.
104  #
105  # Documentation will be generated into +destination+
106
107  def document generator, options, destination
108    generator_name = generator
109
110    options = options.dup
111    options.exclude ||= [] # TODO maybe move to RDoc::Options#finish
112    options.setup_generator generator
113    options.op_dir = destination
114    options.finish
115
116    generator = options.generator.new @rdoc.store, options
117
118    @rdoc.options = options
119    @rdoc.generator = generator
120
121    say "Installing #{generator_name} documentation for #{@spec.full_name}"
122
123    FileUtils.mkdir_p options.op_dir
124
125    Dir.chdir options.op_dir do
126      begin
127        @rdoc.class.current = @rdoc
128        @rdoc.generator.generate
129      ensure
130        @rdoc.class.current = nil
131      end
132    end
133  end
134
135  ##
136  # Generates RDoc and ri data
137
138  def generate
139    return if @spec.default_gem?
140    return unless @generate_ri or @generate_rdoc
141
142    setup
143
144    options = nil
145
146    args = @spec.rdoc_options
147    args.concat @spec.require_paths
148    args.concat @spec.extra_rdoc_files
149
150    case config_args = Gem.configuration[:rdoc]
151    when String then
152      args = args.concat config_args.split
153    when Array then
154      args = args.concat config_args
155    end
156
157    delete_legacy_args args
158
159    Dir.chdir @spec.full_gem_path do
160      options = ::RDoc::Options.new
161      options.default_title = "#{@spec.full_name} Documentation"
162      options.parse args
163    end
164
165    options.quiet = !Gem.configuration.really_verbose
166
167    @rdoc = new_rdoc
168    @rdoc.options = options
169
170    store = RDoc::Store.new
171    store.encoding = options.encoding if options.respond_to? :encoding
172    store.dry_run  = options.dry_run
173    store.main     = options.main_page
174    store.title    = options.title
175
176    @rdoc.store = store
177
178    say "Parsing documentation for #{@spec.full_name}"
179
180    Dir.chdir @spec.full_gem_path do
181      @rdoc.parse_files options.files
182    end
183
184    document 'ri',       options, @ri_dir if
185      @generate_ri   and (@force or not File.exist? @ri_dir)
186
187    document 'darkfish', options, @rdoc_dir if
188      @generate_rdoc and (@force or not File.exist? @rdoc_dir)
189  end
190
191  ##
192  # #new_rdoc creates a new RDoc instance.  This method is provided only to
193  # make testing easier.
194
195  def new_rdoc # :nodoc:
196    ::RDoc::RDoc.new
197  end
198
199  ##
200  # Is rdoc documentation installed?
201
202  def rdoc_installed?
203    File.exist? @rdoc_dir
204  end
205
206  ##
207  # Removes generated RDoc and ri data
208
209  def remove
210    base_dir = @spec.base_dir
211
212    raise Gem::FilePermissionError, base_dir unless File.writable? base_dir
213
214    FileUtils.rm_rf @rdoc_dir
215    FileUtils.rm_rf @ri_dir
216  end
217
218  ##
219  # Is ri data installed?
220
221  def ri_installed?
222    File.exist? @ri_dir
223  end
224
225  ##
226  # Prepares the spec for documentation generation
227
228  def setup
229    self.class.load_rdoc
230
231    raise Gem::FilePermissionError, @doc_dir if
232      File.exist?(@doc_dir) and not File.writable?(@doc_dir)
233
234    FileUtils.mkdir_p @doc_dir unless File.exist? @doc_dir
235  end
236
237end
238
239