source: extensions/gsdl-video/trunk/installed/cmdline/lib/ruby/1.8/rdoc/code_objects.rb@ 18425

Last change on this file since 18425 was 18425, checked in by davidb, 15 years ago

Video extension to Greenstone

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