1# frozen_string_literal: true 2require 'cgi' 3 4## 5# A Context is something that can hold modules, classes, methods, attributes, 6# aliases, requires, and includes. Classes, modules, and files are all 7# Contexts. 8 9class RDoc::Context < RDoc::CodeObject 10 11 include Comparable 12 13 ## 14 # Types of methods 15 16 TYPES = %w[class instance] 17 18 ## 19 # If a context has these titles it will be sorted in this order. 20 21 TOMDOC_TITLES = [nil, 'Public', 'Internal', 'Deprecated'] # :nodoc: 22 TOMDOC_TITLES_SORT = TOMDOC_TITLES.sort_by { |title| title.to_s } # :nodoc: 23 24 ## 25 # Class/module aliases 26 27 attr_reader :aliases 28 29 ## 30 # All attr* methods 31 32 attr_reader :attributes 33 34 ## 35 # Block params to be used in the next MethodAttr parsed under this context 36 37 attr_accessor :block_params 38 39 ## 40 # Constants defined 41 42 attr_reader :constants 43 44 ## 45 # Sets the current documentation section of documentation 46 47 attr_writer :current_section 48 49 ## 50 # Files this context is found in 51 52 attr_reader :in_files 53 54 ## 55 # Modules this context includes 56 57 attr_reader :includes 58 59 ## 60 # Modules this context is extended with 61 62 attr_reader :extends 63 64 ## 65 # Methods defined in this context 66 67 attr_reader :method_list 68 69 ## 70 # Name of this class excluding namespace. See also full_name 71 72 attr_reader :name 73 74 ## 75 # Files this context requires 76 77 attr_reader :requires 78 79 ## 80 # Use this section for the next method, attribute or constant added. 81 82 attr_accessor :temporary_section 83 84 ## 85 # Hash <tt>old_name => [aliases]</tt>, for aliases 86 # that haven't (yet) been resolved to a method/attribute. 87 # (Not to be confused with the aliases of the context.) 88 89 attr_accessor :unmatched_alias_lists 90 91 ## 92 # Aliases that could not be resolved. 93 94 attr_reader :external_aliases 95 96 ## 97 # Current visibility of this context 98 99 attr_accessor :visibility 100 101 ## 102 # Current visibility of this line 103 104 attr_writer :current_line_visibility 105 106 ## 107 # Hash of registered methods. Attributes are also registered here, 108 # twice if they are RW. 109 110 attr_reader :methods_hash 111 112 ## 113 # Params to be used in the next MethodAttr parsed under this context 114 115 attr_accessor :params 116 117 ## 118 # Hash of registered constants. 119 120 attr_reader :constants_hash 121 122 ## 123 # Creates an unnamed empty context with public current visibility 124 125 def initialize 126 super 127 128 @in_files = [] 129 130 @name ||= "unknown" 131 @parent = nil 132 @visibility = :public 133 134 @current_section = Section.new self, nil, nil 135 @sections = { nil => @current_section } 136 @temporary_section = nil 137 138 @classes = {} 139 @modules = {} 140 141 initialize_methods_etc 142 end 143 144 ## 145 # Sets the defaults for methods and so-forth 146 147 def initialize_methods_etc 148 @method_list = [] 149 @attributes = [] 150 @aliases = [] 151 @requires = [] 152 @includes = [] 153 @extends = [] 154 @constants = [] 155 @external_aliases = [] 156 @current_line_visibility = nil 157 158 # This Hash maps a method name to a list of unmatched aliases (aliases of 159 # a method not yet encountered). 160 @unmatched_alias_lists = {} 161 162 @methods_hash = {} 163 @constants_hash = {} 164 165 @params = nil 166 167 @store ||= nil 168 end 169 170 ## 171 # Contexts are sorted by full_name 172 173 def <=>(other) 174 return nil unless RDoc::CodeObject === other 175 176 full_name <=> other.full_name 177 end 178 179 ## 180 # Adds an item of type +klass+ with the given +name+ and +comment+ to the 181 # context. 182 # 183 # Currently only RDoc::Extend and RDoc::Include are supported. 184 185 def add klass, name, comment 186 if RDoc::Extend == klass then 187 ext = RDoc::Extend.new name, comment 188 add_extend ext 189 elsif RDoc::Include == klass then 190 incl = RDoc::Include.new name, comment 191 add_include incl 192 else 193 raise NotImplementedError, "adding a #{klass} is not implemented" 194 end 195 end 196 197 ## 198 # Adds +an_alias+ that is automatically resolved 199 200 def add_alias an_alias 201 return an_alias unless @document_self 202 203 method_attr = find_method(an_alias.old_name, an_alias.singleton) || 204 find_attribute(an_alias.old_name, an_alias.singleton) 205 206 if method_attr then 207 method_attr.add_alias an_alias, self 208 else 209 add_to @external_aliases, an_alias 210 unmatched_alias_list = 211 @unmatched_alias_lists[an_alias.pretty_old_name] ||= [] 212 unmatched_alias_list.push an_alias 213 end 214 215 an_alias 216 end 217 218 ## 219 # Adds +attribute+ if not already there. If it is (as method(s) or attribute), 220 # updates the comment if it was empty. 221 # 222 # The attribute is registered only if it defines a new method. 223 # For instance, <tt>attr_reader :foo</tt> will not be registered 224 # if method +foo+ exists, but <tt>attr_accessor :foo</tt> will be registered 225 # if method +foo+ exists, but <tt>foo=</tt> does not. 226 227 def add_attribute attribute 228 return attribute unless @document_self 229 230 # mainly to check for redefinition of an attribute as a method 231 # TODO find a policy for 'attr_reader :foo' + 'def foo=()' 232 register = false 233 234 key = nil 235 236 if attribute.rw.index 'R' then 237 key = attribute.pretty_name 238 known = @methods_hash[key] 239 240 if known then 241 known.comment = attribute.comment if known.comment.empty? 242 elsif registered = @methods_hash[attribute.pretty_name + '='] and 243 RDoc::Attr === registered then 244 registered.rw = 'RW' 245 else 246 @methods_hash[key] = attribute 247 register = true 248 end 249 end 250 251 if attribute.rw.index 'W' then 252 key = attribute.pretty_name + '=' 253 known = @methods_hash[key] 254 255 if known then 256 known.comment = attribute.comment if known.comment.empty? 257 elsif registered = @methods_hash[attribute.pretty_name] and 258 RDoc::Attr === registered then 259 registered.rw = 'RW' 260 else 261 @methods_hash[key] = attribute 262 register = true 263 end 264 end 265 266 if register then 267 attribute.visibility = @visibility 268 add_to @attributes, attribute 269 resolve_aliases attribute 270 end 271 272 attribute 273 end 274 275 ## 276 # Adds a class named +given_name+ with +superclass+. 277 # 278 # Both +given_name+ and +superclass+ may contain '::', and are 279 # interpreted relative to the +self+ context. This allows handling correctly 280 # examples like these: 281 # class RDoc::Gauntlet < Gauntlet 282 # module Mod 283 # class Object # implies < ::Object 284 # class SubObject < Object # this is _not_ ::Object 285 # 286 # Given <tt>class Container::Item</tt> RDoc assumes +Container+ is a module 287 # unless it later sees <tt>class Container</tt>. +add_class+ automatically 288 # upgrades +given_name+ to a class in this case. 289 290 def add_class class_type, given_name, superclass = '::Object' 291 # superclass +nil+ is passed by the C parser in the following cases: 292 # - registering Object in 1.8 (correct) 293 # - registering BasicObject in 1.9 (correct) 294 # - registering RubyVM in 1.9 in iseq.c (incorrect: < Object in vm.c) 295 # 296 # If we later find a superclass for a registered class with a nil 297 # superclass, we must honor it. 298 299 # find the name & enclosing context 300 if given_name =~ /^:+(\w+)$/ then 301 full_name = $1 302 enclosing = top_level 303 name = full_name.split(/:+/).last 304 else 305 full_name = child_name given_name 306 307 if full_name =~ /^(.+)::(\w+)$/ then 308 name = $2 309 ename = $1 310 enclosing = @store.classes_hash[ename] || @store.modules_hash[ename] 311 # HACK: crashes in actionpack/lib/action_view/helpers/form_helper.rb (metaprogramming) 312 unless enclosing then 313 # try the given name at top level (will work for the above example) 314 enclosing = @store.classes_hash[given_name] || 315 @store.modules_hash[given_name] 316 return enclosing if enclosing 317 # not found: create the parent(s) 318 names = ename.split('::') 319 enclosing = self 320 names.each do |n| 321 enclosing = enclosing.classes_hash[n] || 322 enclosing.modules_hash[n] || 323 enclosing.add_module(RDoc::NormalModule, n) 324 end 325 end 326 else 327 name = full_name 328 enclosing = self 329 end 330 end 331 332 # fix up superclass 333 if full_name == 'BasicObject' then 334 superclass = nil 335 elsif full_name == 'Object' then 336 superclass = '::BasicObject' 337 end 338 339 # find the superclass full name 340 if superclass then 341 if superclass =~ /^:+/ then 342 superclass = $' #' 343 else 344 if superclass =~ /^(\w+):+(.+)$/ then 345 suffix = $2 346 mod = find_module_named($1) 347 superclass = mod.full_name + '::' + suffix if mod 348 else 349 mod = find_module_named(superclass) 350 superclass = mod.full_name if mod 351 end 352 end 353 354 # did we believe it was a module? 355 mod = @store.modules_hash.delete superclass 356 357 upgrade_to_class mod, RDoc::NormalClass, mod.parent if mod 358 359 # e.g., Object < Object 360 superclass = nil if superclass == full_name 361 end 362 363 klass = @store.classes_hash[full_name] 364 365 if klass then 366 # if TopLevel, it may not be registered in the classes: 367 enclosing.classes_hash[name] = klass 368 369 # update the superclass if needed 370 if superclass then 371 existing = klass.superclass 372 existing = existing.full_name unless existing.is_a?(String) if existing 373 if existing.nil? || 374 (existing == 'Object' && superclass != 'Object') then 375 klass.superclass = superclass 376 end 377 end 378 else 379 # this is a new class 380 mod = @store.modules_hash.delete full_name 381 382 if mod then 383 klass = upgrade_to_class mod, RDoc::NormalClass, enclosing 384 385 klass.superclass = superclass unless superclass.nil? 386 else 387 klass = class_type.new name, superclass 388 389 enclosing.add_class_or_module(klass, enclosing.classes_hash, 390 @store.classes_hash) 391 end 392 end 393 394 klass.parent = self 395 396 klass 397 end 398 399 ## 400 # Adds the class or module +mod+ to the modules or 401 # classes Hash +self_hash+, and to +all_hash+ (either 402 # <tt>TopLevel::modules_hash</tt> or <tt>TopLevel::classes_hash</tt>), 403 # unless #done_documenting is +true+. Sets the #parent of +mod+ 404 # to +self+, and its #section to #current_section. Returns +mod+. 405 406 def add_class_or_module mod, self_hash, all_hash 407 mod.section = current_section # TODO declaring context? something is 408 # wrong here... 409 mod.parent = self 410 mod.full_name = nil 411 mod.store = @store 412 413 unless @done_documenting then 414 self_hash[mod.name] = mod 415 # this must be done AFTER adding mod to its parent, so that the full 416 # name is correct: 417 all_hash[mod.full_name] = mod 418 if @store.unmatched_constant_alias[mod.full_name] then 419 to, file = @store.unmatched_constant_alias[mod.full_name] 420 add_module_alias mod, mod.name, to, file 421 end 422 end 423 424 mod 425 end 426 427 ## 428 # Adds +constant+ if not already there. If it is, updates the comment, 429 # value and/or is_alias_for of the known constant if they were empty/nil. 430 431 def add_constant constant 432 return constant unless @document_self 433 434 # HACK: avoid duplicate 'PI' & 'E' in math.c (1.8.7 source code) 435 # (this is a #ifdef: should be handled by the C parser) 436 known = @constants_hash[constant.name] 437 438 if known then 439 known.comment = constant.comment if known.comment.empty? 440 441 known.value = constant.value if 442 known.value.nil? or known.value.strip.empty? 443 444 known.is_alias_for ||= constant.is_alias_for 445 else 446 @constants_hash[constant.name] = constant 447 add_to @constants, constant 448 end 449 450 constant 451 end 452 453 ## 454 # Adds included module +include+ which should be an RDoc::Include 455 456 def add_include include 457 add_to @includes, include 458 459 include 460 end 461 462 ## 463 # Adds extension module +ext+ which should be an RDoc::Extend 464 465 def add_extend ext 466 add_to @extends, ext 467 468 ext 469 end 470 471 ## 472 # Adds +method+ if not already there. If it is (as method or attribute), 473 # updates the comment if it was empty. 474 475 def add_method method 476 return method unless @document_self 477 478 # HACK: avoid duplicate 'new' in io.c & struct.c (1.8.7 source code) 479 key = method.pretty_name 480 known = @methods_hash[key] 481 482 if known then 483 if @store then # otherwise we are loading 484 known.comment = method.comment if known.comment.empty? 485 previously = ", previously in #{known.file}" unless 486 method.file == known.file 487 @store.rdoc.options.warn \ 488 "Duplicate method #{known.full_name} in #{method.file}#{previously}" 489 end 490 else 491 @methods_hash[key] = method 492 if @current_line_visibility 493 method.visibility, @current_line_visibility = @current_line_visibility, nil 494 else 495 method.visibility = @visibility 496 end 497 add_to @method_list, method 498 resolve_aliases method 499 end 500 501 method 502 end 503 504 ## 505 # Adds a module named +name+. If RDoc already knows +name+ is a class then 506 # that class is returned instead. See also #add_class. 507 508 def add_module(class_type, name) 509 mod = @classes[name] || @modules[name] 510 return mod if mod 511 512 full_name = child_name name 513 mod = @store.modules_hash[full_name] || class_type.new(name) 514 515 add_class_or_module mod, @modules, @store.modules_hash 516 end 517 518 ## 519 # Adds a module by +RDoc::NormalModule+ instance. See also #add_module. 520 521 def add_module_by_normal_module(mod) 522 add_class_or_module mod, @modules, @store.modules_hash 523 end 524 525 ## 526 # Adds an alias from +from+ (a class or module) to +name+ which was defined 527 # in +file+. 528 529 def add_module_alias from, from_name, to, file 530 return from if @done_documenting 531 532 to_full_name = child_name to.name 533 534 # if we already know this name, don't register an alias: 535 # see the metaprogramming in lib/active_support/basic_object.rb, 536 # where we already know BasicObject is a class when we find 537 # BasicObject = BlankSlate 538 return from if @store.find_class_or_module to_full_name 539 540 unless from 541 @store.unmatched_constant_alias[child_name(from_name)] = [to, file] 542 return to 543 end 544 545 new_to = from.dup 546 new_to.name = to.name 547 new_to.full_name = nil 548 549 if new_to.module? then 550 @store.modules_hash[to_full_name] = new_to 551 @modules[to.name] = new_to 552 else 553 @store.classes_hash[to_full_name] = new_to 554 @classes[to.name] = new_to 555 end 556 557 # Registers a constant for this alias. The constant value and comment 558 # will be updated later, when the Ruby parser adds the constant 559 const = RDoc::Constant.new to.name, nil, new_to.comment 560 const.record_location file 561 const.is_alias_for = from 562 add_constant const 563 564 new_to 565 end 566 567 ## 568 # Adds +require+ to this context's top level 569 570 def add_require(require) 571 return require unless @document_self 572 573 if RDoc::TopLevel === self then 574 add_to @requires, require 575 else 576 parent.add_require require 577 end 578 end 579 580 ## 581 # Returns a section with +title+, creating it if it doesn't already exist. 582 # +comment+ will be appended to the section's comment. 583 # 584 # A section with a +title+ of +nil+ will return the default section. 585 # 586 # See also RDoc::Context::Section 587 588 def add_section title, comment = nil 589 if section = @sections[title] then 590 section.add_comment comment if comment 591 else 592 section = Section.new self, title, comment 593 @sections[title] = section 594 end 595 596 section 597 end 598 599 ## 600 # Adds +thing+ to the collection +array+ 601 602 def add_to array, thing 603 array << thing if @document_self 604 605 thing.parent = self 606 thing.store = @store if @store 607 thing.section = current_section 608 end 609 610 ## 611 # Is there any content? 612 # 613 # This means any of: comment, aliases, methods, attributes, external 614 # aliases, require, constant. 615 # 616 # Includes and extends are also checked unless <tt>includes == false</tt>. 617 618 def any_content(includes = true) 619 @any_content ||= !( 620 @comment.empty? && 621 @method_list.empty? && 622 @attributes.empty? && 623 @aliases.empty? && 624 @external_aliases.empty? && 625 @requires.empty? && 626 @constants.empty? 627 ) 628 @any_content || (includes && !(@includes + @extends).empty? ) 629 end 630 631 ## 632 # Creates the full name for a child with +name+ 633 634 def child_name name 635 if name =~ /^:+/ 636 $' #' 637 elsif RDoc::TopLevel === self then 638 name 639 else 640 "#{self.full_name}::#{name}" 641 end 642 end 643 644 ## 645 # Class attributes 646 647 def class_attributes 648 @class_attributes ||= attributes.select { |a| a.singleton } 649 end 650 651 ## 652 # Class methods 653 654 def class_method_list 655 @class_method_list ||= method_list.select { |a| a.singleton } 656 end 657 658 ## 659 # Array of classes in this context 660 661 def classes 662 @classes.values 663 end 664 665 ## 666 # All classes and modules in this namespace 667 668 def classes_and_modules 669 classes + modules 670 end 671 672 ## 673 # Hash of classes keyed by class name 674 675 def classes_hash 676 @classes 677 end 678 679 ## 680 # The current documentation section that new items will be added to. If 681 # temporary_section is available it will be used. 682 683 def current_section 684 if section = @temporary_section then 685 @temporary_section = nil 686 else 687 section = @current_section 688 end 689 690 section 691 end 692 693 ## 694 # Is part of this thing was defined in +file+? 695 696 def defined_in?(file) 697 @in_files.include?(file) 698 end 699 700 def display(method_attr) # :nodoc: 701 if method_attr.is_a? RDoc::Attr 702 "#{method_attr.definition} #{method_attr.pretty_name}" 703 else 704 "method #{method_attr.pretty_name}" 705 end 706 end 707 708 ## 709 # Iterator for ancestors for duck-typing. Does nothing. See 710 # RDoc::ClassModule#each_ancestor. 711 # 712 # This method exists to make it easy to work with Context subclasses that 713 # aren't part of RDoc. 714 715 def each_ancestor # :nodoc: 716 end 717 718 ## 719 # Iterator for attributes 720 721 def each_attribute # :yields: attribute 722 @attributes.each { |a| yield a } 723 end 724 725 ## 726 # Iterator for classes and modules 727 728 def each_classmodule(&block) # :yields: module 729 classes_and_modules.sort.each(&block) 730 end 731 732 ## 733 # Iterator for constants 734 735 def each_constant # :yields: constant 736 @constants.each {|c| yield c} 737 end 738 739 ## 740 # Iterator for included modules 741 742 def each_include # :yields: include 743 @includes.each do |i| yield i end 744 end 745 746 ## 747 # Iterator for extension modules 748 749 def each_extend # :yields: extend 750 @extends.each do |e| yield e end 751 end 752 753 ## 754 # Iterator for methods 755 756 def each_method # :yields: method 757 return enum_for __method__ unless block_given? 758 759 @method_list.sort.each { |m| yield m } 760 end 761 762 ## 763 # Iterator for each section's contents sorted by title. The +section+, the 764 # section's +constants+ and the sections +attributes+ are yielded. The 765 # +constants+ and +attributes+ collections are sorted. 766 # 767 # To retrieve methods in a section use #methods_by_type with the optional 768 # +section+ parameter. 769 # 770 # NOTE: Do not edit collections yielded by this method 771 772 def each_section # :yields: section, constants, attributes 773 return enum_for __method__ unless block_given? 774 775 constants = @constants.group_by do |constant| constant.section end 776 attributes = @attributes.group_by do |attribute| attribute.section end 777 778 constants.default = [] 779 attributes.default = [] 780 781 sort_sections.each do |section| 782 yield section, constants[section].select(&:display?).sort, attributes[section].select(&:display?).sort 783 end 784 end 785 786 ## 787 # Finds an attribute +name+ with singleton value +singleton+. 788 789 def find_attribute(name, singleton) 790 name = $1 if name =~ /^(.*)=$/ 791 @attributes.find { |a| a.name == name && a.singleton == singleton } 792 end 793 794 ## 795 # Finds an attribute with +name+ in this context 796 797 def find_attribute_named(name) 798 case name 799 when /\A#/ then 800 find_attribute name[1..-1], false 801 when /\A::/ then 802 find_attribute name[2..-1], true 803 else 804 @attributes.find { |a| a.name == name } 805 end 806 end 807 808 ## 809 # Finds a class method with +name+ in this context 810 811 def find_class_method_named(name) 812 @method_list.find { |meth| meth.singleton && meth.name == name } 813 end 814 815 ## 816 # Finds a constant with +name+ in this context 817 818 def find_constant_named(name) 819 @constants.find do |m| 820 m.name == name || m.full_name == name 821 end 822 end 823 824 ## 825 # Find a module at a higher scope 826 827 def find_enclosing_module_named(name) 828 parent && parent.find_module_named(name) 829 end 830 831 ## 832 # Finds an external alias +name+ with singleton value +singleton+. 833 834 def find_external_alias(name, singleton) 835 @external_aliases.find { |m| m.name == name && m.singleton == singleton } 836 end 837 838 ## 839 # Finds an external alias with +name+ in this context 840 841 def find_external_alias_named(name) 842 case name 843 when /\A#/ then 844 find_external_alias name[1..-1], false 845 when /\A::/ then 846 find_external_alias name[2..-1], true 847 else 848 @external_aliases.find { |a| a.name == name } 849 end 850 end 851 852 ## 853 # Finds a file with +name+ in this context 854 855 def find_file_named name 856 @store.find_file_named name 857 end 858 859 ## 860 # Finds an instance method with +name+ in this context 861 862 def find_instance_method_named(name) 863 @method_list.find { |meth| !meth.singleton && meth.name == name } 864 end 865 866 ## 867 # Finds a method, constant, attribute, external alias, module or file 868 # named +symbol+ in this context. 869 870 def find_local_symbol(symbol) 871 find_method_named(symbol) or 872 find_constant_named(symbol) or 873 find_attribute_named(symbol) or 874 find_external_alias_named(symbol) or 875 find_module_named(symbol) or 876 find_file_named(symbol) 877 end 878 879 ## 880 # Finds a method named +name+ with singleton value +singleton+. 881 882 def find_method(name, singleton) 883 @method_list.find { |m| 884 if m.singleton 885 m.name == name && m.singleton == singleton 886 else 887 m.name == name && !m.singleton && !singleton 888 end 889 } 890 end 891 892 ## 893 # Finds a instance or module method with +name+ in this context 894 895 def find_method_named(name) 896 case name 897 when /\A#/ then 898 find_method name[1..-1], false 899 when /\A::/ then 900 find_method name[2..-1], true 901 else 902 @method_list.find { |meth| meth.name == name } 903 end 904 end 905 906 ## 907 # Find a module with +name+ using ruby's scoping rules 908 909 def find_module_named(name) 910 res = @modules[name] || @classes[name] 911 return res if res 912 return self if self.name == name 913 find_enclosing_module_named name 914 end 915 916 ## 917 # Look up +symbol+, first as a module, then as a local symbol. 918 919 def find_symbol(symbol) 920 find_symbol_module(symbol) || find_local_symbol(symbol) 921 end 922 923 ## 924 # Look up a module named +symbol+. 925 926 def find_symbol_module(symbol) 927 result = nil 928 929 # look for a class or module 'symbol' 930 case symbol 931 when /^::/ then 932 result = @store.find_class_or_module symbol 933 when /^(\w+):+(.+)$/ 934 suffix = $2 935 top = $1 936 searched = self 937 while searched do 938 mod = searched.find_module_named(top) 939 break unless mod 940 result = @store.find_class_or_module "#{mod.full_name}::#{suffix}" 941 break if result || searched.is_a?(RDoc::TopLevel) 942 searched = searched.parent 943 end 944 else 945 searched = self 946 while searched do 947 result = searched.find_module_named(symbol) 948 break if result || searched.is_a?(RDoc::TopLevel) 949 searched = searched.parent 950 end 951 end 952 953 result 954 end 955 956 ## 957 # The full name for this context. This method is overridden by subclasses. 958 959 def full_name 960 '(unknown)' 961 end 962 963 ## 964 # Does this context and its methods and constants all have documentation? 965 # 966 # (Yes, fully documented doesn't mean everything.) 967 968 def fully_documented? 969 documented? and 970 attributes.all? { |a| a.documented? } and 971 method_list.all? { |m| m.documented? } and 972 constants.all? { |c| c.documented? } 973 end 974 975 ## 976 # URL for this with a +prefix+ 977 978 def http_url(prefix) 979 path = name_for_path 980 path = path.gsub(/<<\s*(\w*)/, 'from-\1') if path =~ /<</ 981 path = [prefix] + path.split('::') 982 983 File.join(*path.compact) + '.html' 984 end 985 986 ## 987 # Instance attributes 988 989 def instance_attributes 990 @instance_attributes ||= attributes.reject { |a| a.singleton } 991 end 992 993 ## 994 # Instance methods 995 #-- 996 # TODO rename to instance_methods 997 998 def instance_method_list 999 @instance_method_list ||= method_list.reject { |a| a.singleton } 1000 end 1001 1002 ## 1003 # Breaks method_list into a nested hash by type (<tt>'class'</tt> or 1004 # <tt>'instance'</tt>) and visibility (+:public+, +:protected+, +:private+). 1005 # 1006 # If +section+ is provided only methods in that RDoc::Context::Section will 1007 # be returned. 1008 1009 def methods_by_type section = nil 1010 methods = {} 1011 1012 TYPES.each do |type| 1013 visibilities = {} 1014 RDoc::VISIBILITIES.each do |vis| 1015 visibilities[vis] = [] 1016 end 1017 1018 methods[type] = visibilities 1019 end 1020 1021 each_method do |method| 1022 next if section and not method.section == section 1023 methods[method.type][method.visibility] << method 1024 end 1025 1026 methods 1027 end 1028 1029 ## 1030 # Yields AnyMethod and Attr entries matching the list of names in +methods+. 1031 1032 def methods_matching(methods, singleton = false, &block) 1033 (@method_list + @attributes).each do |m| 1034 yield m if methods.include?(m.name) and m.singleton == singleton 1035 end 1036 1037 each_ancestor do |parent| 1038 parent.methods_matching(methods, singleton, &block) 1039 end 1040 end 1041 1042 ## 1043 # Array of modules in this context 1044 1045 def modules 1046 @modules.values 1047 end 1048 1049 ## 1050 # Hash of modules keyed by module name 1051 1052 def modules_hash 1053 @modules 1054 end 1055 1056 ## 1057 # Name to use to generate the url. 1058 # <tt>#full_name</tt> by default. 1059 1060 def name_for_path 1061 full_name 1062 end 1063 1064 ## 1065 # Changes the visibility for new methods to +visibility+ 1066 1067 def ongoing_visibility=(visibility) 1068 @visibility = visibility 1069 end 1070 1071 ## 1072 # Record +top_level+ as a file +self+ is in. 1073 1074 def record_location(top_level) 1075 @in_files << top_level unless @in_files.include?(top_level) 1076 end 1077 1078 ## 1079 # Should we remove this context from the documentation? 1080 # 1081 # The answer is yes if: 1082 # * #received_nodoc is +true+ 1083 # * #any_content is +false+ (not counting includes) 1084 # * All #includes are modules (not a string), and their module has 1085 # <tt>#remove_from_documentation? == true</tt> 1086 # * All classes and modules have <tt>#remove_from_documentation? == true</tt> 1087 1088 def remove_from_documentation? 1089 @remove_from_documentation ||= 1090 @received_nodoc && 1091 !any_content(false) && 1092 @includes.all? { |i| !i.module.is_a?(String) && i.module.remove_from_documentation? } && 1093 classes_and_modules.all? { |cm| cm.remove_from_documentation? } 1094 end 1095 1096 ## 1097 # Removes methods and attributes with a visibility less than +min_visibility+. 1098 #-- 1099 # TODO mark the visibility of attributes in the template (if not public?) 1100 1101 def remove_invisible min_visibility 1102 return if [:private, :nodoc].include? min_visibility 1103 remove_invisible_in @method_list, min_visibility 1104 remove_invisible_in @attributes, min_visibility 1105 remove_invisible_in @constants, min_visibility 1106 end 1107 1108 ## 1109 # Only called when min_visibility == :public or :private 1110 1111 def remove_invisible_in array, min_visibility # :nodoc: 1112 if min_visibility == :public then 1113 array.reject! { |e| 1114 e.visibility != :public and not e.force_documentation 1115 } 1116 else 1117 array.reject! { |e| 1118 e.visibility == :private and not e.force_documentation 1119 } 1120 end 1121 end 1122 1123 ## 1124 # Tries to resolve unmatched aliases when a method or attribute has just 1125 # been added. 1126 1127 def resolve_aliases added 1128 # resolve any pending unmatched aliases 1129 key = added.pretty_name 1130 unmatched_alias_list = @unmatched_alias_lists[key] 1131 return unless unmatched_alias_list 1132 unmatched_alias_list.each do |unmatched_alias| 1133 added.add_alias unmatched_alias, self 1134 @external_aliases.delete unmatched_alias 1135 end 1136 @unmatched_alias_lists.delete key 1137 end 1138 1139 ## 1140 # Returns RDoc::Context::Section objects referenced in this context for use 1141 # in a table of contents. 1142 1143 def section_contents 1144 used_sections = {} 1145 1146 each_method do |method| 1147 next unless method.display? 1148 1149 used_sections[method.section] = true 1150 end 1151 1152 # order found sections 1153 sections = sort_sections.select do |section| 1154 used_sections[section] 1155 end 1156 1157 # only the default section is used 1158 return [] if 1159 sections.length == 1 and not sections.first.title 1160 1161 sections 1162 end 1163 1164 ## 1165 # Sections in this context 1166 1167 def sections 1168 @sections.values 1169 end 1170 1171 def sections_hash # :nodoc: 1172 @sections 1173 end 1174 1175 ## 1176 # Sets the current section to a section with +title+. See also #add_section 1177 1178 def set_current_section title, comment 1179 @current_section = add_section title, comment 1180 end 1181 1182 ## 1183 # Given an array +methods+ of method names, set the visibility of each to 1184 # +visibility+ 1185 1186 def set_visibility_for(methods, visibility, singleton = false) 1187 methods_matching methods, singleton do |m| 1188 m.visibility = visibility 1189 end 1190 end 1191 1192 ## 1193 # Given an array +names+ of constants, set the visibility of each constant to 1194 # +visibility+ 1195 1196 def set_constant_visibility_for(names, visibility) 1197 names.each do |name| 1198 constant = @constants_hash[name] or next 1199 constant.visibility = visibility 1200 end 1201 end 1202 1203 ## 1204 # Sorts sections alphabetically (default) or in TomDoc fashion (none, 1205 # Public, Internal, Deprecated) 1206 1207 def sort_sections 1208 titles = @sections.map { |title, _| title } 1209 1210 if titles.length > 1 and 1211 TOMDOC_TITLES_SORT == 1212 (titles | TOMDOC_TITLES).sort_by { |title| title.to_s } then 1213 @sections.values_at(*TOMDOC_TITLES).compact 1214 else 1215 @sections.sort_by { |title, _| 1216 title.to_s 1217 }.map { |_, section| 1218 section 1219 } 1220 end 1221 end 1222 1223 def to_s # :nodoc: 1224 "#{self.class.name} #{self.full_name}" 1225 end 1226 1227 ## 1228 # Return the TopLevel that owns us 1229 #-- 1230 # FIXME we can be 'owned' by several TopLevel (see #record_location & 1231 # #in_files) 1232 1233 def top_level 1234 return @top_level if defined? @top_level 1235 @top_level = self 1236 @top_level = @top_level.parent until RDoc::TopLevel === @top_level 1237 @top_level 1238 end 1239 1240 ## 1241 # Upgrades NormalModule +mod+ in +enclosing+ to a +class_type+ 1242 1243 def upgrade_to_class mod, class_type, enclosing 1244 enclosing.modules_hash.delete mod.name 1245 1246 klass = RDoc::ClassModule.from_module class_type, mod 1247 klass.store = @store 1248 1249 # if it was there, then we keep it even if done_documenting 1250 @store.classes_hash[mod.full_name] = klass 1251 enclosing.classes_hash[mod.name] = klass 1252 1253 klass 1254 end 1255 1256 autoload :Section, 'rdoc/context/section' 1257 1258end 1259