1# :markup: tomdoc
2
3# A parser for TomDoc based on TomDoc 1.0.0-rc1 (02adef9b5a)
4#
5# The TomDoc specification can be found at:
6#
7# http://tomdoc.org
8#
9# The latest version of the TomDoc specification can be found at:
10#
11# https://github.com/mojombo/tomdoc/blob/master/tomdoc.md
12#
13# To choose TomDoc as your only default format see RDoc::Options@Saved+Options
14# for instructions on setting up a <code>.rdoc_options</code> file to store
15# your project default.
16#
17# There are a few differences between this parser and the specification.  A
18# best-effort was made to follow the specification as closely as possible but
19# some choices to deviate were made.
20#
21# A future version of RDoc will warn when a MUST or MUST NOT is violated and
22# may warn when a SHOULD or SHOULD NOT is violated.  RDoc will always try
23# to emit documentation even if given invalid TomDoc.
24#
25# Here are some implementation choices this parser currently makes:
26#
27# This parser allows rdoc-style inline markup but you should not depended on
28# it.
29#
30# This parser allows a space between the comment and the method body.
31#
32# This parser does not require the default value to be described for an
33# optional argument.
34#
35# This parser does not examine the order of sections.  An Examples section may
36# precede the Arguments section.
37#
38# This class is documented in TomDoc format.  Since this is a subclass of the
39# RDoc markup parser there isn't much to see here, unfortunately.
40
41class RDoc::TomDoc < RDoc::Markup::Parser
42
43  # Internal: Token accessor
44
45  attr_reader :tokens
46
47  # Internal: Adds a post-processor which sets the RDoc section based on the
48  # comment's status.
49  #
50  # Returns nothing.
51
52  def self.add_post_processor # :nodoc:
53    RDoc::Markup::PreProcess.post_process do |comment, code_object|
54      next unless code_object and
55                  RDoc::Comment === comment and comment.format == 'tomdoc'
56
57      comment.text.gsub!(/(\A\s*# )(Public|Internal|Deprecated):\s+/) do
58        section = code_object.add_section $2
59        code_object.temporary_section = section
60
61        $1
62      end
63    end
64  end
65
66  add_post_processor
67
68  # Public: Parses TomDoc from text
69  #
70  # text - A String containing TomDoc-format text.
71  #
72  # Examples
73  #
74  #   RDoc::TomDoc.parse <<-TOMDOC
75  #   This method does some things
76  #
77  #   Returns nothing.
78  #   TOMDOC
79  #   # => #<RDoc::Markup::Document:0xXXX @parts=[...], @file=nil>
80  #
81  # Returns an RDoc::Markup::Document representing the TomDoc format.
82
83  def self.parse text
84    parser = new
85
86    parser.tokenize text
87    doc = RDoc::Markup::Document.new
88    parser.parse doc
89    doc
90  end
91
92  # Internal: Extracts the Signature section's method signature
93  #
94  # comment - An RDoc::Comment that will be parsed and have the signature
95  #           extracted
96  #
97  # Returns a String containing the signature and nil if not
98
99  def self.signature comment
100    return unless comment.tomdoc?
101
102    document = comment.parse
103
104    signature = nil
105    found_heading = false
106    found_signature = false
107
108    document.parts.delete_if do |part|
109      next false if found_signature
110
111      found_heading ||=
112        RDoc::Markup::Heading === part && part.text == 'Signature'
113
114      next false unless found_heading
115
116      next true if RDoc::Markup::BlankLine === part
117
118      if RDoc::Markup::Verbatim === part then
119        signature = part
120        found_signature = true
121      end
122    end
123
124    signature and signature.text
125  end
126
127  # Public: Creates a new TomDoc parser.  See also RDoc::Markup::parse
128
129  def initialize
130    super
131
132    @section = nil
133  end
134
135  # Internal: Builds a heading from the token stream
136  #
137  # level - The level of heading to create
138  #
139  # Returns an RDoc::Markup::Heading
140
141  def build_heading level
142    heading = super
143
144    @section = heading.text
145
146    heading
147  end
148
149  # Internal: Builds a verbatim from the token stream.  A verbatim in the
150  # Examples section will be marked as in ruby format.
151  #
152  # margin - The indentation from the margin for lines that belong to this
153  #          verbatim section.
154  #
155  # Returns an RDoc::Markup::Verbatim
156
157  def build_verbatim margin
158    verbatim = super
159
160    verbatim.format = :ruby if @section == 'Examples'
161
162    verbatim
163  end
164
165  # Internal: Builds a paragraph from the token stream
166  #
167  # margin - Unused
168  #
169  # Returns an RDoc::Markup::Paragraph.
170
171  def build_paragraph margin
172    p :paragraph_start => margin if @debug
173
174    paragraph = RDoc::Markup::Paragraph.new
175
176    until @tokens.empty? do
177      type, data, = get
178
179      if type == :TEXT then
180        paragraph << data
181        skip :NEWLINE
182      else
183        unget
184        break
185      end
186    end
187
188    p :paragraph_end => margin if @debug
189
190    paragraph
191  end
192
193  # Internal: Turns text into an Array of tokens
194  #
195  # text - A String containing TomDoc-format text.
196  #
197  # Returns self.
198
199  def tokenize text
200    text.sub!(/\A(Public|Internal|Deprecated):\s+/, '')
201
202    setup_scanner text
203
204    until @s.eos? do
205      pos = @s.pos
206
207      # leading spaces will be reflected by the column of the next token
208      # the only thing we loose are trailing spaces at the end of the file
209      next if @s.scan(/ +/)
210
211      @tokens << case
212                 when @s.scan(/\r?\n/) then
213                   token = [:NEWLINE, @s.matched, *token_pos(pos)]
214                   @line_pos = char_pos @s.pos
215                   @line += 1
216                   token
217                 when @s.scan(/(Examples|Signature)$/) then
218                   @tokens << [:HEADER, 3, *token_pos(pos)]
219
220                   [:TEXT, @s[1], *token_pos(pos)]
221                 when @s.scan(/([:\w][\w\[\]]*)[ ]+- /) then
222                   [:NOTE, @s[1], *token_pos(pos)]
223                 else
224                   @s.scan(/.*/)
225                   [:TEXT, @s.matched.sub(/\r$/, ''), *token_pos(pos)]
226                 end
227    end
228
229    self
230  end
231
232end
233
234