1require 'optparse'
2require 'pathname'
3
4##
5# RDoc::Options handles the parsing and storage of options
6#
7# == Saved Options
8#
9# You can save some options like the markup format in the
10# <tt>.rdoc_options</tt> file in your gem.  The easiest way to do this is:
11#
12#   rdoc --markup tomdoc --write-options
13#
14# Which will automatically create the file and fill it with the options you
15# specified.
16#
17# The following options will not be saved since they interfere with the user's
18# preferences or with the normal operation of RDoc:
19#
20# * +--coverage-report+
21# * +--dry-run+
22# * +--encoding+
23# * +--force-update+
24# * +--format+
25# * +--pipe+
26# * +--quiet+
27# * +--template+
28# * +--verbose+
29#
30# == Custom Options
31#
32# Generators can hook into RDoc::Options to add generator-specific command
33# line options.
34#
35# When <tt>--format</tt> is encountered in ARGV, RDoc calls ::setup_options on
36# the generator class to add extra options to the option parser.  Options for
37# custom generators must occur after <tt>--format</tt>.  <tt>rdoc --help</tt>
38# will list options for all installed generators.
39#
40# Example:
41#
42#   class RDoc::Generator::Spellcheck
43#     RDoc::RDoc.add_generator self
44#
45#     def self.setup_options rdoc_options
46#       op = rdoc_options.option_parser
47#
48#       op.on('--spell-dictionary DICTIONARY',
49#             RDoc::Options::Path) do |dictionary|
50#         rdoc_options.spell_dictionary = dictionary
51#       end
52#     end
53#   end
54#
55# == Option Validators
56#
57# OptionParser validators will validate and cast user input values.  In
58# addition to the validators that ship with OptionParser (String, Integer,
59# Float, TrueClass, FalseClass, Array, Regexp, Date, Time, URI, etc.),
60# RDoc::Options adds Path, PathArray and Template.
61
62class RDoc::Options
63
64  ##
65  # The deprecated options.
66
67  DEPRECATED = {
68    '--accessor'      => 'support discontinued',
69    '--diagram'       => 'support discontinued',
70    '--help-output'   => 'support discontinued',
71    '--image-format'  => 'was an option for --diagram',
72    '--inline-source' => 'source code is now always inlined',
73    '--merge'         => 'ri now always merges class information',
74    '--one-file'      => 'support discontinued',
75    '--op-name'       => 'support discontinued',
76    '--opname'        => 'support discontinued',
77    '--promiscuous'   => 'files always only document their content',
78    '--ri-system'     => 'Ruby installers use other techniques',
79  }
80
81  ##
82  # RDoc options ignored (or handled specially) by --write-options
83
84  SPECIAL = %w[
85    coverage_report
86    dry_run
87    encoding
88    files
89    force_output
90    force_update
91    generator
92    generator_name
93    generator_options
94    generators
95    op_dir
96    option_parser
97    pipe
98    rdoc_include
99    root
100    static_path
101    stylesheet_url
102    template
103    template_dir
104    update_output_dir
105    verbosity
106    write_options
107  ]
108
109  ##
110  # Option validator for OptionParser that matches a directory that exists on
111  # the filesystem.
112
113  Directory = Object.new
114
115  ##
116  # Option validator for OptionParser that matches a file or directory that
117  # exists on the filesystem.
118
119  Path = Object.new
120
121  ##
122  # Option validator for OptionParser that matches a comma-separated list of
123  # files or directories that exist on the filesystem.
124
125  PathArray = Object.new
126
127  ##
128  # Option validator for OptionParser that matches a template directory for an
129  # installed generator that lives in
130  # <tt>"rdoc/generator/template/#{template_name}"</tt>
131
132  Template = Object.new
133
134  ##
135  # Character-set for HTML output.  #encoding is preferred over #charset
136
137  attr_accessor :charset
138
139  ##
140  # If true, RDoc will not write any files.
141
142  attr_accessor :dry_run
143
144  ##
145  # The output encoding.  All input files will be transcoded to this encoding.
146  #
147  # The default encoding is UTF-8.  This is set via --encoding.
148
149  attr_accessor :encoding
150
151  ##
152  # Files matching this pattern will be excluded
153
154  attr_accessor :exclude
155
156  ##
157  # The list of files to be processed
158
159  attr_accessor :files
160
161  ##
162  # Create the output even if the output directory does not look
163  # like an rdoc output directory
164
165  attr_accessor :force_output
166
167  ##
168  # Scan newer sources than the flag file if true.
169
170  attr_accessor :force_update
171
172  ##
173  # Formatter to mark up text with
174
175  attr_accessor :formatter
176
177  ##
178  # Description of the output generator (set with the <tt>--format</tt> option)
179
180  attr_accessor :generator
181
182  ##
183  # For #==
184
185  attr_reader :generator_name # :nodoc:
186
187  ##
188  # Loaded generator options.  Used to prevent --help from loading the same
189  # options multiple times.
190
191  attr_accessor :generator_options
192
193  ##
194  # Old rdoc behavior: hyperlink all words that match a method name,
195  # even if not preceded by '#' or '::'
196
197  attr_accessor :hyperlink_all
198
199  ##
200  # Include line numbers in the source code
201
202  attr_accessor :line_numbers
203
204  ##
205  # Name of the file, class or module to display in the initial index page (if
206  # not specified the first file we encounter is used)
207
208  attr_accessor :main_page
209
210  ##
211  # The default markup format.  The default is 'rdoc'.  'markdown', 'tomdoc'
212  # and 'rd' are also built-in.
213
214  attr_accessor :markup
215
216  ##
217  # If true, only report on undocumented files
218
219  attr_accessor :coverage_report
220
221  ##
222  # The name of the output directory
223
224  attr_accessor :op_dir
225
226  ##
227  # The OptionParser for this instance
228
229  attr_accessor :option_parser
230
231  ##
232  # Directory where guides, FAQ, and other pages not associated with a class
233  # live.  You may leave this unset if these are at the root of your project.
234
235  attr_accessor :page_dir
236
237  ##
238  # Is RDoc in pipe mode?
239
240  attr_accessor :pipe
241
242  ##
243  # Array of directories to search for files to satisfy an :include:
244
245  attr_accessor :rdoc_include
246
247  ##
248  # Root of the source documentation will be generated for.  Set this when
249  # building documentation outside the source directory.  Defaults to the
250  # current directory.
251
252  attr_accessor :root
253
254  ##
255  # Include the '#' at the front of hyperlinked instance method names
256
257  attr_accessor :show_hash
258
259  ##
260  # Directory to copy static files from
261
262  attr_accessor :static_path
263
264  ##
265  # The number of columns in a tab
266
267  attr_accessor :tab_width
268
269  ##
270  # Template to be used when generating output
271
272  attr_accessor :template
273
274  ##
275  # Directory the template lives in
276
277  attr_accessor :template_dir
278
279  ##
280  # Documentation title
281
282  attr_accessor :title
283
284  ##
285  # Should RDoc update the timestamps in the output dir?
286
287  attr_accessor :update_output_dir
288
289  ##
290  # Verbosity, zero means quiet
291
292  attr_accessor :verbosity
293
294  ##
295  # URL of web cvs frontend
296
297  attr_accessor :webcvs
298
299  ##
300  # Minimum visibility of a documented method. One of +:public+,
301  # +:protected+, +:private+. May be overridden on a per-method
302  # basis with the :doc: directive.
303
304  attr_accessor :visibility
305
306  def initialize # :nodoc:
307    init_ivars
308  end
309
310  def init_ivars # :nodoc:
311    @dry_run = false
312    @exclude = []
313    @files = nil
314    @force_output = false
315    @force_update = true
316    @generator = nil
317    @generator_name = nil
318    @generator_options = []
319    @generators = RDoc::RDoc::GENERATORS
320    @hyperlink_all = false
321    @line_numbers = false
322    @main_page = nil
323    @markup = 'rdoc'
324    @coverage_report = false
325    @op_dir = nil
326    @page_dir = nil
327    @pipe = false
328    @rdoc_include = []
329    @root = Pathname(Dir.pwd)
330    @show_hash = false
331    @static_path = []
332    @stylesheet_url = nil # TODO remove in RDoc 4
333    @tab_width = 8
334    @template = nil
335    @template_dir = nil
336    @title = nil
337    @update_output_dir = true
338    @verbosity = 1
339    @visibility = :protected
340    @webcvs = nil
341    @write_options = false
342
343    if Object.const_defined? :Encoding then
344      @encoding = Encoding::UTF_8
345      @charset = @encoding.name
346    else
347      @encoding = nil
348      @charset = 'UTF-8'
349    end
350  end
351
352  def init_with map # :nodoc:
353    init_ivars
354
355    encoding = map['encoding']
356    @encoding = if Object.const_defined? :Encoding then
357                  encoding ? Encoding.find(encoding) : encoding
358                end
359
360    @charset        = map['charset']
361    @exclude        = map['exclude']
362    @generator_name = map['generator_name']
363    @hyperlink_all  = map['hyperlink_all']
364    @line_numbers   = map['line_numbers']
365    @main_page      = map['main_page']
366    @markup         = map['markup']
367    @op_dir         = map['op_dir']
368    @show_hash      = map['show_hash']
369    @tab_width      = map['tab_width']
370    @template_dir   = map['template_dir']
371    @title          = map['title']
372    @visibility     = map['visibility']
373    @webcvs         = map['webcvs']
374
375    @rdoc_include = sanitize_path map['rdoc_include']
376    @static_path  = sanitize_path map['static_path']
377  end
378
379  def yaml_initialize tag, map # :nodoc:
380    init_with map
381  end
382
383  def == other # :nodoc:
384    self.class === other and
385      @encoding       == other.encoding       and
386      @generator_name == other.generator_name and
387      @hyperlink_all  == other.hyperlink_all  and
388      @line_numbers   == other.line_numbers   and
389      @main_page      == other.main_page      and
390      @markup         == other.markup         and
391      @op_dir         == other.op_dir         and
392      @rdoc_include   == other.rdoc_include   and
393      @show_hash      == other.show_hash      and
394      @static_path    == other.static_path    and
395      @tab_width      == other.tab_width      and
396      @template       == other.template       and
397      @title          == other.title          and
398      @visibility     == other.visibility     and
399      @webcvs         == other.webcvs
400  end
401
402  ##
403  # Check that the files on the command line exist
404
405  def check_files
406    @files.delete_if do |file|
407      if File.exist? file then
408        if File.readable? file then
409          false
410        else
411          warn "file '#{file}' not readable"
412
413          true
414        end
415      else
416        warn "file '#{file}' not found"
417
418        true
419      end
420    end
421  end
422
423  ##
424  # Ensure only one generator is loaded
425
426  def check_generator
427    if @generator then
428      raise OptionParser::InvalidOption,
429        "generator already set to #{@generator_name}"
430    end
431  end
432
433  ##
434  # Set the title, but only if not already set. Used to set the title
435  # from a source file, so that a title set from the command line
436  # will have the priority.
437
438  def default_title=(string)
439    @title ||= string
440  end
441
442  ##
443  # For dumping YAML
444
445  def encode_with coder # :nodoc:
446    encoding = @encoding ? @encoding.name : nil
447
448    coder.add 'encoding', encoding
449    coder.add 'static_path',  sanitize_path(@static_path)
450    coder.add 'rdoc_include', sanitize_path(@rdoc_include)
451
452    ivars = instance_variables.map { |ivar| ivar.to_s[1..-1] }
453    ivars -= SPECIAL
454
455    ivars.sort.each do |ivar|
456      coder.add ivar, instance_variable_get("@#{ivar}")
457    end
458  end
459
460  ##
461  # Completes any unfinished option setup business such as filtering for
462  # existent files, creating a regexp for #exclude and setting a default
463  # #template.
464
465  def finish
466    @op_dir ||= 'doc'
467
468    @rdoc_include << "." if @rdoc_include.empty?
469
470    if @exclude.nil? or Regexp === @exclude then
471      # done, #finish is being re-run
472    elsif @exclude.empty? then
473      @exclude = nil
474    else
475      @exclude = Regexp.new(@exclude.join("|"))
476    end
477
478    finish_page_dir
479
480    check_files
481
482    # If no template was specified, use the default template for the output
483    # formatter
484
485    unless @template then
486      @template     = @generator_name
487      @template_dir = template_dir_for @template
488    end
489
490    self
491  end
492
493  ##
494  # Fixes the page_dir to be relative to the root_dir and adds the page_dir to
495  # the files list.
496
497  def finish_page_dir
498    return unless @page_dir
499
500    @files << @page_dir.to_s
501
502    page_dir = @page_dir.expand_path.relative_path_from @root
503
504    @page_dir = page_dir
505  end
506
507  ##
508  # Returns a properly-space list of generators and their descriptions.
509
510  def generator_descriptions
511    lengths = []
512
513    generators = RDoc::RDoc::GENERATORS.map do |name, generator|
514      lengths << name.length
515
516      description = generator::DESCRIPTION if
517        generator.const_defined? :DESCRIPTION
518
519      [name, description]
520    end
521
522    longest = lengths.max
523
524    generators.sort.map do |name, description|
525      if description then
526        "  %-*s - %s" % [longest, name, description]
527      else
528        "  #{name}"
529      end
530    end.join "\n"
531  end
532
533  ##
534  # Parses command line options.
535
536  def parse argv
537    ignore_invalid = true
538
539    argv.insert(0, *ENV['RDOCOPT'].split) if ENV['RDOCOPT']
540
541    opts = OptionParser.new do |opt|
542      @option_parser = opt
543      opt.program_name = File.basename $0
544      opt.version = RDoc::VERSION
545      opt.release = nil
546      opt.summary_indent = ' ' * 4
547      opt.banner = <<-EOF
548Usage: #{opt.program_name} [options] [names...]
549
550  Files are parsed, and the information they contain collected, before any
551  output is produced. This allows cross references between all files to be
552  resolved. If a name is a directory, it is traversed. If no names are
553  specified, all Ruby files in the current directory (and subdirectories) are
554  processed.
555
556  How RDoc generates output depends on the output formatter being used, and on
557  the options you give.
558
559  Options can be specified via the RDOCOPT environment variable, which
560  functions similar to the RUBYOPT environment variable for ruby.
561
562    $ export RDOCOPT="--show-hash"
563
564  will make rdoc show hashes in method links by default.  Command-line options
565  always will override those in RDOCOPT.
566
567  Available formatters:
568
569#{generator_descriptions}
570
571  RDoc understands the following file formats:
572
573      EOF
574
575      parsers = Hash.new { |h,parser| h[parser] = [] }
576
577      RDoc::Parser.parsers.each do |regexp, parser|
578        parsers[parser.name.sub('RDoc::Parser::', '')] << regexp.source
579      end
580
581      parsers.sort.each do |parser, regexp|
582        opt.banner << "  - #{parser}: #{regexp.join ', '}\n"
583      end
584
585      opt.banner << "\n  The following options are deprecated:\n\n"
586
587      name_length = DEPRECATED.keys.sort_by { |k| k.length }.last.length
588
589      DEPRECATED.sort_by { |k,| k }.each do |name, reason|
590        opt.banner << "    %*1$2$s  %3$s\n" % [-name_length, name, reason]
591      end
592
593      opt.accept Template do |template|
594        template_dir = template_dir_for template
595
596        unless template_dir then
597          $stderr.puts "could not find template #{template}"
598          nil
599        else
600          [template, template_dir]
601        end
602      end
603
604      opt.accept Directory do |directory|
605        directory = File.expand_path directory
606
607        raise OptionParser::InvalidArgument unless File.directory? directory
608
609        directory
610      end
611
612      opt.accept Path do |path|
613        path = File.expand_path path
614
615        raise OptionParser::InvalidArgument unless File.exist? path
616
617        path
618      end
619
620      opt.accept PathArray do |paths,|
621        paths = if paths then
622                  paths.split(',').map { |d| d unless d.empty? }
623                end
624
625        paths.map do |path|
626          path = File.expand_path path
627
628          raise OptionParser::InvalidArgument unless File.exist? path
629
630          path
631        end
632      end
633
634      opt.separator nil
635      opt.separator "Parsing options:"
636      opt.separator nil
637
638      if Object.const_defined? :Encoding then
639        opt.on("--encoding=ENCODING", "-e", Encoding.list.map { |e| e.name },
640               "Specifies the output encoding.  All files",
641               "read will be converted to this encoding.",
642               "The default encoding is UTF-8.",
643               "--encoding is preferred over --charset") do |value|
644                 @encoding = Encoding.find value
645                 @charset = @encoding.name # may not be valid value
646               end
647
648        opt.separator nil
649      end
650
651      opt.on("--all", "-a",
652             "Synonym for --visibility=private.") do |value|
653        @visibility = :private
654      end
655
656      opt.separator nil
657
658      opt.on("--exclude=PATTERN", "-x", Regexp,
659             "Do not process files or directories",
660             "matching PATTERN.") do |value|
661        @exclude << value
662      end
663
664      opt.separator nil
665
666      opt.on("--extension=NEW=OLD", "-E",
667             "Treat files ending with .new as if they",
668             "ended with .old. Using '-E cgi=rb' will",
669             "cause xxx.cgi to be parsed as a Ruby file.") do |value|
670        new, old = value.split(/=/, 2)
671
672        unless new and old then
673          raise OptionParser::InvalidArgument, "Invalid parameter to '-E'"
674        end
675
676        unless RDoc::Parser.alias_extension old, new then
677          raise OptionParser::InvalidArgument, "Unknown extension .#{old} to -E"
678        end
679      end
680
681      opt.separator nil
682
683      opt.on("--[no-]force-update", "-U",
684             "Forces rdoc to scan all sources even if",
685             "newer than the flag file.") do |value|
686        @force_update = value
687      end
688
689      opt.separator nil
690
691      opt.on("--pipe", "-p",
692             "Convert RDoc on stdin to HTML") do
693        @pipe = true
694      end
695
696      opt.separator nil
697
698      opt.on("--tab-width=WIDTH", "-w", OptionParser::DecimalInteger,
699             "Set the width of tab characters.") do |value|
700        @tab_width = value
701      end
702
703      opt.separator nil
704
705      opt.on("--visibility=VISIBILITY", "-V", RDoc::VISIBILITIES,
706             "Minimum visibility to document a method.",
707             "One of 'public', 'protected' (the default)",
708             "or 'private'. Can be abbreviated.") do |value|
709        @visibility = value
710      end
711
712      opt.separator nil
713
714      markup_formats = RDoc::Text::MARKUP_FORMAT.keys.sort
715
716      opt.on("--markup=MARKUP", markup_formats,
717             "The markup format for the named files.",
718             "The default is rdoc.  Valid values are:",
719             markup_formats.join(', ')) do |value|
720        @markup = value
721      end
722
723      opt.separator nil
724
725      opt.on("--root=ROOT", Directory,
726             "Root of the source tree documentation",
727             "will be generated for.  Set this when",
728             "building documentation outside the",
729             "source directory.  Default is the",
730             "current directory.") do |root|
731        @root = Pathname(root)
732      end
733
734      opt.separator nil
735
736      opt.on("--page-dir=DIR", Directory,
737             "Directory where guides, your FAQ or",
738             "other pages not associated with a class",
739             "live.  Set this when you don't store",
740             "such files at your project root.",
741             "NOTE: Do not use the same file name in",
742             "the page dir and the root of your project") do |page_dir|
743        @page_dir = Pathname(page_dir)
744      end
745
746      opt.separator nil
747      opt.separator "Common generator options:"
748      opt.separator nil
749
750      opt.on("--force-output", "-O",
751             "Forces rdoc to write the output files,",
752             "even if the output directory exists",
753             "and does not seem to have been created",
754             "by rdoc.") do |value|
755        @force_output = value
756      end
757
758      opt.separator nil
759
760      generator_text = @generators.keys.map { |name| "  #{name}" }.sort
761
762      opt.on("-f", "--fmt=FORMAT", "--format=FORMAT", @generators.keys,
763             "Set the output formatter.  One of:", *generator_text) do |value|
764        check_generator
765
766        @generator_name = value.downcase
767        setup_generator
768      end
769
770      opt.separator nil
771
772      opt.on("--include=DIRECTORIES", "-i", PathArray,
773             "Set (or add to) the list of directories to",
774             "be searched when satisfying :include:",
775             "requests. Can be used more than once.") do |value|
776        @rdoc_include.concat value.map { |dir| dir.strip }
777      end
778
779      opt.separator nil
780
781      opt.on("--[no-]coverage-report=[LEVEL]", "--[no-]dcov", "-C", Integer,
782             "Prints a report on undocumented items.",
783             "Does not generate files.") do |value|
784        value = 0 if value.nil? # Integer converts -C to nil
785
786        @coverage_report = value
787        @force_update = true if value
788      end
789
790      opt.separator nil
791
792      opt.on("--output=DIR", "--op", "-o",
793             "Set the output directory.") do |value|
794        @op_dir = value
795      end
796
797      opt.separator nil
798
799      opt.on("-d",
800             "Deprecated --diagram option.",
801             "Prevents firing debug mode",
802             "with legacy invocation.") do |value|
803      end
804
805      opt.separator nil
806      opt.separator 'HTML generator options:'
807      opt.separator nil
808
809      opt.on("--charset=CHARSET", "-c",
810             "Specifies the output HTML character-set.",
811             "Use --encoding instead of --charset if",
812             "available.") do |value|
813        @charset = value
814      end
815
816      opt.separator nil
817
818      opt.on("--hyperlink-all", "-A",
819             "Generate hyperlinks for all words that",
820             "correspond to known methods, even if they",
821             "do not start with '#' or '::' (legacy",
822             "behavior).") do |value|
823        @hyperlink_all = value
824      end
825
826      opt.separator nil
827
828      opt.on("--main=NAME", "-m",
829             "NAME will be the initial page displayed.") do |value|
830        @main_page = value
831      end
832
833      opt.separator nil
834
835      opt.on("--[no-]line-numbers", "-N",
836             "Include line numbers in the source code.",
837             "By default, only the number of the first",
838             "line is displayed, in a leading comment.") do |value|
839        @line_numbers = value
840      end
841
842      opt.separator nil
843
844      opt.on("--show-hash", "-H",
845             "A name of the form #name in a comment is a",
846             "possible hyperlink to an instance method",
847             "name. When displayed, the '#' is removed",
848             "unless this option is specified.") do |value|
849        @show_hash = value
850      end
851
852      opt.separator nil
853
854      opt.on("--template=NAME", "-T", Template,
855             "Set the template used when generating",
856             "output. The default depends on the",
857             "formatter used.") do |(template, template_dir)|
858        @template     = template
859        @template_dir = template_dir
860      end
861
862      opt.separator nil
863
864      opt.on("--title=TITLE", "-t",
865             "Set TITLE as the title for HTML output.") do |value|
866        @title = value
867      end
868
869      opt.separator nil
870
871      opt.on("--copy-files=PATH", Path,
872             "Specify a file or directory to copy static",
873             "files from.",
874             "If a file is given it will be copied into",
875             "the output dir.  If a directory is given the",
876             "entire directory will be copied.",
877             "You can use this multiple times") do |value|
878        @static_path << value
879      end
880
881      opt.separator nil
882
883      opt.on("--webcvs=URL", "-W",
884             "Specify a URL for linking to a web frontend",
885             "to CVS. If the URL contains a '\%s', the",
886             "name of the current file will be",
887             "substituted; if the URL doesn't contain a",
888             "'\%s', the filename will be appended to it.") do |value|
889        @webcvs = value
890      end
891
892      opt.separator nil
893      opt.separator "ri generator options:"
894      opt.separator nil
895
896      opt.on("--ri", "-r",
897             "Generate output for use by `ri`. The files",
898             "are stored in the '.rdoc' directory under",
899             "your home directory unless overridden by a",
900             "subsequent --op parameter, so no special",
901             "privileges are needed.") do |value|
902        check_generator
903
904        @generator_name = "ri"
905        @op_dir ||= RDoc::RI::Paths::HOMEDIR
906        setup_generator
907      end
908
909      opt.separator nil
910
911      opt.on("--ri-site", "-R",
912             "Generate output for use by `ri`. The files",
913             "are stored in a site-wide directory,",
914             "making them accessible to others, so",
915             "special privileges are needed.") do |value|
916        check_generator
917
918        @generator_name = "ri"
919        @op_dir = RDoc::RI::Paths::SITEDIR
920        setup_generator
921      end
922
923      opt.separator nil
924      opt.separator "Generic options:"
925      opt.separator nil
926
927      opt.on("--write-options",
928             "Write .rdoc_options to the current",
929             "directory with the given options.  Not all",
930             "options will be used.  See RDoc::Options",
931             "for details.") do |value|
932        @write_options = true
933      end
934
935      opt.separator nil
936
937      opt.on("--[no-]dry-run",
938             "Don't write any files") do |value|
939        @dry_run = value
940      end
941
942      opt.separator nil
943
944      opt.on("-D", "--[no-]debug",
945             "Displays lots on internal stuff.") do |value|
946        $DEBUG_RDOC = value
947      end
948
949      opt.separator nil
950
951      opt.on("--[no-]ignore-invalid",
952             "Ignore invalid options and continue",
953             "(default true).") do |value|
954        ignore_invalid = value
955      end
956
957      opt.separator nil
958
959      opt.on("--quiet", "-q",
960             "Don't show progress as we parse.") do |value|
961        @verbosity = 0
962      end
963
964      opt.separator nil
965
966      opt.on("--verbose", "-v",
967             "Display extra progress as RDoc parses") do |value|
968        @verbosity = 2
969      end
970
971      opt.separator nil
972
973      opt.on("--help",
974             "Display this help") do
975        RDoc::RDoc::GENERATORS.each_key do |generator|
976          setup_generator generator
977        end
978
979        puts opt.help
980        exit
981      end
982
983      opt.separator nil
984    end
985
986    setup_generator 'darkfish' if
987      argv.grep(/\A(-f|--fmt|--format|-r|-R|--ri|--ri-site)\b/).empty?
988
989    deprecated = []
990    invalid = []
991
992    begin
993      opts.parse! argv
994    rescue OptionParser::InvalidArgument, OptionParser::InvalidOption => e
995      if DEPRECATED[e.args.first] then
996        deprecated << e.args.first
997      elsif %w[--format --ri -r --ri-site -R].include? e.args.first then
998        raise
999      else
1000        invalid << e.args.join(' ')
1001      end
1002
1003      retry
1004    end
1005
1006    unless @generator then
1007      @generator = RDoc::Generator::Darkfish
1008      @generator_name = 'darkfish'
1009    end
1010
1011    if @pipe and not argv.empty? then
1012      @pipe = false
1013      invalid << '-p (with files)'
1014    end
1015
1016    unless quiet then
1017      deprecated.each do |opt|
1018        $stderr.puts 'option ' << opt << ' is deprecated: ' << DEPRECATED[opt]
1019      end
1020
1021      unless invalid.empty? then
1022        invalid = "invalid options: #{invalid.join ', '}"
1023
1024        if ignore_invalid then
1025          $stderr.puts invalid
1026          $stderr.puts '(invalid options are ignored)'
1027        else
1028          $stderr.puts opts
1029          $stderr.puts invalid
1030          exit 1
1031        end
1032      end
1033    end
1034
1035    @files = argv.dup
1036
1037    finish
1038
1039    if @write_options then
1040      write_options
1041      exit
1042    end
1043
1044    self
1045  end
1046
1047  ##
1048  # Don't display progress as we process the files
1049
1050  def quiet
1051    @verbosity.zero?
1052  end
1053
1054  ##
1055  # Set quietness to +bool+
1056
1057  def quiet= bool
1058    @verbosity = bool ? 0 : 1
1059  end
1060
1061  ##
1062  # Removes directories from +path+ that are outside the current directory
1063
1064  def sanitize_path path
1065    require 'pathname'
1066    dot = Pathname.new('.').expand_path
1067
1068    path.reject do |item|
1069      path = Pathname.new(item).expand_path
1070      relative = path.relative_path_from(dot).to_s
1071      relative.start_with? '..'
1072    end
1073  end
1074
1075  ##
1076  # Set up an output generator for the named +generator_name+.
1077  #
1078  # If the found generator responds to :setup_options it will be called with
1079  # the options instance.  This allows generators to add custom options or set
1080  # default options.
1081
1082  def setup_generator generator_name = @generator_name
1083    @generator = @generators[generator_name]
1084
1085    unless @generator then
1086      raise OptionParser::InvalidArgument,
1087            "Invalid output formatter #{generator_name}"
1088    end
1089
1090    return if @generator_options.include? @generator
1091
1092    @generator_name = generator_name
1093    @generator_options << @generator
1094
1095    if @generator.respond_to? :setup_options then
1096      @option_parser ||= OptionParser.new
1097      @generator.setup_options self
1098    end
1099  end
1100
1101  ##
1102  # Finds the template dir for +template+
1103
1104  def template_dir_for template
1105    template_path = File.join 'rdoc', 'generator', 'template', template
1106
1107    $LOAD_PATH.map do |path|
1108      File.join File.expand_path(path), template_path
1109    end.find do |dir|
1110      File.directory? dir
1111    end
1112  end
1113
1114  ##
1115  # This is compatibility code for syck
1116
1117  def to_yaml opts = {} # :nodoc:
1118    return super if YAML.const_defined?(:ENGINE) and not YAML::ENGINE.syck?
1119
1120    YAML.quick_emit self, opts do |out|
1121      out.map taguri, to_yaml_style do |map|
1122        encode_with map
1123      end
1124    end
1125  end
1126
1127  ##
1128  # Displays a warning using Kernel#warn if we're being verbose
1129
1130  def warn message
1131    super message if @verbosity > 1
1132  end
1133
1134  ##
1135  # Writes the YAML file .rdoc_options to the current directory containing the
1136  # parsed options.
1137
1138  def write_options
1139    RDoc.load_yaml
1140
1141    open '.rdoc_options', 'w' do |io|
1142      io.set_encoding Encoding::UTF_8 if Object.const_defined? :Encoding
1143
1144      YAML.dump self, io
1145    end
1146  end
1147
1148end
1149
1150