1# frozen_string_literal: true
2##
3# A TopLevel context is a representation of the contents of a single file
4
5class RDoc::TopLevel < RDoc::Context
6
7  MARSHAL_VERSION = 0 # :nodoc:
8
9  ##
10  # This TopLevel's File::Stat struct
11
12  attr_accessor :file_stat
13
14  ##
15  # Relative name of this file
16
17  attr_accessor :relative_name
18
19  ##
20  # Absolute name of this file
21
22  attr_accessor :absolute_name
23
24  ##
25  # All the classes or modules that were declared in
26  # this file. These are assigned to either +#classes_hash+
27  # or +#modules_hash+ once we know what they really are.
28
29  attr_reader :classes_or_modules
30
31  attr_accessor :diagram # :nodoc:
32
33  ##
34  # The parser class that processed this file
35
36  attr_reader :parser
37
38  ##
39  # Creates a new TopLevel for the file at +absolute_name+.  If documentation
40  # is being generated outside the source dir +relative_name+ is relative to
41  # the source directory.
42
43  def initialize absolute_name, relative_name = absolute_name
44    super()
45    @name = nil
46    @absolute_name = absolute_name
47    @relative_name = relative_name
48    @file_stat     = File.stat(absolute_name) rescue nil # HACK for testing
49    @diagram       = nil
50    @parser        = nil
51
52    @classes_or_modules = []
53  end
54
55  def parser=(val)
56    @parser = val
57    @store.update_parser_of_file(absolute_name, val) if @store
58    @parser
59  end
60
61  ##
62  # An RDoc::TopLevel is equal to another with the same relative_name
63
64  def == other
65    self.class === other and @relative_name == other.relative_name
66  end
67
68  alias eql? ==
69
70  ##
71  # Adds +an_alias+ to +Object+ instead of +self+.
72
73  def add_alias(an_alias)
74    object_class.record_location self
75    return an_alias unless @document_self
76    object_class.add_alias an_alias
77  end
78
79  ##
80  # Adds +constant+ to +Object+ instead of +self+.
81
82  def add_constant constant
83    object_class.record_location self
84    return constant unless @document_self
85    object_class.add_constant constant
86  end
87
88  ##
89  # Adds +include+ to +Object+ instead of +self+.
90
91  def add_include(include)
92    object_class.record_location self
93    return include unless @document_self
94    object_class.add_include include
95  end
96
97  ##
98  # Adds +method+ to +Object+ instead of +self+.
99
100  def add_method(method)
101    object_class.record_location self
102    return method unless @document_self
103    object_class.add_method method
104  end
105
106  ##
107  # Adds class or module +mod+. Used in the building phase
108  # by the Ruby parser.
109
110  def add_to_classes_or_modules mod
111    @classes_or_modules << mod
112  end
113
114  ##
115  # Base name of this file
116
117  def base_name
118    File.basename @relative_name
119  end
120
121  alias name base_name
122
123  ##
124  # Only a TopLevel that contains text file) will be displayed.  See also
125  # RDoc::CodeObject#display?
126
127  def display?
128    text? and super
129  end
130
131  ##
132  # See RDoc::TopLevel::find_class_or_module
133  #--
134  # TODO Why do we search through all classes/modules found, not just the
135  #       ones of this instance?
136
137  def find_class_or_module name
138    @store.find_class_or_module name
139  end
140
141  ##
142  # Finds a class or module named +symbol+
143
144  def find_local_symbol(symbol)
145    find_class_or_module(symbol) || super
146  end
147
148  ##
149  # Finds a module or class with +name+
150
151  def find_module_named(name)
152    find_class_or_module(name)
153  end
154
155  ##
156  # Returns the relative name of this file
157
158  def full_name
159    @relative_name
160  end
161
162  ##
163  # An RDoc::TopLevel has the same hash as another with the same
164  # relative_name
165
166  def hash
167    @relative_name.hash
168  end
169
170  ##
171  # URL for this with a +prefix+
172
173  def http_url(prefix)
174    path = [prefix, @relative_name.tr('.', '_')]
175
176    File.join(*path.compact) + '.html'
177  end
178
179  def inspect # :nodoc:
180    "#<%s:0x%x %p modules: %p classes: %p>" % [
181      self.class, object_id,
182      base_name,
183      @modules.map { |n,m| m },
184      @classes.map { |n,c| c }
185    ]
186  end
187
188  ##
189  # Time this file was last modified, if known
190
191  def last_modified
192    @file_stat ? file_stat.mtime : nil
193  end
194
195  ##
196  # Dumps this TopLevel for use by ri.  See also #marshal_load
197
198  def marshal_dump
199    [
200      MARSHAL_VERSION,
201      @relative_name,
202      @parser,
203      parse(@comment),
204    ]
205  end
206
207  ##
208  # Loads this TopLevel from +array+.
209
210  def marshal_load array # :nodoc:
211    initialize array[1]
212
213    @parser  = array[2]
214    @comment = array[3]
215
216    @file_stat          = nil
217  end
218
219  ##
220  # Returns the NormalClass "Object", creating it if not found.
221  #
222  # Records +self+ as a location in "Object".
223
224  def object_class
225    @object_class ||= begin
226      oc = @store.find_class_named('Object') || add_class(RDoc::NormalClass, 'Object')
227      oc.record_location self
228      oc
229    end
230  end
231
232  ##
233  # Base name of this file without the extension
234
235  def page_name
236    basename = File.basename @relative_name
237    basename =~ /\.(rb|rdoc|txt|md)$/i
238
239    $` || basename
240  end
241
242  ##
243  # Path to this file for use with HTML generator output.
244
245  def path
246    http_url @store.rdoc.generator.file_dir
247  end
248
249  def pretty_print q # :nodoc:
250    q.group 2, "[#{self.class}: ", "]" do
251      q.text "base name: #{base_name.inspect}"
252      q.breakable
253
254      items = @modules.map { |n,m| m }
255      items.concat @modules.map { |n,c| c }
256      q.seplist items do |mod| q.pp mod end
257    end
258  end
259
260  ##
261  # Search record used by RDoc::Generator::JsonIndex
262
263  def search_record
264    return unless @parser < RDoc::Parser::Text
265
266    [
267      page_name,
268      '',
269      page_name,
270      '',
271      path,
272      '',
273      snippet(@comment),
274    ]
275  end
276
277  ##
278  # Is this TopLevel from a text file instead of a source code file?
279
280  def text?
281    @parser and @parser.include? RDoc::Parser::Text
282  end
283
284  def to_s # :nodoc:
285    "file #{full_name}"
286  end
287
288end
289
290