1require 'fileutils' 2 3## 4# A set of rdoc data for a single project (gem, path, etc.). 5# 6# The store manages reading and writing ri data for a project and maintains a 7# cache of methods, classes and ancestors in the store. 8# 9# The store maintains a #cache of its contents for faster lookup. After 10# adding items to the store it must be flushed using #save_cache. The cache 11# contains the following structures: 12# 13# @cache = { 14# :ancestors => {}, # class name => ancestor names 15# :attributes => {}, # class name => attributes 16# :class_methods => {}, # class name => class methods 17# :instance_methods => {}, # class name => instance methods 18# :modules => [], # classes and modules in this store 19# :pages => [], # page names 20# } 21#-- 22# TODO need to prune classes 23 24class RDoc::Store 25 26 ## 27 # Errors raised from loading or saving the store 28 29 class Error < RDoc::Error 30 end 31 32 ## 33 # Raised when a stored file for a class, module, page or method is missing. 34 35 class MissingFileError < Error 36 37 ## 38 # The store the file should exist in 39 40 attr_reader :store 41 42 ## 43 # The file the #name should be saved as 44 45 attr_reader :file 46 47 ## 48 # The name of the object the #file would be loaded from 49 50 attr_reader :name 51 52 ## 53 # Creates a new MissingFileError for the missing +file+ for the given 54 # +name+ that should have been in the +store+. 55 56 def initialize store, file, name 57 @store = store 58 @file = file 59 @name = name 60 end 61 62 def message # :nodoc: 63 "store at #{@store.path} missing file #{@file} for #{@name}" 64 end 65 66 end 67 68 ## 69 # Stores the name of the C variable a class belongs to. This helps wire up 70 # classes defined from C across files. 71 72 attr_reader :c_enclosure_classes # :nodoc: 73 74 attr_reader :c_enclosure_names # :nodoc: 75 76 ## 77 # Maps C variables to class or module names for each parsed C file. 78 79 attr_reader :c_class_variables 80 81 ## 82 # Maps C variables to singleton class names for each parsed C file. 83 84 attr_reader :c_singleton_class_variables 85 86 ## 87 # If true this Store will not write any files 88 89 attr_accessor :dry_run 90 91 ## 92 # Path this store reads or writes 93 94 attr_accessor :path 95 96 ## 97 # The RDoc::RDoc driver for this parse tree. This allows classes consulting 98 # the documentation tree to access user-set options, for example. 99 100 attr_accessor :rdoc 101 102 ## 103 # Type of ri datastore this was loaded from. See RDoc::RI::Driver, 104 # RDoc::RI::Paths. 105 106 attr_accessor :type 107 108 ## 109 # The contents of the Store 110 111 attr_reader :cache 112 113 ## 114 # The encoding of the contents in the Store 115 116 attr_accessor :encoding 117 118 ## 119 # Creates a new Store of +type+ that will load or save to +path+ 120 121 def initialize path = nil, type = nil 122 @dry_run = false 123 @encoding = nil 124 @path = path 125 @rdoc = nil 126 @type = type 127 128 @cache = { 129 :ancestors => {}, 130 :attributes => {}, 131 :class_methods => {}, 132 :c_class_variables => {}, 133 :c_singleton_class_variables => {}, 134 :encoding => @encoding, 135 :instance_methods => {}, 136 :main => nil, 137 :modules => [], 138 :pages => [], 139 :title => nil, 140 } 141 142 @classes_hash = {} 143 @modules_hash = {} 144 @files_hash = {} 145 146 @c_enclosure_classes = {} 147 @c_enclosure_names = {} 148 149 @c_class_variables = {} 150 @c_singleton_class_variables = {} 151 152 @unique_classes = nil 153 @unique_modules = nil 154 end 155 156 ## 157 # Adds +module+ as an enclosure (namespace) for the given +variable+ for C 158 # files. 159 160 def add_c_enclosure variable, namespace 161 @c_enclosure_classes[variable] = namespace 162 end 163 164 ## 165 # Adds C variables from an RDoc::Parser::C 166 167 def add_c_variables c_parser 168 filename = c_parser.top_level.relative_name 169 170 @c_class_variables[filename] = make_variable_map c_parser.classes 171 172 @c_singleton_class_variables[filename] = c_parser.singleton_classes 173 end 174 175 ## 176 # Adds the file with +name+ as an RDoc::TopLevel to the store. Returns the 177 # created RDoc::TopLevel. 178 179 def add_file absolute_name, relative_name = absolute_name 180 unless top_level = @files_hash[relative_name] then 181 top_level = RDoc::TopLevel.new absolute_name, relative_name 182 top_level.store = self 183 @files_hash[relative_name] = top_level 184 end 185 186 top_level 187 end 188 189 ## 190 # Returns all classes discovered by RDoc 191 192 def all_classes 193 @classes_hash.values 194 end 195 196 ## 197 # Returns all classes and modules discovered by RDoc 198 199 def all_classes_and_modules 200 @classes_hash.values + @modules_hash.values 201 end 202 203 ## 204 # All TopLevels known to RDoc 205 206 def all_files 207 @files_hash.values 208 end 209 210 ## 211 # Returns all modules discovered by RDoc 212 213 def all_modules 214 modules_hash.values 215 end 216 217 ## 218 # Ancestors cache accessor. Maps a klass name to an Array of its ancestors 219 # in this store. If Foo in this store inherits from Object, Kernel won't be 220 # listed (it will be included from ruby's ri store). 221 222 def ancestors 223 @cache[:ancestors] 224 end 225 226 ## 227 # Attributes cache accessor. Maps a class to an Array of its attributes. 228 229 def attributes 230 @cache[:attributes] 231 end 232 233 ## 234 # Path to the cache file 235 236 def cache_path 237 File.join @path, 'cache.ri' 238 end 239 240 ## 241 # Path to the ri data for +klass_name+ 242 243 def class_file klass_name 244 name = klass_name.split('::').last 245 File.join class_path(klass_name), "cdesc-#{name}.ri" 246 end 247 248 ## 249 # Class methods cache accessor. Maps a class to an Array of its class 250 # methods (not full name). 251 252 def class_methods 253 @cache[:class_methods] 254 end 255 256 ## 257 # Path where data for +klass_name+ will be stored (methods or class data) 258 259 def class_path klass_name 260 File.join @path, *klass_name.split('::') 261 end 262 263 ## 264 # Hash of all classes known to RDoc 265 266 def classes_hash 267 @classes_hash 268 end 269 270 ## 271 # Removes empty items and ensures item in each collection are unique and 272 # sorted 273 274 def clean_cache_collection collection # :nodoc: 275 collection.each do |name, item| 276 if item.empty? then 277 collection.delete name 278 else 279 # HACK mongrel-1.1.5 documents its files twice 280 item.uniq! 281 item.sort! 282 end 283 end 284 end 285 286 ## 287 # Prepares the RDoc code object tree for use by a generator. 288 # 289 # It finds unique classes/modules defined, and replaces classes/modules that 290 # are aliases for another one by a copy with RDoc::ClassModule#is_alias_for 291 # set. 292 # 293 # It updates the RDoc::ClassModule#constant_aliases attribute of "real" 294 # classes or modules. 295 # 296 # It also completely removes the classes and modules that should be removed 297 # from the documentation and the methods that have a visibility below 298 # +min_visibility+, which is the <tt>--visibility</tt> option. 299 # 300 # See also RDoc::Context#remove_from_documentation? 301 302 def complete min_visibility 303 fix_basic_object_inheritance 304 305 # cache included modules before they are removed from the documentation 306 all_classes_and_modules.each { |cm| cm.ancestors } 307 308 remove_nodoc @classes_hash 309 remove_nodoc @modules_hash 310 311 @unique_classes = find_unique @classes_hash 312 @unique_modules = find_unique @modules_hash 313 314 unique_classes_and_modules.each do |cm| 315 cm.complete min_visibility 316 end 317 318 @files_hash.each_key do |file_name| 319 tl = @files_hash[file_name] 320 321 unless tl.text? then 322 tl.modules_hash.clear 323 tl.classes_hash.clear 324 325 tl.classes_or_modules.each do |cm| 326 name = cm.full_name 327 if cm.type == 'class' then 328 tl.classes_hash[name] = cm if @classes_hash[name] 329 else 330 tl.modules_hash[name] = cm if @modules_hash[name] 331 end 332 end 333 end 334 end 335 end 336 337 ## 338 # Hash of all files known to RDoc 339 340 def files_hash 341 @files_hash 342 end 343 344 ## 345 # Finds the enclosure (namespace) for the given C +variable+. 346 347 def find_c_enclosure variable 348 @c_enclosure_classes.fetch variable do 349 break unless name = @c_enclosure_names[variable] 350 351 mod = find_class_or_module name 352 353 unless mod then 354 loaded_mod = load_class_data name 355 356 file = loaded_mod.in_files.first 357 358 return unless file # legacy data source 359 360 file.store = self 361 362 mod = file.add_module RDoc::NormalModule, name 363 end 364 365 @c_enclosure_classes[variable] = mod 366 end 367 end 368 369 ## 370 # Finds the class with +name+ in all discovered classes 371 372 def find_class_named name 373 @classes_hash[name] 374 end 375 376 ## 377 # Finds the class with +name+ starting in namespace +from+ 378 379 def find_class_named_from name, from 380 from = find_class_named from unless RDoc::Context === from 381 382 until RDoc::TopLevel === from do 383 return nil unless from 384 385 klass = from.find_class_named name 386 return klass if klass 387 388 from = from.parent 389 end 390 391 find_class_named name 392 end 393 394 ## 395 # Finds the class or module with +name+ 396 397 def find_class_or_module name 398 name = $' if name =~ /^::/ 399 @classes_hash[name] || @modules_hash[name] 400 end 401 402 ## 403 # Finds the file with +name+ in all discovered files 404 405 def find_file_named name 406 @files_hash[name] 407 end 408 409 ## 410 # Finds the module with +name+ in all discovered modules 411 412 def find_module_named name 413 @modules_hash[name] 414 end 415 416 ## 417 # Returns the RDoc::TopLevel that is a text file and has the given 418 # +file_name+ 419 420 def find_text_page file_name 421 @files_hash.each_value.find do |file| 422 file.text? and file.full_name == file_name 423 end 424 end 425 426 ## 427 # Finds unique classes/modules defined in +all_hash+, 428 # and returns them as an array. Performs the alias 429 # updates in +all_hash+: see ::complete. 430 #-- 431 # TODO aliases should be registered by Context#add_module_alias 432 433 def find_unique all_hash 434 unique = [] 435 436 all_hash.each_pair do |full_name, cm| 437 unique << cm if full_name == cm.full_name 438 end 439 440 unique 441 end 442 443 ## 444 # Fixes the erroneous <tt>BasicObject < Object</tt> in 1.9. 445 # 446 # Because we assumed all classes without a stated superclass 447 # inherit from Object, we have the above wrong inheritance. 448 # 449 # We fix BasicObject right away if we are running in a Ruby 450 # version >= 1.9. If not, we may be documenting 1.9 source 451 # while running under 1.8: we search the files of BasicObject 452 # for "object.c", and fix the inheritance if we find it. 453 454 def fix_basic_object_inheritance 455 basic = classes_hash['BasicObject'] 456 return unless basic 457 if RUBY_VERSION >= '1.9' 458 basic.superclass = nil 459 elsif basic.in_files.any? { |f| File.basename(f.full_name) == 'object.c' } 460 basic.superclass = nil 461 end 462 end 463 464 ## 465 # Friendly rendition of #path 466 467 def friendly_path 468 case type 469 when :gem then 470 parent = File.expand_path '..', @path 471 "gem #{File.basename parent}" 472 when :home then '~/.rdoc' 473 when :site then 'ruby site' 474 when :system then 'ruby core' 475 else @path 476 end 477 end 478 479 def inspect # :nodoc: 480 "#<%s:0x%x %s %p>" % [self.class, object_id, @path, module_names.sort] 481 end 482 483 ## 484 # Instance methods cache accessor. Maps a class to an Array of its 485 # instance methods (not full name). 486 487 def instance_methods 488 @cache[:instance_methods] 489 end 490 491 ## 492 # Loads all items from this store into memory. This recreates a 493 # documentation tree for use by a generator 494 495 def load_all 496 load_cache 497 498 module_names.each do |module_name| 499 mod = find_class_or_module(module_name) || load_class(module_name) 500 501 # load method documentation since the loaded class/module does not have 502 # it 503 loaded_methods = mod.method_list.map do |method| 504 load_method module_name, method.full_name 505 end 506 507 mod.method_list.replace loaded_methods 508 509 loaded_attributes = mod.attributes.map do |attribute| 510 load_method module_name, attribute.full_name 511 end 512 513 mod.attributes.replace loaded_attributes 514 end 515 516 all_classes_and_modules.each do |mod| 517 descendent_re = /^#{mod.full_name}::[^:]+$/ 518 519 module_names.each do |name| 520 next unless name =~ descendent_re 521 522 descendent = find_class_or_module name 523 524 case descendent 525 when RDoc::NormalClass then 526 mod.classes_hash[name] = descendent 527 when RDoc::NormalModule then 528 mod.modules_hash[name] = descendent 529 end 530 end 531 end 532 533 @cache[:pages].each do |page_name| 534 page = load_page page_name 535 @files_hash[page_name] = page 536 end 537 end 538 539 ## 540 # Loads cache file for this store 541 542 def load_cache 543 #orig_enc = @encoding 544 545 open cache_path, 'rb' do |io| 546 @cache = Marshal.load io.read 547 end 548 549 load_enc = @cache[:encoding] 550 551 # TODO this feature will be time-consuming to add: 552 # a) Encodings may be incompatible but transcodeable 553 # b) Need to warn in the appropriate spots, wherever they may be 554 # c) Need to handle cross-cache differences in encodings 555 # d) Need to warn when generating into a cache with different encodings 556 # 557 #if orig_enc and load_enc != orig_enc then 558 # warn "Cached encoding #{load_enc} is incompatible with #{orig_enc}\n" \ 559 # "from #{path}/cache.ri" unless 560 # Encoding.compatible? orig_enc, load_enc 561 #end 562 563 @encoding = load_enc unless @encoding 564 565 @cache[:pages] ||= [] 566 @cache[:main] ||= nil 567 @cache[:c_class_variables] ||= {} 568 @cache[:c_singleton_class_variables] ||= {} 569 570 @cache[:c_class_variables].each do |_, map| 571 map.each do |variable, name| 572 @c_enclosure_names[variable] = name 573 end 574 end 575 576 @cache 577 rescue Errno::ENOENT 578 end 579 580 ## 581 # Loads ri data for +klass_name+ and hooks it up to this store. 582 583 def load_class klass_name 584 obj = load_class_data klass_name 585 586 obj.store = self 587 588 case obj 589 when RDoc::NormalClass then 590 @classes_hash[klass_name] = obj 591 when RDoc::NormalModule then 592 @modules_hash[klass_name] = obj 593 end 594 end 595 596 ## 597 # Loads ri data for +klass_name+ 598 599 def load_class_data klass_name 600 file = class_file klass_name 601 602 open file, 'rb' do |io| 603 Marshal.load io.read 604 end 605 rescue Errno::ENOENT => e 606 error = MissingFileError.new(self, file, klass_name) 607 error.set_backtrace e.backtrace 608 raise error 609 end 610 611 ## 612 # Loads ri data for +method_name+ in +klass_name+ 613 614 def load_method klass_name, method_name 615 file = method_file klass_name, method_name 616 617 open file, 'rb' do |io| 618 obj = Marshal.load io.read 619 obj.store = self 620 obj.parent = 621 find_class_or_module(klass_name) || load_class(klass_name) unless 622 obj.parent 623 obj 624 end 625 rescue Errno::ENOENT => e 626 error = MissingFileError.new(self, file, klass_name + method_name) 627 error.set_backtrace e.backtrace 628 raise error 629 end 630 631 ## 632 # Loads ri data for +page_name+ 633 634 def load_page page_name 635 file = page_file page_name 636 637 open file, 'rb' do |io| 638 obj = Marshal.load io.read 639 obj.store = self 640 obj 641 end 642 rescue Errno::ENOENT => e 643 error = MissingFileError.new(self, file, page_name) 644 error.set_backtrace e.backtrace 645 raise error 646 end 647 648 ## 649 # Gets the main page for this RDoc store. This page is used as the root of 650 # the RDoc server. 651 652 def main 653 @cache[:main] 654 end 655 656 ## 657 # Sets the main page for this RDoc store. 658 659 def main= page 660 @cache[:main] = page 661 end 662 663 ## 664 # Converts the variable => ClassModule map +variables+ from a C parser into 665 # a variable => class name map. 666 667 def make_variable_map variables 668 map = {} 669 670 variables.each { |variable, class_module| 671 map[variable] = class_module.full_name 672 } 673 674 map 675 end 676 677 ## 678 # Path to the ri data for +method_name+ in +klass_name+ 679 680 def method_file klass_name, method_name 681 method_name = method_name.split('::').last 682 method_name =~ /#(.*)/ 683 method_type = $1 ? 'i' : 'c' 684 method_name = $1 if $1 685 686 method_name = if ''.respond_to? :ord then 687 method_name.gsub(/\W/) { "%%%02x" % $&[0].ord } 688 else 689 method_name.gsub(/\W/) { "%%%02x" % $&[0] } 690 end 691 692 File.join class_path(klass_name), "#{method_name}-#{method_type}.ri" 693 end 694 695 ## 696 # Modules cache accessor. An Array of all the module (and class) names in 697 # the store. 698 699 def module_names 700 @cache[:modules] 701 end 702 703 ## 704 # Hash of all modules known to RDoc 705 706 def modules_hash 707 @modules_hash 708 end 709 710 ## 711 # Returns the RDoc::TopLevel that is a text file and has the given +name+ 712 713 def page name 714 @files_hash.each_value.find do |file| 715 file.text? and file.page_name == name 716 end 717 end 718 719 ## 720 # Path to the ri data for +page_name+ 721 722 def page_file page_name 723 file_name = File.basename(page_name).gsub('.', '_') 724 725 File.join @path, File.dirname(page_name), "page-#{file_name}.ri" 726 end 727 728 ## 729 # Removes from +all_hash+ the contexts that are nodoc or have no content. 730 # 731 # See RDoc::Context#remove_from_documentation? 732 733 def remove_nodoc all_hash 734 all_hash.keys.each do |name| 735 context = all_hash[name] 736 all_hash.delete(name) if context.remove_from_documentation? 737 end 738 end 739 740 ## 741 # Saves all entries in the store 742 743 def save 744 load_cache 745 746 all_classes_and_modules.each do |klass| 747 save_class klass 748 749 klass.each_method do |method| 750 save_method klass, method 751 end 752 753 klass.each_attribute do |attribute| 754 save_method klass, attribute 755 end 756 end 757 758 all_files.each do |file| 759 save_page file 760 end 761 762 save_cache 763 end 764 765 ## 766 # Writes the cache file for this store 767 768 def save_cache 769 clean_cache_collection @cache[:ancestors] 770 clean_cache_collection @cache[:attributes] 771 clean_cache_collection @cache[:class_methods] 772 clean_cache_collection @cache[:instance_methods] 773 774 @cache[:modules].uniq! 775 @cache[:modules].sort! 776 777 @cache[:pages].uniq! 778 @cache[:pages].sort! 779 780 @cache[:encoding] = @encoding # this gets set twice due to assert_cache 781 782 @cache[:c_class_variables].merge! @c_class_variables 783 @cache[:c_singleton_class_variables].merge! @c_singleton_class_variables 784 785 return if @dry_run 786 787 marshal = Marshal.dump @cache 788 789 open cache_path, 'wb' do |io| 790 io.write marshal 791 end 792 end 793 794 ## 795 # Writes the ri data for +klass+ (or module) 796 797 def save_class klass 798 full_name = klass.full_name 799 800 FileUtils.mkdir_p class_path(full_name) unless @dry_run 801 802 @cache[:modules] << full_name 803 804 path = class_file full_name 805 806 begin 807 disk_klass = load_class full_name 808 809 klass = disk_klass.merge klass 810 rescue MissingFileError 811 end 812 813 # BasicObject has no ancestors 814 ancestors = klass.direct_ancestors.compact.map do |ancestor| 815 # HACK for classes we don't know about (class X < RuntimeError) 816 String === ancestor ? ancestor : ancestor.full_name 817 end 818 819 @cache[:ancestors][full_name] ||= [] 820 @cache[:ancestors][full_name].concat ancestors 821 822 attribute_definitions = klass.attributes.map do |attribute| 823 "#{attribute.definition} #{attribute.name}" 824 end 825 826 unless attribute_definitions.empty? then 827 @cache[:attributes][full_name] ||= [] 828 @cache[:attributes][full_name].concat attribute_definitions 829 end 830 831 to_delete = [] 832 833 unless klass.method_list.empty? then 834 @cache[:class_methods][full_name] ||= [] 835 @cache[:instance_methods][full_name] ||= [] 836 837 class_methods, instance_methods = 838 klass.method_list.partition { |meth| meth.singleton } 839 840 class_methods = class_methods. map { |method| method.name } 841 instance_methods = instance_methods.map { |method| method.name } 842 attribute_names = klass.attributes.map { |attr| attr.name } 843 844 old = @cache[:class_methods][full_name] - class_methods 845 to_delete.concat old.map { |method| 846 method_file full_name, "#{full_name}::#{method}" 847 } 848 849 old = @cache[:instance_methods][full_name] - 850 instance_methods - attribute_names 851 to_delete.concat old.map { |method| 852 method_file full_name, "#{full_name}##{method}" 853 } 854 855 @cache[:class_methods][full_name] = class_methods 856 @cache[:instance_methods][full_name] = instance_methods 857 end 858 859 return if @dry_run 860 861 FileUtils.rm_f to_delete 862 863 marshal = Marshal.dump klass 864 865 open path, 'wb' do |io| 866 io.write marshal 867 end 868 end 869 870 ## 871 # Writes the ri data for +method+ on +klass+ 872 873 def save_method klass, method 874 full_name = klass.full_name 875 876 FileUtils.mkdir_p class_path(full_name) unless @dry_run 877 878 cache = if method.singleton then 879 @cache[:class_methods] 880 else 881 @cache[:instance_methods] 882 end 883 cache[full_name] ||= [] 884 cache[full_name] << method.name 885 886 return if @dry_run 887 888 marshal = Marshal.dump method 889 890 open method_file(full_name, method.full_name), 'wb' do |io| 891 io.write marshal 892 end 893 end 894 895 ## 896 # Writes the ri data for +page+ 897 898 def save_page page 899 return unless page.text? 900 901 path = page_file page.full_name 902 903 FileUtils.mkdir_p File.dirname(path) unless @dry_run 904 905 cache[:pages] ||= [] 906 cache[:pages] << page.full_name 907 908 return if @dry_run 909 910 marshal = Marshal.dump page 911 912 open path, 'wb' do |io| 913 io.write marshal 914 end 915 end 916 917 ## 918 # Source of the contents of this store. 919 # 920 # For a store from a gem the source is the gem name. For a store from the 921 # home directory the source is "home". For system ri store (the standard 922 # library documentation) the source is"ruby". For a store from the site 923 # ri directory the store is "site". For other stores the source is the 924 # #path. 925 926 def source 927 case type 928 when :gem then File.basename File.expand_path '..', @path 929 when :home then 'home' 930 when :site then 'site' 931 when :system then 'ruby' 932 else @path 933 end 934 end 935 936 ## 937 # Gets the title for this RDoc store. This is used as the title in each 938 # page on the RDoc server 939 940 def title 941 @cache[:title] 942 end 943 944 ## 945 # Sets the title page for this RDoc store. 946 947 def title= title 948 @cache[:title] = title 949 end 950 951 ## 952 # Returns the unique classes discovered by RDoc. 953 # 954 # ::complete must have been called prior to using this method. 955 956 def unique_classes 957 @unique_classes 958 end 959 960 ## 961 # Returns the unique classes and modules discovered by RDoc. 962 # ::complete must have been called prior to using this method. 963 964 def unique_classes_and_modules 965 @unique_classes + @unique_modules 966 end 967 968 ## 969 # Returns the unique modules discovered by RDoc. 970 # ::complete must have been called prior to using this method. 971 972 def unique_modules 973 @unique_modules 974 end 975 976end 977 978