1##
2# A section of documentation like:
3#
4#   # :section: The title
5#   # The body
6#
7# Sections can be referenced multiple times and will be collapsed into a
8# single section.
9
10class RDoc::Context::Section
11
12  include RDoc::Text
13
14  MARSHAL_VERSION = 0 # :nodoc:
15
16  ##
17  # Section comment
18
19  attr_reader :comment
20
21  ##
22  # Section comments
23
24  attr_reader :comments
25
26  ##
27  # Context this Section lives in
28
29  attr_reader :parent
30
31  ##
32  # Section title
33
34  attr_reader :title
35
36  @@sequence = "SEC00000"
37
38  ##
39  # Creates a new section with +title+ and +comment+
40
41  def initialize parent, title, comment
42    @parent = parent
43    @title = title ? title.strip : title
44
45    @@sequence.succ!
46    @sequence = @@sequence.dup
47
48    @comments = []
49
50    add_comment comment
51  end
52
53  ##
54  # Sections are equal when they have the same #title
55
56  def == other
57    self.class === other and @title == other.title
58  end
59
60  ##
61  # Adds +comment+ to this section
62
63  def add_comment comment
64    comment = extract_comment comment
65
66    return if comment.empty?
67
68    case comment
69    when RDoc::Comment then
70      @comments << comment
71    when RDoc::Markup::Document then
72      @comments.concat comment.parts
73    when Array then
74      @comments.concat comment
75    else
76      raise TypeError, "unknown comment type: #{comment.inspect}"
77    end
78  end
79
80  ##
81  # Anchor reference for linking to this section
82
83  def aref
84    title = @title || '[untitled]'
85
86    CGI.escape(title).gsub('%', '-').sub(/^-/, '')
87  end
88
89  ##
90  # Extracts the comment for this section from the original comment block.
91  # If the first line contains :section:, strip it and use the rest.
92  # Otherwise remove lines up to the line containing :section:, and look
93  # for those lines again at the end and remove them. This lets us write
94  #
95  #   # :section: The title
96  #   # The body
97
98  def extract_comment comment
99    case comment
100    when Array then
101      comment.map do |c|
102        extract_comment c
103      end
104    when nil
105      RDoc::Comment.new ''
106    when RDoc::Comment then
107      if comment.text =~ /^#[ \t]*:section:.*\n/ then
108        start = $`
109        rest = $'
110
111        comment.text = if start.empty? then
112                         rest
113                       else
114                         rest.sub(/#{start.chomp}\Z/, '')
115                       end
116      end
117
118      comment
119    when RDoc::Markup::Document then
120      comment
121    else
122      raise TypeError, "unknown comment #{comment.inspect}"
123    end
124  end
125
126  def inspect # :nodoc:
127    "#<%s:0x%x %p>" % [self.class, object_id, title]
128  end
129
130  ##
131  # The files comments in this section come from
132
133  def in_files
134    return [] if @comments.empty?
135
136    case @comments
137    when Array then
138      @comments.map do |comment|
139        comment.file
140      end
141    when RDoc::Markup::Document then
142      @comment.parts.map do |document|
143        document.file
144      end
145    else
146      raise RDoc::Error, "BUG: unknown comment class #{@comments.class}"
147    end
148  end
149
150  ##
151  # Serializes this Section.  The title and parsed comment are saved, but not
152  # the section parent which must be restored manually.
153
154  def marshal_dump
155    [
156      MARSHAL_VERSION,
157      @title,
158      parse,
159    ]
160  end
161
162  ##
163  # De-serializes this Section.  The section parent must be restored manually.
164
165  def marshal_load array
166    @parent  = nil
167
168    @title    = array[1]
169    @comments = array[2]
170  end
171
172  ##
173  # Parses +comment_location+ into an RDoc::Markup::Document composed of
174  # multiple RDoc::Markup::Documents with their file set.
175
176  def parse
177    case @comments
178    when String then
179      super
180    when Array then
181      docs = @comments.map do |comment, location|
182        doc = super comment
183        doc.file = location if location
184        doc
185      end
186
187      RDoc::Markup::Document.new(*docs)
188    when RDoc::Comment then
189      doc = super @comments.text, comments.format
190      doc.file = @comments.location
191      doc
192    when RDoc::Markup::Document then
193      return @comments
194    else
195      raise ArgumentError, "unknown comment class #{comments.class}"
196    end
197  end
198
199  ##
200  # The section's title, or 'Top Section' if the title is nil.
201  #
202  # This is used by the table of contents template so the name is silly.
203
204  def plain_html
205    @title || 'Top Section'
206  end
207
208  ##
209  # Removes a comment from this section if it is from the same file as
210  # +comment+
211
212  def remove_comment comment
213    return if @comments.empty?
214
215    case @comments
216    when Array then
217      @comments.delete_if do |my_comment|
218        my_comment.file == comment.file
219      end
220    when RDoc::Markup::Document then
221      @comments.parts.delete_if do |document|
222        document.file == comment.file.name
223      end
224    else
225      raise RDoc::Error, "BUG: unknown comment class #{@comments.class}"
226    end
227  end
228
229  ##
230  # Section sequence number (deprecated)
231
232  def sequence
233    warn "RDoc::Context::Section#sequence is deprecated, use #aref"
234    @sequence
235  end
236
237end
238
239