1##
2# A Document containing lists, headings, paragraphs, etc.
3
4class RDoc::Markup::Document
5
6  include Enumerable
7
8  ##
9  # The file this document was created from.  See also
10  # RDoc::ClassModule#add_comment
11
12  attr_reader :file
13
14  ##
15  # If a heading is below the given level it will be omitted from the
16  # table_of_contents
17
18  attr_accessor :omit_headings_below
19
20  ##
21  # The parts of the Document
22
23  attr_reader :parts
24
25  ##
26  # Creates a new Document with +parts+
27
28  def initialize *parts
29    @parts = []
30    @parts.concat parts
31
32    @file = nil
33    @omit_headings_from_table_of_contents_below = nil
34  end
35
36  ##
37  # Appends +part+ to the document
38
39  def << part
40    case part
41    when RDoc::Markup::Document then
42      unless part.empty? then
43        parts.concat part.parts
44        parts << RDoc::Markup::BlankLine.new
45      end
46    when String then
47      raise ArgumentError,
48            "expected RDoc::Markup::Document and friends, got String" unless
49        part.empty?
50    else
51      parts << part
52    end
53  end
54
55  def == other # :nodoc:
56    self.class == other.class and
57      @file == other.file and
58      @parts == other.parts
59  end
60
61  ##
62  # Runs this document and all its #items through +visitor+
63
64  def accept visitor
65    visitor.start_accepting
66
67    visitor.accept_document self
68
69    visitor.end_accepting
70  end
71
72  ##
73  # Concatenates the given +parts+ onto the document
74
75  def concat parts
76    self.parts.concat parts
77  end
78
79  ##
80  # Enumerator for the parts of this document
81
82  def each &block
83    @parts.each(&block)
84  end
85
86  ##
87  # Does this document have no parts?
88
89  def empty?
90    @parts.empty? or (@parts.length == 1 and merged? and @parts.first.empty?)
91  end
92
93  ##
94  # The file this Document was created from.
95
96  def file= location
97    @file = case location
98            when RDoc::TopLevel then
99              location.relative_name
100            else
101              location
102            end
103  end
104
105  ##
106  # When this is a collection of documents (#file is not set and this document
107  # contains only other documents as its direct children) #merge replaces
108  # documents in this class with documents from +other+ when the file matches
109  # and adds documents from +other+ when the files do not.
110  #
111  # The information in +other+ is preferred over the receiver
112
113  def merge other
114    if empty? then
115      @parts = other.parts
116      return self
117    end
118
119    other.parts.each do |other_part|
120      self.parts.delete_if do |self_part|
121        self_part.file and self_part.file == other_part.file
122      end
123
124      self.parts << other_part
125    end
126
127    self
128  end
129
130  ##
131  # Does this Document contain other Documents?
132
133  def merged?
134    RDoc::Markup::Document === @parts.first
135  end
136
137  def pretty_print q # :nodoc:
138    start = @file ? "[doc (#{@file}): " : '[doc: '
139
140    q.group 2, start, ']' do
141      q.seplist @parts do |part|
142        q.pp part
143      end
144    end
145  end
146
147  ##
148  # Appends +parts+ to the document
149
150  def push *parts
151    self.parts.concat parts
152  end
153
154  ##
155  # Returns an Array of headings in the document.
156  #
157  # Require 'rdoc/markup/formatter' before calling this method.
158
159  def table_of_contents
160    accept RDoc::Markup::ToTableOfContents.to_toc
161  end
162
163end
164
165