/tools/Ruby/lib/ruby/1.8/rdoc/code_objects.rb

http://github.com/agross/netopenspace · Ruby · 765 lines · 587 code · 110 blank · 68 comment · 37 complexity · 1622d96f389850dd43d6928f223f2960 MD5 · raw file

  1. # We represent the various high-level code constructs that appear
  2. # in Ruby programs: classes, modules, methods, and so on.
  3. require 'rdoc/tokenstream'
  4. module RDoc
  5. # We contain the common stuff for contexts (which are containers)
  6. # and other elements (methods, attributes and so on)
  7. #
  8. class CodeObject
  9. attr_accessor :parent
  10. # We are the model of the code, but we know that at some point
  11. # we will be worked on by viewers. By implementing the Viewable
  12. # protocol, viewers can associated themselves with these objects.
  13. attr_accessor :viewer
  14. # are we done documenting (ie, did we come across a :enddoc:)?
  15. attr_accessor :done_documenting
  16. # Which section are we in
  17. attr_accessor :section
  18. # do we document ourselves?
  19. attr_reader :document_self
  20. def document_self=(val)
  21. @document_self = val
  22. if !val
  23. remove_methods_etc
  24. end
  25. end
  26. # set and cleared by :startdoc: and :enddoc:, this is used to toggle
  27. # the capturing of documentation
  28. def start_doc
  29. @document_self = true
  30. @document_children = true
  31. end
  32. def stop_doc
  33. @document_self = false
  34. @document_children = false
  35. end
  36. # do we document ourselves and our children
  37. attr_reader :document_children
  38. def document_children=(val)
  39. @document_children = val
  40. if !val
  41. remove_classes_and_modules
  42. end
  43. end
  44. # Do we _force_ documentation, even is we wouldn't normally show the entity
  45. attr_accessor :force_documentation
  46. # Default callbacks to nothing, but this is overridden for classes
  47. # and modules
  48. def remove_classes_and_modules
  49. end
  50. def remove_methods_etc
  51. end
  52. def initialize
  53. @document_self = true
  54. @document_children = true
  55. @force_documentation = false
  56. @done_documenting = false
  57. end
  58. # Access the code object's comment
  59. attr_reader :comment
  60. # Update the comment, but don't overwrite a real comment
  61. # with an empty one
  62. def comment=(comment)
  63. @comment = comment unless comment.empty?
  64. end
  65. # There's a wee trick we pull. Comment blocks can have directives that
  66. # override the stuff we extract during the parse. So, we have a special
  67. # class method, attr_overridable, that lets code objects list
  68. # those directives. Wehn a comment is assigned, we then extract
  69. # out any matching directives and update our object
  70. def CodeObject.attr_overridable(name, *aliases)
  71. @overridables ||= {}
  72. attr_accessor name
  73. aliases.unshift name
  74. aliases.each do |directive_name|
  75. @overridables[directive_name.to_s] = name
  76. end
  77. end
  78. end
  79. # A Context is something that can hold modules, classes, methods,
  80. # attributes, aliases, requires, and includes. Classes, modules, and
  81. # files are all Contexts.
  82. class Context < CodeObject
  83. attr_reader :name, :method_list, :attributes, :aliases, :constants
  84. attr_reader :requires, :includes, :in_files, :visibility
  85. attr_reader :sections
  86. class Section
  87. attr_reader :title, :comment, :sequence
  88. @@sequence = "SEC00000"
  89. def initialize(title, comment)
  90. @title = title
  91. @@sequence.succ!
  92. @sequence = @@sequence.dup
  93. set_comment(comment)
  94. end
  95. private
  96. # Set the comment for this section from the original comment block
  97. # If the first line contains :section:, strip it and use the rest. Otherwise
  98. # remove lines up to the line containing :section:, and look for
  99. # those lines again at the end and remove them. This lets us write
  100. #
  101. # # ---------------------
  102. # # :SECTION: The title
  103. # # The body
  104. # # ---------------------
  105. def set_comment(comment)
  106. return unless comment
  107. if comment =~ /^.*?:section:.*$/
  108. start = $`
  109. rest = $'
  110. if start.empty?
  111. @comment = rest
  112. else
  113. @comment = rest.sub(/#{start.chomp}\Z/, '')
  114. end
  115. else
  116. @comment = comment
  117. end
  118. @comment = nil if @comment.empty?
  119. end
  120. end
  121. def initialize
  122. super()
  123. @in_files = []
  124. @name ||= "unknown"
  125. @comment ||= ""
  126. @parent = nil
  127. @visibility = :public
  128. @current_section = Section.new(nil, nil)
  129. @sections = [ @current_section ]
  130. initialize_methods_etc
  131. initialize_classes_and_modules
  132. end
  133. # map the class hash to an array externally
  134. def classes
  135. @classes.values
  136. end
  137. # map the module hash to an array externally
  138. def modules
  139. @modules.values
  140. end
  141. # Change the default visibility for new methods
  142. def ongoing_visibility=(vis)
  143. @visibility = vis
  144. end
  145. # Given an array +methods+ of method names, set the
  146. # visibility of the corresponding AnyMethod object
  147. def set_visibility_for(methods, vis, singleton=false)
  148. count = 0
  149. @method_list.each do |m|
  150. if methods.include?(m.name) && m.singleton == singleton
  151. m.visibility = vis
  152. count += 1
  153. end
  154. end
  155. return if count == methods.size || singleton
  156. # perhaps we need to look at attributes
  157. @attributes.each do |a|
  158. if methods.include?(a.name)
  159. a.visibility = vis
  160. count += 1
  161. end
  162. end
  163. end
  164. # Record the file that we happen to find it in
  165. def record_location(toplevel)
  166. @in_files << toplevel unless @in_files.include?(toplevel)
  167. end
  168. # Return true if at least part of this thing was defined in +file+
  169. def defined_in?(file)
  170. @in_files.include?(file)
  171. end
  172. def add_class(class_type, name, superclass)
  173. add_class_or_module(@classes, class_type, name, superclass)
  174. end
  175. def add_module(class_type, name)
  176. add_class_or_module(@modules, class_type, name, nil)
  177. end
  178. def add_method(a_method)
  179. puts "Adding #@visibility method #{a_method.name} to #@name" if $DEBUG
  180. a_method.visibility = @visibility
  181. add_to(@method_list, a_method)
  182. end
  183. def add_attribute(an_attribute)
  184. add_to(@attributes, an_attribute)
  185. end
  186. def add_alias(an_alias)
  187. meth = find_instance_method_named(an_alias.old_name)
  188. if meth
  189. new_meth = AnyMethod.new(an_alias.text, an_alias.new_name)
  190. new_meth.is_alias_for = meth
  191. new_meth.singleton = meth.singleton
  192. new_meth.params = meth.params
  193. new_meth.comment = "Alias for \##{meth.name}"
  194. meth.add_alias(new_meth)
  195. add_method(new_meth)
  196. else
  197. add_to(@aliases, an_alias)
  198. end
  199. end
  200. def add_include(an_include)
  201. add_to(@includes, an_include)
  202. end
  203. def add_constant(const)
  204. add_to(@constants, const)
  205. end
  206. # Requires always get added to the top-level (file) context
  207. def add_require(a_require)
  208. if self.kind_of? TopLevel
  209. add_to(@requires, a_require)
  210. else
  211. parent.add_require(a_require)
  212. end
  213. end
  214. def add_class_or_module(collection, class_type, name, superclass=nil)
  215. cls = collection[name]
  216. if cls
  217. puts "Reusing class/module #{name}" if $DEBUG
  218. else
  219. cls = class_type.new(name, superclass)
  220. puts "Adding class/module #{name} to #@name" if $DEBUG
  221. # collection[name] = cls if @document_self && !@done_documenting
  222. collection[name] = cls if !@done_documenting
  223. cls.parent = self
  224. cls.section = @current_section
  225. end
  226. cls
  227. end
  228. def add_to(array, thing)
  229. array << thing if @document_self && !@done_documenting
  230. thing.parent = self
  231. thing.section = @current_section
  232. end
  233. # If a class's documentation is turned off after we've started
  234. # collecting methods etc., we need to remove the ones
  235. # we have
  236. def remove_methods_etc
  237. initialize_methods_etc
  238. end
  239. def initialize_methods_etc
  240. @method_list = []
  241. @attributes = []
  242. @aliases = []
  243. @requires = []
  244. @includes = []
  245. @constants = []
  246. end
  247. # and remove classes and modules when we see a :nodoc: all
  248. def remove_classes_and_modules
  249. initialize_classes_and_modules
  250. end
  251. def initialize_classes_and_modules
  252. @classes = {}
  253. @modules = {}
  254. end
  255. # Find a named module
  256. def find_module_named(name)
  257. return self if self.name == name
  258. res = @modules[name] || @classes[name]
  259. return res if res
  260. find_enclosing_module_named(name)
  261. end
  262. # find a module at a higher scope
  263. def find_enclosing_module_named(name)
  264. parent && parent.find_module_named(name)
  265. end
  266. # Iterate over all the classes and modules in
  267. # this object
  268. def each_classmodule
  269. @modules.each_value {|m| yield m}
  270. @classes.each_value {|c| yield c}
  271. end
  272. def each_method
  273. @method_list.each {|m| yield m}
  274. end
  275. def each_attribute
  276. @attributes.each {|a| yield a}
  277. end
  278. def each_constant
  279. @constants.each {|c| yield c}
  280. end
  281. # Return the toplevel that owns us
  282. def toplevel
  283. return @toplevel if defined? @toplevel
  284. @toplevel = self
  285. @toplevel = @toplevel.parent until TopLevel === @toplevel
  286. @toplevel
  287. end
  288. # allow us to sort modules by name
  289. def <=>(other)
  290. name <=> other.name
  291. end
  292. # Look up the given symbol. If method is non-nil, then
  293. # we assume the symbol references a module that
  294. # contains that method
  295. def find_symbol(symbol, method=nil)
  296. result = nil
  297. case symbol
  298. when /^::(.*)/
  299. result = toplevel.find_symbol($1)
  300. when /::/
  301. modules = symbol.split(/::/)
  302. unless modules.empty?
  303. module_name = modules.shift
  304. result = find_module_named(module_name)
  305. if result
  306. modules.each do |module_name|
  307. result = result.find_module_named(module_name)
  308. break unless result
  309. end
  310. end
  311. end
  312. else
  313. # if a method is specified, then we're definitely looking for
  314. # a module, otherwise it could be any symbol
  315. if method
  316. result = find_module_named(symbol)
  317. else
  318. result = find_local_symbol(symbol)
  319. if result.nil?
  320. if symbol =~ /^[A-Z]/
  321. result = parent
  322. while result && result.name != symbol
  323. result = result.parent
  324. end
  325. end
  326. end
  327. end
  328. end
  329. if result && method
  330. if !result.respond_to?(:find_local_symbol)
  331. p result.name
  332. p method
  333. fail
  334. end
  335. result = result.find_local_symbol(method)
  336. end
  337. result
  338. end
  339. def find_local_symbol(symbol)
  340. res = find_method_named(symbol) ||
  341. find_constant_named(symbol) ||
  342. find_attribute_named(symbol) ||
  343. find_module_named(symbol)
  344. end
  345. # Handle sections
  346. def set_current_section(title, comment)
  347. @current_section = Section.new(title, comment)
  348. @sections << @current_section
  349. end
  350. private
  351. # Find a named method, or return nil
  352. def find_method_named(name)
  353. @method_list.find {|meth| meth.name == name}
  354. end
  355. # Find a named instance method, or return nil
  356. def find_instance_method_named(name)
  357. @method_list.find {|meth| meth.name == name && !meth.singleton}
  358. end
  359. # Find a named constant, or return nil
  360. def find_constant_named(name)
  361. @constants.find {|m| m.name == name}
  362. end
  363. # Find a named attribute, or return nil
  364. def find_attribute_named(name)
  365. @attributes.find {|m| m.name == name}
  366. end
  367. end
  368. # A TopLevel context is a source file
  369. class TopLevel < Context
  370. attr_accessor :file_stat
  371. attr_accessor :file_relative_name
  372. attr_accessor :file_absolute_name
  373. attr_accessor :diagram
  374. @@all_classes = {}
  375. @@all_modules = {}
  376. def TopLevel::reset
  377. @@all_classes = {}
  378. @@all_modules = {}
  379. end
  380. def initialize(file_name)
  381. super()
  382. @name = "TopLevel"
  383. @file_relative_name = file_name
  384. @file_absolute_name = file_name
  385. @file_stat = File.stat(file_name)
  386. @diagram = nil
  387. end
  388. def full_name
  389. nil
  390. end
  391. # Adding a class or module to a TopLevel is special, as we only
  392. # want one copy of a particular top-level class. For example,
  393. # if both file A and file B implement class C, we only want one
  394. # ClassModule object for C. This code arranges to share
  395. # classes and modules between files.
  396. def add_class_or_module(collection, class_type, name, superclass)
  397. cls = collection[name]
  398. if cls
  399. puts "Reusing class/module #{name}" if $DEBUG
  400. else
  401. if class_type == NormalModule
  402. all = @@all_modules
  403. else
  404. all = @@all_classes
  405. end
  406. cls = all[name]
  407. if !cls
  408. cls = class_type.new(name, superclass)
  409. all[name] = cls unless @done_documenting
  410. end
  411. puts "Adding class/module #{name} to #@name" if $DEBUG
  412. collection[name] = cls unless @done_documenting
  413. cls.parent = self
  414. end
  415. cls
  416. end
  417. def TopLevel.all_classes_and_modules
  418. @@all_classes.values + @@all_modules.values
  419. end
  420. def TopLevel.find_class_named(name)
  421. @@all_classes.each_value do |c|
  422. res = c.find_class_named(name)
  423. return res if res
  424. end
  425. nil
  426. end
  427. def find_local_symbol(symbol)
  428. find_class_or_module_named(symbol) || super
  429. end
  430. def find_class_or_module_named(symbol)
  431. @@all_classes.each_value {|c| return c if c.name == symbol}
  432. @@all_modules.each_value {|m| return m if m.name == symbol}
  433. nil
  434. end
  435. # Find a named module
  436. def find_module_named(name)
  437. find_class_or_module_named(name) || find_enclosing_module_named(name)
  438. end
  439. end
  440. # ClassModule is the base class for objects representing either a
  441. # class or a module.
  442. class ClassModule < Context
  443. attr_reader :superclass
  444. attr_accessor :diagram
  445. def initialize(name, superclass = nil)
  446. @name = name
  447. @diagram = nil
  448. @superclass = superclass
  449. @comment = ""
  450. super()
  451. end
  452. # Return the fully qualified name of this class or module
  453. def full_name
  454. if @parent && @parent.full_name
  455. @parent.full_name + "::" + @name
  456. else
  457. @name
  458. end
  459. end
  460. def http_url(prefix)
  461. path = full_name.split("::")
  462. File.join(prefix, *path) + ".html"
  463. end
  464. # Return +true+ if this object represents a module
  465. def is_module?
  466. false
  467. end
  468. # to_s is simply for debugging
  469. def to_s
  470. res = self.class.name + ": " + @name
  471. res << @comment.to_s
  472. res << super
  473. res
  474. end
  475. def find_class_named(name)
  476. return self if full_name == name
  477. @classes.each_value {|c| return c if c.find_class_named(name) }
  478. nil
  479. end
  480. end
  481. # Anonymous classes
  482. class AnonClass < ClassModule
  483. end
  484. # Normal classes
  485. class NormalClass < ClassModule
  486. end
  487. # Singleton classes
  488. class SingleClass < ClassModule
  489. end
  490. # Module
  491. class NormalModule < ClassModule
  492. def is_module?
  493. true
  494. end
  495. end
  496. # AnyMethod is the base class for objects representing methods
  497. class AnyMethod < CodeObject
  498. attr_accessor :name
  499. attr_accessor :visibility
  500. attr_accessor :block_params
  501. attr_accessor :dont_rename_initialize
  502. attr_accessor :singleton
  503. attr_reader :aliases # list of other names for this method
  504. attr_accessor :is_alias_for # or a method we're aliasing
  505. attr_overridable :params, :param, :parameters, :parameter
  506. attr_accessor :call_seq
  507. include TokenStream
  508. def initialize(text, name)
  509. super()
  510. @text = text
  511. @name = name
  512. @token_stream = nil
  513. @visibility = :public
  514. @dont_rename_initialize = false
  515. @block_params = nil
  516. @aliases = []
  517. @is_alias_for = nil
  518. @comment = ""
  519. @call_seq = nil
  520. end
  521. def <=>(other)
  522. @name <=> other.name
  523. end
  524. def to_s
  525. res = self.class.name + ": " + @name + " (" + @text + ")\n"
  526. res << @comment.to_s
  527. res
  528. end
  529. def param_seq
  530. p = params.gsub(/\s*\#.*/, '')
  531. p = p.tr("\n", " ").squeeze(" ")
  532. p = "(" + p + ")" unless p[0] == ?(
  533. if (block = block_params)
  534. # If this method has explicit block parameters, remove any
  535. # explicit &block
  536. $stderr.puts p
  537. p.sub!(/,?\s*&\w+/)
  538. $stderr.puts p
  539. block.gsub!(/\s*\#.*/, '')
  540. block = block.tr("\n", " ").squeeze(" ")
  541. if block[0] == ?(
  542. block.sub!(/^\(/, '').sub!(/\)/, '')
  543. end
  544. p << " {|#{block}| ...}"
  545. end
  546. p
  547. end
  548. def add_alias(method)
  549. @aliases << method
  550. end
  551. end
  552. # Represent an alias, which is an old_name/ new_name pair associated
  553. # with a particular context
  554. class Alias < CodeObject
  555. attr_accessor :text, :old_name, :new_name, :comment
  556. def initialize(text, old_name, new_name, comment)
  557. super()
  558. @text = text
  559. @old_name = old_name
  560. @new_name = new_name
  561. self.comment = comment
  562. end
  563. def to_s
  564. "alias: #{self.old_name} -> #{self.new_name}\n#{self.comment}"
  565. end
  566. end
  567. # Represent a constant
  568. class Constant < CodeObject
  569. attr_accessor :name, :value
  570. def initialize(name, value, comment)
  571. super()
  572. @name = name
  573. @value = value
  574. self.comment = comment
  575. end
  576. end
  577. # Represent attributes
  578. class Attr < CodeObject
  579. attr_accessor :text, :name, :rw, :visibility
  580. def initialize(text, name, rw, comment)
  581. super()
  582. @text = text
  583. @name = name
  584. @rw = rw
  585. @visibility = :public
  586. self.comment = comment
  587. end
  588. def to_s
  589. "attr: #{self.name} #{self.rw}\n#{self.comment}"
  590. end
  591. def <=>(other)
  592. self.name <=> other.name
  593. end
  594. end
  595. # a required file
  596. class Require < CodeObject
  597. attr_accessor :name
  598. def initialize(name, comment)
  599. super()
  600. @name = name.gsub(/'|"/, "") #'
  601. self.comment = comment
  602. end
  603. end
  604. # an included module
  605. class Include < CodeObject
  606. attr_accessor :name
  607. def initialize(name, comment)
  608. super()
  609. @name = name
  610. self.comment = comment
  611. end
  612. end
  613. end