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