1require 'abbrev' 2require 'optparse' 3 4begin 5 require 'readline' 6rescue LoadError 7end 8 9begin 10 require 'win32console' 11rescue LoadError 12end 13 14require 'rdoc' 15 16## 17# For RubyGems backwards compatibility 18 19require 'rdoc/ri/formatter' 20 21## 22# The RI driver implements the command-line ri tool. 23# 24# The driver supports: 25# * loading RI data from: 26# * Ruby's standard library 27# * RubyGems 28# * ~/.rdoc 29# * A user-supplied directory 30# * Paging output (uses RI_PAGER environment variable, PAGER environment 31# variable or the less, more and pager programs) 32# * Interactive mode with tab-completion 33# * Abbreviated names (ri Zl shows Zlib documentation) 34# * Colorized output 35# * Merging output from multiple RI data sources 36 37class RDoc::RI::Driver 38 39 ## 40 # Base Driver error class 41 42 class Error < RDoc::RI::Error; end 43 44 ## 45 # Raised when a name isn't found in the ri data stores 46 47 class NotFoundError < Error 48 49 ## 50 # Name that wasn't found 51 52 alias name message 53 54 def message # :nodoc: 55 "Nothing known about #{super}" 56 end 57 end 58 59 ## 60 # Show all method documentation following a class or module 61 62 attr_accessor :show_all 63 64 ## 65 # An RDoc::RI::Store for each entry in the RI path 66 67 attr_accessor :stores 68 69 ## 70 # Controls the user of the pager vs $stdout 71 72 attr_accessor :use_stdout 73 74 ## 75 # Default options for ri 76 77 def self.default_options 78 options = {} 79 options[:interactive] = false 80 options[:profile] = false 81 options[:show_all] = false 82 options[:use_cache] = true 83 options[:use_stdout] = !$stdout.tty? 84 options[:width] = 72 85 86 # By default all standard paths are used. 87 options[:use_system] = true 88 options[:use_site] = true 89 options[:use_home] = true 90 options[:use_gems] = true 91 options[:extra_doc_dirs] = [] 92 93 return options 94 end 95 96 ## 97 # Dump +data_path+ using pp 98 99 def self.dump data_path 100 require 'pp' 101 102 open data_path, 'rb' do |io| 103 pp Marshal.load(io.read) 104 end 105 end 106 107 ## 108 # Parses +argv+ and returns a Hash of options 109 110 def self.process_args argv 111 options = default_options 112 113 opts = OptionParser.new do |opt| 114 opt.accept File do |file,| 115 File.readable?(file) and not File.directory?(file) and file 116 end 117 118 opt.program_name = File.basename $0 119 opt.version = RDoc::VERSION 120 opt.release = nil 121 opt.summary_indent = ' ' * 4 122 123 opt.banner = <<-EOT 124Usage: #{opt.program_name} [options] [names...] 125 126Where name can be: 127 128 Class | Module | Module::Class 129 130 Class::method | Class#method | Class.method | method 131 132 gem_name: | gem_name:README | gem_name:History 133 134All class names may be abbreviated to their minimum unambiguous form. If a name 135is ambiguous, all valid options will be listed. 136 137A '.' matches either class or instance methods, while #method 138matches only instance and ::method matches only class methods. 139 140README and other files may be displayed by prefixing them with the gem name 141they're contained in. If the gem name is followed by a ':' all files in the 142gem will be shown. The file name extension may be omitted where it is 143unambiguous. 144 145For example: 146 147 #{opt.program_name} Fil 148 #{opt.program_name} File 149 #{opt.program_name} File.new 150 #{opt.program_name} zip 151 #{opt.program_name} rdoc:README 152 153Note that shell quoting or escaping may be required for method names containing 154punctuation: 155 156 #{opt.program_name} 'Array.[]' 157 #{opt.program_name} compact\\! 158 159To see the default directories ri will search, run: 160 161 #{opt.program_name} --list-doc-dirs 162 163Specifying the --system, --site, --home, --gems or --doc-dir options will 164limit ri to searching only the specified directories. 165 166ri options may be set in the 'RI' environment variable. 167 168The ri pager can be set with the 'RI_PAGER' environment variable or the 169'PAGER' environment variable. 170 EOT 171 172 opt.separator nil 173 opt.separator "Options:" 174 175 opt.separator nil 176 177 opt.on("--[no-]interactive", "-i", 178 "In interactive mode you can repeatedly", 179 "look up methods with autocomplete.") do |interactive| 180 options[:interactive] = interactive 181 end 182 183 opt.separator nil 184 185 opt.on("--[no-]all", "-a", 186 "Show all documentation for a class or", 187 "module.") do |show_all| 188 options[:show_all] = show_all 189 end 190 191 opt.separator nil 192 193 opt.on("--[no-]list", "-l", 194 "List classes ri knows about.") do |list| 195 options[:list] = list 196 end 197 198 opt.separator nil 199 200 opt.on("--[no-]pager", "-T", 201 "Send output directly to stdout,", 202 "rather than to a pager.") do |use_pager| 203 options[:use_stdout] = !use_pager 204 end 205 206 opt.separator nil 207 208 opt.on("--width=WIDTH", "-w", OptionParser::DecimalInteger, 209 "Set the width of the output.") do |width| 210 options[:width] = width 211 end 212 213 opt.separator nil 214 215 opt.on("--server [PORT]", Integer, 216 "Run RDoc server on the given port.", 217 "The default port is 8214.") do |port| 218 options[:server] = port || 8214 219 end 220 221 opt.separator nil 222 223 formatters = RDoc::Markup.constants.grep(/^To[A-Z][a-z]+$/).sort 224 formatters = formatters.sort.map do |formatter| 225 formatter.to_s.sub('To', '').downcase 226 end 227 formatters -= %w[html label test] # remove useless output formats 228 229 opt.on("--format=NAME", "-f", 230 "Uses the selected formatter. The default", 231 "formatter is bs for paged output and ansi", 232 "otherwise. Valid formatters are:", 233 formatters.join(' '), formatters) do |value| 234 options[:formatter] = RDoc::Markup.const_get "To#{value.capitalize}" 235 end 236 237 opt.separator nil 238 opt.separator "Data source options:" 239 opt.separator nil 240 241 opt.on("--[no-]list-doc-dirs", 242 "List the directories from which ri will", 243 "source documentation on stdout and exit.") do |list_doc_dirs| 244 options[:list_doc_dirs] = list_doc_dirs 245 end 246 247 opt.separator nil 248 249 opt.on("--doc-dir=DIRNAME", "-d", Array, 250 "List of directories from which to source", 251 "documentation in addition to the standard", 252 "directories. May be repeated.") do |value| 253 value.each do |dir| 254 unless File.directory? dir then 255 raise OptionParser::InvalidArgument, "#{dir} is not a directory" 256 end 257 258 options[:extra_doc_dirs] << File.expand_path(dir) 259 end 260 end 261 262 opt.separator nil 263 264 opt.on("--no-standard-docs", 265 "Do not include documentation from", 266 "the Ruby standard library, site_lib,", 267 "installed gems, or ~/.rdoc.", 268 "Use with --doc-dir") do 269 options[:use_system] = false 270 options[:use_site] = false 271 options[:use_gems] = false 272 options[:use_home] = false 273 end 274 275 opt.separator nil 276 277 opt.on("--[no-]system", 278 "Include documentation from Ruby's standard", 279 "library. Defaults to true.") do |value| 280 options[:use_system] = value 281 end 282 283 opt.separator nil 284 285 opt.on("--[no-]site", 286 "Include documentation from libraries", 287 "installed in site_lib.", 288 "Defaults to true.") do |value| 289 options[:use_site] = value 290 end 291 292 opt.separator nil 293 294 opt.on("--[no-]gems", 295 "Include documentation from RubyGems.", 296 "Defaults to true.") do |value| 297 options[:use_gems] = value 298 end 299 300 opt.separator nil 301 302 opt.on("--[no-]home", 303 "Include documentation stored in ~/.rdoc.", 304 "Defaults to true.") do |value| 305 options[:use_home] = value 306 end 307 308 opt.separator nil 309 opt.separator "Debug options:" 310 opt.separator nil 311 312 opt.on("--[no-]profile", 313 "Run with the ruby profiler") do |value| 314 options[:profile] = value 315 end 316 317 opt.separator nil 318 319 opt.on("--dump=CACHE", File, 320 "Dumps data from an ri cache or data file") do |value| 321 options[:dump_path] = value 322 end 323 end 324 325 argv = ENV['RI'].to_s.split.concat argv 326 327 opts.parse! argv 328 329 options[:names] = argv 330 331 options[:use_stdout] ||= !$stdout.tty? 332 options[:use_stdout] ||= options[:interactive] 333 options[:width] ||= 72 334 335 options 336 337 rescue OptionParser::InvalidArgument, OptionParser::InvalidOption => e 338 puts opts 339 puts 340 puts e 341 exit 1 342 end 343 344 ## 345 # Runs the ri command line executable using +argv+ 346 347 def self.run argv = ARGV 348 options = process_args argv 349 350 if options[:dump_path] then 351 dump options[:dump_path] 352 return 353 end 354 355 ri = new options 356 ri.run 357 end 358 359 ## 360 # Creates a new driver using +initial_options+ from ::process_args 361 362 def initialize initial_options = {} 363 @paging = false 364 @classes = nil 365 366 options = self.class.default_options.update(initial_options) 367 368 @formatter_klass = options[:formatter] 369 370 require 'profile' if options[:profile] 371 372 @names = options[:names] 373 @list = options[:list] 374 375 @doc_dirs = [] 376 @stores = [] 377 378 RDoc::RI::Paths.each(options[:use_system], options[:use_site], 379 options[:use_home], options[:use_gems], 380 *options[:extra_doc_dirs]) do |path, type| 381 @doc_dirs << path 382 383 store = RDoc::RI::Store.new path, type 384 store.load_cache 385 @stores << store 386 end 387 388 @list_doc_dirs = options[:list_doc_dirs] 389 390 @interactive = options[:interactive] 391 @server = options[:server] 392 @use_stdout = options[:use_stdout] 393 @show_all = options[:show_all] 394 395 # pager process for jruby 396 @jruby_pager_process = nil 397 end 398 399 ## 400 # Adds paths for undocumented classes +also_in+ to +out+ 401 402 def add_also_in out, also_in 403 return if also_in.empty? 404 405 out << RDoc::Markup::Rule.new(1) 406 out << RDoc::Markup::Paragraph.new("Also found in:") 407 408 paths = RDoc::Markup::Verbatim.new 409 also_in.each do |store| 410 paths.parts.push store.friendly_path, "\n" 411 end 412 out << paths 413 end 414 415 ## 416 # Adds a class header to +out+ for class +name+ which is described in 417 # +classes+. 418 419 def add_class out, name, classes 420 heading = if classes.all? { |klass| klass.module? } then 421 name 422 else 423 superclass = classes.map do |klass| 424 klass.superclass unless klass.module? 425 end.compact.shift || 'Object' 426 427 superclass = superclass.full_name unless String === superclass 428 429 "#{name} < #{superclass}" 430 end 431 432 out << RDoc::Markup::Heading.new(1, heading) 433 out << RDoc::Markup::BlankLine.new 434 end 435 436 ## 437 # Adds "(from ...)" to +out+ for +store+ 438 439 def add_from out, store 440 out << RDoc::Markup::Paragraph.new("(from #{store.friendly_path})") 441 end 442 443 ## 444 # Adds +extends+ to +out+ 445 446 def add_extends out, extends 447 add_extension_modules out, 'Extended by', extends 448 end 449 450 ## 451 # Adds a list of +extensions+ to this module of the given +type+ to +out+. 452 # add_includes and add_extends call this, so you should use those directly. 453 454 def add_extension_modules out, type, extensions 455 return if extensions.empty? 456 457 out << RDoc::Markup::Rule.new(1) 458 out << RDoc::Markup::Heading.new(1, "#{type}:") 459 460 extensions.each do |modules, store| 461 if modules.length == 1 then 462 include = modules.first 463 name = include.name 464 path = store.friendly_path 465 out << RDoc::Markup::Paragraph.new("#{name} (from #{path})") 466 467 if include.comment then 468 out << RDoc::Markup::BlankLine.new 469 out << include.comment 470 end 471 else 472 out << RDoc::Markup::Paragraph.new("(from #{store.friendly_path})") 473 474 wout, with = modules.partition { |incl| incl.comment.empty? } 475 476 out << RDoc::Markup::BlankLine.new unless with.empty? 477 478 with.each do |incl| 479 out << RDoc::Markup::Paragraph.new(incl.name) 480 out << RDoc::Markup::BlankLine.new 481 out << incl.comment 482 end 483 484 unless wout.empty? then 485 verb = RDoc::Markup::Verbatim.new 486 487 wout.each do |incl| 488 verb.push incl.name, "\n" 489 end 490 491 out << verb 492 end 493 end 494 end 495 end 496 497 ## 498 # Adds +includes+ to +out+ 499 500 def add_includes out, includes 501 add_extension_modules out, 'Includes', includes 502 end 503 504 ## 505 # Looks up the method +name+ and adds it to +out+ 506 507 def add_method out, name 508 filtered = lookup_method name 509 510 method_out = method_document name, filtered 511 512 out.concat method_out.parts 513 end 514 515 ## 516 # Adds documentation for all methods in +klass+ to +out+ 517 518 def add_method_documentation out, klass 519 klass.method_list.each do |method| 520 begin 521 add_method out, method.full_name 522 rescue NotFoundError 523 next 524 end 525 end 526 end 527 528 ## 529 # Adds a list of +methods+ to +out+ with a heading of +name+ 530 531 def add_method_list out, methods, name 532 return if methods.empty? 533 534 out << RDoc::Markup::Heading.new(1, "#{name}:") 535 out << RDoc::Markup::BlankLine.new 536 537 if @use_stdout and !@interactive then 538 out.concat methods.map { |method| 539 RDoc::Markup::Verbatim.new method 540 } 541 else 542 out << RDoc::Markup::IndentedParagraph.new(2, methods.join(', ')) 543 end 544 545 out << RDoc::Markup::BlankLine.new 546 end 547 548 ## 549 # Returns ancestor classes of +klass+ 550 551 def ancestors_of klass 552 ancestors = [] 553 554 unexamined = [klass] 555 seen = [] 556 557 loop do 558 break if unexamined.empty? 559 current = unexamined.shift 560 seen << current 561 562 stores = classes[current] 563 564 break unless stores and not stores.empty? 565 566 klasses = stores.map do |store| 567 store.ancestors[current] 568 end.flatten.uniq 569 570 klasses = klasses - seen 571 572 ancestors.concat klasses 573 unexamined.concat klasses 574 end 575 576 ancestors.reverse 577 end 578 579 ## 580 # For RubyGems backwards compatibility 581 582 def class_cache # :nodoc: 583 end 584 585 ## 586 # Builds a RDoc::Markup::Document from +found+, +klasess+ and +includes+ 587 588 def class_document name, found, klasses, includes, extends 589 also_in = [] 590 591 out = RDoc::Markup::Document.new 592 593 add_class out, name, klasses 594 595 add_includes out, includes 596 add_extends out, extends 597 598 found.each do |store, klass| 599 comment = klass.comment 600 # TODO the store's cache should always return an empty Array 601 class_methods = store.class_methods[klass.full_name] || [] 602 instance_methods = store.instance_methods[klass.full_name] || [] 603 attributes = store.attributes[klass.full_name] || [] 604 605 if comment.empty? and 606 instance_methods.empty? and class_methods.empty? then 607 also_in << store 608 next 609 end 610 611 add_from out, store 612 613 unless comment.empty? then 614 out << RDoc::Markup::Rule.new(1) 615 616 if comment.merged? then 617 parts = comment.parts 618 parts = parts.zip [RDoc::Markup::BlankLine.new] * parts.length 619 parts.flatten! 620 parts.pop 621 622 out.concat parts 623 else 624 out << comment 625 end 626 end 627 628 if class_methods or instance_methods or not klass.constants.empty? then 629 out << RDoc::Markup::Rule.new(1) 630 end 631 632 unless klass.constants.empty? then 633 out << RDoc::Markup::Heading.new(1, "Constants:") 634 out << RDoc::Markup::BlankLine.new 635 list = RDoc::Markup::List.new :NOTE 636 637 constants = klass.constants.sort_by { |constant| constant.name } 638 639 list.items.concat constants.map { |constant| 640 parts = constant.comment.parts if constant.comment 641 parts << RDoc::Markup::Paragraph.new('[not documented]') if 642 parts.empty? 643 644 RDoc::Markup::ListItem.new(constant.name, *parts) 645 } 646 647 out << list 648 out << RDoc::Markup::BlankLine.new 649 end 650 651 add_method_list out, class_methods, 'Class methods' 652 add_method_list out, instance_methods, 'Instance methods' 653 add_method_list out, attributes, 'Attributes' 654 655 add_method_documentation out, klass if @show_all 656 end 657 658 add_also_in out, also_in 659 660 out 661 end 662 663 ## 664 # Hash mapping a known class or module to the stores it can be loaded from 665 666 def classes 667 return @classes if @classes 668 669 @classes = {} 670 671 @stores.each do |store| 672 store.cache[:modules].each do |mod| 673 # using default block causes searched-for modules to be added 674 @classes[mod] ||= [] 675 @classes[mod] << store 676 end 677 end 678 679 @classes 680 end 681 682 ## 683 # Returns the stores wherein +name+ is found along with the classes, 684 # extends and includes that match it 685 686 def classes_and_includes_and_extends_for name 687 klasses = [] 688 extends = [] 689 includes = [] 690 691 found = @stores.map do |store| 692 begin 693 klass = store.load_class name 694 klasses << klass 695 extends << [klass.extends, store] if klass.extends 696 includes << [klass.includes, store] if klass.includes 697 [store, klass] 698 rescue RDoc::Store::MissingFileError 699 end 700 end.compact 701 702 extends.reject! do |modules,| modules.empty? end 703 includes.reject! do |modules,| modules.empty? end 704 705 [found, klasses, includes, extends] 706 end 707 708 ## 709 # Completes +name+ based on the caches. For Readline 710 711 def complete name 712 klasses = classes.keys 713 completions = [] 714 715 klass, selector, method = parse_name name 716 717 # may need to include Foo when given Foo:: 718 klass_name = method ? name : klass 719 720 if name !~ /#|\./ then 721 completions = klasses.grep(/^#{Regexp.escape klass_name}[^:]*$/) 722 completions.concat klasses.grep(/^#{Regexp.escape name}[^:]*$/) if 723 name =~ /::$/ 724 725 completions << klass if classes.key? klass # to complete a method name 726 elsif selector then 727 completions << klass if classes.key? klass 728 elsif classes.key? klass_name then 729 completions << klass_name 730 end 731 732 if completions.include? klass and name =~ /#|\.|::/ then 733 methods = list_methods_matching name 734 735 if not methods.empty? then 736 # remove Foo if given Foo:: and a method was found 737 completions.delete klass 738 elsif selector then 739 # replace Foo with Foo:: as given 740 completions.delete klass 741 completions << "#{klass}#{selector}" 742 end 743 744 completions.concat methods 745 end 746 747 completions.sort.uniq 748 end 749 750 ## 751 # Converts +document+ to text and writes it to the pager 752 753 def display document 754 page do |io| 755 text = document.accept formatter(io) 756 757 io.write text 758 end 759 end 760 761 ## 762 # Outputs formatted RI data for class +name+. Groups undocumented classes 763 764 def display_class name 765 return if name =~ /#|\./ 766 767 found, klasses, includes, extends = 768 classes_and_includes_and_extends_for name 769 770 return if found.empty? 771 772 out = class_document name, found, klasses, includes, extends 773 774 display out 775 end 776 777 ## 778 # Outputs formatted RI data for method +name+ 779 780 def display_method name 781 out = RDoc::Markup::Document.new 782 783 add_method out, name 784 785 display out 786 end 787 788 ## 789 # Outputs formatted RI data for the class or method +name+. 790 # 791 # Returns true if +name+ was found, false if it was not an alternative could 792 # be guessed, raises an error if +name+ couldn't be guessed. 793 794 def display_name name 795 if name =~ /\w:(\w|$)/ then 796 display_page name 797 return true 798 end 799 800 return true if display_class name 801 802 display_method name if name =~ /::|#|\./ 803 804 true 805 rescue NotFoundError 806 matches = list_methods_matching name if name =~ /::|#|\./ 807 matches = classes.keys.grep(/^#{name}/) if matches.empty? 808 809 raise if matches.empty? 810 811 page do |io| 812 io.puts "#{name} not found, maybe you meant:" 813 io.puts 814 io.puts matches.sort.join("\n") 815 end 816 817 false 818 end 819 820 ## 821 # Displays each name in +name+ 822 823 def display_names names 824 names.each do |name| 825 name = expand_name name 826 827 display_name name 828 end 829 end 830 831 ## 832 # Outputs formatted RI data for page +name+. 833 834 def display_page name 835 store_name, page_name = name.split ':', 2 836 837 store = @stores.find { |s| s.source == store_name } 838 839 return display_page_list store if page_name.empty? 840 841 pages = store.cache[:pages] 842 843 unless pages.include? page_name then 844 found_names = pages.select do |n| 845 n =~ /#{Regexp.escape page_name}\.[^.]+$/ 846 end 847 848 if found_names.length.zero? then 849 return display_page_list store, pages 850 elsif found_names.length > 1 then 851 return display_page_list store, found_names, page_name 852 end 853 854 page_name = found_names.first 855 end 856 857 page = store.load_page page_name 858 859 display page.comment 860 end 861 862 ## 863 # Outputs a formatted RI page list for the pages in +store+. 864 865 def display_page_list store, pages = store.cache[:pages], search = nil 866 out = RDoc::Markup::Document.new 867 868 title = if search then 869 "#{search} pages" 870 else 871 'Pages' 872 end 873 874 out << RDoc::Markup::Heading.new(1, "#{title} in #{store.friendly_path}") 875 out << RDoc::Markup::BlankLine.new 876 877 list = RDoc::Markup::List.new(:BULLET) 878 879 pages.each do |page| 880 list << RDoc::Markup::Paragraph.new(page) 881 end 882 883 out << list 884 885 display out 886 end 887 888 ## 889 # Expands abbreviated klass +klass+ into a fully-qualified class. "Zl::Da" 890 # will be expanded to Zlib::DataError. 891 892 def expand_class klass 893 klass.split('::').inject '' do |expanded, klass_part| 894 expanded << '::' unless expanded.empty? 895 short = expanded << klass_part 896 897 subset = classes.keys.select do |klass_name| 898 klass_name =~ /^#{expanded}[^:]*$/ 899 end 900 901 abbrevs = Abbrev.abbrev subset 902 903 expanded = abbrevs[short] 904 905 raise NotFoundError, short unless expanded 906 907 expanded.dup 908 end 909 end 910 911 ## 912 # Expands the class portion of +name+ into a fully-qualified class. See 913 # #expand_class. 914 915 def expand_name name 916 klass, selector, method = parse_name name 917 918 return [selector, method].join if klass.empty? 919 920 case selector 921 when ':' then 922 [find_store(klass), selector, method] 923 else 924 [expand_class(klass), selector, method] 925 end.join 926 end 927 928 ## 929 # Filters the methods in +found+ trying to find a match for +name+. 930 931 def filter_methods found, name 932 regexp = name_regexp name 933 934 filtered = found.find_all do |store, methods| 935 methods.any? { |method| method.full_name =~ regexp } 936 end 937 938 return filtered unless filtered.empty? 939 940 found 941 end 942 943 ## 944 # Yields items matching +name+ including the store they were found in, the 945 # class being searched for, the class they were found in (an ancestor) the 946 # types of methods to look up (from #method_type), and the method name being 947 # searched for 948 949 def find_methods name 950 klass, selector, method = parse_name name 951 952 types = method_type selector 953 954 klasses = nil 955 ambiguous = klass.empty? 956 957 if ambiguous then 958 klasses = classes.keys 959 else 960 klasses = ancestors_of klass 961 klasses.unshift klass 962 end 963 964 methods = [] 965 966 klasses.each do |ancestor| 967 ancestors = classes[ancestor] 968 969 next unless ancestors 970 971 klass = ancestor if ambiguous 972 973 ancestors.each do |store| 974 methods << [store, klass, ancestor, types, method] 975 end 976 end 977 978 methods = methods.sort_by do |_, k, a, _, m| 979 [k, a, m].compact 980 end 981 982 methods.each do |item| 983 yield(*item) # :yields: store, klass, ancestor, types, method 984 end 985 986 self 987 end 988 989 ## 990 # Finds the given +pager+ for jruby. Returns an IO if +pager+ was found. 991 # 992 # Returns false if +pager+ does not exist. 993 # 994 # Returns nil if the jruby JVM doesn't support ProcessBuilder redirection 995 # (1.6 and older). 996 997 def find_pager_jruby pager 998 require 'java' 999 require 'shellwords' 1000 1001 return nil unless java.lang.ProcessBuilder.constants.include? :Redirect 1002 1003 pager = Shellwords.split pager 1004 1005 pb = java.lang.ProcessBuilder.new(*pager) 1006 pb = pb.redirect_output java.lang.ProcessBuilder::Redirect::INHERIT 1007 1008 @jruby_pager_process = pb.start 1009 1010 input = @jruby_pager_process.output_stream 1011 1012 io = input.to_io 1013 io.sync = true 1014 io 1015 rescue java.io.IOException 1016 false 1017 end 1018 1019 ## 1020 # Finds a store that matches +name+ which can be the name of a gem, "ruby", 1021 # "home" or "site". 1022 # 1023 # See also RDoc::Store#source 1024 1025 def find_store name 1026 @stores.each do |store| 1027 source = store.source 1028 1029 return source if source == name 1030 1031 return source if 1032 store.type == :gem and source =~ /^#{Regexp.escape name}-\d/ 1033 end 1034 1035 raise RDoc::RI::Driver::NotFoundError, name 1036 end 1037 1038 ## 1039 # Creates a new RDoc::Markup::Formatter. If a formatter is given with -f, 1040 # use it. If we're outputting to a pager, use bs, otherwise ansi. 1041 1042 def formatter(io) 1043 if @formatter_klass then 1044 @formatter_klass.new 1045 elsif paging? or !io.tty? then 1046 RDoc::Markup::ToBs.new 1047 else 1048 RDoc::Markup::ToAnsi.new 1049 end 1050 end 1051 1052 ## 1053 # Runs ri interactively using Readline if it is available. 1054 1055 def interactive 1056 puts "\nEnter the method name you want to look up." 1057 1058 if defined? Readline then 1059 Readline.completion_proc = method :complete 1060 puts "You can use tab to autocomplete." 1061 end 1062 1063 puts "Enter a blank line to exit.\n\n" 1064 1065 loop do 1066 name = if defined? Readline then 1067 Readline.readline ">> " 1068 else 1069 print ">> " 1070 $stdin.gets 1071 end 1072 1073 return if name.nil? or name.empty? 1074 1075 name = expand_name name.strip 1076 1077 begin 1078 display_name name 1079 rescue NotFoundError => e 1080 puts e.message 1081 end 1082 end 1083 1084 rescue Interrupt 1085 exit 1086 end 1087 1088 ## 1089 # Is +file+ in ENV['PATH']? 1090 1091 def in_path? file 1092 return true if file =~ %r%\A/% and File.exist? file 1093 1094 ENV['PATH'].split(File::PATH_SEPARATOR).any? do |path| 1095 File.exist? File.join(path, file) 1096 end 1097 end 1098 1099 ## 1100 # Lists classes known to ri starting with +names+. If +names+ is empty all 1101 # known classes are shown. 1102 1103 def list_known_classes names = [] 1104 classes = [] 1105 1106 stores.each do |store| 1107 classes << store.module_names 1108 end 1109 1110 classes = classes.flatten.uniq.sort 1111 1112 unless names.empty? then 1113 filter = Regexp.union names.map { |name| /^#{name}/ } 1114 1115 classes = classes.grep filter 1116 end 1117 1118 page do |io| 1119 if paging? or io.tty? then 1120 if names.empty? then 1121 io.puts "Classes and Modules known to ri:" 1122 else 1123 io.puts "Classes and Modules starting with #{names.join ', '}:" 1124 end 1125 io.puts 1126 end 1127 1128 io.puts classes.join("\n") 1129 end 1130 end 1131 1132 ## 1133 # Returns an Array of methods matching +name+ 1134 1135 def list_methods_matching name 1136 found = [] 1137 1138 find_methods name do |store, klass, ancestor, types, method| 1139 if types == :instance or types == :both then 1140 methods = store.instance_methods[ancestor] 1141 1142 if methods then 1143 matches = methods.grep(/^#{Regexp.escape method.to_s}/) 1144 1145 matches = matches.map do |match| 1146 "#{klass}##{match}" 1147 end 1148 1149 found.concat matches 1150 end 1151 end 1152 1153 if types == :class or types == :both then 1154 methods = store.class_methods[ancestor] 1155 1156 next unless methods 1157 matches = methods.grep(/^#{Regexp.escape method.to_s}/) 1158 1159 matches = matches.map do |match| 1160 "#{klass}::#{match}" 1161 end 1162 1163 found.concat matches 1164 end 1165 end 1166 1167 found.uniq 1168 end 1169 1170 ## 1171 # Loads RI data for method +name+ on +klass+ from +store+. +type+ and 1172 # +cache+ indicate if it is a class or instance method. 1173 1174 def load_method store, cache, klass, type, name 1175 methods = store.send(cache)[klass] 1176 1177 return unless methods 1178 1179 method = methods.find do |method_name| 1180 method_name == name 1181 end 1182 1183 return unless method 1184 1185 store.load_method klass, "#{type}#{method}" 1186 end 1187 1188 ## 1189 # Returns an Array of RI data for methods matching +name+ 1190 1191 def load_methods_matching name 1192 found = [] 1193 1194 find_methods name do |store, klass, ancestor, types, method| 1195 methods = [] 1196 1197 methods << load_method(store, :class_methods, ancestor, '::', method) if 1198 [:class, :both].include? types 1199 1200 methods << load_method(store, :instance_methods, ancestor, '#', method) if 1201 [:instance, :both].include? types 1202 1203 found << [store, methods.compact] 1204 end 1205 1206 found.reject do |path, methods| methods.empty? end 1207 end 1208 1209 ## 1210 # Returns a filtered list of methods matching +name+ 1211 1212 def lookup_method name 1213 found = load_methods_matching name 1214 1215 raise NotFoundError, name if found.empty? 1216 1217 filter_methods found, name 1218 end 1219 1220 ## 1221 # Builds a RDoc::Markup::Document from +found+, +klasses+ and +includes+ 1222 1223 def method_document name, filtered 1224 out = RDoc::Markup::Document.new 1225 1226 out << RDoc::Markup::Heading.new(1, name) 1227 out << RDoc::Markup::BlankLine.new 1228 1229 filtered.each do |store, methods| 1230 methods.each do |method| 1231 out << RDoc::Markup::Paragraph.new("(from #{store.friendly_path})") 1232 1233 unless name =~ /^#{Regexp.escape method.parent_name}/ then 1234 out << RDoc::Markup::Heading.new(3, "Implementation from #{method.parent_name}") 1235 end 1236 1237 out << RDoc::Markup::Rule.new(1) 1238 1239 if method.arglists then 1240 arglists = method.arglists.chomp.split "\n" 1241 arglists = arglists.map { |line| line + "\n" } 1242 out << RDoc::Markup::Verbatim.new(*arglists) 1243 out << RDoc::Markup::Rule.new(1) 1244 end 1245 1246 if method.respond_to?(:superclass_method) and method.superclass_method 1247 out << RDoc::Markup::BlankLine.new 1248 out << RDoc::Markup::Heading.new(4, "(Uses superclass method #{method.superclass_method})") 1249 out << RDoc::Markup::Rule.new(1) 1250 end 1251 1252 out << RDoc::Markup::BlankLine.new 1253 out << method.comment 1254 out << RDoc::Markup::BlankLine.new 1255 end 1256 end 1257 1258 out 1259 end 1260 1261 ## 1262 # Returns the type of method (:both, :instance, :class) for +selector+ 1263 1264 def method_type selector 1265 case selector 1266 when '.', nil then :both 1267 when '#' then :instance 1268 else :class 1269 end 1270 end 1271 1272 ## 1273 # Returns a regular expression for +name+ that will match an 1274 # RDoc::AnyMethod's name. 1275 1276 def name_regexp name 1277 klass, type, name = parse_name name 1278 1279 case type 1280 when '#', '::' then 1281 /^#{klass}#{type}#{Regexp.escape name}$/ 1282 else 1283 /^#{klass}(#|::)#{Regexp.escape name}$/ 1284 end 1285 end 1286 1287 ## 1288 # Paginates output through a pager program. 1289 1290 def page 1291 if pager = setup_pager then 1292 begin 1293 yield pager 1294 ensure 1295 pager.close 1296 @jruby_pager_process.wait_for if @jruby_pager_process 1297 end 1298 else 1299 yield $stdout 1300 end 1301 rescue Errno::EPIPE 1302 ensure 1303 @paging = false 1304 end 1305 1306 ## 1307 # Are we using a pager? 1308 1309 def paging? 1310 @paging 1311 end 1312 1313 ## 1314 # Extracts the class, selector and method name parts from +name+ like 1315 # Foo::Bar#baz. 1316 # 1317 # NOTE: Given Foo::Bar, Bar is considered a class even though it may be a 1318 # method 1319 1320 def parse_name name 1321 parts = name.split(/(::?|#|\.)/) 1322 1323 if parts.length == 1 then 1324 if parts.first =~ /^[a-z]|^([%&*+\/<>^`|~-]|\+@|-@|<<|<=>?|===?|=>|=~|>>|\[\]=?|~@)$/ then 1325 type = '.' 1326 meth = parts.pop 1327 else 1328 type = nil 1329 meth = nil 1330 end 1331 elsif parts.length == 2 or parts.last =~ /::|#|\./ then 1332 type = parts.pop 1333 meth = nil 1334 elsif parts[1] == ':' then 1335 klass = parts.shift 1336 type = parts.shift 1337 meth = parts.join 1338 elsif parts[-2] != '::' or parts.last !~ /^[A-Z]/ then 1339 meth = parts.pop 1340 type = parts.pop 1341 end 1342 1343 klass ||= parts.join 1344 1345 [klass, type, meth] 1346 end 1347 1348 ## 1349 # Looks up and displays ri data according to the options given. 1350 1351 def run 1352 if @list_doc_dirs then 1353 puts @doc_dirs 1354 elsif @list then 1355 list_known_classes @names 1356 elsif @server then 1357 start_server 1358 elsif @interactive or @names.empty? then 1359 interactive 1360 else 1361 display_names @names 1362 end 1363 rescue NotFoundError => e 1364 abort e.message 1365 end 1366 1367 ## 1368 # Sets up a pager program to pass output through. Tries the RI_PAGER and 1369 # PAGER environment variables followed by pager, less then more. 1370 1371 def setup_pager 1372 return if @use_stdout 1373 1374 jruby = Object.const_defined?(:RUBY_ENGINE) && RUBY_ENGINE == 'jruby' 1375 1376 pagers = [ENV['RI_PAGER'], ENV['PAGER'], 'pager', 'less', 'more'] 1377 1378 pagers.compact.uniq.each do |pager| 1379 next unless pager 1380 1381 pager_cmd = pager.split.first 1382 1383 next unless in_path? pager_cmd 1384 1385 if jruby then 1386 case io = find_pager_jruby(pager) 1387 when nil then break 1388 when false then next 1389 else io 1390 end 1391 else 1392 io = IO.popen(pager, 'w') rescue next 1393 end 1394 1395 next if $? and $?.pid == io.pid and $?.exited? # pager didn't work 1396 1397 @paging = true 1398 1399 return io 1400 end 1401 1402 @use_stdout = true 1403 1404 nil 1405 end 1406 1407 ## 1408 # Starts a WEBrick server for ri. 1409 1410 def start_server 1411 require 'webrick' 1412 1413 server = WEBrick::HTTPServer.new :Port => @server 1414 1415 server.mount '/', RDoc::Servlet 1416 1417 trap 'INT' do server.shutdown end 1418 trap 'TERM' do server.shutdown end 1419 1420 server.start 1421 end 1422 1423end 1424 1425