PageRenderTime 55ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/rdoc/ri/driver.rb

https://github.com/nazy/ruby
Ruby | 1059 lines | 814 code | 162 blank | 83 comment | 37 complexity | 3bbc42833fc84b28132c1fc98346d311 MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause, AGPL-3.0, 0BSD, Unlicense
  1. require 'abbrev'
  2. require 'optparse'
  3. begin
  4. require 'readline'
  5. rescue LoadError
  6. end
  7. require 'rdoc/ri'
  8. require 'rdoc/ri/paths'
  9. require 'rdoc/markup'
  10. require 'rdoc/markup/formatter'
  11. require 'rdoc/text'
  12. ##
  13. # For RubyGems backwards compatibility
  14. require 'rdoc/ri/formatter'
  15. ##
  16. # The RI driver implements the command-line ri tool.
  17. #
  18. # The driver supports:
  19. # * loading RI data from:
  20. # * Ruby's standard library
  21. # * RubyGems
  22. # * ~/.rdoc
  23. # * A user-supplied directory
  24. # * Paging output (uses RI_PAGER environment variable, PAGER environment
  25. # variable or the less, more and pager programs)
  26. # * Interactive mode with tab-completion
  27. # * Abbreviated names (ri Zl shows Zlib documentation)
  28. # * Colorized output
  29. # * Merging output from multiple RI data sources
  30. class RDoc::RI::Driver
  31. ##
  32. # Base Driver error class
  33. class Error < RDoc::RI::Error; end
  34. ##
  35. # Raised when a name isn't found in the ri data stores
  36. class NotFoundError < Error
  37. ##
  38. # Name that wasn't found
  39. alias name message
  40. def message # :nodoc:
  41. "Nothing known about #{super}"
  42. end
  43. end
  44. attr_accessor :stores
  45. ##
  46. # Controls the user of the pager vs $stdout
  47. attr_accessor :use_stdout
  48. ##
  49. # Default options for ri
  50. def self.default_options
  51. options = {}
  52. options[:use_stdout] = !$stdout.tty?
  53. options[:width] = 72
  54. options[:interactive] = false
  55. options[:use_cache] = true
  56. options[:profile] = false
  57. # By default all standard paths are used.
  58. options[:use_system] = true
  59. options[:use_site] = true
  60. options[:use_home] = true
  61. options[:use_gems] = true
  62. options[:extra_doc_dirs] = []
  63. return options
  64. end
  65. ##
  66. # Dump +data_path+ using pp
  67. def self.dump data_path
  68. require 'pp'
  69. open data_path, 'rb' do |io|
  70. pp Marshal.load(io.read)
  71. end
  72. end
  73. ##
  74. # Parses +argv+ and returns a Hash of options
  75. def self.process_args argv = []
  76. options = default_options
  77. opts = OptionParser.new
  78. setup_options(opts, options)
  79. argv = ENV['RI'].to_s.split.concat argv
  80. opts.parse!(argv)
  81. fixup_options(options, argv)
  82. rescue OptionParser::ParseError => e
  83. puts opts, nil, e
  84. abort
  85. end
  86. def self.setup_options(opt, options)
  87. begin
  88. opt.accept File do |file,|
  89. File.readable?(file) and not File.directory?(file) and file
  90. end
  91. opt.program_name = File.basename $0
  92. opt.version = RDoc::VERSION
  93. opt.release = nil
  94. opt.summary_indent = ' ' * 4
  95. opt.banner = <<-EOT
  96. Usage: #{opt.program_name} [options] [names...]
  97. Where name can be:
  98. Class | Class::method | Class#method | Class.method | method
  99. All class names may be abbreviated to their minimum unambiguous form. If a name
  100. is ambiguous, all valid options will be listed.
  101. The form '.' method matches either class or instance methods, while #method
  102. matches only instance and ::method matches only class methods.
  103. For example:
  104. #{opt.program_name} Fil
  105. #{opt.program_name} File
  106. #{opt.program_name} File.new
  107. #{opt.program_name} zip
  108. Note that shell quoting may be required for method names containing
  109. punctuation:
  110. #{opt.program_name} 'Array.[]'
  111. #{opt.program_name} compact\\!
  112. To see the default directories ri will search, run:
  113. #{opt.program_name} --list-doc-dirs
  114. Specifying the --system, --site, --home, --gems or --doc-dir options will
  115. limit ri to searching only the specified directories.
  116. Options may also be set in the 'RI' environment variable.
  117. EOT
  118. opt.separator nil
  119. opt.separator "Options:"
  120. opt.separator nil
  121. formatters = RDoc::Markup.constants.grep(/^To[A-Z][a-z]+$/).sort
  122. formatters = formatters.sort.map do |formatter|
  123. formatter.to_s.sub('To', '').downcase
  124. end
  125. opt.on("--format=NAME", "-f",
  126. "Uses the selected formatter. The default",
  127. "formatter is bs for paged output and ansi",
  128. "otherwise. Valid formatters are:",
  129. formatters.join(' '), formatters) do |value|
  130. options[:formatter] = RDoc::Markup.const_get "To#{value.capitalize}"
  131. end
  132. opt.separator nil
  133. opt.on("--no-pager", "-T",
  134. "Send output directly to stdout,",
  135. "rather than to a pager.") do
  136. options[:use_stdout] = true
  137. end
  138. opt.separator nil
  139. opt.on("--width=WIDTH", "-w", OptionParser::DecimalInteger,
  140. "Set the width of the output.") do |value|
  141. options[:width] = value
  142. end
  143. opt.separator nil
  144. opt.on("--interactive", "-i",
  145. "In interactive mode you can repeatedly",
  146. "look up methods with autocomplete.") do
  147. options[:interactive] = true
  148. end
  149. opt.separator nil
  150. opt.on("--[no-]profile",
  151. "Run with the ruby profiler") do |value|
  152. options[:profile] = value
  153. end
  154. opt.separator nil
  155. opt.separator "Data source options:"
  156. opt.separator nil
  157. opt.on("--list-doc-dirs",
  158. "List the directories from which ri will",
  159. "source documentation on stdout and exit.") do
  160. options[:list_doc_dirs] = true
  161. end
  162. opt.separator nil
  163. opt.on("--doc-dir=DIRNAME", "-d", Array,
  164. "List of directories from which to source",
  165. "documentation in addition to the standard",
  166. "directories. May be repeated.") do |value|
  167. value.each do |dir|
  168. unless File.directory? dir then
  169. raise OptionParser::InvalidArgument, "#{dir} is not a directory"
  170. end
  171. options[:extra_doc_dirs] << File.expand_path(dir)
  172. end
  173. end
  174. opt.separator nil
  175. opt.on("--no-standard-docs",
  176. "Do not include documentation from",
  177. "the Ruby standard library, site_lib,",
  178. "installed gems, or ~/.rdoc.",
  179. "Use with --doc-dir") do
  180. options[:use_system] = false
  181. options[:use_site] = false
  182. options[:use_gems] = false
  183. options[:use_home] = false
  184. end
  185. opt.separator nil
  186. opt.on("--[no-]system",
  187. "Include documentation from Ruby's standard",
  188. "library. Defaults to true.") do |value|
  189. options[:use_system] = value
  190. end
  191. opt.separator nil
  192. opt.on("--[no-]site",
  193. "Include documentation from libraries",
  194. "installed in site_lib.",
  195. "Defaults to true.") do |value|
  196. options[:use_site] = value
  197. end
  198. opt.separator nil
  199. opt.on("--[no-]gems",
  200. "Include documentation from RubyGems.",
  201. "Defaults to true.") do |value|
  202. options[:use_gems] = value
  203. end
  204. opt.separator nil
  205. opt.on("--[no-]home",
  206. "Include documentation stored in ~/.rdoc.",
  207. "Defaults to true.") do |value|
  208. options[:use_home] = value
  209. end
  210. opt.separator nil
  211. opt.separator "Debug options:"
  212. opt.separator nil
  213. opt.on("--dump=CACHE", File,
  214. "Dumps data from an ri cache or data file") do |value|
  215. options[:dump_path] = value
  216. end
  217. end
  218. end
  219. def self.fixup_options(options, argv)
  220. options[:names] = argv
  221. options[:use_stdout] ||= !$stdout.tty?
  222. options[:use_stdout] ||= options[:interactive]
  223. options[:width] ||= 72
  224. options
  225. end
  226. ##
  227. # Runs the ri command line executable using +argv+
  228. def self.run argv = ARGV
  229. options = process_args argv
  230. if options[:dump_path] then
  231. dump options[:dump_path]
  232. return
  233. end
  234. ri = new options
  235. ri.run
  236. end
  237. ##
  238. # Creates a new driver using +initial_options+ from ::process_args
  239. def initialize initial_options = {}
  240. @paging = false
  241. @classes = nil
  242. options = self.class.default_options.update(initial_options)
  243. @formatter_klass = options[:formatter]
  244. require 'profile' if options[:profile]
  245. @names = options[:names]
  246. @doc_dirs = []
  247. @stores = []
  248. RDoc::RI::Paths.each(options[:use_system], options[:use_site],
  249. options[:use_home], options[:use_gems],
  250. *options[:extra_doc_dirs]) do |path, type|
  251. @doc_dirs << path
  252. store = RDoc::RI::Store.new path, type
  253. store.load_cache
  254. @stores << store
  255. end
  256. @list_doc_dirs = options[:list_doc_dirs]
  257. @interactive = options[:interactive]
  258. @use_stdout = options[:use_stdout]
  259. end
  260. ##
  261. # Adds paths for undocumented classes +also_in+ to +out+
  262. def add_also_in out, also_in
  263. return if also_in.empty?
  264. out << RDoc::Markup::Rule.new(1)
  265. out << RDoc::Markup::Paragraph.new("Also found in:")
  266. paths = RDoc::Markup::Verbatim.new
  267. also_in.each do |store|
  268. paths.parts.push ' ', store.friendly_path, "\n"
  269. end
  270. out << paths
  271. end
  272. ##
  273. # Adds a class header to +out+ for class +name+ which is described in
  274. # +classes+.
  275. def add_class out, name, classes
  276. heading = if classes.all? { |klass| klass.module? } then
  277. name
  278. else
  279. superclass = classes.map do |klass|
  280. klass.superclass unless klass.module?
  281. end.compact.shift || 'Object'
  282. "#{name} < #{superclass}"
  283. end
  284. out << RDoc::Markup::Heading.new(1, heading)
  285. out << RDoc::Markup::BlankLine.new
  286. end
  287. ##
  288. # Adds "(from ...)" to +out+ for +store+
  289. def add_from out, store
  290. out << RDoc::Markup::Paragraph.new("(from #{store.friendly_path})")
  291. end
  292. ##
  293. # Adds +includes+ to +out+
  294. def add_includes out, includes
  295. return if includes.empty?
  296. out << RDoc::Markup::Rule.new(1)
  297. out << RDoc::Markup::Heading.new(1, "Includes:")
  298. includes.each do |modules, store|
  299. if modules.length == 1 then
  300. include = modules.first
  301. name = include.name
  302. path = store.friendly_path
  303. out << RDoc::Markup::Paragraph.new("#{name} (from #{path})")
  304. if include.comment then
  305. out << RDoc::Markup::BlankLine.new
  306. out << include.comment
  307. end
  308. else
  309. out << RDoc::Markup::Paragraph.new("(from #{store.friendly_path})")
  310. wout, with = modules.partition { |incl| incl.comment.empty? }
  311. out << RDoc::Markup::BlankLine.new unless with.empty?
  312. with.each do |incl|
  313. out << RDoc::Markup::Paragraph.new(incl.name)
  314. out << RDoc::Markup::BlankLine.new
  315. out << incl.comment
  316. end
  317. unless wout.empty? then
  318. verb = RDoc::Markup::Verbatim.new
  319. wout.each do |incl|
  320. verb.push ' ', incl.name, "\n"
  321. end
  322. out << verb
  323. end
  324. end
  325. end
  326. end
  327. ##
  328. # Adds a list of +methods+ to +out+ with a heading of +name+
  329. def add_method_list out, methods, name
  330. return unless methods
  331. out << RDoc::Markup::Heading.new(1, "#{name}:")
  332. out << RDoc::Markup::BlankLine.new
  333. out.push(*methods.map do |method|
  334. RDoc::Markup::Verbatim.new ' ', method
  335. end)
  336. out << RDoc::Markup::BlankLine.new
  337. end
  338. ##
  339. # Returns ancestor classes of +klass+
  340. def ancestors_of klass
  341. ancestors = []
  342. unexamined = [klass]
  343. seen = []
  344. loop do
  345. break if unexamined.empty?
  346. current = unexamined.shift
  347. seen << current
  348. stores = classes[current]
  349. break unless stores and not stores.empty?
  350. klasses = stores.map do |store|
  351. store.ancestors[current]
  352. end.flatten.uniq
  353. klasses = klasses - seen
  354. ancestors.push(*klasses)
  355. unexamined.push(*klasses)
  356. end
  357. ancestors.reverse
  358. end
  359. ##
  360. # For RubyGems backwards compatibility
  361. def class_cache # :nodoc:
  362. end
  363. ##
  364. # Hash mapping a known class or module to the stores it can be loaded from
  365. def classes
  366. return @classes if @classes
  367. @classes = {}
  368. @stores.each do |store|
  369. store.cache[:modules].each do |mod|
  370. # using default block causes searched-for modules to be added
  371. @classes[mod] ||= []
  372. @classes[mod] << store
  373. end
  374. end
  375. @classes
  376. end
  377. ##
  378. # Completes +name+ based on the caches. For Readline
  379. def complete name
  380. klasses = classes.keys
  381. completions = []
  382. klass, selector, method = parse_name name
  383. # may need to include Foo when given Foo::
  384. klass_name = method ? name : klass
  385. if name !~ /#|\./ then
  386. completions.push(*klasses.grep(/^#{klass_name}/))
  387. elsif selector then
  388. completions << klass if classes.key? klass
  389. elsif classes.key? klass_name then
  390. completions << klass_name
  391. end
  392. if completions.include? klass and name =~ /#|\.|::/ then
  393. methods = list_methods_matching name
  394. if not methods.empty? then
  395. # remove Foo if given Foo:: and a method was found
  396. completions.delete klass
  397. elsif selector then
  398. # replace Foo with Foo:: as given
  399. completions.delete klass
  400. completions << "#{klass}#{selector}"
  401. end
  402. completions.push(*methods)
  403. end
  404. completions.sort
  405. end
  406. ##
  407. # Converts +document+ to text and writes it to the pager
  408. def display document
  409. page do |io|
  410. text = document.accept formatter(io)
  411. io.write text
  412. end
  413. end
  414. ##
  415. # Outputs formatted RI data for class +name+. Groups undocumented classes
  416. def display_class name
  417. return if name =~ /#|\./
  418. klasses = []
  419. includes = []
  420. found = @stores.map do |store|
  421. begin
  422. klass = store.load_class name
  423. klasses << klass
  424. includes << [klass.includes, store] if klass.includes
  425. [store, klass]
  426. rescue Errno::ENOENT
  427. end
  428. end.compact
  429. return if found.empty?
  430. also_in = []
  431. includes.reject! do |modules,| modules.empty? end
  432. out = RDoc::Markup::Document.new
  433. add_class out, name, klasses
  434. add_includes out, includes
  435. found.each do |store, klass|
  436. comment = klass.comment
  437. class_methods = store.class_methods[klass.full_name]
  438. instance_methods = store.instance_methods[klass.full_name]
  439. attributes = store.attributes[klass.full_name]
  440. if comment.empty? and !(instance_methods or class_methods) then
  441. also_in << store
  442. next
  443. end
  444. add_from out, store
  445. unless comment.empty? then
  446. out << RDoc::Markup::Rule.new(1)
  447. out << comment
  448. end
  449. if class_methods or instance_methods or not klass.constants.empty? then
  450. out << RDoc::Markup::Rule.new
  451. end
  452. unless klass.constants.empty? then
  453. out << RDoc::Markup::Heading.new(1, "Constants:")
  454. out << RDoc::Markup::BlankLine.new
  455. list = RDoc::Markup::List.new :NOTE
  456. constants = klass.constants.sort_by { |constant| constant.name }
  457. list.push(*constants.map do |constant|
  458. parts = constant.comment.parts if constant.comment
  459. parts << RDoc::Markup::Paragraph.new('[not documented]') if
  460. parts.empty?
  461. RDoc::Markup::ListItem.new(constant.name, *parts)
  462. end)
  463. out << list
  464. end
  465. add_method_list out, class_methods, 'Class methods'
  466. add_method_list out, instance_methods, 'Instance methods'
  467. add_method_list out, attributes, 'Attributes'
  468. out << RDoc::Markup::BlankLine.new
  469. end
  470. add_also_in out, also_in
  471. display out
  472. end
  473. ##
  474. # Outputs formatted RI data for method +name+
  475. def display_method name
  476. found = load_methods_matching name
  477. raise NotFoundError, name if found.empty?
  478. out = RDoc::Markup::Document.new
  479. out << RDoc::Markup::Heading.new(1, name)
  480. out << RDoc::Markup::BlankLine.new
  481. found.each do |store, methods|
  482. methods.each do |method|
  483. out << RDoc::Markup::Paragraph.new("(from #{store.friendly_path})")
  484. unless name =~ /^#{Regexp.escape method.parent_name}/ then
  485. out << RDoc::Markup::Heading.new(3, "Implementation from #{method.parent_name}")
  486. end
  487. out << RDoc::Markup::Rule.new(1)
  488. if method.arglists then
  489. arglists = method.arglists.chomp.split "\n"
  490. arglists = arglists.map { |line| [' ', line, "\n"] }
  491. out << RDoc::Markup::Verbatim.new(*arglists.flatten)
  492. out << RDoc::Markup::Rule.new(1)
  493. end
  494. out << RDoc::Markup::BlankLine.new
  495. out << method.comment
  496. out << RDoc::Markup::BlankLine.new
  497. end
  498. end
  499. display out
  500. end
  501. ##
  502. # Outputs formatted RI data for the class or method +name+.
  503. #
  504. # Returns true if +name+ was found, false if it was not an alternative could
  505. # be guessed, raises an error if +name+ couldn't be guessed.
  506. def display_name name
  507. return true if display_class name
  508. display_method name if name =~ /::|#|\./
  509. true
  510. rescue NotFoundError
  511. matches = list_methods_matching name if name =~ /::|#|\./
  512. matches = classes.keys.grep(/^#{name}/) if matches.empty?
  513. raise if matches.empty?
  514. page do |io|
  515. io.puts "#{name} not found, maybe you meant:"
  516. io.puts
  517. io.puts matches.join("\n")
  518. end
  519. false
  520. end
  521. ##
  522. # Displays each name in +name+
  523. def display_names names
  524. names.each do |name|
  525. name = expand_name name
  526. display_name name
  527. end
  528. end
  529. ##
  530. # Expands abbreviated klass +klass+ into a fully-qualified class. "Zl::Da"
  531. # will be expanded to Zlib::DataError.
  532. def expand_class klass
  533. klass.split('::').inject '' do |expanded, klass_part|
  534. expanded << '::' unless expanded.empty?
  535. short = expanded << klass_part
  536. subset = classes.keys.select do |klass_name|
  537. klass_name =~ /^#{expanded}[^:]*$/
  538. end
  539. abbrevs = Abbrev.abbrev subset
  540. expanded = abbrevs[short]
  541. raise NotFoundError, short unless expanded
  542. expanded.dup
  543. end
  544. end
  545. ##
  546. # Expands the class portion of +name+ into a fully-qualified class. See
  547. # #expand_class.
  548. def expand_name name
  549. klass, selector, method = parse_name name
  550. return [selector, method].join if klass.empty?
  551. "#{expand_class klass}#{selector}#{method}"
  552. end
  553. ##
  554. # Yields items matching +name+ including the store they were found in, the
  555. # class being searched for, the class they were found in (an ancestor) the
  556. # types of methods to look up (from #method_type), and the method name being
  557. # searched for
  558. def find_methods name
  559. klass, selector, method = parse_name name
  560. types = method_type selector
  561. klasses = nil
  562. ambiguous = klass.empty?
  563. if ambiguous then
  564. klasses = classes.keys
  565. else
  566. klasses = ancestors_of klass
  567. klasses.unshift klass
  568. end
  569. methods = []
  570. klasses.each do |ancestor|
  571. ancestors = classes[ancestor]
  572. next unless ancestors
  573. klass = ancestor if ambiguous
  574. ancestors.each do |store|
  575. methods << [store, klass, ancestor, types, method]
  576. end
  577. end
  578. methods = methods.sort_by do |_, k, a, _, m|
  579. [k, a, m].compact
  580. end
  581. methods.each do |item|
  582. yield(*item) # :yields: store, klass, ancestor, types, method
  583. end
  584. self
  585. end
  586. ##
  587. # Creates a new RDoc::Markup::Formatter. If a formatter is given with -f,
  588. # use it. If we're outputting to a pager, use bs, otherwise ansi.
  589. def formatter(io)
  590. if @formatter_klass then
  591. @formatter_klass.new
  592. elsif paging? or !io.tty? then
  593. RDoc::Markup::ToBs.new
  594. else
  595. RDoc::Markup::ToAnsi.new
  596. end
  597. end
  598. ##
  599. # Runs ri interactively using Readline if it is available.
  600. def interactive
  601. puts "\nEnter the method name you want to look up."
  602. if defined? Readline then
  603. Readline.completion_proc = method :complete
  604. puts "You can use tab to autocomplete."
  605. end
  606. puts "Enter a blank line to exit.\n\n"
  607. loop do
  608. name = if defined? Readline then
  609. Readline.readline ">> "
  610. else
  611. print ">> "
  612. $stdin.gets
  613. end
  614. return if name.nil? or name.empty?
  615. name = expand_name name.strip
  616. begin
  617. display_name name
  618. rescue NotFoundError => e
  619. puts e.message
  620. end
  621. end
  622. rescue Interrupt
  623. exit
  624. end
  625. ##
  626. # Lists classes known to ri
  627. def list_known_classes
  628. classes = []
  629. stores.each do |store|
  630. classes << store.modules
  631. end
  632. classes = classes.flatten.uniq.sort
  633. page do |io|
  634. if paging? or io.tty? then
  635. io.puts "Classes and Modules known to ri:"
  636. io.puts
  637. end
  638. io.puts classes.join("\n")
  639. end
  640. end
  641. ##
  642. # Returns an Array of methods matching +name+
  643. def list_methods_matching name
  644. found = []
  645. find_methods name do |store, klass, ancestor, types, method|
  646. if types == :instance or types == :both then
  647. methods = store.instance_methods[ancestor]
  648. if methods then
  649. matches = methods.grep(/^#{method}/)
  650. matches = matches.map do |match|
  651. "#{klass}##{match}"
  652. end
  653. found.push(*matches)
  654. end
  655. end
  656. if types == :class or types == :both then
  657. methods = store.class_methods[ancestor]
  658. next unless methods
  659. matches = methods.grep(/^#{method}/)
  660. matches = matches.map do |match|
  661. "#{klass}::#{match}"
  662. end
  663. found.push(*matches)
  664. end
  665. end
  666. found.uniq
  667. end
  668. ##
  669. # Loads RI data for method +name+ on +klass+ from +store+. +type+ and
  670. # +cache+ indicate if it is a class or instance method.
  671. def load_method store, cache, klass, type, name
  672. methods = store.send(cache)[klass]
  673. return unless methods
  674. method = methods.find do |method_name|
  675. method_name == name
  676. end
  677. return unless method
  678. store.load_method klass, "#{type}#{method}"
  679. end
  680. ##
  681. # Returns an Array of RI data for methods matching +name+
  682. def load_methods_matching name
  683. found = []
  684. find_methods name do |store, klass, ancestor, types, method|
  685. methods = []
  686. methods << load_method(store, :class_methods, ancestor, '::', method) if
  687. types == :class or types == :both
  688. methods << load_method(store, :instance_methods, ancestor, '#', method) if
  689. types == :instance or types == :both
  690. found << [store, methods.compact]
  691. end
  692. found.reject do |path, methods| methods.empty? end
  693. end
  694. ##
  695. # Returns the type of method (:both, :instance, :class) for +selector+
  696. def method_type selector
  697. case selector
  698. when '.', nil then :both
  699. when '#' then :instance
  700. else :class
  701. end
  702. end
  703. ##
  704. # Paginates output through a pager program.
  705. def page
  706. if pager = setup_pager then
  707. begin
  708. yield pager
  709. ensure
  710. pager.close
  711. end
  712. else
  713. yield $stdout
  714. end
  715. rescue Errno::EPIPE
  716. ensure
  717. @paging = false
  718. end
  719. ##
  720. # Are we using a pager?
  721. def paging?
  722. @paging
  723. end
  724. ##
  725. # Extract the class, selector and method name parts from +name+ like
  726. # Foo::Bar#baz.
  727. #
  728. # NOTE: Given Foo::Bar, Bar is considered a class even though it may be a
  729. # method
  730. def parse_name(name)
  731. parts = name.split(/(::|#|\.)/)
  732. if parts.length == 1 then
  733. if parts.first =~ /^[a-z]/ then
  734. type = '.'
  735. meth = parts.pop
  736. else
  737. type = nil
  738. meth = nil
  739. end
  740. elsif parts.length == 2 or parts.last =~ /::|#|\./ then
  741. type = parts.pop
  742. meth = nil
  743. elsif parts[-2] != '::' or parts.last !~ /^[A-Z]/ then
  744. meth = parts.pop
  745. type = parts.pop
  746. end
  747. klass = parts.join
  748. [klass, type, meth]
  749. end
  750. ##
  751. # Looks up and displays ri data according to the options given.
  752. def run
  753. if @list_doc_dirs then
  754. puts @doc_dirs
  755. elsif @interactive then
  756. interactive
  757. elsif @names.empty? then
  758. list_known_classes
  759. else
  760. display_names @names
  761. end
  762. rescue NotFoundError => e
  763. abort e.message
  764. end
  765. ##
  766. # Sets up a pager program to pass output through. Tries the RI_PAGER and
  767. # PAGER environment variables followed by pager, less then more.
  768. def setup_pager
  769. return if @use_stdout
  770. pagers = [ENV['RI_PAGER'], ENV['PAGER'], 'pager', 'less', 'more']
  771. pagers.compact.uniq.each do |pager|
  772. next unless pager
  773. io = IO.popen pager, "w" rescue next
  774. next if $? and $?.exited? # pager didn't work
  775. @paging = true
  776. return io
  777. end
  778. @use_stdout = true
  779. nil
  780. end
  781. end